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

时间:2008-08-12 07:32:33

标签: c# winforms controls

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

我需要与其他表单上的控件进行交互。我试图通过使用,例如,以下...来访问控件...

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

它不像我期望的那样工作。我最终得到了Main引发的异常。但是,如果我设置控件public而不是private,我可以直接访问它们,因为......

otherForm.nameOfControl.Visible = false;

但这是最好的方法吗?是否将另一种形式的控件public视为“最佳做法”?是否有“更好”的方式来访问另一种形式的控件?

进一步说明:

这实际上是我提出的另一个问题 Best method for creating a “tree-view preferences dialog” type of interface in C#? 的后续行动。我得到的答案非常好,解决了许多很多组织问题,这些问题在保持用户界面简单易用的同时兼顾了运行时和设计时。然而,它确实提出了一个轻松控制界面其他方面的问题。

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

17 个答案:

答案 0 :(得分:37)

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

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

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

答案 1 :(得分:20)

我个人会建议 NOT 这样做......如果它正在响应某种行为并且需要改变它的外观,我宁愿提出一个事件并让它自行解决..

这种形式之间的联系总让我感到紧张。我总是尽量保持用户界面尽可能轻和独立

我希望这会有所帮助。也许你可以扩展这个场景,如果没有?

答案 2 :(得分:8)

第一种当然不起作用。表单上的控件是私有的,只能通过设计在该表单中可见。

让一切公开也不是最好的方式。

如果我想向外界揭露某些东西(也可能意味着另一种形式),我会为它制作一个公共财产。

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

答案 3 :(得分:5)

在阅读其他详细信息后,我同意robcthegeek:举办活动。创建一个自定义EventArgs并通过它传递必要的参数。

答案 4 :(得分:3)

假设您有两个表单,并且希望通过另一个表单隐藏一个表单的属性:

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

当你想通过form2按钮获得form1的焦点时,然后:

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

答案 5 :(得分:2)

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

答案 6 :(得分:2)

我会在父表单中处理此问题。您可以通过事件通知另一个表单需要自行修改。

答案 7 :(得分:1)

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

为此,我使用在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; } }
    }
}

答案 8 :(得分:1)

我同意为此使用事件。由于我怀疑您正在构建MDI应用程序(因为您创建了许多子表单)并动态创建窗口并且可能不知道何时取消订阅事件,因此我建议您查看Weak Event Patterns。唉,这只适用于框架3.0和3.5,但类似的东西可以通过弱引用很容易实现。

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

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

答案 9 :(得分:1)

你可以

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

答案 10 :(得分:0)

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

答案 11 :(得分:0)

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

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

答案 12 :(得分:0)

将修饰符从公共更改为内部。 .Net故意使用私有修饰符而不是公共修饰符,因为防止对您的方法/属性/控件的任何非法访问。事实上,公共修饰符可以随时随地访问,因此它们非常危险。项目中的任何实体都可以访问您的方法/属性。但是在内部修饰符中,没有正文(您当前项目的其他部分)可以访问您的方法/属性。

假设您正在创建一个具有一些秘密字段的项目。因此,如果这些字段可以从您的项目中访问,那么它可能是危险的,并且与您最初的想法相反。作为一个好建议,我可以说总是使用内部修饰符而不是公共修饰符。

但有些奇怪!

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

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

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

在某个静态类和静态变量中声明你的整个Form,并且仍然有内部修饰符。然后,当您假设使用该表单向用户显示时,请将新的Form()构造传递给该静态类/变量。现在它可以随心所欲地访问。但是你还需要更多的东西。 您也可以在Designer File of Form中声明元素内部修饰符。您的表格是开放的,它可以随处访问。它非常适合你。

考虑这个例子。

假设您要访问Form的TextBox。

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

第二个去那个表格的设计师类,它假设被其他表格访问。将其TextBox修饰符声明从private更改为internal。别担心;更改后,.Net永远不会再将其更改为私有修饰符。

第三,当您想要打开该表单时,请将新的表单构造传递给该静态变量 - >>静态类。

第四;从任何其他表单(项目中的任何位置),您可以访问该表单/控件,而From是打开的。

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

2 - 任何想要打开最终表单的表单(在我们的示例FormB中有TextBox)。

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

请查看以下代码:

internal static class A
{
    internal static FormC FrmC;
}

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

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

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

任何评论/想法都让我知道。我很高兴听到你或其他任何人关于这个话题的更多信息。老实说,过去我对这个提到的问题有一些问题。最好的方法是第二种解决方案,我希望它可以为你工作。让我知道任何新的想法/建议。

答案 13 :(得分:0)

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

例如,如果我只想在输入所有必需信息时启用“保存”按钮,我将使用谓词方法来告知表示该表单的模型对象何时处于可以保存的状态。然后在我选择是否启用按钮的上下文中,我只使用该方法的结果。

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

编写单元和验收测试也会更容易,因为您可以遵循“Trust But Verify”模式:

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

  2. 你可以编写一个单独的一组来检查你的Save按钮是否以适当的方式连接到“is savable”谓词(无论你的框架是什么,在Mac OS X上的Cocoa中这通常是通过约束)。

  3. 只要两组测试都通过,您就可以确信您的用户界面将按照您希望的方式运行。

答案 14 :(得分:0)

你的孩子表格真的需要成为表格吗?它们可能是用户控件吗?这样,他们可以轻松地为主窗体提升事件来处理,你可以更好地将他们的逻辑封装到一个类中(至少在逻辑上,它们已经在所有类之后)。

@Lars:你就在这里。这是我在开始的日子里所做的事情,并且从那时起就没有这样做了,这就是为什么我第一次提出引发事件的原因,但我的另一种方法确实打破了任何封装的外观。

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

答案 15 :(得分:0)

@Lars,好好调用Form引用的传递,看到它也是我自己。讨厌。从未见过他们将它们传递给BLL层!这甚至没有意义!这可能会严重影响绩效吗?如果BLL中的某个地方保留了引用,表单会留在内存中吗?

你有同情心! ;)


@Ed,RE关于制作Forms UserControls的评论。 Dylan已经指出,根表单实例化许多子表单,给人一种MDI应用程序的印象(我假设用户可能希望关闭各种表单)。如果我在这个假设中是正确的,我认为他们最好保留为表格。当然可以接受纠正:)

答案 16 :(得分:0)

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