TemplateBinding 不适用于 WPF 自定义控件中的 INotifyPropertyChanged

问题描述

我最近 created an IconButton in WPF 作为 CustomControl。它正在为 DependencyProperties 使用 TemplateBinding:

IconButton.cs

public class IconButton : Button
{
  public static readonly DependencyProperty TextProperty;
  public static readonly DependencyProperty MDL2IconCodeProperty;

  public string Text
  {
    get { return (string)GetValue(TextProperty); }
    set { SetValue(TextProperty,value); }
  }


  public string MDL2IconCode
  {
    get { return (string)GetValue(MDL2IconCodeProperty); }
    set { SetValue(MDL2IconCodeProperty,value); }
  }


  static IconButton()
  {
    DefaultStyleKeyProperty.OverrideMetadata(typeof(IconButton),new FrameworkPropertyMetadata(typeof(IconButton)));

    TextProperty = DependencyProperty.Register("Text",typeof(string),typeof(IconButton),new PropertyMetadata("Button text",OnTextChanged));

    MDL2IconCodeProperty = DependencyProperty.Register("MDL2IconCode",new PropertyMetadata("\uf13e",OnIconTextChanged));
  }

  static void OnTextChanged(DependencyObject o,DependencyPropertyChangedEventArgs e)
  {
    var iconButton = o as IconButton;
    if (iconButton == null)
    {
      return;
    }
    string newText = e.NewValue as string;
    iconButton.Text = newText;
  }

  static void OnIconTextChanged(DependencyObject o,DependencyPropertyChangedEventArgs e)
  {
    var iconButton = o as IconButton;
    if (iconButton == null)
    {
      return;
    }

    string newText = e.NewValue as string;
    iconButton.MDL2IconCode = newText;
  }
}

Generic.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:UI.CustomControls">


  <Style targettype="{x:Type local:IconButton}" 
         BasedOn="{StaticResource {x:Type Button}}">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate targettype="{x:Type local:IconButton}">
          <Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
                  Command="{TemplateBinding Command}"
                  CommandParameter="{TemplateBinding CommandParameter}"
                  CommandTarget="{TemplateBinding CommandTarget}">
            <StackPanel>
              <TextBlock HorizontalAlignment="Center"
                         Text="{TemplateBinding MDL2IconCode}"
                         FontFamily="Segoe MDL2 Assets"
                         FontSize="16"
                         x:Name="iconTextBlock"/>
              <TextBlock HorizontalAlignment="Center" 
                         Text="{TemplateBinding Text}"
                         x:Name="textTextBlock"/>
            </StackPanel>

          </Button>

        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>

但它只工作了一半。我很快意识到绑定到 DependencyProperties 仅适用于 XAML 设计器,而不适用于 viewmodel。因此,当我在设计器中设置 Text 属性时,它会起作用。但是从 viewmodel 绑定到它,该属性既不是初始设置也不是在 INotifyPropertyChanged 事件上更新。

因此作为测试,我将 Text 属性的 TemplateBinding 更改为

{Binding RelativeSource={RelativeSource TemplatedParent},Path=Text}

但这没有帮助。

我的代码可能有什么问题?

带有 TemplateBinding 的 WPF 自定义控件是否支持 INotifyPropertyChanged?

解决方法

问题在于您以一种破坏 Text 的方式设置了 MDL2IconCodeBinding 的新值,因此更改不会传播到 UI。

iconButton.Text = newText;
....
iconButton.MDL2IconCode = newText;

正确的方法是使用 SetCurrentValue 方法改变属性的有效值,但现有的触发器、数据绑定和样式将继续工作。

static void OnTextChanged(DependencyObject o,DependencyPropertyChangedEventArgs e)
{
    var iconButton = o as IconButton;
    if (iconButton == null)
    {
        return;
    }
    string newText = e.NewValue as string;
    iconButton.SetCurrentValue(TextProperty,newText);
}

static void OnIconTextChanged(DependencyObject o,DependencyPropertyChangedEventArgs e)
{
    var iconButton = o as IconButton;
    if (iconButton == null)
    {
        return;
    }
    string newText = e.NewValue as string;
    iconButton.SetCurrentValue(MDL2IconCodeProperty,newText);
}

但是如果您在 OnTextChangedOnIconTextChanged 中没有任何特殊逻辑,那么您可以去掉 PropertyChangedCallbacks 并且它仍然可以工作。

TextProperty = DependencyProperty.Register("Text",typeof(string),typeof(IconButton),new PropertyMetadata("Button text"));

MDL2IconCodeProperty = DependencyProperty.Register("MDL2IconCode",new PropertyMetadata("\uf13e"));