Советы пользователям Delphi и C++Builder

         

Часть 1. Почти все о заставках


Наталия Елманова

Компьютер Пресс - CD, 1999, N 2

Copyright © N.Elmanova & ComputerPress Magazine.

Этой статьей мы начинаем серию небольших советов для пользователей Delphi и С++Builder, позволяющих решить некоторые проблемы, возникающие при создании приложений и, возможно, улучшить их интерфейс или функциональность.



Часть 2. Немного о внешних устройствах и операционной системе


Наталия Елманова

Компьютер Пресс - CD, 1999, N 3

Copyright © N.Elmanova & ComputerPress Magazine.

В данной статье содержатся советы, позволяющие решить некоторые из множества проблем, возникающие при использовании внешних устройств в приложениях Delphi и С++Builder.



Часть 3. MIDAS и "маленькие" настольные приложения


Наталия Елманова

Компьютер Пресс - CD, 1999, N 4

© Copyright N.Elmanova & ComputerPress Magazine.

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



Часть 4. Что можно поместить в DBGrid




Наталия Елманова

Компьютер Пресс - CD, 1999, N 5

© Copyright N.Elmanova & ComputerPress Magazine.

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



Часть 5. Снова о внешних устройствах и операционной системе


Наталия Елманова

Компьютер Пресс - CD, 1999, N 10

© Copyright N.Elmanova & ComputerPress Magazine.

Во второй статье данного цикла ("Компьютер Пресс", 1999, N 3) были рассмотрены некоторые из множества проблем, возникающие при использовании внешних устройств в приложениях Delphi и C++Builder, а также вопросы получения информации об операционной системе. В соответствии с многочисленными пожеланиями читателей мы снова возвращаемся к этой теме.



Часть 7 (совет 18). Немного о джойстиках


Наталия Елманова

Компьютер Пресс - CD, 1999, N 7

© Copyright N.Elmanova & ComputerPress Magazine.

Посвящается всем любителям компьютерных игр…

В предыдущей статье данного цикла ("Компьютер Пресс", 1999, N 3) были рассмотрены некоторые из вопросов, связанных с использованием внешних устройств. Данная статья продолжает начатую тему. Так как данный номер посвящен приложениям multimedia, а примеры использования компонента TMediaPlayer и создания проигрывателя звуковых компакт-дисков доступны российским читателям в нескольких различных модификациях (см., например, недавно вышедшую книгу С.Тейксейра и К.Пачеко "Borland Delphi 4: руководство разработчика"; подобные примеры есть и в других источниках), данная статья посвящена теме довольно экзотической - использованию джойстика. Некоторые (но далеко не все) идеи, послужившие основой для рассмотренного ниже примера, заимствованы из малодоступного российскому читателю источника "C++Builder - how-to" (J.Miano, T.Cabanski, H.Howe. - Waite Group Press, 1997).

Использование джойстика базируется на использовании нескольких функций Windows API и структур Windows, описанных в файле mmsystem.h.

Сервисы, обслуживающие джойстики, загружаются при загрузке операционной системы. В 32-разрядных версиях Windows джойстиков может быть два, и каждый может иметь от одной до четырех кнопок. Каков именно ваш джойстик и каковы характеристики его драйвера, можно определить с помощью соответствующих функций Windows API. Помимо этого, с помощью других функций можно определить, какие из кнопок нажаты в данный момент, и каковы текущие координаты, связанные с наклоном его ручки (joystick's positional information). Это можно сделать, либо опрашивая джойстик периодически, либо обрабатывая полученные от него события.

Создадим простейшее приложение для тестирования джойстика. С этой целью создадим форму, на которую поместим компонент TImage с изображением джойстика, и четыре компонента TShape (в данном случае это красные эллипсы) для изображения нажатых кнопок. Сделаем компоненты TShape невидимыми. Кроме того, поместим на форму компоненты TMemo и TPaintBox. И, наконец, поместим на форму компонент TImageList, в который поместим одно-единственное изображение, более или менее похожее на прицел джойстика (рис.1):




Рис.1. Главная форма приложения для тестирования джойстика

Интерфейс приложения готов. Теперь можно приступить к созданию кода.

Для начала следует не забыть сослаться в приложении сослаться на файл mmsystem.h (в нем описаны необходимые структуры и функции для управления джойстиком и получения информации от него).

Первое, что нужно сделать - это узнать, установлен ли вообще драйвер джойстика, и если да, подключен ли джойстик. Для этой цели используется функция joyGetNumDevs(). Если она возвращает значение, отличное от нуля, это означает, что драйвер джойстика установлен. Однако это еще не означает, что сам джойстик подключен:

int joycount=joyGetNumDevs(); if (joycount=0) Memo1->Lines->Add("Драйверы джойстика не установлены");

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

jr=joyGetPosEx(JOYSTICKID1,&JoyInfo); if (jr ==JOYERR_NOERROR) { connect=true; jnum= JOYSTICKID1; } else if (jr == MMSYSERR_INVALPARAM) ShowMessage("Ошибка определения наличия джойстика"); else if((jr=joyGetPosEx(JOYSTICKID2,&JoyInfo)) == JOYERR_NOERROR) { connect = true; jnum= JOYSTICKID2; }

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

Memo1->Lines->Add("Номер устройства - "+ IntToStr(jnum)); Memo1->Lines->Add("Наименование - " + AnsiString(JoyCaps.szPname)); Memo1->Lines->Add("Число кнопок - " + IntToStr(JoyCaps.wNumButtons));

Если джойстик подключен, можно обрабатывать его события. Функция joySetCapture предназначена для перенаправления сообщений джойстика данному окну (в нашем случае - главному окну нашего приложения). Параметры этой функции содержат дескриптор окна, указатель на то, первый или второй джойстик должен посылать сообщения, частоту отправки этих сообщений (в данном случае используется минимально возможный период, определяемый с помощью все той же структуры JOYCAPS), а также параметр, указывающий на то, нужно ли посылать сообщения, если в состоянии джойстика ничего не изменилось.



if (connect) joySetCapture(Handle,jnum,2*JoyCaps.wPeriodMin,FALSE);

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

if (connect) joyReleaseCapture(jnum);

Теперь можно создать функции, реагирующие на события джойстика. Одна из них должна делать видимыми те из объектов TShape, которые соответствуют нажатым кнопкам. Вторая должна отображать "прицел", перемещая его внутри компонента TPaintBox. Помимо этого, следует сослаться на эти функции в h-файле, указав, что они являются реакцией на соответствующие сообщения Windows. Текст h-файла, таким образом, имеет вид:

#ifndef joy1H #define joy1H //--------------------------------------------------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> #include <ExtCtrls.hpp> #include <Graphics.hpp> #include <ImgList.hpp> #include <Buttons.hpp> //--------------------------------------------------------------------------- class TForm1 : public TForm { __published: // IDE-managed Components TImage *Image1; TShape *Shape2; TShape *Shape1; TShape *Shape3; TShape *Shape4; TMemo *Memo1; TImageList *ImageList1; TPaintBox *PaintBox1; void __fastcall FormCreate(TObject *Sender); void __fastcall UpdButton(TMessage &msg); void __fastcall UpdCoord(TMessage &msg); void __fastcall FormDestroy(TObject *Sender); private: int jnum; JOYCAPS JoyCaps; bool connect; int stepX,stepY; TPoint Pos;

// User declarations public: // User declarations __fastcall TForm1(TComponent* Owner); BEGIN_MESSAGE_MAP MESSAGE_HANDLER(MM_JOY1BUTTONDOWN,TMessage,UpdButton) MESSAGE_HANDLER(MM_JOY1BUTTONUP,TMessage,UpdButton) MESSAGE_HANDLER(MM_JOY1MOVE,TMessage,UpdCoord) MESSAGE_HANDLER(MM_JOY2BUTTONDOWN,TMessage,UpdButton) MESSAGE_HANDLER(MM_JOY2BUTTONUP,TMessage,UpdButton) MESSAGE_HANDLER(MM_JOY2MOVE,TMessage,UpdCoord) END_MESSAGE_MAP(TForm) }; //--------------------------------------------------------------------------- extern PACKAGE TForm1 *Form1; //--------------------------------------------------------------------------- #endif



Сам исходный текст модуля, связанного с главной формой приложения, имеет вид:

//--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include <mmsystem.h> #include "joy1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { MMRESULT jr; JOYINFOEX JoyInfo; int joycount=joyGetNumDevs(); if (joycount=0) Memo1->Lines->Add("Драйверы джойстика не установлены"); else { connect=false; jr=joyGetPosEx(JOYSTICKID1,&JoyInfo); if (jr ==JOYERR_NOERROR) { connect=true; jnum= JOYSTICKID1; } else if (jr == MMSYSERR_INVALPARAM) ShowMessage("Ошибка определения наличия джойстика"); else if((jr=joyGetPosEx(JOYSTICKID2,&JoyInfo)) == JOYERR_NOERROR) { connect = true; jnum= JOYSTICKID2; } } joyGetDevCaps(jnum,&JoyCaps, sizeof(JOYCAPS)); if (connect) Memo1->Lines->Add("Джойстик подключен") ; else Memo1->Lines->Add("Джойстик не подключен") ; Memo1->Lines->Add("Номер устройства - "+ IntToStr(jnum)); Memo1->Lines->Add("Наименование - " + AnsiString(JoyCaps.szPname)); Memo1->Lines->Add("Число кнопок - " + IntToStr(JoyCaps.wNumButtons)); if (connect) joySetCapture(Handle,jnum,2*JoyCaps.wPeriodMin,FALSE); stepX = (JoyCaps.wXmax - JoyCaps.wXmin)/ PaintBox1->Width; stepY = (JoyCaps.wYmax - JoyCaps.wYmin)/ PaintBox1->Height; } //--------------------------------------------------------------------------- void __fastcall TForm1::UpdButton(TMessage &msg) { Shape1->Visible= (msg.WParam &JOY_BUTTON1); Shape2->Visible= (msg.WParam &JOY_BUTTON2); Shape3->Visible= (msg.WParam &JOY_BUTTON3); Shape4->Visible= (msg.WParam &JOY_BUTTON4); } //--------------------------------------------------------------------------- void __fastcall TForm1::UpdCoord(TMessage &msg) { JOYINFO JoyInfo; TCanvas *pCanvas = PaintBox1->Canvas; pCanvas->Brush->Color=Form1->Color; pCanvas->FillRect(Rect(0,0,PaintBox1->Width,PaintBox1->Height)); Pos.x = msg.LParamLo; Pos.y = msg.LParamHi; int x=(Pos.x-JoyCaps.wXmin)/stepX-ImageList1->Width/2; int y=(Pos.y-JoyCaps.wYmin)/stepY-ImageList1->Height/2; ImageList1->Draw(pCanvas,x,y,0,true); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormDestroy(TObject *Sender) { if (connect) joyReleaseCapture(jnum); } //---------------------------------------------------------------------------

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



Рис.2. Приложение для тестирования джойстика на этапе выполнения


Создание простейших заставок


Нередко при запуске Windows-приложений перед работой приложения появляется заставка (в англоязычной литературе она называется Splash screen), содержащая информацию о запускаемом приложении и, возможно, иные сведения (серийный номер продукта, версию и пр.). Каким образом можно создать такую заставку?

Добавим к нашему приложению форму, свойство BorderStyle ее установим равным bsNone, поместим на нее метку и компонент TTimer. Свойство Interval компонента TTimer установим равным 1000, умноженное на число секунд, в течение которых заставка должна находиться на экране (значение 3000 соответствует 3 секундам).

Рис. 1. Форма, которая должна появиться в качестве заставки

Создадим обработчик события OnTimer компонента TTimer: В случае Delphi он выглядит так:

procedure TForm3.Timer1Timer(Sender: TObject); begin Close; end;

В случае С++Builder он имеет следующий вид:

void __fastcall TForm3::Timer1Timer(TObject *Sender) { Close(); }

В опциях проекта перенесем вновь созданную форму в раздел Available Forms.

Теперь создадим обработчик события OnCreate главной формы приложения, который в случае Delphi выглядит так:

procedure TForm1.FormCreate(Sender: TObject); begin Form3:=TForm3.Create(Application); Form3.ShowModal; Form3.Free; end;

В случае C++Builder этот же обработчик события выглядит так:

void __fastcall TForm1::FormCreate(TObject *Sender) { Form3= new TForm3(this); Form3->ShowModal(); Form3->Free(); }

Теперь перед запуском приложения на экране будет появляться заставка следующего вида:

Рис. 2. Заставка на этапе выполнения



Вывод информации о приложении на заставке


Если запустить какое-либо из приложений MS Office 97, на появляющейся при загрузке приложения заставке можно увидеть не только сведения о самом приложении, но также и регистрационный номер и фамилию пользователя. Как известно, эти сведения вводятся пользователем в процессе установки приложения, и в настоящее время принято хранить подобные сведения в реестре Windows. Если создать дистрибутив приложения с помощью наиболее часто используемого в нашей стране для этой цели средства InstallShield (или с помощью InstallShield Express, входящего в состав старших версий Delphi и C++Builder), в реестре Windows появится раздел, похожий на представленный на рис.3:

Рис. 3. Раздел реестра, создаваемый программой установки

В данном случае приложение MyApp версии 1.0 создано компанией Splash Screen Company, и при его установке были введены серийный номер 176-99027, а также имя пользователя и имя компании, в которой данный пользователь работает.

Если инсталляционное приложение создано не с помощью InstallShield, а с помощью иных средств (например, написано на Delphi или C++Builder), наименование раздела и ключей реестра могут отличаться от приведенных выше, но в любом случае они должны быть Вам известны.

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

Рис. 4. Форма заставки после внесения изменений

Создадим обработчик события, связанный с созданием формы. В случае Delphi он имеет следующий вид:

procedure TForm2.FormCreate(Sender: TObject); var key,ser,usr:string; Reg:TRegistry; begin try Reg:=TRegistry.Create; key:='SOFTWARE\Splash Screen Company\MyApp\1.0'; Reg.Rootkey:= HKEY_LOCAL_MACHINE; Reg.OpenKey(key,false); usr:=Reg.ReadString('Name'); ser:=Reg.ReadString('Serial'); Label2.Caption:='Пользователь: '+usr; Label3.Caption:='Серийный номер: '+ser; Reg.CloseKey; Reg.Destroy; except Label2.Caption:='Unregistered user'; Label3.Caption:='Please register'; end; end;


При этом в секции uses модуля, связанного с формой, следует сослаться на модуль registry.
Для С++Builder обработчик этого события выглядит так:
void __fastcall TForm1::FormCreate(TObject *Sender) {TRegistry *Reg; try { Reg=new TRegistry; Reg->RootKey=HKEY_LOCAL_MACHINE; AnsiString key="SOFTWARE\\Splash Screen Company\\MyApp\\1.0"; Reg->OpenKey(key,false); AnsiString usr=Reg->ReadString("Name"); AnsiString ser=Reg->ReadString("Serial"); Label2->Caption="Пользователь: "+usr; Label3->Caption="Серийный номер: "+ser; Reg->CloseKey(); Reg->Free(); } catch (...) { Label2->Caption="Unregistered user "; Label3->Caption="Please register "; } }
Естественно, в начале текста модуля следует вставить строку:
#include <registry.hpp>
Обратите внимание: в С++ и Pascal форматы строк, содержащих имена разделов реестра, разные.
В результате наша заставка на этапе выполнения в случае наличия в реестре соответствующего раздела приобретет следующий вид:

Рис. 5. Заставка с именем пользователя и серийным номером на этапе выполнения
Если же в реестре нет такого раздела, вместо имени пользователя и серийного номера булет выводиться сообщение о том, что пользователь не зарегистрирован.
Отметим, что при желании можно добавить в этот обработчик события код, анализирующий серийный номер и в случае, если он не удовлетворяет каким-либо заранее заданным требованиям, прекратить выполнение приложения (или запустить его демонстрационную версию).

Заставка с видеофрагментом


Теперь создадим заставку, в которой будет отображаться видеофрагмент (такие заставки нередко используются разработчиками игр и мультимедиа-энциклопедий). С этой целью удалим с формы таймер (он уже не нужен, так как время существования заставки теперь определяется длительностью видеофрагмента), и уничтожим связанный с ним обработчик события OnTimer. Удалим также компонент TImage. Затем добавим компонент TPanel (он будет использоваться в качестве "экрана" для воспроизведения видеофрагмента, и компонент TMediaPlayer. Свойство Visible этого компонента установим равным false, свойство AutoOpen - раным true, а в качестве значения свойства FileName выберем имя файла, содержащего видеофрагмент.

Рис. 6. Форма заставки, воспроизводящей видеофрагмент

Изменим обработчик события, связанного с созданием формы. Теперь мы должны инициировать воспроизведение фрагмента, используя в качестве экрана компонент TPanel. При этом компонент TPanel должен располагаться равноудаленно от левого и правого краев формы и иметь размеры, соответствующие размерам кадров видеофрагмента (их можно узнать, обратившись к свойству DisplayRect компонента TMediaPlayer). В случае Delphi этот обработчик события теперь выглядит так:

procedure TForm2.FormCreate(Sender: TObject); var key,ser,usr:string; Reg:TRegistry; begin try Reg:=TRegistry.Create; key:='SOFTWARE\Splash Screen Company\MyApp\1.0'; Reg.Rootkey:= HKEY_LOCAL_MACHINE; Reg.OpenKey(key,false); usr:=Reg.ReadString('Name'); ser:=Reg.ReadString('Serial'); Label2.Caption:='Пользователь: '+usr; Label3.Caption:='Серийный номер: '+ser; Reg.CloseKey; Reg.Destroy; except Label2.Caption:='Unregistered user'; Label3.Caption:='Please register'; end; MediaPlayer1.DisplayRect:=Rect(Panel1.Left,Panel1.Top,0,0); Panel1.Width:=MediaPlayer1.DisplayRect.Right; Panel1.Height:=MediaPlayer1.Displayrect.Bottom; Panel1.Left:=Round((Form2.Width-Panel1.Width)*0.5); MediaPlayer1.Open; MediaPlayer1.Play; end;

В случае C++Builder соответствующий код имеет вид:

void __fastcall TForm2::FormCreate(TObject *Sender) {TRegistry *Reg; {TRegistry *Reg; try { Reg=new TRegistry; Reg->RootKey=HKEY_LOCAL_MACHINE; AnsiString key="SOFTWARE\\Splash Screen Company\\MyApp\\1.0"; Reg->OpenKey(key,false); AnsiString usr=Reg->ReadString("Name"); AnsiString ser=Reg->ReadString("Serial"); Label2->Caption="Пользователь: "+usr; Label3->Caption="Серийный номер: "+ser; Reg->CloseKey(); Reg->Free(); } catch (...) { Label2->Caption="Unregistered user "; Label3->Caption="Please register "; } MediaPlayer1->DisplayRect=Rect(Panel1->Left,Panel1->Top,0,0); Panel1->Width=MediaPlayer1->DisplayRect.Right; Panel1->Height=MediaPlayer1->DisplayRect.Bottom; Panel1->Left=INT((Form2->Width-Panel1->Width)*0.5); MediaPlayer1->Open(); MediaPlayer1->Play(); }


Обратите внимание: при вычислении координат левого верхнего угла компонента TPanel в приведенных выше обработчиках событий используются функции округления действительных чисел, которые имеют разные названия для Pascal и C++.
Так как мы ликвидировали обработчик события, связанный с компонентом TTimer, наша форма теперь не закроется автоматически. Очевидно, что закрытие формы в данном случае должно быть связано с моментом окончания воспроизведения видеофрагмента. С этой целью воспользуемся событием OnNotify компонента TMediaPlayer, в котором проанализируем его свойство Mode - в момент окончания воспроизведения оно изменится. Соответствующий код для Delphi имеет вид:
procedure TForm2.MediaPlayer1Notify(Sender: TObject); begin Mediaplayer1.Notify := True; if Mediaplayer1.Mode=mpStopped then Form2.Close; end;
Код для C++Builder имеет при этом следующий вид:
void __fastcall TForm1::MediaPlayer1Notify(TObject *Sender) { MediaPlayer1->Notify= true; if (MediaPlayer1->Mode==mpStopped) { Form2->Close(); } }
На этапе выполнения мы получим заставку примерно следующего вида:

Рис. 7. Заставка, воспроизводящая видеофрагмент

Определение наличия дискеты или компакт-диска в дисководе


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

Для получения информации о диске можно использовать функцию Windows API GetVolumeInformation. Эта функция возвращает информацию об имени и метке тома, а также характеристиках его файловой системы.

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

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

Рис. 1. Форма приложения для получения информации о дисках

Создадим обработчик события OnChange компонента TDriveComboBox. В случае C++Builder он выглядит следующим образом:

void __fastcall TForm1::DriveComboBox1Change(TObject *Sender) { DWORD VolSN,MaxCompLen,FSFlags, FC,SPC,BPS,NC; char VolName[255],FSName[100]; AnsiString s=UpperCase(DriveComboBox1->Drive)+":\\"; Label1->Caption=s; if (GetVolumeInformation(s.c_str(),VolName,255,&VolSN,&MaxCompLen, &FSFlags,FSName,100)) { Label2->Caption="Имя тома: "+AnsiString(VolName); Label3->Caption="Файловая система: "+AnsiString(FSName); if (GetDiskFreeSpace(s.c_str(), &SPC,&FC,&BPS,&NC)) { Label4->Caption="Свободных байт: "+IntToStr(SPC*FC*BPS); } else { Label4->Caption=""; } } else { Label2->Caption="Диск не готов"; Label3->Caption=""; Label4->Caption=""; } }


В случае Delphi этот же обработчик события выглядит так:
procedure TForm1.DriveComboBox1Change(Sender: TObject); var VolName: array [0..255] of char; FSName : array [0..100] of char; VolSN: PDWORD; MaxCompLen,FSFlags: Cardinal; FC,SPC,BPS,NC:DWORD; s: string; begin VolSN:=nil; s:=UpperCase(DriveComboBox1.Drive)+':\'; Label1.Caption:=s; if (GetVolumeInformation(PChar(s),VolName,255, VolSN, MaxCompLen, FSFlags,FSName,100)) then begin Label2.Caption:='Имя тома: '+AnsiString(VolName); Label3.Caption:='Файловая система: '+AnsiString(FSName) ; if GetDiskFreeSpace(PChar(s),SPC,BPS,FC,NC) then Label4.Caption:='Свободных байт: '+IntToStr(SPC*FC*BPS) else Label4.Caption:='' end else begin Label2.Caption:='Диск не готов'; Label3.Caption:=''; Label4.Caption:='' end; end;
Запустив приложение, мы можем выбирать устройство из списка доступных устройств и получать информацию о готовности устройства, метке тома и числе свободных байт.

Рис. 2. Приложение для вывода информации о дисках на этапе выполнения

Заставка с видеофрагментом, отображаемым на полном экране


И, наконец, создадим заставку, воспроизводящую видеофагмент на всем экране. В этом случае надписи на форме и данные из реестра уже не нужны, но при этом нужно растянуть на весь экран и форму, и компонент TPanel. Поэтому обработчик события, связанного с созданием формы, несколько упрощается. В случае Delphi он теперь имеет вид:

procedure TForm2.FormCreate(Sender: TObject); begin Form2.Left:=0; Form2.Top:=0; Form2.Width:=Screen.Width; Form2.Height:=Screen.Height; Panel1.Align:=alClient; Mediaplayer1.Open; MediaPlayer1.DisplayRect:=Rect(0,0,Screen.Width,Screen.Height); MediaPlayer1.Play; end;

Соответственно в случае C++Builder он имеет вид:

void __fastcall TForm2::FormCreate(TObject *Sender) { Form1->Left=0; Form1->Top=0; Form1->Width=Screen->Width; Form1->Height=Screen->Height; Panel1->Align=alClient; MediaPlayer1->Open(); MediaPlayer1->DisplayRect=Rect(0,0,Screen->Width,Screen->Height); MediaPlayer1->Play(); }

Теперь форма, панель и сам видеофрагмент занимают весь экран, закрывая собой все окна, включая панель задач Windows. Тем не менее рекомендуется для полностью корректного воспроизведения подобного рода приложений установить свойство FormStyle данной формы равным fsStayOnTop.

Рис. 8. Заставка, воспроизводящая видеофрагмент на полном экране



Получение сведений о видеорежиме


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

Обычно для этой цели используется функция Windows API GetDeviceCaps, использующая в качестве входного параметра контекст графического устройства (каковым является экран). В данном примере мы воспользуемся обычной формой VCL для доступа к этому контексту, используя ее свойство Canvas.

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

Создадим простейшее приложение для определения этих параметров. С этой целью поместим на форму кнопку и несколько меток.

Рис. 3. Форма приложения для определения цветового разрешения экрана.

Создадим обработчик события, связанный с нажатием на кнопку. В C++Builder он выглядит следующим образом:

void __fastcall TForm1::Button1Click(TObject *Sender) { long int TC,BPP,CP,VR; BPP=GetDeviceCaps(Form1->Canvas->Handle,BITSPIXEL); CP=GetDeviceCaps(Form1->Canvas->Handle,PLANES); TC=pow(pow(2,BPP),CP); Label1->Caption="Бит на пиксел: "+IntToStr(BPP); if (TC<3) { Label2->Caption="Монохромный дисплей "; } else { Label2->Caption="Число цветов: "+IntToStr(TC); } ; }

Аналогичный код для Delphi имеет вид:

procedure TForm1.Button1Click(Sender: TObject); Var TC,BPP,CP,VR,x: integer; x2:real; begin BPP:=GetDeviceCaps(Form1.Canvas.Handle,BITSPIXEL); CP:=GetDeviceCaps(Form1.Canvas.Handle,PLANES); x2:=intpower(2,BPP); x2:=intpower(x2,CP); TC:=round(x2); Label1.Caption:='Бит на пиксел: '+IntToStr(BPP); if (TC<3) then Label2.Caption:='Монохромный дисплей ' else Label2.Caption:='Число цветов: '+IntToStr(TC); end;

Запущенное приложение выглядит примерно так:

Рис. 4. Приложение для определения цветового разрешения экрана.

Помимо цветового разрешения и числа поддерживаемых цветов c помощью функции GetDeviceCaps можно получить много другой информации о графических устройствах, например, о разрешении в пикселах на дюйм, числе поддерживаемых шрифтов и др. При работе в Windows NT можно даже определить частоту развертки монитора.



Получение сведений о частоте развертки монитора


Модифицируем ранее созданный пример, добавив определение типа операционной системы, и, если это Windows NT - определение частоты развертки. С этой целью необходимо в первую очередь определить тип операционной системы.

Как определить тип операционной системы? Для этой цели воспользуемся функцией Windows API GetVersionEx. Но, прежде чем ее вызвать, следует создать структуру типа OSVERSIONINFO, содержащую сведения об имении версии операционной системы (подробное описание этой структуры можно найти в справке по функциям Windows API, поставляемой с обоими средствами разработки), и корректно определить ее размер с помощью функции sizeof.

Поместим еще одну метку на форму нашего примера и изменим обработчик события, связанного с нажатием на кнопку: В случае C++Builder он теперь имеет вид:

void __fastcall TForm1::Button1Click(TObject *Sender) { long int TC,BPP,CP,VR; OSVERSIONINFO OV; BPP=GetDeviceCaps(Form1->Canvas->Handle,BITSPIXEL); CP=GetDeviceCaps(Form1->Canvas->Handle,PLANES); TC=pow(pow(2,BPP),CP); Label1->Caption="Бит на пиксел: "+IntToStr(BPP); if (TC<3) { Label2->Caption="Монохромный дисплей "; } else { Label2->Caption="Число цветов: "+IntToStr(TC); } ; OV.dwOSVersionInfoSize=sizeof(OV); GetVersionEx(&OV); if (OV.dwPlatformId==VER_PLATFORM_WIN32_NT) { VR=GetDeviceCaps(Form1->Canvas->Handle,VREFRESH); Label3->Caption="Частота развертки: "+IntToStr(VR)+" Гц"; } else { Label3->Caption="Частота развертки не определется в данной ОС"; } }

Соответствующий код для Delphi выглядит так:

procedure TForm1.Button1Click(Sender: TObject); Var TC,BPP,CP,VR,x: integer; x1,x2:real; OV: TOSVersionInfo; begin BPP:=GetDeviceCaps(Form1.Canvas.Handle,BITSPIXEL); CP:=GetDeviceCaps(Form1.Canvas.Handle,PLANES); x2:=intpower(2,BPP); x2:=intpower(x2,CP); TC:=round(x2); Label1.Caption:='Бит на пиксел: '+IntToStr(BPP); if (TC<3) then Label2.Caption:='Монохромный дисплей ' else Label2.Caption:='Число цветов: '+IntToStr(TC); OV.dwOSVersionInfoSize:=SizeOf(OV); GetVersionEx(OV); if OV.dwPlatformID=VER_PLATFORM_WIN32_NT then begin VR:=GetDeviceCaps(Form1.Canvas.Handle,VREFRESH); Label3.Caption:='Частота развертки: '+IntToStr(VR)+' Гц'; end else Label3.Caption:='Частота развертки не определется в данной ОС'; end; end.

Если запустить это приложение в операционной системе Windows NT, оно отобразит сведения о частоте развертки:

Рис. 5. Приложение для определения частоты развертки под управлением Windows NT

Рис. 6. То же приложение под управлением Windows 95



Создание "маленького" приложения с базой данных без использования BDE


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

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

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

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

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

Иными словами, установка BDE влечет за собой создаиие дистрибутива и риск быть вовлеченным в коллизии, связанные с заменой этой библиотеки более старой версией или замены файлов конфигурации BDE. В сложных многопользовательских системах с серверными базами данных и с большим количеством таблиц такой риск может быть сведен к минимуму, так как в силу высокой стоимости таких систем на подобных предприятиях обычно устанавливаются жесткие корпоративные правила для пользователей, в том числе запрещающие несанкционированную установку программного обеспечения без ведома системного адлминистратора. Но если приложение невелико и использует одну-две таблицы, оно не должно требовать наличия жестких корпоративных правил его использования. Отметим также, что в этом случае сама библиотека BDE может существенно превышать по объему и приложение, и поставляемые с ним данные.

По этим причинам разработчики таких приложений нередко изобретают свои форматы данных, дабы не использовать BDE вообще, а просто считывать файл с диска.

Как ни парадоксально, разработчики, занимающиеся созданием подобных приложений, даже имея клиент-серверные версии Delphi или C++Builder, просто не обращают внимания на страницу MIDAS палитры компонентов этих средств разработки, считая, что эти компоненты - не для их задач. А ведь именно там и содержится компонент TClientDataSet, позволяющий создать такой файл и использовать его.

Компонент TCLientDataSet обычно используется в "тонких" клиентах серверов доступа к данным. Подобные приложения широко используют кэширование данных в оперативной памяти рабочей станции и нередко учитывают возможность разрыва связи с сервером доступа к данным. Соответственно компонент TCLientDataSet позволяет сохранить содержимое своего кэша в файле и загрузить его оттуда. После этого можно просто забыть о сервере доступа к данным и работать только с этим файлом (его можно даже редактировать). Библиотека BDE при этом не нужна - "тонкие" клиенты ее не используют (в том числе и отчуждаемые "тонкие" клиенты).

Создадим пример такого "маленького" приложения. Но прежде создадим приложение для переноса данных из таблиц в файл, содержащий кэш компонента TCLientDataSet. Для этого создадим новый проект и поместим на его главную форму три компонента TTable, три компонента TDataSourse и один компонент TClientDataSet, один компонент TDBGrid и один компонент TDBNavigator (последние два компонента нужны только для контроля и просмотра данных и, по существу, совершенно не обязательны). Установим следующие свойства этих компонентов:

DataSource

КомпонентСвойствоЗначение
DBGrid1DataSource3
DBNavigator1DataSourceDataSource3
Table1DatabaseNameBCDEMOS (или DBDEMOS)
TableNamecustomer.db
Activetrue
DataSource1DataSetTable1
Table2DatabaseNameBCDEMOS (или DBDEMOS)
TableNameorders.db
IndexFieldNamesCustNo
MasterFieldsCustNo
MasterSourceDataSource1
Activetrue
DataSource2DataSetTable2
Table3DatabaseNameBCDEMOS (или DBDEMOS)
TableNameitems.db
IndexFieldNamesOrderNo
MasterFieldsOrderNo
MasterSourceDataSource2
Activetrue
ClientDataSet1ProviderNameProvider1
Activetrue
DataSource3DataSetClientDataSet1
<


/p>
Далее выберем из контекстного меню компонента ClientDataSet1 опцию Assign Local Data и в появившемся списке выберем Table1:

Рис. 1 Выбор источника данных для заполнения компонента TClientDataSet.
После этого в кэш компонента TCLientDataSet будут загружены данные:

Рис. 2 Главная форма приложения после выбора источника данных для TCLientDataSet .
Теперь из контекстного меню этого же компонента выберем опцию Save To File и в появившейся диалоговой панели открытия файла введем имя файла, в котором будут храниться данные из кэша. Итак, файл с данными готов.
Теперь можно создать наше "маленькое" приложение. Для этого достаточно просто удалить с формы компоненты Table1, Table2, Table3, DataSource1, DataSource2 - они больше не нужны.
Есть две возможности создания таких "маленьких" приложений. Самый простой из них - хранить данные непосредственно в исполняемом файле приложения (если их объем невелик). Для этой цели следует из контекстного меню компонента TClientDataSet выбрать опцию Load From File и выбрать имя файла, сохраненного прежде. Теперь данные из этого файла содержатся в ресурсах формы, в чем можно убедиться, открыв форму в текстовом представлении. Если скомпилировать такое приложение, его можно передать пользователю. Единственное, что требуется добавить в комплект поставки - файл dbclient.dll из каталога Winnt\System32 (или Windows\System).
Еще один вариант - выполнить метод LoadFromFile компонента TClientDataSet в обработчике события OnCreate формы. В этом случае файл с кэшированными данными следует также включить в комплект поставки приложения, и объем его может быть достаточно велик (насколько именно - зависит от ресурсов рабочей станции, на которой используется такое приложение).
Отметим, что Delphi 4 и C++Builder 4 позволяют хранить в таком файле данные из нескольких связанных таблиц (именно это и было нами сделано). Поэтому в полученном наборе данных имеется поле типа TDataSetField, предоставляющее доступ к detail-записям (в нашем случае к записям таблиц orders.db и items.db).

Рис. 3 Приложение, использующее локальную копию данных, на этапе выполнения.
Пользователи Delphi 3 и C++Builder 3 могут создавать несколько компонентов TCLientDataSet и организовывать связь между ними в приложении так же, как и в случае обычных таблиц, используя свойства MasterField и MasterSource.
Отметим, что, помимо настольных приложений, сохраненные в файле кэшированные данные могут быть использованы при создании демо-версий и прототипов клиент-серверных приложений, где затруднена или исключена в силу лицензионных ограничений поставка полноценной версии СУБД.

Как сэкономить место на форме при отображении связанных таблиц


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

Возьмем наше самое первое приложение, содержащее три компонента TTable и компонент TClientDataSet, и добавим в него компонент TDataSetProvider. Установим значение его свойства DataSet равным Table1. Затем свойство ProviderName компонента ClientDataSet1 установим равным DataSetProvider1.

Теперь наше приложение позволяет редактировать данные из всех трех таблиц, при этом интерфейс приложения окажется примерно тем же, что и на предыдущем рисунке. Единственное, о чем дополнительно следует позаботиться, это о пересылке отредактированных данных обратно в исходные таблицы с помощью метода ApplyUpdates компонента TClientDataSet. Обычно для этой цели к форме добавляют какой-либо интерфейсный элемент, инициирующий выполнение этого метода. Иногда этот метод добавляют к обработчику события AfterPost компонента TClientDataSet. В случае C++Builder этот код имеет вид:

void __fastcall TForm1::ClientDataSet1AfterPost(TDataSet *DataSet) { ClientDataSet1->ApplyUpdates(-1); } Соответствующий код для Delphi имеет вид: procedure TForm1.ClientDataSet1AfterPost(DataSet: TDataSet); begin ClientDataSet1.ApplyUpdates(-1); end;

Отметим, однако, что это не самый эффективный способ сохранения отредактированных записей, так как метод Post в данном случае выполняется локально, а метод ApplyUpdates требует обращения к базе данных, и при использовании сетевой СУБД лучше выполнять его не так часто, кaк метод Post.



Как произвести сортировку данных в компоненте TClientDataSet


Для сортировки данных в компоненте TClientDataSet можно использовать свойство IndexFieldNames (точно так же, как и в случае использования компонента TTable). Помимо этого, компонент TClientDataSet обладает методами AddIndex и DeleteIndex. Эти методы позволяют произвести сортировку данных на этапе выполнения.

На форму приложения, содержащую данные из компонента TClientDataSet, добавим компонент TListBox и создадим два обработчика события:

void __fastcall TForm1::FormCreate(TObject *Sender) { ListBox1->Items=ClientDataSet1->FieldList; } //------------------------------------------------------------ void __fastcall TForm1::ListBox1Click(TObject *Sender) { AnsiString fn=ListBox1->Items->Strings[ListBox1->ItemIndex]; TIndexOptions opts; opts << ixCaseInsensitive; ClientDataSet1->AddIndex(fn + "Index", fn , opts, "", "",0); ClientDataSet1->IndexName = fn + "Index"; Edit1->Text=fn; }

Соответствующий код для Delphi имеет вид:

procedure TForm1.FormCreate(Sender: TObject); begin ListBox1.Items:=ClientDataSet1.FieldList; end; procedure TForm1.ListBox1Click(Sender: TObject); var fn:string ; begin fn:= ListBox1.Items.Strings[ListBox1.ItemIndex]; ClientDataSet1.AddIndex(fn + 'Index', fn, [ixCaseInsensitive],'','',0); ClientDataSet1.IndexName := fn + 'Index'; end;

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



Создание универсального


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

При выборе таблицы из списка ее данные отображаются в верхнем из компонентов TDBGrid и заполняют кэш компонента TCLientDataSet, отображаемый в нижнем из компонентов TDBGrid. При нажатии кнопки Save to CDS file появляется диалог сохранения файла, в котором следует ввести имя файла для сохранения таблицы, после чего происходит сохранение содержимого таблицы в файле.

Рис. 5 Приложение для сохранения таблиц в локальных файлах на этапе выполнения.

Отметим, что объем сохраняемых таблиц должен быть разумным - CLientDataSet хранит данные в оперативной памяти рабочей станции.

Исходный текст этого же приложения на Object Pascal читатели могут создать самостоятельно.



Создание универсального инструмента для сохранения содержимого таблиц в локальных файлах


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

С этой целью создадим форму, содержащую два компонента TListBox, два компонента TDBGrid, один компонент TSplitter, два компонента TEdit, две кнопки и один TCheckBox.

Поместим также на форму TSaveDialog, TDatabase, TSession, TTable, TClientDataSet, TDataSetProvider, два компонента TDataSource.

Рис. 4 Форма приложения для сохранения таблиц в локальных файлах.

Установим следующие значения свойств этих компонентов:

КомпонентСвойствоЗначение
Session1SessionNameMySession
Database1SessionNameMySession
LoginPromptfalse
DatabaseNameMyDB
Table1SessionNameMySession
ClientDataSet1ProviderNameDataSetProvider1
Activefalse
DataSource1DataSetTable1
DataSource2DataSetClientDataSet1
DataSetProvider1DataSetTable1
DBGrid1DataSourceDataSource1
DBGrid2DataSourceDataSource2
SaveDialog1 FilterClientDataSetFiles|*.cds|All files|*.*
DefaultExt*.cds

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

//------------------------------------------------------------ #include <vcl.h> #pragma hdrstop #include "uni_cds1.h" //------------------------------------------------------------ #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //------------------------------------------------------------ __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //---------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { TStringList *DBList = new TStringList(); try { Session1->GetAliasNames(DBList); for (int I = 0; I < DBList->Count; I++) ListBox1->Items->Add(DBList->Strings[I]); } __finally { delete DBList; } } //------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { Database1->Connected=false; ClientDataSet1->Close(); Database1->Params->Clear(); Database1->AliasName= ListBox1->Items->Strings[ListBox1->ItemIndex]; Database1->Params->Add("User Name="+Edit1->Text); Database1->Params->Add("Password="+Edit2->Text); TStringList *TabList = new TStringList(); try { Database1->Connected=true; Session1->GetTableNames("MyDB","",!(CheckBox1->Checked),false, TabList) ; ListBox2->Items=TabList; } __finally { delete TabList; } } //----------------------------------------------------------- void __fastcall TForm1::ListBox2Click(TObject *Sender) { Table1->Close(); ClientDataSet1->Close(); Table1->DatabaseName=Database1->DatabaseName ; Table1->TableName= ListBox2->Items->Strings[ListBox2->ItemIndex]; Table1->Open(); ClientDataSet1->Open(); } void __fastcall TForm1::Button2Click(TObject *Sender) { if (SaveDialog1->Execute()) { ClientDataSet1->SaveToFile(SaveDialog1->FileName); } } //-----------------------------------------------------------



Как изменить цвет строки в TDBGrid


Предположим, нам требуется изменить атрибуты текста и фона строки в компоненте TDBGrid, если значение какого-либо поля удовлетворяет заранее заданному условию. Для этой цели принято использовать обработчик события OnDrawColumnCell этого компонента. Отметим, что возможности, предоставляемые при его использовании, весьма разнообразны.

Рассмотрим простейшее приложение с TDBGrid, содержащее один компонент TTable, один компонент TDataSource и один компонент TDBGrid: Установим значения их свойств в соответствии с приведенной ниже таблицей:

КомпонентСвойствоЗначение
Table1DatabaseNameBCDEMOS (или DBDEMOS)
TableNameevents.db
Activetrue
DataSource1DataSetTable1
DBGrid1DataSourceDataSource1

Рис. 1 Тестовое приложение на этапе проектирования

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

Его параметр Rect - структура, описывающая занимаемый ячейкой прямоугольник, параметр Column - колонка DBGrid, в которой следует изменить способ рисования изображения. Для вывода текста используется метод TextOut свойства Canvas компонента TDBGrid.

Предположим, нам нужно изменить цвет текста и фона строки в зависимости от значения какого-либо поля (например, VenueNo). Создадим обработчик события OnDrawColumnCell компонента DBGrid1. В случае C++Builder он имеет вид:

void __fastcall TForm1::DBGrid1DrawColumnCell(TObject *Sender, const TRect &Rect, int DataCol, TColumn *Column, TGridDrawState State) { if (Table1->FieldByName("VenueNo")->Value==1) { DBGrid1->Canvas->Brush->Color=clGreen; DBGrid1->Canvas->Font->Color=clWhite; DBGrid1->Canvas->FillRect(Rect); DBGrid1->Canvas->TextOut(Rect.Left+2,Rect.Top+2,Column->Field->Text); } }

В случае Delphi соответствующий код имеет вид:

procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); begin if (Table1.FieldByName('VenueNo').Value=1) then begin with DBGrid1.Canvas do begin Brush.Color:=clGreen; Font.Color:=clWhite; FillRect(Rect); TextOut(Rect.Left+2,Rect.Top+2,Column.Field.Text); end; end; end;


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

Рис. 2 Изменение цвета фона и шрифта в строках со значением поля VenueNo=1 на этапе выполнения.
При выводе выделенных строк все данные в ячейках оказались выровненными по левому краю. Если мы хотим более корректно отобразить выравнивание текста в колонке, следует слегка модифицировать наш код, учтя значение свойства Alignment текущей (то есть рисуемой в данный момент) колонки:
void __fastcall TForm1::DBGrid1DrawColumnCell(TObject *Sender, const TRect &Rect, int DataCol, TColumn *Column, TGridDrawState State) { if (Table1->FieldByName("VenueNo")->Value==1) { DBGrid1->Canvas->Brush->Color=clGreen; DBGrid1->Canvas->Font->Color=clWhite; DBGrid1->Canvas->FillRect(Rect); if (Column->Alignment==taRightJustify) { DBGrid1->Canvas->TextOut(Rect.Right-2- DBGrid1->Canvas->TextWidth(Column->Field->Text), Rect.Top+2,Column->Field->Text); } else { DBGrid1->Canvas->TextOut(Rect.Left+2,Rect.Top+2,Column->Field->Text); } } }
Соответствующий код для Delphi имеет вид:
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); begin if (Table1.FieldByName('VenueNo').Value=1) then begin with DBGrid1.Canvas do begin Brush.Color:=clGreen; Font.Color:=clWhite; FillRect(Rect); if (Column.Alignment=taRightJustify) then TextOut(Rect.Right-2- TextWidth(Column.Field.Text), Rect.Top+2,Column.Field.Text) else TextOut(Rect.Left+2,Rect.Top+2,Column.Field.Text); end; end; end;
В этом случае выравнивание текста в колонках совпадает с выравниванием столбцов.
Отметим, что величина смещения (в данном случае 2 пиксела), вообще говоря, зависит от гарнитуры и размера шрифта, используемого в данной колонке, и должна подбираться индивидуально.

Рис. 3 Изменение цвета с учетом выравнивания текста в колонках.
Если необходимо отобразить нестандартным образом не всю строку, а только некоторые ячейки, следует проанализировать имя поля, отображаемого в данной колонке, как в приведенном ниже обработчике событий. Пример для C++Builder выглядит так:


void __fastcall TForm1::DBGrid1DrawColumnCell(TObject *Sender, const TRect &Rect, int DataCol, TColumn *Column, TGridDrawState State) { if ((Table1->FieldByName("VenueNo")->Value==1) & (Column->FieldName=="VenueNo") ) { DBGrid1->Canvas->Brush->Color=clGreen; DBGrid1->Canvas->Font->Color=clWhite; DBGrid1->Canvas->FillRect(Rect); DBGrid1->Canvas->TextOut(Rect.Right-2- DBGrid1->Canvas->TextWidth(Column->Field->Text), Rect.Top+2,Column->Field->Text); } }
Соответствующий код для Delphi имеет вид:
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); begin if (Table1.FieldByName('VenueNo').Value=1) and (Column.FieldName='VenueNo') then begin with DBGrid1.Canvas do begin Brush.Color:=clGreen; Font.Color:=clWhite; FillRect(Rect); TextOut(Rect.Right-2- TextWidth(Column.Field.Text), Rect.Top+2,Column.Field.Text) end; end; end;
В результате выделенными оказываются только ячейки, для которых выполняются выбранные нами условия:

Рис. 4 Выделение цветом данных в одной колонке.

Как заменить данные в столбце компонента TDBGrid


Нередко в колонке DBGrid нужно вывести не реальное значение, хранящееся в поле соответствующей таблицы, а другие данные, соответствующие имеющимся (например, символьную строку вместо ее числового кода). В этом случае также используется метод TextOut свойства Canvas компонента TDBGrid:

void __fastcall TForm1::DBGrid1DrawColumnCell(TObject *Sender, const TRect &Rect, int DataCol, TColumn *Column, TGridDrawState State) { if (Column->FieldName=="VenueNo") { DBGrid1->Canvas->Brush->Color=clWhite; DBGrid1->Canvas->FillRect(Rect); if (Table1->FieldByName("VenueNo")->Value==1) { DBGrid1->Canvas->Font->Color=clRed; DBGrid1->Canvas->TextOut(Rect.Right-2- DBGrid1->Canvas->TextWidth("our venue"), Rect.Top+2,"our venue"); } else { DBGrid1->Canvas->TextOut(Rect.Right-2- DBGrid1->Canvas->TextWidth("other venue"), Rect.Top+2,"other venue"); } } }

Соответствующий код для Delphi имеет вид:

procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); begin if (Column.FieldName='VenueNo') then begin with DBGrid1.Canvas do begin Brush.Color:=clWhite; FillRect(Rect); if (Table1.FieldByName('VenueNo').Value=1) then begin Font.Color:=clRed; TextOut(Rect.Right-2- DBGrid1.Canvas.TextWidth('our venue'), Rect.Top+2,'our venue'); end else begin TextOut(Rect.Right-2- DBGrid1.Canvas.TextWidth('other venue'), Rect.Top+2,'other venue'); end; end; end; end;

Рис. 5 Замена данных в колонке другими значениями.

Еще один пример - использование значков из шрифтов Windings или Webdings в качестве подставляемой строки.

void __fastcall TForm1::DBGrid1DrawColumnCell(TObject *Sender, const TRect &Rect, int DataCol, TColumn *Column, TGridDrawState State) { if (Column->FieldName=="VenueNo") { DBGrid1->Canvas->Brush->Color=clWhite; DBGrid1->Canvas->FillRect(Rect); DBGrid1->Canvas->Font->Name="Wingdings"; DBGrid1->Canvas->Font->Size=-14; if (Table1->FieldByName("VenueNo")->Value==1) { DBGrid1->Canvas->Font->Color=clRed; DBGrid1->Canvas->TextOut(Rect.Right-2- DBGrid1->Canvas->TextWidth("J"), Rect.Top+1,"J"); } else { DBGrid1->Canvas->Font->Color=clBlack; DBGrid1->Canvas->TextOut(Rect.Right-2- DBGrid1->Canvas->TextWidth("F"), Rect.Top+1,"F") ; } } }

Соответствующий код для Delphi имеет вид:

procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); begin if (Column.FieldName='VenueNo') then begin with DBGrid1.Canvas do begin Brush.Color:=clWhite; FillRect(Rect); Font.Name:='Wingdings'; Font.Size:=-14; if (Table1.FieldByName('VenueNo').Value=1) then begin Font.Color:=clRed; TextOut(Rect.Right-2- DBGrid1.Canvas.TextWidth('J'), Rect.Top+1,'J'); end else begin Font.Color:=clBlack; TextOut(Rect.Right-2- DBGrid1.Canvas.TextWidth('F'), Rect.Top+1,'F'); end; end; end; end;

Рис. 6 Использование символов из шрифта Windings для выделения нужных значений в колонке.



Как поместить графическое изображение в TDBGrid


Использование свойства Canvas компонента TDBGrid в методе OnDrawColumnCell позволяет не только выводить в ячейке текст методом TextOut, но и размещать в ячейках графические изображения. В этом случае используется метод Draw свойства Canvas.

Модифицируем наш пример, добавив на форму компонент TImageList и поместив в него несколько изображений.

Рис. 7 Компонент TImageLis с изображениями, помещаемыми в TDBGrid

Модифицируем код нашего приложения:

void __fastcall TForm1::DBGrid1DrawColumnCell(TObject *Sender, const TRect &Rect, int DataCol, TColumn *Column, TGridDrawState State) { Graphics::TBitmap *Im1; Im1= new Graphics::TBitmap; if (Column->FieldName=="VenueNo") { DBGrid1->Canvas->Brush->Color=clWhite; DBGrid1->Canvas->FillRect(Rect); if (Table1->FieldByName("VenueNo")->Value==1) { ImageList1->GetBitmap(0,Im1); } else { ImageList1->GetBitmap(2,Im1); } DBGrid1->Canvas->Draw((Rect.Left+Rect.Right-Im1->Width)/2,Rect.Top,Im1); } }

Соответствующий код для Delphi имеет вид:

procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); var Im1: TBitmap; begin Im1:=TBitmap.Create; if (Column.FieldName='VenueNo' ) then begin with DBGrid1.Canvas do begin Brush.Color:=clWhite; FillRect(Rect); if (Table1.FieldByName('VenueNo').Value=1) then begin ImageList1.GetBitmap(0,Im1); end else begin ImageList1.GetBitmap(2,Im1); end; Draw(round((Rect.Left+Rect.Right-Im1.Width)/2),Rect.Top,Im1); end; end; end;

Теперь в TDBGrid в колонке VenueNo находятся графические изображения.

Рис. 8 Вывод графических изображений в колонке



в процессе работы приложения следует


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

Для перезагрузки операционной системы можно использовать функцию Windows API ExitWindowsEx, первый из параметров которой определяет способ завершения работы Windows 95/98.

Создадим простейший пример, использующий эту функцию. Для этого на форму приложения поместим три кнопки:



Рис. 1. Форма приложения для инициирования перезагрузки Windows.

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

void __fastcall TForm1::Button1Click(TObject *Sender) { ExitWindowsEx(EWX_LOGOFF,0); } //---------------------------------------------------------------------------

void __fastcall TForm1::Button2Click(TObject *Sender) { ExitWindowsEx(EWX_SHUTDOWN,0); } //---------------------------------------------------------------------------

void __fastcall TForm1::Button3Click(TObject *Sender) { ExitWindowsEx(EWX_REBOOT,0); } //---------------------------------------------------------------------------

В случае Delphi эти же обработчики событий выглядят так:

procedure TForm1.Button1Click(Sender: TObject); begin ExitWindowsEx(EWX_LOGOFF,0); end;

procedure TForm1.Button2Click(Sender: TObject); begin ExitWindowsEx(EWX_SHUTDOWN,0); end;

procedure TForm1.Button3Click(Sender: TObject); begin ExitWindowsEx(EWX_REBOOT,0); end;

Запустив приложение, мы можем выбрать способ завершения работы Windows либо сеанса пользователя Windows.

Отметим, что завершение работы или сеанса пользователя вWindows 95/98 действительно может быть осуществлено всеми тремя способами. В Windows NT же в общем случае работоспособен только первый обработчик события. Дело в том, что в соответствии с соображениями безопасности не всякое приложение имеет право произвести завершение работы Windows NT (например, хотя бы потому, что под управлением этой операционной системы могут выполняться различные сетевые и иные сервисы, жизненно важные для функционирования других компьютеров сети). Если же тем не менее возникнет необходимость произвести завершение работы Windows NT, а не просто завершение сеанса пользователя, следует воспользоваться функцией Windows API AdjustTokenPrivileges для предоставления данному приложению права остановки или перезагрузки операционной системы.


Отключение хранителя экрана


Нередко разработчики приложений, выполняющих какие-либо длительные действия (например, обработку большого количества данных или пересылку данных на удаленный компьютер с помощью низкоскоростной линии связи), сталкиваются с проблемами, создаваемыми так называемыми хранителями экрана (screen savers). Хранители экрана представляют собой, по существу, такие же Windows-приложения, нередко потребляющие немалое количество самых разнообразных ресурсов, за исключением собственно люминофора, которым покрыта внутренняя поверхность экрана монитора. Поэтому при запущенном хранителе экрана в общем случае производительность любого другого приложения, выполняющего какие-либо длительные действия, может существенно снизиться.

Чтобы понять, как предотвратить запуск хранителя экрана, следует вспомнить, что операционные системы с графическим пользовательским интерфейсом (в том числе Windows) основаны на посылке и обработке сообщений, а также на обработке событий. Windows постоянно обрабатывает разнообразные события, в том числе события, связанные с перемещением мыши или нажатием клавиш. Хранитель экрана запускается операционной системой, если по истечении заранее заданного (то есть установленного пользователем операционной системы) промежутка времени не происходило двух указанных выше типов событий. Для этого операционная система посылает активному приложению сообщение WM_SYSCOMMAND с параметром WPARAM, равным SC_SCREENSAVE. Именно это сообщение и следует перехватить в нашем приложении.

Перехват сообщения можно осуществить, создав обработчик события OnMessage объекта TApplication, наступающего при получении приложением очередного сообщения Windows. Соответствующая функция имеет два параметра: имя сообщения и параметр handled, указывающий, обрабатывается ли такое сообщение данным приложением. Если он установлен равным false, сообщение обрабатывается операционной системой. В противном случае оно обрабатывается приложением.

Создадим простейший пример, иллюстрирующий перехват такого сообщения. Для этого поместим на форму радиогруппу из двух элементов:



Рис. 2. Форма приложения для определения цветового разрешения экрана.
Теперь создадим функцию-член класса TForm1 (в C++Builder 4 для этого есть специальный эксперт, встроенный в Code Explorer, однако не возбраняется создать определение функции вручную):

Рис. 3. Создание новой функции-члена класса TForm1 с помощью C++Builder 4.
Создадим текст этой функции, а также обработчик события, связанный с созданием формы приложения и указывающий, какая функция должна вызываться при получении сообщения данным приложением:
void __fastcall TForm1::ProcessMess(TMsg & msg, bool &handled) { //TODO: Add your source code here if (msg.message==WM_SYSCOMMAND && msg.wParam==SC_SCREENSAVE && RadioGroup1->ItemIndex==1) handled=true; else handled=false;
} //---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender) { Application->OnMessage = ProcessMess;
}
Если функция создана вручную, следует не забыть создать ее определение в h-файле:
class TForm1 : public TForm { __published: // IDE-managed Components TRadioGroup *RadioGroup1; void __fastcall FormCreate(TObject *Sender); private: void __fastcall ProcessMess(TMsg & msg, bool &handled);
public: // User declarations __fastcall TForm1(TComponent* Owner); };
Аналогичный код для Delphi имеет вид:
unit Unit1;
interface
uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls;
type TForm1 = class(TForm) RadioGroup1: TRadioGroup; procedure FormCreate(Sender: TObject); private procedure ProcessMess(var msg:TMsg; var handled:boolean) ; { Private declarations } public { Public declarations } end;
var Form1: TForm1;
implementation
{$R *.DFM}
{ TForm1 } procedure TForm1.ProcessMess(var msg: TMsg; var handled: boolean); begin if (msg.message=WM_SYSCOMMAND) and (msg.wParam=SC_SCREENSAVE) and (RadioGroup1.ItemIndex=1) then handled:=true else handled:=false;
end;
procedure TForm1.FormCreate(Sender: TObject); begin Application.OnMessage:=ProcessMess; end;end.
Если созданное таким образом приложение активно, хранитель экрана будет запускаться (или не запускаться) в зависимости от того, какая опция выбрана в радиогруппе.

Получение сведений об операционной системе


Нередко при разработке приложений требуется определить, какова версия операционной системы, где расположены системные каталоги, каковы значения переменных окружения. Для этой цели используются функции Windows API, такие как GetVersionEx (определение типа операционной системы), GetComputerName(определение сетевого имени компьютера), GetUserName (определение имени пользователя), GetSystemDirectory (определение местоположения системного каталога), GetWindowsDirectory (определение каталога Windows), ExpandEnvironmentStrings (определение значений переменных окружения), а также структуры Windows (например, OSVERSIONINFO). Подробные сведения об этих функциях и структурах содержатся в справке по Windows API, входящей в комплект поставки как C++Builder, так и Delphi.

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

void __fastcall TForm1::Button1Click(TObject *Sender) { LPTSTR lpszSystemInfo; DWORD cchBuff = 256; TCHAR tchBuffer2[1000]; DWORD dwResult; TCHAR tchBuffer[1000]; int nSize;

lpszSystemInfo = tchBuffer2;

GetComputerName(lpszSystemInfo, &cchBuff); nSize = sprintf(tchBuffer, "Компьютер: %s", lpszSystemInfo); Label1->Caption=AnsiString(tchBuffer);

GetUserName(lpszSystemInfo, &cchBuff); nSize = sprintf(tchBuffer, "Пользователь: %s", lpszSystemInfo); Label2->Caption=AnsiString(tchBuffer);

nSize = GetSystemDirectory(lpszSystemInfo, MAX_PATH); nSize = sprintf(tchBuffer, "System directory: %s", lpszSystemInfo); Label3->Caption=AnsiString(tchBuffer);

nSize = GetWindowsDirectory(lpszSystemInfo, MAX_PATH); nSize = sprintf(tchBuffer, "Windows directory: %s", lpszSystemInfo); Label4->Caption=AnsiString(tchBuffer); Label5->Caption="Переменные окружения :";


dwResult = ExpandEnvironmentStrings("OS=%OS% ", lpszSystemInfo, 500); Label6->Caption=AnsiString(lpszSystemInfo);
dwResult = ExpandEnvironmentStrings("INCLUDE=%INCLUDE%",lpszSystemInfo,500); Label7->Caption=AnsiString(lpszSystemInfo);
dwResult = ExpandEnvironmentStrings("CLASSPATH=%CLASSPATH%",lpszSystemInfo,500); Label8->Caption=AnsiString(lpszSystemInfo);
dwResult = ExpandEnvironmentStrings("ComSpec=%ComSpec%",lpszSystemInfo,500); Label9->Caption=AnsiString(lpszSystemInfo);
OSVERSIONINFO osvi; char szVersion [80];
memset(&osvi, 0, sizeof(OSVERSIONINFO)); osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); GetVersionEx (&osvi);
if (osvi.dwPlatformId == VER_PLATFORM_WIN32s) wsprintf (szVersion, "Microsoft Win32s %d.%d (Build %d)", osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
else if (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) wsprintf (szVersion, "Microsoft Windows 95 %d.%d (Build %d)",
osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
else if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) wsprintf (szVersion, "Microsoft Windows NT %d.%d (Build %d)",
osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); Label10->Caption="Операционная система: "+AnsiString(szVersion);
}
Соответствующий код для Delphi выглядит так:
unit Unit1;
interface
uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type TForm1 = class(TForm) Button1: TButton; Label1: TLabel; Label2: TLabel; Label3: TLabel; Label4: TLabel; Label5: TLabel; Label6: TLabel; Label7: TLabel; Label8: TLabel; Label9: TLabel; Label10: TLabel; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end;
var Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject); var osvi: TOSVersionInfo; si: TSystemInfo; s:string; dwResult, nsize: Cardinal; ch : array[0..1000] of Char; begin


GetComputerName(ch, nsize); Label1.Caption:='Компьютер: '+ch;
GetUserName(ch, nsize); Label2.Caption:='Пользователь: '+ch;
nSize := GetSystemDirectory(ch, MAX_PATH); Label3.Caption:='System directory: '+ch;
nSize := GetWindowsDirectory(ch, MAX_PATH); Label4.Caption:='Windows directory: '+ch;
Label5.Caption:='Переменные окружения :';
dwResult:= ExpandEnvironmentStrings('OS=%OS% ', ch, 500); Label6.Caption:=ch;
dwResult:= ExpandEnvironmentStrings('INCLUDE=%INCLUDE%',ch,500); Label7.Caption:=ch;
dwResult:= ExpandEnvironmentStrings('CLASSPATH=%CLASSPATH%',ch,500); Label8.Caption:=ch;
dwResult := ExpandEnvironmentStrings('ComSpec=%ComSpec%',ch,500); Label9.Caption:=ch;
osvi.dwOSVersionInfoSize := sizeof (OSVERSIONINFO); GetVersionEx (osvi);
if (osvi.dwPlatformId = VER_PLATFORM_WIN32s) then s:= 'Microsoft Win32s '+IntToStr(osvi.dwMajorVersion)+'.'+ IntToStr(osvi.dwMinorVersion) +' Build '+ IntToStr(osvi.dwBuildNumber);
if (osvi.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS) then s:= 'Microsoft Windows 95 '+IntToStr(osvi.dwMajorVersion)+'.'+ IntToStr(osvi.dwMinorVersion) +' Build '+ IntToStr(osvi.dwBuildNumber);
if (osvi.dwPlatformId = VER_PLATFORM_WIN32_NT) then s:= 'Microsoft Windows NT '+IntToStr(osvi.dwMajorVersion)+'.'+ IntToStr(osvi.dwMinorVersion) +' Build '+ IntToStr(osvi.dwBuildNumber);
Label10.Caption:=' Операционная система: '+s;
end;
end.
Если запустить это приложение, оно отобразит сведения о системе и переменных окружения:

Рис. 4. Приложение для вывода сведений о системе
Отметим, что в настоящее время имеется немалое количество свободно распространяемых, условно-бесплатных и коммерческих компонентов для Delphi и C++Builder, предназначенных для получения системной информации (некоторые из них содержатся на компакт-диске в данном номере журнала). Все они так или иначе используют те же самые функции Windows API для установки или считывания значений своих свойств или выполнения методов, и основное их преимущество заключается главным образом в том, что они избавляют программиста от необходимости создания кода, подобного приведенному выше. Соответственно при разработке приложений можно использовать как непосредственно функции Windows API, как в приведенных выше примерах, так и готовые компоненты.