My ViewModel实现INotifyPropertyChanged和INotifyDataErrorInfo接口。当属性更改时,验证将触发,从而启用\禁用"保存"按钮。
因为Validation步骤很耗时,所以我使用了Delay绑定属性。
我的问题是,在"名称"属性更新之前,我可以键入我的更改并按"保存"。
当我按下SaveChanges时,我想强制立即更新TextBox.Text。目前,我必须在执行之前添加睡眠,以确保ViewModel上发生了所有更改。
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Delay=1000}" />
<Button Command="{Binding SaveChanges}" />
有人有什么建议吗?
由于.NET 4.5存在BindingOperations
BindingOperations.GetSourceUpdatingBindings(this).ToList().ForEach(x => x.UpdateSource());
您可以在viewModel上实现IPropertyChanged接口,然后从Name属性设置器检查值是否已更改,并引发该属性的OnPropertyChanged事件。
您可以使用该属性更改事件连接SaveChanges命令CanExecute方法,如果尚未更新,则返回false;如果延迟已过并且属性已更新,则将返回true。
因此,SaveChanges按钮将保持禁用状态,直到CanExecute返回true为止。
不确定在您的案例中延迟的目的。然而,我能想到的其他几个选择如下。
-
将UpdateSourceTrigger设置为显式并以自己的方式处理延迟。然后,您可以随时更新源代码。
-
使用Binding.IsAsync,它将异步获取和设置值。这里有一个很好的例子。
创建一个自定义控件文本框并设置延迟时间属性。
公共类DelayedBindingTextBox:TextBox{
private Timer timer;
private delegate void Method();
/// <summary>
/// Gets and Sets the amount of time to wait after the text has changed before updating the binding
/// </summary>
public int DelayTime {
get { return (int)GetValue(DelayTimeProperty); }
set { SetValue(DelayTimeProperty, value); }
}
// Using a DependencyProperty as the backing store for DelayTime. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DelayTimeProperty =
DependencyProperty.Register("DelayTime", typeof(int), typeof(DelayedBindingTextBox), new UIPropertyMetadata(667));
//override this to update the source if we get an enter or return
protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e) {
//we dont update the source if we accept enter
if (this.AcceptsReturn == true) { }
//update the binding if enter or return is pressed
else if (e.Key == Key.Return || e.Key == Key.Enter) {
//get the binding
BindingExpression bindingExpression = this.GetBindingExpression(TextBox.TextProperty);
//if the binding is valid update it
if (BindingCanProceed(bindingExpression)){
//update the source
bindingExpression.UpdateSource();
}
}
base.OnKeyDown(e);
}
protected override void OnTextChanged(TextChangedEventArgs e) {
//get the binding
BindingExpression bindingExpression = this.GetBindingExpression(TextBox.TextProperty);
if (BindingCanProceed(bindingExpression)) {
//get rid of the timer if it exists
if (timer != null) {
//dispose of the timer so that it wont get called again
timer.Dispose();
}
//recreate the timer everytime the text changes
timer = new Timer(new TimerCallback((o) => {
//create a delegate method to do the binding update on the main thread
Method x = (Method)delegate {
//update the binding
bindingExpression.UpdateSource();
};
//need to check if the binding is still valid, as this is a threaded timer the text box may have been unloaded etc.
if (BindingCanProceed(bindingExpression)) {
//invoke the delegate to update the binding source on the main (ui) thread
Dispatcher.Invoke(x, new object[] { });
}
//dispose of the timer so that it wont get called again
timer.Dispose();
}), null, this.DelayTime, Timeout.Infinite);
}
base.OnTextChanged(e);
}
//makes sure a binding can proceed
private bool BindingCanProceed(BindingExpression bindingExpression) {
Boolean canProceed = false;
//cant update if there is no BindingExpression
if (bindingExpression == null) { }
//cant update if we have no data item
else if (bindingExpression.DataItem == null) { }
//cant update if the binding is not active
else if (bindingExpression.Status != BindingStatus.Active) { }
//cant update if the parent binding is null
else if (bindingExpression.ParentBinding == null) { }
//dont need to update if the UpdateSourceTrigger is set to update every time the property changes
else if (bindingExpression.ParentBinding.UpdateSourceTrigger == UpdateSourceTrigger.PropertyChanged) { }
//we can proceed
else {
canProceed = true;
}
return canProceed;
}
}
我在WPF应用程序中遇到了同样的问题,并提出了以下解决方案:
public class DelayedProperty<T> : INotifyPropertyChanged
{
#region Fields
private T actualValue;
private DispatcherTimer timer;
private T value;
#endregion
#region Properties
public T ActualValue => this.actualValue;
public int Delay { get; set; } = 800;
public bool IsPendingChanges => this.timer?.IsEnabled == true;
public T Value
{
get
{
return this.value;
}
set
{
if (this.Delay > 0)
{
this.value = value;
if (timer == null)
{
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(this.Delay);
timer.Tick += ValueChangedTimer_Tick;
}
if (timer.IsEnabled)
{
timer.Stop();
}
timer.Start();
this.RaisePropertyChanged(nameof(IsPendingChanges));
}
else
{
this.value = value;
this.SetField(ref this.actualValue, value);
}
}
}
#endregion
#region Event Handlers
private void ValueChangedTimer_Tick(object sender, EventArgs e)
{
this.FlushValue();
}
#endregion
#region Public Methods
/// <summary>
/// Force any pending changes to be written out.
/// </summary>
public void FlushValue()
{
if (this.IsPendingChanges)
{
this.timer.Stop();
this.SetField(ref this.actualValue, this.value, nameof(ActualValue));
this.RaisePropertyChanged(nameof(IsPendingChanges));
}
}
/// <summary>
/// Ignore the delay and immediately set the value.
/// </summary>
/// <param name="value">The value to set.</param>
public void SetImmediateValue(T value)
{
this.SetField(ref this.actualValue, value, nameof(ActualValue));
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetField<U>(ref U field, U valueField, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<U>.Default.Equals(field, valueField)) { return false; }
field = valueField;
this.RaisePropertyChanged(propertyName);
return true;
}
protected void RaisePropertyChanged(string propertyName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
要使用它,您需要创建一个属性,如:
public DelayedProperty<string> Name { get;set; } // Your choice of DP or INPC if you desire.
并将您的文本框更改为:
<TextBox Text="{Binding Name.Value, UpdateSourceTrigger=PropertyChanged}" />
然后在处理SaveChanges
命令时,您可以调用:
this.Name?.FlushValue();
然后,您将能够从属性中访问ActualValue。我目前订阅了Name属性上的PropertyChanged事件,但我正在考虑为此创建一个特定的事件。
我希望能找到一个更简单的解决方案,但这是我目前能想到的最好的解决方案。