我的ViewModel实现了INotifyPropertyChanged和INotifyDataErrorInfo接口。更改属性后,将触发验证,然后启用\禁用“保存”按钮。
因为验证步骤非常耗时,所以我使用了延迟绑定属性。
我的问题是我可以输入我的更改并在“名称”属性更新之前按“保存”。
当我按SaveChanges时,我想强制立即更新TextBox.Text。目前,我必须在执行前添加一个睡眠,以确保ViewModel上发生了所有更改。
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Delay=1000}" />
<Button Command="{Binding SaveChanges}" />
有没有人有指示?
答案 0 :(得分:1)
您可以在viewModel上实现IPropertyChanged接口,然后从Name属性setter中检查该值是否已更改,并为该属性引发OnPropertyChanged事件。
您可以使用该属性更改事件来连接SaveChanges命令CanExecute方法,如果尚未更新则返回false,如果延迟已经过去并且属性已更新,则返回true。
因此,SaveChanges按钮保持禁用状态,直到CanExecute返回true。
答案 1 :(得分:0)
不确定您的案件有延迟的目的。但是,我能想到的其他几个选项如下。
将UpdateSourceTrigger设置为explicit并以您自己的方式处理延迟。然后你可以随时使用UpdateSource。
使用Binding.IsAsync,它将异步获取和设置值。 Here is a good example
答案 2 :(得分:0)
创建自定义控件文本框并设置延迟时间属性。
公共类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;
}
}
答案 3 :(得分:0)
我在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更改为:
<TextBox Text="{Binding Name.Value, UpdateSourceTrigger=PropertyChanged}" />
然后在处理SaveChanges
命令时,您可以调用:
this.Name?.FlushValue();
然后,您就可以从该属性访问ActualValue。我目前订阅了Name属性上的PropertyChanged事件,但我正在考虑为此创建一个特定的事件。
我希望找到一个更简单易用的解决方案,但这是我现在能想到的最好的解决方案。
答案 4 :(得分:0)
.NET 4.5开始存在BindingOperations
BindingOperations.GetSourceUpdatingBindings(this).ToList().ForEach(x => x.UpdateSource());