Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых
[Назад] [Содержание] [Дальше]

Визуальное проектирование приложений C#


А.В. Фролов, Г.В. Фролов

Глава 7. Многооконный пользовательский интерфейс.. 2

Использование фреймов.. 4

Создание главного окна приложения. 4

Добавление элемента управления TreeView.. 5

Добавление вертикального разделителя. 6

Добавление панели Panel 7

Добавление элемента управления ListView.. 7

Добавление горизонтального разделителя. 8

Добавление окна RichTextBox. 8

Элемент управления TreeView... 9

Инициализация дерева TreeView.. 9

Получение списка дисков. 9

Получение списка подкаталогов. 9

Метод DriveTreeInit 10

Метод GetDirs. 11

Обработчик события BeforeExpand. 12

Добавление значков к узлам дерева. 13

Создание списка изображений. 14

Подключение списка изображений к дереву. 15

Изменения в исходном тексте программы.. 15

Добавление флажков к узлам дерева. 16

Редактирование текста узлов дерева. 17

Список ListView... 18

Создание и настройка списка ListView.. 18

Режимы отображения. 18

Создание и настройка столбцов таблицы.. 18

Сортировка содержимого списка. 19

Подключение значков к списку. 19

Наполнение списка ListView.. 20

Алгоритм наполнения списка. 20

Обработчик события AfterSelect 21

Отображение содержимого текстовых файлов. 24

Приложения MDI 26

Меню Windows. 26

Системное меню MDI-окна. 27

Создание главного окна MDI-приложения. 27

Создание MDI-окон. 29

Шаблон MDI-окна. 29

Программный код для создания MDI-окна. 30

Упорядочивание MDI-окон. 31

Передача данных через буфер Clipboard. 32

Копирование данных в буфер Clipboard. 32

Вставка данных из буфера Clipboard. 33

 

Глава 7. Многооконный пользовательский интерфейс

Предыдущие главы нашей книги были посвящены созданию однооконных приложений, напоминающих по своему пользовательскому интерфейсу программу Microsoft Notepad. В этой главе мы расскажем Вам о проектировании приложений с многооконным пользовательским интерфейсом. Это приложения с фреймами (панелями), а также приложения с многооконным интерфейсом документов (Multiple-document interface, MDI).

В качестве примеров автономных приложений Microsoft Windows, реализующих многооконный интерфейс с фреймами можно привести Microsoft Explorer (рис. 7-1) и почтовую программу Microsoft Outlook Express, а также систему разработки Microsoft Visual Studio .NET.

Рис. 7-1. Главное окно программы Windows Explorer

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

Технология фреймов очень часто используется и для создания Web-сайтов, имеющих сложную иерархическую структуру. На рис. 7-2 мы показали информационный сайт http://info.datarecovery.ru службы восстановления данных DataRecovery.Ru, в котором используются фреймы.

Рис. 7-2. Сайт информационного центра с фреймами

В левом фрейме отображается довольно развесистое дерево навигации, открывающее доступ к десяткам книг и статей, общий объем которых превышает несколько тысяч страниц. Это статьи, посвященные вопросам восстановления данных, антивирусной защиты и другим аспектам компьютерных технологий, полное собрание наших книг серий «Библиотека системного программиста» и «Персональный компьютер. Шаг за шагом», вышедших в издательстве «Диалог-МИФИ» (http://www.bitex.ru/~dialog), а также сведения о других наших книгах и работах.

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

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

Что же касается некогда широко распространенного многооконного интерфейса документов MDI, то ему следует такая известная программа, как редактор REGEDT32.EXE регистрационная база данных ОС Microsoft Windows (рис. 7-3).

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

Сейчас приложения с интерфейсом MDI вытесняются приложениями, имеющими современный пользовательский интерфейс на базе фреймов. Тем не менее, средства Microsoft .NET Framework позволят Вам при необходимости легко снабжать свои приложения и тем, и другим интерфейсом.

Рис. 7-3. Главное окно программы Registry Editor

В этой главе мы также расскажем Вам об использовании элементов управления TreeView и ListView, с помощью которых можно просматривать древовидные и списковые структуры данных. Такие элементы управления используются, например, в упоминавшейся выше программе Windows Explorer (рис. 7-1).

Использование фреймов

Изучение способов создания приложений с многооконным интерфейсом мы начнем с фреймов. Для реализации фреймов нам потребуется элемент управления Splitter, значок которого есть в инструментальной панели системы Microsoft Visual Studio .NET. Элемент управления Splitter представляет собой разделитель, предназначенный для регулирования размеров окна фрейма.

Специально для демонстрации способов создания приложений с фреймами мы вместе с Вами подготовим приложение FramesApp, действующее аналогично программе Microsoft Explorer. На примере этого приложения мы также изучим элементы управления TreeView и ListView.

Создание главного окна приложения

Создайте приложение FramesApp типа Windows Application, пользуясь мастером проектов (точно так же, как мы это делали ранее).

Добавьте в форму главного окна приложения меню и строку состояния. В меню File создайте строку Exit, предназначенную для завершения работы приложения (рис. 7-4).

Рис. 7-4. Главное окно приложения FramesApp с меню и строкой состояния

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

Добавление элемента управления TreeView

Добавьте в окно нашего приложения элемент управления  TreeView, перетащив мышью его значок из панели инструментов Toolbox в окно формы.

Установите значение свойства Dock, равным Left. Для этого щелкните левую кнопку в окне редактирования этого свойства (рис. 7-5).

Рис. 7-5. Настройка свойства Dock элемента управления TreeView

В результате элемент управления TreeView будет выровнен по левой границе главного окна приложения, как это показано на рис. 7-6.

Рис. 7-6. Выравнивание элемента управления TreeView

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

В нашем приложении окно элемента управления TreeView будет использовано для отображения списка дисков и каталогов (аналогично дереву, расположенному в левой части главного окна приложения Windows Explorer).

Добавление вертикального разделителя

Снабдим элемент управления TreeView разделителем. Для этого перетащите значок элемента управления Splitter из панели инструментов Toolbox в окно формы.

Разделитель будет автоматически расположен справа от окна элемента управления TreeView (рис. 7-7).

Рис. 7-7. Добавление разделителя к элементу управления TreeView

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

Самое важное свойство разделителя Splitter — это свойство Dock, задающее его расположение. По умолчанию значение этого свойства равно Left, благодаря чему разделитель оказался «запаркован» слева от окна дерева TreeView. При необходимости можно изменять расположение разделителя с помощью окна редактирования, аналогичного окну, показанному на рис. 7-5.

Свойство BorderStyle позволяет задавать внешний вид разделителя и может иметь значения None (используется по умолчанию), FixedSingle и Fixed3D. В первом случае разделитель не имеет рамки, во втором случае используется тонкая рамка, а в третьем — трехмерная рамка.

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

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

Для объединения окон элементов управления ListView и RichTextBox мы создадим панель на базе элемента управления Panel. Перетащите значок этого элемента управления из инструментальной панели Toolbox в окно нашей формы, а затем установите значение свойства Dock этой панели равным Fill. Последнее действие можно сделать, щелкнув кнопку, расположенную в центре окна редактирования данного свойства.

В результате наша панель будет расположена справа от разделителя и займет всю правую часть окна приложения (рис. 7-8).

Рис. 7-8. Добавление панели

Добавление элемента управления ListView

На следующем шаге мы добавим в окно нашего приложения элемент управления ListView, перетащив его значок из пиктограммы Toolbox. После установки значения свойства Dock, равным Top, и увеличения высоты окна этого элемента управления, главное окно нашего приложения примет вид, показанный на рис. 7-9.

Рис. 7-9. Добавление элемента управления ListView

Как мы уже говорили, в окне элемента управления ListView наша программа будет показывать список файлов и каталогов, расположенный в текущем каталоге, выбранном с помощью дерева TreeView.

Добавление горизонтального разделителя

Теперь нашей задачей будет добавление в правую часть главного окна горизонтального разделителя Splitter. Перетащите его пиктограмму из панели инструментов Toolbox в окно формы, а затем установите значение свойства Dock добавленного разделителя, равным Top.

В результате разделитель будет расположен непосредственно под окном элемента управления ListView, как это показано на рис. 7-10.

Рис. 7-10. Добавление горизонтального разделителя

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

Последнее действие — добавление элемента управления RichTextBox. Перетащите его значок из панели Toolbox и установите значение свойства Dock, равным Fill.

Все! Теперь запускайте приложение на выполнение. На рис. 7-11 мы показали внешний вид его окна.

Рис. 7-11. Многооконный пользовательский интерфейс с формами реализован полностью

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

Заметим, что в некоторых случаях для достижения необходимого взаимного расположения окон Вам придется изменять расположение окон элементов управления по оси Z (Z-order). Это можно сделать с помощью строк Bring to Front и Send to Back контекстного меню. Это меню появляется на экране, если щелкнуть правой клавишей мыши окно элемента управления.

Элемент управления TreeView

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

Надо отметить, что класс TreeView достаточно сложный. Он содержит множество методов, свойств и создает различные события. Мы будем изучать его постепенно.

Заметим, что вместе с классом TreeView мы будем использовать классы TreeNode и TreeNodeCollection. Первый из них содержит записи узлов дерева, а второй — контейнер с такими записями.

Инициализация дерева TreeView

Когда Вы перетаскиваете значок дерева TreeView из панели Toolbox в окно формы, создается объект treeView1 класса System.Windows.Forms.TreeView:

private System.Windows.Forms.TreeView treeView1;

При инициализации мы будем ссылаться на переменную treeView1, хранящую ссылку на наше дерево.

Инициализация дерева выполняется в конструкторе класса Form1 с помощью подготовленного нами метода DriveTreeInit:

public Form1()
{
  //
  // Required for Windows Form Designer support
  //
  InitializeComponent();

  //
  // TODO: Add any constructor code after InitializeComponent call
  //

  DriveTreeInit();
}

Получение списка дисков

Ниже мы опишем исходный текст этого метода, а пока немного отвлечемся — расскажем о классах и методах, позволяющих получить информацию о дисках и каталогах. Более полную информацию по этому вопросу Вы найдете в [3].

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

string[]drivesArray = Directory.GetLogicalDrives();

foreach(string s in drivesArray)
  Console.Write("{0} ", s);

Этот метод не имеет параметров. После выполнения он возвращает ссылку на массив текстовых строк вида «C:\» с обозначениями всех доступных логических дисковых устройств.

Для обращения к этому методу, а также к другим методам, работающим с дисками, каталогами и файлами, подключите пространство имен System.IO:

using System.IO;

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

Для того чтобы получить список всех подкаталогов заданного каталога, мы используем метод GetDirectories класса DirectoryInfo. Ниже приведен фрагмент кода, позволяющий записать в массив diArray информацию обо всех подкаталогах корневого каталога диска C:

DirectoryInfo[] diArray;
string fullPath = "C:\\";

DirectoryInfo di = new DirectoryInfo(fullPath);
      
try
{
  diArray = di.GetDirectories();
}
catch
{
  …
}

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

Обратите внимание, что вызов метода GetDirectories необходимо выполнять в блоке try-catch, т.к. этот метод может вызывать исключения. Исключения возникают, например, если методу передается строка нулевой длины, если в строке имеется ошибка, если программа не обладает достаточными правами доступа для просмотра содержимого каталога или если указанный путь не найден.

Метод DriveTreeInit

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

Исходный текст этой функции Вы найдете ниже:

/// <summary>
/// Инициализация окна древовидного списка дисковых устройств
/// </summary>
public void DriveTreeInit()
{
  string[] drivesArray = Directory.GetLogicalDrives();

  treeView1.BeginUpdate();
  treeView1.Nodes.Clear();

  foreach(string s in drivesArray)
  {
     TreeNode drive = new TreeNode(s, 0, 0);
     treeView1.Nodes.Add(drive);

     GetDirs(drive);
  }


  treeView1.EndUpdate();
}

В самом начале своей работы этот метод получает список логических дисковых устройств, установленных в системе, и сохраняет его в массиве drivesArray.

Далее метод DriveTreeInit вызывает метод treeView1.BeginUpdate. Этот метод временно блокирует перерисовку окна дерева до тех пор, пока не будет вызван метод treeView1.EndUpdate. Пара этих методов используется в том случае, когда нужно добавить, удалить или изменить большое количество элементов дерева. Если не заблокировать перерисовку окна, на обновление дерева уйдет слишком много времени.

В классе TreeView определено свойство Nodes, хранящее все узлы дерева. Перед тем как приступить к заполнению дерева, метод DriveTreeInit удаляет все узлы, вызывая для этого метод treeView1.Nodes.Clear.

Заполнение дерева происходит в цикле:

foreach(string s in drivesArray)
{
  TreeNode drive = new TreeNode(s, 0, 0);
  treeView1.Nodes.Add(drive);

  GetDirs(drive);
}

Напомним, что массив drivesArray содержит массив текстовых строк с обозначениями логических устройств, установленных в системе. Для каждого такого устройства в цикле создается объект класса TreeNode — узел дерева. В качестве первого параметра конструктору передается текстовая строка названия устройства. Остальные параметры мы рассмотрим позже.

Созданный узел добавляется в набор узлов дерева с помощью метода treeView1.Nodes.Add. В качестве параметра этому методу передается ссылка на объект TreeNode, т.е. на добавляемый узел.

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

Метод GetDirs

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

Вот исходный текст этого метода:

/// <summary>
/// Получение списка каталогов
/// </summary>
public void GetDirs(TreeNode node)
{
  DirectoryInfo[] diArray;

  node.Nodes.Clear();

  string fullPath = node.FullPath;
  DirectoryInfo di = new DirectoryInfo(fullPath);
      
  try
  {
     diArray = di.GetDirectories();
  }
  catch
  {
     return;
  }

  foreach (DirectoryInfo dirinfo in diArray)
  {
     TreeNode dir = new TreeNode(dirinfo.Name, 0, 0);
     node.Nodes.Add(dir);
  }
}

Первым делом метод GetDirs удаляет все элементы из текущего узла, вызывая для этого метод node.Nodes.Clear.

Далее метод переписывает в переменную fullPath типа string полный путь node.FullPath к узлу дерева. Эта строка получается объединением (конкатенацией) текстовых строк всех родительских узлов. Если корневой узел хранит текстовую строку обозначения логического диска, то после выполнения этой процедуры в переменной fullPath будет храниться полный путь к файлу или каталогу.

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

В том случае если содержимое каталога было успешно получено, оно сохраняется в массиве diArray.

Далее содержимое массива diArray используется для заполнения узла дерева содержимым каталога:

foreach (DirectoryInfo dirinfo in diArray)
{
  TreeNode dir = new TreeNode(dirinfo.Name, 0, 0);
  node.Nodes.Add(dir);
}

Здесь в цикле создаются объекты класса TreeNode, т.е. узел дерева. В качестве первого параметра конструктору этого класса передается имя текущего элемента (каталога или файла) обрабатываемого каталога. Эти объекты добавляются в дерево рассмотренным ранее методом node.Nodes.Add.

Теперь если запустить нашу программу на выполнение, то в окне дерева появится список дисковых устройств (рис. 7-12).

Рис. 7-12. В окне дерева отображается список дисковых устройств

С этим списком, однако, есть одна проблема — узлы нашего дерева не раскрываются, сколько ни щелкай их мышью. Для ликвидации этой проблемы нам нужно обработать одно из событий, создаваемых элементом управления TreeView, а именно, события BeforeExpand.

Обработчик события BeforeExpand

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

Для создания обработчика этого события выделите дерево в окне дизайнера формы, а затем откройте вкладку событий, показанную на рис. 7-13.

Рис. 7-13. Добавление обработчика события BeforeExpand

Далее в поле BeforeExpand ведите имя обработчика событий treeView1_OnBeforeExpand.

После этого добавьте в этот обработчик событий следующий код:

private void treeView1_OnBeforeExpand(object sender,
  System.Windows.Forms.TreeViewCancelEventArgs e)
{
  treeView1.BeginUpdate();
      
  foreach(TreeNode node in e.Node.Nodes)
  {
     GetDirs(node);
  }

  treeView1.EndUpdate();
}

Событие BeforeExpand возникает при попытке пользователя раскрыть узел дерева. В этом случае наш обработчик заполняет открываемый узел при помощи рассмотренного ранее метода GetDirs. Ссылка на узел извлекается из поля e.Node.Nodes, передаваемого обработчику событий в качестве параметра.

После создания обработчика события BeforeExpand появится возможность раскрывать узлы дерева (рис. 7-14).

Рис. 7-14. Теперь узлы дерева можно раскрывать

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

Вы можете улучшить внешний вид дерева, показанного на рис. 7-14, если добавить к его узлам значки дисковых устройств и каталогов (папок). Как правило, большинство подобных деревьев всегда «украшается» значками.

Для нашего дерева Вы можете найти подходящие значки в каталоге с названием Program Files\Microsoft Visual Studio .NET\Common7\Graphics\icons. Выберите один значок для диска, один для закрытой папки и один для открытой папки.

Далее скопируйте эти значки в каталог проекта приложения FramesApp и подключите их к проекту. Чтобы подключить флажки к проекту, выберите из меню Project системы Microsoft Visual Studio .NET строку Add Existing Item, а затем добавьте файлы значков при помощи появившегося на экране диалогового окна.

Создание списка изображений

Чтобы использовать изображения в дереве, их необходимо объединить в список класса ImageList. Добавьте этот список в приложение, перетащив значок компонента ImageList в окно проектирования формы из панели Toolbox (рис. 7-15).

Рис. 7-15. Добавили элемент управления ImageList

Выделив элемент imageList1 левой клавишей мыши, отредактируйте его свойство Images. Для редактирования будет открыто окно Image Collection Editor, показанное на рис. 7-16.

Рис. 7-16. Добавили значки диска и папок

Воспользуйтесь кнопкой Add для добавления в список файлов изображений, скопированных ранее в каталог нашего приложения. Изображения следует разместить в том порядке, в каком они показаны на рис. 7-16. А именно, первым (с индексом 0) должно идти изображение для диска, вторым (с индексом 1) — изображение закрытой папки, и, наконец, третьим (с индексом 2) — изображение открытой папки.

Подключение списка изображений к дереву

Создав и заполнив список изображений, подключите его к дереву просмотра дисков и каталогов. Для этого отредактируйте свойство ImageList элемента управления treeView1, присвоив ему ссылку на список изображений imageList1 (рис. 7-17).

Рис. 7-17. Подключили список изображений к дереву

Изменения в исходном тексте программы

Теперь для отображения значков в узлах дерева нам необходимо изменить исходный текст методов DriveTreeInit и GetDirs. Напомним, что первый из этих методов инициализирует дерево, а второй — добавляет к узлу дерева список каталогов.

Обратите внимание на конструктор класса TreeNode, создающий узлы дерева внутри тела цикла foreach:

public void DriveTreeInit()
{
  string[] drivesArray = Directory.GetLogicalDrives();

  treeView1.BeginUpdate();
  treeView1.Nodes.Clear();

  foreach(string s in drivesArray)
  {
     TreeNode drive = new TreeNode(s, 0, 0);
     treeView1.Nodes.Add(drive);

     GetDirs(drive);
  }

  treeView1.EndUpdate();
}

Этот конструктор имеет три параметра. Про первый параметр мы уже рассказывали — он задает текст надписи для узла дерева. Теперь настало время рассказать и про два остальных параметра.

Если к элементу управления TreeView подключен список изображений, то второй и третий параметры конструктора класса TreeNode задают индексы изображений для узла дерева. При этом второй параметр определяет изображение невыделенного узла дерева, а третий — выделенного.

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

Другое дело — метод GetDirs:

public void GetDirs(TreeNode node)
{
  DirectoryInfo[] diArray;
  node.Nodes.Clear();
  string fullPath = node.FullPath;
  DirectoryInfo di = new DirectoryInfo(fullPath);
      
  try
  {
     diArray = di.GetDirectories();
  }
  catch
  {
     return;
  }

  foreach (DirectoryInfo dirinfo in diArray)
  {
     TreeNode dir = new TreeNode(dirinfo.Name, 1, 2);
     node.Nodes.Add(dir);
  }
}

Здесь конструктору класса TreeNode, размещенному внутри оператора цикла foreach, через второй и третий параметры мы передаем индексы значков закрытой и открытой папки, соответственно. В результате при отображении дерева папки, которые выделил пользователь, выделяются своим обозначением (рис. 7-18).

Рис. 7-18. Дерево со значками дисковых устройств и папок

Что же касается дисковых устройств, то все они отображаются одним и тем же значком.

К сожалению, среди многочисленных классов библиотеки Microsoft .NET Framework нам не удалось найти ни одного, позволяющего получить физические параметры дисковых устройств. Поэтому мы не смогли использовать отдельные значки для НГМД, сменных устройств памяти и устройств CD-ROM.

Тем не менее, средства Microsoft .NET Framework позволяют программам C# обращаться к программному интерфейсу OC Win32 API, поэтому после соответствующей доработки приложения можно будет использовать разные значки для изображения устройств внешней памяти разного типа.

Добавление флажков к узлам дерева

Присвоив свойству CheckBoxes значение True можно снабдить узлы дерева индивидуальными флажками (рис. 7-19).

Рис. 7-19. Флажками можно отмечать устройства и папки

Отметив все или некоторые узлы дерева флажками, можно выполнять над соответствующими объектами групповые операции. Если дерево показывает структуру дисков и файлов, то это могут быть такие операции, как, например, удаление, перемещение или копирование.

Редактирование текста узлов дерева

Изменив значение свойства LabelEdit на True можно разрешить пользователям редактировать текстовые надписи, расположенные около узлов дерева (рис. 7-20).

Рис. 7-20. Редактирование текста узлов дерева

Если дерево связано, например, с файловой системой, то такая возможность пригодится для переименования папок.

Список ListView

В правом верхнем фрейме нашего приложения мы расположили элемент управления ListView. Этот элемент обладает расширенными возможностями отображения списков различных объектов, имеющих линейную структуру. В нашем случае этот элемент управления будет использован для отображения содержимого каталогов, примерно в таком же виде, как это показано в правой части окна программы Windows Explorer (рис. 7-1).

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

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

Режимы отображения

Прежде всего, необходимо настроить свойство View. Это свойство определяет, в каком виде будет отображаться список. Если значение этого свойства равно Details, то мы увидим содержимое списка в виде детализированной таблицы. Именно это значение и потребуется нам в приложении FramesApp. Значение List задает режим отображения в виде простого списка, а значения SmallIcon и LargeIcon — в виде списка со значками маленького и большого размера, соответственно.

Итак, первым делом выделите список ListView в окне дизайнера форм и установите для свойства View значение Details.

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

Создание и настройка столбцов таблицы

Так как мы будем отображать список в виде таблицы, нам необходимо создать столбцы таблицы и определить их атрибуты. Для этого необходимо отредактировать свойство Columns. Это делается при помощи редактора ColumnHeader Collection Editor, показанного на рис. 7-21.

Рис. 7-21. Редактор столбцов списка

С помощью кнопки Add добавьте в таблицу три столбца, а затем настройте свойства Text, TextAlign и Width  этих столбцов.

Свойство Text задает название столбца, отображаемого в верхней части списка ListView. Первый столбец (с индексом 0) должен называться Имя, второй — Размер, а третий — Изменен.

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

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

С помощью свойства Width можно указать начальную ширину столбца в пикселах. После отображения списка пользователь сможет изменять ширину столбцов при помощи мыши.

После того как Вы создадите столбцы и определите их атрибуты, оттранслируйте приложение FramesApp и запустите его на выполнение. Теперь в правом верхнем фрейме приложения появится пустой список с заголовками (рис. 7-22).

Рис. 7-22. Появились заголовки столбцов

Сортировка содержимого списка

Свойство Sorting, задающее сортировку списка, может иметь значения None, Ascending и Descending.

В первом случае список отображает элементы в том порядке, в котором они были добавлены. Значение Ascending задает сортировку в порядке возрастания, а значение Descending — в порядке убывания.

Подключение значков к списку

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

В приложении FramesApp мы будем использовать 6 значков для отображения папок и файлов различных типов (файлы исполнимых программ, файлов справочной системы, текстовых файлов, файлов документов и пр.).

Создавая приложение, скопируйте файлы значков CLSDFOLD.BMP, DOC.BMP, EXE.BMP, HLP.BMP, TXT.BMP и WINDOC.BMP из папки Program Files\Microsoft Visual Studio .NET\Common7\Graphics\bitmaps\Outline\NoMask, а затем добавьте их к проекту приложения.

Для работы с этими значками нам потребуется еще один список изображений ImageList. Перетащите его значок их панели Toolbar в окно дизайнера форм. Новый список будет иметь идентификатор imageList2.

Отредактируйте этот список, как показано на рис. 7-23.

Рис. 7-23. Список изображений для списка каталогов и файлов

Необходимо расположить файлы списка в следующем порядке:

·         CLSDFOLD.BMP;

·         DOC.BMP;

·         EXE.BMP;

·         HLP.BMP;

·         TXT.BMP;

·         WINDOC.BMP

В нашем приложении список ListView отображается только в одном режиме, а именно, как детализированная таблица. Такая таблица снабжается значками только маленького размера. Если же Ваше приложение будет использовать режим LargeIcon, необходимо подготовить дополнительный список ImageList, добавив в него файлы значков большого размера.

Подготовив список изображений imageList2, подключите его к элементу управления ListView. Для этого присвойте свойствам SmallImageList, LargeImageList значение imageList2.

В том случае, если список ListView будет показывать значки и маленьких, и больших размеров, присвойте свойству SmallImageList идентификатор списка изображений маленького размера, а свойству LargeImageList — идентификатор списка изображений большого размера.

Наполнение списка ListView

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

Алгоритм наполнения списка

Алгоритм наполнения списка достаточно прост. Прежде всего, необходимо очистить список от предыдущего содержимого, т.к. наполнение одного и того же списка может происходить неоднократно. Эта операция выполняется с помощью метода Clear свойства Items списка:

listView1.Items.Clear();

Далее для добавления элемента в список нужно создать новый элемент как объект класса ListViewItem:

ListViewItem lvi = new ListViewItem("myfile.txt");

В качестве параметра конструктору передается текстовая строка имени файла. Она отображается в первом столбце детализированной таблицы.

Далее нужно добавить атрибуты элемента. Данная операция выполняется с помощью метода SubItems.Add:

lvi.SubItems.Add("1024");
lvi.SubItems.Add("10-05-2003");

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

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

lvi.ImageIndex = 4;

После того как все атрибуты элемента определены, Вы можете добавить элемент в список при помощи метода Add:

listView1.Items.Add(lvi);

В нашем приложении FramesApp наполнение списка будет происходить в цикле.

Обработчик события AfterSelect

Теперь нашей задачей будет наполнить функциональностью список ListView. Когда пользователь выделяет диск или каталог в дереве просмотра TreeView, создается событие AfterSelect. Обработчик этого события должен определить, какой диск или какой каталог был выделен, а затем наполнить окно списка ListView именами каталогов и файлов, расположенных на этом диске или в этом каталоге.

Создайте обработчик события AfterSelect в таком виде, как это показано ниже:

private void treeView1_OnAfterSelect(object sender,
  System.Windows.Forms.TreeViewEventArgs e)
{
  TreeNode selectedNode = e.Node;
  fullPath = selectedNode.FullPath;

  DirectoryInfo di = new DirectoryInfo(fullPath);
  FileInfo[] fiArray;
  DirectoryInfo[] diArray;

  try
  {
     fiArray = di.GetFiles();
     diArray = di.GetDirectories();
  }
  catch
  {
     return;
  }

  listView1.Items.Clear();

  foreach(DirectoryInfo dirInfo in diArray)
  {
     ListViewItem lvi = new ListViewItem(dirInfo.Name);
     lvi.SubItems.Add("0");
     lvi.SubItems.Add(dirInfo.LastWriteTime.ToString());
     lvi.ImageIndex = 0;

     listView1.Items.Add(lvi);
  }


  foreach(FileInfo fileInfo in fiArray)
  {
     ListViewItem lvi = new ListViewItem(fileInfo.Name);
     lvi.SubItems.Add(fileInfo.Length.ToString());
     lvi.SubItems.Add(fileInfo.LastWriteTime.ToString());
           
     string filenameExtension =
       Path.GetExtension(fileInfo.Name).ToLower();

     switch (filenameExtension)
     {
       case ".com":
       {
          lvi.ImageIndex = 2;
          break;
       }
       case ".exe":
       {
          lvi.ImageIndex = 2;
          break;
       }
       case ".hlp":
       {
          lvi.ImageIndex = 3;
          break;
       }
       case ".txt":
       {
          lvi.ImageIndex = 4;
          break;
       }
       case ".doc":
       {
          lvi.ImageIndex = 5;
          break;
       }
       default:
       {
          lvi.ImageIndex = 1;
          break;
       }
     }

     listView1.Items.Add(lvi);
  }
}

Расскажем о том, как работает этот обработчик событий.

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

TreeNode selectedNode = e.Node;

Далее, полный путь к выделенному узлу записывается в поле fullPath класса string (Вам необходимо создать такое поле в классе Form1):

fullPath = selectedNode.FullPath;

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

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

DirectoryInfo di = new DirectoryInfo(fullPath);
FileInfo[] fiArray;
DirectoryInfo[] diArray;

try
{
  fiArray = di.GetFiles();
  diArray = di.GetDirectories();
}
catch
{
  return;
}

Для выполнения этих операций применяются методы GetFiles и GetDirectories. Перечень файлов обработчик события сохраняет в массиве fiArray, а перечень каталогов — в массиве diArray.

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

listView1.Items.Clear();

Наполнение списка именами каталогов выполняется в цикле foreach:

foreach(DirectoryInfo dirInfo in diArray)
{
  ListViewItem lvi = new ListViewItem(dirInfo.Name);
  lvi.SubItems.Add("0");
  lvi.SubItems.Add(dirInfo.LastWriteTime.ToString());
  lvi.ImageIndex = 0;

  listView1.Items.Add(lvi);
}

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

В свойство lvi.ImageIndex записывается нулевое значение — индекс значка в списке изображений imageList2 с изображением закрытой папки.

После заполнения всех атрибутов элемента списка этот элемент добавляется в список методом listView1.Items.Add.

На следующем этапе в список добавляются имена файлов, расположенных в выделенном каталоге. Эти имена тоже добавляются в цикле

foreach(FileInfo fileInfo in fiArray)
{
  ListViewItem lvi = new ListViewItem(fileInfo.Name);
  lvi.SubItems.Add(fileInfo.Length.ToString());
  lvi.SubItems.Add(fileInfo.LastWriteTime.ToString());
           
  string filenameExtension =
     Path.GetExtension(fileInfo.Name).ToLower();

  switch (filenameExtension)
  {
     case ".com":
     {
       lvi.ImageIndex = 2;
       break;
     }
     …
     case ".doc":
     {
       lvi.ImageIndex = 5;
       break;
     }
     default:
     {
       lvi.ImageIndex = 1;
       break;
     }
  }

  listView1.Items.Add(lvi);
}

Размер очередного файла мы получаем с помощью свойства  Length, а дату последнего изменения — с помощью свойства LastWriteTime (как и для каталогов).

Что же касается значков, то тут алгоритм немного сложнее. Нам нужно определить тип текущего файла, проанализировав расширение его имени, а затем выбрать и записать в свойство lvi.ImageIndex индекс соответствующего значка. Расширение имени файла извлекается из полного имени методом Path.GetExtension, а затем все его буквы преобразуется в прописные методом ToLower. Непосредственный выбор значка выполняется при помощи оператора switch.

Подготовленный элемент списка, описывающий текущий файл, добавляется в список методом listView1.Items.Add.

Отображение содержимого текстовых файлов

Итак, теперь наше приложение может отображать в левом фрейме дерево каталогов, а в правом верхнем фрейме — содержимое выбранных каталогов. Остался незадействованным только правый нижний фрейм, в котором расположено окно текстового редактора RichTextBox.

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

Вот исходный текст нашего обработчика события ItemActivate:

private void listView1_OnItemActivate(object sender,
  System.EventArgs e)
{
  foreach(ListViewItem lvi in listView1.SelectedItems)
  {
     string ext = Path.GetExtension(lvi.Text).ToLower();
     if(ext == ".txt" || ext == ".htm" || ext == ".html")
     {
       try
       {
          richTextBox1.LoadFile(Path.Combine(fullPath, lvi.Text),
            RichTextBoxStreamType.PlainText);

          statusBar1.Text = lvi.Text;
       }
       catch
       {
          return;
       }
     }
     else if(ext == ".rtf")
     {
       try
       {
          richTextBox1.LoadFile(Path.Combine(fullPath, lvi.Text),
            RichTextBoxStreamType.RichText);

          statusBar1.Text = lvi.Text;
       }
       catch
       {
          return;
       }
     }
  }
}

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

foreach(ListViewItem lvi in listView1.SelectedItems)
{
  …
}

Идентификаторы выделенных элементов хранятся в наборе listView1.SelectedItems.

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

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

string ext = Path.GetExtension(lvi.Text).ToLower();

Если расширение имени соответствует текстовому файлу или файлу HTML, то мы загружаем содержимое файла в окно редактора RichTextBox, пользуясь для этого методом LoadFile:

if(ext == ".txt" || ext == ".htm" || ext == ".html")
{
  try
  {
     richTextBox1.LoadFile(Path.Combine(fullPath, lvi.Text),
       RichTextBoxStreamType.PlainText);

     statusBar1.Text = lvi.Text;
  }
  catch
  {
     return;
  }
}

В качестве первого параметра методу передается полный путь к файлу, полученный комбинированием полного пути каталога fullPath, выделенного в дереве, и имени файла lvi.Text, выделенного в списке ListView.

Через второй параметр методу LoadFile передается тип загружаемого документа, заданный как RichTextBoxStreamType.PlainText (т.е. текстовый документ).

Дополнительно имя документа отображается в строке состояния главного окна приложения, для чего это имя переписывается из свойства lvi.Text в свойство statusBar1.Text.

Обработка файлов с документами типа RTF, имеющих расширение имени rtf, выполняется аналогично. При этом тип документа указывается методу LoadFile как RichTextBoxStreamType.RichText.

На рис. 7-24 мы показали внешний вид главного окна нашего приложения, когда в нижний правый фрейм загружен документ RTF.

Рис. 7-24. Просмотр документа RTF в окне нашего приложения

Приложения MDI

Многие пользователи ОС Microsoft Windows хорошо знакомы с многооконным интерфейсом MDI (Multiple Document Interface), позволяющим в одном приложении работать одновременно с несколькими документами или с разными представлениями одного и того же документа.

Этот интерфейс был впервые описан в руководстве по разработке интерфейса пользователя System Application Architecture Common User Access Advanced Interface Design Guide (SAA/CUA), созданном IBM. Интерфейс MDI  использовался в ОС Microsoft Windows, начиная с версии 3.0, а также в графической оболочке Presentation Manager операционной системы OS/2. Современные версии ОС Microsoft Windows также позволяют создавать приложения с интерфейсом MDI (MDI-приложения).

В начале этой главы мы упоминали программу Registry Editor (рис. 7-3), использующую интерфейс MDI. Этим интерфейсом был снабжен и текстовый процессор Microsoft Word for Windows версии 2.0, способный предоставить пользователю два различных представления одного и того же документа. В одном окне могло отображаться, например, обычное представление документа, а в другом — представление в режиме просмотра оглавления документа.

Окна, отображающие содержимое документов внутри главного окна MDI-приложения, называются MDI-окнами.

Меню Windows

Любое MDI-приложение содержит меню Windows, предназначенное для управления окнами, отображающими различные документы или различные представления одного и того же документа.

Как правило, в меню Windows есть строки Cascade и Tile, с помощью которых пользователь может упорядочить MDI-окна, расположив их с перекрытием (друг за другом) или рядом друг с другом.

В этом меню могут быть и другие строки, управляющие расположением MDI-окон. Например, если приложение допускает минимизацию MDI-окон (когда они отображаются в виде значков), в меню Windows присутствует строка Arrange Icons. С помощью этой строки пользователь может упорядочить расположение пиктограмм свернутых окон.

Ниже разделительной черты в меню Windows обычно находятся строки, обозначающие отдельные MDI-окна. Если выбрать одну из этих строк, указанное окно будет активизировано и показано на первом плане.

Меню Windows может быть разным в различных MDI-приложениях. Однако в любом случае это меню позволяет пользователю автоматически упорядочить расположение MDI-окон и выбрать нужное окно из списка для активизации.

Двигая MDI-окна при помощи заголовка, Вы не сможете переместить их за пределы главного окна приложения. В то же время MDI-окна можно делать активными, при этом заголовок активного MDI-окна выделяется цветом.

Системное меню MDI-окна

Каждое MDI-окно имеет, как правило, системное меню и кнопки изменения размера. С помощью системного меню пользователь может изменять размеры или перемещать MDI-окно (строки Restore, Move, Size, Maximize, Minimize), закрывать окно (строка Close), передавать фокус ввода от одного MDI-окна другому (строка Next Window).

Если MDI-окно сворачивается в значок (минимизируется), то этот значок располагается в нижней части главного окна приложения.

Создание главного окна MDI-приложения

Главное окно приложения MDI и отдельные MDI-окна связывают родительские отношения. При этом главное окно MDI-приложения является родительским для дочерних MDI-окон. В этом разделе мы рассмотрим процедуру создания главного окна приложения MDI средствами Microsoft Visual Studio .NET на языке C#.

Для демонстрации способов создания приложений MDI мы создадим программу MDIApp.

Вначале необходимо создать новый проект приложения Windows Application Project, как обычно. Далее откройте окно редактирования свойств главного окна (формы) приложения и установите значение свойства IsMDIContainer, равным True (рис. 7-25).

Рис. 7-25. Установка свойства IsMDIContainer

В результате главное окно приложения превратится в контейнер для дочерних MDI-окон.

Далее, добавьте в приложение главное меню. Для этого перетащите из панели Toolbox системы Microsoft Visual Studio .NET значок главного меню MainMenu.

Создайте меню File со строками New, Close и Exit, как это показано на рис. 7-26.

Рис. 7-26. Меню File

С помощью строки New этого меню мы будем создавать новые MDI-окна с редактором текста на базе элемента управления RichTextBox. Строка Close предназначена для закрытия MDI-окон, а строка Exit — для завершения работы приложения.

Создайте также меню Edit со строками Copy и Paste, а также меню Window,  предназначенное для управления MDI-окнами и показанное на рис. 7-27.

Рис. 7-27. Меню Window

Строка Cascade предназначена для каскадного расположения открытых MDI-окон, а строки Horizontally Tile и Vertically Tile — для размещения окон рядом друг с другом в горизонтальном и вертикальном направлении, соответственно.

После создания меню Window Вам необходимо выделить его, а затем в окне редактирования свойств установить значение свойства MdiList равным True (рис. 7-28).  При этом во время работы приложения в меню Window будут автоматически добавлены строки списка открытых MDI-окон. В этом меню строки активных окон будут отмечены.

Рис. 7-28. Установка свойства MdiList

Создание MDI-окон

Как мы уже говорили, MDI-окна обычно используются для отображения различных документов или различных представлений одного и того же документа. В нашем приложении MDIApp MDI-окна будут содержать внутри себя элемент управления RichTextBox, т.е. редактор текста.

Шаблон MDI-окна

Прежде всего, нужно добавить в проект приложения новую форму, которая будет играть роль шаблона для создания дочерних MDI-окон. Для добавления формы щелкните правой клавишей мыши в окне Solution Explorer название проекта MDIApp и выберите в контекстном меню Add строку Add Windows Form.

После этого на экране появится диалоговое окно Add New Item,  в котором нужно выбрать значок Windows Form и затем нажать кнопку Open. В результате в окне дизайнера форм появится новая форма Form2.

Так как это окно будет предназначено для редактирования текста, перетащите в него из панели Toolbox значок элемента управления RichTextBox. Чтобы окно редактора текста RichTextBox заполнило собой клиентскую часть MDI-окна, установите значение свойства Dock равным Fill. Кроме того, проследите, чтобы свойство Anchor было равно Top, Left. Установка значений этого свойства выполняется при помощи редактора, окно которого показано на рис. 7-29.

Рис. 7-29. Установка свойства Anchor

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

Программный код для создания MDI-окна

Теперь нам нужно написать программный код, создающий MDI-окна. Этот код должен получать управление, когда пользователь выбирает строку New из меню File.

Добавьте следующий обработчик события Click для упомянутой выше строки New меню File:

private void menuItem2_Click(object sender, System.EventArgs e)
{
  Form2
mdiChild = new Form2();
 
mdiChild.MdiParent = this;
 
mdiChild.Show();
}

Здесь мы сначала создаем новую форму как объект класса Form2, а затем сохраняем ссылку на эту форму в переменной mdiChild. Свойство MdiParent этого окна должно содержать ссылку на родительское окно приложения MDI, поэтому мы записываем в него ссылку на объект класса Form1, используя ключевое свойство this.

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

На рис. 7-30 мы показали главное окно нашего приложения, в котором было создано четыре MDI-окна.

Рис. 7-30. Создано четыре MDI-окна

Создав с помощью меню File несколько дочерних MDI-окон, раскройте меню Window и убедитесь, что в нем появилось несколько новых строк (рис. 7-31).

Рис. 7-31. Новые строки в меню Window

С помощью этих строк Вы сможете выдвинуть на передний план любое нужное Вам MDI-окно.

Упорядочивание MDI-окон

Средства управления дочерними MDI-окнами позволяют легко упорядочить эти окна одним из трех способов.

Прежде всего, окна могут быть упорядочены с перекрытием (каскадным образом), как  это показано на рис. 7-30. Кроме этого, возможно расположение окон рядом по горизонтали (рис. 7-32) и по вертикали (рис. 7-33).

Рис. 7-32. Упорядочивание по горизонтали (типа Horizontally Tile)

Рис. 7-33. Упорядочивание по вертикали (типа Vertically Tile)

Чтобы пользователь мог упорядочивать дочерние MDI-окна, мы предусмотрели в меню Window строки Cascade, Horizontally Tile и Vertically Tile. Вот обработчики событий от этих строк меню, которые Вам необходимо добавить в исходный текст приложения:

private void menuItem11_Click(object sender, System.EventArgs e)
{
  this.LayoutMdi(MdiLayout.Cascade);
}

private void menuItem10_Click(object sender, System.EventArgs e)
{
  this.LayoutMdi(MdiLayout.TileHorizontal);
}

private void menuItem12_Click(object sender, System.EventArgs e)
{
  this.LayoutMdi(MdiLayout.TileVertical);
}

Как видите, все они вызывают метод LayoutMdi родительской формы, передавая ему в качестве параметра одну из констант, определяющих нужный тип упорядочивания. Это константы MdiLayout.Cascade, MdiLayout.TileHorizontal и MdiLayout.TileVertical.

Передача данных через буфер Clipboard

В большинстве случаев для работы с документами, отображаемыми в MDI-окнах, используется одно меню — главное меню приложений. Например, с помощью строк New и Close меню File пользователь нашего приложения MDIApp может, соответственно, создавать и уничтожать MDI-окна.

Теперь мы реализуем операцию обмена данных через универсальный буфер обмена Clipboard, для которого в нашем приложении предусмотрено меню Edit. Строка Copy этого меню должна копировать данные из активного MDI-окна в буфер Clipboard, а строка Paste — вставлять данные из буфера Clipboard в активное MDI-окно. Активным окном здесь называется MDI-окно, имеющее фокус ввода.

Копирование данных в буфер Clipboard

Вначале мы реализуем операцию копирования выделенного фрагмента текста из активного MDI-окна в Clipboard. Для этого добавьте следующий обработчик событий к строке Copy меню Edit:

private void menuItem8_Click(object sender, System.EventArgs e)
{
  Form activeChild = this.ActiveMdiChild;

  if(activeChild != null)
  {
     RichTextBox editBox = (RichTextBox)activeChild.ActiveControl;

     if(editBox != null)
     {
       Clipboard.SetDataObject(editBox.SelectedText);
     }
  }
}

Здесь мы вначале определяем идентификатор активного MDI-окна, извлекая его из свойства ActiveMdiChild родительского окна приложения MDI. Этот идентификатор сохраняется в переменной activeChild.

Если в приложении нет активного окна, то приведенный выше обработчик событий завершает свою работу, не выполняя никаких других действий.

Определив идентификатор активного MDI-окна, обработчик события получает идентификатор активного элемента управления, расположенного внутри этого окна. Для этого используется свойство activeChild.ActiveControl, хранящее ссылку на активный элемент управления.

В нашем случае MDI-окно содержит только один элемент управления — редактор RichTextBox. Поэтому полученную ссылку мы преобразуем явным образом к типу RichTextBox и сохраняем в переменной editBox для дальнейшего использования.

Теперь мы получили идентификатор нашего редактора текста и готовы переписать выделенный в его окне фрагмент текста в универсальный буфер обмена Clipboard. Запись в Clipboard осуществляется методом Clipboard.SetDataObject.

В качестве параметра мы передаем методу Clipboard.SetDataObject выделенный текст, извлеченный из редактора текста с помощью свойства editBox.SelectedText.

Вставка данных из буфера Clipboard

Теперь дополним приложение кодом, необходимым для вставки данных из универсального буфера обмена Clipboard в активное MDI-окно.

Добавьте следующий обработчик события для строки Paste меню Edit:

private void menuItem9_Click(object sender, System.EventArgs e)
{
  Form activeChild = this.ActiveMdiChild;

  if(activeChild != null)
  {
     RichTextBox editBox = (RichTextBox)activeChild.ActiveControl;

     if(editBox != null)
     {
       IDataObject data = Clipboard.GetDataObject();

       if(data.GetDataPresent(DataFormats.Text))
       {
          editBox.SelectedText =
            data.GetData(DataFormats.Text).ToString();              
       }
     }
  }
}

Как видите, этот обработчик вначале определяет идентификатор активного MDI-окна, а затем, пользуясь этим идентификатором и свойством ActiveControl, получает идентификатор редактора текста RichTextBox.

Далее для вставки данных из буфера Clipboard наш метод получает ссылку на интерфейс IDataObject, вызывая для этого метод Clipboard.GetDataObject. Эта ссылка сохраняется в переменной data.

Пользуясь полученной ссылкой на интерфейс IDataObject, наш обработчик события определяет, имеется ли в буфере Clipboard текст, который можно было бы вставить в редактор текста. Для этого используется метод GetDataPresent. В качестве параметра этому методу передается идентификатор формата текстовых данных DataFormats.Text (напомним, что в буфере Clipboard могут одновременно храниться данные разных форматов).

Если в буфере Clipboard имеются текстовые данные, программа извлекает их при помощи метода GetData, а затем преобразует в текстовую строку при помощи метода ToString. Далее эта текстовая строка записывается в свойство SelectedText нашего редактора текста, благодаря чему и происходит вставка данных из буфера Clipboard.

[Назад] [Содержание] [Дальше]