Библиотека времени выполнения .NET и FCL

Источник: rsdn
Автор: М. Кэнту

Автор: М. Кэнту
Материал предоставил: Издательство ''Питер''

Несколько последних глав (с 5 по 8) были посвящены библиотеке времени выполнения Delphi (RTL) и VCL, главным образом с точки зрения разработки для платформы Win32. Я упоминал о различиях между Win32 и .NET и приводил примеры кода для обеих платформ, однако практически не уделял внимание технологиям, специфическим для .NET.

Настоящая глава заполняет этот пробел. В ней рассматриваются три разные темы. В первой части будут описаны некоторые важные изменения в поддержке Delphi RTL для .NET. Во второй части рассматривается собственная библиотека .NET с обсуждением некоторых базовых классов FCL (Framework Class Library), объединяемых общим термином BCL (Base Class Library). В третьей части содержится обзор библиотеки форм и элементов WinForms и ее сравнение с VCL.

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

Библиотека времени выполнения Delphi для .NET

Начнем с анализа различий между библиотеками времени выполнения Delphi для .NET и Win32. Компания Borland приложила немалые усилия для того, чтобы низкоуровневые процедуры и основные классы Delphi для .NET оставались совместимыми со своими аналогами для Win32. Но даже если объявления классов остались прежними, их реализация обычно существенно изменяется.

В некоторых случаях в Delphi для объединения интерфейсов стандартных классов Delphi (таких, как TObject и TComponent) с их аналогами для .NET (System.Object и System.ComponentModel.Component) применяются классы-помощники. Везде, где это возможно, основные классы Delphi RTL для .NET создаются как "обертки" для существующих классов FCL.

Модуль System в Delphi для .NET

Переход от Delphi для Win32 к Delphi для .NET сопровождался многочисленными изменениями в модуле System - большими, чем в каком-либо другом модуле RTL. Большинство изменений обусловлено коренными различиями в реализации, и компании Borland пришлось изрядно потрудиться, чтобы обеспечить высокую совместимость с существующим кодом Delphi.

Как уже было показано в главе 4, тип TObject теперь объявляется как псевдоним для типа System.Object, корневого класса .NET Framework. Также вы видели, что классы-помощники (TObjectHelper) позволили интегрировать большинство стандартных методов класса TObject в класс System.Object. А это означает, что такие методы доступны для любых классов, в том числе и не откомпилированных в Delphi для .NET.

Впрочем, не все аспекты TObject можно перенести в .NET. Приведу короткое сравнение методов, разделив их на группы:

  • Методы, относящиеся к классам (ClassType, ClassName, ClassNameIs, ClassInfo, ClassParent и InheritsFrom), доступны и работают так же, как раньше.
  • Низкоуровневые методы для работы с памятью (MethodAddress, MethodName и FieldAddress) доступны, но работают по-другому, потому что в новой версии они не базируются на указателях.
  • Низкоуровневые методы конструирования и уничтожения объектов (InitInstance, CleanupInstance, NewInstance и FreeInstance) не поддерживаются, как и конструкторы/деструкторы второго уровня (AfterConstruction и BeforeDestruction).
  • Также не поддерживаются методы, относящиеся к интерфейсам (GetInterface, GetInterfaceEntry и GetInterfaceTable), а также метод SafeCallException.
  • Метод InstanceSize использоваться не может, потому что размер объекта определяется не компилятором, а во время загрузки на стадии выполнения. Любые допущения относительно размера объекта в .NET считаются ошибками проектирования, потому что они могут нарушить работу кода на других платформах .NET, не базирующихся на Win32.
  • Виртуальный метод Dispatch остался доступным, хотя он имеет нетипизованный параметр (var Message); виртуальный метод DefaultHandler не поддерживается.
  • Наконец, метод Free остался доступным, хотя теперь вместо деструктора он вызывает метод IDisposable.Dispose (см. главу 4).

Превратившись в псевдоним System.Object, класс TObject приобрел ряд новых методов - таких, как Equals, GetHashCode, ToString и GetType. Они будут описаны позднее, в разделе "Класс System.Object".

Кроме объявления TObject, между модулем Win32 System и модулем .NET Borland.Delphi.System существуют и другие различия. Вот лишь некоторые из них:

  • Ссылка на класс TClass работает иначе (см. главу 4).
  • Класс Exception был перемещен из модуля SysUtils и превратился в псевдоним для класса .NET System.Exception.
  • Базовый интерфейс IInterface не содержит методов (так как в .NET подсчет ссылок не применяется, а поддержка интерфейсов напрямую проверяется CLR при преобразованиях типов).
  • Функция Assigned имеет параметр типа GCHandle, тогда как в Win32 ее параметр был нетипизованным.
  • В .NET существуют многочисленные типы атрибутов.
  • В .NET некоторые обычные типы данных, включая AnsiString, Currency и TDateTime, оформляются в виде записей. Такие записи содержат методы и определяют операторы, в том числе операторы преобразования.
  • Запись TMethod содержит несколько новых методов.
  • В .NET исключена вся поддержка управления памятью.

Borland.Delphi.DLL

Другое принципиальное отличие от прошлых версий Delphi состоит в том, что в .NET язык поставляется с DLL-библиотекой времени выполнения. В Win32 базовая функциональность времени выполнения (определяемая в модуле System) включалась в каждый исполняемый файл независимо от использования пакетов времени выполнения. Впрочем, это приводило к крайне незначительным дополнительным затратам ресурсов, а некоторые функции (скажем, поддержка универсального типа Variant) включались в исполняемый файл только в случае ее фактического использования.

В .NET по умолчанию функциональность времени выполнения также включается в исполняемый файл. Тем не менее на этой платформе также можно выбрать режим использования внешней среды времени выполнения из библиотеки Borland.Delphi.DLL. Библиотека занимает совсем немного места (99,5 Кбайт) и включается в GAC программой установки Delphi. Вы можете поместить ее копию в локальную папку компиляции, для этого следует выделить сборку в списке References и выполнить команду Copy Local в меню проекта.

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

Например, минимальное приложение MiniSizeNet (см. главу 5) уменьшается с 28 Кбайт до 12 Кбайт. Впрочем, в реальных приложениях средний выигрыш получается не столь заметным, как в этом крайнем случае.

Borland.VclRtl

По аналогии с пакетами, составляющими Win32 VCL, библиотека Delphi условно делится на две части: библиотеку глобальных функций и низкоуровневых классов (см. главу 5) и библиотеку визуальных компонентов (см. главу 6). То же относится и к библиотеке Delphi для .NET, состоящей из сборок Borland.VclRtl и Borland.Vcl. Здесь важно то, что часть VCL почти не изменилась (поскольку она работает параллельно с классами .NET Framework), а часть RTL интегрируется с FCL.

И все же общий подход, использованный в Delphi RTL, отличается от подхода FCL, так как в Delphi определяются сотни глобальных функций (в главе 4 рассказано о том, как глобальные функции отображаются на статические члены классов, сгенерированных компилятором Delphi для .NET). Компания Borland избрала этот путь для сохранения совместимости с существующим кодом Delphi (что бы по этому поводу ни говорили блюстители "чистоты" ООП).

Некоторые модули RTL практически не изменились, в том числе ConvUtils, DateUtils, Math, StrUtils и SyncObjs из пространства имен Borland.Vcl. В ряде других модулей присутствуют ограниченные изменения:

  • Borland.Vcl - определения записей TPoint, TSize и TRect, которые в .NET имеют множество методов и конструкторов.
  • Borland.Vcl.Variants и Borland.Vcl.VarConv unitVarConv - интерфейс в целом остался прежним, но реализация полностью изменилась, поскольку ранее она базировалась на конкретной поддержке Win32 (части архитектуры COM). По тем же причинам исчез модуль VarUtils. Обратите внимание: модуль Borland.Vcl.Variants (или просто Variants, если по умолчанию используется пространство имен Borland.Vcl) теперь необходимо вручную включать в директиву uses программы, использующей универсальный тип, потому что его поддержка была исключена из базовой системной функциональности.

Также в Delphi для .NET появился ряд новых модулей RTL:

  • Borland.Vcl.Complex - Complex, модульопределение записи для представления комплексных чисел дает полный пример перегрузки операторов. Заменяет (все еще существующий) модуль Borland.Vcl.VarCmplx.
  • Borland.Vcl.Convert - Convert, модульопределение записи TConvert, обеспечивающей поддержку интерфейса .NET IComparable и IConvertible для механизма преобразования, определенного в модуле ConvUtils.
  • Borland.Vcl.WinUtils - WinUtils, модульвспомогательные функции, заменяющие или дополняющие такие функции Windows API, как MakeObjectInstance или AllocateHWnd; определения функций преобразования стандартных типов данных Delphi в типы данных .NET (скажем, преобразования низкоуровневых буферов в массивы); доступ к глобальным данным, включая HInstance и GetCmdShow.

Наконец, в .NET отсутствуют модули DelphiMM и ShareMem.

Объявления функций Windows API в Borland.Vcl.Windows

Модуль Borland.Vcl.Windows, наряду с другими аналогичными модулями, предоставляет интерфейс к классическим функциям Win32 API из приложений .NET. Определения этих функций API существенно изменились, как в следующем случае:

// Определение для 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;

Borland.Vcl.SysUtils

Многочисленные изменения произошли в модуле 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).

Классы Borland.VclRtl

Большинство классов 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 (см. далее раздел "Регулярные выражения").

Borland.Vcl.Classes

Основные классы 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
Таблица 9.1. Некоторые классы Delphi для .NET, инкапсулирующие соответствующие классы FCL

Что касается класса 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

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 Лексический разбор и поддержка регулярных выражений
Таблица 9.2. Пространства имен BCL

Именно эта часть FCL будет рассматриваться в данном разделе. Я ни при каких условиях не смог бы описать (или хотя бы просто перечислить) все классы BCL в книге, так как на эту тему можно легко написать целую книгу. Только пространство имен System содержит более 100 классов. Подробный справочник можно найти в .NET Framework SDK; его также можно открыть непосредственно из Delphi 2005 IDE. Я постараюсь описать лишь несколько важных классов, по возможности сравнивая их с аналогами из Delphi RTL.

Класс System.Object

Класс Object является общим предком для всех классов FCL (и Delphi для .NET). Он играет ту же роль, что и класс TObject в Delphi для Win32. Я уже упоминал, что TObject в Delphi для .NET является псевдонимом для System.Object, но не демонстрировал возможности этого класса.

Среди ключевых методов System.Object только GetType имеет аналог в объекте Delphi TObject, а остальные методы относятся к сравнению и представлению объектов:

  • Метод ReferenceEqual сравнивает две ссылки, переданные в параметрах, и проверяет, относятся ли они к одному объекту (то есть к одному адресу памяти). Это соответствует применению символа = к переменным, содержащим ссылки на объекты.
  • Метод Equals сравнивает содержимое объектов (поле за полем) и проверяет, совпадают ли хранящиеся в них данные; при этом сами объекты могут находиться по разным адресам памяти.
  • Метод GetType возвращает тип объекта. Результат относится к типу System.Type и с ним можно работать при помощи API-рефлексии.
  • Метод ToString преобразует объект в строковое представление, описывающее конкретный экземпляр и его данные. Метод является виртуальным и переопределяется в производных классах.
  • Метод GetHashCode возвращает хеш-код, идентифицирующий объект в хеш-таблицах. Метод также является виртуальным и часто переопределяется.

Для демонстрации некоторых методов (и особенно сравнения объектов) я создал пример 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);

Класс StringBuilder

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.

Потоки данных в .NET

Операции с файлами с применением потоковых классов составляют еще одну область, в которой уместно ограничиться "родной" поддержкой 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

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 демонстрирует процесс включения компонентов в контейнер

WinForms

Последняя область FCL, которой я хочу уделить особое внимание, - визуальная составляющая библиотеки, основанная на традиционном графическом интерфейсе Windows. Библиотека WinForms похожа на визуальную часть VCL и работает параллельно с ней. Тем не менее в отличие от RTL компания Borland не пыталась объединить две библиотеки и сохранила VCL как альтернативу для WinForms от компании Microsoft. Обе библиотеки в конечном счете вызывают функции Win32 API.

Только код, без DFM-файлов

Библиотека 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).

Имеются и другие существенные различия:

  • Свойство Caption (иногда называемое Text, как в TEdit) в WinForms всегда называется Text. Наличие единого имени - хорошая мысль, но к нему надо привыкнуть.
  • Свойство Color элемента или формы в WinForms называется BackColor. Также имеется свойство ForeColor, определяющее цвет текста (который в VCL задается свойством Font.Color).
  • Как упоминалось ранее, свойство Tag относится к типу System.Object, классSystem.Object.
  • Все элементы обладают интегрированной поддержкой связи с данными. В WinForms нет разных элементов Edit и DBEdit, как в VCL, а существует единый элемент TextBox, который может связываться с данными. Связь осуществляется через свойство DataBindings (см. описание ADO.NET в главе 16).
  • Также существует общее свойство CauseValidation и два взаимосвязанных события (Validating и Validated), обусловленные тем фактом, что все элементы могут связываться с данными.
  • Специальные свойства AccessibleDescription, AccessibleName и AccessibleRole предназначены для пользователей с ослабленным зрением и слепых.
  • Свойство VCL PopupMenu в WinForms называется ContextMenu.
  • Для вывода простого окна сообщения вместо глобальной функции ShowMessage используется один из перегруженных методов класса MessageBox (например, MessageBox.Show).
  • Класс WinForms Application содержит метод DoEvents, заменяющий метод ProcessMessages класса VCL TApplication.

Элементы WinForms

По аналогии с 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.

От GDI к GDI+

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

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
Таблица 9.3. Различия в именах свойств, методов и событий форм в VCL и WinForms

Класс Form в WinForms обладает некоторыми дополнительными возможностями по сравнению со своим аналогом VCL. Свойство BackgroundImage задает фоновое изображение для формы, свойство Language активизирует поддержку локализации, а свойство ShowInTaskBar упрощает работу с панелью задач. Конечно, к этому следует прибавить и поддержку GDI+ при рисовании на форме.Load, событие (WinForms) MenuStart, события

ПРИМЕЧАНИЕ

Формы WinForms поддерживают MDI. Это довольно странно, учитывая, что компания Microsoft объявила интерфейс MDI устаревшим при выходе Windows 95.

Возможности VCL, отсутствующие в WinForms

Несмотря на все приятные новшества 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.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=20308