使用计时器在模型和标签之间进行数据绑定
Databinding between Model and Label using a Timer
我正在尝试将标签的 Text
属性 绑定到模型的 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
class:
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++;
}
}
几个示例,使用您现在正在使用的 System.Windows.Forms.Timer that replaces the System.Timers.Timer。
后者在 ThreadPool Threads 中引发其 Elapsed
事件。与许多其他对象一样,DataBindings 不能跨线程工作。
您可以在此处阅读其他一些详细信息:
System.Windows.Forms.Timer
已经 同步,其 Tick
事件在 UI 线程中引发。
使用此计时器的新模型 class:
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 线程同步,因为如前所述,属性Changed无法跨线程编组通知,您的 DataBindings 将无法工作。
您可以(主要)使用两种方法:
- 使用 SynchronizationContext class to capture the current
WindowsFormsSynchronizationContext
and Post 来更新 属性 值。
- 向您的 class 添加构造函数,该构造函数还接受实现 ISynchronizeInvoke (any Control, in practice). You can use this object to set the
System.Threading.Timer
's SynchronizingObject 属性.
的 UI 元素
设置后,Elapsed
事件将在与 Sync 对象相同的线程中引发。
注意:您不能将模型对象声明为字段并同时进行初始化:只有在初始化起始窗体之后才会有 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);
}
}
修改后的模型 class 两者都使用了:
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();
}
}
}
}
我正在尝试将标签的 Text
属性 绑定到模型的 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
class:
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++;
}
}
几个示例,使用您现在正在使用的 System.Windows.Forms.Timer that replaces the System.Timers.Timer。
后者在 ThreadPool Threads 中引发其 Elapsed
事件。与许多其他对象一样,DataBindings 不能跨线程工作。
您可以在此处阅读其他一些详细信息:
System.Windows.Forms.Timer
已经 同步,其 Tick
事件在 UI 线程中引发。
使用此计时器的新模型 class:
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 线程同步,因为如前所述,属性Changed无法跨线程编组通知,您的 DataBindings 将无法工作。
您可以(主要)使用两种方法:
- 使用 SynchronizationContext class to capture the current
WindowsFormsSynchronizationContext
and Post 来更新 属性 值。 - 向您的 class 添加构造函数,该构造函数还接受实现 ISynchronizeInvoke (any Control, in practice). You can use this object to set the
System.Threading.Timer
's SynchronizingObject 属性.
的 UI 元素 设置后,Elapsed
事件将在与 Sync 对象相同的线程中引发。
注意:您不能将模型对象声明为字段并同时进行初始化:只有在初始化起始窗体之后才会有 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);
}
}
修改后的模型 class 两者都使用了:
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();
}
}
}
}