【问题标题】:Access dynamic control from class in a thread从线程中的类访问动态控制
【发布时间】:2012-05-24 14:04:06
【问题描述】:

我正在处理一个小项目,该项目处理轮询设备以检查 I/O 控件的状态。我已经实现了一个处理特定设备的小项目,但我决定最终要实现不同的设备,因此已经转移到类:接口方法。然而,这引起了一些问题,因为我移动了很多代码。

在我移动代码等之前,我通过使用委托来访问动态表单控件;

 if (result != null)
            {
                this.Invoke((MethodInvoker)delegate
                {
                    txtOutput1.Text = (result[4] == 0x00 ? "HIGH" : "LOW"); // runs on UI thread

                    if (result[4] == 0x00)
                    {
                        this.Controls["btn" + buttonNumber].BackColor = Color.Green;
                    }
                    else
                    {
                        this.Controls["btn" + buttonNumber].BackColor = Color.Red;
                    }


                });

            }

在我将某些方法移动到从接口继承的新类之前,这一直很好。我不想只将动态按钮设置为公开,我不确定我是否可以创建 get;set;对于动态按钮,考虑到它们有很多并且是在启动时创建的。另一个问题是 this.invoke" 命令。我相信调用命令不起作用,除非它被放置在一个表单上......现在它已经被移动到一个类,所以我需要看看另一种方法。

有人知道我应该去哪里吗?

编辑 1:

该程序被设计为用于处理输入/输出的硬件设备的监控系统。使用这些我可以检查是否触发了门警报等。该程序本身在形式/设计方面非常简单。目前我有一个表单,它根据数据库中的信息生成按钮,例如,如果配置了 10 个设备,则有 10 个按钮。每个都显示绿色/红色,具体取决于硬件状态。

我的主窗体会为每个监控它的设备触发一个线程,但是因为我希望拥有多种类型的设备,所以我将它们移动到不同的类和一个处理所有常用方法的接口。目前我有一个设备类,它实现了一个接口。关于这个问题,我现在需要访问我正在更新的单个主表单的实例,而不是创建一个新实例,这样我就可以使用我在将所述逻辑移动到表单本身时创建的新方法.

编辑 2:

    IdeviceInterface bfdeviceimp = new bf2300deviceimp();
   // some other declarations and initialize components

 private void btnConnect_Click(object sender, EventArgs e)
    {
        updateUI();
    }

    public void updateUI()
    {

        DBConnector mDBConnector = new DBConnector();
        int count = mDBConnector.Count() - 1;
        DataTable dataTable = mDBConnector.Select("SELECT * FROM devices");


        int x = 12;
        int y = 65;
        for (int i = 0; i <= count && i < 25; i++)
        {

            Button btnAdd = new Button();
            btnAdd.Text = dataTable.Rows[i]["deviceDescription"].ToString();
            btnAdd.Location = new Point(x, y);
            btnAdd.Tag = i;
            btnAdd.Name = "btn" + i.ToString();
            btnAdd.BackColor = Color.Green;
            var temp = i + 1;
            this.Controls.Add(btnAdd);

            this.Controls[btnAdd.Name].MouseClick += (sender, e) =>
            {
                int index = temp;
                generalMethods.generatePopup(sender, e, index);
            };

            string address = dataTable.Rows[i]["deviceIP"].ToString();
            int port = int.Parse(dataTable.Rows[i]["devicePort"].ToString());


            ThreadStart workerThread = delegate { start(address, port, i); };
            new Thread(workerThread).Start();

            x = (x + 75);
            if (i != 0 && (i % 5) == 0)
            {
                x = 12;
                y = y + 30;
            }
            if (i == 25)
            {
                Button btnPreviousPage = new Button();
                btnPreviousPage.Text = "<";
                btnPreviousPage.Location = new Point(150, 350);
                btnPreviousPage.Tag = "left";
                this.Controls.Add(btnPreviousPage);

                Button btnNextPage = new Button();
                btnNextPage.Text = ">";
                btnNextPage.Location = new Point(225, 350);
                btnNextPage.Tag = "right";
                this.Controls.Add(btnNextPage);
            }
        }
    }
    public void start(string address, int port, int i)
    {
        if (timer == null)
        {
            timer = new System.Timers.Timer(1000);


            timer.Elapsed += delegate(object sender, ElapsedEventArgs e) { timerElapsed(sender, e, address, port, i); };
        }
        timer.Enabled = true;
        // MessageBox.Show("Thread " + i + " Started.");
    }
    public void timerElapsed(object sender, ElapsedEventArgs e, string address, int port, int i)
    {
        bfdeviceimp.newconnect(address, port, i);
    }

最后是我的设备类:

   class bf2300deviceimp : IdeviceInterface
{
   public void newconnect(string address, int port, int buttonNumber)
    {
        //send data
        byte[] bData = new byte[71];
        bData[0] = 240;
        bData[1] = 240;
        bData[2] = 0;
        bData[3] = 1;
        bData[68] = 240;
        bData[69] = 240;
        bData[70] = this.newCalculateCheckSum(bData);


        try
        {
            byte[] result = this.newSendCommandResult(address, port, bData, 72);

           //form1.setAlarmColour(result, buttonNumber);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }

    }

你建议我把 statechanged 处理程序放在哪里?

【问题讨论】:

    标签: c# forms interface controls invoke


    【解决方案1】:

    您应该使用基于事件的方法来解决此问题,在表单之间传递信息时通常会出现这种情况。您的每个设备都应该有一个自定义事件,它们定义了当该设备的状态发生变化时会触发该事件。该事件可能应该只在与该设备交互的接口中定义。表单在创建各种设备类时应该订阅事件,并且在事件处理程序中它应该适当地更新按钮/文本框。

    如果您不习惯这种编程风格,这可能是一个不错的选择。请随时在 cmets 中询问更多详细信息,我可以详细说明为什么我会以我的方式做某事或实际做了什么。

    public Form1()
    {
        InitializeComponent();
    
        //not sure if this is on initialization or in a button click event handler or wherever.
        IDevice device = new SomeDevice();
        device.StatusChanged += GetHandlerForDevice(1);
        device.DoStuff();
    
        IDevice device2 = new SomeDevice(); //could be another class that implements IDevice
        device.StatusChanged += GetHandlerForDevice(2);
        device.DoStuff();
    }
    
    /// <summary>
    /// The handlers for device status changed only vary based on the button number for each one.
    /// This method takes a button number and returns an event handler that uses that button number.
    /// </summary>
    /// <param name="buttonNumber"></param>
    /// <returns></returns>
    private EventHandler<StatusChangedEventArgs> GetHandlerForDevice(int buttonNumber)
    {
        //use currying so that the event handler which doesn't have an appropriate signature
        //can be attached to the status changed event.
        return (sender, args) => device_StatusChanged(sender, args, buttonNumber);
    }
    
    private void device_StatusChanged(object sender, StatusChangedEventArgs args, int buttonNumber)
    {
        this.Invoke((MethodInvoker)delegate
        {
            txtOutput1.Text = (args.CurrentStatus == IDevice.Status.Green ? "HIGH" : "LOW"); // runs on UI thread
    
            if (args.CurrentStatus == IDevice.Status.Green)
            {
                this.Controls["btn" + buttonNumber].BackColor = Color.Green;
            }
            else
            {
                this.Controls["btn" + buttonNumber].BackColor = Color.Red;
            }
    
    
        });
    }
    
    
    
    public interface IDevice
    {
        event EventHandler<StatusChangedEventArgs> StatusChanged;
        Status CurrentStatus { get; }
    
    
        public enum Status
        {
            Green,
            Red
        }
    
        void DoStuff();
        // rest of interface ...
    }
    
    public class StatusChangedEventArgs : EventArgs
    {
        public IDevice.Status CurrentStatus { get; set; }
        //can add additional info to pass from an IDevice to a form if needed.
    }
    
    public class SomeDevice : IDevice
    {
        public event EventHandler<StatusChangedEventArgs> StatusChanged;
    
        private IDevice.Status _currentStatus;
        /// <summary>
        /// Gets the current status of the device this object represents.
        /// When set (privately) it fires the StatusChanged event.
        /// </summary>
        public IDevice.Status CurrentStatus
        {
            get { return _currentStatus; }
            private set
            {
                _currentStatus = value;
                if (StatusChanged != null)
                {
                    StatusChangedEventArgs args = new StatusChangedEventArgs();
                    args.CurrentStatus = value;
                    StatusChanged(this, args);
                }
            }
        }
    
    
        public void DoStuff()
        {
            //... do stuff
            CurrentStatus = IDevice.Status.Green; //will fire status changed event
        }
    }
    

    【讨论】:

    • 我假设我必须创建一个新的表单实例才能从类中访问方法?这会导致任何问题吗?
    • 如果您没有任何表单实例,那么您打算更新什么文本框/按钮?你想得到你想改变的任何形式的实例。最终,无论您尝试做什么,都需要这样做。
    • 对不起,我的评论可能没有意义,我的意思是。做类似的事情; BF2300 形式1 = 新的 BF2300(); form1.setAlarmColour(result, buttonNumber);创建表单的新实例,还是获取我希望更新的当前实例?
    • @Shane'Shamus'Coulter 创建了一个新表格。由于我们对您的程序的结构一无所知,因此我无法明智地告诉您应该如何适当地访问表单的实例。
    • 无论如何,谢谢,解决了我的问题,只需要对表单访问做一点阅读,但就我最初的问题而言,解决了。
    【解决方案2】:

    将所有逻辑移到表单中的方法内部,并在外部使用。

    【讨论】:

      【解决方案3】:

      在您的表单中创建一个属性

      public SynchronizationContext SyncContext { get; set;}
      

      在表单构造函数中添加:

      this.SyncContext = WindowsFormsSynchronizationContext.Current;
      

      制作界面:

      public Interface IChangeClient 
      {
          void Process(<some_type> result); // place your logic here
      }
      

      (或类似的东西)并在您的表单中实现它以更改您的按钮和文本。

      扩展您的原始界面以使用(可能作为参数)SynchronizationContextIBackgroundChangeClient

      你的代码看起来像这样:

      if (result != null)
      {
          oSyncContext.Post(new System.Threading.SendOrPostCallback(
                  delegate(object state)
                  {
                      IBackgroundChangeClient client = (state as object[])[0] as IBackgroundChangeClient
                      //i dont konw the type of this
                      var innerResult= (state as object[])[1];
                      client.Process(innerResult);
                  }), new object[] { oBackgroundChangeClient, result[4]});
      
      }
      

      【讨论】:

      • 文本框也是同一个表单的一部分。几乎整个 sn-p 发布的代码只是 UI 更改。业务逻辑都在设置变量(未显示)。我不明白您为什么不希望将显示的整个 sn-p 移到表单中。
      • 您为什么认为调用者有责任将所有内容移至 UI 线程?为什么不将这种逻辑保留在表单的方法中。如果你这样做会更容易,而且在我看来,这会让 UI 组件自己正确管理它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-12-04
      • 2017-11-02
      • 2013-04-07
      • 1970-01-01
      • 2018-12-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多