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

时间:2018-06-23 02:26:24

标签: c# wpf wpf-controls

我有一个自定义控件(MediaPlayer),其中包含2个其他自定义控件,一个媒体播放器(主机)和一个控件栏(UI)。

此控件本身非常简单,只需将两者绑定在一起即可显示。

现在我遇到的第一个问题是无法从MediaPlayer设置Host或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}}" />

现在的问题是我想将Script和Path属性公开为依赖项属性,以便可以对它们进行数据绑定。我不能采用与上述完全相同的方法,那么我该怎么做?路径和脚本将绑定到的主机是在运行时在OnApplyTemplate中创建的。

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

我猜一个选择是复制基本样式模板而不是从其继承,我可以在那里而不是在运行时启动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>

在PART_MediaUI中添加x:FieldModifier =“ public”会引发“名称空间中不存在FieldModifier属性”

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

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 个答案:

答案 0 :(得分:0)

我在上面对您如何使用DependencyProperty并将其设置为类型等发表了评论。这虽然很好,但是对于您所需要的来说可能会过高。只需使用x:FieldModifier="public"即可获得所需的内容。

这是一个例子:

我制作了3个用户控件,并拥有我的MainWindow。用户控件为MainControlSubControlASubControlB

MainControl中,我首先给控件一个逻辑名称,然后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>

然后我将那个MainControl放在我的MainWindow中,并像这样使用它:

<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>

希望这会有所帮助。这个想法是,您还可以从DependencyProperty等控件(或问题中使用的任何控件)中引用Visibility

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


好吧,接下来从您的评论/问题中回答问题,让我更深入地解释它的工作原理。首先,SubControlASubControlB只是我用来进行示例工作的两个空白UserControls

xaml中,括号之间的所有内容都将在此时进行初始化。我们使用名称空间/类型名称作为属性的目标,方括号之间的内容将传递给该属性的设置器。

考虑这个MainWindow ...我要做的就是在其中放置一个自定义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>

在这个ExampleControl中,我有两个相同的按钮,除了一个说按钮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具有内置的解析器,该解析器接收您发送的字符串,并尝试将其初始化为该属性所需的类型。看看这对枚举(Visibility:System.Windows.Visibility),基元(Height:System.Double)和独特对象(例如Background:System.Windows.Media.Brush)的工作方式。

还要注意,BackgroundBrush类型,可以是从Brush派生的任何类型。在示例中,我使用一个以SolidColorBrush为底的Brush

但是;在Background中,我还将更进一步。请注意,不仅分配SolidColorBrush,而且分配Color的{​​{1}}属性。

花些时间来了解SolidColorBrush是如何解析和使用这些功能的,我相信它将回答您有关我如何从{中引用xamlSubControlA的问题{1}}开头。

答案 1 :(得分:0)

我找到了部分解决方案。我不是从Control继承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'”

UI作为这样的依赖项属性公开。设计器允许设置UI =“ ...”,但不能设置UI.IsVolumeVisible =“ false”,也不能识别。有没有办法在自定义控件中公开它?

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); }