代码之家  ›  专栏  ›  技术社区  ›  Etienne Charland

自定义控件和依赖项属性继承

  •  0
  • Etienne Charland  · 技术社区  · 6 年前

    我有一个自定义控件(media player),它包含另外两个自定义控件,一个媒体播放器(主机)和一个控件栏(ui)。

    这个控件本身非常简单,它只是将两者绑定在一起以供显示。

    现在我遇到的第一个问题是无法从mediaplayer设置主机或ui属性,所以我复制了设计时所有相关的属性,并通过绑定将它们链接起来。这是实现这一目标的权利吗?这有点笨重,但很管用。

    <Style TargetType="{x:Type local:MediaPlayerWpf}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:MediaPlayerWpf}">
                    <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid>
                            <Grid x:Name="PART_HostGrid" Margin="0,0,0,46">
                                <!--Filled by SetPlayerHost -->
                            </Grid>
                            <local:PlayerControls x:Name="PART_MediaUI" Height="46" Width="Auto"
                                    VerticalAlignment="Bottom" MouseFullscreen="{TemplateBinding MouseFullscreen}"
                                    MousePause="{TemplateBinding MousePause}"
                                    IsPlayPauseVisible="{Binding IsPlayPauseVisible, RelativeSource={RelativeSource TemplatedParent}}"
                                    IsStopVisible="{TemplateBinding IsStopVisible}"
                                    IsLoopVisible="{TemplateBinding IsLoopVisible}"
                                    IsVolumeVisible="{TemplateBinding IsVolumeVisible}"
                                    IsSpeedVisible="{TemplateBinding IsSpeedVisible}"
                                    IsSeekBarVisible="{TemplateBinding IsSeekBarVisible}"
                                    PositionDisplay="{TemplateBinding PositionDisplay}" />
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    这是一个通用媒体播放器的类。然后我有另一个来自它的自定义控件,它设置为使用特定的媒体播放器。(一个使用MPV视频播放器,另一个显示VapourSynth脚本输出)

    派生类如下所示。

    <Style TargetType="{x:Type local:VsMediaPlayer}" BasedOn="{StaticResource {x:Type ui:MediaPlayerWpf}}" />
    

    现在的问题是,我想将脚本和路径属性公开为依赖属性,以便可以对它们进行数据绑定。我不能采取和上面完全一样的方法,那我该怎么做呢?路径和脚本将绑定到的主机是在运行时在onapplytemplate中创建的。

    我对如何使这一个工作有点困惑,我不确定上面的第一个代码是否是最好的解决方案。谢谢你的指导。

    我想有一个选择是复制基样式模板而不是从它继承,我可以在那里初始化宿主类,而不是在运行时。还有别的选择吗?

    edit:host属性在我的通用mediaplayer类中是这样声明的,但是我无法从设计器中找到设置其子属性(host.source)的方法。

    public static DependencyProperty HostProperty = DependencyProperty.Register("Host", typeof(PlayerBase), typeof(MediaPlayerWpf),
        new PropertyMetadata(null, OnHostChanged));
    public PlayerBase Host { get => (PlayerBase)GetValue(HostProperty); set => SetValue(HostProperty, value); }
    private static void OnHostChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
        MediaPlayerWpf P = d as MediaPlayerWpf;
        if (e.OldValue != null)
            P.HostGrid.Children.Remove(e.OldValue as PlayerBase);
        if (e.NewValue != null) {
            P.HostGrid.Children.Add(e.NewValue as PlayerBase);
            P.TemplateUI.PlayerHost = e.NewValue as PlayerBase;
        }
    }
    

    编辑:这是mediaplayer的xaml代码

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:EmergenceGuardian.MediaPlayerUI">
    
        <Style TargetType="{x:Type local:MediaPlayerWpf}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:MediaPlayerWpf}">
                        <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                            <Grid>
                                <ContentPresenter x:Name="PART_HostGrid" Margin="0,0,0,46"
                                        Content="{TemplateBinding Content}" />
                                <local:PlayerControls x:Name="PART_MediaUI" Height="46" Width="Auto"
                                        VerticalAlignment="Bottom" MouseFullscreen="{TemplateBinding MouseFullscreen}"
                                        MousePause="{TemplateBinding MousePause}"
                                        IsPlayPauseVisible="{Binding IsPlayPauseVisible, RelativeSource={RelativeSource TemplatedParent}}"
                                        IsStopVisible="{TemplateBinding IsStopVisible}"
                                        IsLoopVisible="{TemplateBinding IsLoopVisible}"
                                        IsVolumeVisible="{TemplateBinding IsVolumeVisible}"
                                        IsSpeedVisible="{TemplateBinding IsSpeedVisible}"
                                        IsSeekBarVisible="{TemplateBinding IsSeekBarVisible}"
                                        PositionDisplay="{TemplateBinding PositionDisplay}" />
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>
    

    将x:fieldmodifier=“public”添加到part_mediaui throws“命名空间中不存在属性fieldmodifier”

    解决方案!!!在处理了一些附加属性之后,我终于理解了它们是如何工作的,并且附加属性确实是正确的解决方案。这将允许我在父类上设置uiproperties.isvolumevible。我只需要对每个属性重复该代码。

    public static class UIProperties {
        // IsVolumeVisible
        public static readonly DependencyProperty IsVolumeVisibleProperty = DependencyProperty.RegisterAttached("IsVolumeVisible", typeof(bool),
            typeof(UIProperties), new UIPropertyMetadata(false, OnIsVolumeVisibleChanged));
        public static bool GetIsVolumeVisible(DependencyObject obj) => (bool)obj.GetValue(IsVolumeVisibleProperty);
        public static void SetIsVolumeVisible(DependencyObject obj, bool value) => obj.SetValue(IsVolumeVisibleProperty, value);
        private static void OnIsVolumeVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            if (!(d is MediaPlayerWpf P))
                return;
            P.UI.IsVolumeVisible = (bool)e.NewValue;
        }
    }
    
    2 回复  |  直到 6 年前
        1
  •  0
  •   Etienne Charland    6 年前

    我找到了一个部分解。我不是从控件继承mediaplayer,而是从contentcontrol继承。

    在mediaplayer generic.xaml中,我在ui控件的正上方显示这样的内容

    <ContentPresenter x:Name="PART_HostGrid" Margin="0,0,0,46" Content="{TemplateBinding Content}" />
    

    重写属性元数据以确保内容为playerbase类型并将内容引用传递给ui控件

    static MediaPlayerWpf() {
        ContentProperty.OverrideMetadata(typeof(MediaPlayerWpf), new FrameworkPropertyMetadata(ContentChanged, CoerceContent));
    }
    public override void OnApplyTemplate() {
        base.OnApplyTemplate();
        UI = TemplateUI;
        UI.PlayerHost = Content as PlayerBase;
    }
    
    private static void ContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
        MediaPlayerWpf P = d as MediaPlayerWpf;
        if (P.TemplateUI != null)
            P.TemplateUI.PlayerHost = e.NewValue as PlayerBase;
    }
    
    private static object CoerceContent(DependencyObject d, object baseValue) {
        return baseValue as PlayerBase;
    }
    

    然后我就可以这样用了

    <MediaPlayerUI:MediaPlayerWpf x:Name="Player" IsVolumeVisible="False" IsSpeedVisible="False" IsLoopVisible="False" PositionDisplay="Seconds">
        <VapourSynthUI:VsMediaPlayerHost x:Name="PlayerHost" />
    </MediaPlayerUI:MediaPlayerWpf>
    

    优点是我不再需要从mediaplayerWPF继承,因此要管理的控件更少。

    但是,我仍然需要复制ui属性以将它们公开给设计器,还没有找到以任何其他方式访问它们的方法。

    在generic.xaml中设置x:fieldmodifier=“public”将导致“xml命名空间中不存在属性'fieldmodifier'”

    public static DependencyPropertyKey UIPropertyKey = DependencyProperty.RegisterReadOnly("UI", typeof(PlayerControls), typeof(MediaPlayerWpf), new PropertyMetadata());
    public static DependencyProperty UIProperty = UIPropertyKey.DependencyProperty;
    public PlayerControls UI { get => (PlayerControls)GetValue(UIProperty); private set => SetValue(UIPropertyKey, value); }
    
        2
  •  0
  •   Michael Puckett II    6 年前

    我在上面评论了你如何使用 DependencyProperty 把它设置成那种类型等等。这都是好的,但可能是你需要的杀伤力过大。只需使用 x:FieldModifier="public" 得到你想要的。

    下面是一个例子:

    我做了3个用户控件 MainWindow 是的。用户控件是 MainControl , SubControlA 我是说, SubControlB 是的。

    主控 我首先给控件起一个逻辑名称,然后 FieldModifier 对公众。

    <UserControl x:Class="Question_Answer_WPF_App.MainControl"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:Question_Answer_WPF_App"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
        <StackPanel>
            <local:SubControlA x:Name="SubControlA" x:FieldModifier="public"/>
            <local:SubControlB x:Name="SubControlB" x:FieldModifier="public"/>
        </StackPanel>
    </UserControl>
    

    然后我把它放在 主控 在我的 主窗口 像这样使用它:

    <Window x:Class="Question_Answer_WPF_App.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:Question_Answer_WPF_App"
            mc:Ignorable="d"
            Title="MainWindow"
            Height="450"
            Width="800">
    
        <Grid>
            <local:MainControl>
                <local:SubControlA>
                    <TextBlock Text="I'm in SubControlA" />
                </local:SubControlA>
            </local:MainControl>
        </Grid>
    </Window>
    

    希望这有帮助。你也可以参考 关联属性 这些控件中的 Visibility 等等(或者你在问题中使用的任何东西)。

    这只是一个例子,我不建议这么便宜。


    好的,下面我来解释一下你的评论/问题的答案,让我再深入一点。首先, 分角色 分角色 只有两个空的 UserControls 我做了这个例子。

    xaml 方括号之间的任何内容都将在此时初始化。我们使用名称空间/类型名作为属性的目标,括号之间的任何内容都指向该属性的setter。

    想想这个 主窗口 …我所做的就是定个规矩 UserControl 里面看起来像这样 Xaml公司

    <Window x:Class="Question_Answer_WPF_App.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:Question_Answer_WPF_App"
            mc:Ignorable="d"
            Title="MainWindow"
            Height="450"
            Width="800">
    
        <local:ExampleControl />
    
    </Window>
    

    跑步的时候看起来是这样的

    enter image description here


    现在来看看习俗 ExampleControl 因为到目前为止没什么大不了的。

    <UserControl x:Class="Question_Answer_WPF_App.ExampleControl"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                 xmlns:System="clr-namespace:System;assembly=mscorlib"
                 xmlns:Media="clr-namespace:System.Windows.Media;assembly=PresentationCore"
                 xmlns:Windows="clr-namespace:System.Windows;assembly=PresentationCore"
                 mc:Ignorable="d"
                 d:DesignHeight="450"
                 d:DesignWidth="800">
        <StackPanel>
    
            <Button Visibility="Visible"
                    Height="50" 
                    Background="Blue"
                    Content="Button A"/>
    
            <Button>
                <Button.Visibility>
                    <Windows:Visibility> Visible </Windows:Visibility>
                </Button.Visibility>
                <Button.Height>
                    <System:Double> 50 </System:Double>
                </Button.Height>
                <Button.Background>
                    <Media:SolidColorBrush>
                        <Media:SolidColorBrush.Color>
                            <Media:Color>
                                <Media:Color.R> 0 </Media:Color.R>
                                <Media:Color.G> 0 </Media:Color.G>
                                <Media:Color.B> 255 </Media:Color.B>
                                <Media:Color.A> 255 </Media:Color.A>
                            </Media:Color>
                        </Media:SolidColorBrush.Color>
                    </Media:SolidColorBrush>
                </Button.Background>
                <Button.Content> Button B </Button.Content>
            </Button>
    
        </StackPanel>
    </UserControl>
    

    在这个 示例控件 我有两个相同的按钮,除了一个说 按钮A 另一个 按钮B 是的。

    还要注意,我必须包括对某些类型的命名空间的引用,例如:

    xmlns:System="clr-namespace:System;assembly=mscorlib"
    xmlns:Media="clr-namespace:System.Windows.Media;assembly=PresentationCore"
    xmlns:Windows="clr-namespace:System.Windows;assembly=PresentationCore"
    

    XAML

    还要注意 Background 作为一个 Brush 类型可以是派生自 刷子 是的。在这个例子中,我使用 SolidColorBrush 它的基础是 刷子 .

    但是,我也在 背景 是的。注意,我不仅分配 单色画刷 但我指定 Color 财产 单色画刷 也。

    花点时间了解 Xaml公司 正在分析和使用这些特性,我相信它将回答您关于我如何引用的问题 分角色 分角色 从我的 主控 在这个答案的开头。