|
|
|||||||||||||||||||||||||||||
|
Библиотека времени выполнения .NET и FCLИсточник: rsdn Автор: М. Кэнту
Автор: М. Кэнту
|
// Определение для Win32 function SetFocus; external user32 name 'SetFocus'; // Определение для .NET [DllImport(user32, CharSet = CharSet.Ansi, SetLastError = True, EntryPoint = 'SetFocus')] function SetFocus; external; |
Атрибут DLLImport используется для реализации вызовом неуправляемых (unmanaged) функций, реализованных в стандартных DLL-библиотеках Win32. Атрибут предоставляет информацию, необходимую для правильного оформления вызова. Хотя функции API отображаются на один физический уровень (базовые Windows DLL), объявление этих функций в .NET значительно отличается от их аналогов для Win32
На вызовы неуправляемых функций Win32 распространяются те же правила компиляции и условия времени выполнения. Например, все параметры на стороне CLR должны быть безопасными по отношению к типам. По этой причине все строковые параметры PChar теперь определяются как относящиеся к строковому типу (с расширенной кодировкой!). В момент вызова CLR выполняет необходимые преобразования формата параметров.
Как правило, на уровне исходного кода вносятся относительно небольшие изменения - такие, как удаление преобразований PChar из вызовов и прямая передача строковых параметров. Однако задача создания вызовов функций Win32, которые бы компилировались с единым исходным кодом на платформах Win32 и .NET, оказывается весьма нетривиальной.
Рассмотрим два фрагмента из примеров, упоминавшихся в предыдущих главах. В первом фрагменте директива IFDEF распространяется на весь вызов функции API, а во втором - только на конкретный параметр:
// Из примера MiniPack, глава 5 {$IFDEF WIN32} hFile := CreateFile (PChar (ParamStr (0)), 0, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0); {$ENDIF} {$IFDEF CLR} hFile := CreateFile (ParamStr (0), 0, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0); {$ENDIF} // Из примера BmpViewer, глава 8 DrawText (Control.Canvas.Handle, {$IFDEF WIN32}PChar (FileName),{$ENDIF} {$IFDEF CLR}FileName,{$ENDIF} Length (FileName), OutRect, dt_Left or dt_SingleLine or dt_VCenter); |
При объявлении функций API для Win32 и .NET также следует помнить, что один из параметров функций Win32 API может относиться к разным типам в зависимости от значений других параметров. Классическим примером служит функция API CreateWindow, в которой параметр может использоваться как для передачи идентификатора главного меню, так и идентификатора дочернего окна MDI. У других функций API (например, FindResource) в одном параметре также может передаваться как строка, так и целое число. С учетом возможных комбинаций параметров объявление функции приобретает следующий вид:
function FindResource(hModule: HMODULE; lpName, lpType: string): HRSRC; overload; function FindResource(hModule: HMODULE; lpName: string; lpType: Integer): HRSRC; overload; function FindResource(hModule: HMODULE; lpName, lpType: Integer): HRSRC; overload; function FindResource(hModule: HMODULE; lpName: Integer; lpType: string): HRSRC; overload; |
Другой распространенный случай - передача указателей на структуры данных, которые могут быть равны nil. С заменой указателей записями (структурные типы не могут иметь неопределенные значения) понадобится перегруженная версия для передачи пустого параметра:
function InvalidateRect(hWnd: HWND; const lpRect: TRect; bErase: BOOL): BOOL; overload; function InvalidateRect(hWnd: HWND; lpRect: IntPtr; bErase: BOOL): BOOL; overload; |
Многочисленные изменения произошли в модуле SysUtils, который в Delphi 2005 также был доработан в версии для Win32. В него были включены такие возможности .NET, как преобразования формата даты между строковым форматом Delphi и строковым форматом CLR (ConvertDelphiDateTimeFormat и ConvertClrDateTimeFormat) и заменители функций Win32 API для выполнения операций инкремента/декремента, безопасных по отношению к программным потокам (InterlockedIncrement и InterlockedDecrement, а также InterlockedExchange).
В Delphi для .NET функция SameStr, функцияSameStr заменяет CompareMem, файловые идентификаторы базируются на модуле System.IO.FileStream, а функции FileRead и FileWrite имеют перегруженные версии, безопасные по отношению к типам.
Наконец, из модуля были исключены все операции прямого управления памятью, в том числе AllocMem, строковые функции на базе PChar и функция AppendStr (которая считалась устаревшей еще в Delphi 7).
Большинство классов RTL сохранило прежний интерфейс, хотя в некоторых случаях реализация существенно изменилась. В частности, среди модулей пространства имен Borland.Vcl практически не изменились модули Contnrs, HelpIntfs, IniFiles и Registry.
Поддержка COM была сведена к минимуму, поэтому в модулях ComObj и ActiveX почти не осталось содержания, а модули ComServ, VCLCom и StdVCL были исключены (наряду с CorbaVcl и модулем ZLib, обеспечивающим поддержку сжатия потоков).
Многие изменения в реализации связаны с заменой "родной" реализации вспомогательными классами .NET. Например, класс TMask (модуль Borland.Vcl.Mask) в Delphi для .NET представляет собой "обертку" для класса System.Text.RegularExpressions.RegEx (см. далее раздел "Регулярные выражения").
Основные классы Delphi RTL определяются в модуле Classes, модульClasses (или Borland.Vcl. Classes). Во многих важных случаях эти классы сохранили совместимость на уровне интерфейсов, но были переписаны заново для использования соответствующих классов библиотеки .NET. В табл. 9.1 перечислены важные соответствия между классами.
Другие распространенные классы, определяемые в этом модуле (в том числе TCollection, классTCollection, TStrings и TStringList), не изменились. Стоит заметить, что у классов потоков данных имеются аналоги в FCL, и некоторые потоковые классы Delphi отображаются на них. В частности, базовый класс потока TCLRStreamWrapper, классTCLRStreamWrapper отображается на потоковые средства CLR. В Delphi для .NET такие классы, как TFileStream, классTFileStream, наследуют от TCLRStreamWrapper. Также существует класс TStreamToCLRStream, классTStreamToCLRStream для выполнения обратной функции, то есть инкапсуляции потока Delphi в потоке CLR. Потоковые классы содержат много перегруженных методов чтения и записи для обеспечения безопасности типов.
Другая группа классов использует классы-помощники для интеграции с FCL без потери совместимости с существующим кодом. Прежде всего, это относится к классам TPersistent и TComponent:
type TPersistent = System.MarshalByRefObject; TPersistentHelper = class helper (TObjectHelper) for TPersistent TComponent = System.ComponentModel.Component; TComponentHelper = class helper (TPersistentHelper) for Tcomponent |
Класс-"обертка" Delphi | Класс .NET FCL |
---|---|
TList | System.Collections.ArrayList |
TThread | System.Threading.Thread |
TCLRStreamWrapper (THandleStream) | System.IO.Stream |
Что касается класса TComponent, классу-помощнику необходима дополнительная память для хранения владельца, списка компонентов и имени компонента. Поскольку класс-помощник не может определять новые поля, Delphi для .NET использует объект TComponentSite, управляемый через интерфейс ISite класса FCL Component (см. далее раздел "Класс Component").
В классе TComponent в Delphi для .NET появился ряд новых возможностей: методы класса ChangeComponentName и SetComponentParent, глобальные функции SendNotification и DelegatesEqual, а также новое определение свойства Tag (теперь относящееся к типу Variant).
ПРИМЕЧАНИЕ В VCL для Win32 свойство Tag относится к типу Integer; в VCL для .NET оно относится к типу Variant, а в WinForms - к типу System.Object. Такие разночтения могут породить путаницу и проблемы с совместимостью при переносе кода Delphi между библиотеками. Не забывайте, что тип Variant в Delphi для .NET определяется как System.Object, так что определения VCL для .NET и WinForms на самом деле идентичны. |
BCLБиблиотека .NET FCL (Framework Class Library) представляет собой большой набор классов, относящихся к разным областям, от низкоуровневых операций до пользовательского интерфейса и веб-программирования. Компания Microsoft обычно обозначает некоторые основные классы FCL термином BCL (Base Class Library). В табл. 9.2 перечислены пространства имен BCL.пространства имен;BCL
Пространство имен | Содержание |
---|---|
System | Базовые типы, поддержка среды, математические функции и многое другое |
System.CodeDom | Поддержка создания кода, компиляции и запуска |
System.Collections | Контейнерные классы (списки, хеш-таблицы) |
System.Diagnostics | Регистрация событий, счетчики производительности и т. д. |
System.Globalization | Поддержка глобализации приложений .NET |
System.IO | Поддержка потоков, основанных на файловых системах и последовательных портах |
System.Resources | Упрощение локализации приложений |
System.Text | Поддержка кодировок и класс для эффективного построения строк |
System.Text.RegularExpressions | Лексический разбор и поддержка регулярных выражений |
Именно эта часть FCL будет рассматриваться в данном разделе. Я ни при каких условиях не смог бы описать (или хотя бы просто перечислить) все классы BCL в книге, так как на эту тему можно легко написать целую книгу. Только пространство имен System содержит более 100 классов. Подробный справочник можно найти в .NET Framework SDK; его также можно открыть непосредственно из Delphi 2005 IDE. Я постараюсь описать лишь несколько важных классов, по возможности сравнивая их с аналогами из Delphi RTL.
Класс Object является общим предком для всех классов FCL (и Delphi для .NET). Он играет ту же роль, что и класс TObject в Delphi для Win32. Я уже упоминал, что TObject в Delphi для .NET является псевдонимом для System.Object, но не демонстрировал возможности этого класса.
Среди ключевых методов System.Object только GetType имеет аналог в объекте Delphi TObject, а остальные методы относятся к сравнению и представлению объектов:
Для демонстрации некоторых методов (и особенно сравнения объектов) я создал пример FclSystemObject. Одна из кнопок примера вызывает разные методы класса System.Object для самой кнопки; результат показан на рис. 9.1.
Рис. 9.1. Кнопка Base в приложении FclSystemObject вызывает основные методы System.Object
Вторая кнопка демонстрирует различия между методами Equals и ReferenceEquals. В программе определяется пользовательский класс, переопределяющий методы ToString и Equals:
type AnyObject = class private Value: Integer; name: string; public constructor Create (aName: string; aValue: Integer); function Equals(obj: TObject): Boolean; override; function ToString: string; override; end; function AnyObject.Equals(obj: TObject): Boolean; begin Result := (obj.GetType = Self.GetType) and ((obj as AnyObject).Value = Value); end; function AnyObject.ToString: string; begin Result := Name; end; |
Как видно из листинга, класс изменяет выводимую информацию (свое имя) и реализует собственные правила сравнения (в которых задействовано только одно из двух полей). Строковое представление используется при заполнении двух полей со списками при запуске программы:
procedure TWinForm.TWinForm_Load( sender: System.Object; e: System.EventArgs); begin ao1 := AnyObject.Create ('ao1', 10); ao2 := AnyObject.Create ('ao2 or ao3', 20); ao3 := ao2; ao4 := AnyObject.Create ('ao4', 20); ComboBox1.Items.Add(ao1); ComboBox1.Items.Add(ao2); ComboBox1.Items.Add(ao3); ComboBox1.Items.Add(ao4); // То же для ComboBox2 |
Другой переопределенный метод Equals вызывается при нажатии кнопки Reference Equals после выделения двух объектов в полях со списками:
TextBox1.AppendText ('Equals: ' + ComboBox1.SelectedItem.Equals ( ComboBox2.SelectedItem).ToString + sLineBreak); TextBox1.AppendText ('ReferenceEquals: ' + ReferenceEquals (ComboBox1.SelectedItem, ComboBox2.SelectedItem).ToString + sLineBreak); |
Обратите внимание: Equals является методом объекта и получает сравниваемый объект в параметре, тогда как Reference Equals является статическим методом класса и получает в параметрах оба сравниваемых объекта.
Хотя многие классы определяют или переопределяют ToString, во многих классах и базовых типах присутствует обратный метод (статический метод класса) Parse для преобразования строки в значение. Например, если имеется Double и строка, то преобразование между ними может выполняться кодом следующего вида:
var DoubleVal: Double; DoubleStr: string; begin DoubleVal := 345.23; DoubleStr := DoubleVal.ToString; DoubleVal := System.Double.Parse(DoubleStr); |
System.Text.StringBuilder, классПри описании строк в .NET (см. главу 4) я упоминал, что при конкатенации строк рекомендуется использовать класс StringBuilder, потому что строки .NET неизменны, а добавление данных в существующую строку означает создание копии всей строки. Пример StringConcatSpeed из главы 4 наглядно продемонстрировал проблему и некоторые возможные решения.
Однако этим возможности класса System.Text.StringBuilder не ограничиваются. Этот класс позволяет создавать строки из разных типов данных (благодаря многократно перегруженным методам Append и AppendFormat). Наряду с присоединением символов класс позволяет организовать эффективное удаление (метод Remove), замену (метод Replace) и вставку (метод Insert). После того как построение строки будет завершено, строковые данные объекта выделяются стандартным методом ToString.
Итак, класс StringBuilder используется по соображениям скорости, потому что модификация строк требует создания новых объектов в памяти, а при использовании класса StringBuilder программа продолжает использовать один объект. В этом вам поможет убедиться приложение StringBuilderDemo, которое удаляет символ из строки и вставляет другой символ в другую позицию той же строки:
// str: string nPos := I mod str.Length; str := str.Remove(nPos, 1); str := str.Insert(nPos, str [(I*2) mod str.Length + 1]); // strB: StringBuilder nPos := I mod strB.Length; strB.Remove(nPos, 1); strB.Insert(nPos, strB [(I*2) mod strB.Length]); |
В программе StringBuilderDemo (рис. 9.2), являющейся приложением WinForms, определяются две строки: короткая и длинная. Пользователь выбирает строку при помощи переключателя и кнопок, выполняет приведенные фрагменты миллион раз и получает информацию о времени выполнения. Для короткой строки (около 20 символов) различия минимальны, но с увеличением объема строки (около 200 символов) версия StringBuilder начинает работать вдвое быстрее.
Рис. 9.2. Программа StringBuilderDemo демонстрирует различия в скорости при работе с обычными строками и объектами StringBuilder
Контейнерные классы
В BCL включен удобный набор контейнерных классов - несомненно более полный, чем в "родной" Delphi RTL. Классы выполняют широкий спектр функций, от динамически расширяемых массивов объектов (ArrayList, классArrayList) до низкоуровневых поразрядных операций с целыми числами (BitArray, классBitArray), от контейнеров с быстрым доступом (таких, как HashTable, классHashTable) до специализированных контейнеров типа стеков или очередей.
Я также намерен уделить основное внимание паре классов, которые будут сравниваться с соответствующими "родными" решениями Delphi. Класс ArrayList отчасти напоминает TList (более того, реализация TList в Delphi для .NET базируется на ArrayList), однако между ними существуют и серьезные различия.
На мой взгляд, самое принципиальное различие - невозможность сортировки элементов списка или эффективного выполнения метода Find. В таких случаях приходится использовать отдельный класс SortedList, классSortedList. Это относится и к контейнерам для строк (скажем, TStringList).
Для сравнения эффективности двух реализаций было написано приложение VCL StringListSpeedNet. Оно демонстрирует различия в синтаксисе и скорости при поиске строки в строковом массиве, хранящемся в одном из двух контейнеров:
SourceList: TStringList; SourceArray: ArrayList; |
Две кнопки в верхней части формы (форма для версии .NET показана на рис. 9.3) заполняют списки случайными строками. Код практически идентичен - как, впрочем, и эффективность:
// TStringList for i := 1 to nPopItems do sourceList.Add (NewRandomString); // ArrayList for i := 1 to nPopItems do sourceArray.Add (NewRandomString); |
Рис. 9.3. Приложение StringListSpeedNet с результатами хронометража
В приложении используются две операции поиска: простой вызов Find для несортированного списка и копирование строк в отсортированный список с последующим поиском среди отсортированных элементов. Во втором случае копирование выполняется следующим образом:
// TStringList: Копирование и сортировка sSorted := TStringList.Create; sSorted.AddStrings (sourceList); sSorted.Sorted := True; RepeatFind (sSorted); // ArrayList: Копирование и сортировка sSorted := ArrayList.Create; sSorted.AddRange(SourceArray); sSorted.Sort; // True для двоичного поиска RepeatFindNet (sSorted, True); |
И снова код почти не изменился. Впрочем, есть одна немаловажная подробность: для версии ArrayList используется другой класс. Более того, для поиска в строке вызывается другой метод с дополнительным параметром. Вызываемые методы выполняют операцию поиска тысячу раз; далее приводится их основной код:
// TStringList for i := 1 to 1000 do if sList.IndexOf(sList [ random (sList.Count)]) >= 0 then Inc (Result); // ArrayList for i := 1 to 1000 do begin if not fSorted then begin if sList.IndexOf(sList [ random (sList.Count)]) >= 0 then Inc (Result); end else begin if sList.BinarySearch (sList [ random (sList.Count)]) >= 0 then Inc (Result); end; end; |
Необходимость вызывать другой метод для производного класса мне не очень-то по душе, но при попытке вызова IndexOf для SortedList снова происходит возврат к медленному последовательному поиску. С другой стороны, "родное" решение .NET работает гораздо быстрее, чем код Delphi RTL. Различия весьма значительные; версия с классом TStringList работает почти втрое медленнее, как для простого поиска, так и для поиска в отсортированном списке. Дело в том, что специально для .NET Framework была написана оптимизированная версия кода. Если написать аналогичную программу для Win32, эффективность кода Delphi RTL будет примерно соответствовать эффективности реализации ArrayList для .NET.
ПРИМЕЧАНИЕ Даже это крайне ограниченное сравнение показывает, что низкая эффективность кода .NET часто является обычным мифом. Программы, интенсивно работающие с процессором и памятью, в .NET работают быстро. Впрочем, вызовы функций Windows API и других функций Win32 из приложений .NET происходят медленнее по сравнению с версиями для платформы Win32. |
Другой полезный контейнер .NET - HashTable - ускоряет поиск элементов в списках (в том числе и строковых). Этот класс относится к категории ассоциативных массивов , или словарей ; каждый объект в таком контейнере представлен ключом (используемый для идентификации объекта) и значением (фактическими данными объекта). При поиске объекта по ключу (который может быть объектом произвольного типа) класс HashTable определяет хеш-код объекта - как было показано ранее, эта возможность встроена в класс System.Object.
Приведу два фрагмента кода из приложения StringListSpeedNet:
var sHashed: HashTable; I, nSkipped: Integer; begin sHashed := HashTable.Create; // Копирование (игнорировать сортировку, // использовать хеширование, пропускать дубликаты!) for i := 0 to sourceArray.Count - 1 do if not sHashed.ContainsKey(sourceArray[i]) then sHashed.Add (sourceArray[i], nil) else Inc (nSkipped); RepeatFindHash (sHashed); // Из RepeatFindHash var i: Integer; akey: string; begin for i := 1 to 1000 do begin akey := sourceArray [random (sourceArray.Count)].ToString; if sList.ContainsKey (akey) then Inc (Result); end; |
Для списка, состоящего из 100 000 строк, поиск в HashTable занимает вдвое меньше времени, чем поиск в отсортированном списке (и в 10 раз меньше, чем при простом поиске). Правда, не стоит забывать, что в хеш-таблице не могут храниться дубликаты строк.
Подмножество BCL библиотеки FCL содержит множество классов, и отобрать среди них несколько интересных нелегко. И все же нельзя не упомянуть поддержку регулярных выражений, обеспечиваемую классом Regex, классRegex (определяемым в пространстве именSystem.Text.RegularExpressions, класс System.Text.RegularExpressions). регулярные выраженияпоиск;регулярные выражениявыражения, регулярныеРегулярные выражения представляют собой чрезвычайно мощное, хотя и непростое средство поиска информации в строках с применением сложных шаблонов. Класс BCL Regex применяется для разнообразных целей, среди которых проверка ввода, разбиение строк, поиск и замена подстрок и т. д.
Пример AssortedBCL демонстрирует три разных случая. Первый - проверка входного текста на соответствие формату номера кредитной карты (четыре группы из четырех цифр, разделенные дефисами):
var regex1: Regex; begin regex1 := RegEx.Create ('^\d{4}-\d{4}-\d{4}-\d{4}$'); if regex1.IsMatch (TextInput.Text) then TextLog.AppendText(TextInput.Text + ': regex match' + sLineBreak); |
Во втором случае та же строка делится на четыре части методом Split, возвращающим массив строк:
var regex1: Regex; lines: array of string; str: string; begin regex1 := RegEx.Create ('-'); lines := regex1.Split(TextInput.Text); for str in lines do TextLog.AppendText(str + sLineBreak); |
Обратите внимание на применение цикла for..in. Наконец, третья кнопка ищет заданный символ в строке. Сначала ищется одно совпадение, а затем - все совпадения. Учтите, что в первом случае метод Match всегда возвращает объект класса Match с флагом, обозначающим исход операции (успех или неудача). Во втором случае при отсутствии совпадений метод Matches возвращает пустую коллекцию. Соответствующий код выглядит так:
var regex1: Regex; match1: Match; multiMatch: MatchCollection; begin regex1 := RegEx.Create ('3'); match1 := regex1.Match(TextInput.Text); if match1.Success then TextLog.AppendText('Found at ' + match1.Index.ToString + sLineBreak) else TextLog.AppendText('Not Found' + sLineBreak); multiMatch := regex1.Matches(TextInput.Text); for match1 in multiMatch do TextLog.AppendText('Multi-Found at ' + match1.Index.ToString + sLineBreak) |
Регулярные выражения не имеют прямых аналогов в Delphi RTL, хотя их упрощенная версия предоставляется классом TMask, классTMask из модуля Masks.
Операции с файлами с применением потоковых классов составляют еще одну область, в которой уместно ограничиться "родной" поддержкой Delphi. Впрочем, в .NET также существует аналогичная концепция и ее можно использовать при написании приложений, которые не должны компилироваться для Win32. В частности, это относится к приложениям WinForms.
При работе с потоками .NET можно использовать классы, специализированные для чтения и записи различных структур данных (строки, числа и т. д.) или же предназначенные для чтения и записи байтовых массивов, предоставляемых потоковыми классами. При работе с файлами в большинстве случаев достаточно использовать классы StreamWriter, классStreamReader, классStreamReader/StreamWriter для текстовых файлов и классы BinaryWriter, классBinaryReader, классBinaryReader/BinaryWriter для двоичных файлов.
ПРИМЕЧАНИЕ Аналогами таких классов в Delphi RTL можно считать низкоуровневые классы TReader и TWriter, в основном используемые для чтения и записи свойств в DFM-подобных потоках. |
Например, приложение AssortedBCL позволяет сохранить содержимое журнала в локальном файле:
var sw1: StreamWriter; begin sw1 := StreamWriter.Create ('temp.txt'); try sw1.WriteLine(TextLog.Text); finally sw1.Close; end; |
Для повторной загрузки файла также можно было бы воспользоваться объектом StreamReader, но на этот раз я написал более подробный код с созданием файлового потока и его присваиванием объекту класса StreamReader:
var sr1: StreamReader; fs: FileStream; begin fs := FileStream.Create ('temp.txt', FileMode.Open, FileAccess.Read); try sr1 := StreamReader.Create (fs); TextLog.Text := sr1.ReadToEnd; finally fs.Close; end; end; |
Единственное реальное отличие состоит в том, что в данном случае можно явно задать разрешения доступа к файлу и настроить их для конкретной ситуации.
Component, классЕще один класс, заслуживающий более подробного описания, - базовый класс библиотеки компонентов System.Component, классSystem.Component. Как упоминалось ранее, в Delphi для .NET класс TComponent представляет собой псевдоним этого класса, а класс TComponentHelper предоставляет дополнительные возможности для имитации класса компонента VCL для Win32. В обоих случаях класс Component/TComponent является базовым классом всех визуальных и невизуальных компонентов, используемых на стадии конструирования в среде разработки.
В VCL компоненты играют роль контейнеров (или владельцев) для других компонентов - причем не на визуальном, а на логическом уровне (визуальная принадлежность определяется свойством Parent). Самый важный аспект принадлежности компонентов заключается в том, что владелец отвечает за уничтожение принадлежащих ему компонентов (см. главу 6).
Напротив, в .NET FCL за уничтожение объектов отвечает система GC. Тем не менее логические связи между компонентами необходимы на стадии конструирования, поэтому вы можете определять контейнеры компонентов, размещать в них компоненты и ссылаться на интерфейс контейнера (IContainer) через свойство Container компонента (также имеется свойство Site для обращения к интерфейсу ISite, интерфейсISite, который также задается контейнером и позволяет получить доступ к дополнительной информации на стадии конструирования).
Хотя эти свойства предназначены для стадии конструирования, мне кажется, было бы неплохо заменить список Components в VCL чем-то аналогичным. Пример CompContain демонстрирует принципы работы с контейнерами. В начале программа включает несколько компонентов во вновь созданный контейнер:
procedure CompContainForm.TWinForm_Load( sender: System.Object; e: System.EventArgs); var container1: System.ComponentModel.Container; begin container1 := System.ComponentModel.Container.Create; container1.Add (Panel1); container1.Add (btnParent); container1.Add (Self); |
Даже если объект контейнера не сохранен в переменной, к нему можно обратиться через любой из включенных в него объектов - как это сделано в следующем коде, выводящем строковое представление самого контейнера ToString, количество содержащихся в нем компонентов и некоторые данные о каждом из его компонентов (рис. 9.4):
procedure CompContainForm.btnContain_Click( sender: System.Object; e: System.EventArgs); var comp: Component; begin if (btnParent.Container <> nil) then begin txtLog.AppendText ('Container: ' + TObject(btnParent.Container).ToString + sLineBreak); txtLog.AppendText ('Container components count: ' + btnParent.Container.Components.Count.ToString + sLineBreak); for Comp in btnParent.Container.Components do txtLog.AppendText ('Container components: ' + Comp.ToString + sLineBreak); end; |
Включение нескольких связанных компонентов в контейнер отчасти компенсирует потерю очень интересной возможности компонентов VCL - вызова метода Notification. В Win32 очень важно было знать, в какой момент другой компонент удаляется из конструктора, потому что при этом приходилось задавать равными nil любые ссылки на уничтожаемый объект. Однако данная возможность также использовалась для управления ссылками между объектами - в .NET у нее не существует прямого аналога.
Рис. 9.4. Приложение CompContain демонстрирует процесс включения компонентов в контейнер
Последняя область FCL, которой я хочу уделить особое внимание, - визуальная составляющая библиотеки, основанная на традиционном графическом интерфейсе Windows. Библиотека WinForms похожа на визуальную часть VCL и работает параллельно с ней. Тем не менее в отличие от RTL компания Borland не пыталась объединить две библиотеки и сохранила VCL как альтернативу для WinForms от компании Microsoft. Обе библиотеки в конечном счете вызывают функции Win32 API.
Библиотека WinForms, как и VCL, включает формы и визуальные элементы с похожими свойствами, методами и событиями. Среди важных различий следует отметить то, что в WinForms нет ничего похожего на DFM-файлы. По аналогии с Java, все операции с формой (или другим визуальным контейнером), выполняемые на стадии конструирования, автоматически отражаются в исходном коде программы. Сгенерированный код (листинг 9.1) получается довольно большим, но, на мой взгляд, его стоит привести хотя бы один раз.
Листинг 9.1. Конструктор и код инициализации приложения CompContain
constructor CompContainForm.Create; begin inherited Create; InitializeComponent; end; procedure CompContainForm.InitializeComponent; begin Self.btnContainer := System.Windows.Forms.Button.Create; Self.Panel1 := System.Windows.Forms.Panel.Create; Self.btnColor := System.Windows.Forms.Button.Create; Self.btnParent := System.Windows.Forms.Button.Create; Self.txtLog := System.Windows.Forms.TextBox.Create; Self.Panel1.SuspendLayout; Self.SuspendLayout; // // btnContainer // Self.btnContainer.Location := System.Drawing.Point.Create(40, 24); Self.btnContainer.Name := 'btnContainer'; Self.btnContainer.Size := System.Drawing.Size.Create(120, 23); Self.btnContainer.TabIndex := 0; Self.btnContainer.Text := 'Show Container'; Include(Self.btnContainer.Click, Self.btnContain_Click); // // Panel1 // Self.Panel1.BackColor := System.Drawing.SystemColors.ControlLight; Self.Panel1.BorderStyle := System.Windows.Forms.BorderStyle.Fixed3D; Self.Panel1.Controls.Add(Self.btnColor); Self.Panel1.Controls.Add(Self.btnParent); Self.Panel1.Location := System.Drawing.Point.Create(40, 72); Self.Panel1.Name := 'Panel1'; Self.Panel1.Size := System.Drawing.Size.Create(192, 136); Self.Panel1.TabIndex := 1; // // btnColor // Self.btnColor.Location := System.Drawing.Point.Create(48, 72); Self.btnColor.Name := 'btnColor'; Self.btnColor.Size := System.Drawing.Size.Create(96, 23); Self.btnColor.TabIndex := 1; Self.btnColor.Text := 'Reset Color'; Include(Self.btnColor.Click, Self.btnColor_Click); // // btnParent // Self.btnParent.Location := System.Drawing.Point.Create(48, 32); Self.btnParent.Name := 'btnParent'; Self.btnParent.Size := System.Drawing.Size.Create(96, 23); Self.btnParent.TabIndex := 0; Self.btnParent.Text := 'Show Parent'; Include(Self.btnParent.Click, Self.btnParent_Click); // // txtLog // Self.txtLog.Location := System.Drawing.Point.Create(264, 24); Self.txtLog.Multiline := True; Self.txtLog.Name := 'txtLog'; Self.txtLog.Size := System.Drawing.Size.Create(480, 184); Self.txtLog.TabIndex := 2; Self.txtLog.Text := ''; // // CompContainForm // Self.AutoScaleBaseSize := System.Drawing.Size.Create(5, 13); Self.BackColor := System.Drawing.SystemColors.Control; Self.ClientSize := System.Drawing.Size.Create(760, 230); Self.Controls.Add(Self.txtLog); Self.Controls.Add(Self.Panel1); Self.Controls.Add(Self.btnContainer); Self.Name := 'CompContainForm'; Self.Text := 'CompContain'; Include(Self.Load, Self.TWinForm_Load); Self.Panel1.ResumeLayout(False); Self.ResumeLayout(False); end; |
Как видно из листинга, конструктор вызывает метод InitializeComponent. Метод начинает свою работу с создания всех задействованных компонентов, что позволяет компонентам (в большинстве случаев) ссылаться друг на друга независимо от порядка инициализации. В первой части кода также находятся методы, временно приостанавливающие перерисовку формы (метод SuspendLayout). Затем следуют определения свойств всех компонентов, за ними следует определение свойств формы и активизация перерисовки (метод ResumeLayout).
Инициализация каждого элемента включает его добавление в свойство Controls компонента, в котором он содержится. В данном примере большинство компонентов размещается на форме, а два - на панели (btnColor и btnParent). Для подключения обработчиков к событиям в коде инициализации используется функция Include: как говорилось в главе 4, в .NET используются групповые (multicast) события, поэтому одному событию могут назначаться несколько обработчиков.
При переходе с VCL на .NET возникает одна серьезная проблема: несмотря на сходство многих концепций, также существует масса ложных "двойников", сбивающих с толку. Например, если вы знаете о поддержке стыковки в VCL, то вас удивит, что в WinForms ее не существует, хотя имеется свойство Dock, - однако оно соответствует свойству Align многих элементов VCL. Различия в выборе имен также относятся к обработчикам события;WinFormsсобытий, которые в .NET вызываются по именам (Click или KeyPress), а в VCL снабжаются префиксом On (например, OnClick или OnKeyPress). Когда Microsoft потребовалось выбрать одну из существующих схем записи, была выбрана стандартная схема Visual Basic. Ситуация немного усложняется тем, что имя OnClick существует и принадлежит виртуальному методу, инициирующему событие Click (тогда как в VCL Click - виртуальный метод, инициирующий событие OnClick).
Имеются и другие существенные различия:
По аналогии с VCL, компоненты WinForms делятся на две обширные категории: визуальные, обычно называемые элементами, и невизуальные. Эти категории отличаются прежде всего тем, что на стадии конструирования невизуальные компоненты не размещаются на форме в виде значков, а находятся на отдельной области.
В WinForms не существует понятия графического элемента (TGraphicControl в VCL); таким образом, у каждого элемента имеется идентификатор окна, хранящийся в свойстве Handle.
В свойствах элемента WinForms хранится информация о родителе (Parent) и список дочерних элементов (Controls) - точно так же, как в VCL. Элементы WinForms обладают атрибутами состояния (такими, как Enabled, Focused и Visible) и позиционными свойствами Size и Location (заменяющими свойства Left, Top, Height и Width).
В приложении CompContain некоторые элементы формы размещаются внутри панели, которая становится их родителем. Одна из кнопок отображает информацию о связях между родителями и потомками. Результат работы следующего кода показан на рис. 9.5:
procedure CompContainForm.btnParent_Click( sender: System.Object; e: System.EventArgs); var ctrl: Control; begin txtLog.AppendText ('btnContainer.Parent: ' + btnContainer.Parent.ToString + sLineBreak); txtLog.AppendText ('btnParent.Parent: ' + btnParent.Parent.ToString + sLineBreak); for ctrl in Self.Controls do txtLog.AppendText ('Form controls: ' + ctrl.ToString + sLineBreak); for ctrl in Panel1.Controls do txtLog.AppendText ('Panel controls: ' + ctrl.ToString + sLineBreak); end; |
Рис. 9.5. Отображение связей "родитель/потомок" в приложении CompContain
В VCL свойства вида Parent Xxx означают, что значение свойства Xxx совпадает со значением одноименного свойства родительского элемента (например, свойства Color и ParentColor). В WinForms для этой цели используются свойства окружения (Cursor, Font, BackColor и ForeColor); если такое свойство не определено, используется значение родительского элемента. Так, в примере CompContain панель окрашена в более светлый цвет. Чтобы вернуть ее к основному цвету формы, достаточно вместо конкретного цвета задать свойству специальное значение Empty:
procedure CompContainForm.btnColor_Click( sender: System.Object; e: System.EventArgs); begin Panel1.BackColor := Color.Empty; end; |
Методы и события классов элементов WinForms в целом похожи на свои аналоги из VCL. Свойство TControl.ControlStyle заменено методами GetStyle и SetStyle.
Взглянув на конкретный набор элементов, доступных по умолчанию, вы найдете в них много общего с VCL. Впрочем, есть и различия, о которых следует упомянуть. Конечно, в обоих наборах присутствуют традиционные элементы Windows (Button, ListBox, TextBox и т. д.) и расширенные стандартные элементы Win32 (TreeView, ListView, RichEdit и т. д.).
Стоит отметить, что в WinForms отсутствует элемент Memo, так как в Windows он в действительности представляет собой многострочное текстовое поле. Следовательно, чтобы создать поле Memo в приложении WinForms, следует разместить элемент TextBox и задать его свойству MultiLine значение True. Кроме того, в WinForms отсутствуют элементы HotKey, Animate, HeaderControl и PageScroller.
Конечно, также отсутствуют все элементы, разработанные компанией Borland: RadioGroup, StringGrid, MaskEdit, Bevel, ValueListEditor, PaintBox, MediaPlayer и др. WinForms также определяет ряд собственных элементов, в том числе LinkLabel и PropertyGrid.
WinForms;GDI+Еще одно важное различие между VCL и WinForms связано с выводом на экран (или другое устройство). В VCL используется классическая библиотека GDI, а в WinForms - более новая библиотека GDI+. По сравнению с GDI она обладает многими новыми возможностями: исчезла концепция контекста устройства, объекты GDI (такие, как кисти и перья) передаются в параметрах функций вывода, система координат основана на универсальных преобразованиях и вещественных координатах, кисти поддерживают градиентную закраску, в структуру Color включена поддержка альфа-наложения, предусмотрена поддержка большего количества графических форматов (GIF, JPEG, TIFF и PNG) и т. д. У GDI+ есть только один недостаток: если в вашей операционной системе нет оптимизированного драйвера GDI+, то графические операции будут выполняться медленнее, чем в традиционной версии GDI. Это особенно заметно при работе со шрифтами и текстом.
ПРИМЕЧАНИЕ Вас интересует, почему компания Borland еще не обновила VCL для поддержки GDI+? Дело в том, что GDI+ откомпилирована в библиотеку с интерфейсом C++, а Delphi поддерживает компоновку с библиотеками COM или DLL языка C. Это обстоятельство существенно усложняет поддержку GDI+ в Delphi для Win32 (хотя и не делает ее невозможной). Конечно, поддержка GDI+ в .NET доступна через WinForms. |
Как упоминалось в предыдущем перечне, в GDI+ не используются контексты устройств. Вам уже не придется выбирать перо в контексте устройства и рисовать линию текущим пером - достаточно нарисовать линию, передавая перо в параметре. С другой стороны, при вызове графического метода может передаваться либо перо, либо кисть, тогда как при вызове функции GDI Rectangle можно нарисовать прямоугольник текущим пером и закрасить его текущей кистью.
Для примера я написал простое приложение, которое рисует случайные линии с альфа-наложением и выводит текст, закрашенный градиентной кистью. Пример WinFormLines содержит меню с двумя связанными командами. Обратите внимание: в отличие от VCL визуальный конструктор WinForms поддерживает предварительный просмотр меню и его создание "на месте". Визуальный конструктор меню WinForms в Delphi 2005 IDE изображен на рис. 9.6.
Код рисования линий (созданное изображение является временным и пропадает при перерисовке) выполняется в цикле тысячу раз:
procedure TLinesForm.MenuItem2_Click( sender: System.Object; e: System.EventArgs); var I: Integer; aPen: Pen; g: Graphics; begin aPen := Pen.Create (Color.Blue); g := Self.CreateGraphics; try for I := 1 to nTimes do begin Application.DoEvents; aPen.Color := Color.FromArgb (80, Random (255), Random (255), Random (255)); g.DrawLine (aPen, Random (Width), Random (Height), Random (Width), Random (Height)); end; finally aPen.Free; g.Dispose; end; end; |
Рис. 9.6. Визуальный конструктор меню в WinForms
В цикле программа меняет цвет пера и рисует линию, передавая перо в качестве параметра. Обратите внимание: цвет линии зависит от альфа-канала, следующего за тремя основными составляющими RGB. Не забудьте вызвать Free (то есть Dispose) для пера и объекта класса Graphics, возвращаемого методом CreateGraphics формы.
Другая команда меню программы выводит крупный текст с градиентной закраской (рис. 9.7). Разобраться в нем несложно - достаточно найти правильные имена классов и параметры:
uses System.Drawing.Drawing2D; procedure TLinesForm.MenuItem3_Click( sender: System.Object; e: System.EventArgs); var aBrush: System.Drawing.Brush; aFont: System.Drawing.Font; g: Graphics; begin aBrush := LinearGradientBrush.Create ( Self.ClientRectangle, Color.Aquamarine, Color.BlueViolet, LinearGradientMode.ForwardDiagonal); aFont := System.Drawing.Font.Create ( 'Arial Black', 25, GraphicsUnit.Millimeter); g := Self.CreateGraphics; try g.DrawString('Delphi for .NET', aFont, aBrush, 10, 10); finally aBrush.Free; aFont.Free; g.Dispose; end; end; |
Рис. 9.7. Градиентная закраска текста в примере WinFormLines
Forms, классКлассы форм в двух библиотеках также имеют много общего, несмотря на различия в именах свойств, методов и событий. Некоторые различия перечислены в табл. 9.3; в нее не вошли различия, упоминавшиеся ранее для общих элементов (поскольку форма является особой разновидностью элемента).
TForm (VCL) | Forms (WinForms) |
---|---|
Событие OnCreate | Событие Load |
Свойство Constraints | Свойства MaximumSize и MinimumSize |
Свойство Button.Default | Свойство AcceptButton |
Свойство BorderIcons | Свойство ControlBox |
Свойства AlphaBlend и AlphaBlendValue | Свойство Opacity |
Свойства TransparentColor и TransparentColorValue | Свойство TransparencyKey |
Значение fsStayOnTop свойства FormStyle | Свойство TopMost |
Событие OnClick главного меню | Событие MenuStart (также имеется событие MenuComplete) |
Метод SelectNext | Методы GetNextControl и SelectNextControl |
Класс Form в WinForms обладает некоторыми дополнительными возможностями по сравнению со своим аналогом VCL. Свойство BackgroundImage задает фоновое изображение для формы, свойство Language активизирует поддержку локализации, а свойство ShowInTaskBar упрощает работу с панелью задач. Конечно, к этому следует прибавить и поддержку GDI+ при рисовании на форме.Load, событие (WinForms) MenuStart, события
ПРИМЕЧАНИЕ Формы WinForms поддерживают MDI. Это довольно странно, учитывая, что компания Microsoft объявила интерфейс MDI устаревшим при выходе Windows 95. |
Несмотря на все приятные новшества WinForms, у некоторых возможностей VCL нет аналогов в WinForms. Не вдаваясь в подробности, скажу, что мне не хватает концепции действий, обеспечиваемой компонентом ActionList и сопутствующей архитектурой. Так как механизм действий действительно востребован, некоторые сторонние разработчики компонентов Delphi заполняют этот пробел, продавая компоненты с поддержкой соответствующей архитектуры. Примером может послужить пакет CommandMaster от Component Science (www.componentscience.net).
Другой полезной возможностью могли бы стать невизуальные контейнеры (такие, как модули данных). Они весьма удобны в приложениях баз данных, поскольку один служебный модуль доступа к данным может совместно использоваться несколькими формами (за подробностями обращайтесь к описанию архитектуры баз данных в Delphi, начиная с главы 13).
Существует еще две области с существенными различиями, в которых я предпочитаю решение VCL. Во-первых, это выбор между фреймами (VCL) и пользовательскими элементами(WinForms), а во-вторых - реализация визуального наследования. В этих технологиях Delphi, в отличие от WinForms, задействованы преимущества потокового сохранения форм и управления свойствами. Впрочем, не хочу создавать у читателя предвзятое впечатление: в обеих библиотеках присутствуют достаточно сходные возможности.
Наконец, у WinForms имеется весьма раздражающая особенность, которая проявляется при удалении обработчиков событий (возможно, добавленных случайно при щелчке на компоненте). В отличие от приложений VCL обработчик нельзя оставить пустым; вы должны удалить объявление и определение метода и отключить обработчик события в коде, сгенерированном визуальным конструктором.
В этой главе рассматривались основные библиотеки, доступные в Delphi для .NET. Мы начали с адаптации "родной" RTL Delphi на платформу .NET, а затем перешли к библиотеке FCL (Framework Class Library), включенной Microsoft в .NET SDK. Я кратко описал некоторые важные классы FCL и часть библиотеки, связанной с пользовательским интерфейсом (WinForms).
Изложение материала ориентировано на разработчиков Delphi с опытом работы, поэтому вместо подробного описания всех возможностей библиотек (нереальная задача с учетом их объема) я сосредоточил внимание на различиях и специфических возможностях.
Конечно, настоящая глава не дает полного представления о .NET FCL. Мы еще не рассматривали ADO.NET (глава 16), HTML и поддержку Интернета (глава 19), веб-программирование с использованием ASP.NET (глава 21), поддержку XML и SOAP (главы 22 и 23). Часть рассматриваемых тем относится исключительно к .NET (например, ADO.NET и ASP.NET). В других упомянутых главах рассматриваются обе платформы, Win32 и.NET, при этом особое внимание уделяется Win32 и межплатформенным возможностям, с кратким описанием специфики FCL.
Главная страница - Программные продукты - Статьи - Разработка ПО, Embarcadero |
Распечатать »
Правила публикации » |
Написать редактору | |||
Рекомендовать » | Дата публикации: 01.04.2009 | |||
|
Новости по теме |
Российские Android-программисты останутся без денег. Google запрещает переводы
|
Рассылки Subscribe.ru |
Статьи по теме |
Новинки каталога Download |
5 бесплатных приложений, которые будут напоминать вам отдохнуть от экрана компьютера или смартфона
|
Исходники |
Отдам код в хорошие руки. Мошенничество в ИТ-сфере
|
Документация |