Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/332.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/wpf/14.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何等待WPF绑定延迟完成_C#_Wpf_Binding - Fatal编程技术网

C# 如何等待WPF绑定延迟完成

C# 如何等待WPF绑定延迟完成,c#,wpf,binding,C#,Wpf,Binding,My ViewModel实现INotifyPropertyChanged和INotifyDataErrorInfo接口。更改属性时,将触发验证,从而启用\禁用保存按钮 因为验证步骤非常耗时,所以我使用了延迟绑定属性 我的问题是,在更新“Name”属性之前,我可以键入更改并按Save 我想在按下SaveChanges时强制立即更新TextBox.Text。目前,我必须在执行之前添加睡眠,以确保ViewModel上发生了所有更改 <TextBox Text="{Binding Name, Up

My ViewModel实现INotifyPropertyChanged和INotifyDataErrorInfo接口。更改属性时,将触发验证,从而启用\禁用保存按钮

因为验证步骤非常耗时,所以我使用了延迟绑定属性

我的问题是,在更新“Name”属性之前,我可以键入更改并按Save

我想在按下SaveChanges时强制立即更新TextBox.Text。目前,我必须在执行之前添加睡眠,以确保ViewModel上发生了所有更改

<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Delay=1000}" />
<Button Command="{Binding SaveChanges}" />


有人有一些指针吗?

您可以在viewModel上实现IPropertyChanged接口,然后从Name属性设置器检查值是否已更改,并为该属性引发OnPropertyChanged事件

您可以使用该属性更改事件连接SaveChanges命令CanExecute方法,如果尚未更新,则返回false;如果延迟已过且属性已更新,则返回true


因此,SaveChanges按钮保持禁用状态,直到CanExecute返回true。

不确定延迟的目的。然而,下面是我能想到的几个其他选择

  • 将UpdateSourceTrigger设置为explicit,并以自己的方式处理延迟。然后,您可以随时更新资源

  • 使用将异步获取和设置值的。


  • 创建自定义控件文本框并设置延迟时间属性

    公共类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());