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

TemplateBinding not working on INotifyPropertyChanged in WPF custom control

我最近 作为 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",
                                                       typeof(string),
                                                       typeof(IconButton),
                                                       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 吗?

问题是您设置新值 TextMDL2IconCode 的方式打破了 Binding,因此更改不会传播到 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",
                                                           typeof(string),
                                                           typeof(IconButton),
                                                           new PropertyMetadata("\uf13e"));