簡體   English   中英

自定義控件和依賴項屬性繼承

[英]Custom Control and Dependency Property Inheritance

我有一個自定義控件(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類。 還有其他選擇嗎?

編輯: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會引發“名稱空間中不存在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;
    }
}

上面我評論了如何使用DependencyProperty並將其設置為此類,等等。這雖然很好,但是對於您所需要的來說卻過於矯kill過正。 只需使用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>

希望這可以幫助。 這個想法是,您還可以從諸如Visibility等(或您在問題中使用的任何控件)的那些控件中引用DependencyProperty

這只是一個示例,因為我不建議這樣做那么便宜。


好的,下面繼續您的評論/問題的答案,讓我更深入地解釋它的工作原理。 首先, 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>

…運行時看起來像這樣

在此處輸入圖片說明


現在來看自定義的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)。

還要注意,作為Brush類型的Background可以是從Brush派生的任何類型。 在示例中,我使用SolidColorBrush ,它的基部為Brush

然而; 我還將在Background更進一步。 請注意,我不僅分配SolidColorBrush但我指定Color的財產SolidColorBrush為好。

請花些時間來了解xaml如何解析和使用這些功能,並且我相信它將在此答案的開頭回答您有關如何從MainControl引用SubControlASubControlB的問題。

我找到了部分解決方案。 我不是從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”,也不能識別<local:UI>。 有沒有辦法從自定義控件中公開它?

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM