Лучший способ получить доступ к элементу управления в другой форме в Windows Forms?

Во-первых, это вопрос о настольном приложении, использующем Windows Forms, а не о ASP.NET .

Мне нужно взаимодействовать с элементами управления в других формах. Я пытаюсь получить доступ к элементам управления, используя, например, следующее ...

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

Это не работает так, как я ожидал. Я получаю исключение из Main . Однако, если я сделаю элементы управления public вместо private, я смогу получить к ним доступ напрямую, так что ...

otherForm.nameOfControl.Visible = false;

Но это лучший способ сделать это? public Считается ли создание элементов управления в другой форме «лучшей практикой»? Есть ли «лучший» способ доступа к элементам управления в другой форме?

Дальнейшее объяснение:

На самом деле это своего рода продолжение другого вопроса, который я задал: лучший метод для создания интерфейса типа «древовидный диалог настроек» в C#? . Ответ, который я получил, был отличным и решил многие, многие организационные проблемы, которые у меня были с точки зрения сохранения понятности пользовательского интерфейса и простоты работы как во время выполнения, так и во время разработки. Тем не менее, он поднял эту мелкую проблему легкого управления другими аспектами интерфейса.

По сути, у меня есть корневая форма, которая создает множество других форм, которые находятся на панели корневой формы. Так, например, переключателю на одной из этих подформ может потребоваться изменить состояние значка полосы состояния в основной, корневой форме. В этом случае мне нужна подформа, чтобы общаться с элементом управления в строке состояния родительской (корневой) формы. (Надеюсь, это имеет смысл, но не в смысле «кто первый».)

Ответов (17)

Предположим, у вас есть две формы, и вы хотите скрыть свойство одной формы через другую:

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

и если вы хотите вернуть фокус обратно к form1 с помощью кнопки form2, тогда:

Form1 ob = new Form1();
ob.Visible = true;
this.Close();
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
*/

Измените модификатор с общедоступного на внутренний. .Net намеренно использует частный модификатор вместо общедоступного, чтобы предотвратить любой незаконный доступ к вашим методам / свойствам / элементам управления из вашего проекта. Фактически, модификатор public может быть доступен где угодно, поэтому они действительно опасны. Любой человек вне вашего проекта может получить доступ к вашим методам / свойствам. Но во внутреннем модификаторе никакое тело (кроме вашего текущего проекта) не может получить доступ к вашим методам / свойствам.

Предположим, вы создаете проект, в котором есть секретные поля. Поэтому, если эти поля доступны вне вашего проекта, это может быть опасно и противоречить вашим первоначальным идеям. В качестве одной хорошей рекомендации я могу сказать, что всегда используйте внутренний модификатор вместо общедоступного.

Но как-то странно!

Я должен сказать также в VB.Net, хотя наши методы / свойства все еще являются частными, они могут быть доступны из других форм / классов, вызывая форму как переменную без каких-либо проблем.

Я не знаю, почему в этом языке программирования поведение отличается от C#. Как мы знаем, оба используют одну и ту же платформу, и они заявляют, что являются почти одной и той же серверной платформой, но, как вы видите, они по-прежнему ведут себя по-разному.

Но я решил эту проблему двумя способами. Или; с помощью интерфейса (что не рекомендуется, как вы знаете, интерфейсам обычно нужен модификатор public, а использование модификатора public не рекомендуется (как я сказал вам выше)),

Или

Объявите всю свою форму где-нибудь в статическом классе и статической переменной, и все еще есть внутренний модификатор. Затем, когда вы предполагаете использовать эту форму для показа пользователям, передайте новую Form() конструкцию этому статическому классу / переменной. Теперь он может быть доступен везде, где вы хотите. Но вам все еще нужно кое-что еще. Вы также объявляете свой внутренний модификатор элемента в файле формы конструктора. Пока ваша форма открыта, она может быть доступна везде. Это может сработать для вас очень хорошо.

Рассмотрим этот пример.

Предположим, вы хотите получить доступ к TextBox формы.

Итак, первая задача - объявление статической переменной в статическом классе (причина статики - простота доступа без использования новых ключей в будущем).

Во-вторых, перейдите к классу дизайнера этой формы, который предполагает доступ к другим формам. Измените его объявление модификатора TextBox с частного на внутреннее. Не волнуйтесь; .Net никогда не изменит его снова на частный модификатор после вашего изменения.

В-третьих, если вы хотите вызвать эту форму для открытия, передайте новую конструкцию формы этой статической переменной - >> статический класс.

Четвертый; из любых других форм (в любом месте вашего проекта) вы можете получить доступ к этой форме / элементу управления, пока открыт From.

Посмотрите на код ниже (у нас есть три объекта. 1- статический класс (в нашем примере мы его называем A )

2 - Любая форма, которая хочет открыть окончательную форму (в нашем примере есть TextBox FormB ).

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 она открыта.


Любой комментарий / идея дайте мне знать. Я рад услышать от вас или кого-либо еще по этой теме больше. Честно говоря, в прошлом у меня были проблемы с этой упомянутой проблемой. Лучшим способом было второе решение, которое, я надеюсь, сработает для вас. Сообщите мне любую новую идею / предложение.

Прочитав дополнительную информацию, я согласен с robcthegeek : поднимите событие. Создайте настраиваемый EventArgs и передайте через него необходимые параметры.

Первый конечно не работает. Элементы управления в форме являются частными и видны только для этой формы по дизайну.

Обнародовать это тоже не лучший способ.

Если я хочу открыть что-то внешнему миру (что также может означать другую форму), я делаю это общедоступным свойством.

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

Действительно ли ваши дочерние формы должны быть формами? Могут ли они вместо этого быть пользовательскими элементами управления? Таким образом, они могли бы легко вызывать события для обработки в основной форме, и вы могли бы лучше инкапсулировать их логику в один класс (по крайней мере, логически они уже являются классами).

@Lars: Ты здесь. Это было то, что я делал с самого начала, и с тех пор мне не приходилось этого делать, поэтому я сначала предложил создать событие, но мой другой метод действительно нарушил бы какое-либо подобие инкапсуляции.

@ Роб: Да, звучит правильно :). 0/2 на этом ...

Я бы справился с этим в родительской форме. Вы можете уведомить другую форму о том, что она должна изменить себя через событие.

Вместо того, чтобы делать элемент управления общедоступным, вы можете создать свойство, контролирующее его видимость:

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

Это создает правильный метод доступа к этому элементу управления, который не будет раскрывать весь набор свойств элемента управления.

Я лично рекомендовал бы НЕ делать этого ... Если он реагирует на какое-то действие и ему нужно изменить свой внешний вид, я бы предпочел создать событие и позволить ему разобраться ...

Такое сочетание форм всегда меня раздражает. Я всегда стараюсь сделать интерфейс максимально легким и независимым .

Надеюсь, это поможет. Возможно, вы могли бы расширить сценарий, если нет?

@Lars, хороший отзыв о передаче ссылок на формы, я тоже это видел. Противный. Никогда не видел, чтобы они передавали их на слой BLL! Это даже не имеет смысла! Это могло серьезно повлиять на производительность, верно? Если бы где-то в BLL сохранялась ссылка, форма осталась бы в памяти, верно?

Примите мои соболезнования! ;)


@Ed, RE ваш комментарий о создании UserControls в формах. Дилан уже указал, что корневая форма создает экземпляры многих дочерних форм, создавая впечатление приложения MDI (где я предполагаю, что пользователи могут захотеть закрыть различные формы). Если я прав в этом предположении, я думаю, что их лучше всего сохранить как формы. Конечно, можно исправить :)

Я согласен с использованием для этого событий. Поскольку я подозреваю, что вы создаете MDI-приложение (поскольку вы создаете множество дочерних форм) и динамически создаете окна и, возможно, не знаете, когда отказаться от подписки на события, я бы рекомендовал вам взглянуть на шаблоны слабых событий . Увы, это доступно только для framework 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;
}

Вам следует обращаться к содержимому одного представления только из другого, если вы создаете более сложные элементы управления / модули / компоненты. В противном случае вы должны сделать это через стандартную архитектуру модель-представление-контроллер: вы должны связать включенное состояние элементов управления, о которых вы заботитесь, с некоторым предикатом уровня модели, который предоставляет правильную информацию.

Например, если бы я хотел включить кнопку «Сохранить» только после того, как была введена вся необходимая информация, у меня был бы метод предиката, который сообщает, когда объекты модели, представляющие эту форму, находятся в состоянии, которое можно сохранить. Затем в контексте, когда я выбираю, включать ли кнопку, я бы просто использовал результат этого метода.

Это приводит к более четкому разделению бизнес-логики от логики представления, позволяя им обоим развиваться более независимо - позволяя с легкостью создавать один интерфейс с несколькими внутренними интерфейсами или несколько интерфейсов с одним сервером.

Также будет намного проще писать модульные и приемочные тесты, потому что при этом вы можете следовать шаблону « Доверяй, но проверяй »:

  1. Вы можете написать один набор тестов, которые настраивают объекты вашей модели различными способами и проверяют, что предикат «сохраняется» возвращает соответствующий результат.

  2. Вы можете написать отдельный набор проверок, подключена ли ваша кнопка «Сохранить» соответствующим образом к предикату «сохраняется» (что бы это ни было для вашей структуры, в Какао на Mac OS X это часто будет через привязку).

Пока проходят оба набора тестов, вы можете быть уверены, что ваш пользовательский интерфейс будет работать так, как вы хотите.

Это выглядит как главный кандидат для отделения презентации от модели данных. В этом случае ваши предпочтения должны храниться в отдельном классе, который запускает обновления событий всякий раз, когда изменяется конкретное свойство (посмотрите INotifyPropertyChanged, если ваши свойства являются дискретным набором, или в одно событие, если они представляют собой более произвольные текстовые ключи ).

В древовидном представлении вы внесете изменения в свою модель предпочтений, после чего она вызовет событие. В других формах вы подпишетесь на изменения, которые вас интересуют. В обработчике событий, который вы используете для подписки на изменения свойств, вы используете this.InvokeRequired, чтобы узнать, находитесь ли вы в правильном потоке для создания пользовательского интерфейса call, если нет, то используйте this.BeginInvoke, чтобы вызвать нужный метод для обновления формы.

Вы можете

  1. Создайте общедоступный метод с необходимым параметром в дочерней форме и вызовите его из родительской формы (с допустимым приведением)
  2. Создайте общедоступное свойство в дочерней форме и получите доступ к нему из родительской формы (с допустимым приведением)
  3. Создайте еще один конструктор в дочерней форме для установки параметров инициализации формы.
  4. Создавать собственные события и / или использовать (статические) классы

Лучшая практика будет №4, если вы используете немодальные формы.

С помощью свойства (выделено) я могу получить экземпляр класса MainForm. Но это хорошая практика? Что вы порекомендуете?

Для этого я использую свойство MainFormInstance, которое работает в методе OnLoad.

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; } }
    }
}
  1. Используйте обработчик событий, чтобы уведомить другую форму о его обработке.
  2. Создайте общедоступное свойство в дочерней форме и получите доступ к нему из родительской формы (с допустимым приведением).
  3. Создайте еще один конструктор в дочерней форме для установки параметров инициализации формы.
  4. Создавайте собственные события и / или используйте (статические) классы.

Лучшая практика - №4, если вы используете немодальные формы.

Шаг 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];
        }
    }
}