winforms 不同线程上的两个窗体如何访问彼此的控件

wfypjpf4  于 2023-08-07  发布在  其他
关注(0)|答案(1)|浏览(132)

我搜索了很长时间才找到从父窗体到子窗体的访问方法,反之亦然。但是当我的表单在不同的线程上时,它们就不起作用了。我需要两个窗体不总是覆盖另一个窗体,所以我在另一个线程上创建子窗体。以下是我的简化代码。无论如何要修改它们?提前感谢!

namespace WinFormsApp1
{
    public class FormA : Form
    {
        public static Thread th1;
        FormB FB = new FormB();

        public Button A1;
        public FormA()
        {
            A1 = new Button();
            A1.Click += new EventHandler(A1Click);
            this.Controls.Add(this.A1);

            Thread th1 = new Thread(this.OpenFormB);
            th1.Start();
        }
        void A1Click(Object sender, EventArgs e)
        {
            FB.B1Text("B1");
        }
        void OpenFormB()
        {
            FB.ShowDialog();    //FB.ShowDialog(this); reports cross-thread error
        }
        void A1Text(string text1)
        {
            if (this.A1.InvokeRequired)
            {
                Del1 STR1 = delegate { A1Text(text1); };
                this.Invoke(STR1, text1);
            }
            else
            {
                this.A1.Text = text1;
            }
        }
        delegate void Del1(string text1);

        public class FormB : Form
        {
            public Button B1;
            public FormB()
            {
                B1 = new Button();
                B1.Click += new EventHandler(B1Click);
                this.Controls.Add(this.B1);
            }
            void B1Click(Object sender, EventArgs e)
            {
                FormA FA = (FormA)this.Owner;   //'Owner' is not passed here because of the cross-thread error so the next command fails
                FA.A1Text("A1");
            }
            public void B1Text(string text1)
            {
                if (this.B1.InvokeRequired)
                {
                    Del1 STR1 = delegate { B1Text(text1); };
                    this.Invoke(STR1, text1);
                }
                else
                {
                    this.B1.Text = text1;
                }
            }
        }
    }
}

字符串
纠正我的代码以实现我的目的

xv8emn3q

xv8emn3q1#

这个答案分为两部分:

***使用多线程:**我保留了多线程,因为您的项目可能需要更大的规模
***不使用多线程:**与您要求的非常相似的操作可以在不首先使用多线程的情况下完成,从而避免跨线程访问异常。

支持多线程

正如你在评论中提到的,FB.ShowDialog(this);不工作,并导致“不能从另一个线程访问”错误。
这是因为,在这里传递Form A作为所有者时,Form B的Owner属性在Form B的线程中发生了更改。但是根据参考源,更改此属性会在新所有者上调用AddOwnedForm,这实际上意味着在Form B的线程上调用了Form A方法。
一个简单的解决方法是在表单B中有一个表单A引用,并在构造表单A时将其从表单A传递到表单B。
下面是我得到的代码:

public partial class FormA : Form
{
    public static Thread th1;
    FormB FB = null;

    public Button A1;
    public FormA()
    {
        InitializeComponent();

        A1 = new Button();
        A1.Click += new EventHandler(A1Click);
        this.Controls.Add(this.A1);

        FB = new FormB(this);

        Thread th1 = new Thread(this.OpenFormB);
        th1.Start();
    }

    void A1Click(Object sender, EventArgs e)
    {
        FB.B1Text("B1");
    }

    void OpenFormB()
    {
        FB.ShowDialog();
    }

    public void A1Text(string text1)
    {
        if (this.A1.InvokeRequired)
        {
            Del1 STR1 = delegate { A1Text(text1); };
            this.Invoke(STR1, text1);
        }
        else
        {
            this.A1.Text = text1;
        }
    }
    delegate void Del1(string text1);
}

public partial class FormB : Form
{
    public Button B1;
    
    FormA FA = null;

    public FormB(FormA owner)
    {
        InitializeComponent();

        B1 = new Button();
        B1.Click += new EventHandler(B1Click);
        this.Controls.Add(this.B1);

        this.FA = owner;
    }

    void B1Click(Object sender, EventArgs e)
    {
        FA.A1Text("A1");
    }

    public void B1Text(string text1)
    {
        if (this.B1.InvokeRequired)
        {
            Del1 STR1 = delegate { B1Text(text1); };
            this.Invoke(STR1, text1);
        }
        else
        {
            this.B1.Text = text1;
        }
    }
    delegate void Del1(string text1);
}

字符串
请注意,这是相当黑客,我们有效地只是创建一个替代所有者。

不支持多线程

您不需要多线程来同时存在两个窗体。
这里真实的的问题是使用FB.ShowDialog();来显示Form B。然而,正如the documentation for this method所说,当调用此方法时,它后面的代码直到对话框关闭后才会执行。
我想这就是为什么你想把它放在另一个线程上,这样它就可以和Form A沿着运行了。
但实际上,您可以使用Show方法以非阻塞方式显示Form B。
这有效地让您同时拥有两个表单,即使它们在同一线程上。
此外,Show有一个重载,它将IWin32Window作为所有者(Form类确实实现了IWin32Window)。
因此,您可以通过以下方式显示Form B:

FB.Show(this);


即使跨线程不是问题,它也不会工作,因为您在调用ShowDialog时没有将所有者作为参数传递,这就是为什么在Form B中尝试使用this.Owner时会崩溃,因为它具有默认值null
但是正如你在评论中提到的,FB.ShowDialog(this);崩溃是因为Form A和Form B不在同一个线程上。
这就是为什么使用Show是避免代码过于复杂的最佳解决方案。
下面是我得到的完整代码:

public partial class FormA : Form
{
    FormB FB = new FormB();

    public Button A1;
    public FormA()
    {
        InitializeComponent();

        A1 = new Button();
        A1.Click += new EventHandler(A1Click);
        this.Controls.Add(this.A1);

        FB.Show(this);
    }

    void A1Click(Object sender, EventArgs e)
    {
        FB.B1Text("B1");
    }

    public void A1Text(string text1)
    {
        this.A1.Text = text1;
    }
}

public partial class FormB : Form
{
    public Button B1;
    public FormB()
    {
        InitializeComponent();

        B1 = new Button();
        B1.Click += new EventHandler(B1Click);
        this.Controls.Add(this.B1);
    }

    void B1Click(Object sender, EventArgs e)
    {
        FormA FA = (FormA)this.Owner;
        FA.A1Text("A1");
    }

    public void B1Text(string text1)
    {
        this.B1.Text = text1;
    }
}


但是,请注意,将两个表单放在同一个线程上意味着当一个表单由于它正在执行的某些工作而被阻塞时,它也会阻塞另一个表单。

相关问题