using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using OMRON.Compolet.CIP;
namespace WindowsFormsApp3
{
public partial class Form1 : Form
{
public static NJCompolet nJCompolet;
Thread th1;
public Customer customer;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
CheckForIllegalCrossThreadCalls = false;
customer = new Customer();
customer.CustomerName = "";
customer.PhoneNumber = "";
nJCompolet = new NJCompolet();
nJCompolet.LocalPort = 2;
nJCompolet.PeerAddress = "192.168.250.1";
nJCompolet.Active = true;
dataBind();
th1 = new Thread(DoWork);
th1.Start();
}
private void dataBind() {
this.Invoke(new Action(() =>
{
textBox1.DataBindings.Add("Text", customer, "CustomerName");
textBox2.DataBindings.Add("Text", customer, "PhoneNumber");
}));
}
private void DoWork() {
while (true)
{
object obj = null;
obj = nJCompolet.ReadVariable("StruckReal.A");
if (obj != null) {
customer.CustomerName = obj.ToString();
}
obj = nJCompolet.ReadVariable("StruckReal.B");
if (obj != null)
{
customer.PhoneNumber = obj.ToString();
}
}
}
}
public class CustomerBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
}
public class Customer : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _customerName;
private string _phoneNumber;
public string CustomerName
{
get { return _customerName; }
set
{
if (_customerName != value)
{
_customerName = value;
//OnPropertyChanged("CustomerName");
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CustomerName)));
}
}
}
public string PhoneNumber
{
get { return _phoneNumber; }
set
{
if (_phoneNumber != value)
{
_phoneNumber = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PhoneNumber)));
}
}
}
}
}
//封装--无需再添加绑定
public class Class1 : NotifyPropertyChangedBase
{
private int A;
public int MyProperty {
get { return this.A; }
set
{
if (A != value)
{
A = value;
RaisePropertyChanged();//注意这里可以省略属性名称
}
}
}
}
public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
//chatgpt
当在WinForms中使用 INotifyPropertyChanged 时,当数据绑定的控件处于不同的线程上下文时,会发生“跨线程异常”。解决此问题的一种方法是确保在更改属性时始终在UI线程上下文中发出 PropertyChanged 事件。可以通过将代码包装在 Control.Invoke
或 Control.BeginInvoke
方法中来实现此目的。
以下是在使用INotifyPropertyChanged时避免跨线程异常的示例代码:
c#复制代码
// ViewModel class implementing INotifyPropertyChanged
public class MyViewModel : INotifyPropertyChanged
{
private string _myProperty;
public string MyProperty
{
get { return _myProperty; }
set
{
if (_myProperty != value)
{
_myProperty = value;
// Raise the PropertyChanged event on the UI thread
if (PropertyChanged != null)
{
Control control = Application.OpenForms[0];
control.Invoke(new Action(() =>
{
PropertyChanged(this, new PropertyChangedEventArgs("MyProperty"));
}));
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
在上面的示例代码中,我们首先检查 _myProperty
的值是否与要设置的新值相同。如果它们不同,则更新该值,并且在 UI 线程上下文中引发 PropertyChanged 事件。这里 Application.OpenForms[0]
表示当前正在运行的窗体,你需要根据你的应用程序来选择正确的窗体。
注意:使用 Control.Invoke
或 Control.BeginInvoke
方法可能会导致性能瓶颈。如果有大量的属性更改需要在 UI 线程上下文中发出 PropertyChanged 事件,可以考虑将其放入队列中,并使用后台线程异步处理队列中的项。
除了使用 Control.Invoke 或 Control.BeginInvoke 方法,还有一些其他方法可以避免在 WinForms 应用程序中出现跨线程异常。
- 使用 SynchronizationContext
.Net Framework 提供了一个抽象类 SynchronizationContext,用于协调异步操作之间的通信。在 WinForms 应用程序中,可以使用 WindowsFormsSynchronizationContext,它是 SynchronizationContext 的派生类,专门为在 UI 线程上下文中执行代码而设计。
以下是使用 WindowsFormsSynchronizationContext 的示例代码:
c#复制代码
public class MyViewModel : INotifyPropertyChanged
{
private string _myProperty;
private readonly WindowsFormsSynchronizationContext _syncContext;
public MyViewModel()
{
_syncContext = WindowsFormsSynchronizationContext.Current;
}
public string MyProperty
{
get { return _myProperty; }
set
{
if (_myProperty != value)
{
_myProperty = value;
// Raise the PropertyChanged event on the UI thread
if (PropertyChanged != null)
{
_syncContext.Post(new SendOrPostCallback(_ =>
{
PropertyChanged(this, new PropertyChangedEventArgs("MyProperty"));
}), null);
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
在上面的示例代码中,我们首先获取当前的 SynchronizationContext,并将其存储在字段 _syncContext
中。然后,在设置属性值时,我们使用 _syncContext.Post
方法将 PropertyChanged 事件的引发推迟到 UI 线程上下文中。
- 使用 TaskScheduler
.NET Task Parallel Library 提供了一个 TaskScheduler 类,可用于在特定的上下文中执行代码。在 WinForms 应用程序中,可以使用 TaskScheduler.FromCurrentSynchronizationContext 方法获取 UI 线程的任务调度程序。
以下是使用 TaskScheduler 的示例代码:
c#复制代码
public class MyViewModel : INotifyPropertyChanged
{
private string _myProperty;
private readonly TaskScheduler _uiTaskScheduler;
public MyViewModel()
{
_uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
public string MyProperty
{
get { return _myProperty; }
set
{
if (_myProperty != value)
{
_myProperty = value;
// Raise the PropertyChanged event on the UI thread
if (PropertyChanged != null)
{
Task.Factory.StartNew(() =>
{
PropertyChanged(this, new PropertyChangedEventArgs("MyProperty"));
}, CancellationToken.None, TaskCreationOptions.None, _uiTaskScheduler);
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
在上面的示例代码中,我们首先使用 TaskScheduler.FromCurrentSynchronizationContext 方法获取 UI 线程的任务调度程序。然后,在设置属性值时,我们使用 Task.Factory.StartNew 方法将 PropertyChanged 事件的引发封装到一个任务中,并使用 _uiTaskScheduler
参数指定该任务应在 UI 线程上下文中执行。
总之,这些方法都可以解决在 WinForms 应用程序中使用 INotifyPropertyChanged 时出现的跨线程异常问题。选择哪种方法取决于你的实际需求和个人偏好。