MIME-Version: 1.0 Content-Type: multipart/related; boundary="----=_NextPart_01C6E59F.7229A350" Данный документ является веб-страницей в одном файле, также называемой файлом веб-архива. Если вы видите это сообщение, значит данный обозреватель или редактор не поддерживает файлы веб-архива. Загрузите обозреватель, поддерживающий веб-архивы, например Microsoft Internet Explorer. ------=_NextPart_01C6E59F.7229A350 Content-Location: file:///C:/64525993/file8483.htm Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset="windows-1251"
СОДЕРЖАНИЕ
 = ;
 = ;
 = ;
 = ; Самые основные вещи в языке C# и платформе .NET вцелом.
 = ;
Глава 1. Как читать чужой код.
&nb= sp;
По просьбам читателей, по= пытался написать об основах языка. Впрочем, только приступив понял, что д= ело гиблое - чтобы писать об основах надо быть или МУДрым АКадемикОМ,= или действительно иметь талант преподавателя. Я такового таланта= не имею. Поэтому смогу только кратко рассказать о некоторых особен= ностях. Поняв, что от такого толку мало, я решил написать для начала как = читать чужой код. Ибо, насколько я понимаю, основные проблемы с понима= нием кода кроются не в незнании, а в неумении эти самые знания добыть.= В этой области писать проще, т.к. мне часто приходится читать чужой код, самого разного уровня и на самых разных, в том числе неизвес= тных мне, языках.
Кратко о языке
Итак язык C# вырос из C++, со= хранив основные особенности синтаксиса и операторы. Да и основные опе= раторы одинаковы во всех языках, так что если вы знаете хоть один язык, то у= вас не должно возникать вопроса что такое for, if...then...else, do...wh= ile и пр. Не самый распространенный оператор использующийся в C# -= foreach. Собственно делает то, что обозначает - "for each =3D для каж= дого", т.е. проводит цикл для каждого члена из массива/коллекции/списка.=
Особенностью всех С-подобных= языков является ограничивание блоков кода фигурными скобками, в от= личии от, например, Basic, в котором все окончания обозначаются как End что-то (Sub, If и пр.). Ну и еще одна особенность - любовь к сокраще= ниям всего чего только можно.
В .NET вцелом появилось нес= колько особенностей - например, изчезли основные типы данных. Теперь к= лючевые слова double и int не представляют собой последовательность ба= йт в памяти, а являются краткой записью для System.Double и System.Int32 (на 32-х разрядных машинах) соответственно. Появились интерфе= йсы, позволяющие описать набор функций, необходимых для совместим= ости с каким-либо классом, и еще много чего, в основном не нужного нов= ичкам.
И не надо забывать, что в .= NET сохранена система windows сообщений и событий. Так что иметь пре= дставление о том, как работает Windows операционка необходимо хотя бы на = начальном уровне.
Основы работы системы= b>
Специалистов заранее прошу = не кривится - я буду писать только о самых основах и простым языком. То что нас= интересует - это система событий (event) и их контроля. Что бы ни произошло в= компе - дернулась мышка, нажали клавишу или открыли окно - это превра= щается в event. Event'ы бывают разные - на все случаи жизни. При желании вам никто не мешает сделает свое событие, впрочем это довольно редко= нужно, хотя уметь полезно. Когда Event создан - он рассылается всем заин= тересованым лицам :), своего рода рассылка по интересам. Ваша программа, ес= ли она хочет получать информацию о каких-то событиях - должна под= писаться на них. Все довольно просто:
Если вам надо чтобы ваша пр= ограмма получала информацию о событии нажатия кнопки - подпишитесь:= p>
&nb= sp;кнопка1.Click +=3D new EventHandler(нажалиКнопку);
Этой строчкой вы включаете = функцию "нажалиКнопку" в список рассылки информации о событии "Click" (нажатие) для кнопки "кнопка1". Помнить на= до только об одном - любые владельцы рассылок люди хитрые, конкурен= тов не любят и рассылают только "своим", что в переводе на п= рограммирование значит - функция, которую вы вносите в список должна быть именно= такого вида, какого ждут в списке, иначе не примут. Последний момент - Ev= entHandler - это посредник между списком рассылки и получателем, именно он = держит имя функции, которой надо сообщить о событии. EventHandler'ов столько же, сколько и разных event'ов, и он тоже должен быть именно т= ого типа, который ждут.
Как читать чужой код<= /p>
Конечно код бывает разный,= и чтобы читать чужой код, который написан на слабознакомом языке, без ко= мментариев и в непривычной манере нужно не только умение, но и изрядная доля удачи и интуиции. Однако если код довольно простой и написан с к= омментариями, хотя бы с какими-то, то прочитать его проблем обычно не составляе= т. Большой плюс C# в этом отношении, это возможность вбить непонятную строчку в Google или MSDN и почти наверняка найдется пример с под= робным описанием, или что-то подобное. Больше чем про C#, среди .NET языко= в, в сети только про VB написано.
Итак, разбор кода. Рассмот= рим код внутри функции, так как все остальное обычно вопросов не вызы= вает.
Код, как привило, структур= ирован - или форматирован табуляцией, или как-то иначе (например regi= on'ами). Очень часто каждый блок имеет комментарий, описывающий произво= димую внутри операцию - например, "Чтение файла", "Заполн= ение таблицы", "Подбор размеров". Таким образом, мы знае= т что там происходит, осталось понять как. Большинство, т.е. поч= ти все, функции и свойства стандартных классов обладают названия= ми точно описывающими их действия. Если вы не знаете английский - = попробуйте сначала перевести название функции, разбив его на отдельные сло= ва по большим буквам: GetChildFromPoint =3D Get Child From Point =3D= Получить Ребенка Из Точки - получить дочерний элемент управления, наход= ящийся в точке. Другой вариант - переводить имя класса и функцию: Conve= rt.ToString =3D Convert To String =3D Перевести В Строку. Некоторые термины, = конечно, придется поискать в сети и после перевода. А некоторые переве= сти понятно не получится, поэтому придется понять, что именно функ= ция делает. Например, в такой вот строчке:
&nb=
sp;largerImagesComboBox.DataSource
=3D Enum.GetNames(typeof(DesktopBackgroundStyle));
даже если вы понятия не име= ете что такое Enum, можно многое понять из простого перевода: объект = ComboBox (выпадающее окно), его свойство Источник Данных (DataSource) п= риравнивается чему-то непонятному. Логично предположить, что источник данных= указывает на какого-либо вида список значений. Функция, предположим неиз= вестного, класса Enum называется GetNames - получить имена, возвращает = она массив строк, который и заполнит выпадающее окно значениями.
Подведем итоги - ничего тол= ком полезного я не сказал. Все мои советы можно свести к трем фразам:<= /p>
1. внимательно читайте ко= д
2. учите английский, кто = не знает
3. пользуйтесь справкой (MS= DN) и Google'ом.
&nb= sp;
Вторая попытка написать чт= о-то полезное для совсем начинающих в С#.
Чуть-чуть о структуре кода:= верхним элементом структуры являются пространства имен (namespace), = довольно фиктивная вешь, призванная упорядочить ту кучу классов, списков, = интерфейсов и констант, которые уже существуют и будут создаваться. В name= space могут входить другие namespace, классы, enum, константы и интер= фейсы. Класс состоит из функций, переменных и констант. По поводу пер= еменных - они могут быть объявлены на любом уровне, и существовать будут только в пределах (и во время жизни) того блока, в котором объяв= лены. Например: переменная объявленная в классе доступна для всего кл= асса, часто для других классов, живет все время, пока жив класс (если он ст= атичный) или объект класса (если не статичный). Другой пример: переменная = объявленная в пределах блока if { }, который находится внутри блока for { }, = который находится внутри функции класса... такая переменная будет дост= упна только в пределах блока if {}, в котором объявлена и будет жить п= ока не закончится выполнение блока, т.е. даже до конца функции не до= живет.
О том, что такое статическ= ий класс, что значит переменная объявлена и пр - читайте ниже.
Синтаксис деклараций=
Декларации - или объявления= - это определение имени и формирование типа, прикрепляемого к этому имени. Т.е. если вам нужно место для хранения целого числа, вы дол= жны объявить переменную, с понятным вам именем, типа данных целое (in= t). К слову, int =3D integer =3D целое число. И помните, декларация н= е создает объект и не выделяет память - это только закрепление имени за ти= пом. Для создания декларированного объекта необходимо его инициа= лизировать (или определить), т.е. либо присвоить начальное значение, либо з= апустить конструктор.
Декларация классов, функций= и переменных на уровне класса требует указания модификатора доступа. Впроч= ем, если не указывать будет использован стандартный, обычно priva= te. Модификаторы доступа - это ключевые слова, определяющие, отк= уда можно будет получить доступ к декларированной переменной/фун= кции и пр. Их всего несколько:
private - доступ возм= ожен только изнутри класса, в котором объявлена.
protected - доступ воз= можен из этого класса и всех наследников.
internal - доступ воз= можен из всей assembly (сборки/библиотеки, файла короче). Также примен= яется для классов, enum и интерфейсов.
public - доступ возмо= жен откуда угодно. Также применяется для классов, enum и интерфейсо= в.
Есть еще модификаторы сос= тояния, определяющие принципиальное состояние функций:
static - функция ста= тична, т.е. возможно ее исполнение без создания объекта класса. Наприм= ер, функции класса System.Math - статичные, поскольку выполняются пр= остым вызовом, без создания объекта класса Math. Также применяется для классов, тогда все входящие в него функции и переменные должны = быть static.
abstract - функция то= лько декларирована, а определение функции должно быть в классах на= следниках. Используется для создания шаблона класса, все наследники кото= рого обязательно имеют некий набор функций. Может существовать толь= ко в abstract классе. Также применяется для классов.
virtual - функцию мож= но переопределить в классе наследнике.
override - используе= тся для указания, что описываемая функция переопределяет родител= ьскую.
Модификатор доступа для фун= кции/переменной внутри класса не может быть более доступным, нежели модификатор= доступа всего класса.
Переменные, декларируемые = внутри функций модификатора доступа не требуют.
Декларация переменных обяз= ательно состоит из типа данных и имени, остальное - по ситуации и желани= ю.
Примеры деклараций переме= нных:
&nb=
sp;int
i;
&nb=
sp;public
double myDouble1, myDouble2;
&nb=
sp;internal
static System.String const_string =3D "константа";
Декларация функций обязате= льно состоит из типа возвращаемых данных, имени и списка аргументов= , остальное - по ситуации. Возращаемым типом данных может быть void - пустой - т.е. нет возвращаемых данных. Список аргументов тоже может быть = пустым. Функции, в отличии от переменных, не могут быть только декларир= ованы - они обязательно сразу определяются. Единственное исключе= ние - тип функций abstract, они только декларируются, а определя= ются уже в классах наследниках.
Примеры деклараций и опре= деления функций:
&nb=
sp;private
void MyFunc1(int i1, double d1) {}
&nb=
sp;internal
abstract string GetAsString();
&nb=
sp;internal
override string GetAsString() {}
&nb=
sp;public
static double GetSum(double d1, double d2) { return d1+d2; }
Декларация класса может вклю= чать указание класса родителя, через двоеточие, далее через запятую= - подключенные интерфейсы. Интерфейс - список функций. В каком-= то кривом смысле - родительский класс, который можно произвольно п= рилепить куда угодно. Таким образом получаются классы, от разных родите= лей, но поддерживающие одни и те же операции. Довольно удобная вещь.= p>
Пример декларации класса:<= /p>
&nb=
sp;internal
class MyClass1 : MyParentClass1, IInterface1, IInterface2 {
&nb=
sp;}
Декларация конструктора к= ласса может включать указание на другой конструктор, который должен б= ыть запущен до декларируемого, например, конструктор родительс= кого класса:
&nb=
sp;public
class MyClass : ParentClass {
&nb=
sp;public
MyClass(int i1) : base() {
&nb=
sp;}
&nb=
sp;public
MyClass() : this(0) {
&nb=
sp;}
&nb=
sp;}
И последнее - декларация = Enum. Enum =3D enumeration =3D перечисление. Список значений. Создае= тся для двух целей: его можно использовать программисту - связать список= целочисленных значений с понятными словами, и пользоваться словами, а не чис= лами, в которых легко запутаться; его можно использовать пользовате= лям - имена значений в списке можно легко вывести на пользователя.<= /p>
Пример декларации Enum:
&nb=
sp;public
enum ImageFileFormat {
&nb=
sp;JPEG,
&nb=
sp;PNG,
&nb=
sp;TIFF,
&nb=
sp;Bitmap}
&nb=
sp;public
enum SupportedLanguages {
&nb=
sp;Русский=3D1,
&nb=
sp;English=3D10,
&nb=
sp;German=3D11}
Глава 3. Синтаксис деклараций массивов, сво= йств, делегатов. Assembly.
&nb= sp;
Массивы
Итак, массив - набор объек= тов одного типа, имеющий четкую последовательность этих объектов и строго з= аданный размер, т.е. количество объектов в наборе. Массив может быть лю= бого типа, размер массива должен быть больше 0 и меньше чем максимальн= ое значение int (на 32 разрядных машинах - 2^32=3D4294967296).
Декларация массива подобна= декларации переменной, с одним только изменением - после типа деклариру= емого массива добавляются квадратные скобки - [].
Пример:
&nb=
sp;int[]
intArray;
В строчке выше показана д=
екларация
одномерного массива типа int. Массив может быть многомерным. Пр=
ичем
в .NET многомерность может быть разной.
Вариант 1 - многомерный мас= сив старого образца, так называемый массив-массивов:
&nb=
sp;int[][]
int2dArray;
Система простая - каждая ско= бка создает свой массив. Т.е. можно представить это в таком виде: (int[])[] arrayName - массив типа int[], который тоже является м= ассивом. Подобных вложений может быть несколько.<= o:p>
Вариант 2 - многомерный мас= сив .NET:
&nb=
sp;int[,]
int2dArray;
Внутри скобки ставится нуж= ное количество запятых, каждая запятая создает плюс одно измерение массива. Т= .е. в приведенной выше строчке декларируется таблица (двумерный м= ассив) для целочисленных данных.
У каждого варианта есть с= вои плюсы и свои минусы. Если коротко - второй вариант удобнее и чуть= быстрее работает, зато первый вариант позволяет разделять массив на со= ставляющие при необходимости. Примеры использования массивов будут пото= м.
Свойства
Свойства - специальная фич= а для возможности проверки данных, вводимых в переменную и задания к= аких-либо действий при изменении значения. Своего рода посредник между пе= ременной и прочим кодом, а по совместительству - охранник и секретарь эт= ой переменной :). Свойства существуют только внутри класса, вмест= е с переменными уровня класса.
Задаются очень просто:
&nb=
sp;private
float _size; //переменная
&nb=
sp;public
property float Size { //свойство - посредник переменной _size
&nb=
sp;get {
&nb=
sp;return
_size;
&nb=
sp;}
&nb=
sp;set {
&nb=
sp;//проверить
на правильность
&nb=
sp;_size
=3D value;
&nb=
sp;OnSizeChanged();
&nb=
sp;}
&nb=
sp;}
Сначала, как обычно, модифи=
катор
доступа. Ключевое слово property указывает, что декларируется
свойство. Далее указывается тип данных, в примере - одинарное д=
робное.
Затем имя свойства и открывается фигурная скобка, начинающая =
блок
описания. Любое свойство должно иметь блок get - получить. Если с=
войство
только-для-чтения, оно не имеет блока set - установить. В каждом бл=
оке
прописывается, что надо сделать. Помните, что свойство не хранит
данные, т.к. не является переменной - оно лишь посредник, поэтом=
у в
блоке get указано - вернуть значение переменной _size, в которой
значение хранится реально. В блоке set написано, что делать при =
установке
нового значения - сначала проверить на правильность... ну, напри=
мер,
чтобы вводили от 0 до 1, когда нужно и т.п. После проверки - уста=
новить
значение; ключевое слово value, в данном случае, указывает на вх=
одящее
значение. Ну и под занавес - выполнить событие "размер изме=
нился".
Завершает декларацию закрывающаяся фигурная скобка.
Свойства позволяют создав= ать все переменные как private, а нужные для открытого доступа выв= одить через свойства, имея таким образом гарантию, что любое внешнее из= менение значения будет проверено на правильность.
Делегаты
Делегаты - если просто, то э= то указатели на функции. Если вам надо передать функцию как аргумент, то у вас= два пути - либо через строковое название функции искать ее в библио= теке (assembly) и вызывать как внешнюю функцию, либо использовать деле= гат. Делегат позволяет определить подпись функции - т.е. возвраща= емый тип, типы и количество аргументов.
Делегат - это объект уровня = класса, собственно это и есть класс, только очень особый. Однако деклара= ция его очень похожа на декларацию абстрактной функции:
&nb=
sp;public
delegate double MyDelegate(double d1, double d2);
Не забывайте, что делегат=
- это
класс, и для того, чтобы его можно было использовать, необходимо=
создать
объект делегата, который приписывается к конкретной функции=
и
дальше передается, если нужен. У делегата есть метод Invoke, к=
оторый
собственно и запускает функцию, на которую делегат указывае=
т.
Пример использования деле= гата внутри функции:
&nb=
sp;MyDelegate
md =3D new MyDelegate(MyFunction);
&nb=
sp;md.Invoke(d1,
d2);
В примере предполагаетс=
я, что
d1 и d2 - переменные типа double, a функция MyFunction имеет по=
дпись:
double MyFunction(double d1, double d2).
Assembly
Кратко: Assembly =3D сборка,= файл, являющийся контейнером для namespace/классов/констант/делега= тов/интерфейсов и ресурсов (картинок/текста/иконок и пр.). Assembly может быть за= пускным (exe) или не запускным (dll). Отличие только в том, что в запускном файле прописан метод WinMain (или main для консольных приложени= й). Несмотря на расширения файла, ничего общего (кроме пользовате= льского применения) со старыми (до .NET) файлами не имеет. Основные два о= тличия от win32 стандартов - assembly имеет собственное описание внутри= себя. Описание включает: версию, автора, параметры безопасности не= обходимые для выполнения и многое другое, в частности - описание подписей всех функций, переменных и т.д. Т.е. не надо знать заранее как вы= зывать ту или иную функцию из библиотеки - это можно узнать на лету. Втор= ое отличие - assembly хранит код не в виде ассемблера, а в MSIL - Mic= rosoft Intermediate Language - своеобразный мета-язык, который компи= лируется на лету и по запросу (just-in-time compiling and debugging) в соот= ветствии с требованиями системы на которой компилируется (разрядность= процессора и т.д.). Плюсы такого подхода - кросс-платформенность и кросс-сист= емность, а также безопасность выполнения. Минусы - код всегда открыт и д= оступен любому для прочтения, а также уходит время и ресурсы на компиля= цию. Впрочем, книги и музыку тоже может любой прочитать/услышать... и = почему-то авторов это не напрягает, а плагиаторов не так уж много...
Assembly может быть одномо= дульным и многомодульным. Модуль - часть assembly, как правило, до сборки = представляющая собой отдельный файл. Т.е. несколько библиотек классов можно объе= динить в одну assembly, и добавить еще картинки до кучи. Каждая картинка= и каждая библиотека будут отдельным модулем внутри единой assembl= y. Плюсов в таком подходе маловато, поэтому редко используется. = MS Visual Studio даже не имеет возможности создавать мультимодуль= ные assembly в интерфейсном режиме.
Лично я отношусь к assembly= по старому - как к exe и dll win32 стандарта. Единственное, о чем при= ходится помнить - assembly должны иметь идентификаторы, если ими собира= етесь пользоваться не вы один. Т.е. версия, автор и разрешения безопас= ности должны быть прописаны. Да и подписать assembly ключом strong name= тоже нужно. Ключ strong name - уникальный идентификатор assembly, кото= рый используется для распознавания разных assembly с одним именем н= а одной машине. Для подписывания есть консольная утилита sn.exe, а в VS20= 05 есть и интерфейсные опции в свойствах проекта для этой цели.
Если кто-то хочет разобрать= ся подробнее - есть неплохая статья (кусок книги) на codeproject: лежит здесь - http://www.codeproject.com/books/1893115593_6.asp.
Глава 4. Основные операторы и конструкты.
&nb= sp;
Рассмотрев синтаксис декла= раций, переходим к основным конструктам или командам языка. Как извес= тно, код внутри функции выполняется построчно, если не используют= ся какие-либо команды. Другими словами - команды языка призваны о= беспечить нелинейность выполнения кода. Самая известная из не-линейных к= оманд - goto - есть в языке C#, однако я настоятельно не рекомендую ей = пользоваться. Остальных команд вполне достаточно, чтобы обеспечить любую нели= нейность, а привыкать к goto - это как правило означает привычку к плохой структуризации кода.
Но сначала, чтобы было про= ще понимать команды, рассмотрим некоторые операторы языка.
Операторы
Операторы сравнения:
‹ - строго меньше. Опреде= лен для любых числовых типов (int, double, short, byte, decimal, float= , bool)
› - строго больше. Аналоги= чно предыдущему.
‹=3D - меньше или равно. Ана= логично предыдущему.
›=3D - больше или равно. Ана= логично предыдущему.)
=3D=3D - равно (эквивалент= но). Определен для большинства типов и классов. Для многих классов означает име= нно идентичность объектов класса, а не равенство их внутренних знач= ений.
!=3D - не равно. Аналогичн= о предыдущему.
Обратите внимание: проверк= а на равенство обозначается двумя знаками равно! Одним знаком обоз= начается оператор присвоения значения.
Логические операторы:
|| - логическое ИЛИ.
&& - логическое И= .
! - логическое НЕ.
Арифметические операторы:= p>
помимо стандартных +, -, *,= / есть их модификации со знаком равно: +=3D, -=3D, *=3D, /=3D. Означа= ют "выполнить оператор и приравнять", т.е.
&nb=
sp;i +=3D
10;
означает прибавить 10 к зна=
чению
переменной i, и записать результат в нее же. Это аналогично запи=
си:
&nb=
sp;i =3D i
+ 10;
есть еще два оператора:
++ - прибавить единицу к п= еременной и записать результат в нее. Инкрементный оператор.
-- - отнять единицу от зна= чения переменной и записать результат в нее. Декрементный оператор.<= /p>
Основные конструкты можно= разделить на цикловые - позволяющие запускать кусок кода в цикле, и услов= ные - позволяющие выполнять нужный кусок кода, выбираемый по услов= ию. Я опишу основные конструкты, если какой забуду, а вам он интер= есен - пишите.
Циклы
Самый распространенный ц= икл - for. Смысловое предназначение - выполнять последовательность д= ействий заданное количество раз. Задается следующим образом:
&nb=
sp;for
(i =3D 0; i ‹ 10; i++) { /*Ваш код здесь*/ }
После ключевого слова for =
следует
круглая скобка, внутри кторой обязательно присутствуют три выр=
ажения,
разделенные точкой с запятой. Первое выражение, в нашем случае
"i=3D0" - операция, которую надо выполнить перед начало=
м цикла.
Зачастую в этом месте пишут декларацию переменной, которая су=
ществует
только для цикла, тогда выражение выглядит, например так: "i=
nt i
=3D 0". С тем же успехом поле может быть пустым. Второе выраже=
ние
устанавливает условие, при котором цикл должен продолжаться...=
его
можно рассматривать как "Продолжать цикл пока выражение 2 ис=
тинно".
Может быть пустым, но тогда вам самим придется внутри цикла прове=
рять
условие выхода и самим же выходить из цикла. Третье выражение за=
дает
операцию, которую надо выполнить по завершении каждого круга ц=
икла.
Тоже может быть пустым.
Итак в строчке примера пок= азан цикл, обнуляющий переменную i вначале, исполняющийся пока пер= еменная i меньше 10, после каждого круга увеличивающий i на 1, что дает 1= 0 выполнений цикла, при нормальных условиях.
Для дополнительного упра= вления выполнением цикла предусмотрены еще два ключевых слова: conti= nue - завершает текущий круг выполнения, break - завершает цикл.
&nb=
sp;for
(int i =3D 0; i ‹ 10; i ++) { // цикл на 10 кругов
&nb=
sp;if
(some_array[i] =3D=3D 0) { continue;} // если i-тая ячейка массива р=
авна 0
- пойти на след круг
&nb=
sp;int
res =3D DoSomething(some_array[i]); // что-то сделать с ячейкой масс=
ива и
вернуть значение
&nb=
sp;if
(res =3D=3D -1) { break;} // если вернулось -1 - прервать цикл
&nb=
sp;}
Цикл while (бывший do...whi= le). Смысловое предназначение - выполнять последовательность дейст= вий, пока что-то не случится/изменится.
задается так:
&nb=
sp;while
(flag =3D=3D true) { /*Ваш код здесь*/ }
После ключевого слова whi=
le следует
круглая скобка, в которой задается логическое выражение, пров=
еряемое
на истинность каждый круг цикла. Цикл выполняется пока выражени=
е истинно,
или до ключевого слова break, внутри цикла. Слово continue приме=
нимо
к этому циклу так же, как и к предыдущему.
Цикл foreach. Смысловое пр= едназначение - выполнить кусок кода для каждого члена массива/списка/коллекц= ии. В .NET появились классы обеспечивающие ненумерованные, динами= ческие массивы. В основном для них и был сделан этот оператор.
задается так:
&nb=
sp;foreach
(Class objectName in collectionName) { /* Ваш код здесь */ }
После ключевого слова for=
each
следует круглая скобка в которой задается класс одиночного объ=
екта,
над которым выполняется операция в основном блоке, имя объекта=
(переменной)
для обращения в основном блоке, затем ключевое слово in и имя пе=
ременной,
указывающее на массив/список/коллекцию объектов того класса, ко=
торый
указали в начале условия. Основной код, как обычно, пишется в фи=
гурных
скобках.
Ну, например, часто встреч= ается - цикл по всем файлам какого-либо типа в каталоге:
&nb=
sp;DirectoryInfo
di =3D new DirectoryInfo("C:\\temp"); // создать объект ин=
формации
о каталоге С:\temp
&nb=
sp;foreach
(FileInfo fi in di.GetFiles("*.txt")) { // для каждого объ=
екта
типа FileInfo в коллекции, возвращаемой функцией GetFiles
&nb=
sp;//
что-то сделать
&nb=
sp;}
Условия
Условие if...else. Смысловое= предназначение - выполнить блок кода, только если условие истинно... с вариант= ом - если ложно - выполнить другой кусок кода.
задается так:
&nb=
sp;if
(flag =3D=3D true) { /* Ваш код здесь */ }
&nb=
sp;else
if (flag2 =3D=3D true) { /* Ваш код здесь */ }
&nb=
sp;else
{ /* Ваш код здесь */ }
После ключевого слова if с=
ледует
логическое условие в круглых скобках, при истинности которого =
выполняется
код, расположенный в фигурных скобках. Если условие ложно - вып=
олняется
следующий блок. Следующий блок должен начинаться с ключевого сло=
ва
else, за которым может следовать слово if и еще одно условие. Та=
кая
цепочка может быть длинной, но надо помнить, что после выполнени=
я хотя
бы одного блока цепочка прерывается.
Пример:
&nb=
sp;bool
flag1 =3D true, flag2 =3D false;
&nb=
sp;if
(flag1 && flag2) {DoSomething(); }
&nb=
sp;else
if (flag1) { DoSomething2(); }
&nb=
sp;else
{ DoSomething3(); }
В этом пример будет выполн=
ена
только функция DoSomething2(). В первом условии мы проверяем оба
флага на истинность, если хотя бы один не равен истинно, переход=
им
на второй блок. Второй блок тоже с условием - проверяем первый фл=
аг
на истинность - мы точно знаем, что хотя бы один флаг ложен, если пе=
рвый
истиннен - второй ложен. Третий блок - если первый не истиннен, то=
либо
второй истиннен, либо оба ложны, но возможно нам это уже не важно..=
.
Пример 2:
&nb=
sp;int i
=3D 1;
&nb=
sp;if (i
=3D=3D 0) { DoSomething1(); }
&nb=
sp;else
if (i =3D=3D 1) { DoSomething2(); }
&nb=
sp;else
if (i =3D=3D 2) {DoSomething3(); }
&nb=
sp;else
{ DoElse(); }
В этом, крайне неграмотном=
примере,
тоже будет выполнена только функция DoSomething2(). А грамотно=
такую
ситуацию расписывать через оператор switch;
Условие switch...case. Смыс= ловое предназначение - выполнять куски кода по значению переменной. = Своеобразная развилка (или переключатель). С дополнительным возможностями.=
задется так:
&nb=
sp;switch
(var) {
&nb=
sp;case
‹val1›:
&nb=
sp;DoSomething();
&nb=
sp;case
‹val2›:
&nb=
sp;DoSomething2();
&nb=
sp;break;
&nb=
sp;default:
&nb=
sp;DefaultAction();
&nb=
sp;break;
&nb=
sp;}
После ключевого слова swit=
ch следует
имя переменной, по чьему значению нужно разветвить код. В фигурн=
ых
скобках задаются варианты значений, которые должны обрабат=
ываться.
Вариант значения задается как ключевое слово case, значение пе=
ременной,
двоеточие. Если функциональный блок заканчивается словом brea=
k -
программа выходит из блока switch после него, если не заканчива=
ется
- программа переходит на выполнение следующего по порядку бло=
ка
case. ключевое слово default на месте case означает "все ва=
рианты".
Пример:
&nb=
sp;int i
=3D 1;
&nb=
sp;switch
(i) {
&nb=
sp;case
2:
&nb=
sp;DoSomething2();
&nb=
sp;break;
&nb=
sp;case
1:
&nb=
sp;DoSomething1();
&nb=
sp;case 3:
&nb=
sp;DoSomething3();
&nb=
sp;break;
&nb=
sp;default:
&nb=
sp;DoDefault();
&nb=
sp;break;
&nb=
sp;}
В этом примере будут выпол=
нены
функции DoSomething1() и DoSomething3().
Переменная может быть не чис= ловой:
&nb=
sp;char
c =3D '!';
&nb=
sp;switch
(c) {
&nb=
sp;case
'?':
&nb=
sp;DoQuestion();
&nb=
sp;break;
&nb=
sp;case
'!':
&nb=
sp;DoExclamation();
&nb=
sp;break;
&nb=
sp;}
В этом примере будет выпол=
нена
только функция DoExclamation(), а если переменная с будет рав=
на
не вопросительному знаку и не восклицательному - ничего сдел=
ано
не будет.
По поводу нахождения осн= овного кода внутри фигурных скобок - если код состоит из одной строчки, = можно писать его без фигурных скобок:
&nb=
sp;if
(flag =3D=3D true) return;
Глава 5. Инициализация переменных. Коллек= ции. Типы данных.
&nb= sp;
Инициализация.
Я уже упоминал это слово, в= таком примерно контексте - После декларации переменной, перед ее ис= пользованием, необходимо эту переменную инциализировать. Попробуем разоб= раться точнее: инициализация - процесс выделения памяти под переменн= ую и заполнение этой памяти значениями по умолчанию. Значения по умо= лчанию - это, почти всегда, разные формы нуля: для int - 0, для double - 0.= 0, для string - "", для bool - false и т. д.
Та часть, которая касаетс= я выделения памяти, это теперь дело компилятора и к программисту почти не и= меет никакого отношения. С точки зрения программиста, инициализац= ия - первое присвоение значения переменной.
Есть такая тонкость - пере= менные, объявленные на уровне класса инициализируются автоматически= при создании объекта класса. Но это относится только к основным тип= ам данных (int, double, bool, byte и т. д.) кроме string. Все переменн= ые классов получают при автоматической инициализации значение n= ull и string тоже.
Комментарий для специалис= тов: если быть точным, то null - это значение по умолчанию для всех пере= менных классов MarshalByReference - обрабатываемых через указател= ь. Основные типы данных являются классами MarshalByValue - обр= абатываемых через значение, поэтому им присваиваются нормальные значения. Класс string по прежнему является массивом, хоть и очень хитрым, и = обрабатывается соответственно.
Инициализация бывает двух ти= пов:
для простых типов данных эт= о простое присвоение значения:
&nb=
sp;int i
=3D 0;
&nb=
sp;double
d =3D 0.2;
&nb=
sp;string
s =3D "test string"
Все это варианты деклара=
ции с
одновременной инициализацией для простых типов данных.
Второй вариант - инициали= зация объекта класса:
&nb=
sp;DateTime
dt =3D new DateTime();
&nb=
sp;MyClass
mc =3D new MyClass();
&nb=
sp;int[]
intArray =3D new int[20];
Обратите внимание - массив=
ы рассматриваются
как классы.
Для специалистов: масси= в, как известно, обрабатывается по указателю. А простые типы данных= тоже могут быть инициализированы как классы: int i =3D new int(); вполн= е правильная запись. Более того, запись "int i =3D 2;" для компилятора= равна "int i =3D new int(); i =3D 2;".
Инициализировать переменную= можно почти в любой момент - главное, до первого обращения. Такая запи= сь вполне допустима:
&nb=
sp;public
partial class Form1 : Form
&nb=
sp;{
&nb=
sp;public
DateTime dt =3D new DateTime();
И последнее: ошибка при ком=
пиляции
"Use of unassigned local variable ..." значит, что вы заб=
ыли
инициализировать переменную, декларированную внутри функции=
, а
ошибка "Null reference exception ..." при работе програ=
ммы
означает, что вы забыли создать объект класса для переменной, д=
екларированной
на уровне класса.
Коллекции/списки и массив= ы
Что такое массивы мы уже ра= ссмотрели, теперь рассмотрим такую вещь как коллекцию/список и сравним.
Список - это любой класс, ко= торый поддерживает интерфейс IList, позволяющий создавать динамиче= ские массивы. Динамический массив - набор объектов одного класса, с= изменяющимся количеством и порядком объектов по ходу выполнения программы.= Базовый интерфейс IList содержит функции для добавления нового объект= а к списку, убирания объекта из списка, вставки объекта в список, по= иска объекта и очистки списка.
Коллекция - любой класс поро= жденный от класса CollectionBase, обеспечивающего функции поддержки списка. Класс CollectionBase поддерживает интерфейс IList.
Есть модификации этих двух= классов для создания коллекций только-для-чтения, разнотиповых коллекци= й, парных массивов (хешей) и пр.
Преимущества списков - дина= мичность выделяемой памяти.
Преимущества массивов - ско= рость.
Как пользоваться - если оч= ень не лень, или очень надо - можете всегда создать свой класс. Такой вари= ант мы рассмотрим позже, когда будем говорить о наследовании. Проще= всего пользоваться классом ArrayList:
&nb=
sp;ArrayList
al =3D new ArrayList();
&nb=
sp;int i
=3D 2;
&nb=
sp;double
d =3D 4.5;
&nb=
sp;string
s =3D "test";
&nb=
sp;MyClass
mc =3D new MyClass();
&nb=
sp;al.Add(i);
&nb=
sp;al.Add(d);
&nb=
sp;al.Add(s);
&nb=
sp;al.Add(mc);
Таким путем мы добавили са=
мые
разные элементы в список и можем до любого из них достучаться как=
до
элемента массива:
&nb=
sp;string
s2 =3D (string)al[2];
s2 будет равно "test&qu=
ot;.
Есть только одно неудобство - ArrayList всегда возвращает пом=
ещенный
в него объект как object, поэтому для нормальной работы нужно пре=
образовывать
возвращаемое значение в нужный тип... что не всегда просто. Именно
это преобразование и совершает слово string, стоящее в круглых с=
кобках
перед обращением al[2]. Подробнее об этом - чуть ниже.
Типы данных
У каждой переменной есть с= вой тип данных или просто тип, то же можно сказать о возвращаемом любой функцией значении (void тоже тип, хотя и особый). В .NET все типы данных являются дочерними от Object, у которого есть всего 4 фу= нкции, зато 2 из них нужны очень часто:
Object.ToString() - возвращ= ает строковое представление об объекте (для чисел - строку с числом,= для сложных объектов - некую строку, описывающую главную информацию= объекта; например FileInfo - класс информации о файле - в методе ToStri= ng() возвращает полный путь файла.)
Object.GetType() - возвраща= ет объект класса Type, описывающий тип данных, которому принадлежи= т исследуемый объект.
Для сравнения типов есть сп= ециальный оператор 'is'. Используется так:
&nb=
sp;if
(var is int) { do something }
вместо var - имя переменной=
,
вместо int - тип данных (имя класса) с которым вы хотите сравнить.<=
span
style=3D'color:black'>
Рассмотрим пример из предыд= ущего параграфа - у нас есть ArrayList из 4 объектов, 4 разных типов:
&nb=
sp;for
(int i =3D 0; i ‹ al.Length; i ++) {
&nb=
sp;if
(al[i] is int) { /*сделать что-то с целым числом */ (int)al[i]}
&nb=
sp;else
if (al[i] is double} { /*сделать что-то с дробным числом*/ (double)a=
l[i]}
&nb=
sp;else
if (al[i] is string} { /*сделать что-то со строкой*/ (string)al[i]}
&nb=
sp;else
{ MessageBox.Show(al[i].ToString()); } // показать сообщение со с=
троковым
представлением объекта
&nb=
sp;}
В этом примере мы пробега=
ем циклом
по списку, смотрим на тип очередной переменной, если это что-то пр=
остое
- преобразуем и что-то делаем, если нет - показываем в сообще=
нии
строковое представление объекта.
Теперь о преобразовании. = Преобразованием типов данных называют два разных процесса.
Один - это конвертация (Con= vert) - настоящая смена типа. Например, превратить double в int - т.е. = отбросить дробную часть, или округлить до ближайшего целого. Или, еще напри= мер, превратить double в string - т.е. создать строку, где записано чис= ло, хранимое в переменной.
Второй - это смена типа (cas= t) - без замены данных. Переменная типа object может на самом деле хр= анить любой объект, и чтобы компилятор понял, что от него хотят - надо = преобразовывать. Бывают и более сложные вещи.
Конвертация совершается вс= егда через функцию, которая знает, как именно надо преобразовывать = типы. А все, что касается строк - надо знать еще и настройки культуры дл= я которой ведется преобразование. Есть специальный класс Convert, которы= й ведает конвертацией. Им и рекомендуется пользоваться.
&nb=
sp;double
d =3D 4.2;
&nb=
sp;string
s1 =3D d.ToString();
&nb=
sp;string
s2 =3D Convert.ToString(4.2);
s1 и s2 - одинаковы.
Преобразование может быть: вс= троенным (implicit - неявным), надстроенным (explicit - явным) и еще одним :)... назовем его условным.
Встроенное - оно либо есть, л= ибо нет. Обеспечивается создателем класса. Т.е. например int в doub= le преобразуется легко, самим компилятором, а вот наоборот - толь= ко человеком.
Надстроенное - это то, что пи= шется в скобках перед именем переменной. Например, можно вот так вот сде= лать:
&nb=
sp;CheckBox
cb =3D new CheckBox();
&nb=
sp;object
obj =3D cb;
&nb=
sp;((CheckBox)obj).Checked
=3D true;
Условное работает только с=
объектами
классов и не работает с простыми типами данных, кроме string. Выг=
лядит
оно как оператор 'as' - 'как' (рассматривать как).
&nb=
sp;string
s =3D al[2] as string;
Оператор говорит компилят=
ору,
что подсовываемую ему переменную надо рассматривать "как&qu=
ot; указанный
тип данных.
&nb= sp;
Итак, для первой части, я ду= маю достаточно. С тем, что уже было рассмотрено, можно начинать прог= раммировать, а знающие другой язык должны были уже получить представление о C= #. Остались нерассмотренными еще многие вещи, включая некоторые и= з основных, но в силу того, что они несколько сложнее - я рассмотрю их в следу= ющих главах.
Пример кода:
&nb=
sp;namespace
tst_form2 //задаем namespace
&nb=
sp;{
&nb=
sp;public
delegate double Delegate1(); // в нем декларируем делегата
&nb=
sp;public
abstract class MyParentClass // создаем класс, абстрактный
&nb=
sp;: Object,
tst_form3.IInterface1 // порожденный от класса Object с подключе=
нным
интерфейсом tst_form3.IInterface1
&nb=
sp;{
&nb=
sp;private
int int1; // внутреннее поле, доступное только внутри класса
&nb=
sp;protected
int Int1 { // свойство для этого поля, доступное наследникам
&nb=
sp;get {
return int1; } // возвратный код
&nb=
sp;set {
//установочный код
&nb=
sp;if
(value › 0) { // проверка на значение
&nb=
sp;int1
=3D value; // если прошли - установить значение
&nb=
sp;}
&nb=
sp;else
throw new ArgumentException("Value must be › 0",
"Int1"); // если нет - кинуть ошибку
&nb=
sp;}
&nb=
sp;}
&nb=
sp;public
double d1; // открытые поля
&nb=
sp;public
double d2; //
&nb=
sp;public
MyParentClass() { //основной конструктор класса
&nb=
sp;int1
=3D 0; // инициализируем переменную начальным значением
&nb=
sp;}
&nb=
sp;public
MyParentClass(double inD1, double inD2) // дополнительный конс=
труктор
класса
&nb=
sp;:
this() { // который сначала запускает основной конструктор и т=
олько
потом исполняется
&nb=
sp;d1 =3D
inD1; // инициализируем переменные входящими аргументами
&nb=
sp;d2 =3D
inD2; // если аргументов нет - переменные инициализируются са=
ми,
стандартными значениями
&nb=
sp;}
&nb=
sp;protected
abstract double GetSum(); // шаблон функции для инициализации =
в дочерних
классах
&nb=
sp;#region
IInterface1 Members // функции интерфейса
&nb=
sp;public
abstract double GetSubstract(); // функция интерфейса - тоже
только шаблон
&nb=
sp;#endregion
&nb=
sp;}
&nb=
sp;namespace
tst_form3 // объявляем еще namespace внутри tst_form2
&nb=
sp;{
&nb=
sp;public
class MyChildClass //создаем класс
&nb=
sp;:
tst_form2.MyParentClass //порожденный от tst_form2.MyParentClass=
&nb=
sp;{
&nb=
sp;private
Delegate1 getSumDel; // декларируем объект делегата
&nb=
sp;internal
MyChildClass() //основной конструктор
&nb=
sp;: base()
{ // запускающий родительский и только потом исполняющийся
&nb=
sp;getSumDel
=3D new Delegate1(GetSum); // инициализируем объект делегата -=
на
функцию GetSum
&nb=
sp;}
&nb=
sp;internal
MyChildClass(double inD1, double inD2) //дополнительный конст=
руктор
&nb=
sp;: base(inD1,
inD2) { // запускающий дополнительный родительский конструкт=
ор
&nb=
sp;getSumDel
=3D new Delegate1(GetSum); // инициализирующий объект делегата=
&nb=
sp;}
&nb=
sp;#region
inherited members // унаследованные функции
&nb=
sp;protected
override double GetSum() { // определяем родительскую абстр=
актную
функцию
&nb=
sp;return
d1 + d2; //возвращаем сумму
&nb=
sp;}
&nb=
sp;public
override double GetSubstract() { // определяем родительскую=
абструктную
функцию
&nb=
sp;return
d1 - d2; // возвращаем разность
&nb=
sp;}
&nb=
sp;#endregion
&nb=
sp;internal
void ChangeInt1(int inInt) { // создаем функцию для смены значени=
я
&nb=
sp;Int1
=3D inInt;
&nb=
sp;}
&nb=
sp;public
double TryToGetSum() { //функция получения суммы
&nb=
sp;return
StaticFunc.GetSumFromChildClass(getSumDel); // вызываем ста=
тичную
функцию другого класса с делегатом для GetSum
&nb=
sp;}
&nb=
sp;}
&nb=
sp;public
interface IInterface1 //интерфейс
&nb=
sp;{
&nb=
sp;double
GetSubstract(); // функции интерфейсов объявляются без модиф=
икаторов
&nb=
sp;}
&nb=
sp;}
&nb=
sp;internal
class StaticFunc //класс для нашей статичной функции
&nb=
sp;{
&nb=
sp;public
static double GetSumFromChildClass(Delegate1 del) {
&nb=
sp;return
del.Invoke(); // возвращаем результат вызова функции, приписан=
ной
к делегату
&nb=
sp;}
&nb=
sp;public
enum SupportedLanguages //простой Enum
&nb=
sp;{
&nb=
sp;Русский
=3D 1,
&nb=
sp;English
=3D 10,
&nb=
sp;German
=3D 11
&nb=
sp;}
&nb=
sp;public
static object[] objArray =3D {1, 2.4, "string", SupportedL=
anguages.Русский
}; // статичный массив
&nb=
sp;}
&nb=
sp;}//закрываем
namespace tst_form2
Для тестрования - напише=
м еще
такую функцию в любом другом namespace... хоть в основном, созда=
нном
автоматически при создании проекта:
&nb=
sp;private
void button1_Click(object sender, EventArgs e) { //при нажатии на кн=
опку
&nb=
sp;tst_form2.tst_form3.MyChildClass
mcc =3D new tst_form2.tst_form3.MyChildClass(1.2, 2.4); // создаем объ=
ект
нашего класса с введенными значениями
&nb=
sp;double
tmp_double =3D mcc.TryToGetSum(); //вернет 3.6
&nb=
sp;tmp_double
=3D mcc.GetSubstract(); //вернет -1.2
&nb=
sp;/*mcc.ChangeInt1(-2);
//произойдет ошибка - неверный параметр*/
&nb=
sp;mcc.ChangeInt1(2);
//int1 станет равен 2
&nb=
sp;int
i1 =3D (int)tst_form2.StaticFunc.SupportedLanguages.English; // i1 =
=3D 10
&nb=
sp;string
s =3D tst_form2.StaticFunc.SupportedLanguages.English.ToString();/=
/s=3D"English"
&nb=
sp;s =3D
"";
&nb=
sp;for
(int i =3D 0; i ‹ tst_form2.StaticFunc.objArray.Length; i++) {
&nb=
sp;s +=3D
tst_form2.StaticFunc.objArray[i].ToString() + "\t";
&nb=
sp;if
(tst_form2.StaticFunc.objArray[i] is int) s +=3D "\n";
&nb=
sp;else
{
&nb=
sp;switch
(tst_form2.StaticFunc.objArray[i].GetType().ToString()) {
&nb=
sp;case
"System.Double":
&nb=
sp;s +=3D
Math.Round((double)tst_form2.StaticFunc.objArray[i]).ToString() +
"\n";
&nb=
sp;break;
&nb=
sp;case
"tst_form2.StaticFunc+SupportedLanguages":
&nb=
sp;s +=3D
((int)(tst_form2.StaticFunc.SupportedLanguages)tst_form2.StaticFun=
c.objArray[i]).ToString();
&nb=
sp;break;
&nb=
sp;default:
&nb=
sp;s +=3D
"\n";
&nb=
sp;break;
&nb=
sp;}
&nb=
sp;}
&nb=
sp;} //
цикл завершается s =3D "1
&nb=
sp;//2.4
2
&nb=
sp;//string
&nb=
sp;//Русский
1"
&nb=
sp;}
Немного дополнений и комме= нтариев:
Если у функции есть модифи= катор override - ее можно переопределять во всех дочерних классах, лю= бого поколения.
Если вы хотите сразу опре= делить функцию, но дать ей возможность быть переопределенной в наследн= иках - присвойте модификатор virtual.
Последовательное выполнение= условий в switch блоке возможно только если case условия определены чис= лами.
По поводу плюсика в имени= типа Enum - Enum, по логике, надо объявлять в namespace, наравне с кла= ссами. Однако, можно их запихать и внутрь класса. Если их объявить в name= space - имя типа будет построено как обычно, а если внутри класса - то ч= ерез плюсик.
&nb= sp; Чуть более сложные вещи в языке C# и платформе .NET.
Глава 1. Модификаторы аргументов. Регулярные выраже= ния.
&nb= sp;
Рассмотрим модификатор= ы аргументов функций.
ref - передает в ка= честве аргумента ссылку на объект. Таким образом вы получаете возможн= ость изменять объект, не создавая новый, и не возвращая его, как резу= льтат функции.
Пример:
&nb=
sp;private
void Func1(ref string str) {
&nb=
sp;str =3D
"новая строка";
&nb=
sp;}
&nb=
sp;private
void MainFunc() {
&nb=
sp;string
str =3D "строка";
&nb=
sp;Func1(ref
str); // str =3D "новая строка"
&nb=
sp;}
out - передает в ка=
честве
аргумента ссылку на неинициализированный объект. Таким образ=
ом
вы получаете возможность возвращать множественные результаты=
работы
функции.
Пример:
&nb= sp;private void Func1(out string result1, out int result2, out object result3) {<= o:p>
&nb=
sp;result1
=3D 10;
&nb=
sp;result2
=3D "результат";
&nb=
sp;result3
=3D new object();
&nb=
sp;}
&nb=
sp;private
void MainFunc() {
&nb=
sp;int
r1;
&nb=
sp;string
r2;
&nb=
sp;object
r3;
&nb=
sp;Func1(out
r1, out r2, out r3);
&nb=
sp;}
Разница между ref и out толь=
ко в
том, что out не требует инициализации аргумента до вызова функ=
ции,
а требует ее внутри функции. ref - наоборот, требует передачи и=
нициализированного
объекта.
param - очень хитрая в= ещь, позволяет делать функции с переменным количеством аргументо= в. Требования просты - param аргумент должен быть последним в списк= е аргументов, аргументы должны быть одного типа, аргументы задаются как мас= сив.
Например так:
&nb=
sp;private
void Func1(byte b1, param int[] data) {}
Если вам необходимо, чтоб=
ы аргументы
были разнотипны - считайте их типом object. Еще один момент - вмес=
то
списка аргументов в строке, можно передавать массив... но тут на=
до
быть осторожным:
&nb=
sp;private
void Func1(byte b1, param object[] data) { }
&nb=
sp;private
void MainFunc() {
&nb=
sp;Func1(1,
1, "str", new object()); // все нормально. data - массив ти=
па
object из трех членов.
&nb=
sp;Func1(2,
new object[] {1, "str", new object()}); // все нормально, da=
ta -
как в предыдущем варианте.
&nb=
sp;Func1(3,
1, new object[]{"str", new object()}); // data - массив типа=
object
из двух членов, второй - массив типа object из 2х членов.
&nb=
sp;}
Регулярные выражения
В общем виде регулярные в= ыражения ( =3D regular expressions =3D RegEx) - это шаблон текстовой стро= ки. Почти все задачи касающиеся поиска и замены в строках предпочтитель= но решать через них. Задачи поиска и замены включают в себя поиск = текста, редактирование текста, преобразование текста (форматирова= ние), выбирание полезной информации из текста для последующего исп= ользования и пр. Пользоваться регулярными выражениями имеет смысл когда о= бъем текста достаточно велик. Для работы с регулярными выражения= ми существует класс Regex (System.Text.RegularExpressions.Regex).=
Использоваться этот класс мож= ет тремя путями:
1. пользоваться статичес= кими методами класса. Предпочтительно, если программе это редко нуж= но, и не при каждом запуске, а также, если само выражение используе= тся только один раз подряд.
2. Создавать объект класса= regex в некомпилируемом виде (флаг Compiled не установлен) и пользов= аться его функциями. Предпочтительно если программе это редко нужно, = и не при каждом запуске, но выражение используется несколько раз под= ряд.
3. Создавать объект класса= regex в компилируемом виде (флаг Compiled установлен) и пользоваться= его функциями. Предпочтительно если программа часто пользуется вы= ражением.
Регулярное выражение состо= ит из шаблона и опций. С опциями разобраться довольно просто, а вот ша= блоны - это отдельный язык. По поводу задания шаблонов могу только пос= оветовать пользоваться разными вспомогательными программами типа Expr= esso и пр.
Возьмем для примера простой= шаблон:
&nb=
sp;(?‹Protocol›\w+):\/\/(?‹Domain›[\w\.]+)\/?\S*
Этот шаблон выдергивает и=
з предложенного
текста все URL, и сохраняет их с выделением протокола и домен=
а.
Код для использования:
&nb=
sp;void
RegexMatch() {
&nb=
sp;string
inStr =3D "blah-blah-blah something here http://www.domain.com/in=
dex.cgi
and some comments here and file here ftp://ftp.domain123.info/repo=
sitory/";
&nb=
sp;StringBuilder
sb =3D new StringBuilder();
&nb=
sp;Regex
r =3D new Regex(@"(?‹Protocol›\w+):\/\/(?‹Domain›[\w\.]+)\/?\S*&q=
uot;,
RegexOptions.IgnoreCase | RegexOptions.Singleline);
&nb=
sp;for
(Match m =3D r.Match(inStr); m.Success; m =3D m.NextMatch()) {
&nb=
sp;sb.AppendFormat("Найдено
URL: {0}, протокол: {1}, домен: {2}\r\n", m.Value, m.Groups["=
;Protocol"].Value,
m.Groups["Domain"].Value);
&nb=
sp;}
&nb=
sp;textBox1.Text
=3D sb.ToString();
&nb=
sp;}
Результат будет:
&nb=
sp;Найдено
URL: http://www.domain.com/index.cgi, протокол: http, домен: www.doma=
in.com
&nb=
sp;Найдено
URL: ftp://ftp.domain123.info/repository/, протокол: ftp, домен: ft=
p.domain123.info
Примеров может быть множест= во, лучше посмотрите шаблоны в действии... а в Expresso есть систе= ма интерактивного составления шаблона... очень полезно для обуче= ния, да и примеров у них много.
Глава 2. Генеалогия классов.
&nb= sp;
Итак, поговорим когда ког= о и как нужно порождать.
Один из постулатов соврем= енного высокоуровневого программирования, не всегда верных, впрочем,= гласит, что "Чем меньше одинаковых блоков кода в программе - тем лучш= е". Первое следствие из этого постулата - блок кода, который исполь= зуется больше, чем в одном месте программы должен быть вынесен в отдельн= ую функцию. Впрочем, это к теме не относится. Второе следствие - на= бор переменных/функций, которые используются больше чем в одном к= лассе должны быть выделены в отдельный родительский класс. В общем-то, этим правилом можно руководствоваться при определении "н= ужен ли вам некий родитель для ваших классов?", если у вас их много.=
Рассмотрим модельную ситуа= цию с множеством классов, и определим какие им нужны родственные свя= зи.
Довольно классический прим= ер - элементы управления. Рассмотрим 4 элемента управления: кнопка, текс= товое поле, панель и группа. У всех этих элементов должны быть свойства:= положение, размер, события нажатия, фокуса и еще можно много всяких написа= ть для удобства программистов. Стало быть один предок, общий для всех= элементов управления нашелся. Думаем дальше - панель и группа должны иметь подчиненные элементы управления, а также уметь прокручивать соб= ственную внутреннюю область. Стало быть у этих двоих должен быть еще общий пр= едок. Итак структура получиться примерно такой:

&nb= sp; Ну вот вам общий принцип выделения общих предков.
Рассмотрим вопросы наследс= тва, ограничения на наследство, растранжиривания наследства и = стерилизации.
Многое из того, о чем сейчас= пойдет речь уже рассказывалось в части 1 главе 1 в разделе о деклараци= ях.
Главное правило наследова= ния - каждый потомок наследует все. Т.е. все члены класса, открытые для наследования, передаются каждому потомку в любом колене. Еди= нственное, что может помешать - переопределение члена класса.
По поводу "как жить потомкам":<= /b>
Итак, модификатор abs= tract у класса говорит о том, что сам класс ничего сделать не может, толь= ко наследство осталось. Будем считать его безвременно почившим р= одственником... тем самым дядей "самых честных правил", который однако = весьма четко описал как именно должны жить его потомки. Тот же модифика= тор у члена класса говорит о том, что класс считает этот член обязатель= ным у своих потомков, но сам толком не знает что это такое. Эдакий стар= ший родственник, знающий как должны жить его потомки, но сам живущий по-другому.
Модификатор virtual у= члена класса говорит о том, что класс знает что делать, однако допускает иное толкование для потомков. Хороший родственник с широкими взглядами :).
Теперь касательно доступа к наследст= ву:
модификатор private о= беспечивает неприкосновенность наследства. Доступ есть только из других уна= следованных функций. Например, у родителя есть открытая функция public1= , и закрытая функция private1. Потомок может обратиться то= лько к public1, но если внутри кода public1 есть обращение= к private1, то все будет работать.
модификатор protected= , в вопросах наследства, эквивалентен public - наследство открыто= для доступа. "Пользуйтесь, родственнички дорогие!"
Теперь о собственном мнении потомков п= о поводу наследства.
Вобщем-то, никто не мешает = потомкам выбирать из наследства только понравившиеся части. Да и посыл= ать старших с их советами "как надо жить" тоже. Любая функци= я, объявленная virtual может быть переопределена модифик= атором override. Если очень надо переопределить функцию не отме= ченную virtual - можно использовать модификатор new. Прав= да с этим надо быть осторожным - если уж вы наследуете от кого-то, к ва= м будут соответственно относится и может быть конфликт с другими клас= сами, ждущими одной подписи функции, а напарывающимися на другую. Д= а и от родни не уйдешь - до оригинального варианта функции все равно всегда можно достучаться, если знать что он есть. Изнутри потомка= достаточно использовать ключевое слово base, а снаружи - преобразо= вать тип объекта в тип предка. Предположим, функция Func1 перео= пределена в классе B, потомке класса А с использование слова <= b>new:
&nb=
sp;B b1
=3D new B();
&nb=
sp;b1.Func1();
// новый вариант функции.
&nb=
sp;((A)b1).Func1();
// старый вариант функции
И самое страшное - стерилизация.
Очень просто делается, как= и все ужасы в нашем мире. Модификатор класса sealed - стерилиз= ует его, и у него уже никогда не будет детей :(. Тот же модификатор, п= римененный к virtual функции отменяет ее виртуальность.
И напоследок - крестные (отцы/матери/д= яди/тети/феи и пр. фольклорные элементы)
В роли крестных выступаю= т интерфейсы. Их у каждого класса может быть сколько угодно, и для того, чтобы им= еть крестных не обязательно иметь родителей. Интерфейсы все очень ст= рогие - они точно знают что должен уметь делать их крестник, и не отстаю= т, пока он всему этому не научиться. Причем все, чему учит интерфейс= обязательно остается в доступном наследстве (public). Ну а возможность пот= омков решать самим зависит только от предка, но никак не от крестного. = Это в классе-предке определяется будет ли функция с модификатором virtual или нет, да и что именно будет делать функция тоже опреде= ляется там.
Часто бывает так, что разны= е интерфейсы под одним и тем же именем разумеют разные вещи... тогда классу ни= чего не остается, как выучить оба толкования, да еще запомнить от кого= какое он получил. Выглядит это примерно так:
каждое поле интерфейса мо= жет быть имплементировано в классе внутренним и внешним образом
&nb=
sp;class
Class1 : IInterface1 {
&nb=
sp;public
void interfaceFunc1() {}; // внутренняя имплементация
&nb=
sp;public
void IInterface1.interfaceFunc2() {}; // внешняя имплементация=
&nb=
sp;}
Имплементирование внешним об=
разом
явно указывает на источник толкования какого-либо поля. Такой=
подход
позволяет избежать ошибок при компиляции и при последующих обр=
ащениях.
Кстати, запомните, если вам нужна от объекта функция какого-то =
из
его интерфейсов - всегда обращайтесь к ней предварительно преоб=
разовав
тип объекта в тип интерфейса. Например: объект ob1 имплементир=
ует
несколько интерфейсов, в том числе IInterface1, с нужной нам фун=
кцией
intFunc1:
&nb=
sp;((IInterface1)ob1).intFunc1();
Этим вы даете понять, что ва= м нужна функция intFunc1 именно в трактовке IInterface1, а не в чьей-ни= будь другой. А то функцию OnPaint кто только не имплементирует...
&nb= sp;
Исключения (Exceptions) - эт= о то, что случается, когда что-то неправильно. Например, вы пытаетесь = открыть файл, которого нет - системная библиотека кинет исключение &qu= ot;файл не найден".
Исключения надо уметь кидать (throw) и ловить (catch).
Лично я знаю 3 методики ра= боты с исключениями, каждая из которых написана программистом с бо= льшим опытом и многометровым списком регалий, причем каждая из них ссы= лается на какого-нибудь великого гуру (одного из создателей .net, напр= имер). Надо заметить, что эти три методики во многом противоречат друг = другу. Вывод - никакой "правильной" или "лучшей" мето= дики работы с исключениями нет. Есть только некоторые правила, и чь= е-то мнение :).
Рассмотрим основы работы с= исключениями и те правила, которые одинаковы во всех трех методиках.
Как кидать исключения<= /b>
Для кидания существует к= лючевое слово throw.
&nb=
sp;throw
new Exception("Произошла ошибка...");
Когда их кидать? - Когда что-то пошло не так. Впрочем, обычно это относится к библиотекам, которые вы пишете для других. Но в жизни всякое бывает и мой последний проект, например, состоит из 4 библиотек, которыми никто, кроме меня пользоваться не будет, однако они все исправно сообщают об ошибках исключениями.
Например, вы пишете функци= ю расчета чего-нибудь по двум дробным входящим аргументам, но вам нужно что= бы аргументы были больше 1, иначе ничего не посчитается... Вы мож= ете проверять аргументы и если они меньше - возвращать double.NaN. = Так устроены математические функции класса Math. А можете сделать так:
&nb=
sp;private
double func1(double arg1, double arg2) {
&nb=
sp;if
(arg1 ‹=3D 1) throw new ArgumentException("Аргумент должен быт=
ь больше
1", "arg1");
&nb=
sp;if
(arg2 ‹=3D 1) throw new ArgumentException("Аргумент должен быт=
ь больше
1", "arg2");
&nb=
sp;//
расчет
&nb=
sp;}
Такой код позволит указать программисту на ошибки. Собственно примерно так и надо писать библиотеки, которыми будут пользовать= ся другие.
Еще ситуация когда надо ки= дать исключения - когда вы перехватываете исключение в своей фун= кции, но вам надо добавить к нему какую-то информацию.... например, так= :
&nb=
sp;try {
&nb=
sp;using
(FileStream fs =3D File.OpenRead(@"C:\\temp.txt")) {
&nb=
sp;}
&nb=
sp;}
&nb=
sp;catch
(FileNotFoundException ex) {
&nb=
sp;throw
new InvalidOperationException("Ошибка открытия файла в =
моей
функции...", ex);
&nb=
sp;}
Такой код позволяет поймать и обработать исключение на уровне функции (например, закрыть потоки или еще чего сделать) и передать пойманное исключение дальше, с довеском в виде собственной информации.
Как ловить исключения<= /b>
Для отлова исключений ис= пользуется конструкция try {} catch {} finally {}.
Блок try (попробоват= ь) содержит код, который может вызвать ошибку.
Блок catch (поймать) с= одержит код обработки ошибки. Таких блоков может быть несколько - на кажд= ый тип ошибки, которые вы ждете.
Блок finally (напос= ледок) необязателен, в него помещается код, который выполняется в л= юбом случае - закончился ли блок try нормально, или кинул исключение.= Особая фича блока finally - он выполняется даже если функция уже верн= ула значение.
Пример:
&nb=
sp;try {
&nb=
sp;//
опасный код
&nb=
sp;}
&nb=
sp;catch
(ArgumentException aex) {
&nb=
sp;MessageBox.Show(aex.Message,
"Error occured");
&nb=
sp;return
1;
&nb=
sp;}
&nb=
sp;catch
(Exception ex) {
&nb=
sp;throw
new InvalidOperationException("Custom message...", e=
x);
&nb=
sp;}
&nb=
sp;finally
{
&nb=
sp;//
закрыть потоки и пр.
&nb=
sp;}
К слову:
Вместо блока finally можно= использовать конструкцию using () {}. Эта конструкция позволяет убеди= ться, что все ресурсы занятые неким объектом будут перекинуты в мусор или удалены, в зависимости от типа, по завершении куска кода, в= не зависимости от результатов этого самого кода.
пример:
&nb=
sp;using
(MyClass object1 =3D new MyClass()) {
&nb=
sp;//
код, использующий object1
&nb=
sp;}
Даже если код кинет исклю=
чение
- object1 будет удален. Для блока using не обязательна инициализа=
ция
а блоке, можно и так:
&nb=
sp;MyClass
ob1 =3D new MyClass();
&nb=
sp;using
(ob1) {}
Конструкция using может выст=
упать
заменителем блока finally только если вам в этом блоке надо было
удалить один объект. Впрочем, это очень часто случается. С другой с=
тороны,
она весьма полезна, когда код, использующий объект имеет несколь=
ко
точек возвращения. Чтобы точно не забыть снести объект - проще з=
аключить
код в блок using. Пример:
&nb=
sp;MyClass
ob1 =3D new MyClass();
&nb=
sp;//
что-то сделать с ob1
&nb=
sp;if
(ob1.property1 =3D=3D 1) {
&nb=
sp;ob1.Dispose();
&nb=
sp;return
1;
&nb=
sp;}
&nb=
sp;//
что-то еще сделать
&nb=
sp;if
(ob1.property1 =3D=3D 2) {
&nb=
sp;ob1.Dispose();
&nb=
sp;return
2;
&nb=
sp;}
Чтобы не писать каждый раз D=
ispose,
можно сделать так:
&nb=
sp;using
(MyClass ob1 =3D new MyClass()) {
&nb=
sp;//
что-то сделать с ob1
&nb=
sp;if
(ob1.property1 =3D=3D 1) return 1;
&nb=
sp;//
что-то еще сделать
&nb=
sp;if
(ob1.property1 =3D=3D 2) return 2;
&nb=
sp;}
Ситуации в которых надо = ловить исключения
В общем виде это звучит как "Ловить исключения надо когда что-то может пойти не так"= . Ни о чем не говорящая фраза. Если быть ближе к реальности - ловить и= сключения надо тогда, когда вы не уверены в обрабатываемых данных. Эта не= уверенность может быть по поводу типа данных, значения данных и т. д. Стандар= тные ситуации, когда вы не можете быть уверены:
Все что касается ввода дан= ных от пользователя - никто не знает что введет пользователь.
Любое чтение из потока, ес= ли только это не поток в памяти - кто знает что там в файле на самом де= ле.
Любая сетевая операция - в= ы не можете быть уверены, что в нужный момент не оборвется кабель или с= исадмин не решит пошутить.
Любое обращение к внешним = базам данных надо рассматривать как сетевую операцию.
Любой вывод в поток - место= на диске заканчивается в самый неподходящий момент.
Общие методики
Исключения - не панацея и не что-то очень хорошее, чем надо часто пользоваться. Они жрут порядк= ом ресурсов, так что рекомендуется проверять значения аргументов= самим, а не полагаться на выброс исключения.
Пример:
&nb=
sp;public
string func1 (string inStr) {
&nb=
sp;if
(string.IsNullOrEmpty(inStr)) { return null; }
&nb=
sp;}
такой код быстрее, чем:
&nb=
sp;public
string func1 (string inStr) {
&nb=
sp;if
(string.IsNullOrEmpty(inStr) { throw new ArgumentException(); }
&nb=
sp;}
и уж, тем более, чем:
&nb=
sp;public
string func1 (string inStr) {
&nb=
sp;try {
&nb=
sp;//
сделать что-то с inStr
&nb=
sp;}
&nb=
sp;catch
(ArgumentException ex) {
&nb=
sp;throw
ex;
&nb=
sp;}
&nb=
sp;}
Теперь расскажу о разных вз= глядах на общую технику. Лично я не считаю хоть один из этих методов прави= льным всегда, имхо - действовать надо по ситуации. А основными парам= етрами, влияющими на мой метод отлова исключений являются размер прог= раммы, стоимость заказа и время на создание. Для поклонников корпорати= вного программирования хочу напомнить - написание всех возможных оши= бок в программе вместе с комментариями займет времени больше, чем со= здание всего остального кода, без интерфейса. И большинство клиентов= хотят программу быстро и дешево, а не долго и дорого, но с подробными ошибками :). В отлове ошибок главное, имхо, чтоб информация об от= ловленной ошибке была достаточной для вас, чтобы эту ошибку найти и исправ= ить. А на пользователя надо выводить только! то, с чем он может справи= ться сам - а это очень немного.
Несколько основных спорн= ых вопросов:
1.Ловить каждый тип= исключения, или ловить все сразу. Как правило, опасный код может выкинуть не один тип исключений, а несколько. Одни люди говорят, что ловить= надо обязательно каждый тип отдельно, другие говорят, что надо ловить= все в одном блоке, а уж потом разбираться что произошло. Имхо, если н= и с одним типом исключений пользователь справиться не может - то ло= вить надо все сразу. Пользователю все равно что случилось - ошибка хра= нимой процедуры, переполнение очереди или еще какая хрень - ему главное знать, что "Транзакция не прошла. Попробуйте еще раз.".
2.Заключать в try - = catch блок как можно меньше кода или как можно больше. Можно создавать один блок try catch для всей функции, а можно создавать по блоку на к= аждую потенциально опасную строчку. В общем и целом - это примерно то ж= е, что и предыдущий вариант. Лично я считаю что засовывать весь код = функции в try catch - бред. Хотя возможны ситуации когда это оправдано. Но= вот создавать по блоку на каждую строчку - это очень уж долго и нудно и = редко имеет смысл.
3.Надо ставить бло= ки catch в опасных строчках функций и один catch где-то на самом верху п= рограммы, или ставить глобальные отловщики на каждом уровне или вообще ник= огда не ставить глобальных catch блоков. Это, пожалуй, самый спорны= й момент в подходах. В приложении есть пример как сделать современный гло= бальный перехватчик исключений. Однако есть ряд людей, которые счита= ют, что такой отловщик - свидетельство плохого кода, мол хороший к= од должен отлавливать все ошибки там, где они возникают, а не на уров= не main функции программы. Возможно они и правы, но я не согласен. Е= ще один спорный момент здесь - один глобальный перехватчик или несколь= ко на каждом структурном уровне... Ну это очень сильно зависит от масш= табности программы. Если программа использует пару десятков библиотек, = написанных разными людьми и может заниматься одновременно несколькими ра= зными задачами - то несколько перехватчиков имеют смысл. Если програм= ма однопоточная и написана одним программистом - то вряд ли, одно= го общего перехватчика должно хватить.
И напоследок - помните, ч= то после отлова исключения программа часто остается в неком промежут= очном состоянии - половина запрошенных пользователем действий выпо= лнена, вторая нет. Так что работать надо осторожно, чтобы не войти в бес= конечный цикл ошибок. Особенно это касается единого перехватчика. Если = программа простая, то в коде перехватчика должно быть приведение програм= мы к рабочему состоянию. Если программа большая и сложная - то везде,= где появление исключения грозит нарушением хода программы должен стоять свой ловец.
Глава 4. Мультипоточность.
&nb= sp;
Мульти-поточность (multi-thre= ading) - свойство программ выполнять несколько задач одновременно. Ус= ловно одновременно, конечно. В зависимости от типа процессора, одн= овременность может быть истинной и имитируемой. Впрочем, для большинства прог= раммистов эта разница несущественна.
Основными моментами, затру= дняющими разработку мультипоточных приложений является невозможность предсказать последовательность завершения отдельных потоков = на разных машинах. Да и отладка мультипоточных приложений всегда = была головной болью. Впрочем, на VS 2005 вроде бы с этим попроще... пока = у меня все вроде работало нормально. А вот 2003 VS глючил страшно если ра= зные потоки запускались из разных библиотек.
При мультипоточном выпол= нении кода основной проблемой является отслеживание доступов из ра= зных потоков к одной странице памяти. Ни в коем случае не должно пол= учиться так, чтобы один поток записывал в переменную, когда другой считы= вает из нее. Следствие из вышесказанного - если у вас все работает н= ормально, т.е. потоки не пересекаются в одной переменной, это не значит,= что они не пересекутся на другой машине. Отсюда другая проблема - с= инхронизация потоков. Для решения этих проблем была разработана техника сем= афоров и различных синхронизаторов, которая, в некоторой степени ос= талась и в .net, однако теперь программист практически не имеет с ней де= ла.
Кратко о семафорах и син= хронизации.
Синхронизация - механизм, по= зволяющий потокам контролировать собственное выполнение относительно друг друга. В общем и целом, синхронизация ведется через флаго= вые переменные и сходна с общим использованием семафоров. По пово= ду семафоров... думал как лучше написать, и решил что картинка будет нагляднее:

&nb= sp; слева и справа - потоки, по ним ползет процесс вычисления :). По центру= - переменная с семафором. Когда левый поток заканчивает считать= - он записывает данные в переменную, потом правый поток приходит= и считывает ее. Вот для того, чтобы правый поток не считал перемен= ную, пока левый ее не запишет и нужна синхронизация... где-то еще дол= жна быть флаговая (bool) переменная, со своим семафором, которая бу= дет сообщать, закончил ли левый поток считать.
Когда нужна мультипоточ= ность
Самое распространенное и= спользование мультипоточности - кнопка отмена (или прервать) во время какого= -либо процесса - загрузки/счета/рендера и пр. Вобщем-то, можно раздели= ть использование многих потоков на две ситуации - простую и сложную= :). Простая - один поток считает (или еще чего делает), второй перери= совывает форму, чтоб не стала белой, проверяет не нажата ли кнопка отмен= а и рисует индикатор прогресса. Сложная - один поток на интерфейсе,= а еще несколько заняты своими делами... например MS Word - один пот= ок на интерфейсе, другой на проверке вводимого, еще один на проверк= е обновлений, еще один на проверке орфографии, еще один на проверке грамматики.= Орфография и грамматика синхронизированы между собой и все потоки синх= ронизированы с интерфейсным.
Мультипоточность в .NET=
Те из вас, кто читал msdn спр= авку по любым классам MS .NET наверняка видели, что для большинства к= лассов написано "thread-safe" - т.е. безопасен для мультипото= чности. Иначе говоря, класс имеет свой, встроенный семафор. Надо отметит= ь, что в .NET все классы имеют свои семафоры, кроме тех, которые испо= льзуют unsafe код. В .NET 2.0 ситуация несколько изменилась, по сравнен= ию с .NET 1.1, в частности, теперь помимо семафора, каждый объект кла= сса Control (и все наследники) имеет приписку к потоку, в котором он создан, и обратиться к нему из другого потока можно только чере= з механизм Delegate Invoke. Это позволяет легко отсечь потенциально опас= ные обращения на стадии первичной отладки. Если вы абсолютно увере= ны, что потоки не пересекуться, вы можете отключить этот механизм = - установите свойство CheckForIllegalCrossThreadCalls объекта Control = равным false.
Обращение к контролу, созд= анному в другом потоке теперь выглядит так:
оригинал MSDN - http://msdn= 2.microsoft.com/en-us/library/ms171728.aspx
&nb=
sp;public
class Form1 : Form
&nb=
sp;{
&nb=
sp;delegate
void SetTextCallback(string text);
&nb=
sp;private
Thread demoThread =3D null;
&nb=
sp;private
TextBox textBox1;
&nb=
sp;private
void SetTextMain() {
&nb=
sp;this.textBox1.Text
=3D "This text was set unsafely."; // небезопасная устано=
вка
текста
&nb=
sp;this.SetText(("This
text was set safely."); // безопасная установка
&nb=
sp;}
&nb=
sp;private
void SetText(string text) {
&nb=
sp;if
(this.textBox1.InvokeRequired) { // если контрол в другом поток=
е
&nb=
sp;SetTextCallback
d =3D new SetTextCallback(SetText); // создать вызов
&nb=
sp;this.Invoke(d,
new object[] { text }); // вызвать функцию из другого потока
&nb=
sp;}
&nb=
sp;else
{ // если контрол в этом потоке
&nb=
sp;this.textBox1.Text
=3D text; // установить текст
&nb=
sp;}
&nb=
sp;}
&nb=
sp;}
&nb=
sp;
Создание простой мультип=
оточности
Для создания простой и без= опасной мультипоточности в .NET 2.0 лучше всего воспользоваться компон= ентом BackgroundWorker (фоновый работник). Очень удобная вещь - комп= онент, который умеет выполнять приписанную к нему функцию в фоновом р= ежиме, с поддержкой "отмены" и прогресса. Для всех функций, ко= торые не связаны с интерфейсом и должны выполняться фоново - рекоме= ндую пользоваться работником.
Пример использования в при= ложении.
Если вам надо запустить нес= колько потоков одновременно, но все они должны выполняться фоново - и= спользуйте его же. Например, если вам надо скачивать кучу файлов из сети, вы = можете создать с десяток работников и пусть все они качают одновременн= о. Они все синхронизируются, через прогресс, с основным потоком, = но никак не связаны между собой.
Сложная мультипоточност= ь
Пару слов о сложной мультип= оточности. Для начала - а вы уверены что оно вам надо? Непростое и муторное э= то дело. Но если вам так уж приперло запустить прогу в кучу потоков = одновременно, да еще и связанных между собой, то делается это примерно так:
Есть класс Thread (System.Thr= eading.Thread). Он обеспечивает собственно потоки. Есть классы ThreadStart и = ParameterizedThreadStart - они держат адрес функции которую нужно запускать в потоке, пер= вый - функция должна быть без аргументов, второй - с аргументом типа object.
Синхронизация потоков полно= стью на вас, равно как если вам припрет сталкивать потоки лбами в объек= те вашего класса - ваша задача отследить, чтобы не было нарушени= й... Каждая переменная в .NET thread-safe, но это не означает что класс= целиком тоже thread-safe. Представьте такую ситуацию - вы записываете в класс последовательно значения переменных в одном потоке, а др= угой поток в это время их считывает. У вас даже Exception не будет - п= росто программа начнет глючить - половина данных из старого состояния, вторая половина из нового... Чтобы этого не случилось - возвра= щаемся к истокам, делаем свои семафоры на каждый класс. Есть куча разных= методов, я покажу на очень простом примере очень простой метод.
Создаем класс с семафором (= замочком):
&nb=
sp;class
CalcRes
&nb=
sp;{
&nb=
sp;public
bool isLocked; // флаг запертости
&nb=
sp;private
int lockId; // ID потока который запер
&nb=
sp;private
int _res; // переменная
&nb=
sp;public
int res { // свойство для переменной
&nb=
sp;get {
&nb=
sp;if
(Thread.CurrentThread.ManagedThreadId =3D=3D lockId || lockI=
d =3D=3D 0)
{ // если замок не установлен или установлен этим потоком
&nb=
sp;return
_res; // вернуть результат
&nb=
sp;}
&nb=
sp;return
0; // или вернуть 0
&nb=
sp;}
&nb=
sp;set {
&nb=
sp;if
(Thread.CurrentThread.ManagedThreadId =3D=3D lockId || lockI=
d =3D=3D 0)
{ // если можно
&nb=
sp;_res
=3D value; // установить значение
&nb=
sp;}
&nb=
sp;}
&nb=
sp;}
&nb=
sp;public
CalcRes() { // конструктор
&nb=
sp;isLocked
=3D false; // не заперт
&nb=
sp;}
&nb=
sp;public
void Lock() { // функция Запереть
&nb=
sp;isLocked
=3D true; // заперт
&nb=
sp;lockId
=3D Thread.CurrentThread.ManagedThreadId; // установить ID =
потока
&nb=
sp;}
&nb=
sp;public
void UnLock() { // отпереть
&nb=
sp;if
(Thread.CurrentThread.ManagedThreadId =3D=3D lockId) { // ес=
ли id потоков
совпадают
&nb=
sp;lockId
=3D 0; // снять id
&nb=
sp;isLocked
=3D false; // снять замок
&nb=
sp;}
&nb=
sp;}
&nb=
sp;}
Простейшая имплементация =
класса
с возможностью запирания на один поток. Теперь потестим то, что у=
нас
получилось:
Такая вот форма:

&nb= sp; с таким вот кодом:
&nb=
sp;public
partial class Form1 : Form
&nb=
sp;{
&nb=
sp;Thread
thread1; // поток 1
&nb=
sp;Thread
thread2; // поток 2
&nb=
sp;CalcRes
calcRes; // класс для хранения результатов
&nb=
sp;public
Form1() {
&nb=
sp;InitializeComponent();
&nb=
sp;}
&nb=
sp;delegate
void SetTextCallback(TextBox textb, string text); // делегат для з=
аписи
в текстовые поля
&nb= sp;private void button1_Click(object sender, EventArgs e) { // нажатие кнопки<= o:p>
&nb=
sp;if
(button1.Text =3D=3D "button1") { // если кнопку нажали пер=
вый раз
&nb=
sp;calcRes
=3D new CalcRes(); // создать объект класса
&nb=
sp;thread1
=3D new Thread(new ParameterizedThreadStart(CalcFunc)); // со=
здать
поток с припиской к функции с параметром
&nb=
sp;thread2
=3D new Thread(new ThreadStart(StateFunc)); // создать поток без па=
раметров
&nb=
sp;thread1.Start(1000000);
// запустить поток 1 с аргументом 1000000
&nb=
sp;thread2.Start();
// запустить второй поток
&nb=
sp;button1.Text
=3D "stop"; // изменить текст на кнопке
&nb=
sp;}
&nb=
sp;else
{ // если нажали второй раз
&nb=
sp;thread1.Abort();
// прервать поток 1
&nb=
sp;thread2.Abort();
// прервать поток 2
&nb=
sp;button1.Text
=3D "button1"; // восстановить текст на кнопке
&nb=
sp;}
&nb=
sp;}
&nb=
sp;void
CalcFunc(object data) { // функция потока 1
&nb=
sp;for
(int i =3D 0; i ‹ (int)data; i++) { // цикл по аргументу
&nb=
sp;for
(int j =3D 0; j ‹ 100; j++) { // цикл на сотню - для наглядности
&nb=
sp;if
(calcRes.isLocked =3D=3D false) { // если объект не заперт
&nb=
sp;calcRes.Lock();
// запереть
&nb=
sp;calcRes.res++;
// дописать значение
&nb=
sp;SetText(textBox1,
calcRes.res.ToString()); // установить текст
&nb=
sp;calcRes.UnLock();
// отпереть
&nb=
sp;}
&nb=
sp;Thread.Sleep(1);
// подождать миллисекунду
&nb=
sp;}
&nb=
sp;Thread.Sleep(1000);
// подождать секунду
&nb=
sp;}
&nb=
sp;}
&nb=
sp;void
StateFunc() { // функция потока 2
&nb=
sp;while
(thread1.IsAlive) { // если первая нить еще выполняется
&nb=
sp;if
(calcRes.isLocked =3D=3D false) { // если объект не заперт
&nb=
sp;calcRes.Lock();
// запереть
&nb=
sp;SetText(textBox2,
calcRes.res.ToString()); // установить текст
&nb=
sp;calcRes.UnLock();
// отпереть
&nb=
sp;}
&nb=
sp;else
{ // если заперт
&nb=
sp;SetText(textBox2,
"locked"); // написать заперто
&nb=
sp;}
&nb=
sp;SetText(textBox3,
thread1.ThreadState.ToString()); // написать состояние потока =
1
&nb=
sp;Thread.Sleep(10);
// подождать 10 миллисекунд
&nb=
sp;}
&nb=
sp;}
&nb=
sp;void
SetText(TextBox textb, string text) { // установка текста thread-s=
afe
&nb=
sp;if
(textb.InvokeRequired) {
&nb=
sp;SetTextCallback
d =3D new SetTextCallback(SetText);
&nb=
sp;this.Invoke(d,
new object[] { textb, text });
&nb=
sp;}
&nb=
sp;else
{
&nb=
sp;textb.Text
=3D text;
&nb=
sp;}
&nb=
sp;}
&nb=
sp;private
void Form1_FormClosing(object sender, FormClosingEventArgs e)=
{
// при закрытии формы
&nb=
sp;if
(thread1.IsAlive) thread1.Abort(); // убить поток 1
&nb=
sp;if
(thread2.IsAlive) thread2.Abort(); // убить поток 2
&nb=
sp;}
&nb=
sp;}
Вообще-то еще надо проверят=
ь созданы
ли потоки - при закрытии и при обращении из другого потока. Но е=
сли
надо - сами добавите. Все ожидания - для имитации долгой и напря=
женной
работы :). Итак, если бы потоки никогда не сталкивались и наш за=
мок
не работал, то надпись locked никогда бы не появилась во втором те=
кстовом
поле, а во время секундных пауз первого потока в первом тексто=
вом
поле всегда бы было число кратное 100. На картинке показано сост=
ояние
во время секундной паузы.
Глава 5. Вызов функций библиотек Win32 (P/Invoke)= span>
&nb= sp;
Вызов функций Win32 библ= иотек (p/invoke)
Довольно частая ситуация - = вам нужна функция из библиотеки, а она написана для Win32 и в .NET нап= рямую не подключается. Еще чаще - нужна системная функция, а они все "спрятаны" в системных библиотеках, которые все Win32. И= несмотря на многочисленные обещания Microsoft, системные библиотеки Vis= ta будут тоже Win32. WinFX как был так и остается проектом и мечтой.= p>
В языках программировани= я до .NET были различные способы импорта функций из dll. В .NET свой сп= особ, во многом похожий на то, что было в с++ 6.0. Есть такая вот констру= кция:
&nb=
sp;[DllImport(‹имя_dll›,
Аттрибуты)]
которая хранится в namespace System.Runtime.InteropSer=
vices.
Аттрибуты - необязательны, имя dll - путь к dll, в кавычках.
Аттрибуты вызова есть следу= ющие:
CharSet - определяе= т как передавать строки (ANSI или Unicode). Возможные значения Ansi, A= uto, None, Unicode. Рекомендуется ставить Auto. Значение этого аттр= ибута влияет на поиск функций в системных библиотеках. Как вы знаете, у Microsoft'a стандарт - если функция написана для Ansi - в конце = имени дописывается заглавная буква А, если для unicode - W. В режиме Auto - если функция с указанным именем не найдена, система будет= искать функции с тем же именем, но с суффиксом кодировки. Для Windows95 - ищутся только функции A, для всех остальных - W. Чтобы система не = искала функции - укажите аттрибут ExactSpelling=3Dtrue. Если CharSet н= е указан, в С# ставится значение по умолчанию - Ansi.
ExactSpelling - показ= ывает надо ли искать функции с суффиксом кодировки. По умолчанию =3D f= alse, т.е. искать надо.
EntryPoint - название= или адрес функции. Можно не задавать, тогда система будет искать фун= кцию сама. Использовать имеет смысл когда вам надо переименовать фун= кцию. Ну, например функция в библиотеке называется "FunctionO= fApplyingAttributesToGivenObjectsVersion2Model15", а вам такое длинное имя не нравится, и вы хотите обращаться к фун= кции ApplyAttributes. Вот тогда вы в EntryPoint пишете длинное имя,= а свою функцию называете так, как вам надо.
CallingConvention - по= казывает, как надо обращаться с памятью при вызове. Может принимать значе= ния - Cdecl, StdCall, ThisCall, Winapi. Значение по умолчанию - Winapi= , которое равно StdCall на Windows ОС, и Cdecl на Windows CE. Cdecl использует= ся для вызова функций с переменным числом аргументов. ThisCall ис= пользуется для вызова функций, работающих с классами, которые тоже вызва= ны из Win32 dll.
Остальные аттрибуты нужны = очень редко. Подробнее можно почитать здесь - http://msdn2.microsoft.com/en-US/libra= ry/system.runtime.interopservices.dllimportattribute(VS.80).aspx.
Самое сложное в вызове фун= кций из dll - преобразование типов данных. Если кто вдруг не знает - до .NET были другие массивы, не было списков и перечислений, а еще = почти все передавалось через указатель... так что рассмотрим как какие = типы соотносятся.
Простые типы как прав= ило так и пишутся: int, uint, byte.
Таблица соответствий прос= тых типов тут - http://msdn= 2.microsoft.com/en-us/library/ac7ay120.aspx.
Строки. Подробнее зде= сь - h= ttp://msdn2.microsoft.com/en-us/library/e8w969hb.aspx.
Со строками дело нескольк= о хуже. Они передаются по разному, в зависимости от ситуации:
Если строка входящий пара= метр - передается как string, если функция поддерживает Unicode.
&nb=
sp;[DllImport("User32.dll",
EntryPoint=3D"MessageBox", CharSet=3DCharSet.Auto)]
&nb=
sp;public
static extern int MsgBox(int hWnd, String text, String caption, uint ty=
pe);
Если строка возвращаемый параметр - тоже просто string.
Если строка передается ка= к указатель, входящий параметр - StringBuilder.
&nb=
sp;[DllImport(
"Kernel32.dll", CharSet=3DCharSet.Auto)]
&nb=
sp;public
static extern int GetSystemDirectory(StringBuilder sysDirBuf=
fer,
int size);
Если строка возвращается=
как
указатель - декларируется как указатель, который потом перево=
дится
в строку.
&nb=
sp;[
DllImport( "Kernel32.dll", CharSet=3DCharSet.Auto )] public =
static
extern IntPtr GetCommandLine();
&nb=
sp;// использование
&nb=
sp;IntPtr
cmdLineStr =3D GetCommandLine();
&nb=
sp;String
commandLine =3D Marshal.PtrToStringAuto( cmdLineStr );
Массивы. Подробнее зд= есь - h= ttp://msdn2.microsoft.com/en-us/library/dd93y453.aspx.
Массивы передаются напря= мую. Вы указываете модификаторы In, Out для указания что именно можн= о с массивом делать - читать (In) и/или писать (Out).
&nb=
sp;//
функция: int TestArrayOfInts(int* pArray, int pSize);
&nb=
sp;[
DllImport( "..\\LIB\\PinvokeLib.dll" )]
&nb=
sp;public
static extern int TestArrayOfInts([In, Out] int[] array, int size =
);
Если вам нужен указатель на= массив, несколько сложнее.
&nb=
sp;//функция:
int TestRefArrayOfInts(int** ppArray, int* pSize);
&nb=
sp;[
DllImport( "..\\LIB\\PinvokeLib.dll" )]
&nb=
sp;public
static extern int TestRefArrayOfInts( ref IntPtr array, ref int si=
ze
);
&nb=
sp;// использование
&nb=
sp;int[]
array2 =3D new int[10];
&nb=
sp;int
size =3D array2.Length;
&nb=
sp;IntPtr
buffer =3D Marshal.AllocCoTaskMem( Marshal.SizeOf(size)
*array2.Length);
&nb=
sp;Marshal.Copy(
array2, 0, buffer, array2.Length );
&nb=
sp;int sum2
=3D TestRefArrayOfInts( ref buffer, ref size );
Если вам надо передать мас= сив строк, т.е. указатель на массив char, можно сделать так:
&nb=
sp;//
функция: int TestArrayOfStrings(char** ppStrArray, int size);
&nb=
sp;[ DllImport(
"..\\LIB\\PinvokeLib.dll" )]
&nb=
sp;public
static extern int TestArrayOfStrings( [In, Out] String[] stringAr=
ray,
int size );
Если массив постоянный (co= nst), то его можно передать так:
&nb=
sp;// параметр
с++: TCHAR szCSDVersion[ 128 ];
&nb=
sp;[ MarshalAs(
UnmanagedType.ByValTStr, SizeConst=3D128 )] public String versio=
nString;
&nb=
sp;// параметр
с++: int val[3];
&nb=
sp;[ MarshalAs(
UnmanagedType.ByValArray, SizeConst=3D3 )] public int[] val;
Конструкцию MarshalAs расс=
мотрим
ниже.
Структуры и классы. По= дробнее здесь - http://msdn2.microsoft.com/en-us/library/eshywdt7.aspx.
Часто необходимо переда= вать структуры как аргументы, в этом случае действуем следующим обра= зом:
&nb=
sp;//структура
с:
&nb=
sp;typedef
struct _MYPERSON
&nb=
sp;{
&nb=
sp;char*
first;
&nb=
sp;char*
last; } MYPERSON, *LP_MYPERSON;
&nb=
sp;//
создаем свою структуру
&nb=
sp;[
StructLayout( LayoutKind.Sequential, CharSet=3DCharSet.Ansi )]=
&nb=
sp;public
struct MyPerson {
&nb=
sp;public
String first;
&nb=
sp;public
String last;
&nb=
sp;}
Передаем структуру как обыч=
ный аргумент,
либо напрямую, либо указатель на нее используя слово ref. Параме=
тр
CharSet в конструкции StructLayout использовать надо также, к=
ак в
конструкции DLLImport, т.е. если строковые переменные есть - луч=
ше
указывать какие они именно эти строки.
Классы передаются также к= ак структуры. И для каждой с++ структуры, вы можете создавать свой кл= асс.
&nb=
sp;//структура
с++
&nb=
sp;typedef
struct _SYSTEMTIME
&nb=
sp;{
&nb=
sp;WORD
wYear;
&nb=
sp;WORD
wMonth;
&nb=
sp;WORD
wDayOfWeek;
&nb=
sp;WORD
wDay;
&nb=
sp;WORD
wHour;
&nb=
sp;WORD
wMinute;
&nb=
sp;WORD
wSecond;
&nb=
sp;WORD
wMilliseconds; } SYSTEMTIME, *PSYSTEMTIME;
&nb=
sp;//
наш класс
&nb=
sp;[
StructLayout( LayoutKind.Sequential )]
&nb=
sp;public
class SystemTime {
&nb=
sp;public
ushort year; …
&nb=
sp;public
ushort milliseconds;
&nb=
sp;}
&nb=
sp;// использование
&nb=
sp;SystemTime
st =3D new SystemTime();
&nb=
sp;GetSystemTime(
st );
Еще один момент, если функ= ция библиотеки ожидает указатель на структуру и одним из вариантов= может быть null - создавайте класс, а не структуру. Если аргумент функц= ии C# - класс, то null передавать можно, если структура - нельзя.
Прочее. Подробнее зде= сь - h= ttp://msdn2.microsoft.com/en-us/library/ss9sb93t.aspx.
Есть еще некоторые типы да= нных, которые иногда надо передавать и которые весьма нестандартно = обрабатываются.
Указатель на функцию<= /p>
В С# указатель на функцию = превратился в delegate.
&nb=
sp;//
функция с ++
&nb=
sp;void
TestCallBack(FPTR pf, int value);
&nb=
sp;//
декларация в C#
&nb=
sp;public
delegate bool FPtr( int value );
&nb=
sp;[
DllImport( "..\\LIB\\PinvokeLib.dll" )]
&nb=
sp;public
static extern void TestCallBack( FPtr cb, int value );
HandleRef
Хитрая вещь - всего лишь уби= рает указатель на объект из списка на удаление, таким образом позволя= я передавать объект в библиотеку и терять указатель на него в основной програ= мме без риска, что объект будет удален до того, как функция библиотек= и закончится.
&nb=
sp;//
функция с++
&nb=
sp;bool
Read(Handle hFile);
&nb=
sp;//
функция C#
&nb=
sp;[DLLImport("lib.dll")]
&nb=
sp;public
static extern bool Read(HandleRef hndRef);
&nb=
sp;// использование
&nb=
sp;FileStream
fs =3D new FileStream( "HandleRef.txt", FileMode.Open )=
;
&nb=
sp;HandleRef
hr =3D new HandleRef( fs, fs.Handle );
&nb=
sp;Read(hr);
LPARAM
Часто встречается такой во= т аргумент у системных функций. Это указатель на параметр. Передается так= им вот образом:
&nb=
sp;//
функция с++
&nb=
sp;BOOL
Ensure(LPARAM lParam);
&nb=
sp;//
функция C#
&nb=
sp;[
DllImport( "lib.dll" )]
&nb=
sp;public
static extern bool Ensure( IntPtr param );
&nb=
sp;// использование
&nb=
sp;TextWriter
tw =3D System.Console.Out;
&nb=
sp;GCHandle
gch =3D GCHandle.Alloc( tw );
&nb=
sp;Ensure(
(IntPtr)gch );
&nb=
sp;gch.Free();
void*
Бывает такая вот вещь - указ= атель на пустоту... Передается так:
&nb=
sp;//
функция с++
&nb=
sp;void
SetData(void* object);
&nb=
sp;//
функция C#
&nb=
sp;[
DllImport( "lib.dll" )]
&nb=
sp;public
static extern void SetData( [MarshalAs(UnmanagedType.AsAny)] O=
bject
o);
&nb=
sp;// использование
&nb=
sp;SetData(
(short)12 );
&nb=
sp;SetData(
(double)12 );
&nb=
sp;SetData(
"abcd" );
MarshalAs конструкция= b>. Подробнее здесь - http://msdn2.microsoft.com/en-US/library/syst= em.runtime.interopservices.marshalasattribute.aspx.
Эта конструкция позволя= ет указать как именно должен обрабатываться объект. Задается так:
&nb=
sp;MarshalAs(тип,
аттрибуты)
Аттрибуты необязательны. В=
реальном
использовании я видел только аттрибут SizeConst, указывающий р=
азмер
постоянного массива.
Тип, согласно которому на= до обрабатывать объект, лучше всего выбирать из перечисления UnmanagedType (= http://msdn2.microsoft.com/en-US/library/system= .runtime.interopservices.unmanagedtype.aspx). Часть из предложеных типов я уже использовал в примерах, про оста= льные прочитаете сами - больно их много.
&nb= sp;Некоторые дополнительные особенности программирования под .net. =
Глава 1. Settings.
&nb= sp;
&nb= sp; У каждой, более или менее большой программы есть ряд настроек, кот= орые пользователь может менять. Естественно, каждому пользователю= хочется, чтобы программа запоминала его настройки и ее не надо было пер= енастраивать заново при каждом запуске. У некоторых особенно больших програм= м могут быть собственные настройки, которые устанавливаются в момен= т инсталляции или при первом запуске, эти настройки тоже лучше запоминать, что= бы не настраивать программу при каждом запуске.

&nb= sp;
Как запоминать настрой= ки
Стандартное решение - созда= ть класс для хранения параметров настроек и выгружать его содержи= мое в файл. Хоть вручную, хоть сериализацией. Записывать все настрой= ки в реестр - плохая привычка. Если надо хранить настройки для неск= ольких пользователей - можно просто файл сохранять в каталог пользова= теля, или на каждого пользователся заводить свой файл. На Виндах 2000 и = позднее есть свои каталоги, на остальных придется сделать это вручную. Лич= но я считаю, что для программ, не поддерживающих многопользователь= ность, это оптимальный вариант - сохранять класс настроек в файл в родн= ом каталоге программы. Но тут может быть проблема несовместимости= версий, которую по уму надо всегда решать, однако мало кто с этим связыва= ется. И установка новой версии программы, как правило, приводит к пот= ере всех настроек предыдущей версии. Но эта проблема легко решаетс= я - файл настроек должен содержать в начале номер версии, от которо= й он создан, а программа должна считывать настройки в соответстви= и с указанной версией. Например, своя функция считывания для каждой= версии файла настроек.
Решение .NET 2.0 - использо= вать технологию Settings. К сожалению, при всем удобстве, технологи= я не доработана и не без глюков. В общем виде это выглядит так - в нас= тройках проекта, среди свойств, есть вкладка Settings. Там вы можете задав= ать параметры. Я уже показывал как это делать, но в общем виде, это пр= имерно так:

Первый столбец - имя параметра, второй - тип, третий - группа, четвертый - значение. Тип параметра может быть почти любым - простые типы дан= ных, включая string, и специальный - коллекция string, а также несколько классов рисования, connection string, и все что угодно через вари= ант Browse. Единственное условие - тип должен иметь или ToString/From= String функции для TypeConverter, или быть xmlSerializable. Группа может быть или User - настройки сохраняются для каждого пользователя= , и могут быть им изменены, или Application - настройки не могут бы= ть изменены пользователем. Смысл группы Application - если нужны= константы, которые не могут быть заданы программистом (например, параметры компа или типа того) и задаются при инсталляции программы, что= бы не вычислять параметры при каждом запуске - они записываются в= такие вот свойства. Впрочем, в эти свойства можно записать и обычные кон= станты.
Основные преимущества - Se= ttings сами отслеживают версию программы и пользователя. Есть встрое= нные функции сохранения, загрузки и возврата к стандартным настро= йкам. Можно завязать контролы напрямую на settings. Сами загружаются при запуске, сами сохраняются при закрытии, сами заполняются = если связаны с контролом.
Основные недостатки - на ка= ждую версию создается свой файл, что изрядно засоряет каталог пользо= вателя, если версии выходят достаточно часто. Кстати, стандартным деин= сталлятором настройки не удаляются. Возврат к стандартным настройкам и со= хранение - иногда глючат. Связка контролов с параметром - может порождать глюки. Все значения настроек сохраняются либо в текстовом фор= мате, либо через xmlSerializer, что иногда сильно увеличивает объем.<= /p>
Общая схема работы с= Settings примерно такая:
Если вы не связываете конт= ролы с settings:
Задаете свойства в дизайне= ре, при загрузке формы - загружаете settings и переписываете значе= ния свойств в контролы, при закрытии формы - переписываете значени= я из контролов в settings и сохраняете их.
Это строчка обращения к по= следним сохраненным settings:
&nb=
sp;namespace.Properties.Settings.Default
Вместо "namespace"=
; - подставте
тот namespace в котором вы работаете.
Загрузка настроек, можно п= рямо завести поле в форме:
&nb=
sp;namespace.Properties.Settings
sets =3D namespace.Properties.Settings.Default;
Переписывание значений в кон= тролы:
&nb=
sp;textBox1.Text
=3D sets.TextFromTextbox1;
Переписывание значений из ко= нтролов:
&nb=
sp;sets.TextFromTextbox1
=3D textBox1.Text;
Сохранение настроек:
&nb=
sp;sets.Save();
В идеале, если контролы с= вязаны с настройками - можете ничего не делать. Все сделают за вас, но = есть несколько моментов, из-за которых до сих пор удобнее расширенные = настройки делать вручную.
Все контролы должны быть = с имплементированным IBindableComponent интерфейсом. Соответственно, если вы поль= зуетесь своим контролом - интерфейс имплементировать будете сами. Е= сли контрол без интерфейса - переписывание значений и загрузка оп= ять лягут на вас.
Некоторые контролы из-за ав= томатической синхронизации с settings глючат, и их все равно приходится обра= батывать вручную.
Для удобства управления = есть еще 4 события класса settings:
SettingsLoaded - сраба= тывает, когда загружаются свойства. Рекомендуется использовать для п= роверки правильности начальных значений.
SettingChanged - сраба= тывает до изменения значения свойства. Рекомендуется для проверки вв= еденного значения на правильность.
PropertyChanged - не ре= комендовано к использованию, если не требуется проверка введенного свойст= ва в отдельном потоке.
SettingsSaved - сраба= тывает перед записью свойств в файл. Рекомендуется использовать для про= верки правильности свойств перед сохранением, и для дополнительной си= нхронизации значений.
Итого, settings хорошо исп= ользовать если у вас нет собственных контролов, которые надо связывать, = если все контролы, которые вы используете не глючат и если версии ва= шей программы выходят редко. Тогда это удобно, можно через дизайнер = завязать кучу параметров внешнего вида в настройки, и будет у вас клевая= прога, которая помнит в каком месте экрана ее закрыли :).
Основное на этом заканчива= ется. Остальное - всякие приколы, а-ля собственные классы для свойств, = для обработчика свойств и пр.
&nb= sp;
Сериализация объектов= - процесс перевода созданного объекта класса в универсальный поток байт,= который может быть потом преобразован обратно в объект, с помощью десер= иализации. Такое вот общее определение. Поскольку техника универсальная, = более конкретно ничего сказать не получиться. Рассмотрим на примере,= может так яснее будет:
Пример 1. Предположим вы пи= шете программу для работы с диаграммами, а-ля Visio. Итогом работы п= ользователя будет изображение - растровое, векторное - не важно. Но промежу= точный результат работы - это именно диаграмма, т.е. всякие формы, стрел= ки, текст, все это имеет параметры типа координат, цвета и пр. Пользо= ватель, разумеется, захочет сохранить свою работу, причем сохранить та= к, чтобы можно было потом редактировать. Как вы поступите? Ну, ско= рее всего, создадите свой формат файла и будете туда последователь= но выгружать все, что пользователь насоздавал. Вот тут-то вам и пона= добиться сериализиция - вы можете выгружать объекты класса, вместе со вс= еми свойствами автоматически. Соответственно, когда пользоват= ель захочет загрузить работу - вы запускаете десериализацию.
Пример 2. Предположим, у ва= с есть программа, состоящая из двух частей - клиентской и серверной. По= льзователь работает на клиенте, а потом сохраняет работу на сервере. Вам = надо передать все, что он наработал на сервер... Как именно передавать= - не суть важно, в любом случае, передать вы можете только поток бай= т, значит сначала, надо привести работу пользователя к этому пот= оку. Сериализация именно для этого и делалась - просто, на сей раз, вы = выгружаете работу не в файл, а в сетевой поток. А серверная часть, получив по= ток, десериализирует его и записывает куда ей надо.
Насчет где используется= можно считать я сказал. В примерах выше два основных использова= ния техники. В общем виде это звучит как "используется при необх= одимости передать данные об объекте класса между двумя приложениями, уме= ющими с этим классом работать". Хотя, это не совсем верно, поскольку= можно и не уметь работать с классом, просто тогда не понятно, зачем это н= адо?..
Как пользоваться
Очень просто - любой класс, = который имеет аттрибут [Serializable] и функцию GetObjectData, может быть сериализован. Если у класса есть десериализационный конс= труктор - он может быть десериализован. Большинство классов .net поддер= живают сериализацию. Обратите внимание - когда вы смотрите описание = класса в msdn - у многих классов есть приписка ISerializable, IXmlSeri= alizable, а перед названием класса стоит [SerializableAttribute] (Напр= имер, DataTable). У структур (например, Point) есть только аттрибут. Им= интерфейс не нужен.
Как сделать свой класс сери= ализируемым:
&nb=
sp;[Serializable()]
&nb=
sp;[ComVisibleAttribute(false)]
&nb=
sp;public
class SerializableClass : ISerializable
&nb=
sp;{
&nb=
sp;public
SerializableClass() {
&nb=
sp;dataField
=3D 0;
&nb=
sp;dataField2
=3D 0.0;
&nb=
sp;}
&nb=
sp;private
int dataField;
&nb=
sp;private
double dataField2;
&nb=
sp;public
virtual void GetObjectData(SerializationInfo info, Streami=
ngContext
context) {
&nb=
sp;// добавляем
переменные в список на сохранение
&nb=
sp;info.AddValue("dataField",
dataField);
&nb=
sp;info.AddValue("dataField2",
dataField2);
&nb=
sp;}
&nb=
sp;}
Как включить десериализа= цию - добавить конструктор:
&nb=
sp;public
SerializableClass(SerializationInfo info, StreamingContext =
context)
: this() {
&nb=
sp;dataField
=3D info.GetInt32("dataField");
&nb=
sp;dataField2
=3D info.GetDouble("dataField2");
&nb=
sp;}
А можно десериализацию с= делать универсальной, используя Reflection:
&nb=
sp;public
SerializableClass(SerializationInfo info, StreamingContext =
context)
: this() {
&nb=
sp;SerializationInfoEnumerator
en =3D info.GetEnumerator();
&nb=
sp;en.MoveNext();
&nb=
sp;for
(int i =3D 0; i ‹ info.MemberCount; i++) {
&nb=
sp;this.GetType().InvokeMember(en.Current.Name,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.No=
nPublic
| BindingFlags.SetProperty, null, this, new object[1] { en.Current=
.Value
});
&nb=
sp;en.MoveNext();
&nb=
sp;}
&nb=
sp;}
Сериализацию тоже можно сде= лать универсальной, по сути, если не имплементировать интерфейс -= сериализатор будет стараться выгрузить все члены класса... Но, как правило, в к= лассе находиться несколько членов без аттрибута Serializable, и сер= иализатор выкидывает ошибку. Поэтому, чаще всего, приходиться члены на з= апись забивать вручную, через GetObjectData.
Как запустить сериализац= ию:
Предположим, у нас в классе в= ся нужная инфорамция храниться в коллекции DataObjects, являющейся колл= екцией объектов типа SerializableClass. Также, предположим, у Serial= izableClass есть event mouseDown. Event'ы не сохраняются, естественно, поэто= му при десериализации, надо event опять устанавливать.
&nb=
sp;internal
void Serialize(string filename) {
&nb=
sp;Stream
stream;
&nb=
sp;IFormatter
formatter =3D new BinaryFormatter();
&nb=
sp;try {
&nb=
sp;stream
=3D new FileStream(filename, FileMode.Create, FileAccess.Wri=
te, FileShare.None);
&nb=
sp;}
&nb=
sp;catch
(Exception ex) { throw ex; }
&nb=
sp;try {
&nb=
sp;for
(int i =3D 0; i ‹ DataObjects.Count; i++) {
&nb=
sp;formatter.Serialize(stream,
DataObjects[i]);
&nb=
sp;}
&nb=
sp;}
&nb=
sp;catch
(Exception ex) { throw ex; }
&nb=
sp;finally
{ stream.Close(); }
&nb=
sp;}
Как запустить десериализ= ацию:
&nb=
sp;internal
void DeSerialize(string filename, MouseEventHandler mouseDo=
wn)
{
&nb=
sp;IFormatter
formatter =3D new BinaryFormatter();
&nb=
sp;Stream
stream;
&nb=
sp;try {
&nb=
sp;stream
=3D new FileStream(filename, FileMode.Open, FileAccess.Read, =
FileShare.Read);
&nb=
sp;}
&nb=
sp;catch
(Exception ex) {
&nb=
sp;throw
ex;
&nb=
sp;}
&nb=
sp;object
obj;
&nb=
sp;try {
&nb=
sp;while
(stream.Position ‹ stream.Length-1) {
&nb=
sp;obj =3D
formatter.Deserialize(stream);
&nb=
sp;// устанавливаем
event, если надо
&nb=
sp;((SerializableClass)obj).MouseDown
+=3D mouseDown;
&nb=
sp;//
вносим объект в программу
&nb=
sp;DataObjects.Add((SerializableClass)obj);
&nb=
sp;}
&nb=
sp;}
&nb=
sp;catch
(Exception ex) {
&nb=
sp;throw
ex;
&nb=
sp;}
&nb=
sp;finally
{ stream.Close(); }
&nb=
sp;}
Немного о типах сериали= зации - есть два основных типа: бинарный и xml. Первый компактнее, втор= ой универсальнее. Лично я никогда xml сериализацией не пользовал= ся, и считаю ее мало нужной вещью... можно спорить, конечно, но на мой взгляд, предлагаемая универсальность довольно мнимая вещь.
Помимо этого вы можете сде= лать свой сериализатор, задача долгая, нудная, но может принести нем= алые выгоды. Для справки: сериализация Int32 занимает 54 байта, даже= в бинарном виде, поскольку 50 байт уходят на название класса. Так что иногда лучше пользоваться своими сериализаторами.
Итоги:
Основные плюсы сериализац= ии - удобство для программиста. Помимо простоты написания кода, там еще есть механизм отслеживания версий, например.
Основные минусы - большие о= бъемы, и необходимость часто вручную перечислять тех, кого надо выгру= жать.
&nb= sp; Перевод с комментариями статьи с codeproject.com: http://www.c= odeproject.com/csharp/introreflection.asp. Это не полный перевод статьи, а только пересказ тех мест, которые я сче= л самыми важными.
Введение
Reflection - значител= ьное нововведение в .NET. Через Reflection программы собирают и ра= ботают со своими метаданными. Это мощный механиз для изучения assembly и разных объектов во время работы программы. Рассматриваемое API = находится в System.Reflection namespace. Через Reflection можно получить= информацию о классах, методах, свойствах и событиях любого объекта, а так= же можно вызывать методы. И т.д.
Использование Reflection= для получения списка используемых assembly
&nb=
sp;using
System;
&nb=
sp;using
System.Reflection;
&nb=
sp;namespace
ReflectionDemoCSharp
&nb=
sp;{
&nb=
sp;class
ReferencedAssemblies
&nb=
sp;{
&nb=
sp;[STAThread]
&nb=
sp;static
void Main(string[] args)
&nb=
sp;{
&nb=
sp;Assembly[]
appAssemblies =3D
&nb=
sp;System.AppDomain.CurrentDomain.GetAssemblies
();
&nb=
sp;foreach
(Assembly assembly in appAssemblies )
&nb=
sp;{
&nb=
sp;Console.WriteLine
(assembly.FullName );
&nb=
sp;}
&nb=
sp;Console.ReadLine
();
&nb=
sp;}
&nb=
sp;}
&nb=
sp;}
Вывод будет таким:
&nb=
sp;mscorlib,
Version=3D1.0.5000.0, Culture=3Dneutral, PublicKeyToken=3Db77a5c5=
61934e089
&nb=
sp;ReflectionDemoCSharp,
Version=3D1.0.1882.29904, Culture=3Dneutral, PublicKeyToken=3Dnul=
l
Класс System.AppDomain class= представляет домен приложения, который является некоей изолированной обл= астью, в которой исполняется приложение.
Надо пояснить, что это из= олированная область памяти, потоков, процессов и прочего. Для каждого прило= жения создается свой домен, и все необходимые assembly загружаются в= домен каждого приложения. Для общения между доменами есть свои особые= механизмы.
Метод GetAssemblies() воз= вращает список assembly, загруженных в домен ReflectionDemoCSharp.
Исследование assembly и по= лучение списка типов
Для начала, динамически = загружаем assembly, через Assembly.Load().
&nb=
sp;public
static Assembly.Load(AssemblyName)
Передаем MsCorLib.dll.
&nb=
sp;Assembly
LoadedAssembly =3D Assembly.Load("mscorlib.dll");
Когда assembly загружена, = можем воспользоваться методом GetTypes(), чтобы получить массив тип= ов.
&nb=
sp;System.Type[]
ExistingTypes =3D LoadedAssembly.GetTypes ();
Возвращаемые типы могут пред=
ставлять
классы, интерфейсы или перечисления.
&nb=
sp;using
System;
&nb=
sp;using
System.Reflection ;
&nb=
sp;namespace
ReflectionDemoCSharp
&nb=
sp;{
&nb=
sp;class
ReflectedTypes
&nb=
sp;{
&nb=
sp;[STAThread]
&nb=
sp;static
void Main(string[] args)
&nb=
sp;{
&nb=
sp;Assembly
LoadedAssembly =3D Assembly.Load ("mscorlib.dll");
&nb=
sp;System.Type[]
ExistingTypes =3D LoadedAssembly.GetTypes ();
&nb=
sp;foreach(Type
type in ExistingTypes)
&nb=
sp;Console.WriteLine
(type.ToString ());
&nb=
sp;Console.WriteLine
(ExistingTypes.Length +
&nb=
sp;"
Types Discovered in mscorlib.dll");
&nb=
sp;Console.ReadLine
();
&nb=
sp;}
&nb=
sp;}
&nb=
sp;}
Вывод (частичный):
&nb=
sp;System.Object
&nb=
sp;System.ICloneable
&nb=
sp;System.Collections.IEnumerable
&nb=
sp;System.Collections.ICollection
&nb=
sp;System.Collections.IList
&nb=
sp;System.Array
&nb=
sp;System.Array+SorterObjectArray
&nb=
sp;System.Array+SorterGenericArray
&nb=
sp;System.Collections.IEnumerator
&nb=
sp;1480
Types Discovered in mscorlib.dll
Исследование типа
Попробуем получить список в= сех членов конкретного типа. Метод Type.GetType(TypeName) возвра= щает объект System.Type, соответствующий строке аргумента. Мы запро= сим тип с помощью метода Type.GetMembers(), чтобы получить массив его = членов.
Я уже рассказывал, что в= место Type.GetType("System.Int32") можно использовать typeof(in= t).
&nb=
sp;using
System;
&nb=
sp;using
System.Reflection ;
&nb=
sp;namespace
ReflectionDemoCSharp
&nb=
sp;{
&nb=
sp;class
ReflectedTypes
&nb=
sp;{
&nb=
sp;[STAThread]
&nb=
sp;static
void Main(string[] args)
&nb=
sp;{
&nb=
sp;Type
TypeToReflect =3D Type.GetType("System.Int32");
&nb=
sp;System.Reflection.MemberInfo[]
Members =3Dtype.GetMembers();
&nb=
sp;Console.WriteLine
("Members of "+TypeToReflect.ToString ());
&nb=
sp;Console.WriteLine();
&nb=
sp;foreach
(MemberInfo member in Members )
&nb=
sp;Console.WriteLine(member);
&nb=
sp;Console.ReadLine
();
&nb=
sp;}
&nb=
sp;}
&nb=
sp;}
Вывод:
&nb=
sp;Members
of System.Int32
&nb=
sp;Int32
MaxValue
&nb=
sp;Int32
MinValue
&nb=
sp;System.String
ToString(System.IFormatProvider)
&nb=
sp;System.TypeCode
GetTypeCode()
&nb=
sp;System.String
ToString(System.String, System.IFormatProvider)
&nb=
sp;Int32
CompareTo(System.Object)
&nb=
sp;Int32
GetHashCode()
&nb=
sp;Boolean
Equals(System.Object)
&nb=
sp;System.String
ToString()
&nb=
sp;System.String
ToString(System.String)
&nb=
sp;Int32
Parse(System.String)
&nb=
sp;Int32
Parse(System.String, System.Globalization.NumberStyles)
&nb=
sp;Int32
Parse(System.String, System.IFormatProvider)
&nb=
sp;Int32
Parse(System.String, System.Globalization.NumberStyles, System.I=
FormatProvider)
&nb=
sp;System.Type
GetType()
* System.Reflection.Membe=
rInfo[]
Members =3Dtype.GetMembers() - возвращает всех членов типа.
* System.Reflection.Metho= dInfo[] Methods =3DType.GetMethods() - возвращает только методы типа.
* System.Reflection.Field= Info[] Fields =3DType.GetFields() - возвращает только поля типа.
* System.Reflection.Proper= tyInfo[] Properties =3D type.GetProperties () - возвращает только свой= ства.
* System.Reflection.EventI= nfo[] Events =3D type.GetEvents() - возвращает события типа.
* System.Reflection.Const= ructorInfo[] Constructors =3D type.GetConstructors () - возвращает конст= рукторы типа.
* System.Type[] Interfaces = =3D type.GetInterfaces() - возвращает интерфейсы типа.
Динамический вызов, испол= ьзуя Type.InvokeMember()
Рассмотрим, как динамическ= и вызвать метод, используя type.InvokeMember(). type.InvokeMember() позвол= яет вызывать методы по их названиям.
Аргументы InvokeMember():<= /p>
1. Имя вызываемого метод= а. Передается строкой.
2. Члены перечисления Bin= dingFlags. Перечисление BindingFlags определяет флаги, которые контро= лируют связывание и способ поиска типов и членов.Например, искать ли= среди static методов и т.п.
3. Объект Binder, определ= яющий свойства и процесс связывания. Может быть null, для использования Binder по умолчанию. Этот параметр позволяет пользователю полу= чить внешний контроль над способом выбора переопределенных функци= й и методами преобразования аргументов.
4. Объект, для которого на= до вызвать метод.
5. Массив аргументов, пер= едаваемых методу.
&nb=
sp;using
System;
&nb=
sp;using
System.Reflection ;
&nb=
sp;namespace
ReflectionDemoCSharp
&nb=
sp;{
&nb=
sp;class
ReflectedTypes
&nb=
sp;{
&nb=
sp;[STAThread]
&nb=
sp;static
void Main(string[] args)
&nb=
sp;{
&nb=
sp;Type
TypeToReflect =3D Type.GetType("System.String");
&nb=
sp;object
result =3D null;
&nb=
sp;object[]
arguments =3D {"abc","xyz"};
&nb=
sp;result
=3D TypeToReflect.InvokeMember ("Equals",
&nb=
sp;BindingFlags.InvokeMethod,
null, result, arguments);
&nb=
sp;Console.WriteLine
(result.ToString ());
&nb=
sp;Console.ReadLine
();
&nb=
sp;}
&nb=
sp;}
&nb=
sp;}
Вывод будет "false"=
;.
Дальше в статье рассказы= вается о технологии Reflection.Emit, которая позволяет создавать ass= embly на лету... Честно говоря, я этой технологии вижу только одно при= менение - создание собственных компиляторов. Впрочем, сейчас модно вст= авлять в программы возможность дописывания своих плагинов прямо из ок= на программы... а поскольку плагины проще писать на внутреннем языке приложения, то наверное и собственный компилятор может быть по= лезен. Хотя не знаю, есть же всякие скриптовые компиляторы, уже включенн= ые в состав .net, есть технология VBA, которую многие не любят, но хуже= она от этого не становиться и т.д.
Глава 4. Самодельные элементы управления 1.<= /b>
&nb= sp;
Элемент управления - это лю= бой кусок интерфейса, который сажается в окно и как-то реагирует на пользователя. Всякие кнопки, менюшки, списки и прочее.
Часто бывает ситуация, ког= да хочется (или требуется) как-то расширить возможности элемента. Например= , хочется сделать другую рисовку пунктов меню, или вместо одного ползунка= нужно сделать два - для установки минимума, максимума.
В случаях, когда надо изм= енить рисовку, сначала нужно посмотреть - нельзя ли это сделать с помощ= ью родных методов элемента. Например, все пункты меню, выпадающих списков и подобные им имеют событие DrawItem, в котором можно оп= исывать свою функцию рисования объекта.
Рассмотрим на примере. Нам н= адо сделать выпадающее окно, каждый пункт списка которого состоит из числа, текста и цвета.
Класс хранения данных:
&nb=
sp;public
class ComboData
&nb=
sp;{
&nb=
sp;public
int number; // хранимое число
&nb=
sp;public
string text; // текст
&nb=
sp;public
Color color; // и цвет
&nb=
sp;public
ComboData() { // пустой конструктор
&nb=
sp;number
=3D 0;
&nb=
sp;text
=3D "";
&nb=
sp;color
=3D Color.Black;
&nb=
sp;}
&nb=
sp;public
ComboData(int inNumber, string inText, Color inColor) { // парам=
етрический
конструктор
&nb=
sp;number
=3D inNumber;
&nb=
sp;text
=3D inText;
&nb=
sp;color
=3D inColor;
&nb=
sp;}
&nb=
sp;public
override string ToString() { // метод для вывода в стандартное вы=
падающее
окно
&nb=
sp;return
String.Format("{0}, {1}, {2}", number, text, color);
&nb=
sp;}
&nb=
sp;}
Начальные данные:
&nb=
sp;public
Form1() {
&nb=
sp;InitializeComponent();
&nb=
sp;items
=3D new ComboData[5];
&nb=
sp;items[0]
=3D new ComboData(0, "text1", Color.Red);
&nb=
sp;items[1]
=3D new ComboData(1, "text2", Color.Blue);
&nb=
sp;items[2]
=3D new ComboData(2, "text3", Color.Green);
&nb=
sp;items[3]
=3D new ComboData(3, "text4", Color.Magenta);
&nb=
sp;items[4]
=3D new ComboData(4, "text5", Color.FromArgb(00, 221, 238));=
&nb=
sp;comboBox1.Items.AddRange(items);
&nb=
sp;}
&nb=
sp;public
ComboData[] items;
В общем-то, можно и так ост= авить - будет в выпадающем окне такая картина:

&nb= sp;
Но как-то оно не правильно п= оказывать цвет цифрами. Поэтому, задаем comboBox свойcтво DrawMode =3D Ow= nerDrawFixed и прописываем событие DrawItem:
&nb=
sp;private
void comboBox1_DrawItem(object sender, DrawItemEventArgs e) {
&nb=
sp;SolidBrush
br =3D new SolidBrush(items[e.Index].color); // создаем кисть цвета =
элемента,
который рисуем
&nb=
sp;if
(e.State =3D=3D DrawItemState.Selected) { // если элемент выбран=
&nb=
sp;e.Graphics.FillRectangle(br,
e.Bounds); // закрашиваем его нашим цветом
&nb=
sp;e.Graphics.DrawString(items[e.Index].ToString(),
e.Font, SystemBrushes.HighlightText, e.Bounds.Location); // по=
верх
рисуем текст
&nb=
sp;e.DrawFocusRectangle();
&nb=
sp;}
&nb=
sp;else
{ // если элемент не выбран
&nb=
sp;e.DrawBackground();
// рисуем фон
&nb=
sp;e.Graphics.DrawString(items[e.Index].ToString(),
e.Font, SystemBrushes.MenuText, e.Bounds.Location); // пишем стр=
оку
&nb=
sp;}
&nb=
sp;br.Dispose();
// освобождаем кисть
&nb=
sp;}
Теперь=
это
выглядит так:

&nb= sp;
Принцип, я думаю, понятен.<= /p>
Иногда бывает надо сделать= набор групп элементов. Можно пойти простым путем, и создавать каждый эле= мент в отдельности, а можно создать элемент управления Группа и в про= цессе работы программы создавать его.
Рассмотрим на том же примере= - надо вывести все значения в виде текстовых окон и панелек цвета. Вар= иант 1:
&nb=
sp;internal
void CreateControls() { // функция создания элементов управле=
ния
&nb=
sp;TextBox
tb;
&nb=
sp;Panel
p;
&nb=
sp;SuspendLayout();
&nb=
sp;for
(int i =3D 0; i ‹ items.Length; i++) {
&nb=
sp;tb =3D
new TextBox(); // текстовое поле для числа
&nb=
sp;tb.Location
=3D new Point(20, 50 + i * 25); // позиция
&nb=
sp;tb.Size
=3D new Size(50, 20); // размер
&nb=
sp;tb.Text
=3D items[i].number.ToString(); // текст
&nb=
sp;Controls.Add(tb);
// добавляем элемент в окно
&nb=
sp;tb =3D
new TextBox(); // текстовое поле для текста
&nb=
sp;tb.Location
=3D new Point(75, 50 + i * 25);
&nb=
sp;tb.Size
=3D new Size(50, 20);
&nb=
sp;tb.Text
=3D items[i].text;
&nb=
sp;Controls.Add(tb);
&nb=
sp;p =3D
new Panel(); // панель для цвета
&nb=
sp;p.Location
=3D new Point(130, 50 + i * 25);
&nb=
sp;p.Size
=3D new Size(50, 20);
&nb=
sp;p.BackColor
=3D items[i].color; // задаем цвет
&nb=
sp;p.Tag
=3D i; // устанавливаем идентификатор
&nb=
sp;p.Click
+=3D new EventHandler(p_Click); // устанавливаем событие клик
&nb=
sp;Controls.Add(p);
&nb=
sp;}
&nb=
sp;ResumeLayout(false);
&nb=
sp;}
Добавим в Form1() последней =
строкой
запуск функции создания элементов
&nb=
sp;CreateControls();
И пропишем событие клик дл=
я панелей:
&nb=
sp;void
p_Click(object sender, EventArgs e) {
&nb=
sp;int
idx =3D (int)((Panel)sender).Tag; // номер панели
&nb=
sp;MessageBox.Show(String.Format("color
panel of item {0} just has been clicked", idx)); // сообщение
&nb=
sp;}
Вот так вот.=

&nb= sp;
Вроде все в порядке. С= тем же успехом можно сделать свой элемент управления, который будет = состоять из двух текстовых полей и одной панельки, и в форму добавлять его. Создаем UserControl, создаем на нем два текстовых поля и панель= ку.
Пишем два конструктора:= p>
&nb=
sp;public
UserControl1(ComboData item) { // простой конструктор
&nb=
sp;InitializeComponent();
&nb=
sp;Num =3D
item.number;
&nb=
sp;Tex =3D
item.text;
&nb=
sp;Col =3D
item.color;
&nb=
sp;}
&nb=
sp;public
UserControl1(ComboData item, int panelIdx, EventHandler panel=
Click)
{ // конструткор с поддержкой клика
&nb=
sp;InitializeComponent();
&nb=
sp;Num =3D
item.number;
&nb=
sp;Tex =3D
item.text;
&nb=
sp;Col =3D
item.color;
&nb=
sp;panel1.Tag
=3D panelIdx;
&nb=
sp;panel1.Click
+=3D panelClick;
&nb=
sp;}
Прописываем поля данных:
&nb=
sp;private
int num; // число
&nb=
sp;public
int Num { // свойство число
&nb=
sp;get {
return num; }
&nb=
sp;set {
&nb=
sp;num =3D
value;
&nb=
sp;textBox1.Text
=3D num.ToString(); // обновление текстового поля
&nb=
sp;}
&nb=
sp;}
&nb=
sp;private
string tex; // текст
&nb=
sp;public
string Tex { // свойство текст
&nb=
sp;get {
return tex; }
&nb=
sp;set {
&nb=
sp;tex =3D
value;
&nb=
sp;textBox2.Text
=3D tex;
&nb=
sp;}
&nb=
sp;}
&nb=
sp;private
Color col; // цвет
&nb=
sp;public
Color Col { // свойство цвет
&nb=
sp;get {
return col; }
&nb=
sp;set {
&nb=
sp;col =3D
value;
&nb=
sp;panel1.BackColor
=3D col; // обновление цвета
&nb=
sp;}
&nb=
sp;}
В Form1 пишем функцию созд= ания нашего элемента:
&nb=
sp;internal
void CreateUserControls() {
&nb=
sp;UserControl1
uc;
&nb=
sp;SuspendLayout();
&nb=
sp;for (int
i =3D 0; i ‹ items.Length; i++) {
&nb=
sp;uc =3D
new UserControl1(items[i], i, new EventHandler(p_Click));
&nb=
sp;uc.Location
=3D new Point(20, 50 + i * 25);
&nb=
sp;uc.Tag
=3D i;
&nb=
sp;Controls.Add(uc);
&nb=
sp;}
&nb=
sp;ResumeLayout(false);
&nb=
sp;}
И в Form1() меняем CreateC= ontrols() на CreateUserControls().
Все работает так же, но ис= пользует самодельную группу управления. Удобство этого подхода в двух м= оментах:
1. Если вам понадобятся в= сякие фичи с выделением - в вашем распоряжении событие Paint для все= го элемента управления, рамочку там сделать или еще чего. При созда= нии каждого поля в отдельности подобные рамочки будут делаться сло= жнее, да и скорость упадет заметнее.
2. Если таких элементов уп= равления не 5, а 50, то скорость работы существенно повышается. Система= проверяет видимость элемента управления, и если он целиком невиден - она = не проверяет его дочерние элементы. Стало быть, если использовать = такой метод группировки - система будет проверять 50 элементов на вид= имость. А если поля приписывать форме напрямую, как в варианте 1, то сис= тема будет проверять 150 элементов.
Ну и напоследок: стандарт= ный comboBox имеет событие SelectedIndexChanged, но он не сообща= ет в нем, какой элемент был выбран до смены. Попробуем модифицировать стандартный comboBox так, чтобы он в этом событии сообщал о пред= ыдущем значении.
Создаем очередной UserCont= rol и переходим в код, не обращая внимания на дизайнер. Ставим родите= лем нашего элемента класс ComboBox вместо UserControl:
&nb=
sp;public
partial class UserControl3 : System.Windows.Forms.ComboBox
Прописываем переменную для = хранения предыдущего значения выбранного индекса:
&nb=
sp;private
int selectedIndex_prev;
В конструктор добавляем= инициализацию этой переменной:
&nb=
sp;selectedIndex_prev
=3D -1;
И переписываем событие = SelectedIndexChanged:
&nb=
sp;protected
override void OnSelectedIndexChanged(EventArgs e) {
&nb=
sp;base.OnSelectedIndexChanged(new
UserControl2IndexChangedEventArgs(selectedIndex_prev)); // ст=
андартный
обработчик, но с нашим классом аргументов события
&nb=
sp;selectedIndex_prev
=3D this.SelectedIndex; // изменить пред. индекс
&nb=
sp;}
Создаем отдельный класс для = аргументов нашего события:
&nb=
sp;public
class UserControl2IndexChangedEventArgs : EventArgs
&nb=
sp;{
&nb=
sp;public
UserControl2IndexChangedEventArgs(int prevIdx) {
&nb=
sp;prevSelectedIndex
=3D prevIdx;
&nb=
sp;}
&nb=
sp;public
int prevSelectedIndex;
&nb=
sp;}
Вот и все. Для проверки доб= авим в Form1 наш видоизмененный comboBox и пропишем событие Selecte= dIndexChanged:
&nb=
sp;private
void userControl31_SelectedIndexChanged(object sender, EventA=
rgs
e) {
&nb=
sp;MessageBox.Show(String.Format("Selected
index was changed from {0} to {1}", ((UserControl2IndexChange=
dEventArgs)e).prevSelectedIndex,
userControl31.SelectedIndex));
&nb=
sp;}
Не забываем в конструкт= ор формы добавить заполнение нашего comboBox'a значениями:
&nb=
sp;userControl31.Items.AddRange(items);
Каталог проекта для MS Visu=
al
Studio 2005 с примером находится в архиве примеров, подкаталог
usercontrol1.
Архив примеров можно скача= ть здесь: http://www.ro= binland.com/csharp-basis/samples.zip.
Глава 5. Самодельные элементы управления 2.<= /b>
&nb= sp; В предыдущей главе мы рассмотрели ситуации при которых можно ком= бинировать уже существующие элементы управления, или достаточно просто мо= дифицировать их. Однако, бывают ситуации когда никакой модификацией нужны= й результат не будет достигнут. Рассмотрим одну такую на примере.
Необходимо сделать элемент у= правления, позволяющий устанавливать два значения в заданном интервале. = Для установки одного значения есть родной элемент управления - sli= der (или TrackBar). Ленивые люди скажут, что нечего тут извращаться, достаточно посадить два слайдера и не морочить голову. :) С дру= гой стороны, сделать свой элемент управления достаточно просто, а ин= терфейс он может значительно улучшить, ибо чем меньше элементов в окне - т= ем лучше.

&nb= sp; Приступим.
К проекту присоединяем U= serControl и задаем основные свойства:
&nb=
sp;this.MaximumSize
=3D new System.Drawing.Size(3000, 15); // максимальный размер
&nb=
sp;this.MinimumSize
=3D new System.Drawing.Size(50, 15); // минимальный размер
&nb=
sp;this.Size
=3D new System.Drawing.Size(50, 15); // стартовый размер
Наш элемент управления, с = точки зрения рисования, состоит из вдавленной полоски и двух ползунко= в. Каждый ползунок может быть в трех состояниях - нормальном, подсв= еченном (когда мышка над ним) и активном (когда его тащат). зададим соотв= етствующие флаги. А заодно создадим прямоугольники ползунков, чтобы проще= было потом рисовать и отслеживать:
&nb=
sp;private
bool slider1drag; // ползунок 1 тащат
&nb=
sp;private
bool slider2drag; // ползунок 2 тащат
&nb=
sp;private
bool slider1hover; // ползунок 1 подсвечен
&nb=
sp;private
bool slider2hover; // ползунок 2 подсвечен
&nb=
sp;private
Rectangle slider1; // ползунок 1
&nb=
sp;private
Rectangle slider2; // ползунок 2
Теперь можно попробовать н= арисовать. По сути мы будем рисовать три объемных прямоугольника, один утоп= ленный, два выпуклых... создадим отдельную функцию для этого:
&nb=
sp;private
void DrawRectangle3D(Graphics gr, int x, int y, int width, int heigh=
t,
bool pushed) {
&nb=
sp;if
(pushed) {
&nb=
sp;gr.DrawLine(SystemPens.ControlLightLight,
x + 1, y + height, x + width, y + height);
&nb=
sp;gr.DrawLine(SystemPens.ControlLightLight,
x + width, y + height, x + width, y + 1);
&nb=
sp;gr.DrawLine(SystemPens.ControlDarkDark,
x, y, x + width, y);
&nb=
sp;gr.DrawLine(SystemPens.ControlDarkDark,
x, y, x, y + height);
&nb=
sp;}
&nb=
sp;else
{
&nb=
sp;gr.DrawLine(SystemPens.ControlDarkDark,
x + 1, y + height, x + width, y + height);
&nb=
sp;gr.DrawLine(SystemPens.ControlDarkDark,
x + width, y + height, x + width, y + 1);
&nb=
sp;gr.DrawLine(SystemPens.ControlLightLight,
x, y, x + width, y);
&nb=
sp;gr.DrawLine(SystemPens.ControlLightLight,
x, y, x, y + height);
&nb=
sp;}
&nb=
sp;}
Рисуем системными цветами=
, чтобы
не выбиваться из установленного в системе стиля оформления, х=
отя
бы по цвету.
И вариант функции для раб= оты с объектом Rectangle:
&nb=
sp;private
void DrawRectangle3D(Graphics gr, Rectangle rect, bool pushed) {=
&nb=
sp;if
(pushed) {
&nb=
sp;gr.DrawLine(SystemPens.ControlLightLight,
rect.X + 1, rect.Bottom, rect.Right, rect.Bottom);
&nb=
sp;gr.DrawLine(SystemPens.ControlLightLight,
rect.Right, rect.Bottom, rect.Right, rect.Y + 1);
&nb=
sp;gr.DrawLine(SystemPens.ControlDarkDark,
rect.X, rect.Y, rect.Right, rect.Y);
&nb=
sp;gr.DrawLine(SystemPens.ControlDarkDark,
rect.X, rect.Y, rect.X, rect.Bottom);
&nb=
sp;}
&nb=
sp;else
{
&nb=
sp;gr.DrawLine(SystemPens.ControlDarkDark,
rect.X + 1, rect.Bottom, rect.Right, rect.Bottom);
&nb=
sp;gr.DrawLine(SystemPens.ControlDarkDark,
rect.Right, rect.Bottom, rect.Right, rect.Y + 1);
&nb=
sp;gr.DrawLine(SystemPens.ControlLightLight,
rect.X, rect.Y, rect.Right, rect.Y);
&nb=
sp;gr.DrawLine(SystemPens.ControlLightLight,
rect.X, rect.Y, rect.X, rect.Bottom);
&nb=
sp;}
&nb=
sp;}
Теперь создаем обработчик=
событие
Paint и описываем его:
&nb=
sp;private
void UserControl1_Paint(object sender, PaintEventArgs e) {
&nb=
sp;DrawRectangle3D(e.Graphics,
2, Height / 2 - 2, Width - 4, 4, true); // нарисовать утопленную пол=
оску
&nb=
sp;if
(e.ClipRectangle.IntersectsWith(slider1)) { // если обновляемы=
й регион
пересекает ползунок 1
&nb=
sp;if
(slider1drag) { // если ползунок 1 тащат
&nb=
sp;e.Graphics.FillRectangle(SystemBrushes.ControlDark,
slider1); // закрасить его темным цветом
&nb=
sp;}
&nb=
sp;else
if (slider1hover) { // если ползунок подсвечен
&nb=
sp;e.Graphics.FillRectangle(SystemBrushes.ButtonHighlight,
slider1); // закрасить его цветом подсветки
&nb=
sp;}
&nb=
sp;else
{ // если просто нарисовать
&nb=
sp;e.Graphics.FillRectangle(SystemBrushes.Control,
slider1); // закрасить системным цветом элемента управления
&nb=
sp;}
&nb=
sp;DrawRectangle3D(e.Graphics,
slider1, false); // нарисовать объемную выпуклую рамку
&nb=
sp;}
&nb=
sp;if
(e.ClipRectangle.IntersectsWith(slider2)) { // то же для ползунк=
а 2
&nb=
sp;if
(slider2drag) {
&nb=
sp;e.Graphics.FillRectangle(SystemBrushes.ControlDark,
slider2);
&nb=
sp;}
&nb=
sp;else
if (slider2hover) {
&nb=
sp;e.Graphics.FillRectangle(SystemBrushes.ButtonHighlight,
slider2);
&nb=
sp;}
&nb=
sp;else
{
&nb=
sp;e.Graphics.FillRectangle(SystemBrushes.Control,
slider2);
&nb=
sp;}
&nb=
sp;DrawRectangle3D(e.Graphics,
slider2, false);
&nb=
sp;}
&nb=
sp;}
Теперь надо задать прямоуг= ольники ползунков и все значения на которые они опираются. Сначала необ= ходимые значения:
Минимум полоски слайдера:<= /p>
&nb=
sp;private
int minVal; // поле для внутреннего пользования
&nb=
sp;[Browsable(true)]
// показывать в дизайнере
&nb=
sp;[RefreshProperties(RefreshProperties.All)]
// обновлять остальные свойства при изменении этого
&nb=
sp;public
int MinimumValue { // свойство (видимое и в дизайнере)
&nb=
sp;get {
return minVal; }
&nb=
sp;set {
&nb=
sp;if
(value ›=3D maxVal) { throw new ArgumentException("minimum v=
alue
must be ‹ maximum"); } // проверить на меньше максимума
&nb=
sp;minVal
=3D value; // установить значение
&nb=
sp;if
(Value2 ‹=3D minVal) { Value2 =3D minVal+1; } // поправить значения=
ползунков
если надо
&nb=
sp;if
(Value1 ‹ minVal) { Value1 =3D minVal; }
&nb=
sp;SetPos1();
// поправить прямоугольник ползунка 1
&nb=
sp;SetPos2();//
поправить прямоугольник ползунка 2
&nb=
sp;}
&nb=
sp;}
Аналогично остальные поля:<=
span
style=3D'color:black'>
&nb=
sp;private
int maxVal; // максимум полоски слайдера
&nb=
sp;[Browsable(true)]
&nb=
sp;[RefreshProperties(RefreshProperties.All)]
&nb=
sp;public
int MaximumValue {
&nb=
sp;get {
return maxVal; }
&nb=
sp;set {
&nb=
sp;if
(value ‹=3D minVal) { throw new ArgumentException("maximum v=
alue
must be › minimum"); }
&nb=
sp;maxVal
=3D value;
&nb=
sp;if
(Value1 ›=3D maxVal) { Value1 =3D maxVal-1; }
&nb=
sp;if
(Value2 › maxVal) { Value2 =3D maxVal; }
&nb=
sp;SetPos1();
&nb=
sp;SetPos2();
&nb=
sp;}
&nb=
sp;}
&nb=
sp;private
int val1; // значение ползунка 1
&nb=
sp;[Browsable(true)]
&nb=
sp;public
int Value1 {
&nb=
sp;get {
return val1; }
&nb=
sp;set {
&nb=
sp;if
(value › maxVal) { throw new ArgumentException("value1 must be
between min and max values"); } // меньше максимума
&nb=
sp;if
(value ‹ minVal) { throw new ArgumentException("value1 must be
between min and max values"); } // больше минимума
&nb=
sp;if
(value ›=3D Value2) { throw new ArgumentException("value1 mus=
t be ‹
Value2"); } // меньше ползунка 2
&nb=
sp;val1
=3D value; // установить значение
&nb=
sp;SetPos1();
// поправить прямоугольник ползунка 1
&nb=
sp;}
&nb=
sp;}
&nb=
sp;private
int val2; // аналогично ползунок 2
&nb=
sp;[Browsable(true)]
&nb=
sp;public
int Value2 {
&nb=
sp;get {
return val2; }
&nb=
sp;set {
&nb=
sp;if
(value › maxVal) { throw new ArgumentException("value2 must be
between min and max values"); }
&nb=
sp;if
(value ‹ minVal) { throw new ArgumentException("value2 must be
between min and max values"); }
&nb=
sp;if
(value ‹=3D Value1) { throw new ArgumentException("value2 mus=
t be ›
Value1"); }
&nb=
sp;OnValueChanged(new
UserControl1ValueChangedEventArgs(val2, value, 2));
&nb=
sp;val2
=3D value;
&nb=
sp;SetPos2();
&nb=
sp;}
&nb=
sp;}
&nb=
sp;
Теперь зададим функции оп=
ределения
прямоугольников ползунков:
&nb=
sp;private
void SetPos1() {
&nb= sp;slider1.X =3D (int)(((double)val1 - minVal) / (maxVal - minVal) * (Width - 5));<= o:p>
&nb=
sp;}
&nb=
sp;private
void SetPos2() {
&nb= sp;slider2.X =3D (int)(((double)val2 - minVal) / (maxVal - minVal) * (Width - 5));<= o:p>
&nb=
sp;}
И добавим инициализацию = в конструктор:
&nb=
sp;public
UserControl1() {
&nb=
sp;InitializeComponent();
&nb=
sp;minVal
=3D 0;
&nb=
sp;maxVal
=3D 100;
&nb=
sp;val2
=3D 100;
&nb=
sp;slider1
=3D new Rectangle(0, 2, 4, Height - 4);
&nb=
sp;slider2
=3D new Rectangle(0, 2, 4, Height - 4);
&nb=
sp;SetPos1();
&nb=
sp;SetPos2();
&nb=
sp;}
Поскольку положение ползун= ков опирается еще и на размер элемента управления, добавим событие= SizeChanged и опишем его:
&nb=
sp;private
void UserControl1_SizeChanged(object sender, EventArgs e) {
&nb=
sp;SetPos1();
&nb=
sp;SetPos2();
&nb=
sp;}
Приступаем к "мышиным&q= uot; функциям:
Создаем события нажатие мы= шки, отпускание мышки и движение мышки.
Нажатие мышки:
&nb=
sp;private
void UserControl1_MouseDown(object sender, MouseEventArgs e) {
&nb=
sp;if
(slider1.Contains(e.Location)) { // если нажали на ползунок 1
&nb=
sp;slider1drag
=3D true; // установить флаг ползунок 1 тащат
&nb=
sp;Invalidate(slider1);
// обновить рисовку ползунка 1
&nb=
sp;}
&nb=
sp;else
if (slider2.Contains(e.Location)) { // то же для ползунка 2
&nb=
sp;slider2drag
=3D true;
&nb=
sp;Invalidate(slider2);
&nb=
sp;}
&nb=
sp;}
Отпускание мышки:
&nb=
sp;private
void UserControl1_MouseUp(object sender, MouseEventArgs e) {
&nb=
sp;if
(slider1drag) { // если тащили ползунок 1
&nb=
sp;slider1drag
=3D false; // снять выделение
&nb=
sp;slider1hover
=3D false;
&nb=
sp;Invalidate(slider1);
// перерисовать внутреннюю часть
&nb=
sp;}
&nb=
sp;else
if (slider2drag) { // то же для ползунка 2
&nb=
sp;slider2drag
=3D false;
&nb=
sp;slider2hover
=3D false;
&nb=
sp;Invalidate(slider2);
&nb=
sp;}
&nb=
sp;}
Перемещение мышки. Тут нам по= надобится возможность обновлять не только сам ползунок, но всю область от пре= дыдущего положения ползунка, до его текущего положения. Иначе будут ос= таваться полоски при быстром движении. Для этого создадим еще одно поле R= ectangle, и пропишем его инициализацию в конструктор:
&nb=
sp;private
Rectangle invalidateRect;
&nb=
sp;public
UserControl1() {
&nb=
sp;...
&nb=
sp;invalidateRect
=3D new Rectangle();
&nb=
sp;}
Вот теперь функция переме= щения мышки:
&nb= sp;private void UserControl1_MouseMove(object sender, MouseEventArgs e) {<= o:p>
&nb=
sp;if
(slider1drag) { // если ползунок 1 тащат
&nb=
sp;if
(e.X ›=3D 2 e.X ‹=3D Width - 3 e.X ‹ slider2.Left+1) { // проверить мож=
но ли сюда
двигаться
&nb=
sp;invalidateRect
=3D slider1; // запомним старое положение ползунка
&nb=
sp;Value1
=3D (int)Math.Round(minVal + ((double)(e.X - 2) / (Width - 5)) * (maxV=
al -
minVal)); // установим новое положение по мышке, что обновит по=
ложение
ползунка
&nb=
sp;invalidateRect
=3D Rectangle.Union(slider1, invalidateRect); // получим прямо=
угольник
объединяющий старое и новое положение
&nb=
sp;Invalidate(Rectangle.Inflate(invalidateRect,
1, 1)); // обновим прямоугольник и прилегающие пиксели
&nb=
sp;}
&nb=
sp;}
&nb=
sp;else
if (slider2drag) { // то же для ползунка 2
&nb=
sp;if
(e.X ›=3D 2 e.X ‹=3D Width - 3 e.X › slider1.Right-1) {
&nb=
sp;invalidateRect
=3D slider2;
&nb=
sp;Value2
=3D (int)Math.Round(minVal + ((double)(e.X - 2) / (Width - 5)) * (maxV=
al -
minVal));
&nb=
sp;invalidateRect
=3D Rectangle.Union(slider2, invalidateRect);
&nb=
sp;Invalidate(Rectangle.Inflate(invalidateRect,
1, 1));
&nb=
sp;}
&nb=
sp;}
&nb=
sp;else
{ // если никого не тащат
&nb=
sp;if
(slider1.Contains(e.Location)) { // если мышка над первым ползун=
ком
&nb=
sp;if (!slider1hover)
{ // если флаг подсветки не установлен
&nb=
sp;slider1hover
=3D true; // установить
&nb=
sp;Invalidate(slider1);
// перерисовать внутреннюю часть ползунка
&nb=
sp;}
&nb=
sp;}
&nb=
sp;else
if (slider2.Contains(e.Location)) { // то же для ползунка 2
&nb=
sp;//draw
hover state
&nb=
sp;if
(!slider2hover) {
&nb=
sp;slider2hover
=3D true;
&nb=
sp;Invalidate(slider2);
&nb=
sp;}
&nb=
sp;}
&nb=
sp;else
{ // если мышка не над ползунками
&nb=
sp;if
(slider1hover) { // если ползунок 1 был подсвечен
&nb=
sp;slider1hover
=3D false; // снять подсветку
&nb=
sp;Invalidate(slider1);
// перерисовать внутреннюю часть
&nb=
sp;}
&nb=
sp;else
if (slider2hover) { // то же для ползунка 2
&nb=
sp;slider2hover
=3D false;
&nb=
sp;Invalidate(slider2);
&nb=
sp;}
&nb=
sp;}
&nb=
sp;}
&nb=
sp;GC.Collect();
// очистить память
&nb=
sp;}
&nb=
sp;
Очистка памяти нужна, пото=
му
как функции Rectangle.Union и Rectangle.Inflate создают новые =
объекты
Rectangle, которые после окончания функции уходят в мусор.
Добавим еще модификатор кл= асса, чтобы его было нормально видно в дизайнере:
&nb=
sp;[DesignTimeVisible(true)]
&nb=
sp;public
partial class UserControl1 : UserControl
&nb=
sp;{
Вобщем-то элемент управлен= ия работает. Можно создать форму и посадить на нее наш элемент, настроить и пос= мотреть как работает. Вот только не очень удобно - при изменении значения= , он об этом не сообщает. Добавим ему событие изменение значения:
&nb=
sp;[Browsable(true)]
// видно в дизайнере
&nb=
sp;public
event ValueChangedEventDelegate ValueChanged =3D null; // со=
бытие
&nb=
sp;public
delegate void ValueChangedEventDelegate(object sender, Use=
rControl1ValueChangedEventArgs
e); // делегат обработчика события
&nb=
sp;protected
virtual void OnValueChanged(UserControl1ValueChangedEvent=
Args
e) { // обработчик
&nb=
sp;if
(ValueChanged !=3D null) {
&nb=
sp;ValueChanged(this,
e);
&nb=
sp;}
&nb=
sp;}
Нам понадобится еще класс = аргументов события:
&nb=
sp;public
class UserControl1ValueChangedEventArgs : EventArgs // аргумен=
ты
события
&nb=
sp;{
&nb=
sp;public
UserControl1ValueChangedEventArgs(int inOldValue, int inNew=
Value,
byte inValue) { // конструктор
&nb=
sp;oldValue
=3D inOldValue; // старое значение
&nb=
sp;newValue
=3D inNewValue; // новое значение
&nb=
sp;value
=3D inValue; // какой ползунок
&nb=
sp;}
&nb=
sp;public
int oldValue;
&nb=
sp;public
int newValue;
&nb=
sp;///
‹summary›
&nb=
sp;/// 1
- value 1, 2 - value 2
&nb=
sp;/// ‹/summary›
&nb=
sp;public
byte value;
&nb=
sp;}
&nb=
sp;
И введем вызов события при=
изменении
значений:
&nb=
sp;public
int Value1 {
&nb=
sp;get {
return val1; }
&nb=
sp;set {
&nb=
sp;if
(value › maxVal) { throw new ArgumentException("value1 must be
between min and max values"); }
&nb=
sp;if
(value ‹ minVal) { throw new ArgumentException("value1 must be
between min and max values"); }
&nb=
sp;if
(value ›=3D Value2) { throw new ArgumentException("value1 mus=
t be ‹
Value2"); }
&nb=
sp;OnValueChanged(new
UserControl1ValueChangedEventArgs(val1, value, 1)); // вызов с=
обытия
&nb=
sp;val1
=3D value;
&nb=
sp;SetPos1();
&nb=
sp;}
&nb=
sp;}
&nb=
sp;
Аналогичную строчку добавим=
и для
второго значения:
&nb=
sp;OnValueChanged(new
UserControl1ValueChangedEventArgs(val2, value, 2)); // вызов с=
обытия
&nb=
sp;
И последний штрих - устано=
вим
для класса событием по умолчанию - наше событие:
&nb=
sp;[DefaultEvent("ValueChanged")]
&nb=
sp;public
partial class UserControl1 : UserControl
&nb=
sp;
Вот и все. Можно использов=
ать и
в дизайнере и во время работы программы. Сделаем Form1, добавим=
в
нее наш элемент управления, сделаем пару текстовых окошек и про=
пишем
для нашего элемента управления событие ValueChanged:
&nb=
sp;private
void userControl11_ValueChanged(object sender, UserControl1Va=
lueChangedEventArgs
e) {
&nb=
sp;if
(e.value =3D=3D 1) { // если первый ползунок
&nb=
sp;textBox1.Text
=3D e.newValue.ToString(); // внести новое значение в текстовое =
поле
1
&nb=
sp;}
&nb=
sp;else
if (e.value =3D=3D 2) { // аналогично для ползунка 2
&nb=
sp;textBox2.Text
=3D e.newValue.ToString();
&nb=
sp;}
&nb=
sp;}
&nb=
sp;
Да добавим в конструктор=
формы
такие строчки:
&nb=
sp;textBox1.Text
=3D userControl11.Value1.ToString(); // записать начальное значе=
ние
ползунка 1 в текстовое поле 1
&nb=
sp;textBox2.Text
=3D userControl11.Value2.ToString();// аналогично для ползунка 2=
&nb=
sp;
Вот все и работает. Не иде=
ально,
конечно. Но "дешево, надежно и практично".
Каталог проекта для MS Visu= al Studio 2005 с примером находится в архиве примеров, подкаталог usercontrol2.
Архив примеров можно скача= ть здесь: http:/= /www.robinland.com/csharp-basis/samples.zip.
&nb= sp; Основы GDI+.
Глава 1. Основные понятия и ошибки.
&nb= sp;

&nb= sp;
Приступаю к рассказу как ри= совать в .NET.
Несколько вводных слов.
Если вы хотите что-то изобр= азить на экране - вам надо это что-то нарисовать. Другого способа нет. = Все окна, кнопки и прочие контролы - это все рисуется, просто код ри= сования написан за вас. Но те, кто создают свои элементы управления знают, что рисовать приходится все - начиная от рамки и заканчивая вве= денным текстом.
Есть разные технологии ри= сования. Можно назвать три основные - GDI+, DirectX, системные функции.
Системные функции имеют оче= нь ограниченные возможности, однако работают очень быстро. В случае тех же конт= ролов, лучше пользоваться функциями системы для рисования кусков сти= ля, эти функции работают намного быстрее, чем GDI+, поскольку вместо длительного прорисовывания картинки, они копируют содержимое напрямую в видео память.
DirectX - самая быстрая тех= нология, поскольку рисует прямо в видео памяти, используя графические ч= ипы-ускорители. Однако имеет ряд недостатков - количество и объем подгружаем= ых библиотек и драйверов может превышать объем программы в несколь= ко раз. Не говоря уже о том, что программировать под DirectX много сло= жнее, чем под GDI+.
GDI+ - самая медленная тех= нология. И при этом самая удобная для программиста. В отличии от DirectX - р= исует используя основной процессор, поэтому занимает кучу ресурсов и времени. В отличии от системных функций имеет огромные возможн= ости. Говорят, что в ближайшем будущем (в районе .NET 4.0) GDI+ тоже буд= ет рисовать использую графические чипы...
Основные моменты GDI+=
Все рисование в GDI+ ведет= ся через объект класса Graphics. Это, можно сказать, ядро технологии. Объе= кт содержит функции рисования плоских примитивов, изображений, = текста, поддерживает пространственные преобразования, умеет прово= дить сглаживание в разных режимах и пр. Объект Graphics может быть соз= дан от любого контрола, включая форму, от любого объекта Image, и е= ще несколькими способами, которые вряд ли понадобятся.
Итак, если вам надо что-то г= де-то нарисовать, действуете так:
1. Создаете объект Graphic= s, или получаете уже созданный, от того объекта, на котором вам надо р= исовать.
2. Рисуете через объект Gr= aphics.
3. Удаляете объект Graphic= s.
&nb=
sp;Graphics
gr =3D Graphics.FromHwnd(panel1.Handle);
&nb=
sp;gr.DrawLine(pen1,
point1, point2);
&nb=
sp;gr.FillEllipse(brush1,
0,0,100,100);
&nb=
sp;gr.DrawEllipse(pen2,
0,0,100,100);
&nb=
sp;gr.Dispose();

&nb= sp; Если вашей программе нужно очень много рисовать, да еще из разных функ= ций, вы можете сделать единый объект Graphics, и пользоваться им от раз= ных функций. Впрочем, это как правило плохая методика, гораздо лучш= е использовать родные event.
Использование Graphics в e= vent.
Обычно это выглядит так:
&nb=
sp;private
void panel1_Paint(object sender, System.Windows.Forms.PaintEvent=
Args
e) {
&nb=
sp;e.Graphics.FillEllipse(Brushes.Magenta,0,0,150,150);
&nb=
sp;}

&nb= sp; И лучше всего, весь код рисования закладывать в event. Вам никто не = мешает сделать его сильно параметрическим и пр. Можете в event поставит= ь вызов функции собственно рисования, и передавать объект e.Graphics = как аргумент, с модификатором ref. Это позволит вам использовать фу= нкцию рисования не только для рисования на экране, но и для рисования= на принтере, если у вас будет поддержка печати, для рисования на I= mage, если вы будете сохранять изображение в файл, причем не тратя лиш= них ресурсов на написания трех одинаковых функций, или на создание= новых объектов Graphics и т.д.
Основные приемы
Рисование динамической ин= формации в форме, обычно, производится двумя путями - либо в контроле p= anel, либо в Image контрола pictureBox. Конечно, вам никто не мешает= рисовать прямо в форме, если у вас вся форма отведена под рисование, но там свои заморочки. Мы расcмотрим схему рисования через event Pai= nt в двух контролах.
Общая схема такая:
1. Функция panel1_Paint ил= и pictureBox1_Paint содержит параметрический код рисования.
2. Остальные контролы ме= няют параметры рисования.
Предположим есть две радиок= нопки - одна задает красный цвет, другая синий. Тогда код рисования бу= дет выглядеть примерно так:
Для panel:
&nb=
sp;private
void panel1_Paint(object sender, System.Windows.Forms.PaintEvent=
Args
e) {
&nb=
sp;if
(radioButton1.Checked) {
&nb=
sp;e.Graphics.FillEllipse(Brushes.Red,0,0,150,150);
&nb=
sp;}
&nb=
sp;else
{
&nb=
sp;e.Graphics.FillEllipse(Brushes.Blue,0,0,150,150);
&nb=
sp;}
&nb=
sp;}
Для pictureBox:
&nb=
sp;private
void pictureBox1_Paint(object sender, System.Windows.Forms.Paint=
EventArgs
e) {
&nb=
sp;Graphics
gr =3D Graphics.FromImage(pictureBox1.Image);
&nb=
sp;gr.Clear(Color.White);
&nb=
sp;if
(radioButton1.Checked) {
&nb=
sp;gr.FillEllipse(Brushes.Red,0,0,150,150);
&nb=
sp;}
&nb=
sp;else
{
&nb=
sp;gr.FillEllipse(Brushes.Blue,0,0,150,150);
&nb=
sp;}
&nb=
sp;gr.Dispose();
&nb=
sp;}
В последнем случае - gr.Cle=
ar(Color.White)
- заполняет всю картинку белым цветом, это нужно если надо очист=
ить
то, что было нарисовано до этого.
В чем разница - если вам на= до рисовать только в программе, используйте panel, оно проще и меньше ресурс= ов ест. Если ваша программа работает с изображениями, в том числе с файлами изображений - загрузка/сохранение, рисуйте в picture= Box.Image, так проще сохранять, во-первых, и pictureBox создан для хранения = изображений, так что дешевле записать Image в pictureBox.Image, чем вызывать = в panel1_Paint e.Graphics.DrawImage().
Но не забывайте при загруз= ке формы создавать Image в pictureBox, ибо по умолчанию, pictureBo= x.Image =3D null;
Для радио кнопок:
&nb=
sp;private
void radioButton1_CheckedChanged(object sender, EventArgs e) {
&nb=
sp;panel1.Refresh();
&nb=
sp;//
или
&nb=
sp;pictureBox1.Refresh();
&nb=
sp;}
Основные ошибки
Идеология GDI+ почему-то у м= ногих вызывает кучу проблем... скорее всего, потому что люди не поним= ают принцип работы Windows.
Ошибка 1. Пропадающе= е изображение - люди рисуют в panel, или pictureBox не в event Paint, а по нажа= тию кнопки, или откуда-то еще и удивляются, почему их изображение не= перерисовывается, а пропадает, когда форма перерисовывается (ее свернули/разве= рнули, убрали за экран/вывели обратно и пр.).
Ответ 1. Так и должно= быть - когда вы рисуете по контролу - вы рисуете на экране, когда эк= ран перерисовывается, он перерисовывается с нуля, и если ваш ко= д рисования не включен в цепочку перерисовывания - вашего рисунка и не бу= дет. Чтобы включить ваш код в цепочку - поставьте его в event Paint нужн= ого контрола.
Ошибка 2. При рисова= нии в контроле pictureBox, люди рисуют по контролу, а не по Image.= p>
Ответ 2. Когда рисуе= те - получайте Graphics через Graphics.FromImage(pictureBox.Image),= a не e.Graphics.
Ошибка 3. При использ= овании pictureBox - создается новый Image, в нем проводится рисовани= е, потом он вставляется в pictureBox.
Ответ 3. На хрена соз= давать новый Image, каждый раз когда вы рисуете. Создавать новый надо то= лько когда вы меняете размер pictureBox.
И последнее, несмотря не н= едостатки технологии сбора мусора (Garbage Collector), рекомендуется им пользоваться. Во-первых, если это войдет в привычку сейчас, то ког= да технология заработает (например, в .NET 4.0), вы будете писать = правильно. Во-вторых, технология и сейчас работает с большими объемами в п= амяти, а изображения (и многие другие объекты рисования) относятся им= енно к большим объемам. Так что любой созданный вами объект Graphics дол= жен быть Dispose() после окончания работы с ним, любой созданный Ima= ge должен быть Dispose() после окончания работы с ним. И главное, пос= ле всех Dispose() - не забывайте вызывать GC.Collect().
Знающим английский могу по= рекомендовать сайт Bob Powell (http://www.bobpow= ell.net/), как источник кучи полезной информации для начинающих в GDI+.
&nb= sp;
Как уже говорилось, Graphi= cs - основной объект (и класс) для рисования. Рассмотрим подробно, что= он умеет.
Конструкторы
У класса есть статические = функции для создания объекта Graphics, и обратите внимание - нет public = конструктора. Поэтому создавать объект приходиться одним из следующих способ= ов:
FromHdc - по указате= лю на контекст устройства (Device Context) - используется редко.
FromHwnd - по указате= лю на контрол (или форму).
&nb=
sp;Graphics
gr =3D Graphics.FromHwnd(panel1.Handle);
FromImage - для рисун= ка (Image).
&nb=
sp;Graphics
gr =3D Graphics.FromImage(bitmap);
Поле рисования
Объект Graphics привязан к = определенному полю рисования (Clip), которое имеет привязку к объекту, коорд= инаты и размеры. Очень удобная вещь с точки зрения двух моментов - во-пер= вых, вы можете изменять область, в которой происходит собственно р= исование, не изменяя кода рисования. Например, вам надо кусок существую= щей картинки перекрасить - вы можете закрасить этот кусок, указав то= чно все его координаты, а можете залить всю картинку через Clear, из= менив размеры и координаты Clip так, чтобы они совпали с нужным вам кус= ком. И во-вторых, рисование ведется только в пределах Clip региона, т= .е. на все, что рисуется вне его время и ресурсы не тратятся.
В классе есть набор функци= й для управления координатами и размерами Clip:
свойство Clip - возвр= ащает Region в котором ведется рисование, также позволяет установ= ить новый Region напрямую.
свойство ClipBounds -= возвращает прямоугольник, описывающий регион Clip.
свойство IsClipEmpty= - показывает пуст ли Clip.
свойство IsVisibleClip= Empty - то же, но с проверкой видимой части Clip. Работает только при ри= совании по контролу.
IsVisible - проверяе= т, видим ли данный прямоугольник. Очень удобно, если вы рисуете в контроле что-то, что требует больших и долгих расчетов, прежде чем считать -= проверьте, а показано-то оно будет.
ExcludeClip - исключ= ает из Clip фигуру, переданную как аргумент.
IntersectClip - остав= ляет в Clip только область пересечения Clip и фигуры в аргументе.
ResetClip - устанавл= ивает Clip равным бесконечности.
SetClip - устанавли= вает Clip из аргументов.
TranslateClip - cмеща= ет Clip по плоскости рисования.
Разница между Clip и видимо= й частью Clip: если у вас есть panel контрол, с включенным AutoScroll и сод= ержимое превышает размеры panel, то создавая Graphics по указателю на к= онтрол, вы получите Clip =3D всей области panel, однако видимая часть Cli= p - это та, которая показывается в настоящий момент пользователю.= Но помните, что видимая часть Clip имеет координаты и размер контр= ола - т.е. в случае panel, со скроллом, смещенным вправо до конца, види= мый клип все равно будет от 0;0 до panel.Width;panel.Height.
Рисование и заливка= p>
Среди функций объекта мож= но выделить две большие группы - для рисования и для заливки.
Почти все функции, начина= ющиеся со слова Draw - рисуют заданную фигуру заданной ручкой.
Все функции, начинающиес= я со слова Fill - заливают заданную фигуру заданной кистью.
Кисти и ручки будут рассмот= рены в следующем посте.
Список функций весьма вели= к:
DrawArc - рисует дуг= у
DrawLine - рисует ли= нию
DrawPolygon - рисует = многоугольник и т.д.
Аналогично, функции Fill= Arc, FillPolygon и т.д.
Дополнительное рисование=
Дополнительно к рисованию п= ростых форм есть следующие функции:
DrawString - рисует с= троку, и заливает ее заданной кистью.
DrawIcon и DrawIconUns= tretched - рисует иконку (объект Icon), и рисует иконку без изменения, соо= тветственно.
DrawImage, DrawImageUn= scaled и DrawImageUnscaledAndClipped - рисует картинку (объект= Image), рисует картинку без изменений в указанной точке и рисует карти= нку без изменений с обрезкой по указанному прямоугольнику соответ= ственно.
Clear - заливает все= поле рисования указанным цветом.
CopyFromScreen - копи= рует, попиксельно, изображение на экране в указанном прямоугольнике= в указанный прямоугольник поля рисования.
Текст
Для рисования текста есть= вспомогательные функции:
MeasureString - позво= ляет получить размеры строки, когда она будет нарисована.
MeasureCharacterRanges = - позволяет получить размеры набора символов, когда они будут нарисованы.=
Разница между функциями в = разном подходе к определению допусков на свисающие части букв, разный = допуск на сглаживание и еще чуть-чуть. Подсчет в любом случае не идеальны= й, так как функции почему-то не используют установленный параметр= типа сглаживания шрифта в системе и в объекте Graphics.
свойство TextRendering= Hint - определяет режим сглаживания текста. Варианты - без сглажи= вания (SingleBitPerPixel) и со сглаживанием (AntiAlias), каждый мож= ет быть с подстройкой свисающих частей (GridFit) или без. Плюс есть Cl= earTypeGridFit - рисует через движок ClearType. И есть вариант SystemDefault = - использовать настройки системы.
свойство TextContrast= u> - определяет контрастность текста, если в системе включено сгл= аживание текста или ClearType.
Преобразования
При рисовании довольно ча= сто необходимо провести над изображением, или будущим изображени= ем, некие трансформации - изменить масштаб, повернуть, может быть п= одвинуть, не меняя основного кода рисования. Для этого в GDI+ есть класс Mat= rix, описывающий векторные (координатные) трансформации для каждой= рисуемой точки. Для тех кто не знает, или уже благополучно забыл что такое м= атрицы и как с ними работают, есть функции, которые выполняют операции над матрицей за вас.
свойство Transform - = возвращает и позволяет задать матрицу преобразований.
MultiplyTransform - пе= ремножает текущую матрицу и матрицу в аргументе в заданном порядке.
TranslateTransform - пе= редвигает рисование по плоскости.
RotateTransform - пово= рачивает рисование относительно начала координат.
ResetTransform - обну= ляет все трансформации.
ScaleTransform - изме= няет масштаб, по каждой оси отдельно.
TransformPoints - прео= бразует координаты заданного массива точек из одной системы координ= ат в другую.
Качество
Еще есть набор свойств, опре= деляющих качество рисования.
CompositingMode - опре= деляет как рисунки (Image) будут рисоваться. Варианты SourceCopy - цв= ет рисунка перекрывает подложку, SourceOver - цвет рисунка смеши= вается с цветом подложки, в пропорции, определяемой альфа компонентой= цвета.
CompositingQuality - оп= ределяет качество преобразования рисунков (Image), когда они рисуется один по другому. Из вариантов реально пользоваться стоит только= HighQuality - качественно, но медленно и HighSpeed - быстро, но не очень ка= чественно.
DpiX и DpiY - по= зволяют узнать dpi по обеим осям.
InterpolationMode - оп= ределяет метод интерполяции, по сути - сглаживание, при рисовании. Вари= анты для использования в порядке возрастания качества и времени: N= earestNeighbor, Bilinear, HighQualityBilinear, Bicubic, HighQualityBicu= bic.
PixelOffsetMode - опре= деляет качество offset пикселей, чтоб я понимал что это такое :). Наско= лько я понял, это тоже параметр сглаживания, но на уровне смешения цве= тов пикселей. Для использования обычные два варианта - HighQuality= и HighSpeed, и вариант None - никакой обработки.
SmoothingMode - опред= еляет режим сглаживания линий. Те же варианты - HighQuality, HighSp= eed и None.
Прочее
Есть еще набор функций, ко= торые ни к какой группе не относяться, но иногда они нужны:
GetNearestColor - возв= ращает ближайщий цвет к аргументу в цветовом пространстве объекта Gr= aphics.
Save - позволяет сох= ранить состояние объекта Graphics: трансформации, Clip, качество.
Restore - позволяет = воостановить состоянии объект из ранее сохраненного.
И последнее - есть ещ= е функции для управления метафайлами, контейнерами и еще несколько вспо= могательных. Их не будет в дальнейших примерах, я ими не пользовался никогда, = и думаю, что если они кому нужны - эти люди способны сами разобраться.
&nb= sp;
Прежде чем начать рисовать,= надо бы разобраться с цветами и их использованием. Мы будем рассмат= ривать только вопросы определения и выбора цветов в компьютерных цве= товых пространствах, и не будем касаться биолого-художественных ас= пектов. Хотя есть несколько моментов, которые необходимо знать и помнит= ь:
1. восприятие цветов у каж= дого человека индивидуально.
2. Каждый монитор/принтер/= и т.д. показывают один и тот же цвет (с точки зрения цифр) по-разному. Бо= лее того, большинство мониторов показывают один и тот же цвет по-ра= зному в разных частях экрана.
Цветовые пространства<= /b>
Я думаю все знают известн= ый постулат - любой цвет можно получить смешением трех, так называемых, осно= вных. Поправка первая: "любой цвет" - это любой из палитры, в= оспринимаемой человеческим глазом. Устоявшееся мнение гласит, что челове= ческий глаз различает всего около 16 миллионов цветов. Существуют мно= гие несогласные с этим, но большинство работает именно с 2^24 цвета= ми, и мы будем рассматривать именно такие пространства. Однако, на= до знать, что существуют цветовые пространства построенные на 4= цветах (например, CMYK) и на 6 цветах. Впрочем, 4-х цветные нашли примене= ние только в полиграфии, а с 6-ти цветными я сталкивался только в те= ории, и не знаю где они применяются.
Итак, трех-осевые цветовые = пространства, самое известное из них - RGB - Red (Красный) Green (Зеленый) Blue= (Синий). Используется в ЭЛТ мониторах, во многих принтерах и во многих фо= рматах файлов. Смешивая эти три основные цвета в разных пропорциях можн= о получить "любой" цвет. Отведя по 8 бит на цвет мы получаем 2^24 =3D 16777216 цветов, что, как принято, описывает все цвета, восприни= маемые человеческим глазом. Все довольны.
Второе по популярности про= странство - HSB (HSL/HSV) - Hue (Цветность) Saturation (Насыщенность) Brigh= tness (Яркость) / Lightness (Освещенность) / Value (Значение). Использу= ется практически во всех графических редакторах. Кстати, в стандарт= ном диалоге выбора цвета Windows используется именно это простран= ство:

&nb= sp; В данном случае все разрисовано в форме квадрата, что неверно. Hue= - это параметр, обозначающий угол на цветовом круге. Справка: цве= товой круг Гете, самый известный, при всей своей правильности не был пр= инят в технологическом мире. Однако, именно он послужил основой для= создания пространства HSB. Более правильная форма выбора цвета в про= странстве HSB такая:

&nb= sp; C пространством HSB связана наприятная проблема - параметр Hue = должен принимать значения от 0 до 360, что никак не укладывается в норм= альную двоичную систему записи данных... Да и для Saturation и Bright= ness единого мнения нет - кто-то считает что значения должны быть от 0 = до 100, кто-то от 0 до 1, кто-то от 0 до 240... Поэтому, несмотря на то, ч= то работать многие предпочитают в нем, сохранение данных ведется в RGB, благо оба пространства взаимоконвертируемые, хотя тут есть несколько ловушек, об этом ниже.
Каждая ось любого цветово= го пространства также называется каналом - красный канал, или ка= нал красного и т.п.
Существует еще около 10-15 цв= етовых пространств, но среди Windows программистов они очень мало распро= странены и мы их рассматривать не будем.
Битность цвета
Битность цвета - параметр, = определяющий сколько бит памяти приходится на цвет каждого пикселя. Если вдруг кто не знает: пиксель - минимальная единица площади экрана/раст= рового рисунка, т.е. точка.
Я уже упоминал, что на кажд= ый из трех основных цветов выделили по 8 бит и всем стало хорошо. Одна= ко, это случилось не так давно, а до этого выделять по 3 байта на один п= иксель было непозволительной роскошью. История развития примерно так= ая:
1. Монохромы - один цвет. М= ожет кто помнит, были такие мониторы, которые показывали все исклю= чительно ядовито-зеленым цветом.
2. 4-х цветные. Была такая = вещь, однако долго не прожила, поскольку ее быстро сменили.
3. 16-ти цветные. Это уже на= чало нормального цвета в компьютере. На каждый пиксель выделялось 4 = бита, они описывали один из 16 известных компу цветов. Но, как выяснил= ось позже, это тоже было временно - мониторы стали показывать все 16 миллионов, а компы не могли столько выдать одновременно... И тог= да придумали выход.
4. 256 цветов. На каждый пик= сель выделялся байт памяти, который мог описать цвет. Но тогда же появ= илась идея палитр - быйт памяти определял номер цвета в палитре, а сам= а палитра в 256 цветов выбиралась из полного набора.
5. Дальше все просто - с рос= том компьютерной мощности и объемов памяти появился 16 битный цвет - 65535 цветов, никаких палитр, и выглядит все вполне пристойно. По= том 24 бита - 16 миллионов цветов, с которыми сейчас все и работают.=
6. 32 и 48 бит: 48 бит я пока= в деле не видел, однако он есть, под него есть карты и т.д. 32 бита - имеет= два применения, во-первых для 4-х цветных пространств, а во-вторых для = поддержки прозрачности, об этом чуть ниже.
Прозрачность
Сначала прозрачность появи= лась в битовой форме - т.е. пиксель или полностью прозрачный или цветной. Прозрачность в gif сделана именно так. Потом уже появилась градац= ионная прозрачность. Для нее сделали 256 градаций, т.е. выделили еще бай= т на хранение прозрачности пикселя. Байт, хранящий прозрачность полу= чил название альфа. Добавление альфа канала не создает дополнител= ьной оси пространства. Это не компонент цвета в прямом смысле, - это п= араметр, показывающий, в какой пропорции надо смешивать этот цвет, с леж= ащим "ниже". Среди распространенных форматов файлов только= png поддерживает альфа-канал.
К слову сказать, Windows до= сих пор умеет нормально работать только с битовой прозрачностью, го= ворят Vista научилась работать с градационной, но это мы посмотрим по= сле релиза.
Форматы цветов
Итак, совмещая вышеописан= ное в разных комбинациях мы получаем форматы цветов/цветности. Напр= имер, один из самых сейчас распространенных форматов - 32bppARGB: 32 би= та на пиксель, цветовое пространство RGB с поддержкой альфа-кана= ла. Форматов существует множество, но все они понятны из названия,= я перечислю и опишу только те, которые используются в .NET 2.0 и с= одержаться в перечислении System.Drawing.Imaging.PixelFormat:
Alpha - каждый пиксел= ь содержит только альфа-канал. 8бит.
Canonical - 32 бита, A= RGB. Значения в RGB каналах не изменены.
DontCare - не указыва= ть формат.
Extended - не использ= уется.
Format16bppArgb1555 - 1= 6 бит на пиксель, 1 бит на прозрачность и по 5 бит на цветовые каналы. Та= ким образом получаем 32768 цветов и битовую прозрачность.
Format16bppRgb555 - 16 = бит. по 5 бит на цветовой канал, 1 бит не используется. Те же 32768 цвет= ов, но без прозрачности.
Format16bppRgb565 - 16 = бит, по 5 бит на красный и синий и 6 бит на зеленый. Получаем 65536 цвет= ов.
Format24bppRgb - 24 би= та, по 8 на каждый цвет. Самый распространенный сейчас формат без прозр= ачности.
Format32bppArgb - 32 би= та, по 8 на каждый цвет и 8 на альфа-канал. Самый распространенный се= йчас формат с прозрачностью.
Format32bppPArgb - То ж= е, что и предыдущий, но значения цветовых каналов преобразованы в с= оответствии с альфа значением.
Format32bppRgb - 32 би= та, по 8 на канал и 8 не используются.
Format48bppRgb - 48 бит= , по 16 на каждый канал.
Format64bppArgb - 64 би= та, по 16 на цветовой канал, и 16 на прозрачность.
Format64bppPArgb - то ж= е, что предыдущий, но значения в цветовых канал преобразованы в соо= тветствии с альфа значением.
Format1bppIndexed - ин= дексированные цвета, т.е. завязанные на палитру. Палитра из двух цветов. 1 бит на пиксель.
Format4bppIndexed - 4 б= ита, палитра из 16 цветов.
Format8bppIndexed - 8 б= ит, палитра из 256 цветов.
Format16bppGrayScale - = 65536 градаций серого.
Лично я пользовался только 24bppRGB, 32bppARGB, 8bppIndexed и 16bppGrayScale. При создании мас= ок в графических редакторах часто используют однобитовые форматы, т.е. Alpha вполне может понадобиться. Остальные, на мой взгляд, о= ставлены для совместимости со старыми форматами и с будущими форматам= и.
Использование цветов в .NE= T
Для работы с цветами есть = класс Color. Он содержит набор статических свойств, которые определя= ют распространенные цвета. Например, если вам нужен красный цвет, = проще всего получить его так:
&nb=
sp;Color
красный =3D Color.Red;
Для нестандартных цветов = есть функция FromArgb():
&nb=
sp;Color
странныйЦвет =3D Color.FromArgb(255, 120, 12, 211);
Есть еще две статические фу= нкции для создания цвета:
FromKnownColor - созда= ет цвет из списка известных цветов, т.е. из перечисления KnownColo= r, в которое входят все стандартные цвета и все системные цвета.
FromName - создает цв= ет из строки с именем цвета.
Помимо перечисления KnowC= olor есть еще перечисление SystemColors, которое содержит только сис= темные цвета - цвета рамок, кнопок, области окна и пр.
У класса Color есть еще не с= татические члены:
свойства A, R, G, B - возвра= щают соответствующую компоненту цвета.
свойства IsKnownColor, Is= NamedColor, IsSystemColor - проверяют, является ли цвет "известным&q= uot;, названным и системным соответственно.
свойство Name - возвращае= т имя цвета
ToKnownColor - возвращает = член перечисления KnownColor.
GetHue, GetSaturation, Ge= tBrightness - возвращают значения цвета для осей Hue, Saturation и Bright= ness пространства HSB.
свойство IsEmpty - проверяе= т был ли цвет инициализирован.
ToArgb - возвращает Int32.=
Использование пространс= тва HSB
Частая ситуация для графич= еских программ - надо подсветлить изображение, или понизить контраст= ность, или инвертировать цветность. Такие вопросы легко решаются через HSB пространство, однако очень