在Windows窗体中访问另一个窗体上控件的最佳方法?

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

我需要与其他forms的控件进行交互。 我想通过使用,例如,以下访问控件…

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

它不会像我所期望的那样工作。 我最终从Main抛出一个exception。 但是,如果我把这些控件public而不是private ,我可以直接访问它们,就像这样…

 otherForm.nameOfControl.Visible = false; 

但是,这是做到这一点的最好方法吗? 是否将控制权视为“最佳实践”? 有没有一种“更好”的方式来访问另一种forms的控制?

进一步说明:

这实际上是对我问的另一个问题的后续, 在C#中创build一个“树视图首选项对话框”types的接口的最佳方法? 。 我得到的答案非常好,解决了我在保持界面直观和易于在运行时和devise时都可以工作的许多组织问题。 但是,它确实带来了这个容易控制界面其他方面的小问题。

基本上,我有一个根forms,它实例化了许多其他forms,位于根表单上的一个面板上。 因此,例如,其中一个子窗体上的单选button可能需要更改主根表单上状态条图标的状态。 在这种情况下,我需要子窗体在父(根)forms的状态栏中与控件进行交谈。 (我希望这是有道理的,而不是以“谁是最先”的方式)。

您可以创build一个控制其可见性的属性,而不是公开控制:

 public bool ControlIsVisible { get { return control.Visible; } set { control.Visible = value; } } 

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

我个人会build议要这样做…如果它正在响应某种行动,并且需要改变它的外观,我宁愿提出一个事件,让它自行sorting…

这种forms之间的耦合总是让我紧张。 我总是尽可能让UI保持轻量和独立

我希望这有帮助。 如果不是,也许你可以扩大这种情况?

第一个当然不行。 窗体上的控件是私有的,只有在devise时才可见。

把它全部公开也不是最好的办法。

如果我想向外界揭示一些东西(也可能是另外一种forms),我就为它做一个公共财产。

 public Boolean nameOfControlVisible { get { return this.nameOfControl.Visible; } set { this.nameOfControl.Visible = value; } } 

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

 otherForm.nameOfControlVisible = true; 

你也可以公开所有的控件,但是我认为它太多了,你只能从当前窗体外面看到你真正想要使用的属性。

 public ControlType nameOfControlP { get { return this.nameOfControl; } set { this.nameOfControl = value; } } 

在阅读了更多细节之后,我同意robethegeek :举办一个活动。 创build一个自定义EventArgs并通过它传递必要的参数。

假设你有两种forms,你想通过另一种forms隐藏一个表单的属性:

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

当你想通过form2button来获得form1的焦点时,

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

我会在父窗体中处理这个。 您可以通过其他forms通知其需要通过事件修改自己。

  1. 使用事件处理程序来通知其他表单来处理它。
  2. 在子窗体上创build一个公共属性,并从父窗体访问它(使用有效的强制转换)。
  3. 在子窗体上创build另一个构造函数来设置窗体的初始化参数
  4. 创build自定义事件和/或使用(静态)类。

如果您使用的是非模态表单,最好的做法是#4。

您可以

  1. 在子窗体上创build一个带有所需参数的公共方法,并从父窗体调用它(使用有效的投射)
  2. 在子窗体上创build一个公共属性,并从父窗体访问它(使用有效的投射)
  3. 在子窗体上创build另一个构造函数来设置窗体的初始化参数
  4. 创build自定义事件和/或使用(静态)类

如果您使用的是非模态表单,最佳做法是#4。

通过属性(突出显示),我可以获得MainForm类的实例。 但这是一个很好的做法? 你有什么build议?

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

 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; } } } } 

我同意使用这个事件。 由于我怀疑你正在构build一个MDI应用程序(因为你创build了许多子窗体),并dynamic地创build窗口,并且可能不知道什么时候退订事件,所以我build议你看看弱事件模式 。 唉,这只适用于框架3.0和3.5,但类似的东西可以很容易地实现与弱引用。

但是,如果您想根据表单的引用来查找表单中的控件,仅仅查看表单的控件集合是不够的。 由于每个控件都有自己的控件集合,所以你必须通过它们来find一个特定的控件。 你可以用这两种方法做到这一点(可以改进)。

 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; } 

@Lars,很好地呼吁在表单引用周围传递,看到它以及我自己。 讨厌。 从未见过他们把它们传递到BLL层! 这甚至没有道理! 这可能会严重影响业绩吗? 如果BLL中的某个地方保留了参考,那么这个表单会留在记忆里吗?

你有我的同情! ;)


@Ed,RE关于制作Forms UserControls的评论。 Dylan已经指出,根表单实例化许多子窗体,给人一种MDI应用程序的印象(我假设用户可能想closures各种表单)。 如果我在这个假设中是正确的,我会认为他们最好保持为forms。 当然开放纠正虽然:)

你的孩子的forms真的需要forms吗? 他们可以是用户控制呢? 通过这种方式,他们可以轻松地为主要表单提交事件,并且可以更好地将其逻辑封装到一个类中(至less在逻辑上,它们毕竟已经是类)。

@拉尔斯:你就在这里。 这是我在刚开始的时候做的事情,因为这是我第一次提出一个事件的原因,但是我的另一种方法真的会打破任何封装的外观。

@Rob:是的,听起来是正确的:)。 0/2在这个…

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

例如,如果我只想在input所有必需的信息时启用保存button,我就有一个谓词方法,告诉何时表示该表单的模型对象处于可以保存的状态。 然后在我select是否启用button的上下文中,我只是使用该方法的结果。

这样可以使业务逻辑与表示逻辑更清晰地分离,从而使他们两者能够更独立地发展 – 让您创build一个具有多个后端的前端,或者轻松创build具有单个后端的多个前端。

编写单元和验收testing也会容易得多,因为您可以按照“ 信任但validation ”的模式进行:

  1. 您可以编写一组testing,以各种方式设置模型对象,并检查“可保存”谓词是否会返回适当的结果。

  2. 你可以编写一个单独的集合来检查你的保存button是否以适当的方式连接到“可保存的”谓词(无论对于你的框架,在Mac OS X上的cocoa,通常是通过绑定)。

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

这看起来像是将演示文稿从数据模型中分离出来的主要候选者。 在这种情况下,您的首选项应该存储在一个单独的类中,以便在特定属性发生更改时触发事件更新(如果属性是离散集合,请查看INotifyPropertyChanged;如果它们是更自由forms的基于文本的关键字)。

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

步骤1:

 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步:

 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故意使用私有修饰符,而不是公共的,因为防止任何非法访问您的方法/属性/控制出你的项目。 事实上,公共修饰符可以在任何地方访问,所以它们是非常危险的。 您的项目中的任何机构都可以访问您的方法/属性。 但是在内部修饰符中,没有任何主体(你当前的其他项目)可以访问你的方法/属性。

假设你正在创build一个项目,它有一些秘密的领域。 所以如果这些领域可以从您的项目中获得,那么这可能是危险的,并且违背了您的初步想法。 作为一个很好的build议,我可以说总是使用内部修饰符而不是公共修饰符。

但有些奇怪!

我必须告诉VB.Net,而我们的方法/属性仍然是私人的,它可以从其他窗体/类通过调用窗体作为variables访问,没有任何其他问题。

我不知道为什么在这个编程语言中,行为与C#不同。 正如我们所知,两家公司都使用相同的平台,他们声称他们几乎是相同的后端平台,但正如你所看到的,他们仍然有不同的performance。

但是我用两种方法解决了这个问题。 无论; 通过使用接口(这不是一个推荐,如你所知,接口通常需要公共修饰符,并不推荐使用公共修饰符(正如我上面告诉你的)),

要么

声明你的整个表单在静态类和静态variables的地方,还有内部修饰符。 那么当你想用这个表单来显示给用户的时候,所以把新的Form()构造传递给那个静态类/variables。 现在,它可以随时随地访问。 但是你还需要更多的东西。 你也可以在Form的Form File中声明你的元素内部修饰符。 当你的表格是开放的,它可以在任何地方访问。 它可以为你工作得很好。

考虑这个例子。

假设你想访问一个表单的文本框。

所以第一个工作就是在一个静态类中声明一个静态variables(静态的原因是在将来没有任何使用新的密钥的情况下访问的方便性)。

其次去devise其他forms访问的Form的devise器类。 将其TextBox修饰符声明从private改为internal。 别担心 .Net在改变之后永远不会再将它修改为私有修改器。

第三,当你想调用该窗体打开时,将新的窗体构造传递给该静态variables – 静态类。

第四; 从任何其他表格(无论您在哪个项目中),您都可以访问该表格/控件。

看下面的代码(我们有三个对象:1-一个静态类(在我们的例子中,我们把它命名为A)

2 – 任何想要打开最终forms(其中有文本框)的其他forms(在我们的示例FormB)。

3 – 我们需要打开的真正forms,我们假设访问其内部的TextBox1(在我们的例子FormC)。

看下面的代码:

内部静态类A {

 internal static FormC FrmC; 

}

FormB … {。 。 。

A.FrmC = new FormC();

。 。 。

}

FormC(devise器文件)。 。 。 {

  internal System.Windows.Forms.TextBox TextBox1; 

}

您可以随时随地访问该静态variables(此处FormC)及其内部控件(Here Textbox1),而FormC处于打开状态。


任何意见/想法让我知道。 我很高兴收到您或任何其他机构有关此主题的更多信息。 老实说,我过去曾经遇到过这个问题。 最好的方法是希望它能为你工作的第二个解决scheme。 让我知道任何新的想法/build议。

谢谢,并最好的问候

Mansoor Bozorgmehr

mansour.bozorgmehr@gmail.com

 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 */