Академический Документы
Профессиональный Документы
Культура Документы
Оглавление
Предисловие
Глава 1. Принципы работы приложений Windows
Глава 2. Работа с Microsoft Visual Studio .NET
Глава 3. Формы в приложениях C#
Глава 4. Создание редактора текста
Глава 5. Диалоговые окна
Глава 6. Использование элементов управления
Глава 7. Многооконный пользовательский интерфейс
Глава 8. Приложения с базами данных
Глава 9. Интеграция с MS SQL Server
Глава 10. Графический интерфейс GDI+
Приложение 1. Исходные тексты приложения SimpleNotepad
Библиографический список
Визуальное проектирование
приложений C#
А.В. Фролов, Г.В. Фролов
Аннотация
В книге рассмотрены приемы визуального проектирования автономных приложений на
языке программирования C# с использованием инструментальных средств Microsoft
Visual Studio .NET. Рассмотрены принципы функционирования приложений Windows,
управляемых событиями, на платформе Microsoft .NET Framework. Приведена методика
использования Microsoft Visual Studio .NET для создания и отладки проектов
приложений Windows на языке C#.
Приведена информация, необходимая для быстрого создания деловых
приложений со сложным пользовательским интерфейсом, интегрированных с базами
данных. Описаны приемы проектирования диалоговых окон, элементов управления и
других компонентов. Имеется большое количество примеров исходных программ,
снабженных необходимыми комментариями.
Предисловие
Создавая сложные и очень сложные приложения, профессиональные программисты
часто используют специальные средства ускоренной разработки приложений (Rapidly
Application Development, RAD). Такие средства позволяют значительно сократить сроки
разработки и отладки, так что с их использованием приложение, возможно, будет
создано еще до того, как в нем отпадет необходимость.
Средства RAD широко используются для проектирования таких информационных
систем, как бухгалтерские системы и системы управления хозяйственной
деятельностью предприятий, справочно-информационные системы с базами данных,
управляющие системы и т.п. Кроме того, в связи с переносом бизнеса в Интернет,
сегодня возросла актуальность применения средств RAD и для создания Web-
приложений, функционирующих в Интернете и в интрасетях компаний.
За счет чего средства RAD ускоряют разработку приложений?
Во многом это происходит благодаря применению графических средств
проектирования пользовательского интерфейса.
Напомним, что первое время приложения для операционной системы (ОС)
Microsoft Windows создавались при помощи инструментов, предполагающих
использование обычных редакторов текста, дополненных средствами синтаксического
выделения конструкций языка, средствами отладки и справочно-информационными
системами.
Справедливости ради отметим, что в таких инструментальных средствах, как
Microsoft Visual C++, Borland C++ и аналогичных были предусмотрены так называемые
редакторы ресурсов. Механизм ресурсов приложений Microsoft Windows был разработан
для того, чтобы отделить элементы пользовательского интерфейса (диалоговые окна,
кнопки, списки и т.д.) от программного кода. При этом описание элементов интерфейса
хранится внутри исполнимого файла приложения и поддается редактированию без
перетрансляции исходных текстов самого приложения.
Формально Microsoft Visual C++ в комплекте с библиотекой классов Microsoft
Foundation Classes (MFC) может выступать в качестве системы визуального
проектирования приложений. Однако на деле создание с его помощью программ,
имеющих по-настоящему сложный пользовательский интерфейс, совсем не просто.
К сожалению, внешний вид компонентов пользовательского интерфейса,
хранящихся в ресурсах обычных приложений Microsoft Windows, уже не отвечает
требованиям сегодняшнего дня. Избалованные хорошо проработанным дизайном Web-
сайтов, пользователи требуют аналогичных дизайнерских решений и от автономных
приложений Windows. Теоретически механизм ресурсов приложений, библиотека
классов MFC и программный интерфейс Win32 API (Application Program Interface, API)
позволяют придать приложению самый замысловатый внешний вид, однако это
достигается использованием нетривиальных приемов программирования.
В итоге реализация сложного пользовательского интерфейса приложения при
помощи Microsoft Visual C++ и MFC доступна только опытным программистам, хорошо
знакомыми с архитектурой и принципами работы ОС Microsoft Windows, а также
искушенными в использовании программного интерфейса Win32 API.
Библиотека MFC тоже не свободна от недостатков. Ее объектно-ориентированная
реализация оставляет желать лучшего, и в добавок вместе с приложениями приходится
распространять специальную библиотеку динамической компоновки (Dynamic Load
Library, DLL). К настоящему времени существует несколько различных версий этот
библиотеки, что создает проблемы при установке приложений MFC на компьютеры
пользователей. Эта проблема известна под названием «ада DLL» (Dll Hell).
Успех существующих систем RAD
Среди ранее созданных и наиболее удачных инструментов ускоренной разработки
приложений заслуживает упоминание Borland Delphi. Эта система получила огромную
популярность, в том числе благодаря по-настоящему удобным средствам визуального
проектирования. Эти средства дополняли стандартные элементы интерфейса Microsoft
Windows новыми средствами, имеющими современный и привлекательный внешний
вид. Помимо визуальных средств разработки приложений Microsoft Windows, в Borland
Delphi имелись компоненты, предназначенные для интеграции приложений с базами
данных, а также средства создания активных компонентов Web-сайтов.
Другим инструментом быстрой разработки приложений, несомненно,
заслуживающим упоминания, является Microsoft Visual Basic.
Упрощенно, процесс визуальной разработки приложений в Borland Delphi и
Microsoft Visual Basic заключается в графическом проектировании внешнего вида
(дизайна) приложений, с последующей привязкой программного кода к элементам
пользовательского интерфейса. При этом для решения самых нужных задач (таких,
например, как обращение к базам данных) используется богатая библиотека
программных компонентов.
Создавая новую программу, разработчик имеет дело с графическим
представлением ее главного окна и всех других окон. Перемещая мышью в эти окна из
специальных палитр значки элементов интерфейса (кнопок, меню и т.д.), а также
значки программных компонентов (соединений с базами данных, компонентов
управления меню и др.), программист может очень быстро получить работающий скелет
программы. При этом размещение элементов интерфейса и настройка их размеров
производится простым и понятным образом. Фактически, программист просто «рисует»
окна своего приложения, быстро достигая необходимого результата.
Когда скелет программы создан, его следует наполнить необходимой
функциональностью. Щелкая мышью элементы пользовательского интерфейса и значки
программных компонентов в окне проектируемого приложения, программист вводит в
редакторе текста программный код, предназначенный для работы с этими элементами.
В результате намного упрощается процедура привязки программного кода к элементам
пользовательского интерфейса и другим компонентам приложения.
Такая технология во много раз ускоряет разработку приложений со сложным
пользовательским интерфейсом, что и привело к росту популярности Borland Delphi,
Microsoft Visual Basic и других аналогичных средств.
Визуальное проектирование
приложений C#
А.В. Фролов, Г.В. Фролов
ГЛАВА 1. ПРИНЦИПЫ РАБОТЫ ПРИЛОЖЕНИЙ WINDOWS
РАЗНОВИДНОСТИ ПРОГРАММ
Синхронное и асинхронное выполнение программ
Однопоточные и многопоточные программы
СООБЩЕНИЯ
Создание сообщений
Очередь сообщений
Обработка сообщений
Фокус ввода
Цикл обработки сообщений
Функция окна
Передача сообщений
СОЗДАНИЕ И УНИЧТОЖЕНИЕ ОКНА
СТРУКТУРА ПРИЛОЖЕНИЯ С ОБРАБОТКОЙ СООБЩЕНИЙ
РЕСУРСЫ ПРИЛОЖЕНИЙ MICROSOFT WINDOWS
КАК ВСЕ НЕПРОСТО В МИРЕ WINDOWS
// Заголовок окна
char const szWindowTitle[] = "Window Application";
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
return msg.wParam;
}
// =====================================
// Функция InitApp
// Вызывается из функции WinMain для
// инициализации приложения.
// Выполняет регистрацию класса окна
// =====================================
BOOL InitApp(HINSTANCE hInstance)
{
ATOM aWndClass; // атом для кода возврата
WNDCLASS wc; // структура для регистрации класса окна
memset(&wc, 0, sizeof(wc));
…
// Указатель на функцию окна, обрабатывающую
// сообщения, предназначенные для всех окон,
// созданных на основе данного класса
wc.lpfnWndProc = (WNDPROC) WndProc;
…
// Имя, которое присваивается создаваемому
// классу и используется при создании
// окон данного класса
wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса
aWndClass = RegisterClass(&wc);
// =====================================
// Функция окна WndProc
// НЕ ВЫЗЫВАЕТСЯ ни из одной функции приложения. Эту функцию вызывает
// ОС в процессе обработки сообщений. Для этого адрес функции WndProc
// указывается при регистрации класса окна. Функция выполняет
// обработку сообщений главного окна приложения
// =====================================
LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Выполняем обработку сообщений. Идентификатор
// сообщения передается через параметр msg
switch (msg)
{
// Это сообщение приходит, когда вы поместили курсор
// мыши в область главного окна приложения и нажали
// левую клавишу мыши
case WM_LBUTTONDOWN:
{
MessageBox(NULL,
"Нажата левая клавиша мыши",
"Сообщение", MB_OK | MB_ICONINFORMATION);
return 0;
}
Далее Вам будет предложено установить диск Windows Component Update (это
пятый диск дистрибутива). Щелкните ссылку Install Now! (рис. 2-3), и процесс
установки будет продолжен.
Возможно, что в процессе установки обновлений ОС будет несколько раз
перезагружаться. Для удобства можно сообщить программе установки идентификатор и
пароль пользователя для автоматического входа в систему после перезагрузки. В этом
случае процесс установки упростится и фактически будет сведен к своевременной
замене компакт-дисков в приводе CD-ROM.
Когда обновление компонентов ОС будет закончено, щелкните ссылку Done,
расположенную в нижней части окна списка компонентов, и запустите программу
установки заново. Теперь Вы сможете щелкнуть кнопку 2. Visual Studio .NET для
продолжения установки. Вам будет нужно снова вставить в привод CD-ROM первый
диск дистрибутива.
На следующем этапе мастер запросит серийный номер, а также параметры
установки Microsoft Visual Studio .NET. Вам будет предложен список устанавливаемых
компонентов, показанный на рис. 2-4.
Рис. 2-4. Выбор компонентов Microsoft Visual Studio .NET для установки
Это окно ведет себя подобно обычным окнам Microsoft Windows. Оно имеет
заголовок со значком системного меню, а также со значками, позволяющими изменять
размеры окна и закрывать окно. Заметьте, все это мы сделали, не написав ни одной
строчки программного кода (точнее говоря, за нас все сделал мастер проектов).
Главное окно Microsoft Visual .NET
Теперь, когда мы научились создавать простейшие проекты, посмотрим внимательнее
на главное окно системы разработки Microsoft Visual .NET, чтобы идентифицировать
расположенные там наиболее важные элементы. По мере изложения материала мы еще
не раз будем возвращаться к этому окну.
Рабочее окно
В центре главного окна Microsoft Visual .NET расположено рабочее окно с вкладками.
На рис. 2-7 видны вкладки Start Page и Form1.cs [Design]. По мере работы с проектом в
рабочем окне будут появляться и другие вкладки.
Вкладка Start Page
Вкладка Start Page (рис. 2-9) открывает доступ к списку проектов (вкладка Projects),
системе поиска примеров приложений (вкладка Find Samples), а также ко многим
важным ресурсам Интернета, имеющим отношение к разработке приложений для
платформы Microsoft .NET.
Рис. 2-9. Вкладка Start Page
При помощи ссылок, показанных в левой части рис. 2-9, можно получить доступ
on-line к ресурсам сайта http://msdn.microsoft.com (т.е. к библиотеке MSDN).
Разумеется, для этого компьютер должен быть подключен к Интернету.
Ссылка What’s New позволит просмотреть различные новости, интересные
разработчикам приложений.
Если Вам нужна помощь других пользователей Microsoft Visual .NET и экспертов,
воспользуйтесь ссылкой Online Community. Эта ссылка откроет Вам доступ к
многочисленным конференциям и чатам, где Вы сможете задать свой вопрос и найти на
него ответ.
Воспользовавшись ссылкой Headlines, можно ознакомиться с самыми новыми
ресурсами и новостями, касающимися самых разных аспектов разработки приложений.
Для поиска какой-либо информации по ключевым словам в библиотеке Microsoft
MSDN предназначена ссылка Search Online.
Ссылка Download отправит Вас в раздел сайта MSDN, из которого можно загрузить
различные обновления и дополнения к системе разработки Microsoft Visual .NET,
документацию и другие файлы.
С помощью ссылки XML Web Services можно отыскать нужный Web-сервис, или
зарегистрировать свой собственный. Эта ссылка, а также ссылка Web Hosting,
предназначенная для поиска провайдеров, будет интересна разработчикам Web-
приложений.
И, наконец, на странице My Profile можно настроить некоторые параметры работы
системы Microsoft Visual .NET. Например, здесь Вы можете указать, что при запуске
Microsoft Visual .NET необходимо автоматически загружать проект, с которым работали
в прошлый раз. Для этого в списке At startup нужно выбрать строку Load last loaded
solution (этот список появится в нижней части страницы, открываемой щелчком ссылки
My Profile).
Вкладка проектирования формы
Визуальное проектирование формы осуществляется на вкладке Form1.cs [Design]. Эта
вкладка показана на рис. 2-7.
При необходимости Вы можете изменить размеры создаваемой формы и цвет ее
фона, разместить в форме текст, графические изображения и различные элементы
управления. Для этого нужно щелкнуть левой клавишей мыши панель Toolbox, раскрыв
ее, как это показано на рис. 2-10.
Далее, опять же при помощи мыши, нужно перетащить значки необходимых Вам
элементов управления на поверхность создаваемой формы. На рис. 2-10 мы разместили
таким способом в нашей форме две кнопки и два текстовых поля.
Если это требуется, то с помощью мыши можно отрегулировать размеры и
расположение элементов управления, добавленных в форму, а также размеры самой
формы.
Окно Solution Explorer
Окно Solution Explorer, показанное на рис. 2-11, позволяет просматривать и
редактировать файлы проекта. Просмотр может осуществляться по файлам или по
классам (вкладка Class View).
Для нашего приложения было создано одно решение. На рис. 2-11 ему
соответствует корневой значок дерева файлов, обозначенный как Solution ‘Hello’ (1
project).
На следующем уровне иерархии находится значок проекта с надписью Hello.
Далее следуют папки и файлы, входящие в проект.
Папка References содержит перечень классов, на которые имеются ссылки в
файлах исходных текстов проекта. Обратите внимание, что среди перечисленных в
этой папке классов имеется класс System.Windows.Forms, на базе которого создана
наша форма.
Еще в проекте имеются три файла с именами App.ico, AssemblyInfo.cs и Form1.cs.
Первый из этих файлов содержит изображение значка приложения (application
icon). Значок появляется на экране при переключении окон, а также в панели задач
рабочего стола Microsoft Windows. Второй файл с именем AssemblyInfo.cs содержит
описание сборки (assembly) проекта, и, наконец, третий файл Form1.cs хранит
собственно исходный текст нашего приложения, написанный на языке C#.
Редактирование значка приложения
Если дважды щелкнуть имя файла значка левой клавишей мыши, в рабочем окне
системы Microsoft Visual Studio .NET появится окно редактирования значка приложения
(рис. 2-12).
Рис. 2-12. Окно редактирования значка приложения
namespace Hello
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code
// after InitializeComponent call
//
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
}
}
Пространства имен
В самое начало фала исходного текста приложения мастер проектов вставил строки,
подключающие несколько пространств имен (name space) с помощью ключевого слова
using:
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
Пространство имен System содержит определение фундаментальных и базовых
классов, определяющих типы данных, события, обработчики событий и другие,
необходимые в каждом приложении компоненты.
Пространство имен System.Drawing необходимо для доступа к интерфейсу
графических устройств (Graphics Device Interface, GDI), а точнее, к его расширенной
версии GDI+. Классы, определенные в этом пространстве имен, необходимы для
рисования в окнах приложений текста, линий, двухмерных фигур, изображений и
других графических объектов.
В пространстве имен System.Collections определены классы, реализующие
функциональность таких контейнеров, как массивы, списки, словари, хэши и т.п.
Классы пространства System.ComponentModel используются для реализации
необходимого поведения компонентов и элементов управления приложения на этапе
его разработки и выполнения.
Пространство имен System.Windows.Forms мы уже упоминали — в нем определены
классы, реализующие поведение оконных форм, составляющих базу оконных
приложений Microsoft windows на платформе Microsoft .NET Frameworks.
Класс System.Data необходим приложениям, работающим с базами данных
посредством интерфейса ADO.NET. Этот интерфейс мы рассмотрим позже в 8 и 9
главах, посвященных интеграции приложений с базами данных.
Наше приложение также определяет собственное пространство имен Hello:
namespace Hello
{
…
}
В этом пространстве располагается определение класса Form1 приложения Hello.
Класс Form1
Наше приложение содержим в пространстве имен Hello определение только одного
класса Form1:
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
…
}
Этот класс создается мастером проектов на базе класса
System.Windows.Forms.Form, о котором мы уже говорили ранее. Обратите внимание,
что мастер проектов добавил перед определением класса комментарий специального
вида, включающий в себя тег <summary> XML-описания класса. Это описание Вы
можете отредактировать по своему усмотрению, отразив в нем какую-либо
осмысленную информацию о назначении класса. При необходимости подробное
описание языка XML Вы найдете в [9].
Наличие подобных комментариев упрощает документирование проекта.
Рассмотрим поля и методы, определенные в классе Form1.
Поле components
В классе Form1 мастер проекта создал одно поле components типа private и класса
System.ComponentModel.Container:
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
Оно представляет собой контейнер, предназначенный для хранения компонентов,
размещаемых в форме. Как Вы увидите дальше, с этим полем работает мастер форм.
Конструктор
Задача конструктора класса Form1, вызываемого при создании новой формы,
заключается в инициализации всех компонентов, размещенных в форме. С этой целью
конструктор вызывает метод InitializeComponent, определенный в классе Form1:
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code
// after InitializeComponent call
//
}
Мастер проектов добавил в исходный текст конструктора комментарий, в котором
говорится, что Вы можете добавить после вызова метода InitializeComponent другой
необходимый инициализирующий код.
Метод Dispose
Деструктор класса Form1 в явном виде отсутствует. Однако для освобождения ресурсов
приложения после закрытия формы в этом классе определен метод Dispose:
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
В его задачу входит освобождение ресурсов всех компонентов, хранящихся в
упомянутом выше контейнере components, при закрытии формы. Далее этот метод
вызывает метод Dispose базового класса (т.е. класса System.Windows.Forms.Form) для
выполнения действий по освобождению ресурсов, определенных в этом классе.
Метод InitializeComponent
Мы уже говорили, что конструктор класса Form1 вызывает для инициализации
компонентов приложения метод InitializeComponent. Обратите внимание на то, что
исходный текст этого метода заключен внутри блока, образованного ключевыми
словами #region и #endregion:
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.Size = new System.Drawing.Size(300,300);
this.Text = "Form1";
}
#endregion
В автоматически созданном комментарии к методу InitializeComponent говорится о
том, что этот метод используется мастером форм, и его не следует модифицировать
вручную. В режиме просмотра исходного текста данный блок по умолчанию
отображается в свернутом виде (рис. 2-14), но при необходимости его можно
развернуть.
Что делает метод InitializeComponent?
Прежде всего, этот метод создает новый контейнер для хранения компонентов, и
записывает его в описанное нами ранее поле components:
this.components = new System.ComponentModel.Container();
Далее метод InitializeComponent устанавливает два атрибута формы — ее размер
Size и текст заголовка Text:
this.Size = new System.Drawing.Size(300,300);
this.Text = "Form1";
Так как пока наша форма не содержит никаких элементов управления, на этом
инициализация формы будет закончена.
Метод Main
В классе Form1 определен статический метод Main, получающий управление при
запуске нашего приложения:
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
Этот метод играет роль точки входа приложения, с которой и начинается его
работа.
Метод Main очень прост. Он состоит всего из одной строки, в которой вызывается
метод Application.Run:
Application.Run(new Form1());
В качестве параметра методу Application.Run передается ссылка на новый, только
что созданный объект класса Form1 (т.е. на нашу форму).
Как это работает?
Вначале конструктор создает объект класса Form1 — новую форму, выступающую
в качестве главного окна нашего приложения. Метод Application.Run, получая при
вызове ссылку на эту форму, инициализирует цикл обработки сообщений и запускает
его.
При этом метод Application.Run берет на себя регистрацию функции окна и
обработку сообщений, о которых мы рассказывали в 1 главе нашей книги. Поэтому
программисту, создающему оконное приложение на языке C#, не надо об этом
заботиться.
Когда пользователь закрывает главное окно приложения, метод Application.Run
возвращает управление, и приложение завершает свою работу.
В форме нашего первого приложения нет никаких компонентов и элементов
управления, поэтому нет и обработчиков событий, создаваемых этими компонентами.
Теперь мы немного усовершенствуем приложение, добавив в него кнопку и
предусмотрев обработку событий, связанных с этой кнопкой.
Приложение с обработкой событий
В исходном виде главное окно (форма) приложения Hello обладает весьма небольшой
функциональностью. Наше первое усовершенствование приложения будет заключаться
в добавлении кнопки. Если щелкнуть эту кнопку, которой на экране должно будет
появиться простейшее диалоговое окно с сообщением.
Добавление кнопки в форму приложения
Откройте проект приложения Hello, если Вы его закрыли. Затем перетащите левой
клавишей мыши значок кнопки с надписью Button на изображение нашей формы,
открытой в режиме визуального проектирования (рис. 2-16).
Отыщите в списке свойство Text (оно выделено на рис. 2-17), и замените его
значение — вместо button1 напишите там строку «Сообщение». Теперь если убрать
текстовый курсор из поля Text, то надпись на кнопке изменится.
Добавив кнопку и отредактировав надпись, нажмите клавишу F5 для трансляции и
запуска программы. Теперь в окне нашей программы появится кнопка, которую можно
нажимать (рис. 2-18).
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(200, 16);
this.button1.Name = "button1";
this.button1.TabIndex = 0;
this.button1.Text = "Сообщение";
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.AddRange(new System.Windows.Forms.Control[] {
this.button1});
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
Прежде всего, метод InitializeComponent создает кнопку как объект класса
System.Windows.Forms.Button, и сохраняет ссылку на этот объект в поле button1 для
дальнейшего использования:
this.button1 = new System.Windows.Forms.Button();
После этого начинается процесс размещения кнопки на поверхности формы.
Этот процесс начинается с вызова метода SuspendLayout:
this.SuspendLayout();
Это делается для временного отключения механизма генерации сообщений,
связанных с размещением элементов в окне формы. Такое отключение позволяет
снизить непроизводительные затраты ресурсов процессора при размещении в форме
нескольких компонентов сразу.
После того как все необходимые компоненты будут размещены, механизм
генерации упомянутых выше сообщений включается снова при помощи метода
ResumeLayout:
this.ResumeLayout(false);
В промежутке между вызовами методов SuspendLayout и ResumeLayout программа
добавляет и размещает элементы управления, а также настраивает их свойства.
Первым делом в форму добавляется наша кнопка:
this.button1.Location = new System.Drawing.Point(200, 16);
Для рисования кнопки внутри окна формы в определенной позиции программа
вызывает метод System.Drawing.Point. Координаты места расположения кнопки по
горизонтальной и вертикальной оси координат передаются методу через параметры.
Далее программа устанавливает свойства кнопки:
this.button1.Name = "button1";
this.button1.TabIndex = 0;
this.button1.Text = "Сообщение";
Свойство Name хранит идентификатор кнопки. При необходимости этот
идентификатор можно отредактировать в окне, показанном на рис. 2-17.
В свойстве Text хранится надпись на кнопке. Что же касается свойства TabIndex,
то оно приобретает значение, когда в форме имеется несколько элементов управления,
таких как кнопки, поля редактирования, списки и т.д. Это свойство задает порядок
выбора элементов в окне формы при использовании клавиши табуляции. Напомним,
что с помощью клавиши табуляции можно перемещать фокус ввода между элементами
управления обычных диалоговых окон Microsoft Windows.
После добавления кнопки и настройки ее свойств метод InitializeComponent задает
свойства самой формы:
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.AddRange(
new System.Windows.Forms.Control[]
{
this.button1
}
);
Свойство AutoScaleBaseSize задает базовые размеры, которые используются
формой для автоматического масштабирования. При этом за основу берется размер
системного шрифта.
При помощи свойства ClientSize программа определяет размеры так называемой
клиентской области окна (client area) нашей формы. Эта область не включает в себя
заголовок окна, рамку и полосы прокрутки.
И, наконец, при помощи метода Controls.AddRange программа добавляет в форму
контейнер с массивом System.Windows.Forms.Control[] элементов управления. В нашем
случае этот массив состоит из одной кнопки с идентификатором button1.
Последнее, что делает метод InitializeComponent, это настройка свойств формы
Name и Text:
this.Name = "Form1";
this.Text = "Form1";
Первое из них задает имя (идентификатор) формы, а второе — текстовую строку,
отображаемую в заголовке формы.
Заметьте, что мы сделали лишь несколько движений мышью, добавив кнопку в
окно формы. При этом мастер форм автоматически добавил весь код, необходимый для
создания и размещения кнопки.
Обработка событий от кнопки
Хотя в усовершенствованной версии приложения есть кнопка, она не несет никакой
полезной нагрузки. Теперь нашей задачей будет создание для этой кнопки обработчика
события. Когда пользователь щелкает кнопку мышью, этот обработчик должен будет
выводить на экран компьютера диалоговое окно с сообщением.
Чтобы обеспечить обработку событий от кнопки, дважды щелкните мышью ее
изображение в окне дизайнера форм (рис. 2-16). Сразу после этого в рабочей области
Microsoft Visual Studio .NET откроется окно редактирования исходного текста
программы, в который будет добавлен метод button1_Click:
private void button1_Click(object sender, System.EventArgs e)
{
}
Пока этот метод, получающий управление при щелчке кнопки, ничего не делает,
однако мы скоро изменим данное обстоятельство.
Метод button1_Click — это обработчик события, возникающего, когда
пользователь щелкает кнопку. Чтобы этот метод заработал, его нужно подключить на
этапе инициализации формы. Такое подключение обеспечивает следующая строка,
добавленная дизайнером формы в метод InitializeComponent:
this.button1.Click += new System.EventHandler(this.button1_Click);
Чтобы в процессе обработки сообщения от кнопки вывести на экран сообщение,
измените исходный текст метода button1_Click следующим образом:
private void button1_Click(object sender, System.EventArgs e)
{
MessageBox.Show("Для Вас есть сообщение!");
}
Мы вставили сюда вызов метода MessageBox.Show. Этот метод отображает на
экране диалоговое окно с текстом сообщения, переданного в качестве параметра (рис.
2-19).
Рис. 2-19. Диалоговое окно с сообщением
Если теперь нажать клавишу F11, отладчик приступит к созданию объекта класса
Form1. Вначале будет проинициализировано поле components, а затем, при
последующих нажатиях клавиши F11, отладчик перейдет к пошаговому выполнению
конструктора класса Form1. На рис. 2-21 показан как раз этот момент.
Теперь если нажать кнопку F10, метод InitializeComponent будет выполнен без
отладки. При помощи кнопки F11 Вы сумеете отладить строки метода
InitializeComponent в пошаговом режиме.
Еще Вам может оказаться полезной клавиша Shift-F12, соответствующая строке
Step Out меню Debug. При помощи этой строки можно перевести программу из
пошагового режима в автоматический. Однако после выхода из тела текущего метода
программа снова перейдет в пошаговый режим.
Для того чтобы продолжить выполнение с текущего места без отладки, нажмите
клавишу F5 или выберите строку Continue меню Debug.
Точка останова
Отлаживая программу Hello, Вы можете столкнуться с одной проблемой. Выполняя
программу по шагам, Вы не сможете отладить обработчик события, создаваемого в
результате нажатия кнопки Сообщение. В самом деле, после прохода конструктора
класса Form1 программа будет работать уже без отладки.
Чтобы отладить обработчик события, необходимо установить так называемую
точку остановки (breakpoint). Для этого щелкните левой клавишей мыши в узкое
вертикальное поле слева от той строки кода, на которой нужно установить точку
останова. После этого строка будет выделена красно-кирпичным цветом, а слева
напротив нее появится жирная точка (рис. 2-22).
Рис. 2-22. Установка точки останова
В левой части этой вкладки находится дерево объектов программы. Раскрывая его
ветви, Вы можете получить доступ ко всем необходимым Вам переменным и полям.
Вкладка Autos (рис. 2-24) удобна для контроля текущих изменений значений в
полях и переменных.
Визуальное проектирование
приложений C#
А.В. Фролов, Г.В. Фролов
ГЛАВА 3. ФОРМЫ В ПРИЛОЖЕНИЯХ C#
НАСТРОЙКА ВНЕШНЕГО ВИДА И ПОВЕДЕНИЯ ФОРМЫ
Изменение заголовка окна
Изменение значка формы
Выбор рамки
Кнопки изменения размеров формы
Изменение цвета фона формы
Добавление фонового изображения
Изменение формы курсора
ДОБАВЛЕНИЕ ЭЛЕМЕНТОВ УПРАВЛЕНИЯ
Добавление и настройка текстовых полей
Изменение текста надписи
Изменение шрифта
Цвет текста и фона
Рамка вокруг поля
Изменение курсора мыши
Выравнивание текста
Настройка свойств текстовых полей LinkLabel
Неиспользованные ссылки
Использованные ссылки
Активные ссылки
Заблокированные ссылки
Добавление графических изображений
ПОЗИЦИОНИРОВАНИЕ ЭЛЕМЕНТОВ УПРАВЛЕНИЯ В ФОРМЕ
Привязка к сетке
Настройка взаимного расположения элементов формы
Выравнивание
Установка размеров
Установка промежутков между элементами
Центровка элементов в окне формы
Передний и задний план
ФОРМА И КЛАВИША ТАБУЛЯЦИИ
Свойство TabStop
Свойство TabIndex
ОБРАБОТКА СОБЫТИЙ
Обработка идентификатора и пароля
Закрытие формы
Ссылки на ресурсы Интернета
Изменение внешнего вида ссылки
Запуск браузера
Отправка почтового сообщения
Полный исходный текст приложения UserLogin
Пространства имен
Класс Form1
Поля класса Form1
Инициализация приложения
Инициализация текстовых полей
Инициализация полей ввода текста
Инициализация графических изображений
Инициализация кнопок
Инициализация полей LinkLabel
Инициализация формы
УДАЛЕНИЕ ОБРАБОТЧИКА СОБЫТИЙ
При необходимости можно отредактировать файл значка. Для этого нужно дважды
щелкнуть левой клавишей мыши его имя в окне Solution Explorer.
Заметим, что файл значка *.ico может содержать не одно, а сразу несколько
изображений, каждое из которых имеет свой размер и свою палитру (т.е. набор
цветов). В использованном нами файле SECUR04.ICO имеются два изображения, одно
из которых (большое) имеет размер 32х32 пиксела, а другое (маленькое) — размер
16х16 пикселов. Максимальное количество цветов обоих изображений равно 16.
Для того чтобы отредактировать нужное изображение, выберите из меню Image
главного окна Microsoft Visual Studio .NET строку Current Icon Image Types.
Чтобы значок приложения смотрелся лучше при разных размерах и экранных
разрешениях, возможно, следует добавить в файл пиктограммы еще несколько
изображений. Для этого, находясь в режиме редактирования значка, выберите из меню
Image строку New Icon Image Type. При этом на экране появится список типов
изображений, показанный на рис. 3-5.
Рис. 3-5. Добавление изображения нового типа
Выбор рамки
По умолчанию окно формы снабжается рамкой, с помощью которой пользователь может
изменять размеры окна. Однако такая рамка не всегда удобна. Обычно диалоговые
окна, вроде тех, что предназначены для идентификации пользователей, имеют
фиксированный размер. Настраивая соответствующим образом свойства формы, можно
выбрать необходимый в каждом конкретном случае тип рамки.
Отыщите в окне свойств формы свойство FormBorderStyle, задающее стиль рамки
(рис. 3-7).
Установив значение свойства ControlBox, равным False, можно вообще убрать все
кнопки из заголовка формы. Если же задать значение True свойствам ControlBox и
HelpButton, в заголовке окна появятся две кнопки, первая из которых предназначена
для получения подсказки, а вторая — для закрытия окна (рис. 3-10).
Если выбрать из этого меню строку Lefts, выделенные ранее элементы управления
будут выровнены по левой границе того элемента, который был выбран последним.
Заметим, что вместо меню можно использовать панель инструментов, показанную
на рис. 3-22. Строке Lefts в этой панели соответствует вторая кнопка слева. Самая
левая кнопка панели инструментов выравнивания обеспечивает привязку выбранного
элемента к сетке.
Средствами меню Align можно выровнять выделенные элементы по вертикали и
горизонтали. Их можно выровнять левой (строка Lefts), правой (Rights), верхней (Tops)
и нижней (Bottoms) границам. Можно выполнить центровку элементов по вертикали
(Centers) и горизонтали (Middles), а также привязать границы элементов к сетке (to
Grid).
Установка размеров
Строки меню Make the Same Size позволяют сделать одинаковыми размеры
выделенных элементов управления по вертикали и горизонтали (рис. 3-24).
Закрытие формы
Кнопка Отменить предназначена для закрытия окна формы. Эта операция выполняется
при помощи метода Close. Добавьте обработчик событий для кнопки Отменить, а затем
добавьте вызов метода Close в этот обработчик событий:
private void button2_Click(object sender, System.EventArgs e)
{
Close();
}
Теперь окно нашего приложения можно будет закрывать не только с помощью
кнопки, расположенной в заголовке окна, но и с помощью кнопки Отменить.
Ссылки на ресурсы Интернета
Как мы уже говорили, по своему внешнему виду элемент управления LinkLabel очень
похож на ссылку, размещенную на обыкновенной Web-странице. Используя такие
элементы управления, можно сделать формы приложений похожими по внешнему виду
на страницы Web-сайтов.
В нашем приложении UserLogin элемент управления LinkLabel применяется для
ссылки на Web-сайт службы восстановления данных http://www.datarecovery.ru, а
также для отправки электронного сообщения автору этой книги. Как Вы узнаете позже,
эти элементы управления могут быть использованы для отображения на экране новых
форм или любых других объектов.
Чтобы ссылки, сделанные с помощью элемента управления LinkLabel, заработали,
необходимо создать для них обработчики событий.
Первую из этих ссылок (с надписью www.datarecovery.ru) необходимо обеспечить
таким обработчиком событий:
private void linkLabel1_LinkClicked(object sender,
System.Windows.Forms.LinkLabelLinkClickedEventArgs e)
{
linkLabel1.Links[linkLabel1.Links.IndexOf(e.Link)].Visited = true;
System.Diagnostics.Process.Start(linkLabel1.Text);
}
Изменение внешнего вида ссылки
Первая строка этого обработчика событий обеспечивает изменение внешнего вида
ссылки после ее использования:
linkLabel1.Links[linkLabel1.Links.IndexOf(e.Link)].Visited = true;
Как она работает?
Обработчик события получает два параметра. Через параметр sender типа object
передается ссылка на объект, вызвавший событие. В нашем случае это будет элемент
управления типа LinkLabel с надписью www.datarecovery.ru.
Параметр e типа System.Windows.Forms.LinkLabelLinkClickedEventArgs
предназначен для передачи параметров от элемента управления, вызвавшего
появление события. В частности, через свойство e.Link передается ссылка, которую
щелкнул пользователь.
Элемент управления LinkLabel может содержать информацию о нескольких
ссылках. Полный список ссылок элемента linkLabel1 (с адресом Web-сайта) можно
получить при помощи свойства Links. Далее, метод IndexOf позволяет получить из этого
списка заданную ссылку, а именно, ссылку e.Link.
Теперь остается отметить эту ссылку как использованную, установив значение
свойства Visited, равное true.
Запуск браузера
Итак, внешний вид ссылки мы изменили. Теперь обработчик события должен
отобразить на экране компьютера содержимое главной страницы Web-сайта. Это можно
сделать при помощи метода System.Diagnostics.Process.Start:
System.Diagnostics.Process.Start(linkLabel1.Text);
Данный метод пытается запустить на локальном компьютере программу или
документ, путь к которому передается через единственный параметр. Мы передали
этому методу адрес URL нашего Web-сайта www.datarecovery.ru. В результате
выполнения метода будет запущен браузер, и в его окне появится загружена искомая
Web-страница (рис. 3-30).
namespace UserLogin
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.TextBox textBox2;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.PictureBox pictureBox1;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.LinkLabel linkLabel1;
private System.Windows.Forms.LinkLabel linkLabel2;
private System.Windows.Forms.Label label5;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after
// InitializeComponent call
//
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
//
// TODO: Add any constructor code after
// InitializeComponent call
//
}
Этот метод создает и инициализирует все элементы управления формы, а также
задает их размеры и расположение в окне.
При необходимости выполнить какую-либо дополнительную инициализацию Вы
можете добавить вызовы других методов, расположив их в теле конструктора после
вызова метода InitializeComponent.
На самом первом шаге инициализации, на базе класса
System.Resources.ResourceManager создается система управления ресурсами,
необходимая для работы с национальными языками и параметрами:
System.Resources.ResourceManager resources = new
System.Resources.ResourceManager(typeof(Form1));
В качестве параметра конструктору класса System.Resources.ResourceManager
передается тип создаваемого ресурса. В данном случае это тип формы Form1.
Далее метод InitializeComponent создает все необходимые элементы управления,
добавленные в форму на этапе проектирования:
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.textBox1 = new System.Windows.Forms.TextBox();
this.textBox2 = new System.Windows.Forms.TextBox();
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.pictureBox1 = new System.Windows.Forms.PictureBox();
this.label4 = new System.Windows.Forms.Label();
this.linkLabel1 = new System.Windows.Forms.LinkLabel();
this.linkLabel2 = new System.Windows.Forms.LinkLabel();
this.label5 = new System.Windows.Forms.Label();
Когда все элементы управления созданы, необходимо их проинициализировать и
разместить в окне формы. На время выполнения этой операции метод
InitializeComponent отключает на время механизм генерации сообщений, связанных с
размещением элементов в окне формы:
this.SuspendLayout();
Инициализация и размещение элементов управления выполняется путем
изменения значений соответствующих свойств.
Инициализация текстовых полей
Рассмотрим процесс инициализации текстовых полей на примере поля с надписью
«DataRecovery.Ru»:
//
// label1
//
this.label1.Font = new System.Drawing.Font("Haettenschweiler",
26.25F, System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((System.Byte)(204)));
this.label1.ForeColor =
System.Drawing.Color.FromArgb(((System.Byte)(67)),
((System.Byte)(128)), ((System.Byte)(165)));
this.label1.Location = new System.Drawing.Point(128, 8);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(216, 40);
this.label1.TabIndex = 0;
this.label1.Text = "DataRecovery.Ru";
Для отображения этой надписи мы использовали шрифт Haettenschweiler. При
этом мы создали этот шрифт как объект класса System.Drawing.Font, а затем присвоили
ссылку на этот объект свойству this.label1.Font. Тем самым был изменен шрифт,
использованный для отображения надписи.
Подробно о шрифтах мы расскажем в 10 главе, а сейчас только отметим, что
первый параметр конструктора класса System.Drawing.Font задает название шрифта,
второй — его размер, третий — стиль (обычный, наклонный, жирный и т.д.).
Четвертый параметр конструктора позволяет выбрать единицу измерения для
указания размера шрифта. В нашем случае это пункт (1/72 дюйма). И, наконец, пятый
параметр указывает номер страницы Unicode, который должен быть использован для
отображения текстовой строки. Значение 204 обозначает страницу с символами
кириллицы.
Чтобы задать цвет надписи, необходимо изменить свойство this.label1.ForeColor.
Дизайнер форм создал объект класса System.Drawing.Color.FromArgb, позволяющий
выбрать цвет как набор трех компонентов — красной, зеленой и голубой. Значения
компонентов цвета изменяются от 0 до 255.
Далее задается расположение надписи. С этой целью изменяется значение
свойства this.label1.Location. Дизайнер форм задал координаты в виде объекта класса
System.Drawing.Point, передав соответствующему конструктору через параметры
координаты надписи по горизонтальной и вертикальной оси.
Что же касается размеров надписи, то они задаются при помощи свойства
this.label1.Size. Дизайнер присвоил этому свойству ссылку на объект класса
System.Drawing.Size, задающий размер объекта по горизонтали и вертикали.
Свойство this.label1.Name задает идентификатор (имя) надписи. С помощью этого
идентификатора можно, например, изменять текста надписи или ее другие атрибуты.
Что же касается текста надписи, то он задается свойством this.label1.Text.
Хотя надпись и не получает фокус ввода, дизайнер форм присваивает ей
последовательный номер, определяющий порядок, в котором элементы управления
получают фокус ввода при использовании клавиши табуляции.
Как видите, инициализация и размещение простой текстовой надписи занимает
несколько строк программного текста. К счастью, дизайнер форм создает этот текст для
Вас автоматически.
Инициализация полей ввода текста
В процессе инициализации полей, предназначенных для ввода текста, определяется
значение пяти различных свойств:
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(104, 158);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(104, 20);
this.textBox1.TabIndex = 3;
this.textBox1.Text = "";
Свойства this.textBox1.Location и this.textBox1.Size задают, соответственно,
расположение и размеры поля. Мы рассказывали о них в предыдущем разделе.
Аналогично, дизайнер форм задал имя поля при помощи свойства this.textBox1.Name, и
порядковый номер для табуляции в поле this.textBox1.TabIndex.
Свойство this.textBox1.Text позволяет задать надпись, которая появится в поле
ввода сразу после отображения формы. Мы этой возможностью не пользуемся, и
поэтому в данное свойство записывается пустая строка.
Инициализация графических изображений
При размещении в форме графического изображения (такого, например, как логотип
компании), мы добавляли объект в ресурсы приложения, изменяя свойство Image.
Предварительно мы включали файл изображения в проект, добавляя его к ресурсам
приложения.
Вот как происходит инициализация элемента управления, содержащего наше
изображение:
//
// pictureBox1
//
this.pictureBox1.Image = ((System.Drawing.Bitmap)
(resources.GetObject("pictureBox1.Image")));
this.pictureBox1.Location = new System.Drawing.Point(40, 16);
this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(72, 88);
this.pictureBox1.TabIndex = 7;
this.pictureBox1.TabStop = false;
Здесь с помощью метода resources.GetObject мы извлекаем данные нашего
изображения из ресурсов приложения. Эти данные затем приводятся к типу
System.Drawing.Bitmap (растровые графические изображения), а затем полученная
ссылка используется для инициализации свойства this.pictureBox1.Image.
Подробнее о графических изображениях мы расскажем позже в 10 главе,
посвященной рисованию в окне форм.
Инициализация кнопок
Размеры и расположение кнопок, их имя, текст и порядок табуляции задается таким же
образом, что и для текстовых полей:
//
// button1
//
this.button1.BackColor =
System.Drawing.Color.FromArgb(((System.Byte)(224)),
((System.Byte)(224)), ((System.Byte)(224)));
this.button1.Location = new System.Drawing.Point(264, 160);
this.button1.Name = "button1";
this.button1.TabIndex = 5;
this.button1.Text = "Войти";
Дополнительно кнопке назначается обработчик событий:
this.button1.Click += new System.EventHandler(this.button1_Click);
Этот обработчик получит управление, когда пользователь щелкнет кнопку.
Исходный текст обработчиков для кнопок нашего приложения уже был описан ранее в
этой главе:
private void button1_Click(object sender, System.EventArgs e)
{
MessageBox.Show("User: " + textBox1.Text + "\n" + "Password: " +
textBox2.Text);
}
}
Чтобы удалить его, откройте окно дизайнера форм и щелкните форму один раз.
Далее в инструментальной панели, расположенной в верхней части окна свойств
формы Properties щелкните кнопку Events. На рис. 3-22 эта кнопка показана в нажатом
виде.
}
Этот обработчик позволяет отслеживать процесс ввода текста в поле
редактирования. Однако если эта возможность Вам не нужна, удалите исходный текст
метода.
Удалите также код, подключающий этот обработчик события к полю
редактирования:
this.textBox1.TextChanged +=
new System.EventHandler(this.textBox1_TextChanged);
Визуальное проектирование
приложений C#
А.В. Фролов, Г.В. Фролов
ГЛАВА 4. СОЗДАНИЕ РЕДАКТОРА ТЕКСТА
ПРИЛОЖЕНИЕ SIMPLENOTEPAD
ДОБАВЛЕНИЕ МЕНЮ
Переименование меню и строк меню
Подключение меню к форме
ВСТАВКА ПОЛЯ РЕДАКТИРОВАНИЯ
ОБРАБОТКА СОБЫТИЙ
РАБОТА С ФАЙЛАМИ ДОКУМЕНТОВ
Создание нового документа
Открытие существующего файла
Сохранение файла
ПЕЧАТЬ ДОКУМЕНТА
Добавление программных компонентов для печати
Редактирование меню File
Подключение пространств имен
Настройка параметров страницы документа
Предварительный просмотр документа перед печатью
Отображение окна печати документа
Обработка события PrintPage
Закрытие главного окна редактора текста
РЕАЛИЗАЦИЯ ФУНКЦИЙ МЕНЮ EDIT
РЕАЛИЗАЦИЯ ФУНКЦИЙ МЕНЮ FORMAT
Шрифт символов
Цвет символов
Стиль символов
Выравнивание параграфов
РЕАЛИЗАЦИЯ ФУНКЦИЙ МЕНЮ HELP
Добавление новой формы
Отображение формы
Редактирование класса HelpAboutForm
СОЗДАНИЕ ИНСТРУМЕНТАЛЬНОЙ ПАНЕЛИ
Добавление инструментальной панели в окно приложения
Подключение списка изображений
Наполнение списка изображений
Редактирование кнопок инструментальной панели
СТРОКА СОСТОЯНИЯ
Добавление строки состояния
Настройка свойств строки состояния
Привязка строки состояния к меню
С помощью строки Insert New Вы можете вставить новую строку меню между уже
существующих строк. Строка Insert Separator предназначена для вставки
разделительной линии между строками меню. И, наконец, при помощи строки Edit
Names можно отредактировать идентификаторы строк и меню.
Если же Вам нужно изменить введенные названия строк и меню, это можно
сделать по месту, выбрав нужную строку мышью.
Создайте меню File, чтобы оно было таким, как показано на рис. 4-4.
В меню Edit мы реализуем только самые важные функции, опустив пока поиск,
замену и некоторые другие стандартные функции приложения Microsoft Notepad.
Меню Format (рис. 4-6) состоит только из одной строки Font, с помощью которой
пользователь сможет изменить шрифт текста.
И, наконец, последнее меню нашего приложения, это меню Help (рис. 4-7).
Как только Вы это сделаете, рядом с каждой строкой меню появится имя,
созданное дизайнером форм по умолчанию (рис. 4-9). Это имена вида menuItem1,
menuItem2 и т.д.
Рис. 4-9. Просмотр имен меню и строк меню
Отредактируйте имена меню и строк меню, как это показано на рис. 4-10.
Эту процедуру нужно будет повторить для каждого создаваемого Вами меню и для
каждой строки меню. При этом меню верхнего уровня File, Edit, Format и Help должны
называться menuFile, menuEdit, menuFormat и menuHelp. Имена строк меню
формируются путем добавления к имени меню текста, отображаемого в строке меню.
Например, строка New меню File называется menuFileNew, а строка About меню Help —
menuHelpAbout.
Разумеется, Вы можете придумать и свою собственную систему именования меню
и строк меню, лишь бы она была понятна для Вас и других разработчиков приложения,
которые, возможно, будут изучать его исходные тексты.
Подключение меню к форме
Теперь, когда меню готово, его нужно подключить к форме. Для этого отредактируйте
свойство формы с именем Menu. Раскройте список возможных значений этого свойства
и выберите в этом списке строку mainMenu1.
После этого Вы можете оттранслировать приложение и запустить его, нажав
кнопку F5. Убедитесь, что меню отображается, и Вы можете выбирать его строки. Если
все нормально, можно переходить к следующему этапу проектирования нашего
редактора текста.
Вставка поля редактирования
Редактирование текста в нашем приложении будет выполнять компонент RichTextBox.
Этот компонент очень похож на текстовый редактор TextBox, с которым мы уже имели
дело в предыдущей главе.
Перетащите из панели Toolbox в форму приложения simpleNotepad компонент
RichTextBox.
Настройте свойства компонента, чтобы он занимал все окно приложения. Далее
отредактируйте свойство Dock. Это свойство задает расположение выбранного
компонента внутри содержащей его формы (рис. 4-11).
Рис. 4-11. Редактирование свойства Dock
Обработка событий
Обработчики событий от строк меню создаются таким же образом, что и обработчики
событий от кнопок. Чтобы создать обработчик события для строки меню, ее нужно
щелкнуть дважды левой клавишей мыши.
Создайте обработчики событий для всех строк меню File, Edit, Format и Help,
дважды щелкнув каждую строку этих меню. Все обработчики событий создаются с
пустым телом:
private void menuFileNew_Click(object sender, System.EventArgs e)
{
}
Чтобы наполнить меню нужной нам функциональностью, нам придется написать
вручную код для этих обработчиков событий.
Работа с файлами документов
Прежде всего, мы создадим обработчики событий, необходимые для выполнения таких
функций, как создание нового документа, открытие и сохранение документов. Функции
печати документов, форматирования текста и некоторые другие мы реализуем позже в
этой главе.
Создание нового документа
Если пользователь выберет строку New из меню File, наш редактор текста
SimpleNotepad должен создать новый текстовый документ. В простейшем случае для
этого достаточно просто очистить содержимое окна редактирования, вызвав метод
Clear:
private void menuFileNew_Click(object sender, System.EventArgs e)
{
textBox1.Clear();
}
Метод Clear вызывается для объекта textBox1, т.е. для нашего окна
редактирования, занимающего все пространство формы.
Открытие существующего файла
Для того чтобы реализовать в нашем редакторе текста функцию открытия файла, нам
потребуется компонент OpenFileDialog. Перетащите значок этого компонента из панели
Toolbox в рабочее окно дизайнера формы.
Значок компонента OpenFileDialog будет показан в нижней части формы (рис. 4-
13), причем этот компонент получит идентификатор openFileDialog1. Этот
идентификатор будет использован нами в программном коде, открывающем исходный
файл.
/// <summary>
/// Номер текущей распечатываемой страницы документа
/// </summary>
private uint m_PrintPageNumber;
Первое из этих полей предназначено для хранения ссылки на поток StringReader,
с помощью которого будет осуществляться чтение документа в процессе его печати, а
также предварительного просмотра перед печатью. Второе поле будет хранить номер
текущей распечатываемой страницы документа.
Добавьте в класс SimpleNotepadForm метод MenuFilePrintPreview,
предназначенный для вывода на экран окна предварительного просмотра документа,
показанного на рис. 4-20:
/// <summary>
/// Предварительный просмотр перед печатью документа
/// </summary>
private void MenuFilePrintPreview()
{
m_PrintPageNumber = 1;
printDocument1.DefaultPageSettings.Margins = margins;
printPreviewDialog1.ShowDialog();
m_myReader.Close() ;
}
Кроме того, добавьте вызов метода MenuFilePrintPreview в тело обработчика
события строки Print Preview меню File:
private void menuFilePrintPreview_Click(object sender,
System.EventArgs e)
{
MenuFilePrintPreview();
}
Как работает метод MenuFilePrintPreview?
Прежде всего, этот метод записывает в поле m_PrintPageNumber значение 1, т.к.
просмотр начнется с первой страницы документа:
m_PrintPageNumber = 1;
Далее метод читает текущее содержимое окна редактирования в поток
m_myReader класса StringReader:
string strText = this.richTextBox1.Text;
m_myReader = new StringReader(strText);
Затем метод MenuFilePrintPreview отображает окно предварительного просмотра,
задавая для него размеры полей отступов на странице:
Margins margins = new Margins(100,50,50,50);
printDocument1.DefaultPageSettings.Margins = margins;
printPreviewDialog1.ShowDialog();
Конструктор Margins получает через свои параметры величину левого, правого,
верхнего и нижнего отступа в сотых долях дюйма.
Отобразив панель предварительного просмотра, метод MenuFilePrintPreview
закрывает поток m_myReader:
m_myReader.Close();
Если на этом этапе Вы попытаетесь запустить наше приложение, то окно
предварительного просмотра появится на экране пустым. Причина этого заключается в
том, что пока мы определили не все необходимые обработчики событий.
Отображение окна печати документа
Для отображения стандартного диалогового окна печати документов (рис. 4-19)
добавьте в класс SimpleNotepadForm метод MenuFilePrint:
/// <summary>
/// Печать документа
/// </summary>
private void MenuFilePrint()
{
m_PrintPageNumber = 1;
if (printDialog1.ShowDialog() == DialogResult.OK)
{
this.printDocument1.Print();
}
m_myReader.Close() ;
}
Вызов этого метода должен осуществляться в обработчике событий строки Print
меню File:
private void menuFilePrint_Click(object sender, System.EventArgs e)
{
MenuFilePrint();
}
Расскажем о том, как работает метод MenuFilePrint.
Печать нашего документа будет начинаться с первой страницы, поэтому в поле
m_PrintPageNumber мы записываем значение 1:
m_PrintPageNumber = 1;
Далее мы читаем текущее содержимое окна редактирования текста в поток
m_myReader класса StringReader:
string strText = this.richTextBox1.Text;
m_myReader = new StringReader(strText);
Эта операция выполняется точно таким же способом, что и в только что
описанном методе MenuFilePrintPreview, предназначенном для отображения окна
предварительного просмотра документа.
Далее мы задаем границы отступов на распечатываемой странице и отображаем
диалоговое окно печати документа. Если пользователь щелкает в этом окне кнопку OK,
документ printDocument1 отправляется на печать методом Print:
Margins margins = new Margins(100,50,50,50);
printDocument1.DefaultPageSettings.Margins = margins;
if (printDialog1.ShowDialog() == DialogResult.OK)
{
this.printDocument1.Print();
}
Далее ненужный более поток m_myReader закрывается методом Close:
m_myReader.Close() ;
Заметим, что на данном этапе приложение еще не в состоянии распечатать
документ (точно так же, как оно пока не в состоянии заполнить окно предварительного
просмотра перед печатью). Причина этого в том, что приложение пока еще не знает,
каким именно образом нужно печатать наш документ.
Обработка события PrintPage
Чтобы в нашем приложении заработали функции предварительного просмотра
документа перед печатью и функция печати, необходимо создать обработчик события
PrintPageEventHandler. Для этого нужно дважды щелкнуть левой клавишей мыши
значок компонента printDocument1 (рис. 4-18). После этого дизайнер форм создаст
пустое тело этого обработчика событий.
Далее Вам придется набрать вручную довольно объемистый исходный текст
обработчика, представленный ниже:
/// <summary>
/// Обработка события PrintPage
/// </summary>
private void PrintPageEventHandler(object sender,
System.Drawing.Printing.PrintPageEventArgs e)
{
int lineCount = 0; // счетчик строк
float linesPerPage = 0; // количество строк на одной странице
float yLinePosition = 0; // текущая позиция при печати по
// вертикальной оси
string currentLine = null; // текст текущей строки
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
Теперь, когда пользователь попытается закрыть программу редактора с помощью
строки Exit меню File или с помощью соответствующей кнопки строки заголовка окна,
не сохранив сделанные изменения, на экране появится стандартное диалоговое окно,
предлагающее ему сохранить документ.
Реализация функций меню Edit
Реализация функций меню Edit, стандартных для всех редакторов текста и многих
других приложений, в нашем случае получается очень простой, т.к. элемент
управления RichTextBox имеет в своем составе все необходимые методы.
Все, что Вам нужно сделать, это вызвать эти методы в соответствующих
обработчиках событий. Например, для реализации функции копирования выделенного
фрагмента текста в универсальный буфер обмена Clipboard нужно добавить в тело
обработчика события строки Copy меню Edit вызов метода Copy.
Добавьте в меню Edit нашего редактора текста строку Redo. Затем подготовьте
обработчики событий от всех строк меню Edit следующим образом:
private void menuEditUndo_Click(object sender, System.EventArgs e)
{
richTextBox1.Undo();
}
Для того чтобы добавить меню второго уровня, Вам нужно вначале ввести с
клавиатуры его название (как Вы это делаете для строк меню первого уровня). Затем
необходимо ввести названия строк меню второго уровня в поле Type Here,
расположенном справа от только что введенной строки первого уровня.
Не забудьте также изменить имена строк меню таким образом, чтобы с ними было
легче работать в программе. Мы, например, назвали строку меню Left меню второго
уровня Paragraph Alignment как menuFormatParagraphAlignmentLeft. Остальные строки
меню второго уровня получили аналогичные названия.
Далее создайте обработчики событий для всех строк меню первого и второго
уровня, щелкнув их дважды левой клавишей мыши.
Шрифт символов
Чтобы пользователь мог выбирать шрифт фрагмента текста, выделенного в окне
редактирования программы SimpleNotepad, перетащите мышью из панели Toolbox в
окно дизайнера формы компонент FontDialog. Этот компонент отображает на экране
стандартное диалоговое окно выбора шрифта Font, показанное на рис. 4-26.
if (richTextBox1.SelectionFont.Bold == true)
{
newFontStyle = FontStyle.Regular;
}
else
{
newFontStyle = FontStyle.Bold;
}
CheckMenuFontCharacterStyle();
}
}
Получив управление, метод SetBold прежде всего, определяет шрифт фрагмента
текста, выделенного пользователем, анализируя свойство richTextBox1.SelectionFont.
Если шрифт определить не удалось, и это свойство содержит значение null, наша
программа не делает никаких изменений.
В противном случае программа сохраняет текущий шрифт в переменной
currentFont класса System.Drawing.Font.
Далее метод SetBold проверяет, был ли выделен фрагмент текста жирным
шрифтом, анализируя свойство richTextBox1.SelectionFont.Bold. Если это свойство
содержит значение true, то метод SetBold снимает выделение, если нет, то
устанавливает его.
Для снятия выделения программа записывает в переменную newFontStyle
значение FontStyle.Regular, а для установки — значение FontStyle.Bold.
В дальнейшем содержимое переменной newFontStyle будет использована методом
SetBold для изменения оформления выделенного фрагмента текста. Это изменение
выполняется следующим образом:
richTextBox1.SelectionFont = new Font(
currentFont.FontFamily, currentFont.Size, newFontStyle);
Здесь программа вначале создает новый шрифт как объект класса Font, передавая
конструктору через параметры семейство currentFont.FontFamily текущего шрифта
(установленного до выполнения операции), размер текущего шрифта currentFont.Size, а
также новый стиль шрифта из переменной newFontStyle.
Далее этот шрифт записывается в свойство richTextBox1.SelectionFont, что и
приводит к изменению стиля символов выделенного фрагмента текста.
Обратите также внимание, что при выделении фрагмента текста жирным шрифтом
мы одновременно отмечаем «галочкой» строку меню Bold. С этой целью мы вызываем
созданный нами метод CheckMenuFontCharacterStyle:
/// <summary>
/// Установка отметки строк меню Font->CharacterStyle
/// </summary>
private void CheckMenuFontCharacterStyle()
{
if(richTextBox1.SelectionFont.Bold == true)
{
menuFormatFontCharacterStyleBold.Checked = true;
}
else
{
menuFormatFontCharacterStyleBold.Checked = false;
}
if(richTextBox1.SelectionFont.Italic == true)
{
menuFormatFontCharacterStyleItalic.Checked = true;
}
else
{
menuFormatFontCharacterStyleItalic.Checked = false;
}
if(richTextBox1.SelectionFont.Underline == true)
{
menuFormatFontCharacterStyleUnderline.Checked = true;
}
else
{
menuFormatFontCharacterStyleUnderline.Checked = false;
}
if(richTextBox1.SelectionFont.Strikeout == true)
{
menuFormatFontCharacterStyleStrikeout.Checked = true;
}
else
{
menuFormatFontCharacterStyleStrikeout.Checked = false;
}
}
Метод CheckMenuFontCharacterStyle по очереди проверяет стилевое оформление
выделенных фрагментов, отмечая «галочками» соответствующие строки меню
CharacterStyle или снимая со строк этого меню отметки. Вот как, например, происходит
обработка отметки строки Bold меню CharacterStyle:
if(richTextBox1.SelectionFont.Bold == true)
{
menuFormatFontCharacterStyleBold.Checked = true;
}
else
{
menuFormatFontCharacterStyleBold.Checked = false;
}
Если фрагмент текста, выделенный пользователем, оформлен жирным шрифтом,
свойство richTextBox1.SelectionFont.Bold содержит значение true. В этом случае наша
программа отмечает «галочкой» строку Bold меню CharacterStyle, записывая значение
true в свойство Checked данной строки:
menuFormatFontCharacterStyleBold.Checked = true;
Когда оформление жирным шрифтом снимается, программа убирает галочку,
записывая в свойство Checked значение false:
menuFormatFontCharacterStyleBold.Checked = false;
Исходные тексты методов SetItalic, SetUnderline и SetStrikeout приведены ниже:
/// <summary>
/// Установка стиля символов Italic
/// </summary>
private void SetItalic()
{
if (richTextBox1.SelectionFont != null)
{
System.Drawing.Font currentFont = richTextBox1.SelectionFont;
System.Drawing.FontStyle newFontStyle;
CheckMenuFontCharacterStyle();
if (richTextBox1.SelectionFont.Italic == true)
{
newFontStyle = FontStyle.Regular;
}
else
{
newFontStyle = FontStyle.Italic;
}
CheckMenuFontCharacterStyle();
}
}
/// <summary>
/// Установка стиля символов Underline
/// </summary>
private void SetUnderline()
{
if (richTextBox1.SelectionFont != null)
{
System.Drawing.Font currentFont = richTextBox1.SelectionFont;
System.Drawing.FontStyle newFontStyle;
CheckMenuFontCharacterStyle();
if (richTextBox1.SelectionFont.Underline == true)
{
newFontStyle = FontStyle.Regular;
}
else
{
newFontStyle = FontStyle.Underline;
}
CheckMenuFontCharacterStyle();
}
}
/// <summary>
/// Установка стиля символов Strikeout
/// </summary>
private void SetStrikeout()
{
if (richTextBox1.SelectionFont != null)
{
System.Drawing.Font currentFont = richTextBox1.SelectionFont;
System.Drawing.FontStyle newFontStyle;
if (richTextBox1.SelectionFont.Strikeout == true)
{
newFontStyle = FontStyle.Regular;
}
else
{
newFontStyle = FontStyle.Strikeout;
}
CheckMenuFontCharacterStyle();
}
}
Эти методы мы оставляем Вам для самостоятельного изучения.
Выравнивание параграфов
Оформляя документы, пользователи часто изменяют выравнивание отдельных
параграфов. Применяется выравнивание по левой и правой границам документа, а
также центровка.
После небольшой доработки наш редактор текста тоже сможет выравнивать
параграфы текста, а также графические изображения, вставленные в документ через
универсальный буфер обмена Clipboard (рис. 4-29).
Рис. 4-29. Выравнивание параграфов текста
На экране появится окно Add New Item, показанное на рис. 4-31. В правой части
этого окна, содержащей значки шаблонов, будет выделен шаблон Windows Form.
Чтобы форма вела себя как стандартное диалоговое окно ОС Microsoft Windows,
необходимо настроить некоторые ее свойства. Проведите настройку в соответствии с
табл. 4-1.
Таблица 4-1. Настройка свойств формы About SimpleNotepad
Свойство Значение
FormBorderStyle FixedDialog
StartPosition CenterParent
ControlBox false
MinimizeBox false
MaximizeBox false
ShowInTaskbar false
Text About SimpleNotepad
Сделаем некоторые замечания к этой таблице.
Свойство FormBorderStyle определяет вид рамки, расположенной вокруг окна
формы. Если задать здесь значение FixedDialog, окно формы будет снабжено такой же
рамкой, как и все стандартные диалоговые окна Microsoft Windows.
Свойство StartPosition позволяет указать расположение формы в момент ее
отображения. Мы указали здесь значение CenterParent, в результате чего окно About
SimpleNotepad появится в центре главного окна нашего приложения. Вы также можете
выполнить центровку окна относительно экрана (значение Center Screen), позволить
ОС Microsoft Windows самой расположить окно на экране (значение Windows Default
Locations). Существуют и другие способы определения начального расположения окна
формы. Вы можете выбрать соответствующие значения из меню при редактировании
свойства StartPosition.
Для того чтобы убрать кнопки и управляющее меню из заголовка окна диалоговой
формы, мы установили значения свойств MinimizeBox, MaximizeBox и
ControlBox равными false.
Кроме того, мы «выключили» свойство ShowInTaskbar, записав в него значение
false, для того чтобы окно диалоговой формы не было представлено своим значком в
панели задач ОС Microsoft Windows.
И, наконец, свойство Text задает текст заголовка нашей диалоговой формы.
Отображение формы
Чтобы окно формы About SimpleNotepad появилось на экране при выборе
пользователем строки About меню Help, мы добавили две строки в исходный текст
обработчика событий menuHelpAbout_Click:
private void menuHelpAbout_Click(object sender, System.EventArgs e)
{
Form dlgAbout = new HelpAboutForm();
dlgAbout.ShowDialog();
}
Первая строка создает новую форму dlgAbout как объект класса HelpAboutForm, а
вторая отображает форму на экране, вызывая для этого метод dlgAbout.ShowDialog. Мы
показали форму на рис. 4-33 (на этом рисунке главное окно приложения снабжено
инструментальной панелью, которую мы скоро создадим).
Рис. 4-33. Отображение диалоговой формы About SimpleNotepad
Вначале с помощью кнопки Add добавьте в панель 10 кнопок. Восемь из них будут
играть роль настоящих кнопок, а две будут использованы для разделения трех групп
кнопок.
После добавление кнопки выделите кнопку с идентификатором toolBarButton1
(как это показано на рис. 4-38), и в окне toolBarButton1 Properties отредактируйте
свойство ImageIndex. Редактирование будет заключаться в выборе одного из
изображений, сохраненных Вами ранее в списке imageList1 (рис. 4-39).
Строка состояния
Последнее добавление, которое мы сделаем к программе SimpleNotepad, — это
добавление строки состояния. Строка состояния представляет собой узкое окно,
располагающееся внизу окна приложения и предназначенная для отображения
текущего состояния программы.
Добавление строки состояния
Чтобы добавить строку состояния, перетащите мышью из панели Toolbox в форму
приложения значок элемента управления StatusBar.
Окно строки состояния окажется над окном редактора текста. Чтобы расположить
окна правильно, щелкните правой кнопкой мыши окно редактора текста, а затем
выберите из контекстного меню строку Bring to Front.
В результате этих действий главное окно нашего приложения примет вид,
показанный на рис. 4-44.
Рис. 4-44. В окно приложения добавлена строка состояния
Добавьте в строку состояния с помощью кнопки Add две панели. Первая панель с
идентификатором statusBarPanel1 будет использована для объяснения назначения
строк меню во время их выбора, а вторая — для индикации факта изменения
содержимого документа, загруженного в окно редактирования.
Первая панель (с идентификатором statusBarPanel1) должна изменять свой размер
в зависимости от ширины окна. Поэтому для нее установите значение свойства AutoSize
равным Spring.
Кроме того, присвойте свойству Text панелей statusBarPanel1 и statusBarPanel2
пустую строку. Это нужно сделать потому, что содержимое панелей будет определяться
программой.
Привязка строки состояния к меню
Для того чтобы отслеживать выбор строк меню, наша программа должна предусмотреть
специальный обработчик события Select. Это событие создается строками меню, когда
пользователь выбирает их при помощи мыши или клавиатуры.
Прежде всего, создадим обработчик событий. Для этого выделите мышью меню
File в окне дизайнера формы, а затем щелкните кнопку Events (с изображением
высоковольтного разряда) в окне Properties (рис. 4-46).
Здесь находится список событий и обработчиков для них. Назначьте для события
Select обработчик MenuSelect, щелкнув соответствующую строку списка дважды левой
клавишей мыши или введя его имя с клавиатуры, как это показано на рис. 4-45. После
того как Вы нажмете клавишу Enter, будет создано пустое тело обработчика события
MenuSelect:
private void MenuSelect(object sender, System.EventArgs e)
{
}
На данном этапе наш обработчик будет вызываться только тогда, когда
пользователь раскроет меню File. Теперь нам нужно подключить этот обработчик ко
всем строкам главного меню приложения с целью обработки события Select.
Для этого Вам нужно по очереди выделить все строки меню нашего приложения, а
затем для каждой строки выбрать в только что упомянутом окне Properties из списка
события Select обработчик события MenuSelect (рис. 4-47).
Рис. 4-47. Выбираем из меню обработчик события MunuSelect
Для того чтобы очистить правую панель строки состояния после создания нового
документа, загрузки и сохранения документа, модифицируйте обработчики сообщений
menuFileNew_Click, а также методы MenuFileOpen и MenuFileSaveAs, как это показано
ниже:
private void menuFileNew_Click(object sender, System.EventArgs e)
{
if(m_DocumentChanged)
MenuFileSaveAs();
richTextBox1.Clear();
statusBarPanel2.Text = "";
m_DocumentChanged = false;
}
…
Визуальное проектирование
приложений C#
А.В. Фролов, Г.В. Фролов
ГЛАВА 5. ДИАЛОГОВЫЕ ОКНА
ОКНО СООБЩЕНИЙ MESSAGEBOX
Перегруженные методы MessageBox.Show
Параметры метода MessageBox.Show
Возвращаемое значение
Доработка меню File приложения SimpleNotepad
СОЗДАНИЕ МОДАЛЬНЫХ ОКОН
Замена окну MassageBox
Создание новой формы
Создание обработчиков событий
Отображение формы
Альтернативный способ передачи информации из формы
Диалоговое окно регистрации программы
Создание формы регистрации
Поля ввода текстовой информации
Флажки с зависимой фиксацией
Флажки с независимой фиксацией
Список ComboBox
Добавление календаря
Кнопки для закрытия формы
Настройка свойств формы
Программирование формы регистрации
Обработка событий
Передача регистрационной информации
Добавление свойств
Свойства для текстовых полей
Свойства для флажков с зависимой фиксацией
Свойства для флажков с независимой фиксацией
Свойство для календаря
Отображение формы регистрации
НЕМОДАЛЬНЫЕ ДИАЛОГОВЫЕ ОКНА
Приложение PropertyApp
Форма главного окна приложения PropertyApp
Немодальная форма настройки свойств
ПРОВЕРКА ДАННЫХ ФОРМЫ
Подключение компонента ErrorProvider
Создание обработчика события Validating
Настройка свойств компонента ErrorProvider
Error
Exclamation
Hand
Information
Stop
Warning
Как видите, для диалогового окна MessageBox можно выбирать один из четырех
значков. Если же эти значки Вам не подходят, придется отказаться от использования
диалогового окна MessageBox и создавать свое собственное модальное диалоговое
окно.
Параметр defButton метода MessageBox.Show предназначен для выбора кнопки,
которая получит фокус сразу после отображения диалогового окна. Этот параметр
должен иметь одно из значений перечисления MessageBoxDefaultButton (табл. 5-3).
Таблица 5-3. Перечисление MessageBoxDefaultButton
Константа Номер кнопки, получающей фокус ввода по
умолчанию
Button1 1
Button2 2
Button3 3
switch(result)
{
case DialogResult.Yes:
{
MenuFileSaveAs();
break;
}
case DialogResult.Cancel:
{
return;
}
}
}
richTextBox1.Clear();
statusBarPanel2.Text = "";
m_DocumentChanged = false;
}
Напомним, что при изменении содержимого окна редактирования в переменную
m_DocumentChanged записывается значение true. В этом случае метод MenuFileNew
вызовет метод MessageBox.Show для отображения на экране окна Сохранение
документа, показанного на рис. 5-2.
Далее метод MenuFileNew проанализирует значение, возвращенное методом
MessageBox.Show. Это значение зависит от того, какую кнопку щелкнул пользователь.
Соответственно, метод MenuFileNew предпримет различные действия.
Если пользователь щелкнул кнопку Yes, метод MessageBox.Show возвращает
значение DialogResult.Yes. В этом случае метод MenuFileNew приглашает пользователя
сохранить старый документ, вызывая для этого метод MenuFileSaveAs.
В том случае, когда пользователь отказался от сохранения старого документа,
метод MenuFileNew очищает окно редактора richTextBox1 и убирает строку сообщения
об изменении документа из правой панели строки состояния нашего приложения.
И, наконец, если пользователь щелкнул кнопку Cancel, метод MenuFileNew
возвращает управление, не предпринимая никаких действий.
Аналогичным образом доработаны методы MenuFileOpen и menuFileExit_Click:
/// <summary>
/// Открытие существующего файла
/// </summary>
private void MenuFileOpen()
{
if(m_DocumentChanged)
{
DialogResult result = MessageBox.Show(
"Документ изменен. \nСохранить изменения?",
"Сохранение документа", MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1);
switch(result)
{
case DialogResult.Yes:
{
MenuFileSaveAs();
break;
}
case DialogResult.Cancel:
{
return;
}
}
}
if(openFileDialog1.ShowDialog() ==
System.Windows.Forms.DialogResult.OK &&
openFileDialog1.FileName.Length > 0)
{
try
{
richTextBox1.LoadFile(openFileDialog1.FileName,
RichTextBoxStreamType.RichText);
}
catch (System.ArgumentException ex)
{
richTextBox1.LoadFile(openFileDialog1.FileName,
RichTextBoxStreamType.PlainText);
}
this.Text = "Файл [" + openFileDialog1.FileName + "]";
statusBarPanel2.Text = "";
m_DocumentChanged = false;
}
}
switch(result)
{
case DialogResult.Yes:
{
MenuFileSaveAs();
break;
}
case DialogResult.Cancel:
{
return;
}
}
}
this.Close();
}
Мы оставляем Вам эти методы для самостоятельного изучения.
Создание модальных окон
Как мы уже говорили, помимо только что рассмотренного модального диалогового окна
класса MessageBox программисту доступны стандартные модальные диалоговые окна,
предназначенные для выполнения основных операций. Такие окна мы использовали в
предыдущей главе для приложения SimpleNotepad.
В этом разделе мы расскажем подробнее о создании собственных модальных окон
и приведем примеры приложений, получающих от пользователей данные через такие
окна.
Замена окну MassageBox
Хотя в приложении SimpleNotepad мы уже создавали собственное модальное окно About
SimpleNotepad для отображения информации о программе (рис. 4-33), это окно не
возвращало никаких значений. Теперь мы создадим диалоговое окно, заменяющее окно
MessageBox и предназначенное для выдачи запроса пользователю о необходимости
сохранения документа в приложении SimpleNotepad. Т.е. мы заменим стандартное окно
Сохранение документа, показанное на рис. 5-2, своим собственным модальным
диалоговым окном.
Создание новой формы
Прежде всего, добавьте в проект новую форму. Для этого щелкните правой клавишей
мыши строку названия проекта SimpleNotepad в окне Solution Explorer. Затем выберите
из меню Add строку Add Windows Form (рис. 4-30). На экране появится окно Add New
Item, показанное на рис. 4-31. В правой части этого окна, содержащей значки
шаблонов, будет выделен шаблон Windows Form.
Введите в поле Name строку SaveDocumentNeededForm.cs, а затем щелкните
кнопку Open. В результате к проекту будет добавлена новая форма, а также новый
класс SaveDocumentNeededForm.
Отредактируйте эту форму, добавив в нее графическое изображение, текст с
предупреждением о необходимости сохранения документа, а также кнопки Да, Нет и
Отменить (рис. 5-3).
Чтобы форма вела себя как стандартное модальное диалоговое окно ОС Microsoft
Windows, необходимо настроить некоторые ее свойства. Проведите настройку в
соответствии с табл. 5-6.
Таблица 5-6. Настройка свойств формы SaveDocumentNeededForm
Свойство Значение
FormBorderStyle FixedDialog
StartPosition CenterParent
ControlBox false
MinimizeBox false
MaximizeBox false
ShowInTaskbar false
Text About SimpleNotepad
CancelButton buttonCancel
AcceptButton buttonYes
Почти все эти настройки мы уже использовали для диалогового окна About
SimpleNotepad (рис. 4-32). Это окно мы создавали в предыдущей главе для
отображения информации о приложении SimpleNotepad.
Здесь добавилась только две настройки — мы присвоили свойству CancelButton
идентификатор кнопки Отменить, а свойтсву AcceptButton — идентификатор кнопки Да.
В результате наше диалоговое окно можно будет закрыть при помощи клавиши
Esc. Кроме того, нажатие клавиши Enter на клавиатуре будет эквивалентно щелчку
мышью кнопки Да. Настройте эти свойства подобным образом и для упомянутого выше
диалогового окна About SimpleNotepad.
Заметим, что свойству CancelButton можно присвоить не только идентификатор
кнопки, но и также идентификатор любого объекта, реализующего интерфейс
IButtonControl, в частности, элемент управления LinkLabel.
При добавлении в форму кнопок, а также других элементов управления и
компонентов, дизайнер форм автоматически создает для них идентификаторы.
В предыдущей главе для удобства работы с исходным текстом приложения мы
меняли идентификаторы строк меню. Сейчас мы поменяем идентификаторы кнопок.
Для этого отредактируйте свойство кнопок Name. Установите значение этого
свойства для кнопки Да, равным buttonYes, а для кнопок Нет и Отменить — равным
buttonNo и buttonCancel, соответственно.
Создание обработчиков событий
Далее создайте для всех кнопок обработчики событий, а затем измените их следующим
образом:
private void buttonYes_Click(object sender, System.EventArgs e)
{
DialogResult = DialogResult.Yes;
}
switch(result)
{
case DialogResult.Yes:
{
MenuFileSaveAs();
break;
}
case DialogResult.Cancel:
{
return;
}
}
}
richTextBox1.Clear();
statusBarPanel2.Text = "";
m_DocumentChanged = false;
}
По сравнению с предыдущим вариантом этого метода мы заменили вызов метода
MessageBox.Show следующими строками, отображающими на экране диалоговое окно
Сохранение документа, показанное на рис. 5-3:
SaveDocumentNeededForm dialog = new SaveDocumentNeededForm();
DialogResult result = dialog.ShowDialog();
Обратите внимание, что значение, возвращенное методом dialog.ShowDialog,
сохраняется в переменной result и используется в дальнейшем для определения
кнопки, которую щелкнул пользователь в окне Сохранение документа.
Метод dialog.ShowDialog вернет то самое значение, которое обработчик событий
нажатой кнопки записал в свойство DialogResult формы SaveDocumentNeededForm.
Аналогичные изменения Вам нужно внести в исходный текст методов
MenuFileOpen и menuFileExit_Click.
Альтернативный способ передачи информации из формы
Мы только что рассмотрели способ передачи в вызывающую программу информации о
том, какой кнопкой пользователь завершил работу диалогового окна, основанный на
создании обработчиков событий. Однако существует и еще один, более простой способ.
Удалите из исходного текста формы SaveDocumentNeededForm обработчики
событий от кнопок, добавленные на предыдущем этапе. О том, как это правильно
делать, мы рассказали в разделе «Удаление обработчика событий» 3 главы.
Далее отредактируйте свойство DialogResult кнопок Да, Нет и Отменить (рис. 5-4).
Рис. 5-4. Настройка свойства DialogResult для кнопки Да
Кроме этого, во всех этих полях сотрите тестовые строки, задающие значение
свойства Text и снабдите их поясняющими надписями, как это показано на рис. 5-5.
Так как поле Оставьте Ваш комментарий предназначено для ввода многострочного
комментария, установите значение свойства Multiline равным true. После этого Вы
сможете отрегулировать не только ширину, но и высоту этого поля.
Далее, установите значение свойства Enabled поля ввода названия любимой ОС
textBoxFavoriteOS, равным false. В этом случае сразу после отображения формы данное
поле будет заблокировано. Разблокировка поля будет осуществляться обработчиком
событий от флажка Другая (укажите, какая), т.к. это поле нужно только в том случае,
если пользователь отметил этот флажок.
Флажки с зависимой фиксацией
Форма регистрации содержит две группы флажков с независимой фиксацией. Группа
флажков Пол позволяет указать пол владельца копии программы, а группа флажков
Любимая ОС — название любимой операционной системы владельца.
Заметим, что вторая группа флажков снабжена дополнительным полем ввода
текстовой информации. Если пользователю не нравится ни Microsoft Windows, ни Linux,
он может отметить флажок Другая (укажите, какая), а затем ввести название своей
любимой ОС в этом дополнительном текстовом поле.
Как видно из названия, из флажков с зависимой фиксацией, принадлежащих
одной группе, в отмеченном состоянии может находиться только один флажок. Таким
образом, нельзя указать одновременно и мужской, и женский пол, а из любимых ОС
можно выбрать только одну.
Для объединения флажков Муж. и Жен. в группу Пол перетащите из
инструментальной панели Toolbox элемент управления GroupBox. Запишите строку
«Пол» в свойство Text этого элемента управления.
Затем перетащите внутрь окна элемента управления GroupBox два флажка типа
RadioButton. Это и есть флажки с зависимой фиксацией. Свое название они получили
по аналогии с кнопками радиоприемника, которые можно нажимать только по одной.
Измените надпись около флажков в соответствии с рис. 5-5, отредактировав
свойства флажков Text.
Аналогичным образом создайте группу флажков Любимая ОС, добавив в нее три
флажка MS Windows, Linux и Другая (укажите, какая).
Добавив все флажки и расположив их внутри соответствующих окон GroupBox,
проверьте их идентификаторы, хранящиеся в свойстве Name, на соответствие табл. 5-
8.
Таблица 5-8. Идентификаторы флажков с зависимой фиксацией
Флажок Идентификатор
Муж. radioButton1
Жен. radioButton2
MS Windows radioButton3
Linux radioButton4
Другая (укажите, какая) radioButton5
Флажки с независимой фиксацией
Флажки с независимой фиксацией не нужно объединять в группы, т.к. они работают
независимо друг от друга. Вам нужно добавить в форму регистрации два таких флажка
класса CheckBox, перетащив их мышью из инструментальной панели Toolbox.
Флажок Подписка на новости должен иметь идентификатор checkBoxSendNews, а
флажок Подписка на электронную газету — идентификатор checkBoxSendLetter. Таким
образом, для этих флажков нужно настроить только свойства Name (идентификатор) и
Text (текст, расположенный около флажка).
Список ComboBox
Для выбора одного значения из нескольких возможных удобен элемент управления
ComboBox. Мы применили его для выбора квалификации пользователя из списка
Квалификация.
Перетащив элемент управления ComboBox в окно нашей формы, снабдите его
надписью Квалификация, как это показано на рис. 5-5.
Далее необходимо заполнить список текстовыми строками, отредактировав
свойство Items. Для этого будет запущен редактор набора строк String Collection Editor
(рис. 5-6).
if(rb.Checked)
textBoxFavoriteOS.Enabled = true;
else
textBoxFavoriteOS.Enabled = false;
}
Получив управление, этот обработчик событий сохраняет ссылку на флажок,
создавший событие, в переменной rb. Далее программа проверяет значение,
хранящееся в свойстве rb.Checked.
Если это значение равно true, флажок отмечен. В этом случае обработчик событий
снимает блокировку с поля textBoxFavoriteOS, записывая в свойство
textBoxFavoriteOS.Enabled значение true.
В том случае, когда значение свойства rb.Checked равно false, событие было
вызвано тем, что пользователь снял отметку с нашего флажка. В этом случае
программа вновь блокирует поле textBoxFavoriteOS, записывая в свойство
textBoxFavoriteOS.Enabled значение false.
Передача регистрационной информации
Для того чтобы вызывающая программа смогла получить регистрационную
информацию, введенную пользователем в форме регистрации, нам надо будет
модифицировать класс RegisterForm, добавив в него несколько свойств.
Добавление свойств
Вы можете добавить свойства вручную, отредактировав исходный текст файла
RegisterForm.cs, однако Microsoft Visual Studio .NET может оказать Вам в этом
некоторую помощь.
Откройте вкладку Class View, расположенную в правом верхнем углу этой системы
разработки приложений (рис. 5-7).
Выберите в поле Property access тип доступа public, тип свойства Property type —
string, а имя свойства Property name укажите как UserName. Это свойство будет
использовано нами для получения имени пользователя, введенного в форме
регистрации.
Далее, отметьте флажок get, чтобы создать один функцию доступа get для чтения
значения свойства. Наша программа не будет записывать в это свойство никаких
значений, поэтому функция доступа set не нужна.
Свойства для текстовых полей
В результате выполнения описанных выше действий мастер свойств создаст в файле
RegisterForm.cs пустое тело свойства UserName. Отредактируйте его следующим
образом:
public string UserName
{
get
{
return textBoxName.Text;
}
}
Обращаясь к свойству UserName, вызывающая программа получит текст,
введенный пользователем в поле textBoxName.
Подготовьте аналогичным образом свойства UserEmail, UserLevel и UserComment:
public string UserEmail
{
get
{
return textBoxEmail.Text;
}
}
if(rb.Checked)
{
if(rb.Name != radioButton5.Name)
return rb.Text;
else
return textBoxFavoriteOS.Text;
}
}
}
return "";
}
}
Преобразование типа выполняется только для флажков, а текстовое поле
пропускается.
В том случае, если функция доступа обнаружила отмеченный флажок, она
проверяет его идентификатор. В том случае, если пользователь отметил флажок Другая
(укажите, какая), функция доступа читает свойство textBoxFavoriteOS.Text, а не
свойство Text отмеченной кнопки. В результате функция доступа вернет в этом случае
название ОС, введенное пользователем в текстовом поле.
Заметим, что использованную здесь проверку типов следует применить и в
свойстве UserGender, исходный текст которого был рассмотрен ранее. Это позволит
избежать ошибок в том случае, если Вы решите добавить в группу groupBoxGender
элементы, отличные от флажков RadioButton.
Свойства для флажков с независимой фиксацией
Свойства, предназначенные для передачи в вызывающую программу состояния
флажков с независимой фиксацией, выглядят достаточно просто:
public string SendNews
{
get
{
if(checkBoxSendNews.Checked)
return "Yes";
else
return "No";
}
}
System.Diagnostics.Process.Start(
"mailto:alexandre@frolov.pp.ru?subject=Регистрация&body=" +
body);
}
}
Вначале этот обработчик создает форму регистрации как объект класса
RegisterForm и сохраняет ее идентификатор в переменной dialog. Далее форма
отображается на экране методом dialog.ShowDialog. На рис. 5-9 эта форма показана в
заполненном виде.
Рис. 5-9. Поля окна регистрации заполнены (все приведенные здесь данные
вымышлены)
dialog.SMTP = this.textBoxSMTP.Text;
dialog.POP3 = this.textBoxPOP3.Text;
dialog.Show();
}
Здесь мы вначале создаем немодальное окно как объект класса PropertyForm,
сохраняя ссылку на него в переменной dialog. Далее мы настраиваем свойство Owner,
записывая в него ссылку на главное окно приложения:
dialog.Owner = this;
В результате создаются родительские отношения между главным окном
приложения и дочерним немодальным окном.
На следующем этапе обработчик событий button1_Click переписывает текст из
полей ввода адресов почтовых серверов в свойства dialog.SMTP и dialog.POP3
немодального диалогового окна:
dialog.SMTP = this.textBoxSMTP.Text;
dialog.POP3 = this.textBoxPOP3.Text;
Чуть позже мы создадим это окно и определим все его компоненты.
Следующая строка подключает обработчик события PropertyForm_Apply,
создаваемого дочерним немодальным окном:
dialog.ApplyHandler += new EventHandler(PropertyForm_Apply);
Подключив обработчик событий, программа отображает дочернее окно на экране
методом Show:
dialog.Show();
Обработчик событий PropertyForm_Apply нужно определить в классе формы
главного окна следующим образом:
private void PropertyForm_Apply(object sender, System.EventArgs e)
{
PropertyForm dialog = (PropertyForm)sender;
this.textBoxPOP3.Text = dialog.POP3;
this.textBoxSMTP.Text = dialog.SMTP;
}
Задача обработчика событий PropertyForm_Apply сводится к переписыванию
адресов почтовых серверов из свойств dialog.POP3 и dialog.SMTP дочернего окна в поля
редактирования главного окна. Т.е. этот обработчик предназначен для отображения в
главном окне нашей программы информации, введенной пользователем в дочернем
немодальном окне.
Немодальная форма настройки свойств
Теперь мы займемся формой для немодального окна. Создайте эту форму с именем
PropertyForm, создав класс с именем PropertyForm.
Расположите в форме текстовые поля ввода адресов почтовых серверов и две
кнопки, показанные на рис. 5-12. Идентификаторы текстовых полей ввода Адрес
сервера POP3 и Адрес сервера SMTP должны быть, соответственно, textBoxPOP3 и
textBoxSMTP.
Кнопка Отменить, предназначенная для закрытия немодального диалогового окна,
должна иметь идентификатор buttonCancel, а кнопка Изменить, создающая событие, —
идентификатор buttonApply.
Подготовив форму, отредактируйте исходный текст класса PropertyForm.
Прежде всего, добавьте ссылку ApplyHandler на событие, реализованное в виде
делегата EventHandler:
/// <summary>
/// Обработчик события от кнопки Изменить
/// </summary>
public event EventHandler ApplyHandler;
Далее подготовьте исходный текст обработчика события buttonApply_Click,
создаваемого кнопкой Изменить:
private void buttonApply_Click(object sender, System.EventArgs e)
{
if(ApplyHandler != null)
ApplyHandler(this, new EventArgs());
}
Анализируя содержимое поля ApplyHandler, этот метод проверяет, был ли
определен соответствующий обработчик события в вызывающей программе. Если был,
то метод buttonApply_Click вызывает его. В качестве первого параметра обработчику
при этом передается ссылка на дочернее окно, а в качестве второго — ссылка на новый
объект EventArgs, позволяющий задать параметры события.
Для кнопки Отменить подготовьте обработчик события buttonCancel_Click,
закрывающий окно дочерней немодальной формы:
private void buttonCancel_Click(object sender, System.EventArgs e)
{
this.Close();
}
Теперь Вам нужно определить два свойства с именами SMTP и POP3:
public string SMTP
{
get
{
return textBoxSMTP.Text;
}
set
{
textBoxSMTP.Text = value;
}
}
Визуальное проектирование
приложений C#
А.В. Фролов, Г.В. Фролов
ГЛАВА 6. ИСПОЛЬЗОВАНИЕ ЭЛЕМЕНТОВ УПРАВЛЕНИЯ
КНОПКИ
Надпись на кнопке
Текст
Шрифт
Цвет
Выравнивание текста
Стиль кнопки
Блокирование кнопки
Изображение на поверхности кнопки
Выбор файла изображения
Выравнивание изображения
Фоновое изображение для кнопки
События
ФЛАЖКИ С ЗАВИСИМОЙ ФИКСАЦИЕЙ
Приложение RadioButtonApp
Панели GroupBox и Panel
Добавление объектов в панели
Обработка событий
Использование изображений
ФЛАЖКИ С НЕЗАВИСИМОЙ ФИКСАЦИЕЙ
Приложение CheckBoxApp
Настройка свойств флажков CheckBox
Обработка событий
Переключение флажка в неопределенное состояние
Извлечение состояния флажков
СПИСКИ
Список ListBox
Создание списка
Настройка свойств
Получение списка выделенных строк
Список CheckedListBox
Список ComboBox
Список DomainUpDown
Элемент управления NumericUpDown
ЭЛЕМЕНТ УПРАВЛЕНИЯ TRACKBAR
Создание элемента управления TrackBar
Свойства элемента управления TrackBar
Обработка события Scroll
ЭЛЕМЕНТ УПРАВЛЕНИЯ PROGRESSBAR
ПОЛОСЫ ПРОКРУТКИ HSCROLLBAR И VSCROLLBAR
КАЛЕНДАРЬ
Создание календаря
Настройка свойств
ЭЛЕМЕНТ УПРАВЛЕНИЯ DATETIMEPICKER
Создание элемента управления DateTimePicker
Настройка свойств
ТАЙМЕР
Создание таймера
Свойства таймера
Методы таймера
Обработка события Tick
ЭЛЕМЕНТ УПРАВЛЕНИЯ TABCONTROL
Создание элемента управления TabControl
Добавление страниц
Редактирование свойств блокнота и страниц
ЭЛЕМЕНТ УПРАВЛЕНИЯ TOOLTIP
Добавление всплывающих подсказок
Настройка параметров всплывающих подсказок
Программный код
Стиль кнопки
Учитывая стремление разработчиков приложений придавать своим программам
интерфейс, подобный интерфейсу Web-приложений, создатели класса
System.Windows.Forms.Button позаботились о возможности изменения стиля кнопки.
Стиль кнопки задается свойством FlatStyle. Это свойство может иметь следующие
значения:
Flat;
Popup;
Standard;
System
Стиль Standard предназначен для создания обычных «серых» кнопок, знакомых
Вам по старым приложениям Microsoft Windows.
Если выбрать стиль System, то внешний вид кнопки будет определяться
настройками ОС.
Кнопка Popup рисуется плоской. Однако когда пользователь указывает на нее
курсором мыши, кнопка принимает объемный вид. Этот стиль удобно использовать в
тех случаях, когда нужно создать несколько расположенных рядом кнопок. В этом
случае кнопка, над которой в настоящий момент находится курсор мыши, будет
выделяться своим объемным видом.
И, наконец, кнопка Flat всегда остается плоской. Но если пользователь
располагает над такой кнопкой курсор мыши, кнопка становится темнее.
Стиль кнопки можно определить средствами Microsoft Visual Studio .NET, а можно
задать программно, например:
buttonYellow.FlatStyle = System.Windows.Forms.FlatStyle.Popup;
Заметим, что кнопки, располагаемые на страницах Web-сайтов, обычно ведут себя
именно так, как только что описанные кнопки Popup и Flat. Поэтому если внешний вид
Вашего автономного приложения должен быть похож на внешний вид страниц Web,
применяйте именно эти стили.
Блокирование кнопки
Приложение может динамически во время своей работы блокировать и разблокировать
кнопки и элементы управления, расположенные в формах.
Для того чтобы заблокировать кнопку, установите значение свойства Enabled
равным true. Заблокированная кнопка отображается в форме, однако не реагирует на
мышь и клавиатуру, а также не создает событий.
При необходимости приложение может скрыть кнопку или другой элемент
управления, установив значение свойства Visible равным false. Скрытый элемент
управления не отображается в форме и не создает событий.
Изображение на поверхности кнопки
Можно значительно улучшить внешний вид кнопки, расположив на ее поверхности
графическое изображение. Мы пользовались этим приемом в 4 главе, создавая панель
инструментов в приложении SimpleNotepad.
Выбор файла изображения
Чтобы поместить на поверхность кнопки графическое изображение, необходимо
отредактировать свойство Image. Перед этим необходимо скопировать файл
изображения в каталог проекта, а затем добавить его в проект. Эти процедуры были
описаны ранее, когда мы рассказывали о размещении графических изображений на
поверхности формы.
Программно изображение на поверхности кнопки задается так:
buttonGreen.Image =
((System.Drawing.Bitmap)
(resources.GetObject("buttonGreen.Image")));
Здесь изображение извлекается из ресурсов приложения с помощью метода
resources.GetObject, а затем, после приведения типа к System.Drawing.Bitmap,
записывается в свойство Image.
Выравнивание изображения
Редактируя свойство ImageAlign, Вы можете изменить выравнивание изображения,
добавленного в кнопку (по умолчанию изображение центрируется в окне кнопки). Это
можно делать при помощи средств Microsoft Visual Studio .NET, или программно:
buttonGreen.ImageAlign = System.Drawing.ContentAlignment.TopCenter;
Как правило, если на кнопе имеется и текст, и изображение, нужно задать
соответствующее выравнивание и для текста, и для изображения. Например, можно
выровнять изображение влево по центру, а текст — вправо по центру, как это сделано
в кнопке Голубой на рис. 6-1. А можно выровнять изображение верх по центру, а
текст — вниз по центру (кнопка Зеленый на рис. 6-1)
Фоновое изображение для кнопки
Чтобы еще больше улучшить внешний вид кнопки, Вы можете задать для нее фоновое
изображение. Оно будет использовано таким же образом, как фоновое изображение
формы или Web-страницы.
Если размеры фонового изображения равны размерам окна кнопки или больше
этих размеров, то оно будет показано в окне кнопки полностью или частично. В
противном случае фоновое изображение будет размножено внутри окна кнопки до его
полного заполнения.
Чтобы задать фоновое изображение для кнопки, отредактируйте свойство
BackgroundImage. Это можно сделать во время разработки приложения или из
программы:
buttonWhite.BackgroundImage =
((System.Drawing.Bitmap)
(resources.GetObject("buttonWhite.BackgroundImage")));
События
До сих пор мы обрабатывали только одно событие Click, создаваемое при щелчке
кнопки мышью. В приложении ButtonsApp мы подготовили обработчики этого события,
предназначенные для изменения цвета панели panel1 класса Panel, расположенной в
правой части главного окна этого приложения. Создавая приложение, перетащите эту
панель в форму из инструментальной панели Toolbox.
Каждый такой обработчик события устанавливает свойство panel1.BackColor,
записывая в него тот или иной цвет:
private void buttonRed_Click(object sender, System.EventArgs e)
{
panel1.BackColor = System.Drawing.Color.Red;
}
В результате мастер форм создаст для нас пустые тела обработчиков событий,
получающих управление при перемещении курсора мыши над кнопкой:
private void buttonsEnter(object sender, System.EventArgs e)
{
}
Обработчик buttonsEnter получит управление, когда курсор мыши входит в
область окна кнопки, а обработчик buttonsLeave — когда курсор мыши выходит из этой
области.
Выделив по очереди остальные кнопки, расположенные в окне нашей формы,
назначьте для них те же самые обработчики событий buttonsEnter и buttonsLeave, что и
для первой кнопки. Для этого достаточно выбрать имя существующего обработчика
событий из списка (рис. 6-4).
switch(btn.Text)
{
case "Красный":
{
panel1.BackColor = System.Drawing.Color.Red;
break;
}
case "Желтый":
{
panel1.BackColor = System.Drawing.Color.LightYellow;
break;
}
case "Голубой":
{
panel1.BackColor = System.Drawing.Color.LightSkyBlue;
break;
}
case "Зеленый":
{
panel1.BackColor = System.Drawing.Color.MediumSpringGreen;
break;
}
case "Белый":
{
panel1.BackColor = System.Drawing.Color.White;
break;
}
}
}
Получив управление, наш обработчик событий получает идентификатор кнопки,
вызвавшей событие, и сохраняет его в переменной btn. Затем обработчик сохраняет в
поле oldColor текущий цвет фона кнопки, получая его из свойства btn.BackColor, а
затем раскрашивает фон кнопки в красный цвет:
Button btn = (Button)sender;
oldColor = btn.BackColor;
btn.BackColor = Color.Red;
Далее обработчик события получает текст надписи на кнопке btn.Text и
определяет, какая кнопка вызвала появление события:
switch(btn.Text)
{
case "Красный":
{
panel1.BackColor = System.Drawing.Color.Red;
break;
}
…
}
Далее обработчик событий изменяет соответствующим образом цвет панели
panel1.
Когда мышь покидает окно наших кнопок, управление получает обработчик
событий buttonsLeave:
private void buttonsLeave(object sender, System.EventArgs e)
{
Button btn = (Button)sender;
btn.BackColor = oldColor;
panel1.BackColor = System.Drawing.Color.LightGray;
}
Его задача — восстановление исходного цвета фона кнопки, окно которой
покинул курсор мыши, а также раскрашивание панели panel1 в светло-серый цвет.
Создавая подобные обработчики событий buttonsEnter и buttonsLeave, Вы можете
динамически изменять графическое изображение, расположенное в окне кнопки.
Фоновое графическое изображение, текст надписи или шрифтовое оформление этого
текста. Словом, здесь все ограничивается только Вашей фантазией.
Обработка событий MouseDown и MouseUp позволяет задать, например,
произвольный вид кнопки в нажатом и отжатом состоянии.
Флажки с зависимой фиксацией
В разделе «Диалоговое окно регистрации программы» 5 главы мы кратко рассказывали Вам
об использовании флажков с зависимой и независимой фиксацией в форме
регистрации программы SimpleNotepad. В этом разделе мы рассмотрим свойства и
события флажков подробнее.
Прежде всего, займемся флажками с зависимой фиксацией. Напомним, что такие
флажки объединяются в группы, причем в отмеченном состоянии может находиться
лишь один флажок из группы.
Флажки с зависимой фиксацией создаются на базе класса
System.Windows.Forms.RadioButton.
Приложение RadioButtonApp
Мы будем рассказывать о флажках с зависимой фиксацией на примере приложения
RadioButtonApp, главное окно которого показано на рис. 6-5.
Далее выделите по очереди все остальные флажки группы Укажите цвет фона, и
назначьте для них обработчик событий bkgChanged при помощи только что
упомянутого окна Properties. Таким образом, изменение состояния всех флажков
группы Укажите цвет фона будет отслеживаться единым обработчиком событий
bkgChanged.
Далее повторите эту операцию для группы флажков Укажите цвет текста,
расположенной в правой части главного окна приложения. Назначьте для них
обработчик событий foreChanged.
Ниже мы привели модифицированные исходные тексты обработчиков событий
bkgChanged и foreChanged:
private void bkgChanged(object sender, System.EventArgs e)
{
RadioButton rb = (RadioButton)sender;
switch(rb.Text)
{
case "Красный":
{
label2.BackColor = Color.LightCoral;
break;
}
case "Зеленый":
{
label2.BackColor = Color.LightGreen;
break;
}
case "Синий":
{
label2.BackColor = Color.LightBlue;
break;
}
case "Белый":
{
label2.BackColor = Color.White;
break;
}
}
}
switch(rb.Text)
{
case "Красный":
{
label2.ForeColor = Color.Red;
break;
}
case "Зеленый":
{
label2.ForeColor = Color.Green;
break;
}
case "Синий":
{
label2.ForeColor = Color.Blue;
break;
}
case "Белый":
{
label2.ForeColor = Color.White;
break;
}
}
}
Эти обработчики событий работают по одинаковому алгоритму. Вначале они
сохраняют в переменной rb идентификатор флажка, состояние которого было
изменено. Затем обработчики извлекают текст надписи, расположенной возле флажка.
В зависимости от этого текста, обработчики изменяют цвет фона или текста надписи
label2, расположенной в нижней части главного окна нашего приложения.
При необходимости Ваше приложение может обрабатывать и другие события,
создаваемые флажками с зависимой фиксацией. Полный список и кроткое описание
этих событий Вы сможете просмотреть на вкладке событий окна Properties (рис. 6-6).
Обрабатывая, например, события MouseEnter и MouseLeave, Вы сможете динамически
изменять внешний вид флажка, когда над ним находится курсор мыши.
Использование изображений
В распоряжении программиста имеются средства настройки цвета фона и текста для
флажков, аналогичные средствам настройки данных атрибутов кнопок Button.
Так же как и кнопки класса Button, рассмотренные нами в начале этой главы,
флажки можно снабжать графическими изображениями, а также фоновыми
графическими изображениями.
На рис. 6-7 мы привели внешний вид измененного приложения RadioButtonApp. В
нем мы снабдили флажки, задающие цвет фона, графическими изображениями. Кроме
того, для флажка Белый мы задали фоновое графическое изображение.
if(checkBoxStoreSMTPPassword.Checked)
{
checkBoxStoreSMTPPassword.CheckState =
CheckState.Indeterminate;
}
}
}
Когда пользователь изменяет состояние флажка Использовать авторизацию на
сервере SMTP, обработчик checkBox1_CheckedChanged определяет текущее состояние
флажка (отмечен или не отмечен).
Если флажок отмечен, обработчик события разблокирует поле ввода пароля для
сервера SMTP и флажок Запомнить пароль:
if(cb.Checked)
{
textBoxSMTPPassword.Enabled = true;
checkBoxStoreSMTPPassword.Enabled = true;
}
В противном случае эти поля блокируются:
textBoxSMTPPassword.Enabled = false;
checkBoxStoreSMTPPassword.Enabled = false;
После блокировки полей обработчик события проверяет текущее состояния
флажка Запомнить пароль. Если этот флажок отмечен, то он переводится в
неопределенное состояние:
if(checkBoxStoreSMTPPassword.Checked)
{
checkBoxStoreSMTPPassword.CheckState =
CheckState.Indeterminate;
}
В реальном приложении для обработки аналогичной ситуации нет никакой
необходимости переводить флажок в третье состояние, т.к. можно ограничиться его
блокировкой. Мы привели этот фрагмент кода только как пример способа перевода
флажка в неопределенное состояние.
Реально неопределенное состояние флажков с независимой фиксацией часто
используется в древовидных структурах диалоговых окон настройки каких-либо
параметров. Оно отмечает ветви дерева, содержащие флажки, находящиеся и в
отмеченном, и в неотмеченном состоянии. Такие окна встречаются, например, в
программах установки приложений.
Извлечение состояния флажков
В окне нашего приложения предусмотрена кнопка Сохранить (рис. 6-8),
предназначенная для просмотра информации, введенной пользователем.
Подготовьте обработчик события button1_Click для этой кнопки в следующем
виде:
private void button1_Click(object sender, System.EventArgs e)
{
string str =
"POP3 Server: " + textBoxPOP3Server.Text + "\n"+
"POP3 Username: " + textBoxAccount.Text + "\n" +
"POP3 Password: " + textBoxPOP3Password.Text;
if(checkBoxStorePOP3Password.Checked)
{
str += " (сохранить)";
}
if(checkBoxUseSMTPPassword.Checked)
{
str += "SMTP Password: " + textBoxSMTPPassword.Text;
}
if(checkBoxStoreSMTPPassword.Checked &&
checkBoxStorePOP3Password.Checked)
{
str += " (сохранить)";
}
MessageBox.Show(str);
}
Информация, введенная в текстовых полях, извлекается из свойства Text. Что же
касается текущего состояния флажков с независимой фиксацией, то их состояние
можно получить из свойства Checked.
Вот, например, как мы проверяем состояние флажка Запомнить пароль:
if(checkBoxStorePOP3Password.Checked)
{
str += " (сохранить)";
}
Напомним, что если флажок может находиться в трех состояниях, то свойство
Checked может принимать следующие значения:
CheckState.Checked
CheckState.Unchecked
CheckState.Indeterminate
Списки
Перед программистом часто встает задача организации списков, предназначенных для
выбора строки из некоторого заранее определенного набора строк. Например, может
потребоваться список файлов из текущего каталога, список названий цветов для
раскраски какого-либо объекта приложения, список режимов работы приложения и т.
д. Стандартные диалоговые окна, такие как Open и Save As, содержат списки файлов,
каталогов и дисковых устройств.
На панели инструментов Tools системы разработки программного обеспечения
Microsoft Visual Studio .NET имеются готовые элементы управления, с помощью которых
программист может легко добавить списки различных типов в свое приложение.
На рис. 6-10 мы показали главное окно приложения ListBoxApp, созданного нами
для демонстрации способов применения списков в оконных приложениях C#.
В левом верхнем углу этого окна находится список, созданный на базе класса
ListBox. С его помощью Вы можете создавать одно-колоночные и многоколоночные
списки, имеющие вертикальную (для одно-колоночных списков) и горизонтальную (для
многоколоночных списков) полосу прокрутки. Список ListBox позволяет пользователю
выбирать как один, так и несколько элементов.
Список, создаваемый на базе класса CheckedListBox, тоже допускает выбор одного
или нескольких элементов. Этот список показан в правом верхнем углу рис. 6-10 и
обозначен как CheckedListBox. Он представляет собой комбинацию списка ListBox и
флажков CheckBox, рассмотренных в предыдущем разделе этой главы.
Список класса ComboBox, показанный в левой части рис. 6-10, является
комбинацией списка и однострочного редактора текста. Поэтому для данного списка
используются свойства и события, аналогичные свойствам и событиям списка ListBox и
редактора текста класса TextBox.
Если щелкнуть мышью кнопку со стрелкой, расположенную в правой части окна
списка ComboBox, это окно раскроется (рис. 6-11).
str += "\n";
foreach (string s in listBox1.SelectedItems)
{
str += " " + s;
}
Добавьте приведенные выше строки в тело обработчика событий button1_Click
непосредственно перед вызовом метода MessageBox.Show. На рис. 6-13 мы показали,
как теперь будет отображаться информация о выделенных строках.
str += "\n";
foreach (int idx in checkedListBox1.CheckedIndices)
{
str += " " + idx;
}
str += "\n";
foreach (string s in checkedListBox1.CheckedItems)
{
str += " " + s;
}
На рис. 6-14 мы показали сведения о строках, выделенных в списках ListBox и
CheckedListBox.
Рис. 6-14. Добавлена обработка списка CheckedListBox
Список ComboBox
Как мы уже говорили, элемент управления ComboBox представляет собой комбинацию
однострочного редактора текста и списка. Когда пользователь щелкает кнопку со
стрелкой, расположенную в правой части окна элемента управления ComboBox, список
раскрывается, и пользователь может выбрать из него нужную строку. Если же такой
строки нет, или если пользователь не желает ее искать, строку можно ввести
непосредственно с клавиатуры в поле редактирования.
После создания программа добавляет в список новые строки с помощью метода
AddRange:
this.comboBox1 = new System.Windows.Forms.ComboBox();
…
this.comboBox1.Items.AddRange(
new object[]
{
"Каждый",
"Охотник",
"Желает",
"Знать",
"Где",
"Сидит",
"Фазан"
}
);
Далее происходит настройка основных свойств списка:
this.comboBox1.Location = new System.Drawing.Point(16, 168);
this.comboBox1.Name = "comboBox1";
this.comboBox1.Size = new System.Drawing.Size(120, 21);
this.comboBox1.TabIndex = 2;
this.comboBox1.Text = "Каждый";
Возможно, для Вас будет интересно свойство MaxDropDownItems, задающее
количество строк в раскрывающемся окне списка. По умолчанию оно равно 8.
С помощью свойства Sorted можно отсортировать элементы списка по алфавиту,
для чего нужно присвоить этому свойству значение true.
Чтобы получить строку, выбранную пользователем из списка или введенную в
поле редактирования элемента управления ComboBox, необходимо обратиться к
свойству Text:
str += "\n\nСписок ComboBox: " + comboBox1.Text;
Список DomainUpDown
Элемент управления DomainUpDown, показанный в левой нижней части рис. 6-10,
позволяет последовательно выбирать текстовые строки, нажимая кнопки со стрелками,
расположенные в правой части окна списка. При этом происходит «прокрутка»
текстовых строк списка.
Кроме того, пользователь может ввести нужную ему строку непосредственно в
окне элемента управления DomainUpDown при помощи клавиатуры. В этом список
DomainUpDown напоминает только что рассмотренный элемент управления ComboBox.
Ниже мы привели фрагмент кода, создающий и наполняющий список
DomainUpDown, а также настраивающий его некоторые свойства:
private System.Windows.Forms.DomainUpDown domainUpDown1;
…
this.domainUpDown1.Items.Add("Каждый");
this.domainUpDown1.Items.Add("Охотник");
this.domainUpDown1.Items.Add("Желает");
this.domainUpDown1.Items.Add("Знать");
this.domainUpDown1.Items.Add("Где");
this.domainUpDown1.Items.Add("Сидит");
this.domainUpDown1.Items.Add("Фазан");
this.domainUpDown1.Location = new System.Drawing.Point(16, 232);
this.domainUpDown1.Name = "domainUpDown1";
this.domainUpDown1.TabIndex = 3;
this.domainUpDown1.Text = "Каждый";
Среди свойств списка DomainUpDown заслуживает упоминание свойство ReadOnly.
Если установить значение этого свойства, равное true, пользователь сможет выбирать
строки из списка, но ввод строки с клавиатуры будет ему недоступен.
Свойства UpDownAlign и TextAlign позволяют задать выравнивание кнопок
прокрутки списка и текста строк списка, соответственно. По умолчанию кнопки
располагаются справа, а текст выравнивается по левой границе окна списка.
Если свойство Wrap имеет значение true, то список будет свернутым в кольцо.
Таким образом, пользователь сможет многократно перебирать все его элементы в
прямом или обратном направлении.
Для того чтобы получить строку, введенную пользователем или выбранную в
списке DomainUpDown, Вам потребуется обратиться к свойству Text:
str += "\nСписок DomainpDown: " + domainUpDown1.Text;
В этом окне всего два элемента управления — текстовое поле Label и элемент
управления TrackBar. Последний из них позволяет регулировать значение,
отображаемое в текстовом поле. Диапазон регулировки — от 0 до 100. При
использовании клавиш перемещения курсора значение регулируется с дискретностью
1, а при использовании клавиш PgUp и PgDn — с дискретностью 5.
При визуальном проектировании приложения Вам нужно перетащить значок
элемента управления TrackBar из инструментальной панели Microsoft Visual Studio .NET
в окно формы. При этом мастер формы создаст для Вас весь необходимый программный
код.
Элемент управления TrackBar создается как объект класса
System.Windows.Forms.TrackBar:
this.trackBar1 = new System.Windows.Forms.TrackBar();
Создание календаря
Самый простой способ создать календарь — это переместить его значок MonthCalendar
из панели Toolbox в окно проектируемой формы. При этом с исходный текст
приложения будет добавлен следующий код:
private System.Windows.Forms.MonthCalendar monthCalendar1;
…
MessageBox.Show(s);
}
Здесь мы извлекаем начальную и конечную дату, а затем отображаем на экране
текстовую строку с диапазоном дат, выделенным пользователем в окне календаря (рис.
6-23).
Настройка свойств
Внешний вид окна элемента управления DateTimePicker определяется свойством
Format, которое может иметь несколько значений:
Long;
Short;
Time;
Custom
Значение Long используется по умолчанию. В этом случае окно элемента
управления DateTimePicker содержит поля для ввода числа, месяца и года, а также
кнопку со стрелкой. На рис. 6-24 такой элемент управления показан справа вверху.
Пользователь может вводить компоненты даты, непосредственно редактируя их
при помощи клавиатуры, увеличивать или уменьшать эти компоненты при помощи
клавиш перемещения курсора, а также с помощью кнопки со стрелкой. В последнем
случае рядом с окном элемента управления DateTimePicker появится уже знакомый Вам
календарь (рис. 6-25).
После того как пользователь выберет дату, окно календаря исчезнет. Заметим,
однако, что таким способом можно выбрать только одну дату, но не диапазон дат.
Формат Short аналогичен формату Long, но вместо названия месяца в окне
элемента управления отображается его номер. На рис. 6-24 элемент управления с
таким форматом показан вторым сверху в правом углу.
Формат Time используется для ввода времени в часах, минутах и секундах. На
рис. 6-24 этот формат имеют два оставшихся элемента управления DateTimePicker.
И, наконец, формат Custom позволяет программисту задать произвольный формат
отображения даты и времени в окне элемента управления DateTimePicker.
Чтобы отказаться от ввода даты с помощью календаря, Вы можете установить
значение свойства ShowUpDown, равным true. В этом случае вместо одной кнопки со
стрелкой в окне элемента управления DateTimePicker появятся две кнопки, с помощью
которых пользователь сможет увеличивать или уменьшать отдельные компоненты даты
и времени.
Окно DateTimePicker можно снабдить флажком, установив значение свойства
ShowCheckBox, равным true. Если этот флажок не отмечен пользователем, работа
соответствующего элемента управления DateTimePicker блокируется. Состояние флажка
можно установить или определить при помощи свойства Checked.
Свойства MinDate и MaxDate задают, соответственно, нижнюю и верхнюю границу
изменения дат. Используя эти свойства, программист может ограничить диапазон
изменения дат в окне элемента управления DateTimePicker.
Что же касается значения даты, установленной пользователем, то программа
может извлечь его из свойства Value.
Доработайте обработчик события button1_Click приложения DateTimeApp
следующим образом, добавив в него строки для извлечения и отображения даты и
времени, введенных с помощью элементов управления DateTimePicker:
private void button1_Click(object sender, System.EventArgs e)
{
string s = "Выбран диапазон дат: ";
DateTime dtStart = monthCalendar1.SelectionRange.Start;
DateTime dtEnd = monthCalendar1.SelectionRange.End;
s += "("
+ dtStart.Day + "." + dtStart.Month + "." + dtStart.Year +
"-"+
dtEnd.Day + "." + dtEnd.Month + "." + dtEnd.Year + ")";
s += "\n\nВремя 3: " +
dt3.Hour + ":" + dt3.Minute + ":" + dt3.Second;
s += "\nВремя 4: " +
dt4.Hour + ":" + dt4.Minute + ":" + dt4.Second;
MessageBox.Show(s);
}
Результат работы этого фрагмента нашего приложения показан на рис. 6-26.
Таймер
Во многих программах требуется следить за временем или выполнять какие-либо
периодические действия. Программы MS-DOS для работы с таймером перехватывали
аппаратное прерывание таймера INT 8.
Обычные приложения Microsoft Windows не могут самостоятельно обрабатывать
прерывания таймера, поэтому для работы с ним нужно использовать другие способы.
ОС Microsoft Windows для каждого приложения позволяет создать несколько
виртуальных таймеров. Все эти таймеры работают по прерываниям одного физического
таймера.
Так как работа Microsoft Windows основана на передаче сообщений, логично было
бы предположить, что и работа виртуального таймера также основана на передаче
сообщений. И в самом деле, приложение может заказать для любого своего окна
несколько таймеров, которые будут периодически посылать в функцию окна сообщение
с кодом WM_TIMER.
В распоряжении приложений C# имеется удобный программный компонент Timer,
периодически создающий событие Tick. Обрабатывая это событие, приложения могут
выполнять все необходимые периодические действия.
К сожалению, точность таймера ОС оставляет желать лучшего. Сообщения
таймера, создающие события Tick, проходят через очередь приложения. К тому же,
другие приложения могут блокировать на некоторое время работу Вашего приложения.
Поэтому события таймера возникают в общем случае нерегулярно. Кроме того,
несмотря на возможность указания интервалов времени в миллисекундах, реальная
дискретность таймера определяется периодом прерываний, посылаемых таймером.
Нерегулярность прихода сообщений таймера не вызывает особых проблем, если
речь не идет о системах реального времени. Такие системы, основанные на ОС
Microsoft Windows, должны использовать специальные драйверы для работы с
периферийными устройствами, критичными к скорости реакции системы. Строго
говоря, ОС Microsoft Windows не предназначена для работы в системах реального
времени. Скорее, она ориентирована на работу с пользователем, когда небольшие
задержки событий во времени не имеют никакого значения. Системы реального
времени обычно создаются на базе специальных ОС реального времени, рассмотрение
которых выходит за рамки нашей книги.
Для демонстрации приемов работы с программным компонентом Timer мы
подготовили приложение TimerApp (рис. 6-27), представляющее собой простейшие
часы.
Методы таймера
Наиболее важны методы таймера Start и Stop. Первый из этих методов запускает
таймер, а второй — останавливает.
В приложении TimerApp мы вызываем эти методы при обработке событий от
кнопок Старт и Стоп, соответственно:
private void button1_Click(object sender, System.EventArgs e)
{
timer1.Start();
}
Добавление страниц
Пока наш блокнот отображается в виде пустого прямоугольника и не содержит ни
одной страницы. Для добавления страниц Вам нужно отредактировать свойство
TabPages. Эта операция выполняется с помощью редактора страниц блокнота,
показанного на рис. 6-31.
Выполните эту процедуру для всех элементов управления формы, которые нужно
снабдить всплывающими подсказками. После того как вы оттранслируете приложение,
подсказки начнут работать.
Настройка параметров всплывающих подсказок
Редактируя свойства компонента toolTip1, Вы сможете настроить некоторые параметры
работы всплывающих подсказок.
Чтобы отредактировать эти свойства, выделите компонент toolTip1 левой
клавишей мыши, а затем обратитесь к окну Properties (рис. 6-38).
Рис. 6-38. Редактирование свойств компонента toolTip1
Визуальное проектирование
приложений C#
А.В. Фролов, Г.В. Фролов
ГЛАВА 7. МНОГООКОННЫЙ ПОЛЬЗОВАТЕЛЬСКИЙ ИНТЕРФЕЙС
ИСПОЛЬЗОВАНИЕ ФРЕЙМОВ
Создание главного окна приложения
Добавление элемента управления TreeView
Добавление вертикального разделителя
Добавление панели Panel
Добавление элемента управления ListView
Добавление горизонтального разделителя
Добавление окна RichTextBox
ЭЛЕМЕНТ УПРАВЛЕНИЯ TREEVIEW
Инициализация дерева TreeView
Получение списка дисков
Получение списка подкаталогов
Метод DriveTreeInit
Метод GetDirs
Обработчик события BeforeExpand
Добавление значков к узлам дерева
Создание списка изображений
Подключение списка изображений к дереву
Изменения в исходном тексте программы
Добавление флажков к узлам дерева
Редактирование текста узлов дерева
СПИСОК LISTVIEW
Создание и настройка списка ListView
Режимы отображения
Создание и настройка столбцов таблицы
Сортировка содержимого списка
Подключение значков к списку
Наполнение списка ListView
Алгоритм наполнения списка
Обработчик события AfterSelect
Отображение содержимого текстовых файлов
ПРИЛОЖЕНИЯ MDI
Меню Windows
Системное меню MDI-окна
Создание главного окна MDI-приложения
Создание MDI-окон
Шаблон MDI-окна
Программный код для создания MDI-окна
Упорядочивание MDI-окон
Передача данных через буфер Clipboard
Копирование данных в буфер Clipboard
Вставка данных из буфера Clipboard
Глава 7. Многооконный пользовательский
интерфейс
Предыдущие главы нашей книги были посвящены созданию однооконных приложений,
напоминающих по своему пользовательскому интерфейсу программу Microsoft Notepad.
В этой главе мы расскажем Вам о проектировании приложений с многооконным
пользовательским интерфейсом. Это приложения с фреймами (панелями), а также
приложения с многооконным интерфейсом документов (Multiple-document interface,
MDI).
В качестве примеров автономных приложений Microsoft Windows, реализующих
многооконный интерфейс с фреймами можно привести Microsoft Explorer (рис. 7-1) и
почтовую программу Microsoft Outlook Express, а также систему разработки Microsoft
Visual Studio .NET.
Как мы уже говорили, в окне элемента управления ListView наша программа будет
показывать список файлов и каталогов, расположенный в текущем каталоге,
выбранном с помощью дерева TreeView.
Добавление горизонтального разделителя
Теперь нашей задачей будет добавление в правую часть главного окна
горизонтального разделителя Splitter. Перетащите его пиктограмму из панели
инструментов Toolbox в окно формы, а затем установите значение свойства Dock
добавленного разделителя, равным Top.
В результате разделитель будет расположен непосредственно под окном элемента
управления ListView, как это показано на рис. 7-10.
Рис. 7-10. Добавление горизонтального разделителя
//
// 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:\\";
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();
try
{
diArray = di.GetDirectories();
}
catch
{
return;
}
treeView1.EndUpdate();
}
Событие BeforeExpand возникает при попытке пользователя раскрыть узел
дерева. В этом случае наш обработчик заполняет открываемый узел при помощи
рассмотренного ранее метода GetDirs. Ссылка на узел извлекается из поля
e.Node.Nodes, передаваемого обработчику событий в качестве параметра.
После создания обработчика события BeforeExpand появится возможность
раскрывать узлы дерева (рис. 7-14).
Рис. 7-14. Теперь узлы дерева можно раскрывать
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;
}
Отметив все или некоторые узлы дерева флажками, можно выполнять над
соответствующими объектами групповые операции. Если дерево показывает структуру
дисков и файлов, то это могут быть такие операции, как, например, удаление,
перемещение или копирование.
Редактирование текста узлов дерева
Изменив значение свойства LabelEdit на True можно разрешить пользователям
редактировать текстовые надписи, расположенные около узлов дерева (рис. 7-20).
try
{
fiArray = di.GetFiles();
diArray = di.GetDirectories();
}
catch
{
return;
}
listView1.Items.Clear();
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.
Приложения 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-окон
Как мы уже говорили, 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.
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.
Визуальное проектирование
приложений C#
А.В. Фролов, Г.В. Фролов
ГЛАВА 8. ПРИЛОЖЕНИЯ С БАЗАМИ ДАННЫХ
МЕТОДЫ ДОСТУПА К СУБД
Вызов программных интерфейсов
Прямой вызов программного интерфейса СУБД
Использование программного интерфейса ODBC
Объектные интерфейсы СУБД
Интерфейс Remote Data Object
Интерфейс OLE DB
Интерфейс ActiveX Data Objects
Метод доступа ADO .NET
Многоуровневые системы
Рассоединенные системы
Распределенная обработка данных и XML
Провайдеры данных для управляемого кода
РАБОТА С ОБЪЕКТАМИ DATASET
Приложение DataSetApp
Добавление элемента управления DataSet
Настройка свойств элемента управления DataSet
Создание таблиц
Создание столбцов
Первичный ключ
ЭЛЕМЕНТ УПРАВЛЕНИЯ DATAGRID
Добавление объекта DataGrid в проект приложения DataSetApp
Настройка внешнего вида окна элемента управления DataGrid
Рамка вокруг окна
Заголовок окна
Форматирование содержимого окна
Сортировка
Запрет редактирования
Выбор цвета
Набор стилей оформления TableStyles
Программный код
Создание элемента управления DataGrid
Настройка свойств
Добавление таблицы стилей
РАБОТА С DATASET ПРИ ПОМОЩИ МЕТОДОВ И СВОЙСТВ
Добавление новой строки
Удаление строки
ПРИЛОЖЕНИЕ PHONEBOOKAPP
Создание проекта приложения
Элементы управления для работы со списком имен и фамилий
Элементы управления для работы со списком телефонов
Создание таблиц базы данных
Таблица Contacts
Таблица Phones
Связывание таблиц Contacts и Phones
Проектирование кода для управления базой данных
Добавление нового контакта
Выбор записи в списке имен и фамилий
Редактирование записей таблицы Contacts
Удаление записей таблицы Contacts
Добавление номера телефона
Удаление номера телефона
Изменение номера телефона
Обновление списка контактов
Обновление списка телефонов
ФИЛЬТР ДЛЯ МЕТОДА SELECT
Перегруженные методы Select
КРАТКОЕ ОПИСАНИЕ СИНТАКСИСА СТРОКИ ФИЛЬТРА
Значения
Операторы
Функции
((System.ComponentModel.ISupportInitialize)
(this.dataSet1)).BeginInit();
//
// dataSet1
//
this.dataSet1.DataSetName = "NewDataSet";
this.dataSet1.Locale = new System.Globalization.CultureInfo("ru-RU");
Как видите, объект DataSet создается с помощью конструктора класса
System.Data.DataSet, не имеющего параметров.
Далее с помощью метода ISupportInitialize.BeginInit объекту передается сигнал о
начале инициализации. Этот сигнал поможет объекту выполнить инициализацию своих
свойств наиболее эффективным способом.
На данном этапе приложение устанавливает только два свойства — имя набора
данных DataSetName и сведения о национальной культуре (локализации) Locale.
Подробнее о национальных культурах читайте в [1].
Настройка свойств элемента управления DataSet
Для настройки свойств элемента управления DataSet выделите элемент dataSet1
мышью и воспользуйтесь окном редактирования свойств Properties, показанным на рис.
8-4.
Рис. 8-4. Свойства элемента управления DataSet
Добавить новую таблицу можно при помощи кнопки Add, а удалить — при помощи
кнопки Remove.
Каждая таблица имеет набор свойств, которые можно редактировать в правой
части окна Tables Collection Editor. В частности, свойство Columns задает столбца
таблицы, а свойство Constarints — ограничения.
В свойстве TableName таблицы контактов Contacts Вам необходимо задать
название таблицы, как это показано на рис. 8-6. Измените также имя таблицы (в
свойстве Name), заменив его именем contactsDataTable.
Заметим, что имя и название таблиц можно оставлять такими, какими их создает
по умолчанию мастер таблиц. Однако для удобства программирования целесообразно
заменить их осмысленными строками.
Рассмотрим программный код, создаваемый дизайнером форм при добавлении
таблиц.
Идентификаторы таблиц хранятся в полях класса Form1 с именами
contactsDataTable (таблица Contacts) и phonesDataTable (таблица Phones):
private System.Data.DataTable contactsDataTable;
private System.Data.DataTable phonesDataTable;
Как объекты наши две таблицы создаются при помощи конструкторов класса
System.Data.DataTable:
this.contactsDataTable = new System.Data.DataTable();
this.phonesDataTable = new System.Data.DataTable();
Далее с помощью метода ISupportInitialize.BeginInit обеим таблицам передается
сигнал о начале инициализации:
((System.ComponentModel.ISupportInitialize)
(this.contactsDataTable)).BeginInit();
((System.ComponentModel.ISupportInitialize)
(this.phonesDataTable)).BeginInit();
Созданные таблицы добавляются к элементу управления dataSet1 при помощи
метода AddRange:
this.dataSet1.Tables.AddRange(
new System.Data.DataTable[]
{
this.contactsDataTable,
this.phonesDataTable
}
);
В качестве параметра этому методу передается ссылка на массив таблиц, поэтому
таким способом можно добавить сразу несколько таблиц.
Заметим, что этот фрагмент кода, точно также как и другие фрагменты кода,
описанные в этом разделе, создаются автоматически мастером форм, поэтому Вам не
придется писать его вручную. Однако изучение кода поможет Вам глубже разобраться
в приемах работы с элементом управления DataSet.
Создание столбцов
Для добавления столбцов в таблицу, а также для редактирования свойств столбцов
открывается редактор Columns Collection Editor. На рис. 8-7 мы показали его окно после
добавления в таблицу Contacts строк id, Имя и Фамилия.
Здесь мы задали свойства столбца аналогично тому, как это было сделано для
столбца id таблицы Contacts, рассмотренной выше.
Что же касается столбца Телефон, то настройка его свойств аналогична настройке
свойств столбцов Имя и Фамилия таблицы Contacts (рис. 8-12).
Рис. 8-12. Строка Телефон таблицы Phones
Для хранения ссылок на столбцы наших таблиц дизайнер форм создает в классе
Form1 необходимое количество полей класса System.Data.DataColumn:
private System.Data.DataColumn idDataColumn;
private System.Data.DataColumn fnDataColumn;
private System.Data.DataColumn lnDataColumn;
this.phonesDataTable.Columns.AddRange(
new System.Data.DataColumn[]
{
this.idPhonesDataColumn,
this.phonesDataColumn
}
);
При добавлении столбцов в таблицу ссылки на эти столбцы сохраняются в
свойстве Columns таблицы.
Так как столбцы id в обеих таблицах должны хранить уникальные значения, мы
добавили ограничение System.Data.UniqueConstraint:
this.contactsDataTable.Constraints.AddRange(
new System.Data.Constraint[]
{
new System.Data.UniqueConstraint("Constraint1",
new string[]
{
"id"
}, true)
}
);
this.phonesDataTable.Constraints.AddRange(
new System.Data.Constraint[]
{
new System.Data.UniqueConstraint("Constraint1",
new string[]
{
"id"
}, true)
}
);
Набор ограничений, налагаемых на столбцы таблицы, хранятся в свойстве
Constraints.
Первичный ключ
Как правило, в каждой таблице создается так называемый первичный ключ (primary
key), однозначно идентифицирующий строки таблицы. В наших таблицах роль
первичного ключа выполняет столбец id, предусмотренный в таблицах Contacts и
Phones.
Чтобы сделать столбец таблицы первичным ключом, необходимо настроить его
атрибуты так, как мы делали это для упомянутого выше столбца id. А именно: столбец
должен содержать только уникальные непустые значения. Кроме того, необходимо
настроить свойство таблицы PrimaryKey, указав в нем поле, играющее роль первичного
ключа.
Выбор первичного ключа при редактировании свойства PrimaryKey
осуществляется при помощи набора флажков, каждый из которых соответствует одному
столбцу таблицы (рис. 8-13).
Рис. 8-13. Первичный ключ в таблице Contacts
this.phonesDataTable.PrimaryKey =
new System.Data.DataColumn[]
{
this.idPhonesDataColumn
};
Как видите, при этом в свойство PrimaryKey записывается ссылка на массив
идентификаторов столбцов таблицы, составляющих первичный ключ.
Элемент управления DataGrid
При создании приложений, имеющих графический пользовательский интерфейс,
отображение содержимого баз данных часто выполняют с помощью табличных отчетов.
Хорошей новостью является то, что в наборе элементов управления системы
Microsoft .NET Framework имеется очень мощный компонент с названием DataGrid,
специально предназначенный для решения этой задачи.
Работать с элементом управления DataGrid достаточно просто. Если Вы
подготовили набор таблиц в виде объекта DataSet, то для отображения содержимого
этих таблиц достаточно подключить к этому набору объект DataGrid, выполнив
необходимую настройку свойств. При этом элемент управления DataGrid автоматически
обеспечит Ваше приложение возможностью просматривать и редактировать набор
таблиц, созданных в рамках объекта DataSet. Образно говоря, элементы управления
DataGrid и DataSet как бы созданы друг для друга.
Добавление объекта DataGrid в проект приложения
DataSetApp
Созданное в предыдущем разделе приложение DataSetApp поможет нам изучить
основные возможности элемента управления DataGrid.
Перетащите значок элемента управления DataGrid из вкладки Windows Forms
инструментальной панели Toolbox в окно формы приложения DataSetApp. Далее Вам
нужно будет настроить некоторые свойства элемента управления DataGrid, пользуясь
для этого окном Properties системы Microsoft Visual Studio .NET (рис. 8-15).
Как обычно, кнопка Add позволяет добавить новый стиль, а кнопка Remove —
удалить его.
После добавления стиля необходимо настроить свойства этого стиля. Для этого
воспользуйтесь списком свойств, расположенным в правой части окна редактора
стилей, показанного на рис. 8-24.
Так как мы создавали стиль dataGridTableStyle1 для таблицы Contacts, укажите
имя этой таблицы в свойстве MappingName.
При необходимости Вы можете изменить цветовую палитру элементов
отображаемой таблицы, а также скрыть заголовки столбцов и строк. Последнее
действие выполняется путем присваивания значения false свойствам
ColumnHeadersVisible и RowHeadersVisible.
Далее необходимо создать три стиля оформления для столбцов id, Имя и Фамилия.
Это делается путем редактирования свойства GridColumnStyles. Для редактирования
вызывается специальный редактор стилей столбцов, окно которого показано на рис. 8-
25.
Рис. 8-25. Стили оформления столбца id
Программный код
В предыдущих разделах мы рассказали Вам о том, как можно создавать элементы
управления DataGrid визуальными средствами проектирования, предусмотренным
специально для этой цели в системе Microsoft Visual Studio .NET. Теперь настало время
рассмотреть программный код, который создается при этом дизайнером форм.
Создание элемента управления DataGrid
Элемент управления DataGrid создается при помощи конструктора класса
System.Windows.Forms.DataGrid, как это показано ниже:
private System.Windows.Forms.DataGrid dataGrid1;
this.dataGrid1 = new System.Windows.Forms.DataGrid();
Программа сохраняет идентификатор созданного элемента управления в поле
dataGrid1.
Настройка свойств
Рассмотрим программный код, который создается для настройки свойств элемента
управления DataGrid.
Свойство DataSource задает привязку элемента управления DataGrid к набору
данных DataSet, содержащему таблицы с данными:
this.dataGrid1.DataSource = this.dataSet1;
Свойство DataMember привязывает наш элемент управления к таблице Contacts:
this.dataGrid1.DataMember = "Contacts";
Следующие строки задают шрифт для заголовка и текста:
this.dataGrid1.CaptionFont =
new System.Drawing.Font("Arial", 8.25F,
System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point,
((System.Byte)(204)));
this.dataGrid1.Font =
new System.Drawing.Font("Microsoft Sans Serif", 8.25F,
System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((System.Byte)(204)));
Шрифт указывается в виде ссылки на вновь созданный объект класса
System.Drawing.Font. В качестве первого параметра конструктору этого класса
передается название шрифта, в качестве второго — размер шрифта в единицах,
передаваемый посредством четвертого параметра. В нашем случае размер шрифта
указывается в пунктах.
Третий параметр задает стиль шрифта, а пятый — номер кодовой страницы
Unicode. Значение 204 соответствует кириллической кодовой странице.
Заголовок таблицы задается при помощи свойства CaptionText:
this.dataGrid1.CaptionText = "Контакты";
Значения других свойств определяются следующим образом:
this.dataGrid1.HeaderForeColor =
System.Drawing.SystemColors.ControlText;
this.dataGrid1.Location = new System.Drawing.Point(32, 96);
this.dataGrid1.Name = "dataGrid1";
this.dataGrid1.Size = new System.Drawing.Size(288, 208);
this.dataGrid1.TabIndex = 0;
Добавление таблицы стилей
Таблица стилей создается для нашего элемента управления DataGrid как объект класса
System.Windows.Forms.DataGridTableStyle:
this.dataGridTableStyle1 =
new System.Windows.Forms.DataGridTableStyle();
В процессе инициализации приложения эта таблица стилей подключается к
элементу управления DataGrid при помощи метода AddRange:
this.dataGrid1.TableStyles.AddRange(
new System.Windows.Forms.DataGridTableStyle[]
{
this.dataGridTableStyle1
}
);
Заметим, что к одному элементу управления DataGrid можно подключить сразу
несколько таблиц стилей, по одному на каждую таблицу, хранящуюся в DataGrid.
Для каждого столбца мы создаем свой стиль, который затем подключается к
таблице стилей dataGridTableStyle1 методом AddRange:
this.dataGridTextBoxColumn1 =
new System.Windows.Forms.DataGridTextBoxColumn();
this.dataGridTextBoxColumn2 =
new System.Windows.Forms.DataGridTextBoxColumn();
this.dataGridTextBoxColumn3 =
new System.Windows.Forms.DataGridTextBoxColumn();
this.dataGridTableStyle1.GridColumnStyles.AddRange
(
new System.Windows.Forms.DataGridColumnStyle[]
{
this.dataGridTextBoxColumn1,
this.dataGridTextBoxColumn2,
this.dataGridTextBoxColumn3
}
);
Кроме того, необходимо подключить таблицу стилей к элементу управления
DataGrid, записав соответствующую ссылку в поле DataGrid таблицы стилей:
this.dataGridTableStyle1.DataGrid = this.dataGrid1;
Стили оформления каждого столбца также задаются в процессе инициализации
приложения. Ниже мы привели соответствующий фрагмент кода:
//
// dataGridTextBoxColumn2
//
this.dataGridTextBoxColumn2.Format = "";
this.dataGridTextBoxColumn2.FormatInfo = null;
this.dataGridTextBoxColumn2.HeaderText = "Имя";
this.dataGridTextBoxColumn2.MappingName = "Имя";
this.dataGridTextBoxColumn2.NullText = "(имя)";
this.dataGridTextBoxColumn2.Width = 75;
//
// dataGridTextBoxColumn3
//
this.dataGridTextBoxColumn3.Format = "";
this.dataGridTextBoxColumn3.FormatInfo = null;
this.dataGridTextBoxColumn3.HeaderText = "Фамилия";
this.dataGridTextBoxColumn3.MappingName = "Фамилия";
this.dataGridTextBoxColumn3.NullText = "(фамилия)";
this.dataGridTextBoxColumn3.Width = 75;
//
// dataGridTextBoxColumn1
//
this.dataGridTextBoxColumn1.Format = "";
this.dataGridTextBoxColumn1.FormatInfo = null;
this.dataGridTextBoxColumn1.HeaderText = "Идентификатор";
this.dataGridTextBoxColumn1.MappingName = "id";
this.dataGridTextBoxColumn1.ReadOnly = true;
this.dataGridTextBoxColumn1.Width = 75;
Как видите, программная реализация нашего несложного приложения,
позволяющего отображать и редактировать одну реляционную таблицу, получается
довольно громоздкой.
К счастью, дизайнер форм выполняет за нас всю черновую работу по созданию и
сопровождению программного кода, отвечающего за инициализацию и настройку
свойств элементов управления DataGrid и DataSet. В этом и заключается одно из
преимуществ использования такого инструментального средства, как Microsoft Visual
Studio .NET.
Работа с DataSet при помощи методов и свойств
Как мы уже говорили, элемент управления DataGrid позволяет вводить новые данные в
таблицы набора DataSet. Однако операции добавления и извлечения данных из набора
DataSet можно выполнять и с помощью специально предназначенных для этого методов
и свойств.
Добавление новой строки
Для того чтобы добавить новую строку в таблицу набора данных DataSet, Вы вначале
должны создать эту строку как объект класса DataRow, а затем добавить ее в набор
строк таблицы при помощи метода DataTable.Rows.Add.
Ниже мы показали фрагмент кода, добавленного в конструктор класса Form1, и
предназначенного для вставки в таблицу Contacts двух новых строк:
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
Теперь давайте задействуем поля ввода Имя и Фамилия, а также кнопку Добавить,
расположенные в верхней части окна нашего приложения (рис. 8-28).
Создайте следующий обработчик события Click для кнопки Добавить:
private void button1_Click(object sender, System.EventArgs e)
{
DataRow row = contactsDataTable.NewRow();
row["Имя"] = textBoxFName.Text;
row["Фамилия"] = textBoxLName.Text;
contactsDataTable.Rows.Add(row);
}
Здесь мы предполагаем, что идентификатор поля Имя хранится в textBoxFName, а
поля Фамилия — в textBoxLName.
Удаление строки
Вы можете удалять выделенные строки таблицы при помощи клавиши Delete. Однако
есть и другая возможность — использование метода DataTable.Rows.Delete.
Добавьте в главное окно нашего приложения кнопку Удалить (рис. 8-29). Эта
кнопка будет удалять запись, выделенную в окне элемента управления DataGrid.
Рис. 8-29. Добавлена кнопка для удаления выделенной строки
Для первого столбца задайте в свойстве Text заголовок Имя, для второго —
Фамилия, и, наконец, для третьего — id.
Назначение столбцов Имя и Фамилия очевидно — в них будут отображаться имена
и фамилии людей, чьи телефонные номера хранятся в базе данных. Что же касается
столбца id, то он предназначен для отображения уникальных идентификаторов строк в
таблице Contacts, хранящей имена и фамилии.
Задайте идентификаторы столбцов Имя и Фамилия равными, соответственно,
fnColumnHeader и lnColumnHeader.
Кроме этого, переключите элемент управления ListView в режим отображения
детализированной таблицы. Для этого задайте свойству View значение Details.
Операции добавления, изменения и редактирования имен и фамилий мы будем
выполнять при помощи кнопок Добавить, Изменить и Удалить. Перетащите значки этих
кнопок из инструментальной панели Microsoft Visual Studio .NET и расположите их
справа от списка просмотра имен и фамилий.
Элементы управления для работы со списком телефонов
В нижней части окна приложения PhoneBookApp необходимо расположить элементы
управления, предназначенные для просмотра и редактирования списка телефонов
человека, имя которого выделено в верхнем списке.
Это поле редактирования текстовой информации с меткой Телефон, поле класса
Label для просмотра имени и фамилии человека, телефоны которого в настоящий
момент просматриваются или редактируются, список для просмотра телефонов, а также
кнопки Добавить, Изменить и Удалить. С помощью кнопок пользователь сможет
отредактировать список телефонов.
Поле редактирования номера телефона Телефон должно иметь идентификатор
phoneTextBox. Задайте этот идентификатор, отредактировав соответствующим образом
свойство Name.
Что касается поля Label для просмотра имени и фамилии человека, список
телефонов которого просматривается или редактируется, то оно должно иметь
идентификатор currentContactLabel. Настройте для него свойство BorderStyle, выбрав
значение Fixed3D, а также свойство TextAlign, задав выравнивание MiddleLeft. Вы
можете также изменить шрифт, которым будет отображаться имя и фамилия, при
помощи свойства Font.
Далее Вам необходимо добавить в окно приложения еще один элемент
управления ListView, предназначенный для отображения списка телефонов. Этот
элемент управления должен иметь идентификатор listView2.
Переключите список listView2 в режим отображения детализированной таблицы,
назначив свойству View значение Details, а также добавьте три столбца с названиями
Телефон, id и idContacts (рис. 8-32).
Создайте в таблице Contacts три столбца id, Имя и Фамилия, как это показано на
рис. 8-34.
Столбец id должен играть роль первичного ключа (такого же, как и в таблице
Contacts). Настройка свойств этого столбца показана на рис. 8-35.
Столбец Телефон хранит телефонные номера в виде обычных текстовых строк и
не имеет никаких особенностей.
Что же касается столбца idContacts, то он хранит так называемые внешние ключи
(foreign keys) таблицы Contacts. Таким образом, каждая строка таблицы Phones с
номером телефона содержит идентификатор строки таблицы Contacts с именем и
фамилией владельца этого телефона.
Очевидно, что если у человека имеется несколько телефонных номеров, то в
таблице Phones для него будет создано несколько строк с одинаковыми значениями в
ячейках столбца idContacts.
На рис. 8-36 мы показали настройку свойств для столбца idContacts.
Рис. 8-36. Свойства столбца idContacts таблицы Phones
Как видно из этого рисунка, столбец должен хранить целые числа типа
System.Int32. За это отвечает свойство DataType.
Мы не разрешаем хранение в столбце idContacts пустых значений, т.к. если в базе
данных есть телефон, то он обязательно должен кому-нибудь принадлежать. Поэтому
мы приравниваем значение свойства AllowDBNull константе false.
Связывание таблиц Contacts и Phones
Элемент управления DataSet имеет свойство Relations, специально предназначенное
для создания отношений между таблицами, входящими в набор данных.
Отредактируйте свойство Relations. Когда редактор отношений запускается в
первый раз для набора данных, в котором еще нет отношений, на экране появляется
диалоговое окно Relation, показанное на рис. 8-37.
Рис. 8-37. Создание нового отношения
listUpdate();
phonesUpdate();
}
}
Здесь мы вначале убеждаемся в том, что пользователь ввел как имя, так и
фамилию. Для этого мы сравниваем содержимое свойства Text полей редактирования
fnTextBox и lnTextBox с пустой строкой. Если какое-либо поле пустое, обработчик
события завершает свою работу без выполнения других дополнительных действий.
В том случае, когда пользователь ввел все необходимые данные, обработчик
события создает новую строку в таблице Contacts:
DataRow row = contactsDataTable.NewRow();
Для этого используется метод DataTable.NewRow.
На следующем этапе введенные строки имени и фамилии записываются в
соответствующие столбцы строки:
row["Имя"] = fnTextBox.Text;
row["Фамилия"] = lnTextBox.Text;
Подготовленная таким способом строка добавляется в таблицу:
contactsDataTable.Rows.Add(row);
Далее наш обработчик события последовательно вызывает два метода с именами
listUpdate и phonesUpdate. Первый из них отображает текущее содержимое таблицы
Contacts в окне верхнего списка, а второй — список телефонов человека, имя которого
было выбрано в верхнем списке. Мы вернемся к детальному описанию этих функций
немного позже.
Выбор записи в списке имен и фамилий
Для отслеживания контактов, выбираемых пользователем в верхнем списке, мы
предусмотрели обработчик события listView1_SelectedIndexChanged:
private void listView1_SelectedIndexChanged(object sender,
System.EventArgs e)
{
try
{
int currentRowIndex = listView1.SelectedItems[0].Index;
fnTextBox.Text = row["Имя"].ToString();
lnTextBox.Text = row["Фамилия"].ToString();
currentContactId = (int)row["id"];
phonesUpdate();
}
Когда пользователь выделяет новую строку в списке, этот обработчик вначале
определяет индекс строки, которая стала выделенной в результате выполнения этой
операции:
int currentRowIndex = listView1.SelectedItems[0].Index;
Далее он извлекает соответствующую строку из таблицы Contacts, а также
получает значения столбцов извлеченной строки:
fnTextBox.Text = row["Имя"].ToString();
lnTextBox.Text = row["Фамилия"].ToString();
currentContactId = (int)row["id"];
Имя и фамилия при этом записывается в поля редактирования fnTextBox и
lnTextBox для того чтобы пользователь смог просмотреть их и при необходимости
отредактировать.
Что же касается идентификатора строки id, то он сохраняется в поле
currentContactId класса Form1. Создайте это поле следующим образом:
private int currentContactId;
Дополнительно обработчик события listView1_SelectedIndexChanged записывает
имя и фамилию текущего контакта в поле, расположенное непосредственно над
списком телефонов (чтобы пользователю было ясно, чьи телефоны отображаются в
этом списке):
currentContactLabel.Text = fnTextBox.Text + " "
+ lnTextBox.Text;
Перед тем как завершить свою работу, обработчик события вызывает метод
phonesUpdate для обновления списков телефонов выделенного контакта.
Редактирование записей таблицы Contacts
Для того чтобы отредактировать имя или фамилию человека, нужно выделить его имя в
верхнем списке, содержащем записи таблицы Contacts. После этого только что
рассмотренный обработчик события listView1_SelectedIndexChanged запишет имя и
фамилию в текстовые поля редактирования Имя и Фамилия, соответственно.
Отредактировав имя или фамилию, пользователь должен щелкнуть кнопку
Изменить. Ниже мы привели исходный текст обработчика событий для этой кнопки,
изменяющего содержимое ячеек соответствующей строки в таблице Contacts:
private void button2_Click(object sender, System.EventArgs e)
{
if(fnTextBox.Text != "" && lnTextBox.Text != "")
{
string filter = "id='" + currentContactId.ToString() + "'";
DataRow[] contactsRows = contactsDataTable.Select(filter);
contactsRows[0].BeginEdit();
contactsRows[0]["Имя"] = fnTextBox.Text;
contactsRows[0]["Фамилия"] = lnTextBox.Text;
contactsRows[0].EndEdit();
contactsDataTable.AcceptChanges();
listUpdate();
}
}
Получив управление, наш обработчик убеждается в том, что пользователь задал
не непустую строку имени или фамилии. Если это так, обработчик начинает процедуру
обновления таблицы Contacts.
Для этого он вначале делает выборку из таблицы Contacts с помощью
специального фильтра и метода DataTable.Select. Текстовая строка фильтра, задающая
условие выборки, передается методу DataTable.Select в качестве параметра. Она
определяет условие отбора строк из таблицы. Таким образом, метод DataTable.Select
напоминает по своему назначению оператор SELECT языка запросов SQL, хотя,
конечно, он не обладает всей мощностью этого оператора.
Метод Select возвращает записи в виде массива строк класса DataRow, которые
можно обрабатывать в цикле или индивидуально с указанием индекса.
В нашем случае строка фильтра имеет следующий вид:
id=’<содержимое поля currentContactId>’
Напомним, что поле currentContactId класса Form1 содержит текущий
идентификатор строки таблицы Contacts, выделенной пользователем в списке имен и
фамилий. Это поле обновляется обработчиком событий listView1_SelectedIndexChanged,
когда пользователь выделяет новую строку в упомянутом списке.
Так как нам нужно обновить имя и фамилию человека, чье имя выделено в
списке, то из таблицы Contacts требуется выбрать такие строки, содержимое столбца id
которых равно текущему значению поля currentContactId. В общем случае таких строк
может быть несколько. Мы, однако, будем изменять только первую строку.
Вот как формируется и применяется строка фильтра в нашем приложении:
string filter = "id='" + currentContactId.ToString() + "'";
DataRow[] contactsRows = contactsDataTable.Select(filter);
Обратите внимание, что значение, которому должно быть равно содержимое
столбца id, заключено в одинарные кавычки.
После выполнения метода Select мы получим набор ссылок на строки
contactsRows, удовлетворяющих нашему условию. Чтобы обновить содержимое одной
выбранной строки, записав в нее новое имя и фамилию, мы используем следующий
фрагмент кода:
contactsRows[0].BeginEdit();
contactsRows[0]["Имя"] = fnTextBox.Text;
contactsRows[0]["Фамилия"] = lnTextBox.Text;
contactsRows[0].EndEdit();
Перед началом внесения изменений в ячейки строки мы вызываем метод
BeginEdit, который отключает механизм проверки целостности для связанных таблиц.
Когда все изменения внесены, этот механизм вновь включается при помощи метода
EndEdit.
Далее мы вызываем метод AcceptChanges, который вносит сделанные изменения в
таблицу Contacts, и обновляем содержимое списка контактов при помощи метода
listUpdate:
contactsDataTable.AcceptChanges();
listUpdate();
Удаление записей таблицы Contacts
Для того чтобы удалить строку, выделенную пользователем в списке контактов, нужно
воспользоваться кнопкой Удалить. Ниже мы привели исходный текст метода
button3_Click, выполняющего обработку событий от этой кнопки:
private void button3_Click(object sender, System.EventArgs e)
{
try
{
string filter = "idContacts='" +
listView1.SelectedItems[0].Tag.ToString() + "'";
contactsDataTable.Rows[
listView1.SelectedItems[0].Index].Delete();
contactsDataTable.AcceptChanges();
}
catch(Exception ex)
{
}
listUpdate();
phonesUpdate();
}
Собственно удаление записи, соответствующей выделенной строке, выполняет
следующий фрагмент кода:
contactsDataTable.Rows[listView1.SelectedItems[0].Index].Delete();
contactsDataTable.AcceptChanges();
Так как таблицы Contacts и Phones связаны между собой, то перед удалением
записи из таблицы Contacts нужно удалить все строки таблицы Phones, связанные с
ней. Т.е. если мы удаляем запись об имени и фамилии человека, то перед этим нужно
удалить все телефоны этого человека.
При связывании таблиц Contacts и Phones мы выбрали в списке Update rule
значение Cascade, задающее режим автоматического внесения изменений в дочернюю
таблицу при изменении записей родительской таблицы(рис. 8-37). Поэтому при
удалении строк родительской таблицы происходит обновление соответствующих строк
дочерней таблицы.
Тем не менее, для примера мы приводим фрагмент кода, который удаляет все
строки таблицы Phones, связанные с удаляемой строкой таблицы Contacts:
string filter = "idContacts='" +
listView1.SelectedItems[0].Tag.ToString() + "'";
phonesDataTable.AcceptChanges();
Как видите, мы здесь вначале с помощью фильтра отбираем нужные записи из
таблицы Phones, а потом удаляем их при помощи известного Вам метода Delete. Метод
AcceptChanges завершает процедуру удаления.
Добавление номера телефона
Выделив в верхнем списке имя человека, пользователь может добавить для него один
или несколько телефонов. Эта операция выполняется при помощи кнопки Добавить,
расположенной возле списка телефонов.
Вот обработчик событий для этой кнопки:
private void button4_Click(object sender, System.EventArgs e)
{
if(phoneTextBox.Text != "")
{
try
{
DataRow rowPhone = phonesDataTable.NewRow();
rowPhone["idContacts"] = currentContactId;
rowPhone["Телефон"] = phoneTextBox.Text;
phonesDataTable.Rows.Add(rowPhone);
}
catch (Exception ex)
{
}
phonesUpdate();
}
}
Здесь мы вначале проверяем, что поле нового телефона phoneTextBox не пустое.
Если это так, то мы создаем в таблице Phones новую строку как объект класса DataRow,
вызывая для этого метод phonesDataTable.NewRow.
Номер добавляемого телефона мы сохраняем в столбце Телефон. Что же касается
столбца idContacts, то мы записываем в него идентификатор строки таблицы Contact.
Это та самая строка, которая содержит имя, выбранное пользователем в списке
контактов.
Заполненная строка добавляется в таблицу Phones методом
phonesDataTable.Rows.Add.
И, наконец, после добавления строки наш обработчик событий обновляет
содержимое списка телефонов в окне программы методом phonesUpdate.
Удаление номера телефона
Для удаления номера телефона предназначена кнопка Удалить, расположенная возле
списка телефонов.
Вот обработчик событий для этой кнопки:
private void button6_Click(object sender, System.EventArgs e)
{
try
{
string filter =
"id='" + listView2.SelectedItems[0].Tag.ToString() + "'";
phonesDataTable.AcceptChanges();
phonesUpdate();
}
Прежде всего, наш обработчик определяет идентификатор строки таблицы
Phones, подлежащей удалению. Напомним, что этот идентификатор хранится в столбце
id упомянутой таблицы. При формировании списка listView2 идентификаторы
соответствующих строк сохраняются в свойствах Tag элементов этого списка.
Чтобы стереть строку, мы создаем фильтр filter, при помощи которого мы сможет
получить первую строку из списка выделенных строк. Далее при помощи метода
phonesDataTable.Select мы выделяем нужную строку и затем удаляем ее методом Delete.
Окончательные изменения в таблицу Phones вносятся методом
phonesDataTable.AcceptChanges, после чего методом phonesUpdate выполняется
обновление списка телефонов.
Изменение номера телефона
Для изменения номера телефона используется кнопка Изменить, расположенная справа
от списка телефонов. исходный текст обработчика событий для этой кнопки
представлен ниже:
private void button5_Click(object sender, System.EventArgs e)
{
if(phoneTextBox.Text != "")
{
try
{
string filter = "id='" +
listView2.SelectedItems[0].Tag.ToString() + "'";
DataRow[] phones = phonesDataTable.Select(filter);
phones[0].BeginEdit();
phones[0]["Телефон"] = phoneTextBox.Text;
phones[0].EndEdit();
}
catch (Exception ex)
{
}
phonesDataTable.AcceptChanges();
phonesUpdate();
}
}
Если строка нового номера телефона phoneTextBox не пуста, то обработчик
события button5_Click выделяет с помощью фильтра нужную строку в таблице Phones и
затем изменяет содержимое ячейки в столбце Телефон.
Данная операция выполняется аналогично операции изменения содержимого
строк таблицы Contacts, описанной ранее в разделе «Редактирование записей таблицы
Contacts».
Обновление списка контактов
При описании исходных текстов приложения PhoneBookApp мы неоднократно
упоминали метод listUpdate, обновляющий содержимое списка listView1 из таблицы
Contacts. Вот его исходный текст:
public void listUpdate()
{
listView1.Items.Clear();
if(phones.Length != 0)
{
for(int i = 0; i < phones.Length; i++)
{
ListViewItem lvi =
new ListViewItem(phones[i]["Телефон"].ToString());
lvi.SubItems.Add(phones[i]["id"].ToString());
lvi.SubItems.Add(phones[i]["idContacts"].ToString());
lvi.Tag = phones[i]["id"];
listView2.Items.Add(lvi);
}
}
}
Прежде всего, список телефонов очищается методом listView2.Items.Clear.
Далее наш обработчик должен отобрать в таблице Phones строки,
соответствующие телефонам выделенного контакта. Идентификатор этого контакта в
таблице Contacts хранится в поле currentContactId.
Отбор строк выполняется с помощью фильтра и метода Select, как это показано
ниже:
string filter = "idContacts='" + currentContactId.ToString() + "'";
DataRow[] phones = phonesDataTable.Select(filter);
Если была найдена хотя бы одна строка, запускается цикл добавления записей в
список listView2. Соответствующая процедура добавления записей в список аналогична
процедуре, использованной в только что описанном методе listUpdate.
Обратите внимание — мы сохраняем в свойстве Tag элементов списка
идентификаторы строк таблицы Phones (т.е. содержимое первичного ключа из столбца
id).
Фильтр для метода Select
В классе DataTable имеется несколько перегруженных вариантов метода Select, с
помощью которых можно отбирать строки из таблицы в соответствии с различными
критериями.
Перегруженные методы Select
Простейший из них не имеет параметров и возвращает все записи таблицы в порядке,
определяемым первичным ключом. Если в таблице нет первичного ключа, то записи
будут извлечены в порядке их добавления.
Если методу Select передается один параметр, то он задает текстовую строку
фильтра, содержащую условие отбора записей. Подробнее правила составления такого
фильтра мы рассмотрим ниже в этом разделе.
Вариант метода Select с двумя параметрами позволяет дополнительно задать
столбец, по которому нужно выполнять сортировку, а также порядок сортировки
(прямой или обратный).
Пусть, например, для Интернет-магазина, торгующего книгами, мы создали
таблицу покупок Orders. В этой таблице есть столбец seriesID, хранящий
идентификатор серии книг (таких как «Шаг за шагом», «Самоучитель» и т.д.), а также
столбец Price, в котором хранится стоимость книг.
Следующий фрагмент кода отберет книги, у которых идентификатор серии равен
567, отсортировав его в обратном порядке по стоимости:
DataTable booksTable = DataSet1.Tables["Orders"];
Визуальное проектирование
приложений C#
А.В. Фролов, Г.В. Фролов
ГЛАВА 9. ИНТЕГРАЦИЯ С MS SQL SERVER
ПРИЛОЖЕНИЕ SQLTESTAPP
Создание базы данных
Создание таблицы Contacts
Идентификатор для подключения к базе данных
Создание проекта приложения
Добавление адаптера данных SqlDataAdapter
Программный код для адаптера и соединения
Создание локального набора DataSet
Редактирование содержимого набора DataSet
Загрузка набора данных
Сохранение отредактированного набора данных
ПАРОЛЬНЫЙ ДОСТУП К СИСТЕМЕ
Приложение LoginApp
Таблица Users
Создание проекта приложения
Соединение с базой данных
Запросы SQL
Адаптер SqlDataAdapter
Элемент управления DataGrid
Подключение пользователя
Обновление таблицы Users
ХРАНЕНИЕ ДЕРЕВА В БАЗЕ ДАННЫХ
Приложение ArticlesApp
База данных Articles
Таблица Tree
Таблица Documents
Хранимая процедура sp_InsertDocument
Хранимая процедура sp_ InsertNode
Хранимая процедура sp_ UpdateDocument
Создание проекта приложения ArticlesApp
Соединение с базой данных
Добавление адаптера SqlDataAdapter
Создание набора данных DataSet
Добавление контекстного меню
СОЗДАНИЕ УЗЛА ДЕРЕВА
Метод AddNode
Диалоговое окно для ввода данных добавляемого узла
Открытие и закрытие соединения с базой данных
Использование хранимых процедур
Параметры хранимых процедур
Запуск хранимой процедуры
Получение значений выходных параметров
Добавление текста документа
Диалоговая форма редактирования документа
Свойство Title
Свойство Weight
Свойство Document
Построение дерева
Метод UpdateTree
Метод CreateNodes
РЕДАКТИРОВАНИЕ УЗЛА ДЕРЕВА
Обработчик событий меню Edit
Извлечение идентификатора редактируемого узла
Извлечение данных редактируемого узла дерева
Получение текст редактируемой статьи
Извлечение заголовка и веса сортировки
Обновление информации узла в базе данных
УДАЛЕНИЕ УЗЛА ДЕРЕВА
ОТСЛЕЖИВАНИЕ ПЕРЕМЕЩЕНИЙ ПО ДЕРЕВУ
Далее, выберите из списка Identity для столбца id значение Yes (как это показано
на рис. 9-6). В результате столбец id будет содержать уникальные идентификаторы
строк таблицы.
Кроме того, измените значение параметра Identity Seed, задающее начальное
значение столбца id, с 1 на 0. Именно такое значение используется по умолчанию для
идентификации строк таблиц, хранящихся в наборе данных DataSet.
Отредактировав параметры таблицы, сохраните ее структуру, щелкнув кнопку с
изображением дискеты. Эта кнопка находится в левом верхнем углу окна редактора. В
результате на экране появится диалоговое окно Choose Name, где Вам нужно будет
указать имя таблицы (рис. 9-7). Наша таблица должна называться Contacts.
Нажмите здесь кнопку OK, а затем закройте окно редактора структуры таблицы.
Ниже мы привели сценарий SQL, который создает нужную нам таблицу Contacts
при помощи команд SQL:
CREATE TABLE [dbo].[Contacts] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[FirstName] [varchar] (50) COLLATE Cyrillic_General_CI_AS NULL ,
[LastName] [varchar] (50) COLLATE Cyrillic_General_CI_AS NULL
) ON [PRIMARY]
GO
В списке Select or enter a server name Вы должны указать сервер SQL, к которому
будет подключаться Ваше приложение. Если это тот сервер работает на том же
компьютере, что и приложение, укажите здесь строку localhost.
Далее, отметьте флажок Use a specific user name and password, после чего введите
в поле User name строку c_sharp_app. Задайте также в поле Password пароль
пользователя c_sharp_app.
Чтобы позволить приложению сохранить пароль, отметьте флажок Allow saving
password. Избегайте установки этого флажка, если к безопасности предъявляются
повышенные требования.
Далее отметьте флажок Select the database on the server и выберите в списке,
расположенном под этим флажком, нашу базу данных PhoneBook.
На этом создание соединения с источником данных завершено. Обязательно
проверьте его работоспособность, щелкнув кнопку Test Connection. Если все
нормально, Вы получите сообщение с текстом Test connection succeed.
Проверив соединение, щелкните кнопку OK. На экране появится диалоговое окно
SQL Server Login (рис. 9-19).
В поле Password этого окна Вам нужно ввести пароль пользователя c_sharp_app, а
затем щелкнуть кнопку OK. Теперь в списке соединений появится название нового,
только что созданного Вами соединения (рис. 9-20).
Рис. 9-20. Выбрано созданное и проверенное соединение
Нужного нам результата можно добиться, просто отметив флажки всех строк
таблицы Contacts.
Щелкнув кнопку Next в окне мастера, показанном на рис. 9-22, Вы перейдете к
финальному окну, показанному на рис. 9-24.
this.sqlInsertCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@LastName",
System.Data.SqlDbType.VarChar, 50, "LastName"));
При этом каждый параметр команды SQL создается в виде объекта класса
System.Data.SqlClient.SqlParameter.
В качестве первого параметра (извиняемся за тавтологию) конструктору
передается имя параметра команды SQL, снабженное префиксом @. Второй параметр
задает тип данных, третий — размер данных, и, наконец, четвертый — имя
соответствующего столбца в источнике данных. Заметим, что в классе
System.Data.SqlClient.SqlParameter есть еще несколько перегруженных конструкторов,
описание которых Вы найдете в документации.
Команда обновления данных также имеет параметры:
//
// sqlUpdateCommand1
//
this.sqlUpdateCommand1.CommandText =
"UPDATE dbo.Contacts SET FirstName = @FirstName, LastName = @LastName WHERE (id
="+
"@Original_id) AND (FirstName = @Original_FirstName) AND (LastName = @Original_La"
+
"stName); SELECT id, FirstName, LastName FROM dbo.Contacts WHERE (id = @id)";
this.sqlUpdateCommand1.Connection = this.sqlConnection1;
this.sqlUpdateCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@FirstName",
System.Data.SqlDbType.VarChar, 50, "FirstName"));
this.sqlUpdateCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@LastName",
System.Data.SqlDbType.VarChar, 50, "LastName"));
this.sqlUpdateCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@id",
System.Data.SqlDbType.Int, 4, "id"));
this.sqlUpdateCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@Original_id",
System.Data.SqlDbType.Int, 4,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "id", System.Data.DataRowVersion.Original,
null));
this.sqlUpdateCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@Original_FirstName",
System.Data.SqlDbType.VarChar, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "FirstName",
System.Data.DataRowVersion.Original, null));
this.sqlUpdateCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@Original_LastName",
System.Data.SqlDbType.VarChar, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "LastName",
System.Data.DataRowVersion.Original, null));
Как видите, для команды обновления требуется больше параметров. Наряду с
параметрами, задающими новые значения для ячеек обновляемой строки @FirstName,
@LastName и @id, необходимо передать исходные значения упомянутых ячеек
посредством параметров @Original_FirstName, @Original_LastName и @Original_id.
Для передачи параметров с исходными значениями используется перегруженный
вариант конструктора класса System.Data.SqlClient.SqlParameter, имеющий 10
параметров.
В частности, четвертый параметр задает направление передачи данных как
System.Data.ParameterDirection.Input. Таким образом, здесь добавляются не выходные,
а входные параметры. После выполнения команды эти параметры будут содержать
исходные значения ячеек, какими они были до выполнения команды.
Команда удаления также имеет входные параметры:
//
// sqlDeleteCommand1
//
this.sqlDeleteCommand1.CommandText =
"DELETE FROM dbo.Contacts WHERE (id = @Original_id) AND (FirstName = @Original_Fir"
+
"stName) AND (LastName = @Original_LastName)";
this.sqlDeleteCommand1.Connection = this.sqlConnection1;
this.sqlDeleteCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@Original_id",
System.Data.SqlDbType.Int, 4,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "id", System.Data.DataRowVersion.Original,
null));
this.sqlDeleteCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@Original_FirstName",
System.Data.SqlDbType.VarChar, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "FirstName",
System.Data.DataRowVersion.Original, null));
this.sqlDeleteCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@Original_LastName",
System.Data.SqlDbType.VarChar, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "LastName",
System.Data.DataRowVersion.Original, null));
Через эти параметры команда передает исходные значения ячеек удаляемой
строки.
Команды и параметры удобно редактировать не в исходном тексте приложения, а
при помощи соответствующих графических средств.
Для редактирования параметров, например, команды обновления данных,
раскройте список этих параметров в окне свойств объекта sqlDataAdapter1, как это
показано на рис. 9-26.
Вам нужно отметить флажок New. При необходимости Вы также можете изменить
имя набора данных в поле, расположенном справа от этого флажка.
Убедитесь также, что в списке Choose which table(s) to add to the dataset,
определяющим таблицы, добавляемые в набор данных, выбрана таблица Contacts.
Отметьте также флажок Add this dataset to the designer.
После того как Вы щелкните кнопку OK, будет создан и добавлен в проект
приложения набор данных dataSet11.
Помимо этого к проекту приложения будет добавлена схема набора данных в виде
файла DataSet1.xsd. Этот файл содержит описание таблицы на языке XML.
Если попытаться открыть файл DataSet1.xsd для редактирования, то в окне
Microsoft Visual Studio .NET эта схема появится в виде таблицы (рис. 9-30).
Если в таблице Users уже есть хотя бы одна запись, необходимо ввести
правильный идентификатор и пароль. В этом случае содержимое таблицы Users будет
отображено в окне приложения с помощью адаптера SqlDataAdapter и элемента
управления DataGrid.
Помимо идентификаторов и паролей пользователей таблица Users хранит код
доступа каждого пользователя. В нашем приложении пользователям с кодом доступа 80
разрешается модифицировать таблицу Users, а всем остальным пользователям —
только просматривать содержимое этой таблицы.
Для сохранения изменений в таблице Users, расположенной на сервере SQL, мы
предусмотрели кнопку Обновить. Эта кнопка, однако, будет разблокирована только в
том случае, если код доступа пользователя равен 80.
Таблица Users
Прежде чем приступить к созданию приложения LoginApp Вам нужно будет создать в
базе данных PhoneBook (с которой мы работали раньше) новую таблицу Users.
В таблице Users нужно предусмотреть столбцы id, Login, Password и assess. На
рис. 9-34 мы показали таблицу Users в окне дизайнера таблиц утилиты SQL Server
Enterprise Manager.
Рис. 9-34. Таблица Users
try
{
sqlConnection1.Open();
myReader = sqlCommand1.ExecuteReader();
if(!myReader.Read())
{
sqlConnection1.Close();
myReader.Close();
CreateAdminLogin();
}
else
{
myReader.Close();
sqlCommand2.Parameters["@login"].Value = LoginTextBox.Text;
sqlCommand2.Parameters["@password"].Value =
passwordTextBox.Text;
myReader = sqlCommand2.ExecuteReader();
if(!myReader.Read())
{
MessageBox.Show("Доступ запрещен", "Ошибка");
myReader.Close();
sqlConnection1.Close();
return;
}
myReader.Close();
dataSet11.Clear();
sqlDataAdapter1.Fill(dataSet11);
}
else
{
MessageBox.Show("Доступ запрещен", "Ошибка");
}
myReader.Close();
sqlConnection1.Close();
}
}
catch(Exception ex)
{
MessageBox.Show(ex.Message, "Ошибка");
}
}
Рассмотрим работу этого обработчика в деталях.
Прежде всего, заметим, что мы применили здесь класс SqlDataReader. Этот класс
очень удобен для извлечения результатов запросов SQL в режиме последовательного
чтения.
Получив управление, обработчик события button1_Click открывает соединение
sqlConnection1, о котором мы говорили раньше:
sqlConnection1.Open();
Параметры этого соединения мы задавали во время проектирования нашего
приложения.
Далее наш обработчик событий исполняет команду SQL, хранящуюся в объекте
sqlCommand1:
SqlDataReader myReader;
…
myReader = sqlCommand1.ExecuteReader();
В результате выполнения метода ExecuteReader создается объект класса
SqlDataReader, позволяющий последовательно прочитать все строки результата
выполнения команды SQL.
Напомним, что эта команда выбирает все строки таблицы Users, возвращая
столбцы id, login, password и access. Для выборки нужно использовать метод
SqlDataReader.Read, вызывая его в цикле.
Когда все строки, полученные в результате выполнения команды SQL, будут
получены, метод SqlDataReader.Read возвратит значение false. Мы используем это
обстоятельство в самом начале работы обработчика событий button1_Click, для того
чтобы определить, имеются ли в таблице Users какие-либо записи:
if(!myReader.Read())
{
sqlConnection1.Close();
myReader.Close();
CreateAdminLogin();
}
else
{
…
}
Если таблица Users пустая (как бывает при самом первом запуске приложения),
обработчик закрывает соединение методом sqlConnection1.Close, а также закрывает
объект SqlDataReader. После этого он вызывает метод CreateAdminLogin,
предназначенный для создания самой первой учетной записи администратора в
таблице Users. Исходный текст этого метода мы рассмотрим чуть позже.
Теперь мы рассмотрим ситуацию, когда в таблице Users уже имеются какие-то
записи. Вы можете добавить новые записи в эту таблицу, например, вручную с
помощью утилиты Microsoft SQL Server Enterprise Manager.
Прежде всего, в этом случае мы закрываем ненужный нам больше объект
SqlDataReader:
myReader.Close();
Теперь нам нужно выполнить команду sqlCommand2, которая выбирает из
таблицы Users учетные записи с заданным идентификатором пользователя и паролем.
Перед выполнением этой команды необходимо подготовить параметры @login и
@password, воспользовавшись для этого свойством Value контейнера
sqlCommand2.Parameters:
sqlCommand2.Parameters["@login"].Value = LoginTextBox.Text;
sqlCommand2.Parameters["@password"].Value = passwordTextBox.Text;
myReader = sqlCommand2.ExecuteReader();
В результате выполнения этого запроса будет создан объект класса
SqlDataReader. Если таблица Users не содержит ни одной записи с заданным
идентификатором пользователя и паролем, то метод myReader.Read вернет значение
false:
if(!myReader.Read())
{
MessageBox.Show("Доступ запрещен", "Ошибка");
myReader.Close();
sqlConnection1.Close();
return;
}
Это означает, что был задан неправильный идентификатор или пароль
пользователя. В этом случае наш обработчик событий выводит сообщение об отказе в
доступе, закрывает объект myReader и соединение sqlConnection1, а затем возвращает
управление.
Теперь мы рассмотрим случай, когда идентификатор и пароль был введен
правильно.
Наша программа выполняет дополнительную проверку, сравнивая данные из
ячеек строки, считанной методом myReader.Read, со значениями, введенными
пользователем в полях Идентификатор и Пароль:
if(LoginTextBox.Text == myReader.GetString(1) &&
passwordTextBox.Text == myReader.GetString(2))
{
…
}
else
{
MessageBox.Show("Доступ запрещен", "Ошибка");
}
myReader.Close();
sqlConnection1.Close();
Обратите внимание на то, как мы извлекаем значения ячеек прочитанной
строки — для получения текстовой строки мы вызываем метод myReader.GetString,
указывая ей в качестве параметра индекс столбца. Самый первый столбец нашей
таблицы id имеет индекс 0, второй столбец Login — индекс 1 и т.д.
В табл. 9-2 мы перечислили некоторые методы класса SqlDataReader,
предназначенные для извлечения из ячеек строки данных различных типов,
встроенных в язык программирования C#.
Таблица 9-2. Методы класса SqlDataReader для извлечения содержимого ячеек
строки стандартных типов данных
Имя метода Тип возвращаемого значения
GetBoolean bool
GetByte byte
GetChar char
GetDateTime DateTime
GetDecimal decimal
GetDouble double
GetFloat float
GetGuid Guid
GetInt16 short
GetInt32 int
Getint64 long
GetString string
Метод GetValue позволяет получить данные в естественном формате, т.е. в том
формате, в котором они хранятся в ячейке строки.
Специально для работы с серверами баз данных, такими как Microsoft SQL Server,
этот набор был расширен методами, возвращающими данные в форматах этих
серверов. Некоторые из этих методов перечислены в табл. 9-3.
Таблица 9-3. Методы класса SqlDataReader для извлечения содержимого ячеек
строки типов данных серверов SQL
Имя метода Тип Описание
возвращаемого
значения
GetSqlBinary SqlBinary Поток двоичных данных переменной
длины
GetSqlBoolean SqlBoolean Целое значение 0 или 1
GetSqlByte SqlByte Целое значение без знака размером 8
разрядов и значением от 0 до 255
GetSqlDateTime SqlDateTime Дата и время, исчисляемое в периодах
системного таймера от 1 января 1753
года
GetSqlDecimal SqlDecimal Числовое значение с фиксированной
точкой в диапазоне от -1038 -1 до 10
38 –1
GetSqlDouble SqlDouble Числовое значение с плавающей
точкой в диапазоне от -1.79E +308 до
1.79E +308
GetSqlGuid SqlGuid Глобальный уникальный
идентификатор GUID
GetSqlInt16 SqlInt16 16-разрядное целое со знаком
GetSqlInt32 SqlInt32 32-разрядное целое со знаком
GetSqlInt64 SqlInt64 64-разрядное целое со знаком
GetSqlMoney SqlMoney Денежные суммы в диапазоне от -263
(-922 337 203 685 477,5808) до 2 63 -1
(+922 337 203 685 477,5807)
GetSqlSingle SqlSingle Числовое значение с плавающей
точкой в диапазоне от -3.40E +38 до
3.40E +38
GetSqlString SqlString Поток символов переменного размера
Вернемся к нашему приложению.
Итак, обработчик события button1_Click, создаваемого кнопкой Войти, определил,
что пользователь ввел правильный идентификатор и пароль. Теперь ему нужно
получить код доступа ткущего пользователя. Он извлекает этот код из столбца access,
имеющего индекс 3, при помощи метода GetInt32:
if(myReader.GetInt32(3) != 80)
{
dataGrid1.ReadOnly = true;
button2.Enabled = false;
}
else
{
dataGrid1.ReadOnly = false;
button2.Enabled = true;
}
Если код доступа не равен 80, обработчик события приравнивает свойству
dataGrid1.ReadOnly значение true. Это запрещает редактирование строк, отображаемых
в окне элемента управления DataGrid. Кроме этого, обработчик блокирует кнопку
Обновить, приравнивая свойству button2.Enabled значение false.
В том случае, когда код доступа пользователя равен 80, обработчик события
разрешает редактирование строк и разблокирует кнопку Обновить, изменяя
соответствующим образом свойства dataGrid1.ReadOnly и button2.Enabled.
Завершив проверку прав доступа пользователя, обработчик событий закрывает
объект myReader, т.к. он больше не понадобится:
myReader.Close();
Дальнейшие действия обработчика button1_Click заключаются в очистке
содержимого набора данных dataSet11 с последующим его наполнением из таблицы
Users сервера SQL:
dataSet11.Clear();
sqlDataAdapter1.Fill(dataSet11);
Эта работа выполняется с помощью адаптера sqlDataAdapter1. Соответствующие
процедуры были подробно описаны в разделе «Приложение SQLTestApp» этой главы.
Обновление таблицы Users
Для обновления таблицы Users, отредактированной пользователем с кодом доступа 80,
мы применяем адаптер sqlDataAdapter1 и метод Update. Вот исходный текст
обработчика событий кнопки Обновить:
private void button2_Click(object sender, System.EventArgs e)
{
try
{
sqlDataAdapter1.Update(dataSet11);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message, "Ошибка");
}
}
Точно такая же методика обновления применялась и для таблицы Contacts в
упомянутом ранее приложении SQLTestApp.
Хранение дерева в базе данных
Очень часто возникает задача хранения информации, организованной иерархически.
Реляционные базы данных, такие как Microsoft SQL Server, позволяют хранить
информацию в виде иерархического дерева, причем для представления такого дерева
нужна всего одна таблица.
Каждая строка таблицы, хранящей структуру дерева, соответствует одному узлу
этого дерева. При этом в таблице должно быть, как минимум, два столбца. Первый из
них должен содержать уникальные идентификаторы строки (т.е. идентификаторы
узлов), а второй — идентификатор соответствующего родительского узла. Для
корневого узла в качестве идентификатора родительского узла обычно используется
нулевое или какое-либо другое особое значение.
Для демонстрации способа хранения дерева в базе данных, а также для того
чтобы на конкретном примере изучить некоторые новые для нас методы работы с
базами данных в приложениях C#, разработали приложение ArticlesApp. Это
приложение будет подробно рассмотрено в следующем разделе.
Приложение ArticlesApp
Приложение ArticlesApp представляет собой простейшую информационную систему,
предназначенную для хранения текстовой информации. В базе данных этой системы
хранятся статьи, организованные иерархическим образом.
Главное окно приложения ArticlesApp показано на рис. 9-42.
При помощи этого окна можно добавить в базу данных новую статью, определив
для нее заголовок, тело и вес сортировки.
Если в дереве нет ни одного элемента, то при первом использовании строки Add
контекстного меню в дерево будет добавлен корневой элемент.
Для того чтобы добавить в дерево дочерний элемент, нужно вначале выделить
левой клавишей мыши заголовок родительского элемента, а потом, щелкнув этот
заголовок правой клавишей мыши, выбрать из контекстного меню строку Add.
Редактирование любого элемента выполняется аналогично. Для выполнения этой
операции нужно выделить элемент, а затем, щелкнув его правой клавишей мыши,
выбрать из контекстного меню строку Edit.
С помощью строки Delete можно удалить элемент дерева. Заметим, что программа
удаляет только элементы, не имеющие дочерних элементов. Попытки удалить элемент с
дочерними элементами наше приложение игнорирует.
Для чего нужен вес сортировки?
Мы используем его для управления порядком размещения статей, находящихся на
одном уровне иерархии. Чем этот вес меньше, тем статья располагается выше в окне
дерева. Использование какой-либо другой сортировки, например, сортировки по
алфавиту, для решения задачи упорядочивания заголовков статей неэффективно, т.к.
только тот, кто создает базу данных статей, знает, как нужно располагать заголовки.
База данных Articles
Прежде чем создавать проект приложения ArticlesApp, подготовим базу данных Articles.
В этой базе нам нужно создать две таблицы и три хранимые процедуры.
Таблица Tree
Таблица Tree предназначена для хранения структуры дерева. В ней необходимо
создать четыре столбца с именами id, parent_id, title и weight. Столбец id должен быть
первичным ключом.
Вот сценарий SQL, при помощи которого можно создать таблицу Tree:
CREATE TABLE [dbo].[Tree] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[parent_id] [int] NOT NULL ,
[title] [varchar] (50) COLLATE Cyrillic_General_CI_AS NOT NULL ,
[weight] [int] NOT NULL
) ON [PRIMARY]
GO
//
// TODO: Add any constructor code after InitializeComponent call
//
UpdateTree();
}
Методы AddNode и UpdateTree мы рассмотрим в деталях позже, а пока вернемся к
обработчику событий menuItem1_Click, созданному нами для строки Add контекстного
меню.
В том случае, если в дереве есть узлы, и пользователь выделил какой-либо узел
левой клавишей мыши или при помощи клавиатуры, наш обработчик событий
menuItem1_Click выполняет следующие действия:
if(treeView1.SelectedNode != null)
{
int id = (int)treeView1.SelectedNode.Tag;
AddNode(id);
UpdateTree();
}
Вначале он извлекает из свойства treeView1.SelectedNode.Tag идентификатор
строки таблицы Tree, соответствующий выделенному узлу. Этот идентификатор
записывается в данное свойство методом UpdateTree в процессе построения дерева.
Заметим, что данный идентификатор обозначает узел, являющийся родительским
по отношению к создаваемому узлу. Обработчик событий menuItem1_Click передает
этот идентификатор методу AddNode, а затем перерисовывает обновленное дерево
методом UpdateTree:
AddNode(id);
UpdateTree();
Метод AddNode
Создайте в классе Form1 метод AddNode. Как мы только что говорили, этот метод
предназначен для создания нового узла в дереве заголовков статей. Он не только
добавляет новый узел в окно элемента управления treeView1, но и создает все
необходимые записи в базе данных Articles.
Ниже мы привели полный исходный текст метода AddNode:
public void AddNode(int id)
{
Form2 dialog = new Form2();
if(DialogResult.Yes == dialog.ShowDialog())
{
sqlConnection1.Open();
try
{
SqlCommand cmd = new SqlCommand("sp_InsertNode",
sqlConnection1);
cmd.CommandType = CommandType.StoredProcedure;
param.Direction = ParameterDirection.ReturnValue;
cmd.ExecuteNonQuery();
cmd.ExecuteNonQuery();
sqlConnection1.Close();
После обновления содержимого базы данных метод AddNode закрывает
соединение с базой данных.
Использование хранимых процедур
Теперь мы займемся описанием кода, добавляющего новые записи в базу данных. Этот
код интересен тем, что мы не будем встраивать в него команды SQL, а воспользуемся
механизмом хранимых процедур сервера Microsoft SQL Server.
Прежде всего, наша программа вызывает хранимую процедуру sp_InsertNode,
предназначенную для добавления новой строки в таблицу Tree, хранящую структуру
дерева. Напомним, что этой процедуре нужно передать через входные параметры
идентификатор родительского узла @parent_id, заголовок статьи @title и вес
сортировки @weight.
Вызов хранимой процедуры начинается с создания объекта класса SqlCommand:
SqlCommand cmd = new SqlCommand("sp_InsertNode", sqlConnection1);
Далее программа должна задать тип команды в свойстве CommandType в виде
константы CommandType.StoredProcedure:
cmd.CommandType = CommandType.StoredProcedure;
Это означает, что команда содержит не строку SQL, а имя хранимой процедуры.
Параметры хранимых процедур
На следующем этапе необходимо добавить параметры хранимой процедуры. Наша
хранимая процедура sp_InsertNode имеет три входных и один выходной параметр.
Через выходной параметр со специальным именем RETURN_VALUE хранимая
процедура возвращает идентификатор добавленной строки:
SqlParameter param = cmd.Parameters.Add("RETURN_VALUE",
SqlDbType.Int);
В качестве первого параметра методу Add передается имя параметра хранимой
процедуры, а в качестве второго — тип данных, соответствующих этому параметру.
Чтобы указать, что этот параметр является выходным, мы записываем константу
ParameterDirection.ReturnValue в свойство параметра с именем Direction:
param.Direction = ParameterDirection.ReturnValue;
Если этого не сделать, то по умолчанию параметр будет входным.
Вот как мы указываем входные параметры для хранимой процедуры
sp_InsertNode:
cmd.Parameters.Add("@parent_id", SqlDbType.Int).Value = id;
cmd.Parameters.Add("@title", SqlDbType.VarChar).Value = dialog.Title;
cmd.Parameters.Add("@weight", SqlDbType.Int).Value = dialog.Weight;
Обратите внимание на то, что тип числовых данных указан как SqlDbType.Int, а
тип строчных данных — как SqlDbT ype.VarChar.
Параметру хранимой процедуры @parent_id мы присваиваем значение
идентификатора родительского узла, который передается при вызове методу AddNode.
Что же касается параметров @title и @weight, то для их инициализации мы извлекаем
значения из свойств Title и Weight, определенных нами в классе Form2 диалогового
окна ввода данных узла (рис. 9-43).
Запуск хранимой процедуры
Для запуска хранимой процедуры на выполнение мы вызываем метод ExecuteNonQuery:
cmd.ExecuteNonQuery();
Если у хранимой процедуры имеются параметры (как в нашем случае), то их
необходимо подготовить. Иначе при выполнении метода ExecuteNonQuery возникнет
исключение.
Получение значений выходных параметров
После того как хранимая процедура завершит свою работу, программа может получить
значение ее выходных параметров при помощи свойства Value.
Вот как мы извлекаем значение, возвращаемое хранимой процедурой
sp_InsertNode:
int tree_id = (int)cmd.Parameters["RETURN_VALUE"].Value;
Напомним, что наша хранимая процедура возвращает идентификатор узла,
добавленного в таблицу Tree. Этот идентификатор понадобится нам в дальнейшем для
инициализации ячейки внешнего ключа таблицы Documents, ссылающейся на таблицу
Tree.
Добавление текста документа
Для добавления текста документа, извлеченного из свойства dialog.Document
диалогового окна класса Form2 мы вызываем хранимую процедуру sp_InsertDocument:
cmd = new SqlCommand("sp_InsertDocument", sqlConnection1);
cmd.CommandType = CommandType.StoredProcedure;
Эта процедура имеет один выходной параметр и два входных:
param = cmd.Parameters.Add("RETURN_VALUE", SqlDbType.Int);
param.Direction = ParameterDirection.ReturnValue;
Построение дерева
Для построения дерева заголовков статей в окне элемента управления TreeView в
нашем приложении определен метод UpdateTree, на который мы уже ссылались ранее,
а также метод CreateNodes.
Метод UpdateTree
Метод UpdateTree считывает структуру дерева из базы данных Articles и отображает ее
в окне элемента управления treeView1 класса TreeView.
Вот исходный текст этого метода:
public void UpdateTree()
{
dataSet11.Clear();
sqlDataAdapter1.Fill(dataSet11);
treeView1.Nodes.Clear();
CreateNodes(0,(TreeNode)null);
treeView1.ExpandAll();
}
Получив управление, метод UpdateTree очищает набор данных dataSet11, а затем
наполняет его из таблицы Tree базы данных Aticles, пользуясь для этого адаптером
sqlDataAdapter1 и методом Fill.
На следующем этапе метод UpdateTree удаляет все элементы из дерева treeView1.
Заполнение дерева treeView1 содержимым набора данных dataSet11
осуществляется методом CreateNodes. Далее все узлы заполненного дерева
раскрываются, для чего программа вызывает метод treeView1.ExpandAll.
Метод CreateNodes
Теперь нам нужно изучить метод CreateNodes, который создает в дереве treeView1
узел, соответствующий одной статье. Ниже мы привели исходный текст этого метода:
public void CreateNodes(int iParent, TreeNode pNode)
{
DataView dvwData = new DataView(dataSet11.Tables[0]);
dvwData.RowFilter = "[parent_id] = " + iParent;
if(pNode == null)
{
TreeNode zNode = treeView1.Nodes.Add(Row["title"].ToString() +
" (" + Row["weight"].ToString() + ")");
zNode.Tag = id;
CreateNodes(id, zNode);
}
else
{
if(id == iParent)
{
return;
}
TreeNode zNode = pNode.Nodes.Add(Row["title"].ToString() +
" (" + Row["weight"].ToString() + ")");
zNode.Tag = id;
CreateNodes(id, zNode);
}
}
}
Метод CreateNodes имеет два параметра.
Через первый параметр методу передается идентификатор узла, родительского по
отношению к добавляемому узлу. Если создается корневой узел, то значение этого
параметра равно нулю.
Второй параметр используется для передачи ссылки на родительский узел дерева
treeView1, который является объектом класса TreeNode. При создании корневого узла
этот параметр должен иметь значение null.
Как работает метод CreateNodes?
Прежде всего, он создает представление (view) таблицы dvwData, хранящейся в
наборе данных dataSet11. Это представление включает в себя подмножество строк
таблицы, столбец parent_id которых содержит идентификатор родительского узла,
переданного методу CreateNodes в качестве первого параметра. Иными словами, здесь
происходит отбор дочерних узлов заданного родительского узла.
Вот как создается это представление:
DataView dvwData = new DataView(dataSet11.Tables[0]);
dvwData.RowFilter = "[parent_id] = " + iParent;
В свойстве dvwData.RowFilter мы указываем нужное нам условие отбора строк в
самой первой (и единственной) таблице набора данных dataSet11.
После того как все нужные строки будут отобраны, запускается цикл по всем
строкам полученного представления:
foreach(DataRowView Row in dvwData)
{
…
}
Внутри этого цикла мы извлекаем идентификатор каждой строки id и сохраняем ее
в одноименной переменной. Для преобразования извлеченного значения в число мы
используем метод Int32.Parse:
int id = Int32.Parse(Row["id"].ToString());
Дальнейшие действия, выполняемые нашей программой, зависят от значения
второго параметра метода CreateNodes.
Вот как добавляется корневой узел дерева:
if(pNode == null)
{
TreeNode zNode = treeView1.Nodes.Add(Row["title"].ToString() +
" (" + Row["weight"].ToString() + ")");
zNode.Tag = id;
CreateNodes(id, zNode);
}
Здесь мы с помощью метода treeView1.Nodes.Add добавляем в дерево treeView1
новый элемент. В качестве текстовой строки, отображаемой в окне дерева, мы
используем заголовок статьи, извлеченный из столбца title текущей извлеченной
строки. К этому заголовку мы дописываем в круглых скобках значение веса
сортировки, взятого из столбца weight.
Далее наша программа записывает в свойство Tag идентификатор узла дерева,
взяв его из столбца id. Пользуясь этим свойством, приложение сможет однозначно
сопоставить элементы дерева, выделенные пользователем в окне элемента управления
treeView1, и строки таблицы Tree, хранящей информацию об узлах дерева в базе
данных Microsoft SQL Server.
И, наконец, метод CreateNodes вызывает сам себя рекурсивно для добавления в
дерево всех узлов, дочерних для только что добавленного элемента. Обратите
внимание, что теперь в качестве второго параметра этому методу передается ссылка на
только что созданный узел дерева zNode.
Теперь настало время рассмотреть действия метода CreateNodes при рекурсивном
вызове, когда значение второго параметра не равно null.
Прежде всего, в этом случае метод проверяет условие выхода из рекурсии:
if(id == iParent)
{
return;
}
Если идентификатор узла равен идентификатору родительского узла, работа
метода завершается оператором return.
В противном случае мы создаем узел дерева, записывая идентификатор узла id в
свойство zNode.Tag:
TreeNode zNode = pNode.Nodes.Add(Row["title"].ToString() +
" (" + Row["weight"].ToString() + ")");
zNode.Tag = id;
Далее следует рекурсивный вызов метода CreateNodes:
CreateNodes(id, zNode);
sqlConnection1.Open();
SqlDataReader myReader;
string strCmd = String.Format(
"SELECT document FROM Documents WHERE tree_id = {0}", id);
SqlCommand cmd = new SqlCommand(strCmd, sqlConnection1);
myReader = cmd.ExecuteReader();
if(myReader.Read())
{
dialog.Document = myReader.GetString(0).ToString();
}
myReader.Close();
sqlGetNodeCommand.Parameters["@id"].Value = id;
myReader = sqlGetNodeCommand.ExecuteReader();
if(myReader.Read())
{
dialog.Title = myReader.GetString(2).ToString();
dialog.Weight = myReader.GetInt32(3);
if(DialogResult.Yes == dialog.ShowDialog())
{
try
{
sqlUpdateNodeCommand.Parameters["@id"].Value = id;
sqlUpdateNodeCommand.Parameters["@title"].Value =
dialog.Title;
sqlUpdateNodeCommand.Parameters["@weight"].Value =
dialog.Weight;
myReader.Close();
sqlUpdateNodeCommand.ExecuteNonQuery();
cmd.CommandType = CommandType.StoredProcedure;
cmd.ExecuteNonQuery();
richTextBox1.Text = dialog.Document;
}
catch(Exception ex)
{
MessageBox.Show(ex.Message, "Ошибка");
}
}
sqlConnection1.Close();
UpdateTree();
}
}
}
Получив управление, обработчик событий menuItem3_Click, прежде всего,
проверяет, есть ли какие-либо элементы в окне дерева:
if(treeView1.Nodes.Count != 0)
{
…
}
Если дерево пустое, обработчик возвращает управление, не предпринимая
никаких действий.
Извлечение идентификатора редактируемого узла
В том случае, когда в дереве есть узлы, обработчик получает идентификатор
выделенного узла, извлекая его из свойства treeView1.SelectedNode.Tag:
int id = (int)treeView1.SelectedNode.Tag;
Напомним, что этот идентификатор равен содержимому столбца id строки таблицы
Tree, описывающей данный узел. Этот идентификатор записывается в свойство
treeView1.SelectedNode.Tag во время заполнения окна элемента управления treeView1.
Идентификатор id потребуется нам для извлечения из таблицы Tree информации
об узле дерева, подлежащей редактированию.
Для редактирования мы создаем форму класса Form2:
Form2 dialog = new Form2();
Это та же самая форма, которую мы использовали для создания новых узлов
дерева (рис. 9-43).
Извлечение данных редактируемого узла дерева
Далее нам нужно заполнить поля формы текущей информацией из таблиц Tree и
Document, соответствующей данному узлу с идентификатором id. С этой целью мы
открываем соединения с базой данных sqlConnection1 и создаем объект класса
SqlDataReader:
sqlConnection1.Open();
SqlDataReader myReader;
Получение текст редактируемой статьи
На первом шаге нам нужно создать команду SQL класса SqlCommand, с помощью
которой мы могли бы извлечь текст редактируемой статьи из таблицы Documents. При
этом нам необходимо найти в этой таблице такую строку, для которой в столбце tree_id
хранилось бы значение, равное идентификатору редактируемого узла дерева id.
Необходимую команду SQL мы создаем «на лету», формируя соответствующую
текстовую строку с помощью класса String.Format:
string strCmd = String.Format(
"SELECT document FROM Documents WHERE tree_id = {0}", id);
if(myReader.Read())
{
dialog.Document = myReader.GetString(0).ToString();
}
myReader.Close();
Далее обработчик закрывает объект SqlDataReader, чтобы подготовить его к
выполнению новой команды.
Извлечение заголовка и веса сортировки
Для того чтобы извлечь заголовок редактируемой статьи и вес сортировки, мы создали
команду sqlGetNodeCommand. Ниже приведен текст команды SQL, который нужно
записать в свойство CommandText объекта sqlGetNodeCommand:
SELECT id, parent_id, title, weight FROM dbo.Tree WHERE (id = @id)
Через параметр @id команде передается идентификатор узла дерева, для
которого нужно извлечь информацию из таблицы Tree:
sqlGetNodeCommand.Parameters["@id"].Value = id;
Далее подготовленная команда запускается на выполнение с помощью метода
ExecuteReader:
myReader = sqlGetNodeCommand.ExecuteReader();
Если программа нашла в таблице Tree нужный нам узел, то заголовок статьи и вес
сортировки этого узла записывается, соответственно, в свойства dialog.Title и
dialog.Weight диалогового окна редактирования:
if(myReader.Read())
{
dialog.Title = myReader.GetString(2).ToString();
dialog.Weight = myReader.GetInt32(3);
…
}
sqlConnection1.Open();
try
{
sqlFindChildsCommand.Parameters["@parent_id"].Value = id;
myReader = sqlFindChildsCommand.ExecuteReader();
if(!myReader.Read())
{
myReader.Close();
sqlDeleteRowCommand1.Parameters["@id"].Value = id;
sqlDeleteRowCommand1.ExecuteNonQuery();
richTextBox1.Text = "";
}
else
{
myReader.Close();
}
}
catch(Exception ex)
{
MessageBox.Show(ex.Message, "Ошибка");
}
sqlConnection1.Close();
}
Получив управление, этот метод создает объект myReader класса SqlDataReader и
открывает соединение с базой данных:
SqlDataReader myReader;
sqlConnection1.Open();
Дальнейшая работа выполняется внутри блока try-catch, в задачу которого входит
перехват и обработка исключений.
Прежде всего, необходимо убедиться, что удаляемый узел не имеет дочерних
узлов. Наше приложение позволяет удалять из дерева только такие узлы.
Для поиска всех дочерних узлов выделенного пользователем узла метод
DeleteNode использует команду sqlFindChildsCommand.
Вот текст этой команды, который нужно записать в свойство CommandText
объекта sqlFindChildsCommand:
SELECT id FROM dbo.Tree WHERE (parent_id = @parent_id)
Через параметр @parent_id мы передаем этой команде идентификатор удаляемого
узла:
sqlFindChildsCommand.Parameters["@parent_id"].Value = id;
myReader = sqlFindChildsCommand.ExecuteReader();
Если в таблице Tree есть строки, в столбце parent_id которых содержится
идентификатор удаляемого узла, значит, такой узле удалять нельзя. Вот
соответствующая проверка, выполняемая методом DeleteNode:
if(!myReader.Read())
{
// Удаление узла
…
}
В том случае, когда удаляемый узел не имеет дочерних узлов, нам нужно удалить
строку, описывающую этот узел, из таблицы Tree, а затем удалить соответствующую
статью из таблицы Document.
Для удаления строки из таблицы Tree мы используем команду
sqlDeleteRowCommand1, предварительно закрыв объект myReader:
myReader.Close();
sqlDeleteRowCommand1.Parameters["@id"].Value = id;
sqlDeleteRowCommand1.ExecuteNonQuery();
Свойство объекта sqlDeleteRowCommand1 должно содержать следующий текст
команды SQL:
DELETE FROM dbo.Tree WHERE (id = @id)
Идентификатор удаляемого узла передается команде sqlDeleteRowCommand1 в
качестве единственного параметра.
Для удаления текста статьи мы использовали другой подход — сформировали
исходный текст команды SQL в памяти при помощи метода String.Format:
string strCmd = String.Format(
"DELETE FROM Documents WHERE tree_id = {0}", id);
sqlConnection1.Open();
try
{
SqlDataReader myReader;
if(myReader.Read())
{
richTextBox1.Text = myReader.GetString(0).ToString();
}
myReader.Close();
}
catch(Exception ex)
{
MessageBox.Show(ex.Message, "Ошибка");
}
sqlConnection1.Close();
}
Этот обработчик будет получать управление всякий раз, когда пользователь
выделяет в окне дерева новый заголовок статьи.
Что делает наш обработчик события treeView1_AfterSelect?
Прежде всего, он получает идентификатор узла, выделенного пользователем,
считывая его из свойства treeView1.SelectedNode.Tag:
int id = (int)treeView1.SelectedNode.Tag;
Зная этот идентификатор, мы можем получить текст статьи из таблицы Document,
чтобы отобразить его в правой части главного окна нашего приложения.
Для того чтобы получить текст статьи, мы открываем соединение с базой данных.
Операция выполняется в блоке try-catch, после чего соединение закрывается:
sqlConnection1.Open();
try
{
…
}
catch(Exception ex)
{
MessageBox.Show(ex.Message, "Ошибка");
}
sqlConnection1.Close();
Чтобы извлечь текст статьи из таблицы Documents, мы формируем строку в
памяти, пользуясь методом String.Format:
string strCmd = String.Format(
"SELECT document FROM Documents WHERE tree_id = {0}", id);
Визуальное проектирование
приложений C#
А.В. Фролов, Г.В. Фролов
ГЛАВА 10. ГРАФИЧЕСКИЙ ИНТЕРФЕЙС GDI+
ОСНОВНЫЕ ПОНЯТИЯ
Независимость от аппаратуры
Контекст отображения
Класс Graphics
Приложение GraphicsApp
Отслеживание состояния клавиш мыши
Отслеживание перемещения курсора мыши
Идентификатор окна Handle и объект Graphics
Кисть для рисования
Рисование точки
Рисование в окне элемента управления
СОБЫТИЕ PAINT
Рисование в окнах приложений Microsoft Windows
Сообщение WM_PAINT
Пример обработки события Paint
Перерисовка окон элементов управления
МЕТОДЫ И СВОЙСТВА КЛАССА GRAPHICS
Рисование геометрических фигур
Линия
Набор линий
Прямоугольник
Набор прямоугольников
Многоугольник
Эллипс
Сегмент эллипса
Кривые Безье
Канонические сплайны
Замкнутый сегмент эллипса
Закрашенные фигуры
Рисование изображений
Немного о ресурсах приложения
Значки
Растровые и векторные изображения
Использование класса Image
Режим буксировки
События при буксировке
Обработка события DragOver
Обработка события DragDrop
Загрузка изображения
Рисование загруженного изображения
Рисование текста
ИНСТРУМЕНТЫ ДЛЯ РИСОВАНИЯ
Перья
Кисти
Кисть для сплошной закраски
Кисти типа HatchBrush
Кисти типа TextureBrush
Градиентные кисти
Шрифты
Классификация шрифтов
Шрифты TrueType
Шрифты OpenType
Выбор шрифта
Конструкторы класса Font
Тип шрифта FontStyle
Единицы измерения размера шрифта
Семейство шрифта FontFamily
Приложение FontApp
С помощью мыши пользователь может рисовать внутри окна панели Panel, однако
остальная часть главного окна приложения для рисования недоступна.
Создайте проект приложения GraphicsApp1 и перетащите из панели Toolbox
системы Microsoft Visual Studio в форму Form1 значок элемента управления Panel.
Далее создайте в классе Form1 поле doDraw и обработчики событий, создаваемых
мышью. Обработчики должны создаваться для объекта Panel, а не для формы Form1,
как это было в предыдущем приложении:
bool doDraw = false;
//
// TODO: Add any constructor code after InitializeComponent call
//
text = "Обработка события Paint";
}
И, наконец, измените исходный текст обработчика события Form1_Paint
следующим образом:
private void Form1_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
g.Clear(Color.White);
g.DrawString(text, new Font("Helvetica", 15),
Brushes.Black, 0, 0);
g.DrawRectangle(new Pen(Brushes.Black,2), 10, 30, 200, 100);
g.DrawEllipse(new Pen(Brushes.Black,2), 150, 120, 100, 130);
}
Здесь в теле обработчика Form1_Paint мы определили локальную переменную g
класса Graphics, предназначенную для хранения контекста отображения. Эта
переменная инициализируется при помощи значения, полученного из свойства Graphics
первого параметра обработчика Form1_Paint:
Graphics g = e.Graphics;
Получив контекст отображения, наш обработчик события Paint может рисовать в
соответствующем окне все, что угодно.
Вначале мы закрашиваем окно белым цветом, вызывая для этого метод Clear,
определенный в классе Graphics:
g.Clear(Color.White);
Таким способом мы можем закрасить фон, цвет которого задан для формы в
свойстве BackColor.
Далее мы вызываем методы DrawString, DrawRectangle и DrawEllipse, также
определенные в классе Graphics:
g.DrawString(text, new Font("Helvetica", 15), Brushes.Black, 0, 0);
g.DrawRectangle(new Pen(Brushes.Black,2), 10, 30, 200, 100);
g.DrawEllipse(new Pen(Brushes.Black,2), 150, 120, 100, 130);
Первый из них рисует текстовую строку в верхней части окна, а два других —
прямоугольник и эллипс, соответственно (рис. 10-7).
Набор линий
Вызвав один раз метод DrawLines, можно нарисовать сразу несколько прямых линий,
соединенных между собой. Иными словами, метод DrawLines позволяет соединить
между собой несколько точек. Координаты этих точек по горизонтальной и
вертикальной оси передаются методу через массив класса Point или PointF:
public void DrawLines(Pen, Point[]);
public void DrawLines(Pen, PointF[];
Для демонстрации возможностей метода DrawLines мы подготовили приложение
DrawLinesApp. В классе Form1 этого приложения мы создаем кисть pen для рисования
линий, а также массив точек points, которые нужно соединить линиями:
Pen pen = new Pen(Color.Black, 2);
Point[] points = new Point[50];
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
Как видите, внешний вид линий оставляет желать лучшего — вместо прямых
линий мы получили ломаные линии!
Можно ли избавиться от этой неприятности?
Да, можно, и для этого нужно настроить один из параметров контекста
отображения, а именно параметр SmoothingMode. Этот параметр задает режим
сглаживания при отображении линий.
Включите в исходный текст программы пространство имен
System.Drawing.Drawing2D, в котором определено перечисление SmoothingMode:
using System.Drawing.Drawing2D;
Это пространство имен будет использоваться практически во всех примерах
приложений этой главы, так что не забывайте его включать.
Далее измените обработчик Form1_Paint следующим образом:
private void Form1_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
Graphics g=e.Graphics;
g.Clear(Color.White);
g.SmoothingMode = SmoothingMode.HighQuality;
g.DrawLines(pen, points);
}
Константа SmoothingMode.HighQuality, определенная в перечислении
SmoothingMode, задает высококачественное сглаживание при отображении линии. Как
видно на рис. 10-10, результат получился вполне удовлетворительный.
Рис. 10-10. Сглаживание линий
Многоугольник
Метод DrawPolygon поможет Вам в тех случаях, когда нужно нарисовать многоугольник,
заданный своими вершинами.
Предусмотрено два варианта этого метода:
public void DrawPolygon(Pen, Point[]);
public void DrawPolygon(Pen, PointF[]);
В первом случае методу DrawPolygon через второй параметр передается массив
точек класса Point, в котором координаты точек заданы целыми числами, а во втором —
массив класса PointF, где координаты соединяемых точек задаются в виде числе с
плавающей десятичной точкой.
Для демонстрации возможностей метода DrawPolygon мы создали приложение
DrawPolygonApp. В нем мы определили перо myPen и массив точек myPoints:
Pen myPen = new Pen(Color.Black, 2);
Point[] myPoints =
{
new Point(10, 10),
new Point(100, 40),
new Point(50, 240),
new Point(150, 24),
new Point(100, 100),
new Point(160, 40),
new Point(220, 210)
};
Обработчик события Form1_Paint соединяет эти точки вместе, вызывая метод
DrawPolygon:
private void Form1_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
Graphics g=e.Graphics;
g.Clear(Color.White);
g.DrawPolygon(myPen, myPoints);
}
На рис. 10-12 мы показали результат работы нашего приложения.
Эллипс
Метод DrawEllipse рисует эллипс, вписанный в прямоугольную область, расположение и
размеры которой передаются ему в качестве параметров.
Предусмотрено четыре перегруженных варианта метода DrawEllipse:
public void DrawEllipse(Pen, Rectangle);
public void DrawEllipse(Pen, RectangleF);
public void DrawEllipse(Pen, int, int, int, int);
public void DrawEllipse(Pen, float, float, float, float);
Как видите, эти методы отличаются только способом, при помощи которого
описывается расположение и размеры прямоугольной области, в которую вписан
эллипс. Вы можете задавать расположение и размеры этой области в виде
рассмотренных ранее объектов класса Rectangle, RectangleF, а также в виде целых
чисел или числе с плавающей десятичной точкой.
Пример использования метода DrawEllipse мы привели в разделе «Пример
обработки события Paint», когда рассказывали о приложении PaintApp.
Сегмент эллипса
При помощи метода DrawArc программа может нарисовать сегмент эллипса. Сегмент
задается при помощи координат прямоугольной области, в которую вписан эллипс, а
также двух углов, отсчитываемых в направлении против часовой стрелки. Первый угол
Angle1 задает расположение одного конца сегмента, а второй Angle2 — расположение
другого конца сегмента (рис. 10-13).
Предусмотрено четыре перегруженных варианта метода DrawArc:
public void DrawArc(Pen, Rectangle, float, float);
public void DrawArc(Pen, RectangleF, float, float);
public void DrawArc(Pen, int, int, int, int, int, int);
public void DrawArc(Pen, float, float, float, float, float, float);
Первый параметр метода DrawArc определяет перо, с помощью которой будет
нарисован сегмент. Последние два параметра задают углы Angle1 и Angle2 в
соответствии с рис. 10-13. Расположение и размеры прямоугольной области
передаются методу DrawArc аналогично тому, как это делается для рассмотренного
выше метода DrawEllipse.
Graphics g=e.Graphics;
g.Clear(Color.White);
g.DrawArc(myPen, 10, 10, 200, 150, 30, 270);
}
Здесь мы использовали вариант метода DrawArc, допускающий указание
расположения и размеров прямоугольной области, а также углов в виде целых чисел.
Результат работы приложения DrawArcApp показан на рис. 10-14.
Рис. 10-14. Рисование сегмента эллипса
Кривые Безье
Из институтского курса математики Вам, скорее всего, известно понятие
сплайна (spline). Сплайн представляет собой кривую линию, соединяющую между
собой несколько точек.
Кривая Безье, представляющая собой одну из разновидностей сплайна, задается
четырьмя точками. Две из них — начальная и конечная, а две другие — управляющие.
Кривая Безье проходит через начальную и конечную точки, а управляющие точки
задают изгибы кривой линии. Те из Вас, кто когда-либо работал с векторными
графическими редакторами, например, с редактором Corel Draw, знают о
существовании кривых Безье и управляющих точек.
Для рисования кривых Безье имеются два перегруженных набора методов
DrawBezier и DrawBeziers:
public void DrawBezier(Pen, Point, Point, Point, Point);
public void DrawBezier(Pen, PointF, PointF, PointF, PointF);
public void DrawBezier(Pen, float, float, float, float, float, float,
float, float);
public void DrawBeziers(Pen, Point[]);
public void DrawBeziers(Pen, PointF[]);
Во всех этих методах первый параметр задает перо, которая будет использована
для рисования. Остальные параметры задают координаты начальной, конечной и
управляющих точек.
Что касается метода DrawBeziers, то он позволяет задавать координаты точек в
виде массивов, что может быть удобно в некоторых случаях.
В приложении DrawBezierApp мы рисуем кривую Безье в обработчике события
Paint:
private void Form1_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
Pen myPen = new Pen(Color.Black, 2);
PointF[] myBezierPoints =
{
startPt,
control1Pt,
control2Pt,
endPt
};
Graphics g=e.Graphics;
g.Clear(Color.White);
g.DrawBeziers(myPen, myBezierPoints);
}
Здесь мы создаем начальную и конечную точки startPt и endPt, через которые
проходит наша кривая, а также управляющие точки control1Pt и control2Pt.
Координаты всех точек передаются методу DrawBeziers через массив myBezierPoints.
Управляющие точки изгибают линию, как бы притягивая ее к себе (рис. 10-15).
Канонические сплайны
В отличие от только что рассмотренных кривых линий Безье, линии канонического или
обычного сплайна (cardinal spline) проходит через все заданные точки.
Для рисования обычных сплайнов предусмотрены методы DrawCurve и
DrawClosedCurve. Первый из этих методов рисует незамкнутую кривую линию
(открытый сплайн), а второй — замкнутую (закрытый сплайн).
В простейшем случае методам передается перо и массив соединяемых точек:
public void DrawCurve(Pen, Point[]);
public void DrawCurve(Pen, PointF[]);
public void DrawCurveClosed(Pen, Point[]);
public void DrawCurveClosed(Pen, PointF[]);
Существуют версии методов, позволяющие дополнительно задать так называемую
жесткость (tension) сплайна. Жесткость задается в виде третьего дополнительного
параметра:
public void DrawCurve(Pen, Point[], float);
public void DrawCurve(Pen, PointF[], float);
public void DrawClosedCurve(Pen, Point[], float, FillMode);
public void DrawClosedCurve(Pen, PointF[], float, FillMode);
По умолчанию значение жесткости равно 0,5. При увеличении этого параметра
увеличиваются изгибы кривой линии. При жесткости большей 1 или меньшей 0 кривая
может превратиться в петлю.
Методу DrawClosedCurve дополнительно задается параметр типа FillMode. Если
значение этого параметра равно FillMode.Alternate, при рисовании самопересекающихся
замкнутых сплайнов будут чередоваться закрашенные и не закрашенные области. Если
же значение этого параметра равно FillMode.Winding, большинство замкнутых областей
будет закрашено.
В приложении DrawClosedCurveApp мы рисуем обычный сплайн, используя точки с
теми же координатами, что и в предыдущем примере:
using System.Drawing.Drawing2D;
…
private void Form1_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
Pen myPen = new Pen(Color.Black, 2);
PointF[] myPoints =
{
pt1,
pt2,
pt3,
pt4
};
Graphics g=e.Graphics;
g.Clear(Color.White);
g.DrawClosedCurve(myPen, myPoints, (float)0.3,
FillMode.Alternate);
}
Результат показан на рис. 10-16.
Graphics g=e.Graphics;
g.Clear(Color.White);
Закрашенные фигуры
В классе Graphics определен ряд методов, предназначенных для рисования
закрашенных фигур. Имена некоторых из этих методов, имеющих префикс Fill, мы
перечислили в табл. 10-2.
Таблица 10-2. Методы для рисования закрашенных фигур
Метод Описание
Рисование закрашенного прямоугольника
FillRectangle
Рисование множества закрашенных многоугольников
FillRectangles
Рисование закрашенного многоугольника
FillPolygon
Рисование закрашенного эллипса
FillEllipse
Рисование закрашенного сегмента эллипса
FillPie
Рисование закрашенного сплайна
FillClosedCurve
Рисование закрашенной области типа Region
FillRegion
Есть два отличия методов с префиксом Fill от одноименных методов с префиксом
Draw. Прежде всего, методы с префиксом Fill рисуют закрашенные фигуры, а методы с
префиксом Draw — не закрашенные. Кроме этого, в качестве первого параметра
методам с префиксом Fill передается не перо класса Pen, а кисть класса Brush.
Пример использования метода FillEllipse мы приводили ранее в разделе
«Рисование в окне элемента управления» этой главы. Вот еще один пример:
SolidBrush redBrush = new SolidBrush(Color.Red);
g.FillEllipse(redBrush, 50, 50, 100, 110);
Здесь мы вначале создаем кисть красного цвета как объект класса SolidBrush. Эта
кисть затем передается методу FillEllipse в качестве первого параметра. Остальные
параметры метода FillEllipse задают расположение и размеры прямоугольника, в
который будет вписан эллипс.
Рисование изображений
Рассказывая в [8] о том, как устроена графическая подсистема в ОС Microsoft Windows,
и как ей пользоваться, мы отметили, что рисование графических изображений с
использованием одного только программного интерфейса Win32 API — не простая
задача. Решая ее, программисту приходится учитывать многочисленные детали,
имеющие отношение к цветовому разрешению и режимам видеоадаптера. Кроме того,
часто приходится работать напрямую с внутренними форматами файлов графических
изображений, таких как BMP, GIF, JPEG, PNG и т.д.
Графическая подсистема GDI+, реализованная на платформе Microsoft .NET
Frameworks, содержит мощные и удобные средства работы с графикой и графическими
изображениями, исключающие для программиста необходимость разбираться с
внутренними форматами файлов графических изображений, а также задумываться о
таких вещах, как палитры [8].
Однако прежде чем мы займемся рисованием, необходимо сделать маленькое
отступление и рассказать о ресурсах приложения.
Немного о ресурсах приложения
Если Вы раньше создавали приложения Microsoft Windows, то знаете, что формат
загрузочного модуля таких приложений сложнее формата загрузочного модуля
программ MS-DOS. Кроме выполняемого кода и констант в загрузочном модуле
приложения Microsoft Windows находятся дополнительные данные — ресурсы.
Что такое ресурсы?
Приложение Microsoft Windows может хранить в виде ресурсов текстовые строки,
значки, курсоры, графические изображения, меню, диалоговые окна, произвольные
массивы данных и т. д. Физически ресурсы находятся внутри exe-файла приложения.
Они могут загружаться в оперативную память автоматически при запуске приложения
или по запросу приложения (явному или неявному). Такой механизм обеспечивает
экономное использование оперативной памяти, так как все редко используемые данные
можно хранить на диске и загружать в память только при необходимости.
Например, приложение может иметь сложную систему диагностики ошибочных
ситуаций, содержащую различные диалоговые окна, массивы сообщений об ошибках и
т. п. Когда приложение работает без ошибок (что, очевидно, является нормальным
явлением) ненужные диагностические ресурсы спокойно лежат на диске, не
перегружая оперативную память. Но как только возникает ошибка, и приложение
вызовет функцию обработки ошибки, эти ресурсы будут автоматически загружены.
Ресурсы можно редактировать без повторной трансляции программного проекта.
Это означает, что Вы сможете легко перевести все сообщения и тексты меню
приложения на другой национальный язык, отредактировать графические изображения
или любые другие ресурсы, даже не имея в своем распоряжении исходные тексты.
В приложениях Microsoft .NET Framework тоже используется концепция ресурсов.
В частности, ресурсы таких приложений могут содержать значки и графические
изображения.
Если приложение должно рисовать в своих окнах значки или графические
изображения, целесообразно включить их в ресурсы приложения. В этом случае
данные из файлов, содержащих значки или изображения, будут переписаны в файл
сборки приложения. Таким образом, Вы сможете поставлять приложение как единый
загрузочный файл, не «комплектуя» его дополнительно файлами графических
изображений.
Значки
В этом разделе мы опишем приложение DrawIconApp, предназначенное для
демонстрации способов рисования значков, хранящихся в ресурсах приложения.
Создайте приложение DrawIconApp обычным образом, пользуясь мастером
проектов. Затем перепишите в каталог проекта любой файл значка, взяв его,
например, из каталога значков системы Microsoft Visual Studio .NET.
Далее включите значок в проект приложения, щелкнув название проекта правой
клавишей мыши в окне Solution Explorer и выбрав затем строку контекстного меню Add
Existing Item. В результате всех этих действий файл значка появится в списке файлов
Вашего проекта.
Щелкните этот файл левой клавишей мыши, а затем установите значение свойства
Build Action для файла значка равным Embedded Resource (рис. 10-18).
В результате выполнения этих действий файл значка (в нашем случае это был
файл FACE02.ICO) будет добавлен в ресурсы приложения.
Создайте обработчик события Form1_MouseDown, получающий управление, когда
пользователь щелкает левой клавишей мыши окно приложения:
private void Form1_MouseDown(object sender,
System.Windows.Forms.MouseEventArgs e)
{
Graphics g = Graphics.FromHwnd(this.Handle);
Icon myIcon = new Icon(GetType(),"FACE02.ICO");
g.DrawIcon(myIcon, e.X, e.Y);
}
Этот обработчик получает контекст отображения с помощью метода
Graphics.FromHwnd, а затем создает объект класса Icon, представляющий значок. Для
создания этого объекта мы использовали конструктор с двумя параметрами.
Через первый параметр конструктору класса Icon мы передаем ссылку на объект-
сборку, в котором хранится наш ресурс — значок из файла FACE02.ICO. Мы можем
получить такую ссылку при помощи метода GetType.
Что же касается второго параметра, то через него мы передаем имя ресурса,
которое в нашем случае будет совпадать с именем файла значка.
Функция DrawIcon рисует значок myIcon в точке, где был курсор мыши в момент
щелчка (рис. 10-19).
size.Width *= scale;
size.Height *= scale;
e.Graphics.DrawImage(img,
winRect.X + (winRect.Width - size.Width) / 2,
winRect.Y + (winRect.Height - size.Height) / 2,
size.Width, size.Height);
}
}
Как видите, большая часть программного кода выполняет масштабирование, а для
рисования мы вызываем один из перегруженных методов Graphics.DrawImage.
Получив управление, метод Form1_Paint проверяет, загрузил ли пользователь
какое-либо изображение для рисования. Если загрузил, то метод Form1_Paint
определяет границы прямоугольной области, занимаемой клиентской областью окна
нашего приложения:
RectangleF winRect = new RectangleF(0, 0,
e.Graphics.VisibleClipBounds.Width,
e.Graphics.VisibleClipBounds.Height);
Именно в этой области и будет нарисовано изображение, файл которого
пользователь отбуксировал мышью.
Обычно программы, предназначенные для просмотра изображений, умеют
масштабировать их таким образом, чтобы изображение полностью помещалось в окне
приложения. Чтобы реализовать необходимую логику масштабирования, нам нужно
знать некоторые атрибуты загруженного изображения. Эти атрибуты доступны
приложению как свойства класса Image.
В частности, свойства Width и Height содержат размеры загруженного
изображения в дюймах. Чтобы пересчитать эти значения в пикселы для отображения на
экране, нам потребуются свойства HorizontalResolution и VerticalResolution. Первое из
них содержит количество пискелов в пересчет на один дюйм изображения по
горизонтали, а второе — по вертикали.
Таким образом, чтобы получить размеры изображения в пикселах, нам нужно
воспользоваться следующим выражением:
SizeF size = new SizeF(img.Width / img.HorizontalResolution,
img.Height / img.VerticalResolution);
Размеры изображения получаются в виде объекта класса SizeF, в котором
определены свойства Width и Height.
Зная размеры окна winRect.Width и winRect.Height, в котором мы будем рисовать
изображение, а также размеры изображения size.Width и size.Height, мы вычисляем
масштаб, необходимый для того, чтобы изображение полностью поместилось в окне:
float scale = Math.Min(winRect.Width / size.Width,
winRect.Height / size.Height);
Здесь с помощью метода Math.Min мы выбираем наименьшее среди отношений
ширины и высоты окна к ширине и высоте изображения.
Полученный масштаб используется для вычисления новых размеров изображения:
size.Width *= scale;
size.Height *= scale;
И, наконец, последнее действие — рисование изображения:
e.Graphics.DrawImage(img,
winRect.X + (winRect.Width - size.Width) / 2,
winRect.Y + (winRect.Height - size.Height) / 2,
size.Width, size.Height);
Через первый параметр методу Graphics.DrawImage передается изображение,
загруженное при выполнении операции буксировки. Второй и третий параметры задают
координаты верхнего левого угла прямоугольной области, в которой будет нарисовано
изображение, а четвертый и пятый — размеры этой области. При рисовании будет
выполнено масштабирование.
Расположение прямоугольной области выбирается таким образом, чтобы
изображение было отцентрировано в окне приложения при любых размерах
изображения. Результат работы приложения ImageViewApp показан на рис. 10-21.
Рис. 10-21. Просмотр изображений в окне приложения PictureViewer
g.Clear(Color.White);
g.DrawString(text, new Font("Helvetica", 15),
Brushes.Black, 0, 0);
…
}
В качестве первого параметра методу DrawString передается текстовая строка,
которую нужно нарисовать.
Второй параметр задает шрифт. О шрифтах мы расскажем ниже, в разделе
«Инструменты для рисования». С помощью третьего параметра задается кисть, с
применением которой будет нарисован текст. И, наконец, два последних параметра
определяют координаты точки, в которой начнется рисование текста.
В классе Graphics определено несколько перегруженных вариантов метода
DrawString:
public void DrawString(string, Font, Brush, PointF);
public void DrawString(string, Font, Brush, RectangleF);
public void DrawString(string, Font, Brush, PointF, StringFormat);
public void DrawString(string, Font, Brush, RectangleF,
StringFormat);
public void DrawString(string, Font, Brush, float, float);
public void DrawString(string, Font, Brush, float, float,
StringFormat);
Параметр типа PointF задает расположение точки вывода текста. В последних двух
вариантах метода DrawString расположение этой точки задается при помощи пары
чисел формата float.
Если задан параметр типа RectangleF, то текст будет нарисован внутри области,
размеры и расположение которой заданы этим параметром. В том случае, если текст
выйдет за границы области, то он будет обрезан.
И, наконец, параметр типа StringFormat позволяет выполнить форматирование
текста. Описание этой возможности Вы найдете в документации.
Инструменты для рисования
Все методы класса Graphics, предназначенные для рисования фигур или текста,
получают через один из параметров перо класса Pen или кисть класса Brush, с
помощью которых и выполняется рисование. Метод DrawString, кроме этого, получает
еще и шрифт, применяемый для рисования текста.
В этом разделе нашей книги мы познакомимся с перечисленными выше
инструментами рисования, применяемые в системе GDI+ и доступными приложениям
C# с графическим интерфейсом.
Перья
Перья используются для рисования линий и простейших геометрических фигур и
создаются как объекты класса Pen. Вот соответствующие конструкторы:
public Pen(Color);
public Pen(Color, float);
public Pen(Brush);
public Pen(Brush, float);
Первый из этих конструкторов создает перо заданного цвета. Цвет задается при
помощи объекта класса Color. Второй конструктор позволяет дополнительно задать
толщину пера.
Третий и четвертый конструктор создают перо на основе кисти, причем в
четвертом конструкторе можно указать толщину создаваемого пера. О кистях мы
расскажем чуть позже в этой главе.
После того как перо создано, программа может определить его атрибуты при
помощи свойств класса Pen. Некоторые из этих свойств перечислены в табл. 10-7.
Таблица 10-7. Свойства пера
Свойство Описание
Выравнивание пера
Alignment
Ширина линии
Width
Кисть, используемая пером
Brush
Цвет пера
Color
Стиль пунктирных и штрих-пунктирных линий
DashStyle
Вид точек и штрихов пунктирных и штрих-
DashCup
пунктирных линий
Расстояние от начала линии до начала штриха
DashOffset
Массив шаблонов для создания произвольных
DashPattern
штрихов и пробелов штриховых и штрих-пунктирных
линий
Стиль концов линий
StartCup
EndCup
Формы концов линий
LineCap
Стиль соединения концов двух различных линий
LineJoin
Предельная толщина в области соединения
MiterLimit
остроконечных линий
Как видите, предусмотрены многочисленные возможности для создания самых
разных перьев.
Устанавливая значение свойства Color и Width, приложение может изменить,
соответственно, цвет и ширину линии, рисуемой пером.
Если надо нарисовать пунктирную или штрих-пунктирную линию, приложение
должно задать необходимое значение для свойства DashStyle. При этом допускается
изменять вид точек и тире пунктирных и штрих-пунктирных линий (свойство DashCup),
задавать расстояние от начала линии до начала штриха (свойство DashOffset) или даже
вовсе задать произвольный вид для штрихов и разделяющих эти штрихи пробелов
(свойство DashPattern).
При необходимости изменить внешний вид концов линий используйте свойства
StartCup и EndCup, задающие стиль концов линий. Свойство LineCap определяет форму
концов линий.
Если вас интересует стык между двумя различными линиями, то стиль этого стыка
задается свойством LineJoin, а предельная толщина стыка — стилем MiterLimit.
В приложении PenApp мы покажем способы изменения толщины линии, стиля
пунктирной и штрих-пунктирной линии, а также стиля концов линий. Ниже мы привели
исходный текст обработчика события Form1_Paint, рисующий линии различных типов и
стилей:
using System.Drawing.Drawing2D;
…
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g=e.Graphics;
g.Clear(Color.White);
int x=10;
int y=10;
Pen myPen = new Pen(Color.Black, 1);
y += 15;
myPen.Width = 2;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 3;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 5;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 10;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 3;
myPen.DashStyle = DashStyle.Dash;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 3;
myPen.DashStyle = DashStyle.DashDot;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 3;
myPen.DashStyle = DashStyle.DashDotDot;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 3;
myPen.DashStyle = DashStyle.Dot;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 5;
myPen.DashStyle = DashStyle.Solid;
myPen.StartCap = LineCap.ArrowAnchor;
myPen.EndCap = LineCap.DiamondAnchor;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 5;
myPen.StartCap = LineCap.Round;
myPen.EndCap = LineCap.RoundAnchor;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 5;
myPen.StartCap = LineCap.Square;
myPen.EndCap = LineCap.SquareAnchor;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 5;
myPen.StartCap = LineCap.Triangle;
myPen.EndCap = LineCap.Flat;
g.DrawLine(myPen, x, y, 200, y);
}
В начале своей работы метод Form1_Paint получает ссылку на объект класса
Graphics, т.е. контекст отображения:
Graphics g=e.Graphics;
Используя полученный контекст отображения, метод Form1_Paint закрашивает
окно приложения белым цветом, а затем рисует черную линию толщиной 1 пиксел:
g.Clear(Color.White);
int x=10;
int y=10;
Pen myPen = new Pen(Color.Black, 1);
y += 15;
myPen.Width = 3;
myPen.DashStyle = DashStyle.DashDot;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 3;
myPen.DashStyle = DashStyle.DashDotDot;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 3;
myPen.DashStyle = DashStyle.Dot;
g.DrawLine(myPen, x, y, 200, y);
В результате приложение нарисует в своем окне четыре пунктирные и штрих-
пунктирные линии различного типа.
Финальная часть обработчика события Form1_Paint показывает возможность
изменения стиля концов линий.
Сначала мы устанавливаем значение свойства DashStyle равным DashStyle.Solid,
отменяя рисование пунктирных и штрих-пунктирных линий. Далее мы четыре раза
устанавливаем различные свойства StartCap и EndCap, снабжая линии наконечниками
различных стилей:
y += 15;
myPen.Width = 5;
myPen.DashStyle = DashStyle.Solid;
myPen.StartCap = LineCap.ArrowAnchor;
myPen.EndCap = LineCap.DiamondAnchor;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 5;
myPen.StartCap = LineCap.Round;
myPen.EndCap = LineCap.RoundAnchor;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 5;
myPen.StartCap = LineCap.Square;
myPen.EndCap = LineCap.SquareAnchor;
g.DrawLine(myPen, x, y, 200, y);
y += 15;
myPen.Width = 5;
myPen.StartCap = LineCap.Triangle;
myPen.EndCap = LineCap.Flat;
g.DrawLine(myPen, x, y, 200, y);
Результат выполнения этих операций представлен на рис. 10-22.
g.Clear(Color.White);
g.DrawString(text, new Font("Helvetica", 15),
Brushes.Black, 0, 0);
g.DrawRectangle(new Pen(Brushes.Black,2), 10, 30, 200, 100);
g.DrawEllipse(new Pen(Brushes.Black,2), 150, 120, 100, 130);
}
Кисти типа HatchBrush
При помощи класса HatchBrush можно создать прямоугольную кисть заданного стиля, с
заданным цветом изображения и фона.
Для создания кистей этого типа предусмотрено два конструктора:
public HatchBrush(HatchStyle, Color);
public HatchBrush(HatchStyle, Color, Color);
Первый из этих конструкторов позволяет создать кисть заданного стиля и цвета, а
второй дополнительно позволяет указать цвет фона.
В табл. 10-8 мы перечислили различные стили кисти HatchBrush, представляющие
собой константы перечисления HatchStyle.
Таблица 10-8. Стили кисти типа HatchBrush
Константа Описание
Линии штриховки располагаются в обратном
BackwardDiagonal
направлении (от верхнего правого угла к нижнему
левому углу кисти)
Пересекающиеся горизонтальные и вертикальные
Cross
линии
Диагональные линии, идущие в направлении снизу
DarkDownwardDiagonal
вверх, и расположенные на 50% плотнее, чем при
использовании константы ForwardDiagonal (темная
штриховка)
Горизонтальные линии, которые на 50% плотнее,
DarkHorizontal
чем при использовании константы Horizontal (темная
штриховка)
Диагональные линии, плотнее на 50% чем при
DarkUpwardDiagonal
использовании константы BackwardDiagonal (темная
штриховка)
Вертикальные линии, которые на 50% плотнее, чем
DarkVertical
при использовании константы Vertical (темная
штриховка)
Штриховые диагональные линии, идущие в обратном
DashedDownwardDiagonal
направлении
Штриховые горизонтальные линии
DashedHorizontal
Штриховые диагональные линии, идущие в прямом
DashedUpwardDiagonal
направлении
Штриховые вертикальные линии
DashedVertical
Диагональная «кирпичная» штриховка
DiagonalBrick
Пересекающиеся прямые и обратные диагональные
DiagonalCross
линии
Штриховка в виде дерна
Divot
Прямые и обратные диагональные пересекающиеся
DottedDiamond
линии, состоящие из отдельных точек
Горизонтальные и вертикальные пересекающиеся
DottedGrid
линии, состоящие из отдельных точек
Прямые диагональные линии, идущие в направлении
ForwardDiagonal
от верхнего левого угла к нижнему правому углу
кисти
Горизонтальные линии
Horizontal
Горизонтальные «кирпичные» линии
HorizontalBrick
Штриховка в виде шахматной доски с крупными
LargeCheckerBoard
клетками
Штриховка в виде конфетти
LargeConfetti
Пересекающиеся горизонтальные и вертикальные
LargeGrid
линии (то же, что и Cross)
Светлая обратная диагональная штриховка
LightDownwardDiagonal
Светлая горизонтальная штриховка
LightHorizontal
Светлая прямая диагональная штриховка
LightUpwardDiagonal
Светлая вертикальная штриховка
LightVertical
То же, что и SolidDiamond
Max
То же, что и Horizonal
Min
Средняя горизонтальная штриховка
NarrowHorizontal
Средняя вертикальная штриховка
NarrowVertical
Пересекающиеся прямые и обратные диагональные
OutlinedDiamond
линии штриховки
Эти константы задают процентное соотношение
Percent05
цвета штриховки и цвета фона кисти.
Percent10
Percent20
Percent30
…
Percent90
Штриховка в виде пледа
Plaid
Кровельная штриховка
Shingle
Штриховка в виде шахматной доски с мелкими
SmallCheckerBoard
клетками
Штриховка в виде мелкого конфетти
SmallConfetti
Штриховка в виде мелкой сетки
SmallGrid
Штриховка в виде шахматной доски, расположенная
SolidDiamond
по диагонали
Штриховка с использованием сферических фигур
Sphere
Штриховка в виде решетки
Trellis
Вертикальные линии
Vertical
Волнообразные линии
Wave
Штриховка в виде ткани
Weave
Широкие обратные диагональные линии
WideDownwardDiagonal
Широкие прямые диагональные линии
WideUpwardDiagonal
Зигзагообразные горизонтальные линии
ZigZag
Для того чтобы продемонстрировать использование кистей класса HatchBrush, мы
подготовили приложение HatchBrushApp.
Вот исходный текст обработчика событий Form1_Paint этого приложения, в
котором выполняются существенные для нас операции:
using System.Drawing.Drawing2D;
…
Градиентные кисти
Приложениям GDI+ доступен еще один вид кистей — так называемая градиентная
кисть. Линейная градиентная кисть LinearGradientBrush позволяет задать в кисти
переход от одного цвета к другому. Кисти с множественным градиентом
PathGradientBrush позволяют задать внутри кисти область, которая будет закрашена с
использованием цветового градиента.
В нашей книге из-за недостатка места мы рассмотрим только один вариант
использования линейной градиентной кисти.
Рассмотрим обработчик события Form1_Paint приложения LinearGradientApp,
специально созданного нами для демонстрации возможностей закрашивания при
помощи линейной градиентной кисти:
private void Form1_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
Graphics g=e.Graphics;
g.Clear(Color.White);
Шрифты
Для того чтобы рисовать текст, используются шрифты. ОС Microsoft Windows может
работать с растровыми, векторными и масштабируемыми шрифтами. Кроме этого,
приложения Microsoft Windows могут использовать шрифты, встроенные в устройство
вывода (обычно это принтерные шрифты).
Классификация шрифтов
Растровые шрифты содержат образы всех символов в виде растровых изображений.
При этом для каждого размера шрифта необходимо иметь свой набор символов. Кроме
того, различные устройства вывода имеют разное соотношение горизонтальных и
вертикальных размеров пиксела, что приводит к необходимости хранить отдельные
наборы образов символов не только для разных размеров шрифта, но и для разного
соотношения размеров пиксела физического устройства отображения.
Растровые шрифты плохо поддаются масштабированию, так как при этом
наклонные линии контура символа принимают зазубренный вид.
Векторные шрифты хранятся в виде набора векторов, описывающих отдельные
сегменты и линии контура символа, поэтому они легко масштабируются. Однако их
внешний вид далек от идеального. Как правило, векторные шрифты используются для
вывода текста на векторные устройства, такие, как плоттер.
В состав ОС Microsoft Windows входит не очень большое количество шрифтов,
однако при необходимости Вы можете приобрести дополнительные шрифты как
отдельно, так и в составе различного программного обеспечения. Например, вместе с
графическим редактором Corel Draw поставляются сотни различных шрифтов.
Помимо обычных шрифтов существуют символьные или декоративные шрифты,
содержащие вместо букв различные значки.
Шрифты TrueType
Масштабируемые шрифты TrueType впервые появились в Microsoft Windows версии 3.1
и сильно повлияли на рост популярности этой ОС. Шрифты TrueType поддаются
масштабированию без существенных искажений внешнего вида.
Рис. 10-26 иллюстрирует ухудшение внешнего вида растрового и векторного
шрифтов при увеличенном размере символов. Внешний вид масштабируемого шрифта
не ухудшился.
Масштабируемые шрифты TrueType не только сохраняют свое начертание при
произвольном изменении высоты букв, но и обладают другими достоинствами.
Отметим, например, возможность вывода строк текста, расположенных под любым
углом относительно горизонтальной оси. Растровые и векторные шрифты позволяют
располагать строки текста только в горизонтальном направлении, что может создать
определенные трудности, например, при необходимости надписать название улицы на
карте города.
Еще одно преимущество масштабируемых шрифтов TrueType связано с тем, что Вы
можете встроить такой шрифт непосредственно в документ.
Зачем это может понадобиться?
Дело в том, что стандартный набор шрифтов TrueType, поставляемых в составе ОС
Microsoft Windows, не всегда удовлетворяет пользователей. Поэтому они приобретают
дополнительные шрифты у независимых разработчиков. Однако использование
нестандартных шрифтов может привести к проблемам при необходимости переноса
документа из одного компьютера в другие, так как там нужного шрифта может не
оказаться. Вы, конечно, можете просто скопировать нужный шрифт и перенести его
вместе с документом, однако такая процедура может быть запрещена по условию
лицензионного соглашения с разработчиками шрифта.
Отсчет всех размеров выполняется от так называемой базовой линии (base line)
шрифта. Для размеров используются логические единицы, которые зависят от режима
отображения, установленного в контексте устройства.
На рис. 10-28 общая высота символов отмечена как Height. Эта высота
складывается из двух компонентов — Ascent и Descent. Компонент Ascent представляет
собой высоту от базовой линии с учетом таких элементов, как тильда в букве «Й».
Компонент Descent определяет пространство, занимаемое символами ниже базовой
линии. Сумма Ascent и Descent в точности равна Height.
Величина InternalLeading определяет размер выступающих элементов символов и
может быть равна нулю.
Величина ExternalLeading определяет минимальный межстрочный интервал,
рекомендуемый разработчиком шрифта. Ваше приложение может игнорировать
межстрочный интервал, однако в этом случае строки будут соприкасаться друг с
другом, что не всегда приемлемо.
Как видите, с размерами символов здесь далеко не все так просто, как хотелось
бы!
Растровые шрифты, которые относятся к одному семейству, но имеют разные
размеры букв, хранятся в отдельных файлах. В то же время благодаря возможности
масштабирования шрифтов TrueType для них нет необходимости в отдельном хранении
глифов различных размеров.
Графический интерфейс GDI может выполнять масштабирование растровых
шрифтов, увеличивая (но не уменьшая) размер букв. Результат такого
масштабирования при большом размере букв обычно неудовлетворительный, так как
на наклонных линиях контура букв образуются зазубрины (рис. 10-26). Что же
касается GDI+, то он работает только с масштабируемыми шрифтами, к которым
относятся шрифты TrueType.
Векторные шрифты легко поддаются масштабированию, поэтому для хранения
шрифта одного семейства, но разного размера, можно использовать один файл.
Вы знаете, что шрифты могут иметь нормальное (normal), жирное (bold) или
наклонное (italic) начертание. В табл. 10-11 мы привели примеры различных
начертаний шрифтов. В табл. 10-12 Вы найдете примеры начертаний шрифтов,
доступные приложениям GDI+.
Таблица 10-11. Примеры начертаний шрифтов
Начертание Образец шрифта
AaBbCcDdEeFfGgHhIiJjKkLl АаБбВвГгДдЕеЖжЗзИиКкЛлМмНн
Normal
AaBbCcDdEeFfGgHhIiJjKkLl АаБбВвГгДдЕеЖжЗзИиКкЛлМмНн
Bold
AaBbCcDdEeFfGgHhIiJjKkLl
Italic
АаБбВвГгДдЕеЖжЗзИиКкЛлМмНнОоПпРр
Графический интерфейс GDI получает жирное и наклонное начертание растровых
шрифтов из нормального при помощи соответствующих алгоритмов утолщения и
наклона шрифта. Такие алгоритмы могут быть использованы и для масштабируемых
шрифтов TrueType, однако лучших результатов можно достигнуть при использовании
отдельных файлов шрифтов TrueType для нормального, жирного и наклонного
начертания.
Еще один часто используемый атрибут оформления строк текста —
подчеркивание. Иногда используется шрифт с перечеркнутыми буквами. GDI
выполняет подчеркивание самостоятельно, файлы шрифтов не содержат глифы с
подчеркиванием.
Шрифты OpenType
Операционная система Microsoft Windows 2000 и Microsoft Windows ХР способны
работать с шрифтами OpenType, созданными совместно компаниями Adobe и Microsoft.
Шрифты OpenType сочетают в себе достоинства шрифтов TrueType, а также шрифтов
Type1, разработанных компанией Adobe и широко применяемых в издательском деле.
На рис. 10-29 мы показали содержимое папки шрифтов Fonts, которую можно
найти в окне управляющей панели Control Panel.
Рис. 10-29. Шрифты в ОС Microsoft Windows 2000
g.Clear(Color.White);
g.DrawString(text, new Font("Helvetica", 15),
Brushes.Black, 0, 0);
…
}
Помимо шрифта, методу DrawString необходимо передать кисть для рисования
текста, а также координаты точки, в которой этот текст должен быть нарисован.
Существуют и другие перегруженные варианты метода DrawString, причем для каждого
из них необходимо указать шрифт.
Конструкторы класса Font
В классе Font существует довольно много конструкторов, с помощью которых можно
подобрать любой нужный Вам шрифт:
public Font(string, float);
public Font(FontFamily, float);
public Font(FontFamily, float, FontStyle);
public Font(FontFamily, float, GraphicsUnit);
public Font(string, float, FontStyle);
public Font(string, float, GraphicsUnit);
public Font(FontFamily, float, FontStyle, GraphicsUnit);
public Font(string, float, FontStyle, GraphicsUnit);
public Font(FontFamily, float, FontStyle, GraphicsUnit, byte);
public Font(string, float, FontStyle, GraphicsUnit, byte);
public Font(FontFamily, float, FontStyle, GraphicsUnit, byte, bool);
public Font(string, float, FontStyle, GraphicsUnit, byte, bool);
public Font(Font, FontStyle);
Первому конструктору нужно передать название шрифта (точнее говоря, название
гарнитуры шрифта), а также высоту символов в пунктах (в одном дюйме содержится 72
пункта):
public Font(string, float);
Выбирая название гарнитуры шрифта, учитывайте, что можно указывать только
шрифты TrueType и OpenType. Если указанный Вами шрифт не установлен на
компьютере пользователя, ОС Microsoft Windows заменит его другим шрифтом,
наиболее подходящим с ее «точки зрения». Лучше всего, если программа позволит
пользователю выбирать шрифт для отображения текста из числа установленных в
системе шрифтов, тогда с отображением текста будет меньше проблем.
Последний из конструкторов позволяет создать шрифт на основе другого шрифта,
изменив его стиль FontStyle.
Конструкторы, у которых имеется параметр типа byte, позволяют задавать номер
набора символов в терминах GDI.
И, наконец, последний параметр типа bool позволяет создавать шрифты с
вертикальным расположением строк символов.
Тип шрифта FontStyle
Параметр типа FontStyle задает тип шрифта. Возможные значения констант
перечисления FontStyle и их описания мы привели в табл. 10-12.
Таблица 10-12. Константы перечисления FontStyle
Константа Описание Образец шрифта
Обычный AaBbCcDdEeFfGgHhIiJjKkLl
Regular
АаБбВвГгДдЕеЖжЗзИиКкЛлМмНн
Жирный AaBbCcDdEeFfGgHhIiJjKkLl
Bold
АаБбВвГгДдЕеЖжЗзИиКкЛлМмНн
Наклонный AaBbCcDdEeFfGgHhIiJjKkLl
Italic
АаБбВвГгДдЕеЖжЗзИиКкЛлМмНнОоПпРр
Подчеркнутый AaBbCcDdEeFfGgHhIiJjKkLl
Underline
АаБбВвГгДдЕеЖжЗзИиКкЛлМмНнОоПпРр
Перечеркнутый AaBbCcDdEeFfGgHhIiJjKkLl
Strikeout
АаБбВвГгДдЕеЖжЗзИиКкЛлМмНнОоПпРр
Единицы измерения размера шрифта
Параметр конструктора Font типа GraphicsUnit дает Вам возможность указывать
размеры шрифта не только в пунктах, но и в других единицах измерения. В табл. 10-13
мы привели константы перечисления GraphicsUnit с кратким описанием.
Таблица 10-13. Константы перечисления GraphicsUnit
Константа Описание единицы измерения
1/75 часть дюйма
Display
Дюйм
Inch
Миллиметр
Millimeter
Пиксел
Pixel
Пункт (1/72 дюйма)
Point
Визуальное проектирование
приложений C#
А.В. Фролов, Г.В. Фролов
using System.IO;
using System.Drawing.Printing;
namespace SimpleNotepad
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class SimpleNotepadForm : System.Windows.Forms.Form
{
private System.Windows.Forms.MainMenu mainMenu1;
private System.Windows.Forms.MenuItem menuFile;
private System.Windows.Forms.MenuItem menuFileNew;
private System.Windows.Forms.MenuItem menuFileOpen;
private System.Windows.Forms.MenuItem menuFileSave;
private System.Windows.Forms.MenuItem menuFileSaveAs;
private System.Windows.Forms.MenuItem menuItem3;
private System.Windows.Forms.MenuItem menuFilePageSetup;
private System.Windows.Forms.MenuItem menuFilePrint;
private System.Windows.Forms.MenuItem menuItem6;
private System.Windows.Forms.MenuItem menuFileExit;
private System.Windows.Forms.MenuItem menuEdit;
private System.Windows.Forms.MenuItem menuEditUndo;
private System.Windows.Forms.MenuItem menuItem10;
private System.Windows.Forms.MenuItem menuEditCut;
private System.Windows.Forms.MenuItem menuEditCopy;
private System.Windows.Forms.MenuItem menuEditPaste;
private System.Windows.Forms.MenuItem menuEditDelete;
private System.Windows.Forms.MenuItem menuEditSelectAll;
private System.Windows.Forms.MenuItem menuFormat;
private System.Windows.Forms.MenuItem menuFormatFont;
private System.Windows.Forms.MenuItem menuHelp;
private System.Windows.Forms.MenuItem menuHelpAbout;
private System.Windows.Forms.OpenFileDialog openFileDialog1;
private System.Windows.Forms.RichTextBox richTextBox1;
private System.Windows.Forms.SaveFileDialog saveFileDialog1;
private System.Windows.Forms.MenuItem menuFilePrintPreview;
private System.Drawing.Printing.PrintDocument printDocument1;
private System.Windows.Forms.PageSetupDialog pageSetupDialog1;
private System.Windows.Forms.PrintPreviewDialog printPreviewDialog1;
private System.Windows.Forms.PrintDialog printDialog1;
private System.ComponentModel.IContainer components;
/// <summary>
/// StringReader для печати содержимого редактора текста
/// </summary>
private StringReader m_myReader;
/// <summary>
/// Номер текущей распечатываемой страницы документа
/// </summary>
private uint m_PrintPageNumber;
private System.Windows.Forms.MenuItem menuItem1;
private System.Windows.Forms.MenuItem menuItem2;
private System.Windows.Forms.MenuItem menuFormatFontCharacterStyle;
private System.Windows.Forms.MenuItem menuFormatFontCharacterStyleBold;
private System.Windows.Forms.MenuItem menuFormatFontCharacterStyleItalic;
private System.Windows.Forms.MenuItem menuFormatFontCharacterStyleUnderline;
private System.Windows.Forms.MenuItem menuFormatFontParagraphAlignment;
private System.Windows.Forms.MenuItem menuFormatFontParagraphAlignmentLeft;
private System.Windows.Forms.MenuItem menuFormatFontParagraphAlignmentRight;
private System.Windows.Forms.MenuItem menuFormatFontParagraphAlignmentCenter;
private System.Windows.Forms.FontDialog fontDialog1;
private System.Windows.Forms.MenuItem menuFormatColor;
private System.Windows.Forms.ColorDialog colorDialog1;
private System.Windows.Forms.MenuItem menuItem4;
private System.Windows.Forms.MenuItem menuFormatFontCharacterStyleStrikeout;
private System.Windows.Forms.ToolBar toolBar1;
private System.Windows.Forms.ImageList imageList1;
private System.Windows.Forms.ToolBarButton toolBarButton1;
private System.Windows.Forms.ToolBarButton toolBarButton2;
private System.Windows.Forms.ToolBarButton toolBarButton3;
private System.Windows.Forms.ToolBarButton toolBarButton4;
private System.Windows.Forms.ToolBarButton toolBarButton5;
private System.Windows.Forms.ToolBarButton toolBarButton6;
private System.Windows.Forms.ToolBarButton toolBarButton7;
private System.Windows.Forms.ToolBarButton toolBarButton8;
private System.Windows.Forms.ToolBarButton toolBarButton9;
private System.Windows.Forms.ToolBarButton toolBarButton10;
private System.Windows.Forms.StatusBar statusBar1;
private System.Windows.Forms.StatusBarPanel statusBarPanel1;
private System.Windows.Forms.StatusBarPanel statusBarPanel2;
private System.Windows.Forms.NotifyIcon notifyIcon1;
private System.Windows.Forms.MenuItem menuHelpRegister;
/// <summary>
/// Флаг изменения содержимого документа
/// </summary>
private bool m_DocumentChanged = false;
public SimpleNotepadForm()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
m_PrintPageNumber = 1;
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new SimpleNotepadForm());
}
switch(result)
{
case DialogResult.Yes:
{
MenuFileSaveAs();
break;
}
case DialogResult.Cancel:
{
return;
}
}
}
this.Close();
}
/// <summary>
/// Создание нового файла
/// </summary>
private void MenuFileNew()
{
if(m_DocumentChanged)
{
SaveDocumentNeededForm dialog = new
SaveDocumentNeededForm();
DialogResult result = dialog.ShowDialog();
switch(result)
{
case DialogResult.Yes:
{
MenuFileSaveAs();
break;
}
case DialogResult.Cancel:
{
return;
}
}
}
richTextBox1.Clear();
statusBarPanel2.Text = "";
m_DocumentChanged = false;
}
/// <summary>
/// Открытие существующего файла
/// </summary>
private void MenuFileOpen()
{
if(m_DocumentChanged)
{
SaveDocumentNeededForm dialog = new
SaveDocumentNeededForm();
DialogResult result = dialog.ShowDialog();
switch(result)
{
case DialogResult.Yes:
{
MenuFileSaveAs();
break;
}
case DialogResult.Cancel:
{
return;
}
}
}
if(openFileDialog1.ShowDialog() ==
System.Windows.Forms.DialogResult.OK &&
openFileDialog1.FileName.Length > 0)
{
try
{
richTextBox1.LoadFile(openFileDialog1.FileName,
RichTextBoxStreamType.RichText);
}
catch (System.ArgumentException ex)
{
richTextBox1.LoadFile(openFileDialog1.FileName,
RichTextBoxStreamType.PlainText);
}
this.Text = "Файл [" + openFileDialog1.FileName + "]";
statusBarPanel2.Text = "";
m_DocumentChanged = false;
}
}
/// <summary>
/// Сохранение документа в новом файле
/// </summary>
private void MenuFileSaveAs()
{
if(saveFileDialog1.ShowDialog() ==
System.Windows.Forms.DialogResult.OK &&
saveFileDialog1.FileName.Length > 0)
{
richTextBox1.SaveFile(saveFileDialog1.FileName);
m_DocumentChanged = false;
this.Text = "Файл [" + saveFileDialog1.FileName + "]";
statusBarPanel2.Text = "";
}
}
/// <summary>
/// Обработка события PrintPage
/// </summary>
private void PrintPageEventHandler(object sender,
System.Drawing.Printing.PrintPageEventArgs e)
{
int lineCount = 0; // счетчик строк
float linesPerPage = 0; // количество строк на одной
// странице
float yLinePosition = 0; // текущая позиция при печати по
// вертикальной оси
string currentLine = null; // текст текущей строки
/// <summary>
/// Печать документа
/// </summary>
private void MenuFilePrint()
{
m_PrintPageNumber = 1;
string strText = this.richTextBox1.Text;
m_myReader = new StringReader(strText);
Margins margins = new Margins(100,50,50,50);
printDocument1.DefaultPageSettings.Margins = margins;
if (printDialog1.ShowDialog() == DialogResult.OK)
{
this.printDocument1.Print();
}
m_myReader.Close() ;
}
/// <summary>
/// Предварительный просмотр перед печатью документа
/// </summary>
private void MenuFilePrintPreview()
{
m_PrintPageNumber = 1;
string strText = this.richTextBox1.Text;
m_myReader = new StringReader(strText);
Margins margins = new Margins(100,50,50,50);
printDocument1.DefaultPageSettings.Margins = margins;
printPreviewDialog1.ShowDialog();
m_myReader.Close() ;
}
/// <summary>
/// Настройка параметров страницы
/// </summary>
private void MenuFilePageSetup()
{
pageSetupDialog1.ShowDialog();
}
/// <summary>
/// Установка стиля символов Bold
/// </summary>
private void SetBold()
{
if (richTextBox1.SelectionFont != null)
{
System.Drawing.Font currentFont =
richTextBox1.SelectionFont;
System.Drawing.FontStyle newFontStyle;
if (richTextBox1.SelectionFont.Bold == true)
{
newFontStyle = FontStyle.Regular;
}
else
{
newFontStyle = FontStyle.Bold;
}
CheckMenuFontCharacterStyle();
}
}
/// <summary>
/// Установка стиля символов Italic
/// </summary>
private void SetItalic()
{
if (richTextBox1.SelectionFont != null)
{
System.Drawing.Font currentFont =
richTextBox1.SelectionFont;
System.Drawing.FontStyle newFontStyle;
CheckMenuFontCharacterStyle();
if (richTextBox1.SelectionFont.Italic == true)
{
newFontStyle = FontStyle.Regular;
}
else
{
newFontStyle = FontStyle.Italic;
}
CheckMenuFontCharacterStyle();
}
}
/// <summary>
/// Установка стиля символов Underline
/// </summary>
private void SetUnderline()
{
if (richTextBox1.SelectionFont != null)
{
System.Drawing.Font currentFont =
richTextBox1.SelectionFont;
System.Drawing.FontStyle newFontStyle;
CheckMenuFontCharacterStyle();
if (richTextBox1.SelectionFont.Underline == true)
{
newFontStyle = FontStyle.Regular;
}
else
{
newFontStyle = FontStyle.Underline;
}
CheckMenuFontCharacterStyle();
}
}
/// <summary>
/// Установка стиля символов Strikeout
/// </summary>
private void SetStrikeout()
{
if (richTextBox1.SelectionFont != null)
{
System.Drawing.Font currentFont =
richTextBox1.SelectionFont;
System.Drawing.FontStyle newFontStyle;
if (richTextBox1.SelectionFont.Strikeout == true)
{
newFontStyle = FontStyle.Regular;
}
else
{
newFontStyle = FontStyle.Strikeout;
}
CheckMenuFontCharacterStyle();
}
}
/// <summary>
/// Установка отметки строк меню Font->CharacterStyle
/// </summary>
private void CheckMenuFontCharacterStyle()
{
if(richTextBox1.SelectionFont.Bold == true)
{
menuFormatFontCharacterStyleBold.Checked = true;
}
else
{
menuFormatFontCharacterStyleBold.Checked = false;
}
if(richTextBox1.SelectionFont.Italic == true)
{
menuFormatFontCharacterStyleItalic.Checked = true;
}
else
{
menuFormatFontCharacterStyleItalic.Checked = false;
}
if(richTextBox1.SelectionFont.Underline == true)
{
menuFormatFontCharacterStyleUnderline.Checked = true;
}
else
{
menuFormatFontCharacterStyleUnderline.Checked = false;
}
if(richTextBox1.SelectionFont.Strikeout == true)
{
menuFormatFontCharacterStyleStrikeout.Checked = true;
}
else
{
menuFormatFontCharacterStyleStrikeout.Checked = false;
}
}
}
private void MenuSelect(object sender, System.EventArgs e)
{
MenuItem mi = (MenuItem) sender;
string ms;
switch(mi.Text)
{
case "&New": ms = "Новый документ"; break;
case "&Open...": ms = "Открыть документ"; break;
case "&Save": ms = "Сохранить документ"; break;
case "&Save As...": ms = "Сохранить документ"; break;
case "Page Set&up...": ms = "Параметры страницы"; break;
case "Print Pre&view...": ms = "Предварительный просмотр";
break;
case "&Print...": ms = "Печатать документ"; break;
case "Exit": ms = "Завершение работы"; break;
case "&Undo": ms = "Отменить ввод"; break;
case "&Redo": ms = "Повторить ввод"; break;
case "Cu&t": ms = "Вырезать"; break;
case "&Copy": ms = "Скопировать"; break;
case "&Paste": ms = "Вставить"; break;
case "&Delete": ms = "Удалить"; break;
case "&Select All": ms = "Выделить все"; break;
case "&Font...": ms = "Выбрать шрифт"; break;
case "C&olor...": ms = "Выбрать цвет"; break;
case "&Bold": ms = "Жирный шрифт"; break;
case "&Italic": ms = "Наклонный шрифт"; break;
case "&Underline": ms = "Подчеркивание"; break;
case "&Strikeout": ms = "Перечеркивание"; break;
case "&Left": ms = "Выравнивание по левой границе"; break;
case "&Right": ms = "Выравнивание по правой границе";
break;
case "&Center": ms = "Центровка"; break;
case "&About...": ms = "О программе"; break;
default: ms = ""; break;
}
statusBarPanel1.Text = ms;
}
System.Diagnostics.Process.Start(
"mailto:alexandre@frolov.pp.ru?subject=Регистрация&body="
+ body);
}
}
}
}
Листинг П1-2. Файл ch05\SimpleNotepad\SaveDocumentNeededForm.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
namespace SimpleNotepad
{
/// <summary>
/// Summary description for SaveDocumentNeededForm.
/// </summary>
public class SaveDocumentNeededForm : System.Windows.Forms.Form
{
private System.Windows.Forms.Button buttonYes;
private System.Windows.Forms.Button buttonNo;
private System.Windows.Forms.Button buttonCancel;
private System.Windows.Forms.PictureBox pictureBox1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public SaveDocumentNeededForm()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
}
#endregion
}
}
Листинг П1-3. Файл ch05\SimpleNotepad\RegisterForm.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
namespace SimpleNotepad
{
/// <summary>
/// Summary description for RegisterForm.
/// </summary>
public class RegisterForm : System.Windows.Forms.Form
{
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.TextBox textBoxName;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.TextBox textBoxEmail;
private System.Windows.Forms.Label label5;
private System.Windows.Forms.ComboBox comboBoxLevel;
private System.Windows.Forms.RadioButton radioButton1;
private System.Windows.Forms.RadioButton radioButton2;
private System.Windows.Forms.RadioButton radioButton3;
private System.Windows.Forms.RadioButton radioButton4;
private System.Windows.Forms.RadioButton radioButton5;
private System.Windows.Forms.TextBox textBoxFavoriteOS;
private System.Windows.Forms.MonthCalendar monthCalendar1;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.CheckBox checkBoxSendNews;
private System.Windows.Forms.CheckBox checkBoxSendLetter;
private System.Windows.Forms.TextBox textBoxComment;
private System.Windows.Forms.Label label6;
private System.Windows.Forms.GroupBox groupBoxGender;
private System.Windows.Forms.GroupBox groupBoxOS;
private System.Windows.Forms.ErrorProvider errorProvider1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public RegisterForm()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
}
#endregion
if(rb.Checked)
{
if(rb.Name != radioButton5.Name)
return rb.Text;
else
return textBoxFavoriteOS.Text;
}
}
}
return "";
}
}
}
}
Листинг П1-4. Файл ch05\SimpleNotepad\HelpAboutForm.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
namespace SimpleNotepad
{
/// <summary>
/// Summary description for HelpAboutForm.
/// </summary>
public class HelpAboutForm : System.Windows.Forms.Form
{
private System.Windows.Forms.Button button1;
private System.Windows.Forms.PictureBox pictureBox1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.LinkLabel linkLabel1;
private System.Windows.Forms.LinkLabel linkLabel2;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public HelpAboutForm()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
}
#endregion
Визуальное проектирование
приложений C#
А.В. Фролов, Г.В. Фролов
Библиографический список
Фролов А.В., Фролов Г.В. Создание Web-приложений: Практическое руководство. — М.:
Издательско-торговый дом «Русская Редакция», 2001.
Фролов А.В., Фролов Г.В. Практика применения PERL, PHP, Apache и MySQL для активных Web-
сайтов. — М.: Издательско-торговый дом «Русская Редакция», 2002.
Фролов А.В., Фролов Г.В. Язык C#. Самоучитель. — М.: «ДИАЛОГ-МИФИ», 2002.
Фролов А.В., Фролов Г.В. Операционная система Microsoft Windows 3.1 для программиста. — М.:
«ДИАЛОГ-МИФИ», 1993. — (Библиотека системного программиста; Т. 11).
Дейл Роджерсон. Основы COM. — М.: Издательско-торговый дом «Русская Редакция», 2000.
Том Армстронг. ActiveX: создание Web-приложений. — К.: Издательская группа BHV, 1998.
Фролов А.В., Фролов Г.В. Программирование видеоадаптеров CGA, EGA и VGA. — М.: «ДИАЛОГ-
МИФИ», 1992. — (Библиотека системного программиста; Т. 3).
Фролов А.В., Фролов Г.В. Графический интерфейс GDI в Microsoft Windows. — М.: «ДИАЛОГ-
МИФИ», 1993. — (Библиотека системного программиста; Т. 14).
Дейтел Х.М., Дейтел П. Дж. И др. Как программировать на XML. — М.: ЗАО «Издательство
БИНОМ», 2001.
Петцольд Ч. Программирование для Microsoft Windows на C#. — М.: Издательско-торговый дом
«Русская Редакция», 2002.
Фролов А.В., Фролов Г.В. Программирование для Windows NT. — М.: «ДИАЛОГ-МИФИ», 1996. —
(Библиотека системного программиста; Т. 26).
Фролов А.В., Фролов Г.В. Программирование для Windows NT. — М.: «ДИАЛОГ-МИФИ», 1997. —
(Библиотека системного программиста; Т. 27).
Найк Дилип. Стандарты и протоколы Интернета. — М.: Издательско-торговый дом «Русская
Редакция», 1999.
Пройдаков Э.М., Теплицкий Л.А. Англо-русский толковый словарь по вычислительной технике,
Интернету и программированию. — М.: Издательско-торговый дом «Русская Редакция», 2000.