C# 如何等待WPF绑定延迟完成
My ViewModel实现INotifyPropertyChanged和INotifyDataErrorInfo接口。更改属性时,将触发验证,从而启用\禁用保存按钮 因为验证步骤非常耗时,所以我使用了延迟绑定属性 我的问题是,在更新“Name”属性之前,我可以键入更改并按Save 我想在按下SaveChanges时强制立即更新TextBox.Text。目前,我必须在执行之前添加睡眠,以确保ViewModel上发生了所有更改C# 如何等待WPF绑定延迟完成,c#,wpf,binding,C#,Wpf,Binding,My ViewModel实现INotifyPropertyChanged和INotifyDataErrorInfo接口。更改属性时,将触发验证,从而启用\禁用保存按钮 因为验证步骤非常耗时,所以我使用了延迟绑定属性 我的问题是,在更新“Name”属性之前,我可以键入更改并按Save 我想在按下SaveChanges时强制立即更新TextBox.Text。目前,我必须在执行之前添加睡眠,以确保ViewModel上发生了所有更改 <TextBox Text="{Binding Name, Up
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Delay=1000}" />
<Button Command="{Binding SaveChanges}" />
有人有一些指针吗?您可以在viewModel上实现IPropertyChanged接口,然后从Name属性设置器检查值是否已更改,并为该属性引发OnPropertyChanged事件 您可以使用该属性更改事件连接SaveChanges命令CanExecute方法,如果尚未更新,则返回false;如果延迟已过且属性已更新,则返回true
因此,SaveChanges按钮保持禁用状态,直到CanExecute返回true。不确定延迟的目的。然而,下面是我能想到的几个其他选择
创建自定义控件文本框并设置延迟时间属性 公共类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;
}
专用定时器;
私有委托无效方法();
///
///获取并设置更新绑定之前文本更改后等待的时间量
///
公共整数延迟时间{
获取{return(int)GetValue(DelayTimeProperty);}
set{SetValue(DelayTimeProperty,value);}
}
//使用DependencyProperty作为DelayTime的后备存储。这将启用动画、样式、绑定等。。。
公共静态只读DependencyProperty DelayTimeProperty=
DependencyProperty.Register(“DelayTime”、typeof(int)、typeof(DelayedBindingTextBox)、new UIPropertyMetadata(667));
//如果我们得到enter或return,则重写此项以更新源
受保护的覆盖无效OnKeyDown(System.Windows.Input.KeyEventArgs e){
//如果接受enter,则不更新源
如果(this.AcceptsReturn==true){}
//如果按enter或return,则更新绑定
else if(e.Key==Key.Return | | e.Key==Key.Enter){
//把装订好
BindingExpression BindingExpression=this.GetBindingExpression(TextBox.TextProperty);
//如果绑定有效,请更新它
if(bindingCanProcedure(bindingExpression)){
//更新源代码
bindingExpression.UpdateSource();
}
}
base.OnKeyDown(e);
}
受保护的覆盖void OnTextChanged(textchangedventargs e){
//把装订好
BindingExpression BindingExpression=this.GetBindingExpression(TextBox.TextProperty);
if(bindingCanProcedure(bindingExpression)){
//如果计时器存在,请将其清除
如果(计时器!=null){
//丢弃计时器,使其不再被调用
timer.Dispose();
}
//每次文本更改时重新创建计时器
计时器=新计时器(新计时器回调((o)=>{
//创建一个委托方法在主线程上执行绑定更新
方法x=(方法)委托{
//更新绑定
bindingExpression.UpdateSource();
};
//需要检查绑定是否仍然有效,因为这是一个线程计时器,文本框可能已卸载等。
if(bindingCanProcedure(bindingExpression)){
//调用委托以更新主(ui)线程上的绑定源
Invoke(x,新对象[]{});
}
//丢弃计时器,使其不再被调用
timer.Dispose();
}),null,this.DelayTime,Timeout.Infinite);
}
base.OnTextChanged(e);
}
//确保绑定可以继续
私有bool bindingCanProcedure(BindingExpression BindingExpression){
布尔值=false;
//如果没有BindingExpression,则无法更新
如果(bindingExpression==null){}
//如果没有数据项,则无法更新
如果(bindingExpression.DataItem==null){}
//如果绑定未激活,则无法更新
如果(bindingExpression.Status!=BindingStatus.Active){}
//如果父绑定为空,则无法更新
如果(bindingExpression.ParentBinding==null){}
//如果UpdateSourceTrigger设置为每次属性更改时更新,则无需更新
else如果(bindingExpression.ParentBinding.UpdateSourceTrigger==UpdateSourceTrigger.PropertyChanged){}
//我们可以继续
否则{
canprocedure=true;
}
返回可以继续;
}
}我在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
}
然后,您将能够从属性访问ActualValue。我目前订阅了Name属性上的PropertyChanged事件,但我正在考虑为此制作一个特定事件
我希望找到一个使用起来更简单的解决方案,但这是目前我能想到的最好的解决方案。因为.NET 4.5存在绑定操作
BindingOperations.GetSourceUpdatingBindings(this).ToList().ForEach(x => x.UpdateSource());
我不确定我是否理解这个问题。你为什么需要睡觉?如果您想在保存后立即更新文本框,延迟绑定将无法得到您想要的。我正在寻找一种更通用的解决绑定“延迟”问题的方法。因为我可能会使用不同的控件和不同的值。如果您从一开始就遵循线程,那么您会注意到问题是:“我想强制立即更新Te
<TextBox Text="{Binding Name.Value, UpdateSourceTrigger=PropertyChanged}" />
this.Name?.FlushValue();
BindingOperations.GetSourceUpdatingBindings(this).ToList().ForEach(x => x.UpdateSource());