【问题标题】:INotifyPropertyChanged and ThreadingINotifyPropertyChanged 和线程
【发布时间】:2011-06-17 01:10:55
【问题描述】:

我有一个实现INotifyPropertyChanged 的基类:

protected void OnNotifyChanged(string pName)
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(pName));
    }
}

public event PropertyChangedEventHandler PropertyChanged;

我有一个带有属性Latitude 的派生类,如下所示:

private double latitude;

public double Latitude
{
    get { return latitude; }
    set { latitude = value; OnNotifyChanged("Latitude"); }
}

我的派生类也有一个方法Fly 可以操作Latitude

我还有一个表单,其 TextBox 绑定到我的派生类的 Latitude

txtLat.DataBindings.Clear();    
txtLat.DataBindings.Add("Text", bindSrc, "Latitude");

一个线程用于启动Fly,如下所示:

Thread tFly = new Thread(f.Fly);
tFly.IsBackground = true;
tFly.Start();

Latitude改变时,抛出异常:

DataBinding cannot find a row in the list that is suitable for all bindings.

【问题讨论】:

    标签: c# winforms multithreading binding


    【解决方案1】:

    这似乎是线程关联的一个奇怪问题。最终,代码试图从非 UI 线程进行更新 - 我不清楚为什么它不只是显示跨线程异常 - 我想知道这是否实际上是一个包罗万象的异常处理程序。如果我删除 BindingSource(并直接绑定到对象,这是有效的),您确实会得到一个跨线程异常(这是我所期望的)。

    就个人而言,我倾向于手动处理此问题,即使用对 UI 线程执行 Invoke 并手动更新 Text 的方法订阅事件。但是,我只是在检查一些以前的跨线程绑定代码是否有帮助......


    这是一个使用Invoke的例子:

    using System;
    using System.ComponentModel;
    using System.Threading;
    using System.Windows.Forms;
    
    class FlightUav : INotifyPropertyChanged
    {
        protected void OnNotifyChanged(string pName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(pName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
        private double _latitude;
        public double Latitude
        {
            get { return _latitude; }
            set { _latitude = value; OnNotifyChanged("Latitude"); }
        }
        public void Fly()
        {
            for (int i = 0; i < 100; i++)
            {
                Latitude++;
                Thread.Sleep(10);
            }
        }
        [STAThread]
        static void Main()
        {
            using (Form form = new Form())
            {
                FlightUav currentlyControlledFlightUav = new FlightUav();
    
                currentlyControlledFlightUav.PropertyChanged += delegate
                { // this should be in a *regular* method so that you can -= it when changing bindings...
                    form.Invoke((MethodInvoker)delegate
                    {
                        form.Text = currentlyControlledFlightUav.Latitude.ToString();
                    });
                };
    
    
                using (Button btn = new Button())
                {
                    btn.Text = "Fly";
                    btn.Click += delegate
                    {
                        Thread tFly = new Thread(currentlyControlledFlightUav.Fly);
                        tFly.IsBackground = true;
                        tFly.Start();
                    };
                    form.Controls.Add(btn);
                    Application.Run(form);
                }
            }
        }
    
    
    }
    

    这是一个使用我的一些旧线程代码的(修改)版本的示例:

    using System;
    using System.ComponentModel;
    using System.Threading;
    using System.Windows.Forms;
    
    class FlightUav : INotifyPropertyChanged
    {
        protected void OnNotifyChanged(string pName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(pName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
        private double _latitude;
        public double Latitude
        {
            get { return _latitude; }
            set { _latitude = value; OnNotifyChanged("Latitude"); }
        }
        public void Fly()
        {
            for (int i = 0; i < 100; i++)
            {
                Latitude++;
                Thread.Sleep(10);
            }
        }
        [STAThread]
        static void Main()
        {
            using (Form form = new Form())
            {
                FlightUav currentlyControlledFlightUav = new FlightUav();
                BindingSource bindSrc = new BindingSource();
                var list = new ThreadedBindingList<FlightUav>();
                list.Add(currentlyControlledFlightUav);
                bindSrc.DataSource = list;
    
                form.DataBindings.Clear();
                form.DataBindings.Add("Text", list, "Latitude");
    
                using (Button btn = new Button())
                {
                    btn.Text = "Fly";
                    btn.Click += delegate
                    {
                        Thread tFly = new Thread(currentlyControlledFlightUav.Fly);
                        tFly.IsBackground = true;
                        tFly.Start();
                    };
                    form.Controls.Add(btn);
                    Application.Run(form);
                }
            }
        }
    
    
    }
    public class ThreadedBindingList<T> : BindingList<T>
    {
        private readonly SynchronizationContext ctx;
        public ThreadedBindingList()
        {
            ctx = SynchronizationContext.Current;
        }
        protected override void OnAddingNew(AddingNewEventArgs e)
        {
            SynchronizationContext ctx = SynchronizationContext.Current;
            if (ctx == null)
            {
                BaseAddingNew(e);
            }
            else
            {
                ctx.Send(delegate
                {
                    BaseAddingNew(e);
                }, null);
            }
        }
        void BaseAddingNew(AddingNewEventArgs e)
        {
            base.OnAddingNew(e);
        }
        protected override void OnListChanged(ListChangedEventArgs e)
        {
            if (ctx == null)
            {
                BaseListChanged(e);
            }
            else
            {
                ctx.Send(delegate
                {
                    BaseListChanged(e);
                }, null);
            }
        }
        void BaseListChanged(ListChangedEventArgs e)
        {
            base.OnListChanged(e);
        }
    }
    

    【讨论】:

    • @WulfgarPro 添加了两个不同的示例
    • @WulfgarPro - 是的,我只是简单地介绍了这个例子 - 将它限制在显示某事发生的最低限度。
    • @Marc Gravell - 我已经成功地让 Invoke 示例正常工作。我承认在代表和事件处理方面我还是个新手。我尝试了一些事情,但在尝试退出我的应用程序时,我不断出现Cannot access a disposed object named "FormName" 异常。有什么想法吗?
    • @WulfgarPro 它听起来就像线程仍在运行,导致回调。您应该在关闭表单时取消订阅。
    • @wulfgar 我明天去看看 - 你现在可以再次@Marc 我来提醒我吗?
    猜你喜欢
    • 2011-06-03
    • 1970-01-01
    • 2012-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多