KeyBinding 用作 UserControl,但在 XAML 中使用 属性 元素语法时不起作用

KeyBinding works as UserControl but not when pusing property element syntax in XAML

如标题所述,在使用 属性 元素语法时我无法使 KeyBinding 工作。我所说的工作是指使用 Ctrl+Del 组合键来更改列表框的背景颜色。可以使用组合键或单击按钮,两者都会调用命令,但永远不会调用命令。在调试模式下设置断点将永远不会遇到。

我已经按照文档中的 InputBinding Class example 进行操作,并且只能在使用 UserControl 时使 KeyBinding 工作,并且想了解 为什么 也就是说,什么 我做错了。

下面是代码的 MVCE,使用 属性 元素语法声明,但不起作用。注释掉的是 UserControl 的一行,它封装了 StackPanel 并允许 KeyBinding 工作。取决于在 MainWindow.xaml.cs.

后面的代码中注释掉每个 PropertyElementSyntax 区域并取消注释每个 UserControlSyntax 区域

MainWindow.xaml:

<Window x:Class="LearningKeyBindingWPFApp.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:LearningKeyBindingWPFApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="300">
    <!--<local:UserControl1 x:Name="CustomColorPicker" />-->
    <StackPanel Margin="0,40,0,0">
        <StackPanel.InputBindings>
            <KeyBinding Command="{Binding ChangeColorCommand}"
                        CommandParameter="{Binding ElementName=ColorPicker, Path=SelectedItem}"
                        Key="{Binding ChangeColorCommand.Key}"
                        Modifiers="{Binding ChangeColorCommand.ModifierKeys}" />
            <MouseBinding Command="{Binding ChangeColorCommand}"
                          CommandParameter="{Binding ElementName=ColorPicker, Path=SelectedItem}"
                          MouseAction="{Binding ChangeColorCommand.MouseAction}" />
        </StackPanel.InputBindings>
        <Button Content="Change Color" 
                Command="{Binding ChangeColorCommand}"
                CommandParameter="{Binding ElementName=ColorPicker, Path=SelectedItem}" />
        <ListBox Name="ColorPicker"
                 xmlns:sys="clr-namespace:System;assembly=mscorlib"
                 SelectedIndex="0">
            <sys:String>Red</sys:String>
            <sys:String>Green</sys:String>
            <sys:String>Blue</sys:String>
            <sys:String>Yellow</sys:String>
            <sys:String>Orange</sys:String>
            <sys:String>Purple</sys:String>
        </ListBox>
    </StackPanel>
</Window>

Code-behind 对于 MainWindow.xaml.cs:

public MainWindow()
{
    DataContext = this;

    InitializeComponent();
    InitializeCommand();

    #region UserControlSyntax
    //CustomColorPicker.ColorPicker.Focus();
    #endregion

    #region PropertyElementSyntax
    ColorPicker.Focus();
    #endregion
}

public SimpleDelegateCommand ChangeColorCommand { get; private set; }

private SolidColorBrush _originalColor;

private void InitializeCommand()
{
    #region UserControlSyntax
    //_originalColor = (SolidColorBrush)CustomColorPicker.ColorPicker.Background;
    #endregion

    #region PropertyElementSyntax
    _originalColor = (SolidColorBrush)ColorPicker.Background;
    #endregion

    ChangeColorCommand = new SimpleDelegateCommand(ChangeColor)
    {
        Key = Key.Delete,
        ModifierKeys = ModifierKeys.Control
    };
}

private void ChangeColor(object colorString)
{
    if (colorString == null)
    {
        return;
    }

    var selectedColor = SelectedColor((string)colorString);

    #region UserControlSyntax
    //if (CustomColorPicker.ColorPicker.Background == null)
    //{
    //    CustomColorPicker.ColorPicker.Background = selectedColor;
    //    return;
    //}

    //CustomColorPicker.ColorPicker.Background = ((SolidColorBrush)CustomColorPicker.ColorPicker.Background).Color == selectedColor.Color
    //        ? _originalColor
    //        : selectedColor;
    #endregion

    #region PropertyElementSyntax
    if (ColorPicker.Background == null)
    {
        ColorPicker.Background = selectedColor;
        return;
    }

    var isColorIdentical = ((SolidColorBrush)ColorPicker.Background).Color == selectedColor.Color;
    ColorPicker.Background = isColorIdentical
            ? _originalColor
            : selectedColor;
    #endregion
}

private SolidColorBrush SelectedColor(string value)
{
    #region UserControlSyntax
    //var selectedColor = (Color)ColorConverter.ConvertFromString(value);
    #endregion

    #region PropertyElementSyntax
    var selectedColor = (Color)ColorConverter.ConvertFromString((string)ColorPicker.SelectedItem);
    #endregion

    return new SolidColorBrush(selectedColor);
}

问题是在没有-UserControl的情况下,DataContext是在命令对象初始化之前设置的。

WPF 有一个强大的绑定系统,但它通常依赖于 property-change 通知,通过 INotifyPropertyChanged。只要您获得正确的操作顺序,某些场景在没有它的情况下也可以工作。但是,如果没有 property-change 通知,如果您错过了向 WPF 提供一些 属性 价值的 window 机会,它不会稍后再试。

当您使用 UserControl 时,UserControl 的绑定初始化发生在您设置 ChangeColorCommand 属性 之后。这只是 WPF 如何初始化 UI 树中的各种对象的产物。但这意味着当 UserControl 的绑定查看 ChangeColorCommand 属性 时,它具有您想要的值。

另一方面,当您将 StackPanel 显式放入 window 的 XAML 时,您为 属性 设置时为时已晚WPF 看看吧。它已经在 InitializeComponent() 调用期间解析了这些绑定。稍后设置 属性 无效。

根据您现在拥有的代码,您可以通过多种方式解决该问题:

  1. 最简单的方法是在 调用 InitializeCommand() 之后将 DataContext = this; 的赋值移动到 。更新 DataContext 也需要 WPF 更新所有依赖绑定,因此在 InitializeCommand() 调用之后执行此操作可确保 属性 具有您想要的值。
  2. MainWindow class 中实现 INotifyPropertyChanged,并在设置时为 ChangeColorCommand 属性 引发 PropertyChanged 事件。这将让 WPF 知道该值已更改,并且它应该 re-evaluate 任何依赖于它的绑定。

综上所述,我会更进一步:

  1. 使用 INotifyPropertyChangedChangeColorCommand 实现正确的视图模型对象,并使用 that 作为数据上下文。让你的 UI 对象做 double-duty 因为 UI 和 属性 绑定源(即视图模型的工作)不适合普通的 WPF 模型,牺牲了好处MVVM 通常会提供,当然也会引入这种奇怪的计时问题,其中 属性 绑定未按预期工作的原因并不明显。


好的,从技术上讲,您可以采用第四种方法,即在 InitializeComponent() 之前调用 InitializeCommand()。主要问题是,目前,它依赖于直接检索 UI 对象的 属性 的值,而 UI 对象在 InitializeComponent() 之后才会存在被调用。

这让我回到上面的 #3 选项。事实上,您不应该直接访问 UI 对象属性。那应该是你的视图模型中的另一个 属性,你应该更直接地选择初始颜色应该是什么,而不是在启动时从 UI 中获取它。

我承认,这里的设计有一些回旋余地,但您应该尽量让视图模型和 UI 代码彼此分离。