使用计时器在模型和标签之间进行数据绑定

Posted

技术标签:

【中文标题】使用计时器在模型和标签之间进行数据绑定【英文标题】: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++;            
    

【问题讨论】:

【参考方案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 类捕获当前的WindowsFormsSynchronizationContext 和Post 以更新属性值。 向您的类添加一个构造函数,该构造函数还接受实现ISynchronizeInvoke 的UI 元素(实际上是任何控件)。您可以使用此对象设置System.Threading.Timer 的SynchronizingObject 属性。 设置后,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();
            
        
    

【讨论】:

以上是关于使用计时器在模型和标签之间进行数据绑定的主要内容,如果未能解决你的问题,请参考以下文章

从高度动态的 C++ 数据模型更新 QML:计时器与属性绑定

SpringMVC数据绑定与表单标签库

第二章:2.1 AngularJS 中的数据绑定

绑定属性上的 DataTrigger

弹簧数据绑定标签形式没有必要吗?

如何在 laravel 刀片模板中使用 JSON 数据进行模型绑定?