【问题标题】:Read-only Dependency Property updates but does not work on first use只读依赖属性更新但在首次使用时不起作用
【发布时间】:2018-12-21 15:37:50
【问题描述】:

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

对于这个问题,我用两个 DependencyProperties 制作了一个非常简单的用户控件。一个基于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。如果我运行应用程序,使用 Live 编辑器将其切换到 Style2,然后再切换回 Style1,WingDings 字体会接管,但在重新运行时不会。这绝对看起来像是一个依赖属性问题,但据我所知,我的设置是正确的,尤其是因为其他两个控件似乎可以工作。

【问题讨论】:

  • 根据依赖属性命名约定,ReadOnlyStyle属性的标识符字段应该命名为ReadOnlyStylePropertyKey,而不是ReadOnlyIntPropertyKey
  • 除此之外,直接从 XAML 中 UserControl 的 ControlTemplate 中的 DataTrigger 内的 Setter 中的 Style 资源分配标签样式可能要简单得多。不需要 ReadOnlyStyle 属性。
  • @Clemens 您在另一个实验中发现复制粘贴失败。我做了更正。谢谢!那只是一个命名问题,所以它显然仍然不起作用。我当然没有嫁给 ReadOnlyProperty。这是我发现实际上(几乎)为我想要完成的事情工作的第一件事。我会调查 DataTriggers。

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


【解决方案1】:

诊断

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

解决方案

为了解决这个问题,您应该分配初始值ReadOnlyStyle。但是由于样式保存在资源字典中,因此您不能在构造函数中执行此操作。分配初始值的一个好点是:

protected override void OnInitialized(EventArgs e)
{
    base.OnInitialized(e);
    var style = (Style)userControl1.FindResource("SampleStyle-Font1");
    SetValue(ReadOnlyStylePropertyKey, style);
}

更好的解决方案

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

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

然后修改你的模板:

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

这里有两件重要的事情:

  1. Label 需要有x:Name 以便可以在Setter.TargetName 中引用
  2. Trigger.Property 值需要完全限定,因为ControlTemplate 没有设置TargetType

【讨论】:

  • 您的“WPF 方式”解决方案正是我所寻找的。我知道我构建它的方式不是最好的方式,但它是唯一实际上是半工作的解决方案。我实际上有一个使用触发器构建的解决方案,但我的实现不正确。你填补了空白,所以谢谢你。
猜你喜欢
  • 2010-12-17
  • 1970-01-01
  • 1970-01-01
  • 2013-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-21
  • 1970-01-01
相关资源
最近更新 更多