强制绑定以在触发Command时更新

时间:2012-06-18 15:00:33

标签: wpf mvvm binding command

有没有办法在触发命令之前或之后更新绑定?我有一些文本字段,我可以使用命令编辑和保存,可通过键盘快捷方式访问。由于绑定通常仅在文本字段失去焦点时更新,因此在按下键以保存数据时不会保留最后一次更改。相反,我必须先从文本字段中删除它以使其更新然后保存。

有没有办法以优雅的方式强制更新?我正在使用MVVM(但不是任何MVVM框架),所以我想保留命令代码中的UI特定内容。另外,我真的不想在每次更改时更改绑定以进行更新,只有在焦点丢失时才更新它。

7 个答案:

答案 0 :(得分:10)

如果当前元素是TextBox,您还可以更新绑定源,而不是更改焦点。您可以为其他控件执行类似的操作,但根据我的经验,我TextBox只遇到此问题。

// if the current focused element is textbox then updates the source.
var focusedElement = Keyboard.FocusedElement as FrameworkElement;

if (focusedElement is TextBox)
{
    var expression = focusedElement.GetBindingExpression(TextBox.TextProperty);
    if (expression != null) expression.UpdateSource();
}

答案 1 :(得分:4)

TextBox上,您需要在文本绑定上设置UpdateSourceTrigger,它定义何时使用文本框值更新来源。默认情况下,LostFocus属性为Text,这正是发生的事情 - 它只在失去焦点时更新源。您应该将UpdateSourceTrigger的值设置为PropertyChanged,并且每次文本框值更改时都会更新。

e.g。

<TextBox ... Text="{Binding Foo, UpdateSourceTrigger=PropertyChanged}"/>

使用Binding命令时,Path是默认属性,因此上述内容等于Path=Foo

答案 2 :(得分:2)

以下是我用于类似情况的课程:

public class TextBoxUpdatesTextBindingOnPropertyChanged : Behavior<TextBox>
{
    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.TextChanged += TextBox_TextChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.TextChanged -= TextBox_TextChanged;
    }

    void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        var bindingExpression = AssociatedObject.GetBindingExpression(TextBox.TextProperty);
        bindingExpression.UpdateSource();
    }
}

XAML:

<TextBox Text="{Binding UserName,Mode=TwoWay,UpdateSourceTrigger=Explicit}">
    <i:Interaction.Behaviors>
        <h:TextBoxUpdatesTextBindingOnPropertyChanged />
    </i:Interaction.Behaviors>
</TextBox>

调用OnPropertyChanged("YourPropertyName")将提醒视图更新绑定值。

否则请查看以下答案:WPF TextBox DataBind on EnterKey press

答案 3 :(得分:2)

我现在通过在询问值之前明确地将焦点设置为其他元素来解决这个问题。这显然使得当前具有焦点的元素失去它并更新绑定。

为了设定焦点,我写了一个附属物,灵感来自其他问题的答案。此外,与my other question一起,我做了一些自动化。

所以要使用它,我基本上将我的属性附加到一个元素,在本例中是一个制表符控件:

<TabControl c:Util.ShouldFocus="{Binding ShouldClearFocus}">

在我的视图模型中,我有一个简单的布尔属性ShouldClearFocus,它是一个提升PropertyChangedEvent的标准属性,因此数据绑定有效。然后,当我想重置焦点时,我只需将ShouldClearFocus设置为true。附加属性会自动设置焦点并再次重置属性值。这样我就可以继续设置ShouldClearFocus而无需将其设置为false

附加属性是一个标准实现,将其作为更改处理程序:

public static void ShouldFocusChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    if (!(bool)e.NewValue || !(obj is FrameworkElement))
        return;

    FrameworkElement element = (FrameworkElement)obj;
    if (element.Focusable)
        element.Focus();

    // reset value
    BindingExpression bindingExpression = BindingOperations.GetBindingExpression(obj, ShouldFocusProperty);
    if (bindingExpression != null)
    {
        PropertyInfo property = bindingExpression.DataItem.GetType().GetProperty(bindingExpression.ParentBinding.Path.Path);
        if (property != null)
            property.SetValue(bindingExpression.DataItem, false, null);
    }
    else
        SetShouldFocus(obj, false);
}

答案 4 :(得分:0)

我在我的项目中使用以下内容。问题是按钮没有收到焦点。

在我的app.xaml.cs

    EventManager.RegisterClassHandler(typeof(Button), ButtonBase.ClickEvent, new RoutedEventHandler(ButtonClick));

    private void ButtonClick(object sender, RoutedEventArgs e)
    {
        if (sender != null && sender is Button)
        {
            (sender as Button).Focus();
        }
    }

答案 5 :(得分:0)

我认为最好的方法是设置绑定UpdateSourceTrigger=PropertyChanged,然后使用Reactive Extensions来限制事件流。这样,该属性将仅在用户停止键入一段时间后才更新(1/2秒是一个很好的数量)。

这很好,因为该属性不会一直更新,但它也会更新而无需更改焦点。正是你想要的。

Here is some really useful information on Rx

有一个MVVM扩展库,它具有可绑定的IObservables here。他们在这里的例子完全符合我的建议。

你要求优雅:在这里。它的工作也没那么多。

答案 6 :(得分:0)

在UI上侦听命令调用可能很棘手(如果不是不可能的话,因为到目前为止没有答案可以解决)。我建议不要使用Messenger模式,例如MvvmLight中的模式,而不是笨拙地翻转附加属性:

public class ForceUpdateBindingsMessage
{
    public ViewModelBase ViewModel { get; }

    public ForceUpdateBindingsMessage(ViewModelBase viewModel)
    {
        ViewModel = viewModel;
    }
}

// ViewModel
public ICommand SaveCommand => _saveCommand ?? (_saveCommand = new RelayCommand(() =>
{
    // ...push binding updating responsibility to whoever listens to this (desperate) message
    MessengerInstance.Send(new ForceUpdateBindingsMessage(this));
    // the bindings should be up-to-date now        
}));

现在,在“ UpdateSource”部分,ForceUpdateBindingsMessage应该由某些父控件处理,我选择了MainWindow,以便VeiwModel的绑定在任何地方都可以更新,同时仅将更新限制为调用{ {1}}:

ViewModel

其中public MainWindow() { InitializeComponent(); Messenger.Default.Register<ForceUpdateBindingsMessage>(this, msg => { UpdateBindingsOfViewModel(ViewModelBase viewModel); }); } private void UpdateBindingsOfViewModel(ViewModelBase viewModel) { foreach (var tb in this.GetChildren<TextBox>()) { var binding = tb.GetBindingExpression(TextBox.TextProperty); if (binding?.DataItem == msg.ViewModel) { binding.UpdateSource(); } } } 是常用的扩展帮助器方法:

GetChildren<T>