• Программное соединение событий в C#
  • Другой пример
  • Загрузка сборки
  • Заключение
  • C# Сегодня

    Статья "Программное соединение событий в C#" взята из базы знаний на сайте C# Today www.csharptoday.com издательства Wrox. Код, используемый в статье, можно загрузить вместе с кодом для всей книги со страницы Professional C# на Wrox.com.

    Программное соединение событий в C#

    (Мэттью Рейнольдс)

    Одним из наиболее мощных свойств .NET является возможность создания динамических форм для приложений Windows. Известные и раньше, сегодня они очень легко создаются в .NET. Это позволяет получать объекты, производные из

    System.Windows.Forms.Control
    , непосредственно во время выполнения, и использовать их точно таким же образом, как если бы они были созданы проектировщиком форм. Динамические элементы управления могут использоваться для настройки интерфейса пользователя приложения в зависимости от некоторой информации о среде выполнения, например административной утилиты базы данных, где кнопка динамически добавляется в утилиту для каждой таблицы, содержащейся в базе данных. Немного сложным моментом этой темы является соединение обработчиков событий с элементами управления. В этой статье показано, как динамически создавать элементы управления в C# и соединять методы с событиями элементов управления.

    Создание проекта

    Давайте создадим новый проект Visual C# — Windows Application и назовем его DynamicButtons.

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

    Пусть кнопка будет называться

    cmdCreateButtons
    , а текстовое поле —
    txtLog
    . Убедитесь, что в
    txtLog
    свойство
    Multiline
    задано как
    True
    , а свойство
    ScrollBars
    — как
    Vertical
    .

    Когда будет нажата

    cmdCreateButtons
    , мы добавим к форме шесть кнопок, расположенных в пустом пространстве справа от
    txtLog
    . В то время как обработчики событий конфигурируются для новых кнопок, определим какая кнопка инициирует вызов. Здесь нужно, чтобы все кнопки имели дополнительные целые свойства с именем ID, которые при создании пронумерованы от 1 до 6.

    Одной из отличительных черт .NET, к которой разработчикам VB необходимо привыкнуть, является идея наследования существующих классов из Framework (Среды разработки) и их расширение. Эта техника является очень эффективной. В данном случае необходимо создать новый класс с именем

    DynamicButton
    и наследовать его из
    System.Windows.Forms.Button
    . Это означает, что наш новый класс будет обладать всей функциональностью обычного элемента управления
    Button
    , но при этом иметь и другие свойства, которые нам понадобятся, в частности новое свойство с именем
    DynamicID
    . Так как этот класс является производным из
    Button
    , он выполняет все действия, присущие элементу управления кнопки, т. е. реагирует на нажатия, может быть помещен в форму и т.д.

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

    Button
    , способен также применять
    DynamicButton
    . Однако необязательно знать, как использовать расширенную функциональность, по большей части ее можно применять как обычную кнопку. Вы также вправе вывести свои собственные классы из
    DynamicButton
    и добавить свою собственную функциональность. Итак, создадим новый класс с именем
    DynamicButton
    и добавим следующий код:

    namespace DynamicButton {

     using System;

     public class DynamicButton : System.Windows.Forms.Button {

      public int DynamicId;

      public DynamicButton() {

      }

     }

    }

    Теперь есть новый класс, который ведет себя так же, как кнопка, но имеет целое свойство с именем

    DynamicId
    .

    Фактически мы хотим добавить кнопке функциональность, в частности предоставить ID везде, где создается кнопка, и задать текст по умолчанию для отражения этого ID:

    public DynamicButton(int newId) {

     // задать ID ...

     DynamicId = newId;

     // задать изображение ...

     Text = "Dynamic Button " + DynamicId;

    }

    Далее можно перейти к cозданию кнопок

    Создание кнопок

    Чтобы создать кнопки, добавим код обработчика для кнопки

    cmdCreateButtons
    . Это относительно простой код, но выглядит немного великоватым, так как нам необходимо самостоятельно управлять компоновкой новых кнопок для отслеживания координат кнопки при ее добавлении.

    Первая часть обработчика задает цикл:

    protected void cmdCreateButtons_Click (object sender, System.EventArgs e) {

     // определить, где должны располагаться новые кнопки ...

     int spacing = 8;

     int у = txtLog.Top;

     int width = this.Width - txtLog.Right - (3 * spacing);

     int height = 25;

     // цикл создания новыx кнопок

     int n = 0;

     for (n = 0; n < 6; n++) {

    Здесь задано, что высота кнопки равна 25 и у начинается в той же точке, что и вершина

    txtLog
    . Мы также задали, что
    spacing
    равно 8, это значение используется для разделения кнопок.

    Чтобы создать кнопку, делаем так:

      // создать новую кнопку ...

      DynamicButton newButton = new DynamicButton(n);

    Отметим, как значение

    n
    передается в конструктор в качестве
    DynamicId
    кнопки. Следующим шагом является позиционирование кнопки на форме:

      newButton.Left = txtLog.Right + spacing;

      newButton.Top = y;

      newButton.Width = width;

      newButton.Height = height;

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

    txtLog
    и правым краем самой формы.

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

      // добавление кнопки в форму ...

      this.Controls.Add(newButton);

      // следующая ...

      у += (height + spacing);

     }

    }

    В результате будет получено изображение:

    Кнопки созданы, но пока они ничего не делают. Для того чтобы они работали, необходимо присоединить обработчики событий.

    Присоединение обработчиков событий

    В .NET существует небольшое различие в способах использования термина делегат. Делегат определяется с помощью ключевого слова

    Delegate
    , описывающего сигнатуру метода, который будет использоваться в делегате. Сигнатура метода включает комбинацию параметров и возвращаемый тип. Поэтому метод, который получает один параметр
    Object
    и возвращает
    String
    , будет иметь сигнатуру, отличную от сигнатуры метода, получающего параметр
    Hashtable
    и возвращающего
    String
    .

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

    Delegate
    . Делегируемый метод будет обозначать метод, который реализуется в результате вызова делегата.

    Для присоединения обработчика событий необходимо создать делегируемый метод со следующей сигнатурой:

    void DelegateMethod(Object sender, System.EventArgs e)

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

    System.Object
    , а затем объект
    System.EventArgs
    ,— все будет нормально. Как можно понять, порядок параметров влияет на сигнатуру метода. Если изменить порядок параметров, метод не будет иметь требуемую сигнатуру для делегируемого метода.

    Все обработчики событий в множестве элементов управления

    Windows.Forms
    соответствуют этой модели, значит, возможно создание достаточно мощных обработчиков событий. Можно было бы при желании сделать так, чтобы каждое событие каждого элемента управления на форме проходило через один и тот же делегируемый метод. Фактически делегируемый метод вызывается для любого объекта из любого другого объекта. Поэтому элемент управления кнопки объекта формы, называемого
    MyDialog
    , может вызывать метод для объекта формы, называемого
    MyDialogOwner
    .

    Параметр

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

    // DynamicButton_Click ...

    protected void DynamicButton_Click(object sender, System.EventArgs e) {

     // преобразовать sender в button ...

     DynamicButton button = (DynamicButton)sender;

     // msgbox ...

     AddToLog("Clicked" + button.DynamicId);

    }

    В этом методе нет ничего необычного. Соединим его с событием Click нашей динамической кнопки. Непосредственно перед тем как вызывать

    Controls.Add
    , можно добавить обработчик:

    // соединить обработчик ...

    newButton.Click += new System.EventHandler(this.DynamicButton_Click);

    // добавить кнопку к форме ...

    this.Controls.Add(newButton);

    Теперь, когда возникает связанное с кнопкой событие

    Click
    , рабочая среда Framework просматривает список делегированных методов, соединенных с этим событием, инициируя каждый из них по очереди.

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

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

    Мы не определили, как выглядит наша функция

    AddToLog
    ,— сделаем это сейчас:

    // AddToLog — обновляет представление журнала ...

    private void AddToLog(String buf) {

     // обновляет элемент управления журнала ...

     txtLog.Text = (buf + "\r\n" + txtLog.Text);

    }

    Теперь давайте попробуем выполнить приложение и понажимать на кнопки:

    Другие обработчики

    Обработчики, которые отвечают на другие события в

    DynamicButton
    , могут быть добавлены аналогичным образом. Прежде всего для каждого из них создается метод:

    // DynamicButton_Enter ...

    protected void DynamicButton_Enter(object sender, System.EventArgs e) {

     // преобразовать sender в button ...

     DynamicButton button = (DynamicButton)sender;

     // msgbox ...

     AddToLog("Enter " + button.DynamicId);

    }


    // DynamicButton_Leave ...

    protected void DynamicButton_Leave(object sender, System.EventArgs e) {

     // преобразовать sender в button ...

     DynamicButton button = (DynamicButton)sender;

     // msgbox ...

     AddToLog("Left " + button.DynamicId);

    }

    Затем можно добавить обработчики для каждого из них:

    // соединить обработчик

    newButton.Click +=

     new System.EventHandler(this.DynamicButton_Click);

    newButton.MouseEnter +=

     new System.EventHandler(this.DynamicButton_Enter);

    newButton.MouseLeave +=

     new System.EventHandler(this.DynamicButton_Leave);

    Теперь, если снова выполнить приложение, в журнал будут добавляться сообщения по мере перемещения по кнопкам.

    Другой пример

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

    Динамические элементы управления можно использовать для настройки интерфейса пользователя приложения в зависимости от некоторых данных среды выполнения. Классическим примером этого является добавление новых возможностей в панель инструментов, когда в каталог приложения вносятся новые дополнительные средства (plug-ins) или модули. Например, установка Adobe Acrobat на компьютере может автоматически добавлять в панель инструментов Word кнопку для создания документа Acrobat.

    Другим примером может быть утилита администрирования базы данных. Когда утилита соединяется с базой данных, в интерфейс пользователя должна быть добавлена кнопка, представляющая каждую имеющуюся внутри базы данных таблицу. Или можно создать множество элементов управления текстовых полей с именами всех файлов XML, содержащихся в определенном каталоге, и т.д.

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

    System.Windows.Forms.Control
    , и выводящее кнопку в форме для каждого найденного типа. Нажатие на кнопку будет вызывать экземпляр элемента управления и выводить его в форму.

    Создание проекта

    Создадим новый проект Visual C# — Windows Application и назовем его

    ControlLoader
    . В самом начале мы не будем размещать в новой форме никаких элементов управления, но можем изменить свойство Text на что-нибудь типа "Control Container".

    Итак, добавим следующие члены в форму:

    public class Form1 : System.Windows.Forms.Form {

     // члены ...

     private ArrayList _buttons = new ArrayList();

     private int _nextY = ButtonSpacing;

     private Control _containedControl;

     // константы ...

     const int ButtonHeight = 25;

     const int ButtonSpacint = 5;

     const int ButtonWidth = 200;

    Мы имеем список кнопок, которые добавляются в

    ArrayList
    с именем
    _buttons
    . При добавлении каждой кнопки к форме необходимо разместить ее в правильной у-координате, т.е.
    _nextY
    . Рассмотрим только один элемент управления в конкретный момент, который будет содержаться в
    _containedControl
    . Наконец, мы используем метрику, которая описывает компоновку кнопок, и она задается тремя константами внизу.

    Сами кнопки будут создаваться из нового класса, производного от

    System.Windows.Forms.Button
    . Этот новый класс называется
    TypeButton
    и имеет дополнительное свойство
    ControlType
    , которое содержит объект
    System.Type
    . Этот объект
    Type
    представляет элемент управления в загружаемой сборке. Создадим новый класс с именем
    TypeButton
    и добавим ссылку на пространство имен
    System.Windows.Forms
    .

    using System;

    using System.Windows.Forms;

    Затем добавим код:

    public class TypeButton System.Windows.Forms.Button {

     public Type _controlType;


     public TypeButton() {

     }


     // ControlType — получить или задать свойство ...

     public Type ControlType {

      get {

       return _controlType;

      }

      set {

       _controlType = value;

       this.Text = _controlType.FullName;

      }

     }

    }

    Как можно видеть, задавая свойство

    ControlType
    , мы изменяем текст кнопки, чтобы он стал полным названием типа.

    Метод, который нужен для создания

    TypeButton
    , создает экземпляр элемента управления. Добавим этот метод, использующий
    System.Activator
    для создания экземпляра класса и преобразующий его в
    System.Windows.Forms.Control
    :

    // CreateInstance — создание экземпляра типа данных ... 

    public Control CreateInstance() {

     // возвращает экземпляр нового элемента управления ...

     return (Control)Activator.CreateInstance(ControlType);

    }

    Добавление кнопок

    Для первого проверочного запуска добавим

    TypeButton
    , который представляет экземпляр элемента управления
    System.Windows.Forms.DataGrid
    . Это позволит протестировать логику того, что делается, не вдаваясь в дополнительные трудности, связанные с загрузкой сборки и просмотром типов данных.

    Метод

    Form1.AddType
    будет получать объект
    System.Type
    и добавлять новый объект
    TypeButton
    в форму. Во-первых, необходимо создать экземпляр
    TypeButton
    и задать его свойство
    ControlType
    новым типом:

    // AddType - добавить кнопку типа в компоновку ...

    public void AddType(Type ControlType) {

     // первое: создать новую кнопку типа ...

     TypeButton button = new TypeButton();

     button.ControlType = ControlType;

    Во-вторых, нужно добавить

    TypeButton
    в
    ArrayList
    , который содержит список кнопок:

     // второе: добавить эту кнопку в массив

     _buttons.Add(button);

    После этого можно поместить в форму кнопку и добавить ее в список элементов управления формы:

     // теперь разместим кнопку

     button.Left = ButtonSpacing;

     button.Width = ButtonWidth;

     button.Top = _nextY;

     button.Height = ButtonHeight;

     // настроить следующее значение у ...

     _nextY += (ButtonHeight + ButtonSpacing);

     // вывести кнопку ...

     this.Controls.Add(button);

    Наконец, необходимо присоединить событие

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

     // затем присоединяем обработчик события ...

     button.Click += new EventHandler(this.ButtonClick);

    }

    Пока еще мы не создали

    ButtonClick
    , — сделаем это сейчас:

    // ButtonClick — вызывается всякий раз при нажатии кнопки ...

    protected void ButtonClick(object sender, System.EventArgs e) {

     // преобразовать sender в кнопку типа ...

     TypeButton button = (TypeButton)sender;

    Нашей первой задачей является преобразование

    sender
    в
    TypeButton
    . Это позволит использовать
    CreateInstance
    для создания элемента управления. Если мы уже имеем элемент управления, необходимо сначала удалить его из списка элементов управления формы:

    // если уже имеется содержащийся элемент управления, удалим его ...

     if (_containedControl != null)

      Controls.Remove(_containedControl);

    Создаем элемент управления:

     // создать экземпляр нового элемента управления...

     _containedControl = button.CreateInstance();

    Наконец, мы можем поместить элемент управления в форму.

     // поместить элемент управления на форме ...

     _containedControl.Left = ButtonWidth + (3 * ButtonSpacing);

     _containedControl.Top = ButtonSpacing;

     _containedControl.Width = this.Width - _containedControl.Left - (4 * ButtonSpacing);

     _containedControl.Height = this.Height - (8 * ButtonSpacing);

     this.Controls.Add(_containedControl);

    }

    Тестирование полученного кода

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

    System Windows.Forms.DataGrid
    . Добавим следующий код в
    Form1
    :

    private void Form1_Load(object sender, System.EventArgs e) {

     // при загрузке добавить тип кнопки ...

     DataGrid grid = new DataGrid();

     AddType(grid.GetType());

    }

    Теперь закончим проект. Нажав на кнопку

    System.Windows.Forms.DataGrid
    , увидим:

    Загрузка сборки

    Завершая этот пример, покажем, как можно загрузить сборку во время выполнения, просмотреть сборку в поисках типов данных, которые являются произвольными из

    System.Windows.Forms.Control
    , и добавить кнопки для каждого найденного типа данных.

    Чтобы загрузить сборку нам понадобится URL. Добавим следующий код в

    Form1_Load
    :

    private void Form1_Load(object sender, System.EventArgs e) {

     // при загрузке добавить тип кнопки ...

     DataGrid grid = new DataGrid();

     AddType(grid.GetType());

     // найти имя нашей сборки

     String filename = this.GetType().Module.Assembly.CodeBase;

    Эта строка является обходной техникой для получения сборки, реализующей класс объекта, из которого вызывается код. В данном случае ее получают из URL сборки, содержащей

    Form1
    .

    Нам нужно при загрузке сборки использовать

    try…catch
    , так как существуют процессы, которые могут пойти в этой процедуре неправильно. Мы используем общий метод на
    Assembly
    :

     // проверить и загрузить сборку ...

     try {

      // используем LoadFrom ...

      Assembly controlAssembly = Assembly.LoadFrom(filename);

    После получения сборки проверим в ней типы данных:

      // теперь получим список типов данных ...

      foreach(Type testType in controlAssembly.GetTypes()) {

    Далее хитрость состоит том, чтобы создать экземпляр проверяемого типа данных, но преобразовать его в

    Control
    . Так как все это погружено в другой блок
    try…catch
    , то при переходе к следующей строке мы знаем, что имеется полностью квалифицированный элемент управления.

       // попробуем создать экземпляр элемента управления

       // и преобразовать его в элемент управления ...

       try {

        Control testControl = (Control)Activator.CreateInstance(testType);

    Полезный совет.

    System.Windows.Forms.Form
    является производным от
    Control
    , так как он использует контейнеризацию свойств
    Control
    для вывода элементов управления, нарисованных в форме. Если проверить свойство
    TopLevelControl
    , оно всегда будет задано при выводе класса из формы.

        // нам необходимо убедиться,

        // что это не элемент управления "верхнего уровня" ...

        if (testControl.TopLevelControl == null) {

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

         AddType(testType);

        }

       }

    Мы можем завершить пример двумя обработчиками исключений:

       catch {

        // если мы здесь, мы не заботимся об объекте!

       }

      }

     } catch(Exception ее) {

      MessageBox.show("The assembly could not be loaded. " + ее.Message);

     }

    }

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

    DemoTextBox
    , и добавим следующее предложение наследования:

    public class DemoTextBox : System.Windows.Forms.TextBox

    Теперь создадим другой класс, на этот раз с именем

    DemoMonthCalendar
    , и добавим следующее предложение:

    public class DemoMonthCalendar : System.Windows.Forms.MonthCalendar

    Выполним проект. Должно получиться подобное изображение.

    Заключение

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

    System.Window.Forms.Button
    , который позволяет добавлять дополнительною функциональность и свойства кнопке. Мы увидели также, как соединить с новыми элементами управления методы, вызываемые при инициировании событий. Наконец, мы проверили возможность просмотра сборки в поисках классов и использовали изученную технику для создания простой утилиты, которая загружается и выводит эти классы по команде пользователя.







     

    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх