C# 当子窗体存在多个实例时,如何唯一标识该窗体中的控件

C# 当子窗体存在多个实例时,如何唯一标识该窗体中的控件,c#,uniqueidentifier,mdi,multiple-instances,C#,Uniqueidentifier,Mdi,Multiple Instances,我正在开发一个MDI应用程序,用户可以在其中创建同一表单的多个实例(称为ListForm)。ListForm的每个实例都有一个flowlayoutpanel,其中包含一组唯一的用户控件。ListForm还包含一个StatusStrip ProgressBar和一个名为“ReadAll”的按钮 每个用户控件都有一个“读取”按钮,单击该按钮时将执行读取操作。此操作最多需要3秒钟才能完成 我试图做的是,当用户单击“ReadAll”按钮时,子窗体生成一个后台线程,该线程通过flowlayoutpanel

我正在开发一个MDI应用程序,用户可以在其中创建同一表单的多个实例(称为ListForm)。ListForm的每个实例都有一个flowlayoutpanel,其中包含一组唯一的用户控件。ListForm还包含一个StatusStrip ProgressBar和一个名为“ReadAll”的按钮

每个用户控件都有一个“读取”按钮,单击该按钮时将执行读取操作。此操作最多需要3秒钟才能完成

我试图做的是,当用户单击“ReadAll”按钮时,子窗体生成一个后台线程,该线程通过flowlayoutpanel.controls集合进行迭代,并调用每个用户的controls.PerformClick()方法。这将更新表单中的所有用户控件

问题在于,似乎正在调用表单所有实例的事件处理程序,从而更新ListForm所有实例中的所有用户控件。此外,当我从backgroundworker报告Progress时,ListForm的所有实例的所有ProgressBar都会更新。不需要此功能

如何确保只更新生成backgroundworker的ListForm?是否有唯一标识子窗体的首选方法

提前感谢你的帮助。代码如下

public partial class ListForm: Form
{
    // Background Worker Thread for Read / Write All tasks
    private static BackgroundWorker bw = new BackgroundWorker();

    public ListForm()
    {
        InitializeComponent();

        // Configure the Background Worker that reads and writes all variable data
        bw.WorkerReportsProgress = true;
        bw.WorkerSupportsCancellation = true;
        bw.DoWork += new DoWorkEventHandler(bw_DoWork);
        bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);


    }

    private void btnReadAll_Click(object sender, EventArgs e)
    {

        if (bw.IsBusy != true)
        {
            // Start the ReadAll parameters thread
            btnReadAll.Text = "Cancel Read";
            btnWriteAll.Enabled = false;
            bw.RunWorkerAsync("R");
        }
        else if (bw.WorkerSupportsCancellation == true)
        {
            // Cancel the ReadAll parameters thread
            bw.CancelAsync();
        }
    }

    // ******************************  Background Thread Methods ***************************
    public delegate void DoUIWorkHandler();

    private void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        DoUIWorkHandler DoReadClick;
        DoUIWorkHandler DoWriteClick;

        int CurrentControlCount = 1;
        string StatusText = "";
        int ProgressValue = 0;
        string argument = e.Argument as string;

        // *******************Perform a time consuming operation and report progress. 
        try
        {
            foreach (UserControl c in this.flowLayoutPanel1.Controls)
            {
                if ((worker.CancellationPending == true))
                {
                    e.Cancel = true;
                    break;
                }
                else
                {
                    // Update the status and return it to the UI
                    StatusText = "Updating: (" + (CurrentControlCount).ToString() + " of " + flowLayoutPanel1.Controls.Count.ToString() + ") " + c.ParamProperties.strDHIndexDescription;
                    ProgressValue = (int)(((float)CurrentControlCount / (float)flowLayoutPanel1.Controls.Count) * 100);
                    worker.ReportProgress(ProgressValue, StatusText);
                    System.Threading.Thread.Sleep(20);
                    CurrentControlCount++;

                    // Update the contorl
                    if (c.InvokeRequired)
                    {
                        if (argument == "R")
                        {
                            DoReadClick = c.btnRead.PerformClick;
                            c.Invoke(DoReadClick);
                        }
                        else
                        {
                            DoWriteClick = c.btnWrite.PerformClick;
                            c.Invoke(DoWriteClick);
                        }

                    }
                }
            }
        }
        catch(InvalidCastException ex)
        {
            // Catch any functions that are in the Layout panel
            string ErrorStr = "Could not cast a Function control to a Parameter control. \n\r\r Exception: " + ex.Message;
            srvcAppLogger.Logger.Log(new clsApplicationLogger.LoggerMessage(ErrorStr, "bw_DoWork", "frmVariableHandlerGUI"));
        }
        catch (Exception ex)
        {
            string ErrorStr = "An unecpected exception occured. Error: " + ex.Message.ToString();
            srvcAppLogger.Logger.Log(new clsApplicationLogger.LoggerMessage(ErrorStr, "bw_DoWork", "frmVariableHandlerGUI"));
        }
    }

    private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        this.tsStatus.Text = e.UserState.ToString();
        this.tsProgressBar.Value = e.ProgressPercentage;
    }

您有一个BackgroundWorker实例,您创建的每个ListForm都已注册到此worker。因此,您必须将表单的实例传递给worker

创建一个带有两个属性的小助手类。这只是一个例子。您还可以传递标识符或任何您喜欢的内容:

public struct ReadAllArguments
{
    public bool Read;
    public ListForm CallingForm;

    public ReadAllArguments(bool read, ListForm callingForm)
    {
        Read = read; CallingForm = callingForm;
    }
}
你可以这样通过它:

...
    if (bw.IsBusy != true)
        {
            // Start the ReadAll parameters thread
            btnReadAll.Text = "Cancel Read";
            btnWriteAll.Enabled = false;
            bw.RunWorkerAsync(new ReadAllArguments(true, this));
        }
...
后来有人这样读:

private void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        DoUIWorkHandler DoReadClick;
        DoUIWorkHandler DoWriteClick;

        int CurrentControlCount = 1;
        string StatusText = "";
        int ProgressValue = 0;
        ReadAllArguments arguments = e.Argument as ReadAllArguments;
        if (this != arguments.ListForm)
          return;

        ...

                        if (arguments.Read)
                        {
                            DoReadClick = c.btnRead.PerformClick;
                            c.Invoke(DoReadClick);
                        }
                        else
                        {
                            DoWriteClick = c.btnWrite.PerformClick;
                            c.Invoke(DoWriteClick);
                        }
       ...
您会意识到,您甚至可以将工作方法移出表单,因为没有直接的依赖关系,您不需要访问“this”限定符。你在辩论中通过了一切。在用该参数替换每个“this”之后,您可以向工作人员的DoWork事件注册一个工作方法。这会更干净更优雅

下面是一个示例,说明如何做到这一点:

public partial class ListForm: Form
{
    // Background Worker Thread for Read / Write All tasks
    private static BackgroundWorker bw = new BackgroundWorker();

    static ListForm()
    {
        //We move the do-work out of the instance constructor, because the work that has to be done, is not connected to our instances. So we've only one definition of our work that has to be done
        bw.DoWork += new DoWorkEventHandler(TheWorkThatHasToBeDone);
    }

    public ListForm()
        {
             InitializeComponent();

             // Configure the Background Worker that reads and writes all variable data
             bw.WorkerReportsProgress = true;
             bw.WorkerSupportsCancellation = true;
             //no more registering on instance level 
             bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
             bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);    
        }

    //Your new instance-independent doWork-Method - static here
    private static void TheWorkThatHasToBeDone(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        ReadAllArguments arguments = e.Argument as ReadAllArguments;
        //You call the instance-Method here for your specific instance you want the work to be done for
        arguments.ListForm.bw_DoWork(worker, arguments);
    }


    //Your old bw_DoWork-Method with nicer arguments - you should change the method name...
    private void bw_DoWork(BackgroundWorker worker, ReadAllArguments arguments)
        {
            DoUIWorkHandler DoReadClick;
            DoUIWorkHandler DoWriteClick;

            int CurrentControlCount = 1;
            string StatusText = "";
            int ProgressValue = 0;

            // *******************Perform a time consuming operation and report progress. 
            try
            {
                ...
            }
        }

将内容从表单代码中移出而不使用静态成员会更优雅,但我认为想法很清楚。

要识别对象,可以使用HashCode或创建Id属性,然后在自定义EventArgs中使用它

private Guid _controlId;

    public ListForm()
    {
        _controlId = Guid.NewGuid();
        ...
    }
还可以尝试以这种方式记录事件观察家:

private void btnReadAll_Click(object sender, EventArgs e)
    {
        if (bw.IsBusy != true)
        {
            bw.DoWork += bw_DoWork;
            bw.ProgressChanged += bw_ProgressChanged);
            bw.RunWorkerCompleted +=bw_RunWorkerCompleted;

            // Start the ReadAll parameters thread
            btnReadAll.Text = "Cancel Read";
            btnWriteAll.Enabled = false;
            bw.RunWorkerAsync("R");
        }
        else if (bw.WorkerSupportsCancellation == true)
        {
            // Cancel the ReadAll parameters thread
            bw.CancelAsync();
        }

        bw.DoWork -= bw_DoWork;
        bw.ProgressChanged -= bw_ProgressChanged;
        bw.RunWorkerCompleted -= bw_RunWorkerCompleted;
    }

谢谢你的建议。再考虑一下,这似乎是最好的解决办法。传递表单实例后,可以直接引用该实例。我还必须将表单实例传递给ReportProgress方法,以验证要更新的实例。我明白了,为什么所有ListForm实例都响应backgroundworker对ReportProgress的调用?这是因为windows处理事件的方式吗?感谢您的帮助。这是因为您将它们中的每一个注册为其构造函数中后台工作程序的侦听器。bw.DoWork+=新DoWorkEventHandler(bw_DoWork);这里的bw_DoWork链接到表单的实例。它是:bw.DoWork+=newdoworkeventhandler(this.bw_DoWork)的缩写;因此,如果我跟随你,当工作者发布消息时,来自每个实例的事件处理程序都会响应,因为它们不会区分从工作者实例发布的消息(由其自身表单实例化)和由同一表单的不同实例发布的消息。如果这是真的,为了让表单实例化的事件处理程序只处理特定的工作进程,需要,您必须按照您的建议执行操作,并通过将表单实例传递给工作线程来检查表单实例,或者验证工作线程发布的消息来自与事件处理程序相同的表单实例实例化的工作线程实例。是否可以通过检查传递给事件处理程序的发送方参数来完成此验证?谢谢你的评论!