KeyBinding with Command Binding dont work with Tex

2019-07-04 16:05发布

问题:

I'm using MVVM and have the following problem. My TextBox.Text is bound with UpdateSourceTrigger=LostFocus (thats what the user want). I have a Button with a SaveCommand CommandBinding - this works. Now i have a KeyBinding with Strg+S wich also execute the SaveCommand. And here is the problem: when i m in the Textbox and press Strg+s, the changes are not in the viewmodel.

is there any way to get MVVM Commands with KeyBinding and TextBox UpdateSourceTrigger=LostFocus working together?

some code to check out the problem

<Window>
<Window.InputBindings>
    <KeyBinding Key="S" Modifiers="Control" Command="{Binding SaveCommand}"></KeyBinding>
</Window.InputBindings>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>      
    <TextBox Grid.Row="0" Text="{Binding MyText1, UpdateSourceTrigger=LostFocus}" Width="100"></TextBox>
    <Button Grid.Row="1" Content="_Save" Command="{Binding SaveCommand}" IsDefault="True"></Button> 
</Grid>
</Window>

public partial class MainWindow : Window
{
    private Viewmodel _data;
    public MainWindow()
    {
        _data = new Viewmodel();
        InitializeComponent();
        this.DataContext = _data;
    }
}

public class Viewmodel : INPCBase
{
    private string _myText1;
    private Lazy<DelegateCommand> _save;

    public Viewmodel()
    {
        this._save = new Lazy<DelegateCommand>(()=> new DelegateCommand(this.SaveCommandExecute));
    }

    private void SaveCommandExecute()
    {
        MessageBox.Show(MyText1);
    }

    public string MyText1
    {
        get { return _myText1; }
        set { _myText1 = value; this.NotifyPropertyChanged(()=>MyText1);}
    }

    public ICommand SaveCommand
    {
        get { return _save.Value; }
    }
}

回答1:

at the moment i came up with the following workaround. within the usercontrol/views where i define my KeyBindings, i also listen to the PreviewKeyDown event and set the focus to the next element when eg. Strg+S is pressed.

    private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.S && e.KeyboardDevice.Modifiers == ModifierKeys.Control)
        {
            var fe = Keyboard.FocusedElement as UIElement;

            if (fe != null)
            {
               fe.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
            }

        }
    }


回答2:

I have the same problem and end up with attached property for TextBox.

public static bool GetCommitOnSave(DependencyObject obj)
{
    return (bool)obj.GetValue(CommitOnSaveProperty);
}

public static void SetCommitOnSave(DependencyObject obj, bool value)
{
    obj.SetValue(CommitOnSaveProperty, value);
}

public static readonly DependencyProperty CommitOnSaveProperty =
    DependencyProperty.RegisterAttached("CommitOnSave", typeof(bool), typeof(Helper), new PropertyMetadata(false, CommitOnSavePropertyChanged));

private static void CommitOnSavePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (d is TextBox textBox)
    {
        if ((bool)e.NewValue)
        {
            if ((bool)e.NewValue)
            {
                textBox.KeyDown += TextBox_KeyDown;
            }
            else
            {
                textBox.KeyDown -= TextBox_KeyDown;
            }
        }
    }
}

private static void TextBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
    var textBox = (TextBox)sender;
    if (e.Key == Key.S && Keyboard.Modifiers == ModifierKeys.Control)
    {
        BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();
    }
}

Using <TextBox Text="{Binding Name}" local:Helper.CommitOnSave="True" />

Of course you can set attached property in style for all TextBoxes in a form.



回答3:

I think I find the best solution for me. I mix solution @blindmeis and my previous one with using attached property.

I create command which update binding source of actual keyboard focused element:

public class CommitValueCommand : ICommand
{
    private static CommitValueCommand _instance;
    public static CommitValueCommand Command => _instance ?? (_instance = new CommitValueCommand());

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        if (Keyboard.FocusedElement is TextBox textBox)
        {
            BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();
        }
        //for combobox etc.
        else if (Keyboard.FocusedElement is Selector selector)
        {
            BindingOperations.GetBindingExpression(selector, Selector.SelectedValueProperty).UpdateSource();
        }
    }
}

In Execute method of command SaveCommand just at beginning invoke CommitValueCommand.Command.Execute().