简体   繁体   English

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

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

As title states, I can't get KeyBinding to work when using property element syntax. 正如标题所述,我在使用属性元素语法时无法使KeyBinding起作用。 By work I mean using the key combo of Ctrl+Del to change the background color of the list box. 通过工作我的意思是使用Ctrl+Del组合键来更改列表框的背景颜色。 The key combo can be used or the button can be clicked, both of which invoke the command, yet the command is never invoked. 可以使用键组合或单击按钮,这两个按钮都会调用该命令,但从不调用该命令。 When a breakpoint is set while in debug mode it will never be encountered. 在调试模式下设置断点时,永远不会遇到断点。

I've followed the InputBinding Class example from the documentation and can only get KeyBinding to work when using a UserControl and would like to understand why that is, and what I'm doing wrong. 我已经遵循文档中的InputBinding类示例 ,并且只能在使用UserControl时使KeyBinding工作,并且想要理解为什么会这样,以及我做错了什么

Below is an MVCE of when the code, declared with property element syntax, that does not work. 下面是一个MVCE,用于表示使用属性元素语法声明的代码不起作用。 Commented out is a line for a UserControl which encapsulates the StackPanel and allows the KeyBinding to work. 注释掉是UserControl的一行,它封装了StackPanel并允许KeyBinding工作。 Contingent on the commenting out each PropertyElementSyntax region and uncommenting each UserControlSyntax region in the code behind for MainWindow.xaml.cs . 依赖于注释掉每个PropertyElementSyntax区域并取消注释MainWindow.xaml.cs后面代码中的每个UserControlSyntax区域。

MainWindow.xaml: 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 for MainWindow.xaml.cs: 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);
}

The problem is that in the no- UserControl scenario, the DataContext is set before the command object has been initialized. 问题是在no- UserControl场景中, DataContext是在初始化命令对象之前设置的。

WPF has a robust binding system, but it normally relies on property-change notifications, via INotifyPropertyChanged . WPF有一个健壮的绑定系统,但它通常依赖于属性更改通知,通过INotifyPropertyChanged Some scenarios will work without that, as long as you get the order of operations correct. 只要您获得正确的操作顺序,某些方案就可以在没有它的情况下工作。 But, without property-change notifications, if you miss your window of opportunity to present some property value to WPF, it's not going to try again later. 但是,如果没有属性更改通知,如果您错过了向WPF提供某些属性值的机会窗口,那么以后就不会再尝试了。

When you use the UserControl , the initialization of the bindings for the UserControl occurs after you set up the ChangeColorCommand property. 使用UserControl时,在设置ChangeColorCommand属性后,将初始化UserControl的绑定。 This is just an artifact of how WPF initializes the various objects in the UI tree. 这只是WPF如何初始化UI树中各种对象的工件。 But it means that by the time the UserControl 's bindings look at the ChangeColorCommand property, it has the value you want. 但这意味着,当UserControl的绑定查看ChangeColorCommand属性时,它具有您想要的值。

On the other hand, when you put the StackPanel explicitly into the window's XAML, it's too late by the time you set the property for WPF to see it. 另一方面,当您将StackPanel显式放入窗口的XAML时,在设置WPF的属性以查看它时为时已太晚。 It already resolved those bindings during the InitializeComponent() call. 它已在InitializeComponent()调用期间解析了这些绑定。 Setting the property later has no effect. 稍后设置属性无效。

There are a couple of ways you could address that given the code you have now: 根据您现有的代码,有几种方法可以解决这个问题:

  1. The simplest is to just move the assignment of DataContext = this; 最简单的是只移动DataContext = this;的赋值DataContext = this; to after the call to InitializeCommand() . 调用InitializeCommand() Updating the DataContext requires WPF to update all of the dependent bindings too, so doing that after the InitializeCommand() call ensures the property has the value you want. 更新DataContext需要WPF更新所有依赖绑定,因此在InitializeCommand()调用之后执行此操作可确保该属性具有所需的值。
  2. Implement INotifyPropertyChanged in the MainWindow class, and raise the PropertyChanged event for the ChangeColorCommand property when you set it. MainWindow类中实现INotifyPropertyChanged ,并在设置时为ChangeColorCommand属性引发PropertyChanged事件。 This will let WPF know that the value changed and that it should re-evaluate any bindings that depended on it. 这将让WPF知道值已更改,并且应该重新评估依赖于它的任何绑定。

All that said, I'd go one further: 所有这些,我再往前走一步:

  1. Implement a proper view model object, with INotifyPropertyChanged and a ChangeColorCommand , and use that as the data context. 使用INotifyPropertyChangedChangeColorCommand实现适当的视图模型对象,并将用作数据上下文。 Making your UI objects do double-duty as both UI and property binding source (ie the view model's job) doesn't fit with the normal WPF model, sacrifices the benefits that MVVM would normally provide, and of course introduces this kind of weird timing thing where it's not obvious why a property binding isn't working as expected. 使UI对象具有双重功能,因为UI和属性绑定源(即视图模型的工作)不适合普通的WPF模型,牺牲了MVVM通常会提供的好处,当然还会引入这种奇怪的时机不清楚为什么属性绑定没有按预期工作的事情。


Okay, technically there's a fourth approach you could take, which is to put the call to InitializeCommand() before InitializeComponent() . 好的,从技术上讲,你可以采用第四种方法,即在InitializeCommand()之前调用InitializeComponent() Main problem with that is, at the moment, it relies on retrieving directly the value of a UI object's property, and that UI object won't exist until after InitializeComponent() is called. 主要问题是,目前它依赖于直接检索UI对象属性的值,并且该UI对象在调用InitializeComponent()之后才会存在。

Which brings me back to the #3 option above. 这让我回到了上面的#3选项。 Fact is, you shouldn't be accessing UI object properties directly. 事实上,您不应该直接访问UI对象属性。 That should be another property in your view model, and you should make a more direct choice about what that initial color should be, than just grabbing it from the UI on startup. 这应该是视图模型中的另一个属性,您应该更直接地选择初始颜色应该是什么,而不仅仅是在启动时从UI中抓取它。

I admit, there's some wiggle room for design here, but you should be trying to keep your view model and UI code as divorced from each other as possible. 我承认,这里有一些摆设空间,但你应该尽量保持你的视图模型和UI代码彼此脱节。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM