关于c#:访问Windows窗体中另一个窗体上的控件的最佳方法?

关于c#:访问Windows窗体中另一个窗体上的控件的最佳方法?

Best way to access a control on another form in Windows Forms?

首先,这是一个有关使用Windows窗体的桌面应用程序的问题,而不是ASP.NET问题。

我需要与其他表单上的控件进行交互。我试图通过使用以下方式访问控件:

1
otherForm.Controls["nameOfControl"].Visible = false;

它不符合我的预期。我最终从Main引发了异常。但是,如果我将控件设为public而不是private,则可以直接访问它们,因此...

1
otherForm.nameOfControl.Visible = false;

但这是最好的方法吗?以其他形式制作控件public是否被视为"最佳实践"?是否存在"更好"的方式来访问其他表单上的控件?

进一步说明:

这实际上是对我提出的另一个问题(在C#中创建"树视图首选项对话框"类型的接口的最佳方法)的一种后续措施。我得到的答案很好,解决了我在保持UI直且易于在运行时和设计时使用的组织方面遇到的许多组织问题。但是,它确实提出了一个容易控制接口其他方面的难题。

基本上,我有一个根表单,可以实例化许多其他表单,这些其他表单都位于该根表单的面板中。因此,例如,这些子窗体之一上的单选按钮可能需要更改主根窗体上状态条图标的状态。在这种情况下,我需要子窗体与父(根)窗体的状态栏中的控件进行对话。 (我希望这是有道理的,而不是以"谁在第一"的方式。)


您可以创建一个控制其可见性的属性,而不是将控件公开。

1
2
3
4
5
public bool ControlIsVisible
{
     get { return control.Visible; }
     set { control.Visible = value; }
}

这将为该控件创建一个适当的访问器,该访问器不会公开该控件的整个属性集。


我个人建议不要这样做...如果它正在响应某种动作,并且需要更改其外观,则我更喜欢引发一个事件并让其自行解决...

形式之间的这种联系总是让我感到紧张。我总是尝试使用户界面尽可能的简洁和独立。

我希望这有帮助。如果没有,也许您可??以扩展该方案?


首先当然是行不通的。表单上的控件是私有的,设计上仅对该表单可见。

将其全部公开也不是最好的方法。

如果我想将某些东西暴露给外部世界(这也可能意味着另一种形式),那么我将其设为公共财产。

1
2
3
4
5
public Boolean nameOfControlVisible
{
    get { return this.nameOfControl.Visible; }
    set { this.nameOfControl.Visible = value; }
}

您可以使用此公共属性来隐藏或显示控件,或询问控件当前的可见性属性:

1
otherForm.nameOfControlVisible = true;

您也可以公开完整的控件,但是我认为这太多了,您应该仅在当前表单外部显示真正要使用的属性。

1
2
3
4
5
public ControlType nameOfControlP
{
    get { return this.nameOfControl; }
    set { this.nameOfControl = value; }
}

在阅读了其他详细信息之后,我同意robcthegeek的观点:提出一个事件。创建一个自定义EventArgs并通过它传递必要的参数。


假设您有两种形式,并且想通过另一种形式隐藏一种形式的属性:

1
2
3
form1 ob = new form1();
ob.Show(this);
this.Enabled= false;

当您想通过form2按钮将焦点移回form1时,则:

1
2
3
Form1 ob = new Form1();
ob.Visible = true;
this.Close();

我会以父表格的形式处理。您可以通过事件通知其他表单需要修改自身。


  • 使用事件处理程序来通知其他表单来处理它。
  • 在子窗体上创建一个公共属性,并从父窗体(具有有效的强制转换)访问它。
  • 在子窗体上创建另一个构造函数以设置窗体的初始化参数
  • 创建自定义事件和/或使用(静态)类。
  • 如果您使用的是非模式形式,则最佳做法是#4。


    我同意为此使用事件。由于我怀疑您正在构建MDI应用程序(因为您创建了许多子窗体)并动态创建了窗口,并且可能不知道何时取消订阅事件,因此建议您看一下弱事件模式。 las,这仅适用于框架3.0和3.5,但是使用弱引用可以轻松实现类似的操作。

    但是,如果您想基于表单的引用在表单中找到控件,仅查看表单的控件集合是不够的。由于每个控件都有其自己的控件集合,因此您将必须递归遍历所有控件以找到特定的控件。您可以使用这两种方法(可以改进)来执行此操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public static Control FindControl(Form form, string name)
    {
        foreach (Control control in form.Controls)
        {
            Control result = FindControl(form, control, name);

            if (result != null)
                return result;
        }

        return null;
    }

    private static Control FindControl(Form form, Control control, string name)
    {
        if (control.Name == name) {
            return control;
        }

        foreach (Control subControl in control.Controls)
        {
            Control result = FindControl(form, subControl, name);

            if (result != null)
                return result;
        }

        return null;
    }

    使用属性(突出显示),可以获取MainForm类的实例。但这是一个好习惯吗?您有什么推荐的吗?

    为此,我使用在OnLoad方法上运行的MainFormInstance属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using LightInfocon.Data.LightBaseProvider;
    using System.Configuration;

    namespace SINJRectifier
    {

        public partial class MainForm : Form
        {
            public MainForm()
            {
                InitializeComponent();
            }

            protected override void OnLoad(EventArgs e)
            {
                UserInterface userInterfaceObj = new UserInterface();
                this.chklbBasesList.Items.AddRange(userInterfaceObj.ExtentsList(this.chklbBasesList));
                MainFormInstance.MainFormInstanceSet = this; //Here I get the instance
            }

            private void btnBegin_Click(object sender, EventArgs e)
            {
                Maestro.ConductSymphony();
                ErrorHandling.SetExcecutionIsAllow();
            }
        }

        static class MainFormInstance  //Here I get the instance
        {
            private static MainForm mainFormInstance;

            public static MainForm MainFormInstanceSet { set { mainFormInstance = value; } }

            public static MainForm MainFormInstanceGet { get { return mainFormInstance; } }
        }
    }

    您可以

  • 在子表单上创建带有所需参数的公共方法,并从父表单中调用它(使用有效的强制转换)
  • 在子窗体上创建一个公共属性,并从父窗体访问它(使用有效的强制转换)
  • 在子窗体上创建另一个构造函数以设置窗体的初始化参数
  • 创建自定义事件和/或使用(静态)类
  • 如果您使用的是非模式形式,则最佳做法是#4。


    您的孩子表格真的需要表格吗?他们可以改为用户控件吗?这样,它们可以轻松地引发事件以供主窗体处理,并且您可以更好地将其逻辑封装到单个类中(至少在逻辑上,它们已经存在于所有类中)。

    @拉尔斯:你在这里。这是我在开始的时候就做过的事情,从那以后就不需要这样做了,这就是为什么我最初建议引发一个事件,但是我的另一种方法实际上会破坏任何封装的表象。

    @Rob:是的,听起来不错:)。这个上的0/2 ...


    @Lars,在传递Form引用时很好,我自己也看到了。讨厌。没见过他们将它们传递到BLL层!那根本没有意义!那会严重影响性能吧?如果在BLL的某处保留了引用,则该表格将保留在内存中,对吗?

    你有我的同情! ;)

    @Ed,请回复您对制作Forms UserControls的评论。 Dylan已经指出,根表单会实例化许多子表单,给人以MDI应用程序的印象(我假设用户可能要关闭各种表单)。如果我对此假设是正确的,我认为最好将它们保留为表格。当然可以纠正:)


    这看起来像是将表示与数据模型分离的主要候选对象。在这种情况下,您的首选项应存储在一个单独的类中,该类将在特定属性发生更改时触发事件更新(如果您的属性是离散集,请查看INotifyPropertyChanged;如果它们是更多自由格式的基于文本的键,请查看单个事件)。

    在树状视图中,您将对首选项模型进行更改,然后将触发一个事件。在其他表单中,您将订阅您感兴趣的更改。在用于订阅属性更改的事件处理程序中,您将使用this.InvokeRequired来查看您是否在创建UI的正确线程上调用,如果没有,则使用this.BeginInvoke调用所需的方法来更新表单。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void Enable_Usercontrol1()
    {
        UserControl1 usercontrol1 = new UserControl1();
        usercontrol1.Enabled = true;
    }
    /*
        Put this Anywhere in your Form and Call it by Enable_Usercontrol1();
        Also, Make sure the Usercontrol1 Modifiers is Set to Protected Internal
    */

    第1步:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    string regno, exm, brd, cleg, strm, mrks, inyear;

    protected void GridView1_RowEditing(object sender, GridViewEditEventArgs e)
    {
        string url;
        regno = GridView1.Rows[e.NewEditIndex].Cells[1].Text;
        exm = GridView1.Rows[e.NewEditIndex].Cells[2].Text;
        brd = GridView1.Rows[e.NewEditIndex].Cells[3].Text;
        cleg = GridView1.Rows[e.NewEditIndex].Cells[4].Text;
        strm = GridView1.Rows[e.NewEditIndex].Cells[5].Text;
        mrks = GridView1.Rows[e.NewEditIndex].Cells[6].Text;
        inyear = GridView1.Rows[e.NewEditIndex].Cells[7].Text;

        url ="academicinfo.aspx?regno=" + regno +"," + exm +"," + brd +"," +
              cleg +"," + strm +"," + mrks +"," + inyear;
        Response.Redirect(url);
    }

    第2步:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            string prm_string = Convert.ToString(Request.QueryString["regno"]);

            if (prm_string != null)
            {
                string[] words = prm_string.Split(',');
                txt_regno.Text = words[0];
                txt_board.Text = words[2];
                txt_college.Text = words[3];
            }
        }
    }

    将修饰符从公共更改为内部。 .Net故意使用private修饰符而不是public修饰符,因为它可以防止从项目中非法访问您的方法/属性/控件。实际上,public修饰符可以在任何地方访问,因此它们确实很危险。项目外的任何人都可以访问您的方法/属性。但是在内部修饰符中,没有主体(当前项目的其他主体)可以访问您的方法/属性。

    假设您正在创建一个项目,其中包含一些秘密字段。因此,如果您无法从项目中访问这些字段,则可能很危险,并且有悖于您的最初想法。作为一个好的建议,我可以说总是使用内部修饰符而不是公共修饰符。

    但是有些奇怪!

    我还必须在VB.Net中告诉我们,虽然我们的方法/属性仍然是私有的,但可以通过将form作为变量进行调用而从其他表单/类访问它,而没有其他任何问题。

    我不知道为什么在这种编程语言中行为不同于C#。众所周知,两者都使用相同的平台,并且声称它们几乎是相同的后端平台,但是正如您所看到的,它们的行为仍然有所不同。

    但是我已经通过两种方法解决了这个问题。要么;通过使用Interface(不建议这样做,正如您所知,Interface通常需要public修饰符,并且不建议使用public修饰符(如我在上文所述),

    要么

    在某个静态类和静态变量中声明整个Form,并且仍然有内部修饰符。然后,当您想使用该表单向用户显示时,请将新的Form()构造传递给该静态类/变量。现在,您可以在任意位置访问它。但是您仍然需要更多东西。
    您也可以在"表单设计器文件"中声明元素内部修饰符。表单打开时,可以在任何地方访问它。它可以为您很好地工作。

    考虑这个例子。

    假设您要访问一个窗体的文本框。

    因此,第一项工作是在静态类中声明静态变量(static的原因是易于访问,将来无需使用任何新的键)。

    其次转到该Form的设计器类,该类可能被其他Form访问。将其TextBox修饰符声明从private更改为internal。不用担心更改后,.Net永远不会再将其更改为private修饰符。

    第三,当您要调用该表单打开时,请将新的Form Construction传递给该静态变量->> static类。

    第四;从任何其他窗体(无论在项目中的任何地方)都可以从"打开"中访问该窗体/控件。

    看下面的代码(我们有三个对象。
    1-一个静态类(在我们的示例中,我们将其命名为A)

    2-任何其他要打开最终窗体的窗体(在我们的示例FormB中,都有TextBox)。

    3-我们需要打开的真实窗体,我们假设要访问其内部TextBox1(在我们的示例中为FormC)。

    看下面的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    internal static class A
    {
        internal static FormC FrmC;
    }

    FormB ...
    {
        '(...)
        A.FrmC = new FormC();
        '
    (...)
    }

    FormC (Designer File) . . .
    {
         internal System.Windows.Forms.TextBox TextBox1;
    }

    打开FormC时,可以随时随地访问该静态变量(此处为FormC)及其内部控件(此处为TextBox1)。

    任何意见/想法会让我知道。我很高兴收到您或其他组织的更多信息。老实说,过去我对上述问题有一些疑问。最好的方法是第二个解决方案,希望它对您有用。让我知道任何新想法/建议。


    如果要创建更复杂的控件/模块/组件,则只能访问另一个视图的内容。否则,您应该通过标准的"模型-视图-控制器"体系结构来执行此操作:您应该将您关注的控件的启用状态连接到提供正确信息的某些模型级谓词。

    例如,如果我只想在输入所有必需信息时才启用"保存"按钮,那么我将有一个谓词方法来告知表示该表单的模型对象何时处于可以保存的状态。然后,在我要选择是否启用按钮的情况下,我只会使用该方法的结果。

    这样可以使业务逻辑与表示逻辑更加清晰地分离,从而使它们都可以更独立地发展-让您轻松地创建具有多个后端的一个前端,或轻松地创建具有单个后端的多个前端。

    编写单元测试和验收测试也将变得非常容易,因为您可以遵循"信任但验证"模式:

  • 您可以编写一组以各种方式设置模型对象的测试,并检查"可保存"谓词是否返回适当的结果。

  • 您可以编写另一组检查,以检查"保存"按钮是否以适当的方式连接到"可保存"谓词(无论是哪种框架,在Mac OS X上的Cocoa中,通常都是通过绑定)。

  • 只要这两组测试都通过了,您就可以确信您的用户界面将按您希望的方式工作。


    推荐阅读