只读的依赖项属性已更新,但在首次使用时不起作用

时间:2018-12-21 15:37:51

标签: c# wpf mvvm user-controls dependency-properties

我试图创建一些WPF用户控件以包含在库中以与团队共享,但是只读属性对我的工作方式有问题。

对于这个问题,我做了一个非常简单的带有两个DependencyProperty的用户控件。一个基于enum,另一个基于所选的enum执行操作。 enum用于选择按钮将使用的样式。

该应用程序是一个常规Wpf应用程序,以Wpf用户控件库为参考。我怀疑控制库可能可能导致了该问题,因此我认为它与示例有关。

Wpf控制库1

Dictionary1.xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfControlLibrary1">
    <Style x:Key="SampleStyle-Font1">
        <Setter Property="TextElement.FontFamily" Value="Wingdings" />
        <Setter Property="TextElement.FontSize" Value="30" />
    </Style>
    <Style x:Key="SampleStyle-Font2">
        <Setter Property="TextElement.FontFamily" Value="Elephant" />
        <Setter Property="TextElement.FontSize" Value="30" />
    </Style>
    <Style x:Key="SampleStyle-Font3">
        <Setter Property="TextElement.FontFamily" Value="Times New Roman" />
        <Setter Property="TextElement.FontSize" Value="30" />
    </Style>
</ResourceDictionary>

UserControl1.xaml:

<UserControl x:Class="WpfControlLibrary1.UserControl1"
            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:WpfControlLibrary1"
            mc:Ignorable="d"
            d:DesignHeight="100" d:DesignWidth="200">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Dictionary1.xaml"></ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <UserControl.Template>
        <ControlTemplate>
            <Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserControl1}}, Path=Command}">
                <StackPanel>
                    <Label Style="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserControl1}}, Path=ReadOnlyStyle}" Content="{Binding Path=Content, RelativeSource={x:Static RelativeSource.TemplatedParent}}"></Label>
                </StackPanel>
            </Button>
        </ControlTemplate>
    </UserControl.Template>
</UserControl>

UserControl1.xaml.cs

namespace WpfControlLibrary1 {
    using System.Windows;
    using System.Windows.Controls;

    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class UserControl1 : UserControl {
        public enum StyleSelector {
            Style1,
            Style2,
            Style3
        }

        public static DependencyProperty SelectedStyleProperty =
            DependencyProperty.Register("SelectedStyle", typeof(StyleSelector), typeof(UserControl1), new PropertyMetadata(ReadOnlyStyle_Changed));

        private static readonly DependencyPropertyKey ReadOnlyStylePropertyKey =
            DependencyProperty.RegisterReadOnly("ReadOnlyStyle", typeof(Style),
                typeof(UserControl1), null);

        public UserControl1() {
            InitializeComponent();
        }

        public StyleSelector SelectedStyle {
            get => (StyleSelector)GetValue(SelectedStyleProperty);
            set => SetValue(SelectedStyleProperty, value);
        }

        public Style ReadOnlyStyle => (Style)GetValue(ReadOnlyStylePropertyKey.DependencyProperty);

        private static void ReadOnlyStyle_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            if (!(d is UserControl1 userControl1)) {
                return;
            }

            Style style;
            switch (userControl1.SelectedStyle) {
                case StyleSelector.Style1:
                    style = (Style)userControl1.FindResource("SampleStyle-Font1");
                    break;
                case StyleSelector.Style2:
                    style = (Style)userControl1.FindResource("SampleStyle-Font2");
                    break;
                case StyleSelector.Style3:
                    style = (Style)userControl1.FindResource("SampleStyle-Font3");
                    break;
                default:
                    style = (Style)userControl1.FindResource("SampleStyle-Font1");
                    break;
            }

            userControl1.SetValue(ReadOnlyStylePropertyKey, style);
        }
    }
}

Wpf应用程序

MainWindow.xaml:

<Window x:Class="ReadOnlyDependencyPropertiesWithUserControls.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ReadOnlyDependencyPropertiesWithUserControls"
        xmlns:wpfControlLibrary1="clr-namespace:WpfControlLibrary1;assembly=WpfControlLibrary1"
        mc:Ignorable="d"
        Title="Example" Height="200" Width="400">
    <StackPanel>
        <wpfControlLibrary1:UserControl1 SelectedStyle="Style1" Content="This is the first control"></wpfControlLibrary1:UserControl1>
        <wpfControlLibrary1:UserControl1 SelectedStyle="Style2" Content="This is the second control"></wpfControlLibrary1:UserControl1>
        <wpfControlLibrary1:UserControl1 SelectedStyle="Style3" Content="This is the third control"></wpfControlLibrary1:UserControl1>
    </StackPanel>
</Window>

以下输出显示第一个控件未显示Style。如果我运行该应用程序,请使用“实时”编辑器将其切换为Style2,然后再切换回Style1,WingDings字体会接管,但在全新运行时则不会。显然,这似乎是一个依赖项属性问题,但据我所知,我的设置正确,尤其是因为其他两个控件似乎都起作用。

The first control should use the WingDings font, but does not

1 个答案:

答案 0 :(得分:2)

诊断

之所以不起作用,是因为您在ReadOnlyStyle属性更改处理程序中分配了SelectedStyle属性值。由于SelectedStyle的类型为StyleSelector,类型为enum,并且您没有为此属性显式分配默认值,因此它将具有由default(StyleSelector)分配的默认值StyleSelector.Style1。框架,恰好是ReadOnlyStyle。即使您在第一个控件上将该值显式分配给此属性,该值也不会真正改变,因此不会调用处理程序ergo,ergo null仍为Label,ergo您会得到(具有默认样式的ReadOnlyStyle)。

解决方案

为了解决这个问题,您应该分配初始值protected override void OnInitialized(EventArgs e) { base.OnInitialized(e); var style = (Style)userControl1.FindResource("SampleStyle-Font1"); SetValue(ReadOnlyStylePropertyKey, style); } 。但是,由于样式保留在资源字典中,因此您不能在构造函数中这样做。分配初始值的一个好方法是:

public partial class UserControl1 : UserControl
{
    public enum StyleSelector
    {
        Style1,
        Style2,
        Style3
    }

    public static DependencyProperty SelectedStyleProperty =
        DependencyProperty.Register("SelectedStyle", typeof(StyleSelector), typeof(UserControl1));

    public UserControl1()
    {
        InitializeComponent();
    }

    public StyleSelector SelectedStyle
    {
        get => (StyleSelector)GetValue(SelectedStyleProperty);
        set => SetValue(SelectedStyleProperty, value);
    }
}

更好的解决方案

实现目标的“ WPF 方法”是使用触发器。因此,首先您可以从控件中删除不必要的代码:

<ControlTemplate>
    <Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserControl1}}, Path=Command}">
        <StackPanel>
            <Label x:Name="PART_Label" Content="{Binding Path=Content, RelativeSource={x:Static RelativeSource.TemplatedParent}}" />
        </StackPanel>
    </Button>
    <ControlTemplate.Triggers>
        <Trigger Property="local:UserControl1.SelectedStyle" Value="Style1">
            <Setter TargetName="PART_Label" Property="Style" Value="{StaticResource SampleStyle-Font1}" />
        </Trigger>
        <Trigger Property="local:UserControl1.SelectedStyle" Value="Style2">
            <Setter TargetName="PART_Label" Property="Style" Value="{StaticResource SampleStyle-Font2}" />
        </Trigger>
        <Trigger Property="local:UserControl1.SelectedStyle" Value="Style3">
            <Setter TargetName="PART_Label" Property="Style" Value="{StaticResource SampleStyle-Font3}" />
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

然后修改您的temlpate:

Label

这里有两个重要的事情:

  1. x:Name必须具有Setter.TargetName,以便可以在Trigger.Property中引用它
  2. ControlTemplate的值必须完全合格,因为TargetType没有设置TextBox