【问题标题】:Databinding between Model and Label using a Timer使用计时器在模型和标签之间进行数据绑定
【发布时间】:2021-08-31 20:50:03
【问题描述】:

我正在尝试将我的 Label 的 Text 属性绑定到 Model 的 Counter 属性,但它不会随着每个计时器的增量而更新。

这可能是一个重复的问题,但我真的不明白我在哪里犯了错误。

看起来它可以取初始值,但它不会像预期的那样每秒更新一次。

在表单构造函数中:

public partial class Form1 : Form
{
    Model model;

    public Form1()
    {
        InitializeComponent();

        model = new Model();

        Binding binding = new Binding("Text", model, "Counter", true, DataSourceUpdateMode.OnPropertyChanged);
        label1.DataBindings.Add(binding);
    }
}

我的Model班级:

public class Model: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    // This method is called by the Set accessor of each property.
    // The CallerMemberName attribute that is applied to the optional propertyName
    // parameter causes the property name of the caller to be substituted as an argument.
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            //Console.WriteLine(propertyName);
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private static Timer timer;
    private int counter;

    public int Counter
    {
        get { return counter; }
        set 
        { 
            counter = value;
            NotifyPropertyChanged();
        }
    }

    public Model()
    {
        counter = 0;
        SetTimer();
    }

    public void SetTimer()
    {
        timer = new Timer(1000);
        timer.Elapsed += Timer_Elapsed;
        timer.AutoReset = true;
        timer.Enabled = true;
    }

    private void Timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        Counter++;            
    }
}

【问题讨论】:

    标签: c# winforms data-binding


    【解决方案1】:

    几个示例,使用 System.Windows.Forms.Timer 替换您现在使用的 System.Timers.Timer
    后者在 ThreadPool 线程中引发其 Elapsed 事件。与许多其他对象一样,DataBindings 不能跨线程工作。

    您可以在此处阅读其他详细信息:
    Why does invoking an event in a timer callback cause following code to be ignored?

    System.Windows.Forms.Timer 已经同步,它的Tick 事件在 UI 线程中引发。

    使用此计时器的新模型类:

    using System;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Windows.Forms;
    
    public class Model : INotifyPropertyChanged, IDisposable
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") 
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
        private readonly Timer timer;
        private int counter;
    
        public Model() {
            counter = 0;
            timer = new Timer() { Interval = 1000 };
            timer.Tick += this.Timer_Elapsed;
            StartTimer();
        }
    
        public int Counter {
            get => counter;
            set {
                if (counter != value) {
                    counter = value;
                    NotifyPropertyChanged();
                }
            }
        }
    
        public void StartTimer() => timer.Start();
    
        public void StopTimer() => timer.Stop();
    
        private void Timer_Elapsed(object sender, EventArgs e) => Counter++;
    
        public void Dispose(){
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        protected virtual void Dispose(bool disposing)
        {
            if (disposing) {
                lock (this) {
                    if (timer != null) timer.Tick -= Timer_Elapsed;
                    timer?.Stop();
                    timer?.Dispose();
                }
            }
        }
    }
    

    如果您想使用System.Threading.Timer,您需要将其Elapsed 事件与UI 线程同步,因为如上所述,PropertyChanged 通知无法跨线程编组,您的DataBindings 将无法工作。

    您可以(主要)使用两种方法:

    • 使用SynchronizationContext 类捕获当前的WindowsFormsSynchronizationContextPost 以更新属性值。
    • 向您的类添加一个构造函数,该构造函数还接受实现ISynchronizeInvoke 的UI 元素(实际上是任何控件)。您可以使用此对象设置System.Threading.TimerSynchronizingObject 属性。
      设置后,Elapsed 事件将在与 Sync 对象相同的线程中引发。

    注意:您不能将 Model 对象声明为 Field 并同时进行初始化:在初始化起始 Form 之前没有 SynchronizationContext。您可以在表单的构造函数中或之后的任何时间初始化一个新实例:

    public partial class Form1 : Form
    {
        Model model = new Model(); // <= Won't work
        // ------------------------------------------
        Model model = null;        // <= It's OK
        public Form1()
        {
            InitializeComponent();
    
            // Using the SynchronizationContext
            model = new Model();
    
            // Or, using A Synchronizing Object
            model = new Model(this);
    
            var binding = new Binding("Text", model, "Counter", true, DataSourceUpdateMode.OnPropertyChanged);
            label1.DataBindings.Add(binding);
        }
    }
    

    使用两者的修改后的模型类:

    using System;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Threading;
    using System.Timers;
    
    public class Model : INotifyPropertyChanged, IDisposable
    {
        public event PropertyChangedEventHandler PropertyChanged;
        internal readonly SynchronizationContext syncContext = null;
        internal ISynchronizeInvoke syncObj = null;
    
        private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
        private System.Timers.Timer timer;
        private int counter;
    
        public Model() : this(null) { }
    
        public Model(ISynchronizeInvoke synchObject)
        {
            syncContext = SynchronizationContext.Current;
            syncObj = synchObject;
            timer = new System.Timers.Timer();
            timer.SynchronizingObject = syncObj;
            timer.Elapsed += Timer_Elapsed;
            StartTimer(1000);
        }
    
        public int Counter {
            get => counter;
            set {
                if (counter != value) {
                    counter = value;
                    NotifyPropertyChanged();
                }
            }
        }
    
        public void StartTimer(int interval) {
            timer.Interval = interval;
            timer.AutoReset = true;
            timer.Start();
        }
    
        public void StopTimer(int interval) => timer.Stop();
    
        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (syncObj is null) {
                syncContext.Post((spcb) => Counter += 1, null);
            }
            else {
                Counter += 1;
            }
        }
    
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (disposing) {
                lock (this) {
                    if (timer != null) timer.Elapsed -= Timer_Elapsed;
                    timer?.Stop();
                    timer?.Dispose();
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-17
      • 1970-01-01
      • 1970-01-01
      • 2014-10-19
      • 2016-02-12
      • 2020-01-28
      相关资源
      最近更新 更多