Статьи по Delphi (005)…
Как определить изменение фокуса строки в TDBGrid.
| CPU not found. Press any key to continue |
procedure Form1.DSrc1DataChange(Sender: TObject; Field: TField); |
Поля TTable могут использоваться для сравнения текущих выбранных строк полей (ключ) с вашими требованиями. С той же целью может быть использовано и свойство TDBGrid Fields. Для примера:
if tbl1.Fields[0].AsString = 'BlaBlaBla' then ... |
Или.
if dbGrid1.Fields[I].IsNull then ... |
procedure Form1.dbGrid1DrawDataCell(Sender: TObject; Rect: TRect; Field: TField; State: TGridDrawState); |
Чтобы иметь доступ к индексу строки или колонки выбранной ячейки, вы должны унаследовать ваш класс от TCustomGrid и опубликать свойства времени выполнения Row и Col (текущие строка и колонка сетки, не таблицы!!):
type TSampleDBGrid = class(TCustomGrid) public property Col; property Row; end; |
В соответствующей процедуре или обработчике события осуществите приведение типа:
var G: TSampleDBGrid; begin G := TSampleDBGrid(myDBGrid1); if G.Row = I then ... if G.Col = J then ... |
Дело в том, что TDBGrid является потомком TCustomGrid, который имеет несколько свойств, содержащих координаты сетки, но это не опубликовано в TDBGrid.
...из чего я могу заключить, что вы должны это сделать программным путем. Подразумеваем, что сетка уже существует, и у вас есть доступ к основной таблице TTable:
grid.colcount := dbGrid.fieldcount; table.first; row := 0; while not table.eof do begin grid.rowcount := row + 1; for i := 0 to grid.colcount-1 do grid.cells[i,row] := dbGrid.fields[i].asString; table.next; inc (row); end; |
Посмотрите на следующий код, он может вам помочь. Он берет у элемента управления свойсто 'Name' и помещает его в свойство 'Caption' метки.
unit Unit1;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Label1: TLabel;
Edit1: TEdit;
Edit2: TEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure Edit1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure Edit2MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
close;
end;
procedure TForm1.Edit1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Label1.Caption := TEdit(Sender).Name;
end;
procedure TForm1.Edit2MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Label1.Caption := TEdit(Sender).Name;
end;
end.
|
Определить номер строки в TDBGrid.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
{
I did not found property to know the rownumber of a TDBGrid,
so I will try this in the OnDrawColumnCell of a DBGrid:
}
procedure TformX.DBGridXDrawColumnCell(Sender: TObject; const
Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState);
var
RowNo : Integer;
begin
RowNo := Rect.Bottom div TStringGrid(DBGridX).DefaultRowHeight
//so, RowNo has the Row to be draw in the moment
...
end;
DBGrid - сохранение конфигурации.
| Заходит програмер в свой темный подъезд и слышит, что в темноте кто-то шебуршится. - Бї@дь надо-было сохраниться! - запоздало подумал он. |
Нижеописанный код создает, сохраняет и загружает конфигурационный файл и изменяет размеры столбцов таблицы DBGRID.
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, Grids, DBGrids, Db, DBTables, StdCtrls, IniFiles;
...
procedure TMainForm.NewIni(const NomeIni: string);
var
F: System.Text;
i: Byte;
begin
System.Assign(F, NomeIni);
System.ReWrite(F);
System.WriteLn(F, '[Campi_Ordine]');
for i:=1 to Table1.FieldCount do
System.WriteLn(F, 'Campo',i,'=',Table1.Fields[i-1].FieldName);
System.WriteLn(F, '');
System.WriteLn(F, '[Campi_Size]');
for i:=1 to Table1.FieldCount do
System.WriteLn(F, 'Campo',i,'=',Table1.Fields[i-1].DisplayWidth);
System.Close(F);
end;
procedure TMainForm.SaveIni(const FN: string);
var
Ini: TIniFile;
i: Integer;
S : string;
begin
NewIni(FN);
Ini := TIniFile.Create(FN);
with Ini do begin
for i:=1 to Table1.FieldCount do
begin
S:= Table1.Fields[i-1].FieldName;
WriteString('Campi_Ordine', 'Campo'+IntToStr(i),
Table1.Fields[i-1].FieldName);
WriteInteger('Campi_Size', 'Campo'+IntToStr(i),
Table1.Fields[i-1].DisplayWidth);
end;
end;
Ini.Free;
end;
procedure TMainForm.LoadIni(const FN: string);
var
Ini: TIniFile;
i: Integer;
j: Longint;
S: string;
function MyReadInteger(const Section, Ident: string): Longint;
begin
result := Ini.ReadInteger(Section, Ident, -1);
if result=-1 then
raise Exception.Create('Errore nel file di configurazione.');
end;
function MyReadString(const Section, Ident: string): string;
begin
result := Ini.ReadString(Section, Ident, '');
if result='' then
raise Exception.Create('Errore nel file di configurazione.');
end;
begin
Ini := TIniFile.Create(FN);
try
with Ini do
begin
for i:=1 to Table1.FieldCount do
begin
S:= MyReadString('Campi_Ordine', 'Campo'+IntToStr(i));
j:= MyReadInteger('Campi_Size', 'Campo'+IntToStr(i));
Table1.FieldByName(S).index := i-1;
Table1.FieldByName(S).DisplayWidth := j;
end;
end;
finally
Ini.Free;
end;
end;
|
DBGrid - выбранные строки.

| Автор: Nancy Reid |
| На собрании перед выпуском Windows'98 в компании Micro$oft участникам был задан странный вопрос: - Если вы зашли на авиалайнер и узнали, что ваша группа программистов была ответственна за составление программы, управляющей полетом вашего авиалайнера, сколько бы из вас немедленно выскочило из самолета? Все подняли руки, кроме одного программиста. Когда его спросили, что бы он сделал, тот ответил, что он бы с удовольствием остался на борту. Когда его спросили почему, он ответил: - А чего мне бояться полета? С нашей программой этот самолет даже не доехал бы до взлетной полосы. |
Существует одно свойство, не упомянутое в файлах помощи (оплошность, господа программисты из Borland), имеющее имя SelectedRows, и вот как его можно использовать:
procedure TfrmGrid.Button1Click(Sender: TObject);
var
i: integer;
begin
For i := 0 to DBGrid1.SelectedRows.Count - 1 do
begin
Table1.GoToBookmark(TBookmark(DBGrid1.SelectedRows[i]));
Table1.Delete;
end;
end;
|
Выделить все строки в DBGrid.
| Мужик заходит в лавочку, торгующую легальным софтом. Подходит к одному из продавцов и тихо-тихо говорит: - Мне Windows'2000, пожалуйста. Тут все, и покупатели и продавцы, замирают и пристально смотрят на мужика. Мужик не выдерживает такой паузы, оборачивается ко всем и кричит: - Да! Да! Тр@хаться я иду! |
function GridSelectAll(Grid: TDBGrid): Longint;
begin
Result := 0;
Grid.SelectedRows.Clear;
with Grid.Datasource.DataSet do
begin
First;
DisableControls;
try
while not EOF do
begin
Grid.SelectedRows.CurrentRowSelected := True;
inc(Result);
Next;
end;
finally
EnableControls;
end;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
GridSelectAll(DBGrid1);
end;
|
Как выделить окошко DBGrid другим цветом.
Оформил: DeeCo
Необходимо обработать событие "OnDrawCellData". Например для того, чтобы пометить выбранное окошко красным фоном, необходимо сделать следующее:
procedure TForm1.DBGridDrawDataCell(Sender: TObject; const Rect: TRect;
Field: TField; State: TGridDrawState);
begin
if gdFocused in State then
with (Sender as TDBGrid).Canvas do
begin
Brush.Color := clRed;
FillRect(Rect);
TextOut(Rect.Left, Rect.Top, Field.AsString);
end;
end;
Решение проблемы передачи фокуса TDBGrid.
| - Что для геймера Жизнь? - Два компакта с "Half-life"! |
Очевидно, DBGrid имеет некоторые проблемы с управлением фокусом, если он находится на дочерней MDI-форме. Эта проблема решена в приведенном ниже наследнике TDBGrid, в котором обрабатываются мышиные сообщения и выясняется когда фокус должен быть передан сетке. Наследник создан в виде компонента, который легко устанавливается в Палитру Компонентов. Примечание: код адаптирован для всех версий Delphi. Проблемы могут быть в Delphi 2 и 3, если вы забудете заменить устаревшие в этих версиях модули "winprocs" и "wintypes" на "windows.".
unit FixedDBGrid;
interface
uses
Winprocs, wintypes, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs, Grids, DBGrids;
type
TFixedDBGrid = class(TDBGrid)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
procedure WMRButtonDown(var Message: TWMRButtonDown); message
WM_RBUTTONDOWN;
procedure WMLButtonDown(var Message: TWMLButtonDown); message
WM_LBUTTONDOWN;
published
{ Published declarations }
end;
procedure Register;
implementation
procedure TFixedDBGrid.WMRButtonDown(var Message: TWMRButtonDown);
begin
winprocs.SetFocus(handle); {помните, что winprocs относится только к Delphi 1!}
inherited;
end;
procedure TFixedDBGrid.WMLButtonDown(var Message: TWMLButtonDown);
begin
winprocs.SetFocus(handle); {помните, что winprocs относится только к Delphi 1!}
inherited;
end;
procedure Register;
begin
RegisterComponents('Samples', [TFixedDBGrid]);
end;
end.
|
Сортировка колонок в DBGrid.
| Звонок Пользователя в службу поддержки. - Алло, это служба поддержки??? - Да. - У меня проблема, мой модем не хочет работать!!! - Давайте пропишем Вам строчку инициализации. - Давайте. - AT&F1&M5 - Спасибо!!!Через 5 минут. - У меня все еще не работает!!! - Да??? Ну давайте запишите другую - Я слушаю - ATS10=100 - Спасибо! Проходит еще 5 минут... - У меня Модем все еще плохо работает!!! - Ну что, пишите еще одну строчку... - Слушаю. - AT&F&B1&A0&S0=90 - Спасибо. Прошло еще 5 минут... - Алло!!! У меня сгорел модем!!! - Жаль, а то у меня еще столько в запасе строчек инициализаций осталось... |
Сделаем так, чтобы компонент THeaderControl выглядел похожим на заголовок компонента DBGrid. Произведем необходимые манипулиции в момент создания формы. Дважды щелкните на событии OnCreate формы и введите следующий код:
procedure TForm1.FormCreate(Sender: TObject);
var
TheCap: string;
TheWidth, a: Integer;
begin
DBGrid1.Options := DBGrid1.Options - [dgTitles];
HeaderControl1.Sections.Add;
HeaderControl1.Sections.Items[0].Width := 12;
Table1.Exclusive := True;
Table1.Active := True;
for a := 1 to DBGrid1.Columns.Count do
begin
with DBGrid1.Columns.Items[a - 1] do
begin
TheCap := Title.Caption;
TheWidth := Width;
end;
with HeaderControl1.Sections do
begin
Add;
Items[a].Text := TheCap;
Items[a].Width := TheWidth + 1;
Items[a].MinWidth := TheWidth + 1;
Items[a].MaxWidth := TheWidth + 1;
end;
try
Table1.AddIndex(TheCap, TheCap, []);
except
HeaderControl1.Sections.Items[a].AllowClick := False;
end;
end;
Table1.Active := False;
Table1.Exclusive := False;
Table1.Active := True;
end;
|
Последний шаг. При щелчке на HeaderControl нам необходимо включить правильный индекс таблицы. Создадим обработчик события OnSectionClick компонента HeaderControl как показано ниже:
procedure TForm1.HeaderControl1SectionClick( HeaderControl: THeaderControl; Section: THeaderSection); begin Table1.IndexName := Section.Text; end; |
Это улучшает гибкость. Изменения указаны наклонным курсивом.
procedure TfrmDoc.FormCreate(Sender: TObject);
var
TheCap: string;
TheFn: string;
TheWidth: Integer;
a: Integer;
begin
Dbgrid1.Options := DBGrid1.Options - [DGTitles];
Headercontrol1.sections.Add;
Headercontrol1.Sections.Items[0].Width := 12;
for a := 1 to DBGRID1.Columns.Count do
begin
with DBGrid1.Columns.Items[a - 1] do
begin
TheFn := FieldName;
TheCap := Title.Caption;
TheWidth := Width;
end;
with Headercontrol1.Sections do
begin
Add;
Items[a].Text := TheCap;
Items[a].Width := TheWidth + 1;
Items[a].MinWidth := TheWidth + 1;
Items[a].MaxWidth := TheWidth + 1;
end; (* WITH Headercontrol1.Sections *)
try (* except *)
{ Используем индексы с тем же именем, что и имя поля }
(DataSource1.Dataset as TTable).IndexName := TheFn;
{ Пробуем задать имя индекса }
except
HeaderControl1.Sections.Items[a].AllowClick := False; { Индекс недоступен }
end; (* EXCEPT *)
end; (* FOR *)
end; (* PROCEDURE *)
|
Используйте свойство FieldName компонента DBGrid для задания индекса с тем же именем, что и имя поля.
procedure TfrmDoc.HeaderControl1SectionClick(HeaderControl: THeaderControl; Section: THeaderSection); begin (DataSource1.Dataset as TTable).IndexName := DBGrid1.Columns.Items[ Section.Index - 1 ].FieldName; end; |
Копирование информации из DBGrid-а в Clipboard.
| Автор: Беличенко Б. |
| Утро. 7.30. Звонит будильник. Программер медленно отрывает голову от подушки. Кидает будильник об стену и нервно шепчет: - I'm is bypassing my startup files! И опять засыпает. |
Простая процедура копирования информации из DBGrid-а в Clipboard может существенно облегчить жизнь при реализации требований экспорта выборок данных во внешние приемники. Удобнее вызов "прицепить" к контекстному меню грида.
unit UnGridToClb;
interface
uses
Windows, SysUtils, Classes, Dialogs,
Grids, DBGrids, Db, DBTables, ClipBrd;
procedure CopyGRDToClb(dbg: TDBGrid);
// Копирует DBGrid в буфер обмена,
// после чего данные отлично переносятся
// как в простой текстовый редактор, так и в Excell
implementation
procedure CopyGRDToClb(dbg: TDBGrid);
var
bm: TBookMark;
pch, pch1: PChar;
s, s2: string;
i, j: integer;
begin
s := '';
for j := 0 to dbg.Columns.Count - 1 do
s := s + dbg.Columns.Items[j].Title.Caption + #9;
s := s + #13 + #10;
if not dbg.DataSource.DataSet.active then
begin
ShowMessage('Нет выборки!!!');
Exit;
end;
try
dbg.Visible := False; //Делаем грид невидимым, чтобы не тратилось время
//на его перерисовку при прокрутке DataSet - просто и
//эффективно
bm := dbg.DataSource.DataSet.GetBookmark; // для того чтобы не
// потерять текущую запись
dbg.DataSource.DataSet.First;
while not dbg.DataSource.DataSet.EOF do
begin
s2 := '';
for j := 0 to dbg.Columns.Count - 1 do
begin
s2 := s2 + dbg.Columns.Items[j].Field.AsString + #9;
end;
s := s + s2 + #13 + #10;
dbg.DataSource.DataSet.Next;
end;
//Переключаем клавиатуру "в русский режим",
//иначе - проблемы с кодировкой
GetMem(pch, 100);
GetMem(pch1, 100);
GetKeyboardLayoutName(pch);
StrCopy(pch1, pch);
while pch <> '00000419' do
begin
ActivateKeyboardLayout(HKL_NEXT, 0);
GetKeyboardLayoutName(pch);
if strComp(pch, pch1) = 0 then
//Круг замкнулся - нет такого языка '00000419'
StrCopy(pch, '00000419');
end;
clipboard.AsText := s; //Данные - в буфер!!!
//Возвращаем режим клавиатуры
while strComp(pch, pch1) <> 0 do
begin
ActivateKeyboardLayout(HKL_NEXT, 0);
GetKeyboardLayoutName(pch);
end;
FreeMem(pch);
FreeMem(pch1);
dbg.DataSource.DataSet.GotoBookmark(bm);
//ShowMessage('Данные успешно скопированы в буфер обмена.');
finally
dbg.Visible := True;
end;
end;
end.
|
Как из DBGrid перенести данные в существующий Excel файл.
| Автор: xUSSR |
| Во время раскопок в Америке были найдены кусочки медной проволоки. После долгих исследований было выяснено, что это - остатки древней кабельной сети, которой пользовались индейцы. Во время раскопок в Германии были найдены кусочки стекла. После долгих исследований было выяснено, что это - остатки оптоволоконной сети древних нибелунгов. В России долго копали, копали, копали... и не нашли ничего. Так было выяснено, что древние славяне пользовались спутниковой связью. |
//Если не обезательно из DBGrid-а... то вот пример из Query ...
// Если есть вопросы то ¹ ACQ 134087719
// имя файла шаблона и откуда (можно TTable)
Procedure SendtoExcel(ShFile: String; Querys: TQuery);
Var
ExcelApp, Workbook, Range, Cell1, Cell2, ArrayData: Variant;
BeginCol, BeginRow, j: integer;
RowCount, ColCount: Integer;
Begin
BeginCol := 1;
BeginRow := 3;
// Размеры выводимого массива данных
RowCount := Querys.RecordCount;
ColCount := Querys.FieldDefs.Count;
// Создание Excel
ExcelApp := CreateOleObject('Excel.Application');
// Отключаем реакцию Excel на события, чтобы ускорить вывод информации
ExcelApp.Application.EnableEvents := false;
// Создаем Книгу (Workbook)
// Если заполняем шаблон, то
Workbook := ExcelApp.WorkBooks.Add(ShFile);
ArrayData := VarArrayCreate([1, RowCount, 1, ColCount], varVariant);
// Заполняем массив
Querys.DisableControls;
Querys.First;
While Not Querys.eof Do
Begin
For J := 1 To Querys.FieldDefs.Count Do
Begin
ArrayData[Querys.RecNo, J] :=
Querys.FieldbyName(Querys.FieldDefs.Items[j - 1].DisplayName).value;
End;
Querys.Next;
End;
Querys.EnableControls;
// Левая верхняя ячейка области, в которую будем выводить данные
Cell1 := WorkBook.WorkSheets[1].Cells[BeginRow, BeginCol];
// Правая нижняя ячейка области, в которую будем выводить данные
Cell2 := WorkBook.WorkSheets[1].Cells[BeginRow + RowCount - 1,
BeginCol + ColCount - 1];
// Область, в которую будем выводить данные
Range := WorkBook.WorkSheets[1].Range[Cell1, Cell2];
// А вот и сам вывод данных
// Намного быстрее поячеечного присвоения
Range.Value := ArrayData;
// Делаем Excel видимым
ExcelApp.Visible := True;
End;
|
Экспорт из TDBGrid в Excel без OLE.
Автор: http://www.swissdelphicenter.ch
{
Exporting a DBGrid to excel without OLE
I develop software and about 95% of my work deals with databases.
I enjoied the advantages of using Microsoft Excel in my projects
in order to make reports but recently I decided to convert myself
to the free OpenOffice suite.
I faced with the problem of exporting data to Excel without having
Office installed on my computer.
The first solution was to create directly an Excel format compatible file:
this solution is about 50 times faster than the OLE solution but there
is a problem: the output file is not compatible with OpenOffice.
I wanted a solution which was compatible with each "DataSet";
at the same time I wanted to export only the dataset data present in
a DBGrid and not all the "DataSet".
Finally I obtained this solution which satisfied my requirements.
I hope that it will be usefull for you too.
First of all you must import the ADOX type library
which will be used to create the Excel file and its
internal structure: in the Delphi IDE:
1)Project->Import Type Library:
2)Select "Microsoft ADO Ext. for DDL and Security"
3)Uncheck "Generate component wrapper" at the bottom
4)Rename the class names (TTable, TColumn, TIndex, TKey, TGroup, TUser, TCatalog) in
(TXTable, TXColumn, TXIndex, TXKey, TXGroup, TXUser, TXCatalog)
in order to avoid conflicts with the already present TTable component.
5)Select the Unit dir name and press "Create Unit".
It will be created a file named AOX_TLB.
Include ADOX_TLB in the "uses" directive inside the file in which you want
to use ADOX functionality.
That is all. Let's go now with the implementation:
}
unit DBGridExportToExcel;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, StdCtrls, ComCtrls, DB, IniFiles, Buttons, dbgrids, ADOX_TLB, ADODB;
type
TScrollEvents = class
BeforeScroll_Event: TDataSetNotifyEvent;
AfterScroll_Event: TDataSetNotifyEvent;
AutoCalcFields_Property: Boolean;
end;
procedure DisableDependencies(DataSet: TDataSet; var ScrollEvents:
TScrollEvents);
procedure EnableDependencies(DataSet: TDataSet; ScrollEvents: TScrollEvents);
procedure DBGridToExcelADO(DBGrid: TDBGrid; FileName: string; SheetName:
string);
implementation
//Support procedures: I made that in order to increase speed in
//the process of scanning large amounts
//of records in a dataset
//we make a call to the "DisableControls" procedure and then disable the "BeforeScroll" and
//"AfterScroll" events and the "AutoCalcFields" property.
procedure DisableDependencies(DataSet: TDataSet; var ScrollEvents:
TScrollEvents);
begin
with DataSet do
begin
DisableControls;
ScrollEvents := TScrollEvents.Create();
with ScrollEvents do
begin
BeforeScroll_Event := BeforeScroll;
AfterScroll_Event := AfterScroll;
AutoCalcFields_Property := AutoCalcFields;
BeforeScroll := nil;
AfterScroll := nil;
AutoCalcFields := False;
end;
end;
end;
//we make a call to the "EnableControls" procedure and then restore
// the "BeforeScroll" and "AfterScroll" events and the "AutoCalcFields" property.
procedure EnableDependencies(DataSet: TDataSet; ScrollEvents: TScrollEvents);
begin
with DataSet do
begin
EnableControls;
with ScrollEvents do
begin
BeforeScroll := BeforeScroll_Event;
AfterScroll := AfterScroll_Event;
AutoCalcFields := AutoCalcFields_Property;
end;
end;
end;
//This is the procedure which make the work:
procedure DBGridToExcelADO(DBGrid: TDBGrid; FileName: string; SheetName:
string);
var
cat: _Catalog;
tbl: _Table;
col: _Column;
i: integer;
ADOConnection: TADOConnection;
ADOQuery: TADOQuery;
ScrollEvents: TScrollEvents;
SavePlace: TBookmark;
begin
//
//WorkBook creation (database)
cat := CoCatalog.Create;
cat._Set_ActiveConnection('Provider=Microsoft.Jet.OLEDB.4.0; Data Source=' +
FileName + ';Extended Properties=Excel 8.0');
//WorkSheet creation (table)
tbl := CoTable.Create;
tbl.Set_Name(SheetName);
//Columns creation (fields)
DBGrid.DataSource.DataSet.First;
with DBGrid.Columns do
begin
for i := 0 to Count - 1 do
if Items[i].Visible then
begin
col := nil;
col := CoColumn.Create;
with col do
begin
Set_Name(Items[i].Title.Caption);
Set_Type_(adVarWChar);
end;
//add column to table
tbl.Columns.Append(col, adVarWChar, 20);
end;
end;
//add table to database
cat.Tables.Append(tbl);
col := nil;
tbl := nil;
cat := nil;
//exporting
ADOConnection := TADOConnection.Create(nil);
ADOConnection.LoginPrompt := False;
ADOConnection.ConnectionString :=
'Provider=Microsoft.Jet.OLEDB.4.0; Data Source=' + FileName +
';Extended Properties=Excel 8.0';
ADOQuery := TADOQuery.Create(nil);
ADOQuery.Connection := ADOConnection;
ADOQuery.SQL.Text := 'Select * from [' + SheetName + '$]';
ADOQuery.Open;
DisableDependencies(DBGrid.DataSource.DataSet, ScrollEvents);
SavePlace := DBGrid.DataSource.DataSet.GetBookmark;
try
with DBGrid.DataSource.DataSet do
begin
First;
while not Eof do
begin
ADOQuery.Append;
with DBGrid.Columns do
begin
ADOQuery.Edit;
for i := 0 to Count - 1 do
if Items[i].Visible then
begin
ADOQuery.FieldByName(Items[i].Title.Caption).AsString :=
FieldByName(Items[i].FieldName).AsString;
end;
ADOQuery.Post;
end;
Next;
end;
end;
finally
DBGrid.DataSource.DataSet.GotoBookmark(SavePlace);
DBGrid.DataSource.DataSet.FreeBookmark(SavePlace);
EnableDependencies(DBGrid.DataSource.DataSet, ScrollEvents);
ADOQuery.Close;
ADOConnection.Close;
ADOQuery.Free;
ADOConnection.Free;
end;
end;
end.
Экспортировать DBGrid в HTML.
| Автор: Тенцер А.Л. |
| Звонит юзер оператору АТС: - Девушка! Мне тут какой-то совершенно дикий счет пришел за услуги связи... - Ваш номер? - 555-55-55 - Так... Все верно. Это счет за секс-услуги по телефону. - Да ты че! Я такой гадостью сроду не занимался! - Как? А неделю назад вы звонили Васе Пупкину по телефону 444-44-44 и пытались настроить полуось? |
type
TGridToHTMLOption = (ghWithHeaders);
TGridToHTMLOptions = set of TGridToHTMLOption;
function DBGridToHTML(Grid : TDBGrid;
ExportOptions: TGridToHTMLOptions): String;
const
HTMLStart =
'< !DOCTYPE HTML PUBLIC " -//W3C//DTD HTML 4.0 Transitional//EN" > '#13
+
'< HTML> '#13 +
'< HEAD> < META http-equiv=Content-Type content=" text/html;
charset=windows-1251" > '#13 +
'< STYLE> '#13 +
'BODY {'#13 +
' BACKGROUND: white;'#13 +
' COLOR: black;'#13 +
' FONT-FAMILY: arial;'#13 +
' FONT-SIZE: 8pt;'#13 +
' VERTICAL-ALIGN: top'#13 +
'}'#13 +
'TABLE {'#13 +
' BACKGROUND: white;'#13 +
' BORDER-BOTTOM: silver 0px solid;'#13 +
' BORDER-LEFT: silver 1px solid;'#13 +
' BORDER-RIGHT: silver 0px solid;'#13 +
' BORDER-TOP: silver 1px solid;'#13 +
' FONT-FAMILY: arial;'#13 +
' FONT-SIZE: 8pt;'#13 +
' FONT-WEIGHT: normal;'#13 +
'}'#13 +
'TD {'#13 +
' BORDER-BOTTOM: silver 1px solid;'#13 +
' BORDER-LEFT: silver 0px solid;'#13 +
' BORDER-RIGHT: silver 1px solid;'#13 +
' BORDER-TOP: silver 0px solid;'#13 +
' VERTICAL-ALIGN: top;'#13 +
' TEXT-ALIGN: left;'#13 +
'}'#13 +
'TD.grid {'#13 +
' TEXT-ALIGN: left;'#13 +
'}'#13 +
'TD.gridr {'#13 +
' TEXT-ALIGN: right;'#13 +
'}'#13 +
'TD.gridc {'#13 +
' TEXT-ALIGN: center;'#13 +
'}'#13 +
'TH {'#13 +
' BACKGROUND: silver;'#13 +
' BORDER-BOTTOM: gray 1px solid;'#13 +
' BORDER-LEFT: gray 0px solid;'#13 +
' BORDER-RIGHT: gray 1px solid;'#13 +
' BORDER-TOP: gray 0px solid;'#13 +
' FONT-WEIGHT: bold;'#13 +
'}'#13 +
'TH.grid {'#13 +
' TEXT-ALIGN: left;'#13 +
'}'#13 +
'TH.gridr {'#13 +
' TEXT-ALIGN: right;'#13 +
'}'#13 +
'TH.gridc {'#13 +
' TEXT-ALIGN: center;'#13 +
'}'#13 +
'< /STYLE> '#13 +
'< TITLE> Печать таблицы< /TITLE> '#13 +
'< /HEAD> '#13 +
'< BODY> '#13;
HTMLEnd = '< /BODY> < /HTML> ';
TableStart = '< TABLE WIDTH=" 100%" CELLSPACING=0 CELLPADDING=1> '#13;
TableEnd = '< /TABLE> '#13;
HeaderRowStart = '< TR> '#13;
HeaderRowEnd = '< /TR> '#13;
BodyRowStart = '< TR> '#13;
BodyRowEnd = '< /TR> '#13;
const
StyleNames: array [TAlignment] of String = ('grid', 'gridr',
'gridc');
function TD(Column: TColumn; IsTitle: Boolean; Widht: Integer):
String;
var
S: String;
Align: TAlignment;
Tag: String;
begin
if IsTitle then begin
Tag := 'TH';
Align := Column.Title.Alignment;
S := StyleNames[Align];
end else begin
Tag := 'TD';
Align := Column.Alignment;
if Align = taLeftJustify then begin
if (Column.Field is TBCDField) or
(Column.Field is TCurrencyField) then
Align := taRightJustify;
if (Column.Field is TBooleanField) then
Align := taCenter;
end;
S := StyleNames[Align];
if (Column.Field is TBCDField) or (Column.Field is
TIntegerField) then
S := S + ' NOWRAP'
end;
if Widht > 0 then
S := S + Format(' WIDTH=" %d%%" ', [Widht]);
Result := '< ' + Tag + ' class=' + S + '> ';
if IsTitle then begin
S := Column.Title.Caption
end else begin
if Column.Field is TBooleanField then
with TBooleanField(Column.Field) do begin
if Length(DisplayValues) = 0 then begin
if AsBoolean then
S := 'да'
else
S := 'нет';
end else
S := Column.Field.DisplayText;
end else
S := Column.Field.DisplayText;
end;
if Length(Trim(S)) = 0 then
S := ' ';
Result := Result + S + '< /' + Tag + '> '#13;
end;
var
BM : String;
I : Integer;
Widhts: array of Integer;
TotalWidht: Integer;
begin
Result := '';
with Grid do begin
if Assigned(DataSource) and
Assigned(DataSource.DataSet) and
DataSource.DataSet.Active then
with DataSource.DataSet do begin
DisableControls;
BM := BookMark;
SetLength(Widhts, Columns.Count);
TotalWidht := 0;
for I := 0 to Pred(Columns.Count) do begin
if Assigned(Columns[I].Field) then begin
Widhts[I] := Columns[I].Width;
Inc(TotalWidht, Widhts[I]);
end;
end;
for I := 0 to High(Widhts) do begin
Widhts[I] := Widhts[I] * 100 div TotalWidht;
end;
Result := HTMLStart;
Result := Result + TableStart;
if (ghWithHeaders in ExportOptions) then begin
Result := Result + HeaderRowStart;
for I := 0 to Pred(Columns.Count) do begin
if Assigned(Columns.Items[I].Field) then begin
Result := Result + TD(Columns.Items[I], TRUE, Widhts[I]);
end;
end;
Result := Result + HeaderRowEnd;
end;
First;
while not Eof do begin
Result := Result + BodyRowStart;
for I := 0 to Pred( Columns.Count ) do begin
if Assigned(Columns.Items[I].Field) then begin
Result := Result + TD(Columns.Items[I], FALSE,
-1{Integer(Widhts[Index]});
end;
end;
Result := Result + BodyRowEnd;
Next;
end;
Result := Result + TableEnd + HTMLEnd;
BookMark := BM;
EnableControls;
end;
end;
end;
|
Перемещение по таблице с помощью вертикальной полосы прокрутки.
| Автор: Reinhard Kalinke |
| Компьютерный магазин. Заходит покупатель - толстый упакованный армянин. АРМЯНИН: День добрый! ПРОДАВЕЦ: Здравствуйте! А: Компьютеры есть хорошие? П: Есть, вот модель - Аквариус. А: А сколько у него памяти? П: 4,3 гига винт, 32 метра димм, 4 метра видео, 512 килов кэш. А: И сколько это вместе? |
В DBGRID.PAS измените две следующих процедуры:
procedure TCustomDBGrid.UpdateScrollBar;
var
Pos: Integer;
mPos, mMax: longint;
begin
if FDatalink.Active and HandleAllocated then
with FDatalink.DataSet do
begin
UpdateCursorPos;
if (DBIGetSeqNo(Handle, mPos) = DBIERR_NONE) then
begin
mMax := RecordCount;
while mMax > 1000 do
begin
mMax := mMax div 10;
mPos := mPos div 10;
end;
SetScrollRange(Self.Handle, SB_VERT, 1, mMax, False);
end
else
begin
if BOF then
mPos := 0
else if EOF then
mPos := 4
else
mPos := 2;
SetScrollRange(Self.Handle, SB_VERT, 0, 4, False);
end; (**)
if GetScrollPos(Self.Handle, SB_VERT) <> mPos then
SetScrollPos(Self.Handle, SB_VERT, mPos, True);
end;
end;
procedure TCustomDBGrid.WMVScroll(var Message: TWMVScroll);
var
mMin, mMax: integer;
RecCount, RecNo, NewRecNo: longint;
begin
if not AcquireFocus then
Exit;
if FDatalink.Active then
with Message, FDataLink.DataSet, FDatalink do
case ScrollCode of
SB_LINEUP: MoveBy(-ActiveRecord - 1);
SB_LINEDOWN: MoveBy(RecordCount - ActiveRecord);
SB_PAGEUP: MoveBy(-VisibleRowCount);
SB_PAGEDOWN: MoveBy(VisibleRowCount);
SB_THUMBPOSITION:
if (DBIGetSeqNo(Handle, RecNo) = DBIERR_NONE) then
begin
GetScrollRange(self.Handle, SB_VERT, mMin, mMax);
NewRecNo := Pos * (FDataLink.DataSet.RecordCount div mMax);
MoveBy(NewRecNo - RecNo);
end
else
case Pos of
0: First;
1: MoveBy(-VisibleRowCount);
2: Exit;
3: MoveBy(VisibleRowCount);
4: Last;
end;
SB_BOTTOM: Last;
SB_TOP: First;
end;
end;
|
P.S. Пока вы ковыряетесь в DBGRIDS.PAS: найдите и замените TitleColor на FixedColor в TCustomDBGrid.Create и в TCustomDBGrid.DrawCell. Значение свойства FixedColor влияет на показ заголовков колонок, и они будут выводится как и ожидалось.
Корректная работа при работе с колесиком мышки в TDBGrid.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
{....}
public
procedure AppMessage(var Msg: TMsg; var Handled: Boolean);
{....}
procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean);
var
i: SmallInt;
begin
{Mouse wheel behaves strangely with dgbgrids - this proc sorts this out}
if Msg.message = WM_MOUSEWHEEL then
begin
Msg.message := WM_KEYDOWN;
Msg.lParam := 0;
i := HiWord(Msg.wParam);
if i > 0 then
Msg.wParam := VK_UP
else
Msg.wParam := VK_DOWN;
Handled := False;
end;
end;
// Und im Projekt Source:
// And in the project source:
{....}
Application.OnMessage := Form1.AppMessage;
{....}
DBGrid компонент, который отображает разными цветами удалённые, обновлённые и добавленные записи.
| "...Для продолжения установки Win'2000 вставьте 45234-ю дискету..." |
unit atcDBGrid;
(*
(c) Aveen Tech
2001 - 2002
FileName: atcDBGrid.pas
Version Date Author Comment
1.0 13/06/2000 Majid Vafai Jahan Create.
OVERVIEW
- This grid is inherited from DBGrid and add some required functionality to it.
Functionality:
- Record type are all records that may be modified, unmodified, inserted, deleted.
- Coloring according to Record type.
- show selected Record Type.
*)
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Grids, DBGrids, dbTables, db;
const
AlignFlags : array [TAlignment] of Integer =
( DT_LEFT or DT_WORDBREAK or DT_EXPANDTABS or DT_NOPREFIX,
DT_RIGHT or DT_WORDBREAK or DT_EXPANDTABS or DT_NOPREFIX,
DT_CENTER or DT_WORDBREAK or DT_EXPANDTABS or DT_NOPREFIX );
RTL: array [Boolean] of Integer = (0, DT_RTLREADING);
type
TCachedShow = (csModify, csUnModify, csRemoved, csInserted, csAll, csNormal);
TatcDBGrid = class(TDBGrid)
private
FCachedShow: TCachedShow;
FModifiedColor: TColor;
FInsertedColor: TColor;
FDeletedColor: TColor;
procedure SetCachedShow(const Value: TCachedShow);
protected
procedure DrawDataCell(const Rect: TRect; Field: TField; State: TGridDrawState); override;
procedure DrawColumnCell(const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); override;
public
constructor Create(AOwner: TComponent); override;
published
property atcCachedShow: TCachedShow read FCachedShow write SetCachedShow;
property atcDeletedColor: TColor read FDeletedColor write FDeletedColor;
property atcInsertedColor: TColor read FInsertedColor write FInsertedColor;
property atcModifiedColor: TColor read FModifiedColor write FModifiedColor;
end;
procedure register;
implementation
procedure register;
begin
RegisterComponents('ATC DB Compo', [TatcDBGrid]);
end;
constructor TatcDBGrid.Create(AOwner: TComponent);
(*
Description: Record Type Showing is All except Deletes.
*)
begin
inherited;
FCachedShow := csNormal;
FDeletedColor := clGray;
FInsertedColor := clAqua;
FModifiedColor := clRed;
end;
procedure TatcDBGrid.DrawColumnCell(const Rect: TRect; DataCol: Integer;
Column: TColumn; State: TGridDrawState);
(*
Description: On Drawing Column Color Updated Records.
*)
var
ARect: TRect;
begin
inherited;
if not Assigned(Column.Field) then
exit;
// Copy Rect into Variable.
CopyRect(ARect, Rect);
if Assigned(DataLink) and (DataLink.Active) and (DataLink.DataSet <> nil) then
begin
// если текущая запись изменена
if DataLink.DataSet.UpdateStatus = usModified then
begin
Canvas.Brush.Color := atcModifiedColor;
Canvas.Font.Color := clBlack;
Canvas.FillRect(Rect);
DrawText(Canvas.Handle, PChar(Column.Field.Text), Length(Column.Field.Text), ARect,
AlignFlags[Column.Alignment] or RTL[UseRightToLeftAlignmentForField(Column.Field, Column.Alignment)]);
end
// если текущая запись добавлена.
else
if DataLink.DataSet.UpdateStatus = usInserted then
begin
Canvas.Brush.Color := atcInsertedColor;
Canvas.Font.Color := clBlack;
Canvas.FillRect(Rect);
DrawText(Canvas.Handle, PChar(Column.Field.Text), Length(Column.Field.Text), ARect,
AlignFlags[Column.Alignment] or RTL[UseRightToLeftAlignmentForField(Column.Field, Column.Alignment)]);
end
// если текущая запись удалена.
else
if DataLink.DataSet.UpdateStatus = usDeleted then
begin
Canvas.Brush.Color := atcDeletedColor;
Canvas.Font.Color := clWhite;
Canvas.FillRect(Rect);
DrawText(Canvas.Handle, PChar(Column.Field.Text), Length(Column.Field.Text), ARect,
AlignFlags[Column.Alignment] or RTL[UseRightToLeftAlignmentForField(Column.Field, Column.Alignment)]);
end;
end;
end;
procedure TatcDBGrid.DrawDataCell(const Rect: TRect; Field: TField;
State: TGridDrawState);
(*
Описание: Рисуем ячейки
*)
var
ARect: TRect;
begin
inherited;
CopyRect(ARect, Rect);
if Assigned(DataLink) and (DataLink.Active) and (DataLink.DataSet <> nil) then
begin
// если текущая запись изменена
if DataLink.DataSet.UpdateStatus = usModified then
begin
Canvas.Brush.Color := clRed;
Canvas.Font.Color := clBlack;
Canvas.FillRect(Rect);
DrawText(Canvas.Handle, PChar(Field.Text), Length(Field.Text), ARect,
AlignFlags[Field.Alignment] or RTL[UseRightToLeftAlignmentForField(Field, Field.Alignment)]);
end
// если текущая запись добавлена.
else
if DataLink.DataSet.UpdateStatus = usInserted then
begin
Canvas.Brush.Color := clAqua;
Canvas.Font.Color := clBlack;
Canvas.FillRect(Rect);
DrawText(Canvas.Handle, PChar(Field.Text), Length(Field.Text), ARect,
AlignFlags[Field.Alignment] or RTL[UseRightToLeftAlignmentForField(Field, Field.Alignment)]);
end
// если текущая запись удалена.
else
if DataLink.DataSet.UpdateStatus = usDeleted then
begin
Canvas.Brush.Color := clGray;
Canvas.Font.Color := clWhite;
Canvas.FillRect(Rect);
DrawText(Canvas.Handle, PChar(Field.Text), Length(Field.Text), ARect,
AlignFlags[Field.Alignment] or RTL[UseRightToLeftAlignmentForField(Field, Field.Alignment)]);
end;
end;
end;
procedure TatcDBGrid.SetCachedShow(const Value: TCachedShow);
(*
Description: Record type for showing in grid.
Parameters: Value cached record show.
*)
begin
FCachedShow := Value;
if ComponentState = [csDesigning] then
exit;
if not Assigned(DataSource) or not Assigned(DataSource.DataSet) then
exit;
// для показа только выбранного типа записей.
if Assigned(DataLink) and Assigned(DataLink.DataSet) and (DataLink.Active) then
begin
case FCachedShow of
csAll:
TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtModified, rtInserted, rtDeleted, rtUnmodified];
csModify:
TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtModified];
csUnModify:
TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtUnmodified];
csInserted:
TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtInserted];
csRemoved:
TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtDeleted];
csNormal:
TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtModified, rtInserted, rtUnmodified];
end;
end;
end;
end.
|
DBGrid c FixedCols.
| Автор: Cheers |
| Фирма Microsoft выпустила свой новый продукт - Презерватив 2000. Отличие этого продукта от Презерватив 98 состоит в следующем: - он еще легче надевается на "инструмент", чем раньше; - теперь он интегрируется с "инструментом" так, что практически получается одно целое; - и главное, значительно улучшена навигация по пути прохождения, т.е. прекрасно входит и замечательно выходит. К сожалению, не прошло и двух дней со дня выхода нового изделия, как известный хакер NoPreserve нашел дыру в Презерватив 2000. Надо отдать должное Microsoft, которая буквально через день оперативно среагировала на найденный брак, выпустив заплатку, которую каждый пользователь может бесплатно получить по почте со склада фирмы и пришить в нужное место, согласно инструкции. |
Я делаю это таким образом:
unit Fcdgrid;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Grids, DBGrids, DBCtrls, DB, Menus;
type
TFixedColDBGrid = class(TDBGrid)
private
FUserFixedCols: Integer;
protected
procedure LayoutChanged; override;
procedure SetUserFixedCols(I: Integer);
published
property UserFixedCols: Integer read FUserFixedCols write SetUserFixedCols;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Data Controls', [TFixedColDBGrid]);
end;
procedure TFixedColDBGrid.LayoutChanged;
begin
inherited LayoutChanged; { присваиваем FixedCols 1 если индикатор, иначе 0 }
if ((inherited FixedCols + FUserFixedCols) < inherited ColCount) then
inherited FixedCols := (FUserFixedCols + inherited FixedCols);
end;
procedure TFixedColDBGrid.SetUserFixedCols(I: Integer);
begin
FUserFixedCols := I;
LayoutChanged;
end;
end.
|
DBGrid с номером строки.
| Автор: Mark Meyer |
| Другой кибернетик придумал робота-андроида и настроил его на оптимальное выполнение задач. В качестве теста приказал ему принести 5 тысяч долларов. Робот вернулся через 2 минуты со страховкой маленького сына кибернетика. |
Скомпилируйте это, и вы получите новый компонент с нужными свойствами:
unit RowGrid;
interface
uses
WinTypes, WinProcs, Classes, Grids, DBGrids;
type
TRowDBGrid = class(TDBGrid)
public
property Row;
property RowCount;
property VisibleRowCount;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Data Controls', [TRowDBGrid]);
end;
end.
|
{вот небольшой испытательный демо-проект.. мы
поместили на форму нашу сетку-наследницу, 3 компонента
EditBox и поместили следующий код в обработчик события
ondrawdatacell вашего TRowGrid}
procedure TForm1.RowDBGrid1DrawDataCell(Sender: TObject; const Rect:
TRect; Field: TField; State: TGridDrawState);
begin
eb_row.text := inttostr(rowdbgrid1.row);
eb_rowcount.text := inttostr(rowdbgrid1.rowcount);
eb_visiblerowcount.text := inttostr(rowdbgrid1.visiblerowcount);
end;
|
Синтаксис функции DbiAddAlias.
DbiAddAlias (пропущено в BDE.HLP & Руководстве пользователя) Синтаксис:DBIResult DbiAddAlias( [hCfg], pszAliasName, pszDriverType, pszParams, bPersistent );Описание:
DbiAddAlias добавляет псевдоним в конфигурационный файл, связанный с текущим сеансом.Параметры:
hCfg Type: hDBICfg (Входящий)
Для BDE 2.5 данный параметр должен быть NULL. Указывает, что
конфигурация действует в течение текущего сеанса.
Другие значения для BDE 2.5 не поддерживаются.
pszAliasName Type: pCHAR (Входящий)
Указатель на имя псевдонима. Это имя нового псевдонима, который
должен быть добавлен.
pszDriverType Type: pCHAR (Входящий)
Указатель на тип устройства. Это тип устройства для добавляемого
псевдонима. Если данный параметр NULL, псевдоним будет добавлен
для базы данных STANDARD. Если указан szPARADOX, szDBASE или szASCII,
будет добавлена запись в генератор псевдонимов базы данных STANDARD
для указания того, что данный тип будет предпочтительным типом
устройства. Если указано имя устройства, то оно должно существовать
в измененном файле конфигурации.
pszParams Type: pCHAR (Входящий)
Указатель на список дополнительных параметров. Данный список
определяется следующим образом:
"AliasOption: Option Data[;AliasOption: Option Data][;...]".
AliasOption должен соответствовать одному из значений, возвращаемому
DbiOpenCfgInfoList. Для псевдонимов базы данных STANDARD, единственно
необходимый параметр - PATH, остальные игнорируются (без ошибок).
Пример 1: Чтобы установить путь для использования базы данных
STANDARD:
"PATH:c:\mydata"
Пример 2: Чтобы установить имя сервера и имя пользователя для
использования драйвера SQL:
"SERVER NAME: server:/path/database;USER NAME: myname"
bPersistent Type: BOOL (Входящий)
Определяет область действия нового псевдонима.
TRUE Сохраняется в файле конфигурации для будующих сеансов.
FALSE Для использования только в течение текущего сеанса.
Псевдоним удаляется в конце сеанса (или при выходе из
программы).
Использование:
Созданный данной функцией псевдоним будет иметь параметры по умолчанию,
хранимые в списке параметров драйверов "DB OPEN", если только оне не
перекрыты в параметре pszParams. Вы можете использовать
DbiOpenCfgInfoList, чтобы изменить значение по умолчанию после добавления
псевдонима с помощью DbiAddAlias.
Для псевдонимов стандартной базы данных все параметры pszParams за
исключением PATH игнорируются.
Предварительные условия:
DbiInit должен вызываться до вызова DbiAddAlias.
Возвращаемые значения DBIResult:
DBIERR_INVALIDPARAM
Имя псевдонима Null или один из следующих типов pszDriverType:
szASCII, szDBASE, szPARADOX. В последнем случае используйте
NULL pszDriverType для указания на базу данных STANDARD.
DBIERR_NONE
Псевдоним был успешно добавлен.
DBIERR_NAMENOTUNIQUE
Существует другой псевдоним с тем же именем (работает
когда bPersistent равен TRUE).
DBIERR_OBJNOTFOUND
Один (или более) из дополнительных параметров, указанных в
pszParams не соответствуют правильным типам в секции драйверов
конфигурационного файла.
DBIERR_UNKNOWNDRIVER
Имя устройства в конфигурационном файле при сопоставлении с
pszDriverType не найдено.
Смотри также:
DbiInit, DbiOpenCfgInfoList
Пример DbiAddFilter.
Автор: Mark Erbaugh
type
TmyFilter = record
Expr: CANExpr;
Nodes: array[0..2] of CANNode;
literals: array[0..7] of char;
end;
const
myFilter: TMyFilter = (Expr:
(iVer: 1; iTotalSize: sizeof(TMyFilter); iNodes: 3;
iNodeStart: sizeof(CANExpr); iLiteralStart: sizeof(CANExpr) +
3 * sizeof(CANNode));
Nodes:
((canBinary: (nodeClass: nodeBinary; canOP: canEQ;
iOperand1: sizeof(CANNode); iOperand2: 2 * sizeof(CANNode))),
(canField: (nodeClass: nodeField; canOP: canField2;
iFieldNum: 0; iNameOffset: 0)),
(canConst: (nodeClass: nodeConst; canOP: canCONST2;
iType: fldZSTRING; iSize: 3; iOffset: 5)));
literals:
('T', 'Y', 'P', 'E', #0, 'I', 'N', #0));
var
dbResult: DBIResult;
hFilter, hFilter1: hDBIFilter;
begin (* procedure SetupFilter *)
dbResult := DbiAddFilter(tblAP_.Handle, 1, 1,
False, addr(myFilter), nil, hFilter);
dbResult := DbiActivateFilter(tblAP_.Handle, hFilter);
tblAP_.First;
myFilter.nodes[0].canBinary.canOp := canNE;
dbResult := DbiAddFilter(tblAP1_.Handle, 1, 1,
False, addr(myFilter), nil, hFilter1);
dbResult := DbiActivateFilter(tblAP1_.Handle, hFilter1);
tblAP1_.First;
myFilter.nodes[0].canBinary.canOp := canEQ;
end;
|
Также необходимо включить в ваш файл файлы DBITYPES и DBIPROCS, где определены вызываемые функции и константы.
Как поместить картинку из базы данных, например MsSQL, в компонент TIMAGE.
| Общеизвестно, что люди на Земле делятся на нормальнах и тех кто занимается компьтерами. Причем первых с каждым годом становится все меньше и меньше. СТАДИИ ЗАБОЛЕВАИЯ,СИМТОМЫ И МЕТОД ЛЕЧЕНИЯ 1. Первая стадия (легкая) Симптомы: Человек садиться за компутер, включает его, работает и уходит домой в конце рабочего дня, после чего не вспоминает о компутере до следующего утра. Аппетит и сон нормален головные боли и понос отсутствуют. Лечение: В лечении временно не нуждается. 2. Вторая стадия (полулегкая) Симптомы: У больного наблюдается повышенный интерес к компутеру, выражающийся в нездоровом возбуждении, охватывающим его при виде указанного обьекта. Аппетит нормальный. Сон беспокойный со вскрикиваниями и повизгиваниями. Задерживается на работе на 2-3 часа и топчет кнопки. Лечение: Удалить больного от компутера, принимать внутрь медецинский спирт 3 раза в день по 0.5 столовой ложки. Компутерную литературу убрать в недоступное место. С работы встречать. 3. Третия стадия (средней тяжести) Симптомы: Больной задерживается на работе более 4-5 часов после окончания рабочего дня, копит деньги на домашний компутер. В обиходе начинает употреблять компьютерную терминологию и не реагирует на расширенные глаза окружающих. Аппетит повышенный. Сон беспокойный с выкрикиванием компутерных словечек и беспричинным смехом. Приходит в резкое возбуждение при виде компутера или при встречи с больным 3-й стадии и выше. В этом случае болезнь может перейти в 4-ю стадию. Лечение: Больного изолировать от общества и от компутера, деньги отобрать, женить. При бурном поведении и отказе от лечения вводить внутрижелудочно 1-2 ковша водки с портвейном "777", смешаных в пропорции 1:2. Тазик не давать. 4. Четвертая стадия (тяжелая) Симптомы: Больной покупает модем и компутер. Речь изобилует различными компутерными словечками и их сочетаниями. Изобретает новые слова, копит денег на выделенную телефонную линию. Аппетит сильно повышен. Ест любую пищу в любое время суток при ее наличии. Спит 3-4 часа в день, т.к.ночью звонит по модему и при каждои соединении издает вопли, описанные в 3-ем томе книги "Жизнь Животных" (глава 1 поведение самца макаки-резуса в брачный период). Половое влечение сниженно. Рвота, работа, бред и понос отсутсвуют. Лечение: Лечению подлежит только в стационаре. 5.(Безнадежная) Симптомы: Больной заводит у себя BBS, которой уделяет все свободное от звонков и программирования время. Речь невнятная, состоящая на 80 и более процентов компутерного жаргона со спецтерминами. Аппетит и сон отсутствуют. Ест только то что попадает в пределы прямой видимости, независимо от вида и качества продукта. На окружающих обращает внимание только в том случае если они произносят фразы связанные с компутером. Половое влечение отсутствует полностью, т.к. периодически испытывает чувство глубокого удовлетворения от строки на экране "Connect 33600...". держит около компутера ночную вазу и пачку чая, которую забывает развести в воде. Лечение: Лечению не подлежит. |
Image1.Picture.Assing(TBlobField(Query.FieldByName('Pict'))
|
Или, если известно, что эта картинка - Bitmap, то можно.
Image1.Picture.Bitmap.Assing(TBlobField(Query.FieldByName('Pict'))
|
А можно воспользоваться компонентом TDBImage.
Значение DBLookupComboBox.
Я думаю что у меня есть то, что вы хотите. Если вы обратитесь к свойству LookUpValue, то вы получите поле, которое .... ищете.Я надеюсь что помог вам.
unit clookup;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, DB, DBLookup;
type
TDBJustLookupCombo = class(TDBLookupCombo)
private
{ Private declarations }
protected
{ Protected declarations }
function GetLValue: TField;
public
{ Public declarations }
property LookUpValue: TField read GetLValue;
published
{ Published declarations }
end;
TDBJustLookupList = class(TDBLookupList)
private
{ Private declarations }
protected
{ Protected declarations }
function GetLValue: TField;
public
{ Public declarations }
property LookUpValue: TField read GetLValue;
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Data Controls', [TDBJustLookupList,
TDBJustLookupCombo]);
end;
function TDBJustLookupCombo.GetLValue: TField;
begin
Result := LookupSource.DataSet.FieldByName(LookUpField);
end;
function TDBJustLookupList.GetLValue: TField;
begin
Result := LookupSource.DataSet.FieldByName(LookUpField);
end;
end.
|
Обновить DBLookUpComboBox при скролинге таблицы.
Автор: StoneЕсть табличка, к ней привязан TDBLookupComboBox. Значение в нем отображается только после клика мышкой. Как заставить его перерисоваться после изменения текущей записи в таблице, к которой он привязан?
DBLookupComboBox.KeyValue := Table1.FieldByName('Field1').Value;
|
Показываемое DBLookupComboBox значение.
Автор: Michael PrattСледующая строка в обработчике события OnActivate:
DBLookupCombox1.Perform(WM_KeyDown,38,0); |
Заполняет показываемое в dblookupcombox значение. Вы посылаете сообщение индекса непосредственно в dblookupcombox.
Поиск текста в DBMemo.
Попробуйте так: "Подключите" следующую процедуру к событию OnFind для FindDialog. Единственная проблема заключается в том, что в DBMemo я не могу получить выделенный текст, тем не менее в стандартном Memo такой проблемы нет.
procedure TMainForm.FindDialog1Find(Sender: TObject);
var
Buff, P, FT: PChar;
BuffLen: Word;
begin
with Sender as TFindDialog do
begin
GetMem(FT, Length(FindText) + 1);
StrPCopy(FT, FindText);
BuffLen := DBMemo1.GetTextLen + 1;
GetMem(Buff, BuffLen);
DBMemo1.GetTextBuf(Buff, BuffLen);
P := Buff + DBMemo1.SelStart + DBMemo1.SelLength;
P := StrPos(P, FT);
if P = nil then
MessageBeep(0)
else
begin
DBMemo1.SelStart := P - Buff;
DBMemo1.SelLength := Length(FindText);
end;
FreeMem(FT, Length(FindText) + 1);
FreeMem(Buff, BuffLen);
end;
end;
|
Попробуйте так: "Подключите" следующую процедуру к событию OnFind для FindDialog. Единственная проблема заключается в том, что в DBMemo я не могу получить выделенный текст, тем не менее в стандартном Memo такой проблемы нет.
begin DBMemo1.SelStart:= P - Buff; DBMemo1.SelLength:= Length(FindText); end; FreeMem(FT, Length(FindText) + 1); FreeMem(Buff,BuffLen); DBMemo1.SetFocus; end; |
Выводить текст с помощью DBMS_OUTPUT.PUT_LINE в режиме отладки.
Автор: Nomadic Эта функция используется действительно только для отладки. Для того, чтобы результаты ее работы были видны из SQL Plus, необходимо в нем выдать команду:set serveroutput on size 10000;
Выключение кнопок в DBNavigator.
| Microsoft - маленький софт с большими сюрпризами. |
{ Расширение DBNavigator: позволяет разработчику включать и выключать
отдельные кнопки через методы EnableButton и DisableButton }
unit GNav;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, ExtCtrls, DBCtrls;
type
TMyNavigator = class(TDBNavigator)
public
procedure EnableButton(Btn: TNavigateBtn);
procedure DisableButton(Btn: TNavigateBtn);
end;
procedure Register;
implementation
procedure TMyNavigator.EnableButton(Btn: TNavigateBtn);
begin
Buttons[Btn].Enabled := True;
end;
procedure TMyNavigator.DisableButton(Btn: TNavigateBtn);
begin
Buttons[Btn].Enabled := False;
end;
procedure Register;
begin
RegisterComponents('Samples', [TMyNavigator]);
end;
end.
|
Свойства кнопок DBNavigator.
| Принято решение переименовать Windows 98 в Windows Diana. Во-первых, выглядит очень привлекательно, во-вторых, требует дофига ресурсов, а в-третьих, живописно крушится. |
Для определения видимости вы можете использовать свойство VisibleButtons. Например:
if nbRefresh in DBNavigator1.VisibleButtons then
ShowMessage('Кнопка Refresh видимая') ;
|
Для того, чтобы узнать, активизирована (enabled/disabled) кнопка или нет:
{Вместо nbFirst вы можете определить другой
член TNavigateBtn (например, nbFirst, nbPrior,
nbNext, nbLast, nbInsert, nbDelete, nbEdit,
nbPost, nbCancel, nbRefresh)}
if DBNavigator1.Controls[Ord(nbFirst)].Enabled then
ShowMessage('Кнопка First активизирована') ;
|
Вызов кнопок DBNavigator.
| Встречаются два хакера. Один говорит: - А я знаю что надо сделать чтоб OSR2 ваабще не глючила! - Что??? - Да всего один байт поменять! - Какой ??? - Да в названии OSR2 третий байт надо поменять на "/"!!! Ж:-))) |
Я делаю приблизительно так:
DBNavigator1.BtnClick(nbInsert); |
Я не знаю, задокументирована эта "фича", рекомендована ли для таких случаев... Просто у меня это работает и я этим пользуюсь.
Настройки всплывающих подсказок в DBNavigator.

| Автор: Freddy Hansson |
| Звонок интернет повайдеру: - Алло, это интернет? - Да. - Тогда соедените меня с www.delphiworld.ext |
procedure TForm1.Button1Click(Sender: TObject);
var
ix: integer;
begin
with DBNavigator1 do
for ix := 0 to ControlCount - 1 do
if Controls[ix] is TNavButton then
with Controls[ix] as TNavButton do
case index of
nbFirst: Hint := 'Подсказка для кнопки First';
nbPrior: Hint := 'Подсказка для кнопки Prior';
nbNext: Hint := 'Подсказка для кнопки Next';
nbLast: Hint := '';
{......}
end;
end;
|
DBNavigator без иконок.

| Автор: Ralph Friedman |
| Фидошник играет в Поле Чудес, на табло: *** Якубович читает задание: - Часть тела Фидошник сразу: - Нога! - Но ведь в задании только три буквы... - :-/ а я думал у вас H не настроена. |
var
c: shortint;
s: string;
begin
s := 'A';
with DBNavigator1 do
for c := 0 to ControlCount - 1 do
if Controls[c] is TNavButton then
with TNavButton(Controls[c]) do
begin
ListBox1.Items.Add(Name);
Glyph := nil;
Caption := s;
Inc(s[1]);
end;
end;
|
DBTreeView своими руками.
Автор: Елена Филиппова Специально для Королевства Delphi
Введение
В статье речь пойдет об отображении данных, хранящихся в БД и имеющих иерархическую (древовидную) структуру. Визуальное представление таких данных требует соответствующего инструмента. Существует немало компонент, которые позволяют представлять данные в виде дерева — для краткости будем называть их все DB TreeView. Компоненты эти довольно удобны, но, как правило, "заточены" под определенные задачи и каждый "шаг в сторону" в структуре данных заставляет многих пускаться в поиски. И на Круглом Столе появляются вопросы: "помогите найти компонент DB TreeView, который позволяет делать еще и ..." и так далее.
А ведь в Delphi существует стандартный компонент для представления древовидных данных, это знакомый всем TTreeView, его возможностей хватает с лихвой практически для всех задач по отображению деревьев. Сделать из TreeView самый настоящий DB TreeView, да еще полностью контролировать его развитие, более перспективный путь, нежели каждый раз искать новый чужой компонент.
Весь этот материал основан на моем личном опыте и, естественно, не обязательно является самым оптимальным вариантом. Это частное мнение, которым я просто хочу поделиться.
Рассмотрим два принципиально разных случая:
Дерево подразделений Данные представляют собой классическую древовидную структуру. Глубина вложенности дерева не ограничена, заранее не известна и не одинакова для разных ветвей. Дерево может не иметь общего корневого узла, то есть распадаться на несколько деревьев.; Дерево аналитических признаков Реально данные не являются иерархическими, но могут быть представлены в таком виде. Глубина вложенности дерева одинакова для всех его веток и фиксирована в данном примере. Правило построения иерархии может меняться в режиме run-time.Отличаются эти примеры стуктурой данных и, соответственно, способом формирования дерева.
Тем не менее сущестуют общие правила для генерации дерева. Во-первых, я исхожу из предположения, что все дерево сразу с большой степенью вероятности не понадобится пользователю. Более того, сам процесс формирования TTreeView довольно длительный и занимает ощутимое время, особенно если работать с большим объемом БД.
Гораздо правильнее строить отдельные ветки дерева динамически, только в тот момент, когда они наверняка понадобятся.
То есть, в самый начальный момент необходимо сформировать только самый верхний уровень дерева. Затем, по мере требования, можно достраивать очередную ветвь.
Какой же момент можно считать моментом требования? В принципе есть два равнозначных варианта и отличаются они только внешним видом.
Первый: по двойному клику на текущей ветви дерева проверять, была ли она достроена, если нет то формировать ее и генерировать событие OnExpanding, раскрывая новую ветвь. Второй: при попытке "раскрыть" ветку проводить аналогичные действия.Основное отличие этих подходов: в первом случае пользователю необходимо сделать явно лишнее и не очень естественное для дерева движение , то есть двойной клик. А во втором случае мы должны обеспечить возможность раскрытия для каждой ветки дерева, ведь если у ветви нет значка [+] , то и попытаться раскрыть ее будет невозможно. А значит для всех новых, свеже сформированных ветвей, принудительно нужно обеспечить существования фиктивной дочерней ветви.
Можете сами выбрать, что Вам удобнее, я использую второй вариант.
На сервере http://ib.demo.ru/ была опубликована статья Кузьменко Дмитрия Древовидные (иерархические) структуры данных в реляционных базах данных , часть которой посвящена теме визуального представления древовидных данных. В этой статье предлагалось хранить в таблице дополнительное поле с количеством дочерних записей для каждой ветви именно для того, чтобы иметь возможность принимать решение рисовать или не рисовать знак [+] возле текущей ветки дерева. То есть на ходу определять, имеет ли шанс эта ветка быть раскрытой.
Следовательно, только для процесса визуализации требуется изменять структуру данных, добавлять триггеры для сохранения целостности дополнительной информации.
Каждый способ имеет свои достоинства и недостатки, но мне кажется несколько неоправданным такой подход, когда структура таблиц зависит от способа их отображаения. В примерах, которые иллюстрируют материал этой статьи применяется использование фиктивных дочерних ветвей для обеспечения появления у каждой ветви значка [+].
Примечание:
Такой вопрос, как способы представления иерархических данных в БД, не является предметом данной статьи. Пример построен на локальных Paradox-таблицах. Следовательно для переноса его на SQL-серверные БД стоит учитывать их особенности.Дерево подразделеений
Пусть у нас существует таблица подразделений, каждое из которых может иметь свои внутренние подразделения. Необходимо отображать эти данные в виде дерева.
Используемая в примере таблица — COMPANY.DB.
Структура данных реализована классическим деревом: каждая запись о подразделении представлена полями.
CompanyID — номер подразделения ParentID — номер родительского подразделения Name — название.Для тех подразделений, которые не имеют головных над собой, поле ParentID равно 0.
Формировать уровни дерева мы будем с помощью запроса к таблице ( компонент qTreeCompanies : TQuery ).
Select * From COMPANY Where ParentID=:ParentID |
Параметр ParentID будет определять, какую ветку мы сейчас достраиваем. То есть, к какой ищем дочерние подразделения.
Процедура, формирующая очередной уровень (дочерний для ветви Node ) реализована следующим образом:
Procedure TFormTree.ExpandLevel( Node : TTreeNode);
Var ID , i : Integer;
TreeNode : TTreeNode;
Begin
// Для самого верхнего уровня выбрать только тех,
// кто не имеет родителей.
IF Node = nil Then ID:=0
Else ID:=Integer(Node.Data);
qTreeCompanies.Close;
qTreeCompanies.ParamByName('ParentID').AsInteger:=ID;
qTreeCompanies.Open;
TreeCompanies.Items.BeginUpdate;
// Для каждой строки из полученного набора данных
// формируем ветвь в TreeView, как дочерние ветки к той,
// которую мы только что "раскрыли"
For i:=1 To qTreeCompanies.RecordCount Do
Begin
// Запишем в поле Data ветки ее идентификационный номер(ID) в таблице
TreeNode:=TreeCompanies.Items.AddChildObject(Node ,
qTreeCompanies.FieldByName('Name').AsString ,
Pointer(qTreeCompanies.FieldByName('ID').AsInteger));
TreeNode.ImageIndex:=1;
TreeNode.SelectedIndex:=2;
// Добавим фиктивную (пустую) дочернюю ветвь только для того,
// чтобы был отрисован [+] на ветке и ее можно было бы раскрыть
TreeCompanies.Items.AddChildObject(TreeNode , '' , nil);
qTreeCompanies.Next;
End;
TreeCompanies.Items.EndUpdate;
End;
|
Теперь позаботимся о том, чтобы она вызывалась в нужный нам момент времени. На событие OnExpanding проверим, есть ли у текущей ветки фиктивная дочерняя ветвь и, если она есть, сформируем реальную ветку, предварительно удалив фиктивную.
if Node.getFirstChild.Data = nil then begin Node.DeleteChildren; ExpandLevel(Node); end; |
На форме в проекте кроме дерева расположена еще и сетка (Grid), в которой отображаются записи текущего уровня подразделений. Это, по сути, список дочерних ветвей для текущей ветки дерева. Для того, чтобы синхронизировать TreeView и DBGrid используем нехитрый прием — на событие TTreeView.OnChange (шаг по ветке) добавим следующий код:
IF TreeCompanies.Selected <> nil Then
Begin
// ID родительской ветки , для нее и ищем все дочерние
ID:=Integer(TreeCompanies.Selected.Data);
qCompanies.Close;
qCompanies.ParamByName('ParentID').AsInteger:=ID;
qCompanies.Open;
End;
|
Помните, в процедуре ExpandLevel мы записывали в поле Data каждой ветки ее идентификационный номер? Вот его то мы сейчас и используем.
Для полного ощущения передвижения по Grid'у, как по дереву можно добавить эффект "проваливания" на более глубокий уровень. По двойному клику на записи в Grid'е пользователь проваливается на один уровень вниз по иерархии, если такой уровень, конечно же еще есть.
На событие OnDblClick для грида:
ID:=qCompanies.FieldByName('ID').AsInteger;
// принудительное "невидимое" раскрытие той ветки, на которой стоим
TreeCompanies.OnExpanding(TreeCompanies ,TreeCompanies.Selected , Allow);
// Перебираем все получившиеся дочерние ветки и ищем ту, ID которой
// совпадает с ID строки в правой таблице. То есть ищем ветку в дереве,
// которая соответсвует той записи в таблице, на которой мы стоим
// Как только нашли, визуально раскрываем ветку и делаем ее выделенной,
// то есть визуально "встаем" на нее в дереве
FOR i:=0 To TreeCompanies.Selected.Count-1 Do
IF Integer(TreeCompanies.Selected.Item[i].Data) = ID Then
Begin
TreeCompanies.Selected.Item[i].Expand(False);
TreeCompanies.Selected.Item[i].Selected:=True;
TreeCompanies.Repaint;
Exit;
End;
|
Параллельно с этим раскрывается соответствующая ветка самого дерева. Очень эффектно. :о).
В проекте реализована возможность добавления новых ветвей, то есть новых подразделений. Нажимте на TreeView правую кнопку мышки и достраивайте наше дерево, как Вам угодно!
Дерево аналитических признаков
"Куст — это пучок веток, растущих из одного места" из военных афоризмов.
Пусть у нас есть таблица документов, каждый документ, например, описывает некоторую операцию по покупке( или продаже ) товара. В этой операции участвуют: определенный товар, клиент, у которого куплен (или которому продан) этот товар, и город, в котором данная операция совершена. Таким образом, мы имеем таблицу документов, каждая запись в которой наделена тремя аналитическими признаками: Город, Клиент и Товар.
По сути своей эти данные не являются иерархическими, и никакой явной зависимости между документами не прослеживается. Но тем не менее эта связь существует — одинаковые аналитические признаки. Например, в одну группу можно объединить все документы, имеющие отношение к определенному товару или клиенту.
Если для анализа данных пользователю необходимо работать с документами по зафиксированным аналитическим признакам, то возникает задача визуального представления таких данных. К примеру, заказчик требует показывать ему все документы для определенного города, а потом, для определенного клиента и товара, но в том же городе. Однако желательно видеть информацию ровно в обратном порядке: сначала выбрать документы с нужным товаром, затем увидеть все города, где работали с этим товаром, выбирать нужный город и так далее.
Явно прослеживается древовидная структура, где каждый уровень дерева суть аналитический признак. Именно поэтому, как говорилось выше, в данном дереве фиксирована глубина вложенности. Она однозначно определяется количеством аналитики для документа.
Описание примера, реализующего данную задачу
Используемые таблицы:
Таблица документов DOCUMENTS.DB , где каждый документ определен полями:
DocumentID : номер документа Name : название CityID : ID города ClientID : ID клиента GoodsID : ID товараТаблицы аналитики, соответственно CITIES.DB, CLIENTS.DB и GOODS.DB, содержат поля названия Name и номера (CityID , ClientID , GoodID).
Так как порядок следования аналитики произвольный, зараннее невозможно написать текст SQL-запроса, который будет возвращать данные для очередного уровня дерева. Этот текст придется формировать в run-time, когда все данные будут известны.
Чтобы не зашиваться именно на такой список аналитики, который приведен в примере, стоит затратить немного больше сил и обеспечить себе некоторую универсальность. Для этого добавим еще одну таблицу (таблицу сущностей) Entities, содержащую описание используемой аналитики.
Поля таблицы ENTITIES.DB.
EntityID — идентификатор Name — русское название аналитического признака TableName — название таблицы, в которой он содержится KeyColumn — ключевой столбец этой таблицы OrderNo — номер по порядку в списке используемых признаков IsSelect — выбран или не выбран в текущий список признаков ImageIndex — номер изображения (используется в отображении дерева)В нашем случае эта таблица будет выглядеть так:
| EntityID | Name | TableName | KeyColumn | ... |
| 1 | Город | CITIES | CityID | ... |
| 2 | Товар | GOODS | GoodID | ... |
| 3 | Клиент | CLIENTS | ClientID | ... |
В примере я использую список ListEntities (TCollection), каждый элемент которого содержит поля TableName, KeyColumn и ImageIndex. Элементы в этом списке расположены в том порядке, в каком будет строиться дерево. Заполняется этот список только той аналитикой, которая требуется для конкретного дерева. Например, только города и клиенты или товары и клиенты, или сразу все вместе. Следовательно этот список (ListEntities) и содержит полную информацию для построения дерева в каждый конкретный момент.
Заполнение списка аналитики проводится в модуле setupEntities.pas.
//считывает аналитику из таблицы procedure GetEntities( var List : TEntityLists ); // вызывает диалог для настойки аналитики пользователем function SetEntities( var List : TEntityLists ) : Boolean; |
Формирование дерева в этом случае полностью аналогично предыдущему примеру, с той только разницей, которая касается формирования текста запроса для каждого уровня дерева.
Так же в дерево добавляется единый для всех веток самый верхний корневой узел. С точки зрения аналитики это "фиктивный" узел, так как он будет отображать ВСЕ документы, без указания конкретного значения аналитического признака. Кроме того, достраивается еще один, самый нижний уровень — список документов по зафиксированной для текущей ветки аналитики.
Наличие этих "фиктивных" узлов совершенно не обязательно, но, на мой взгляд, очень логично.
В итоге, глубина вложенности дерева будет равна "количество аналитики" + 2.
Итак, процедура ExpandLevel будет модифицирована следующим образом:
Procedure TFormTree.ExpandLevelAnalytic(Node : TTreeNode );
Var NewItem : TListsItem;
ImageIndex ,
Level , i : Integer;
TreeNode : TTreeNode;
Sql,Name : String;
Begin
IF Node = nil Then Exit;
TreeAnalytic.Items.BeginUpdate;
Level:=Node.Level + 1; // уровень, который будет раскрываться
// Самому первому аналитическому признаку в списке ListEntities
// соответсвует _второй_ физический уровень веток дерева.
// Так как самый верхний уровень дерева фиктивный -"все документы"
// Отсюда и игра с (+/-) 1 при обращении к списку
qTreeAnalytic.Close;
// Определим, на каком типе уровня мы сейчас находимся
//
IF Level > ListEntities.Count
Then Begin // Уровень документов, аналитка закончилась
Sql:='SELECT * FROM Documents Where '+ GetSqlPath(Node);
Name:= 'DocumentID';
ImageIndex:=3;
End
Else Begin // Очередной уровень аналитики
Sql:='SELECT DISTINCT '+ ListEntities[Level-1].AsString + '.* ' +
' FROM Documents , ' + ListEntities[Level-1].AsString + ' WHERE ' +
ListEntities[Level-1].AsString + '.' +ListEntities[Level-1].Name + '=' +
'Documents.'+ListEntities[Level-1].Name + ' AND ' + GetSqlPath(Node) ;
Name:=ListEntities[Level-1].Name;
ImageIndex:=ListEntities[Level-1].ImageIndex;
End;
qTreeAnalytic.Sql.Clear;
qTreeAnalytic.Sql.Add(Sql);
qTreeAnalytic.Open;
// Получен очередной уровень ветвей дерева
For i:=1 To qTreeAnalytic.RecordCount Do
Begin
NewItem:=List.AddItem(qTreeAnalytic.FieldByName(Name).AsInteger , Name);
TreeNode:=TreeAnalytic.Items.AddChildObject( Node ,
qTreeAnalytic.FieldByName('Name').AsString, NewItem );
TreeNode.ImageIndex:=ImageIndex;
TreeNode.SelectedIndex:=TreeNode.ImageIndex;
// Фиктивная дочерняя ветка ТОЛЬКО для уровней аналитики,
// так как документы - последний уровень, за которым ничего и не может быть
IF Level <= ListEntities.Count
Then TreeAnalytic.Items.AddChild(TreeNode , '' );
qTreeAnalytic.Next;
End;
TreeAnalytic.Items.EndUpdate;
End;
|
В предыдущем примере мы запоминали ID строки из таблицы в поле Data каждой ветви дерева. Сейчас нам не годится такой вариант, так как аналитический признак определяется не одним идентификатором, а целым элементом списка ListEntities, вот его то и надо запоминать. Поэтому в поле Data сохраняется ссылка на конкретный элемент этого списка. Благо это Pointer и записать туда можно все, что угодно.
В процедуре используется функция GetSqlPath, которая возвращает полный путь от корня до указанной ветки дерева. Полный путь это есть зафиксированные значения для каждого уровня аналитики. Эти значения необходимы для того, чтобы верно построить запрос. То есть мы фактически формируем дополнительный фильтр для последующих выборок, напрмер — получаем всех клиентов для конкретного города и указанного товара.
Function TFormTree.GetSqlPath( Node : TTreeNode ) : String;
Begin
Result:=' 0=0 ' ;
// Участвуют все ветви дерева, кроме самого верхнего фиктивного уровня
While Node.Level > 0 Do
Begin
Result:= Result + ' AND ' +
'Documents.' + TListsItem(Node.Data).Name + '=' +
TListsItem(Node.Data).AsString ;
// Делаем шаг назад по ветке дерева
Node:=Node.Parent;
End;
End;
|
Пример текстов SQL запроса, который будет сформирован при движении по дереву:
Уровень "товары" — Все товары, которые встречаются в документах, созданных в городе номер 6 и для клиента номер 3.
SELECT DISTINCT Goods.* FROM Documents , Goods WHERE Goods.GoodsID=Documents.GoodsID AND 0=0 AND Documents.CityID=6 AND Documents.ClientID=3 |
Последний уровень "документы" — Все документы, созданные в городе номер 6 и для клиента номер 3, по товару номер 1.
SELECT * FROM Documents Where 0=0 AND Documents.GoodsID=1 AND Documents.CityID=6 AND Documents.ClientID=3 |
Такой подход позволяет легко расширять набор аналитических признаков, которые должны использоваться в программе, практически без изменения кода клиентского приложения. Достаточно изменить структуру таблицы DOCUMENTS и дополнить таблицу ENTITIES.
В некоторой степени можно сказать, что таблица ENTITIES содержит метаданные о структуре базы. Правда с большой натяжкой, так как в данном примере структура просто элементарна, а связи слишком просты и не поддерживают никакой глубины вложения (как, например, в таком случае, когда город не указан явно в документе, но может быть вытянут из таблицы клиентов и так далее).
Для получения набора данных не обязательно использовать именно запросы, точно так же можно использовать хранимые процедуры для SQL-серверных СУБД. Изменится структура метаданных, но не изменится принцип формирования дерева.
На мой взгляд, в качестве примера, стоит внимательно рассмотреть такой подход, чтобы вы могли в своих конкретных задачах на его основе конструировать реальные метаданные и без проблем модифицировать дерево аналитических признаков.
Итак...
Итак, были рассмотрены две принципиально разные задачи, а реализация DBTreeView оказалась практически идентична. Собственно, этот факт и является важным результатом статьи — воспользуйтесь примерами, добавьте собственный функционал и создайте для себя несложный компонент для отображения древовидной структуры. Это не значит, что не стоит пользоваться сторонними компонентами, ни в коем случае. Просто если существующие вас не устраивают полностью, вы будете знать, как это исправить.
Для иллюстрации материала статьи подготовлен проект TreeDB :
Проект откомпилирован в Delphi 5, использует BDE и настроен на алиас TreeDB.
Как можно открыть отчёт (в режиме Print Preview а также Print Direct) в MS Access.
| От: Святой Петр Кому: Всем на Земле Дата Октябрь, 1999 от рождества Христова Тема: Не помирать! Для ускорения регистрационного процесса у врат Рая, мы стали использовать Microsoft Access, после чего у нас начались серьезные проблемы. А посему, пожалуйста, не помирайте пока! |
var
Access: Variant;
begin
// Открываем Access
try
Access := GetActiveOleObject('Access.Application');
except
Access := CreateOleObject('Access.Application');
end;
Access.Visible := True;
// Открываем базу данных
// Второй параметр указывает - будет ли база открыта в Exclusive режиме
Access.OpenCurrentDatabase('C:\My Documents\Books.mdb', True);
// открываем отч¸т
{Значение второго пораметра может быть одним из следующих
acViewDesign, acViewNormal, or acViewPreview. acViewNormal,
которые устанавливаются по умолчанию, для печати отч¸та.
Если Вы не используете библиотеку типов, то можете определить
эти значения следующими:
const
acViewNormal = $00000000;
acViewDesign = $00000001;
acViewPreview = $00000002;
Третий параметр - это имя очереди для текущей базы данных.
Четв¸ртый параметр - это строка для SQL-евского WHERE -
то есть строка SQL, минус WHERE.}
Access.DoCmd.OpenReport('Titles by Author', acViewPreview, EmptyParam,
EmptyParam);
< ... >
// Закрываем базу данных
Access.CloseCurrentDatabase;
// Закрываем Access
{const
acQuitPrompt = $00000000;
acQuitSaveAll = $00000001;
acQuitSaveNone = $00000002;}
Access.Quit(acQuitSaveAll);
end;
|
Как в Delphi сбросить кэш БД на диск.
| - Ты слишком много знал, - сказал Windows HardDisk-у. |
uses BDE {в Delphi 1.x не помню, но вроде bdeprocs};
dbiSaveChanges
|
На Delphi 1.x (16bit) дополнительно вызовите эту процедуру:
procedure DropCache; assembler; asm mov ah,$0D int $21 end; |
История изменений объектов.
Оформил: DeeCo
Автор: Виноградов С. А.
ВведениеБольшинство объектов, хранящихся в базе данных, могут меняться с течением времени. Например, человек может сменить фамилию, паспорт, место жительства. У различных объектов учета может поменяться цена, количество, стоимость. К сожалению, почти все современные СУБД не хранят историю этих изменений и не позволяют получить состояние объектов на определенную дату. Об этом должен позаботиться проектировщик базы данных.
В качестве примера, возьмем таблицу Objects, которая содержит код объекта, его наименование, примечание и другие поля (здесь и далее — синтаксис MS SQL):
create table[Objects] ( [Id]int not null identity primary key, [Code]varchar(120) not null, [Name]varchar(120) not null, [Note]varchar(250), ... )
Рассмотрим варианты хранения истории для этой таблицы.
Дополнительная таблица историиПо многим соображениям, бывает необходимо оставить таблицу объектов на текущий момент в неизменном виде, а историю вести в отдельной таблице. Самый простой способ хранения истории — при любом изменении в объекте, добавлять в таблицу запись с новыми значениями объекта и приписывать к этой записи дату изменения. Тогда, структура таблиц Objects и ObjectsHistory будет примерно такой:
create table[Objects]
(
[Id]int not null identity primary key,
[Code]varchar(120) not null,
[Name]varchar(120) not null,
[Note]varchar(250),
...
)
create table[ObjectsHistory]
(
[object]int not null references[Objects]([Id]),
[Code]varchar(120) not null,
[Name]varchar(120) not null,
[Note]varchar(250),
...
[DateBegin]datetime not null default(getdate()),
[DateEnd]datetime not null default('3000-01-01')
constraint[ObjectHistory]primary key([object], [DateBegin])
)
Первичный ключ в таблице истории состоит из двух полей: идентификатора объекта из основной таблицы (Object) и даты начала действия истории объекта (DateBegin). Дата окончания действия истории (DateEnd) всегда равна дате начала следующей истории объекта, и используется исключительно для более простых условий выборки.
При вставке записи в основную таблицу, необходимо добавить такую же запись в таблицу истории, где Object = идентификатор_объекта, DateBegin = текущая_дата, DateEnd = максимальная_дата.
При модификации записи в основной таблице, нужно вставить измененную запись в таблицу истории, где Object = идентификатор_объекта, DateBegin = текущая_дата, DateEnd = максимальная_дата. При этом у предыдущей записи с таким же идентификатором в таблице истории необходимо установить DateEnd = текущая_дата.
При удалении записи в основной таблице, у последней записи с таким же идентификатором в таблице истории надо установить DateEnd = текущая_дата.
Запись изменений в таблицу истории можно производить триггерами, хранимыми процедурами, или из приложения, используя базовый класс объектов.
Выборка из основной таблицы производится обычным образом, а из таблицы истории с условием:
заданная_дата beetwen DateBegin and DateEnd
Основным достоинством данного метода является отсутствие изменений для хранения и получения последнего состояния объектов.
Недостаток — повышенное дублирование информации (к примеру, последнее состояние будет одинаково в обеих таблицах) и, соответственно, большой объем базы данных.
Одна таблица для всех состоянийЕсли база данных разрабатывается с самого начала, то более логичным будет держать текущее состояние объектов и их историю в одной таблице. Тогда, получение состояния на текущий момент ничем не будет отличаться от получения состояния на заданную дату.
При использовании автонумеруемого поля в качестве идентификатора объекта, это поле придется вынести в отдельную таблицу ключей Objects, так как в таблице ObjectsHistory оно не будет уникальным:
create table[Objects]
(
[Id]int not null identity primary key
)
create table[ObjectsHistory]
(
[object]int not null references[Objects]([Id]),
[Code]varchar(120) not null,
[Name]varchar(120) not null,
[Note]varchar(250),
...
[DateBegin]datetime not null default(getdate()),
[DateEnd]datetime not null default('3000-01-01')
constraint[ObjectHistory]primary key([object], [DateBegin])
)
Первичный ключ в таблице истории состоит из двух полей: идентификатора объекта из таблицы ключей (Object) и даты начала действия истории объекта (DateBegin). Дата окончания действия истории (DateEnd) равна дате начала следующей истории объекта, и используется для более простых условий выборки.
При вставке объекта, необходимо вначале добавить запись в таблицу ключей и получить идентификатор объекта. Затем надо произвести запись в таблицу истории, где Object = идентификатор_объекта, DateBegin = текущая_дата, DateEnd = максимальная_дата.
При модификации объекта, нужно вставить измененную запись в таблицу истории, где Object = идентификатор_объекта, DateBegin = текущая_дата, DateEnd = максимальная_дата. При этом, у предыдущей записи с таким же идентификатором необходимо установить DateEnd = текущая_дата.
При удалении объекта, для последней записи с таким же идентификатором надо установить DateEnd = текущая_дата.
Выборка последнего состояния объектов производится с условием:
текущая_дата beetwen DateBegin and DateEnd
Условие для выборки на определенную дату:
заданная_дата beetwen DateBegin and DateEnd
Достоинством этого способа является его простота, а также однообразность хранения и получения текущего состояния и истории.
Недостатком будет более сложное получение текущего состояния. Информация по-прежнему остается избыточной — при изменении одного из свойств объекта, он копируется целиком.
ЗаключениеСуществует масса других способов хранения истории, начиная от простого журнала, в котором, при любом изменении записи в таблице, пишутся прежние значения полей, преобразованные в строку. Но далеко не все эти способы пригодны для легкого и быстрого получения состояния объектов на любую дату. Здесь были рассмотрены лишь наиболее простые и эффективные варианты представления истории, которые вполне подходят для практического применения.
Создание компонентов для работы с базами данных.
| Базу селектом не испортишь. |
{ пример }
private
FDataLink: TFieldDataLink;
|
Примечание: Наш новый компонент будет иметь свойства DataField и DataSource, FDataLink также будет иметь собственные свойства DataField и Datasource.
{ пример }
published
property DataField: string
read GetDataField
write SetDataField;
property DataSource: TDataSource
read GetDataSource
write SetDataSource;
|
{ пример }
private
FDataLink: TFieldDataLink;
function GetDataField: String;
function GetDataSource: TDataSource;
procedure SetDataField(Const Value: string);
procedure SetDataSource(Value: TDataSource);
.
.
implementation
.
.
function TDBPanel.GetDataField: String;
begin
Result := FDataLink.FieldName;
end;
function TDBPanel.GetDataSource: TDataSource;
begin
Result := FDataLink.DataSource;
end;
procedure TDBPanel.SetDataField(Const Value: string);
begin
FDataLink.FieldName := Value;
end;
procedure TDBPanel.SetDataSource(Value: TDataSource);
begin
FDataLink.DataSource := Value;
end;
|
{ пример }
private
.
.
procedure DataChange(Sender: TObject);
implementation
.
.
procedure TDBPanel.DataChange(Sender: TObject);
begin
if FDataLink.Field = nil then
Caption := '';
else
Caption := FDataLink.Field.AsString;
end;
|
{ пример }
public
constructor Create(AOwner: TComponent); override;
.
.
implementation
.
.
constructor TMyDBPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FDataLink := TFieldDataLink.Create;
FDataLink.OnDataChange := DataChange;
end;
|
{ пример }
public
.
.
destructor Destroy; override;
.
.
implementation
.
.
destructor TDBPanel.Destroy;
begin
FDataLink.OnDataChange := nil;
FDataLink.Free;
inherited Destroy;
end;
|
Полный код компонента.
unit Mydbp;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, ExtCtrls, DB, DBTables;
type
TDBPanel = class(TCustomPanel)
private
FDataLink: TFieldDataLink;
function GetDataField: string;
function GetDataSource: TDataSource;
procedure SetDataField(const Value: string);
procedure SetDataSource(Value: TDataSource);
procedure DataChange(Sender: TObject);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property DataField: string
read GetDataField
write SetDataField;
property DataSource: TdataSource
read GetDataSource
write SetDataSource;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TDBPanel]);
end;
function TDBPanel.GetDataField: string;
begin
Result := FDataLink.FieldName;
end;
function TDBPanel.GetDataSource: TDataSource;
begin
Result := FDataLink.DataSource;
end;
procedure TDBPanel.SetDataField(const Value: string);
begin
FDataLink.FieldName := Value;
end;
procedure TDBPanel.SetDataSource(Value: TDataSource);
begin
FDataLink.DataSource := Value;
end;
procedure TDBPanel.DataChange(Sender: TObject);
begin
if FDataLink.Field = nil then
Caption := ''
else
Caption := FDataLink.Field.AsString;
end;
constructor TDBPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FDataLink := TFieldDataLink.Create;
FDataLink.OnDataChange := DataChange;
end;
destructor TDBPanel.Destroy;
begin
FDataLink.Free;
FDataLink.OnDataChange := nil;
inherited Destroy;
end;
end.
|
Использование DB Controls без базы данных.
Оформил: DeeCo
Область примененияДовольно часто на различных форумах задаются вопросы примерно такого характера: “Как мне красиво (как в DBGrid) отобразить содержимое списка структур?”, “Как можно вывести данные в DBGrid/DBControlGrid?”.
Конечно, существуют различные варианты ответа на каждый из этих вопросов. В первом случае можно попытаться создать потомка TDBGrid. Во втором – создать потомка TDataSource. Однако на мой взгляд, предпочтительнее (как в отношении предоставляемого программисту набора функций, так и в отношении применимости конкретного ответа для решения целого круга похожих задач) способ, применяемый обычно для разрешения более принципиальной задачи: как обеспечить единообразную работу с данными из имеющейся БД (DBase, Interbase, MS SQL Server и т.п.), и с другими, в частности, локальными данными программы?
Работа с базами данных при помощи стандартного набора компонентов Delphi строится по следующей схеме:
Рисунок 1. Схема взаимодействия программы и элементов управления с БД.
Видно, что наиболее полного единообразия работы с данными из БД и другими своими наборами данных можно достичь, создав для доступа к своим данным потомка TDataSet, поскольку именно этот класс отвечает за реальный доступ к данным.
Создание своего TDataSet – вовсе не такая сложная работа, как кажется на первый взгляд при просмотре исходного кода TDataSet и/или его стандартных потомков (например, TIBCustomDataSet). Основным препятствием является почти полное отсутствие документации от Borland. В поставляемом с Delphi простом примере TTextDataSet (потомка TDataSet, работающего с текстовым файлом на диске) прямо говорится: “Currently, the notes in this source file represent the only documentation on how to create a TDataSet implantation”, что в приблизительном переводе означает: “в настоящее время данный пример представляет собой единственную документацию по созданию реализации TDataSet”.
В данной работе я рассмотрю создание двух TDataSet. Один будет обеспечивать доступ в режиме “только чтение” к данным, вычисляемым “на лету” (в качестве примера выбрана таблица квадратов натуральных чисел от 1 до 20). Второй же позволит осуществлять доступ с возможностью модификации к списку структур вида (имя, адрес электронной почты). Любой промежуточный уровень функциональности легко может быть получен путем усечения функциональности второго примера.
В настоящей работе не рассматриваются:
реализация установки фильтров на записи и поиска записей. Впрочем, получить представление о том, как это следует делать, можно, просмотрев исходные тексты методов FindRecord, FindFirst, FindNext, FindLast, FindPrior, Locate и Lookup (модули DB.PAS и DBTABLES.PAS, классы TDataSet и TBDEDataSet ); поля-агрегаты (методы GetAggregateValue, GetAggRecordCount, ResetAggField); реализация интерфейса IProviderSupport (семейство методов PSxxxxx). Простейший DataSetЕсть такое хорошее правило – прежде чем переходить к программированию, четко определить круг задач, которые обязательно должны быть решены. Давайте ему последуем. Я хочу создать потомка TDataSet, работающего с набором данных, содержащим два столбца: целое число и его квадрат. Для простоты этот набор данных будет содержать лишь 20 записей – для чисел от 1 до 20. Borland выдвигает еще одно требование: каждый потомок TDataSet должен поддерживать работу с закладками. Однако я позволю себе пока его проигнорировать – тем более, что и без этого получается практически вполне работоспособный класс. Желающие могут добавить в этот пример поддержку закладок, предварительно ознакомившись с механизмом закладок в следующем разделе.
ПРИМЕЧАНИЕО терминологии: в дальнейшем я буду использовать название TDataSet для обозначения программного кода и классов, тогда как под набором данных буду понимать сам набор данных, доступ к которому обеспечивает конкретный потомок TDataSet.
Чтобы создать потомка TDataSet, нужно понять, как TDataSet выполняет доступ к данным. Общедоступный (public) интерфейс полностью реализуется средствами самого TDataSet; на плечи конкретных потомков ложится лишь обеспечение средств коммуникации общедоступных методов с набором данных. В основном, коммуникация осуществляется методами InternalXXX, часть из которых в TDataSet объявлена пустыми, а часть – и вовсе абстрактными.
Эти методы можно разделить по их назначению на три группы: навигация (перемещение) по набору и чтение записи из БД в кэш-буфер (а также запись из кэш-буфера в набор данных), вставка/удаление записей, чтение/изменение значений полей записи, прочие методы (открытие и закрытие TDataSet, закладки, выделение и освобождение памяти).
Логика TDataSet никогда не работает со значениями полей записи прямо в наборе. Вместо этого вызовы GetRecord читают целиком записи, причем каждая из них заносится в свой кэш-буфер, и дальнейшие операции с полями каждой считанной записи выполняются в кэш-буфере. В TDataSet одновременно в памяти присутствуют, в общем случае, несколько буферов (свойство-массив Buffers). Помимо данных, считанных из БД, потомок TDataSet может размещать в буфере произвольную дополнительную служебную информацию.
Прежде, чем переходить к рассмотрению методов, я поясню одно из центральных для TDataSet понятий - понятие курсора.
Стандартный способ работы с БД таков, что целевая запись для таких воздействий, как удаление записи, изменение записи, вставка новой записи перед данной и т.п. не указывается явно при вызове соответствующей операции, а определяется предыдущими операциями. Соответственно, для хранения описания того места в наборе данных, которое будет являться целевым для следующей операции, необходимо ввести некую сущность – курсор.
Курсор - это произвольная информация, придающая смысл в каждый момент времени таким понятиям, как “текущая”, “следующая” и (для двунаправленных наборов данных) “предыдущая” запись. Позволю себе вольность пользоваться в отношении курсора фразой “перемещение курсора к некоторой записи”, подразумевая изменение составляющей курсор информации так, чтобы курсор указывал на эту запись.
Тот курсор, который определяет целевую позицию для действий, запрашиваемых у TDataSet сторонним кодом, я в дальнейшем буду называть логическим курсором. С логическим курсором в TDataSet связан буфер активной записи (свойство ActiveBuffer хранит адрес этого буфера).
Логический курсор никак не учитывает структуру набора данных, а потому сам по себе в общем случае недостаточен для эффективного указания на запись реального набора. Например, набор может представлять собой дерево, а коду TDataSet потомок, обеспечивающий работу с этим деревом, представляет его как последовательность вершин дерева, получающуюся при его обходе по некоторому правилу. В таком случае самое большее, что можно выяснить, зная значение логического курсора – это номер, под которым встретится данная вершина при таком обходе. Согласитесь, что не слишком удобно основывать работу с деревом на такой информации, ведь ее придется все время пересчитывать, например, в путь от корня дерева к данной вершине.
Поэтому потомкам TDataSet предлагается пользоваться своим собственным курсором, осведомленным о структуре набора. Назовем этот курсор физическим курсором. В простейшем случае, когда набор данных является простым массивом, на роль физического курсора может вполне сгодиться индекс записи в массиве.
ПРИМЕЧАНИЕВ дальнейшем под курсором следует понимать логический курсор, если не оговорено обратное. Запись, на которую указывает логический курсор, я буду называть текущей записью.
Для поддержания согласованности логического курсора с физическим используется следующая техника: после вызова метода, от которого ожидается перемещение физического курсора на непредсказуемое расстояние от его текущего положение (например, при переходе на первую запись или поиске по закладке), вызывается метод Resync, считывающий в кэш-буферы запись, на которую теперь указывает физический курсор, а также несколько окружающих ее записей. Если же перемещение предсказуемо (как в случае метода MoveBy, вызываемого из Prior и Next), то читается лишь часть окружающих записей. Понятно, что в случае, когда вообще не ожидается никаких перемещений, никакие записи и не перечитываются.
Если потомку требуется переместить физический курсор вопреки ожиданиям TDataSet, необходимо оповестить его об этом вызовом CursorPosChanged. Такое оповещение обрабатывается не сразу, и, говоря откровенно, меня до сих пор гложут сомнения, не надежнее ли сразу вызывать Resync (хотя в таком случае можно проиграть в скорости, если после этого Resync будет вызван повторно).
Таким образом, в момент вызова методов InternalXXX логический курсор совпадает с физическим (от возможных сбоев этого соответствия в ходе выполнения закрытым кодом каких-либо внутренних операций спасает выполняющийся перед входом в InternalXXX вызов метода UpdateCursorPos), а после выхода из InternalXXX логический курсор опять-таки приводится в соответствие физическому.
ПРИМЕЧАНИЕUpdateCursorPos работает с буфером текущей записи: для помещения физического курсора на ту же запись, на которую указывает логический, он вызывает InternalSetToRecord, указав в качестве аргумента (буфера записи, к которой нужно перейти) буфер текущей записи (ActiveBuffer).
Единственное обнаруженное мной исключение из этого правила – метод InternalInsert (вызывающийся при вставке записи), перед вызовом которого не выполняется UpdateCursorPos, из-за чего приходится искать способ переместить физический курсор на ту же запись, на которую указывает логический, при этом не пользуясь private-членами TDataSet). Подробнее об этом – в следующем разделе.
При открытии (инициализации) логического курсора (TDataSet.OpenCursor, вызывается при TDataSet.Active:=true и по выходу из режима дизайнера) вызывается метод InternalOpen. Он должен сформировать список определений полей FieldDefs и, если свойство DefaultFields равно True, создать на их базе список полей Fields. Он также должен связать поля из списка Fields с полями набора данных и установить физический курсор в позицию перед первой записью. Кроме того, если до начала манипулирования содержащимися в наборе данными нужно предпринять какие-либо действия (например, установить соединение с удаленным сервером), их также следует выполнить в этом методе.
InternalOpen может вызываться не только из OpenCursor. Запрос на формирование списка FieldDefs может поступить и отдельно: в этом случае вызывается метод InitFieldDefs, который может вызывать InternalInitFieldDefs (последний должен инициализировать FieldDefs).
Для проверки того, что логический курсор открыт (т.е. что набор готов предоставить доступ к своим данным), вызывается функция IsCursorOpen, возвращающая True или False. Ясно, что до выполнения Open она должна возвращать False, а после Open и до последующего Close – True.
Для закрытия набора данных (TDataSet.Close) вызывается метод InternalClose, назначение которого симметрично InternalOpen: если DefaultFields установлено в True, то уничтожить список полей Fields (в этом случае они были созданы самим потомком в ходе выполнения InternalOpen); также, в случае необходимости, выполнить такие действия, как разрыв соединения с сервером БД.
Для навигации (перемещения логического курсора) по набору применяются общедоступные методы First, Prior, Next и Last.
Метод First вызывает метод InternalFirst, который должен установить физический курсор на позицию перед первой записью в наборе. После этого First выполняет серию вызовов GetNextRecord (а те, в конечном счете, GetRecord) для чтения в кэш-буферы следующей (т.е. первой) записи набора и нескольких последующих записей. Затем при помощи вызова DataEvent генерируется уведомление об изменении данных в кэш-буферах.
Зачем нужно устанавливать курсор на позицию перед первой записью, а потом читать следующую запись? Почему бы просто не поместить курсор сразу на первую запись? Дело, возможно, в том, что если для позиционирования на первую запись необходимо переоткрытие набора (например, в однонаправленных наборах), то эта обязанность возлагается на InternalFirst. То есть для помещения курсора на первую запись InternalFirst должен был бы переоткрыть набор и выполнить переход к следующей записи. По-видимому, команда разработчиков Borland решила, что будет лучше, если все команды навигации по записям будут подаваться из находящегося в базовом классе TDataSet слоя абстрактной логики, а не из методов, отвечающих за конкретную реализацию доступа к данным (таких, как InternalFirst). Таким образом, работа IntrnalFirst должна в такой ситуации завершаться переоткрытием (а после этой операции курсор, как уже говорилось, находится перед первой записью).
Аналогично, InternalLast должен устанавливать физический курсор в позицию, находящуюся за последней записью БД (это уже, видимо, просто для единообразия).
Методы Prior и Next апеллируют к одному и тому же методу GetRecord. Последний многофункционален: он должен проверить, не выйдет ли позиция курсора при выполнении операции за пределы БД, изменить позицию физического курсора, считать из набора в буфер записи, переданный в качестве аргумента, данные записи, ставшей текущей, и (в зависимости от параметров вызова) в случае ошибки возбудить исключение.
Когда необходимо прочитать значение некоторого поля текущей записи, вызывается GetFieldData, в который передается ссылка на соответствующий нужному полю объект TField и адрес буфера, содержащего запись. GetFieldData – перегруженный метод, но фактическое извлечение значения поля из буфера записи в так называемом Native-формате (его мы рассмотрим далее) осуществляет лишь одна из его версий (остальные версии в конечном счете просто вызывают именно ее).
Кстати, существует и еще один способ позиционирования физического курсора: метод InternalSetToRecord предназначен для установки курсора на запись, содержимое которой находится в переданном в качестве аргумента буфере (ранее оно было считано в этот буфер методом GetRecord).
Для выделения памяти под новый буфер вызывается метод AllocRecordBuffer, для освобождения занятой под буфер памяти – FreeRecordBuffer. Надо заметить, что TDataSet может повторно использовать уже выделенный однажды буфер для хранения другой записи. Обработку вытеснения записи из буфера при таком повторном использовании можно поместить туда же, куда и действия по подготовке буфера перед помещением туда записи – в InitRecordBuffer. Этот метод вызывается перед каждым помещением в буфер какой-либо записи.
Редактирование данных, в т.ч. добавление и удаление записей, становится возможно, когда метод GetCanModify возвращает True. Процессы, происходящие при редактировании, будут рассмотрены в следующем разделе, поэтому пока мы считаем, что GetCanModify всегда возвращает False.
Метод InternalHandleException служит для обработки возможных исключений, возникающих при чтении объекта из потока. Насколько мне удалось понять из исходных текстов потомков TDataSet, предлагаемых Borland, хорошим тоном является такая его реализация: Application.HandleException(Self).
Метод IsSequenced должен возвращать True, если в наборе каждую запись можно однозначно сопоставить с ее порядковым номером (т.е. числом, показывающим, какой по счету окажется запись при последовательном переборе всех записей, начиная с первой).
Помимо этих методов, в работе с данными может участвовать еще несколько семейств методов, предназначенных для расширения возможностей TDataSet. Одно из них - работа с закладками - как я уже говорил, будет рассмотрено в следующем разделе. Поддержка остальных не является обязательной (см. также перечень не рассматриваемых здесь функций в конце раздела “Область применения”). Однако три из них стоит реализовать, иначе довольно некрасиво смотрится полоса прокрутки в DBGrid и подобных ему элементах управления, а клиенты теряют возможность простого позиционирования на запись по ее номеру.
GetRecordCount должен возвращать общее число содержащихся в наборе записей. GetRecNo должен возвращать номер (начиная с 1) текущей записи. SetRecNo должен устанавливать физический и логический курсоры на запись с указанным номером.Последние два метода фактически являются реализацией свойства RecNo.
Два слова о Native-формате данных: детально мы его обсудим в следующем разделе, пока же – основное. Извлеченные из записи данные следует возвращать в особом формате (Native), своем для каждого типа поля. В нашем примере используются только поля типа ftInteger, для которых Native-формат представляет собой 4-байтовое целое число со знаком, т.е. значение типа integer. (см. чтение в методе GetFieldData).
Листинг 1 TMyDataSet (read-only)
unit DataSet0;
//Простой Read-Only BiDirectional DataSet в виде таблицы
// квадратов целых чисел (два поля – число и его квадрат)
interface
uses DB, Classes {для TComponent};
type
TMyDataSet = class(TDataset)
protected
procedure InternalHandleException; override;
// Инициализация/деинициализация курсора
procedure InternalInitFieldDefs; override;
procedure InternalOpen; override;
function IsCursorOpen: Boolean; override;
procedure InternalClose; override;
// Выборка
function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean):
TGetResult; override;
function AllocRecordBuffer: PChar; override;
procedure FreeRecordBuffer(var Buffer: PChar); override;
procedure InternalInitRecord(Buffer: PChar); override;
// Навигация
procedure InternalFirst; override;
procedure InternalLast; override;
procedure InternalSetToRecord(Buffer: PChar); override;
// Разрешение доступа
function GetCanModify: Boolean; override;
// Необязательные методы
function GetRecordCount: Integer; override;
//Везде в данном коде, где используется RecordCount, можно было бы просто
//написать List.Count. Реализовывать GetRecordCount не обязательно.
procedure SetRecNo(Value: Integer); override;
function GetRecNo: Integer; override;
//Здесь номер записи считается от 1
public
// Чтение поля
function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;
// Создание/уничтожение
constructor Create(AOwner: TComponent); override;
private
FIsOpen: boolean;
FCursor: integer;
end;
TRecordData = record
//Собственно копия записи – по замыслу, она должна считываться из БД
Number, Square: integer;
end;
TRecordBuffer = record //Формат буфера, хранящего запись: данные записи +
//служебная информация
RecordData: TRecordData;
RecordIndex: integer; //Номер записи, считая от 1
end;
PRecordBuffer = ^TRecordBuffer;
implementation
procedure TMyDataSet.InternalHandleException;
begin
Application.HandleException(Self)
end;
procedure TMyDataSet.InternalInitFieldDefs;
begin
FieldDefs.Clear;
with FieldDefs.AddFieldDef do
begin
DataType := ftInteger;
FieldNo := 1;
Name := 'Число';
end;
with FieldDefs.AddFieldDef do
begin
DataType := ftInteger;
FieldNo := 2;
Name := 'Квадрат';
end;
end;
procedure TMyDataSet.InternalOpen;
begin
InternalInitFieldDefs;
if DefaultFields then
CreateFields;
BindFields(true); //Привязываем поля к БД
FIsOpen := true;
FCursor := 0;
end;
function TMyDataSet.IsCursorOpen: Boolean;
begin
result := FIsOpen
end;
procedure TMyDataSet.InternalClose;
begin
BindFields(False); //Отвязываем поля
if DefaultFields then
DestroyFields;
FIsOpen := false;
end;
procedure TMyDataSet.InternalFirst;
begin
FCursor := 0
end;
procedure TMyDataSet.InternalLast;
begin
FCursor := RecordCount + 1
end;
function TMyDataSet.GetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean): TGetResult;
begin
result := grOK;
case GetMode of
gmPrior: if FCursor <= 1 then
result := grBOF
else
Dec(FCursor);
gmNext: if FCursor >= RecordCount then
result := grEOF
else
Inc(FCursor);
gmCurrent: if (FCursor < 1) or (FCursor > RecordCount) then
Result := grError;
end;
if result = grOK then
with PRecordBuffer(Buffer)^ do
begin
RecordData.Number := FCursor;
RecordData.Square := sqr(RecordData.Number);
RecordIndex := FCursor;
end;
if (result = grError) and DoCheck then
DatabaseError('Error in GetRecord()');
end;
function TMyDataSet.GetRecordCount: Integer;
begin
Result := 20
end;
procedure TMyDataSet.SetRecNo(Value: Integer);
begin
if (Value < 1) or (Value >= RecordCount + 1) then
exit;
FCursor := Value;
Resync([])
end;
//Мне не пришло в голову более умного средства синхронизации логического
// курсора с физическим, чем выполнение Resync (ресинхронизация – сброс
// логического курсора и последующее чтение записей, окружающих
// физический курсор)
function TMyDataSet.GetRecNo: Integer;
//Возвращает номер записи, соответствующей активному в данный момент буферу
begin
Result := PRecordBuffer(ActiveBuffer)^.RecordIndex
end;
procedure TMyDataSet.InternalSetToRecord(Buffer: PChar);
begin
FCursor := PRecordBuffer(Buffer)^.RecordIndex
end;
function TMyDataSet.AllocRecordBuffer: PChar;
begin
GetMem(result, sizeof(TRecordBuffer))
end;
procedure TMyDataSet.FreeRecordBuffer(var Buffer: PChar);
begin
FreeMem(Buffer, sizeof(TRecordBuffer))
end;
procedure TMyDataSet.InternalInitRecord(Buffer: PChar);
begin
inherited; //Нам не нужна какая-то особая инициализация буфера записи
// до чтения туда фактической записи
end;
function TMyDataSet.GetCanModify: Boolean;
begin
result := false
end;
function TMyDataSet.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
begin
result := true;
case Field.FieldNo of
1: PInteger(Buffer)^ := PRecordBuffer(ActiveBuffer)^.RecordData.Number;
2: PInteger(Buffer)^ := PRecordBuffer(ActiveBuffer)^.RecordData.Square;
else
result := false;
end;
end;
constructor TMyDataSet.Create(AOwner: TComponent);
begin
inherited;
FIsOpen := false;
end;
end.
Для проверки работы класса TMyDataSet можно использовать следующий тестовый проект:
Листинг 2
program Dataset;
uses Forms, DB, DBGrids, DBCtrls, Controls, DataSet0;
var
F: TForm;
DS: TDataSource;
MyDataSet: TMyDataSet;
begin
Application.Initialize;
Application.CreateForm(TForm, F);
F.Width := 300;
F.Height := 300;
F.Position := poDesktopCenter;
MyDataSet := TMyDataSet.Create(F);
MyDataSet.Open;
DS := TDataSource.Create(F);
DS.DataSet := MyDataSet;
with TDBNavigator.Create(F) do
begin
Parent := F;
Align := alTop;
DataSource := DS
end;
with TDBGrid.Create(F) do
begin
Parent := F;
Align := alClient;
DataSource := DS
end;
Application.Run;
end.
Добавление возможностей редактирования
Разумеется, чаще возникают ситуации, когда необходимо обеспечить возможность редактирования данных, в т.ч. удаления записей и добавления новых. Определенные сложности при этом могут возникать лишь при добавлении новых записей, в то время как редактирование и удаление достаточно тривиальны.
Рассмотрим более сложный и более приближенный к реальности пример потомка TDataSet. Наш класс TMyDataSet будет предоставлять доступ для чтения/изменения/удаления/добавления записей в адресную книгу, представляющую собой список (обычный TList), содержащий указатели на записи, состоящие из двух строковых компонентов: имени и электронного адреса. Кроме того, TMyDataSet будет поддерживать работу с закладками.
Итак, какие действия предпринимает TDataSet при редактировании, добавлении и удалении записей?
Самая простая операция – удаление. Вызов общедоступного метода Delete приводит (если TDataSet не находится в состоянии вставки записи, в противном случае Delete просто вызывает Cancel) к вызову InternalDelete, на который возлагается обязанность удалить запись из набора. Работа завершается вызовом Resync, так что InternalDelete может поместить физический курсор на любую запись по своему усмотрению.
Что касается редактирования, то можно выделить два его вида: обычное редактирование записи (состояние dsEdit), которое я и буду в дальнейшем называть редактированием, и вставка записи (dsInsert).
Для начала редактирования вызывается метод Edit. Он, в свою очередь, вызывает метод InternalEdit. В большинстве ситуаций никаких действий в InternalEdit можно не предпринимать – TDataSet переведет объект в состояние dsEdit, особым образом обработает вычисляемые (calculated) поля, выдаст сообщение об изменении записи и т.д. Для начала вставки записи вызовается Insert или Append. Как в состоянии dsInsert, так и в dsEdit TDataSet позволяет вносить в запись изменения.
Выводится из этих состояний TDataSet также одинаковыми способами. Их два: вызов метода Post (подтверждение изменений – и вставки, если мы находимся в состоянии dsInsert) или метода Cancel (отмена изменений/вставки). Первый из них вызывает InternalPost, второй – InternalCancel. Отметим, что перед вызовом каждого из них осуществляется вызов UpdateCursorPos – а это означает, что физический курсор будет совпадать с логическим. Методы Post и Cancel могут быть вызваны не только напрямую сторонним кодом, но и из других методов TDataSet: так, при попытке, пока TDataSet находится в состоянии редактирования (dsEdit) или вставки (dsInsert), переместить логический курсор на другую запись, операция вставки или редактирования будет либо отменена, либо подтверждена (в зависимости от того, внесены ли в запись изменения). Логика TDataSet полностью возлагает реализацию добавления/удаления/редактирования записей на Internal-методы; в частности, именно они определяют, в какую позицию набора будет при вставке помещаться вставляемая запись, будет ли она добавлена сразу (а при вызове InternalCancel – удалена) или будет существовать только в виде буфера (а физически добавляться при вызове InternalPost).
Серьезное обсуждение операции вставки невозможно без знакомства с аппаратом закладок.
Закладка (bookmark) – это некоторая информация, идентифицирующая положение записи в наборе данных. Предпочтительно представлять закладку ANSI-строкой символов (т.е. последовательностью ненулевых символов, завершающейся нулевым), т.к. клиент может пытаться читать ее из свойства Bookmark методом GetBookmarkStr именно как ANSI-строку (в примере от Borland закладка представляется 4-байтовым адресом записи, что, безусловно, неправильно, т.к. адрес может содержать нулевые байты). Свойство Bookmark возвращает закладку, соответствующую текущей записи. Впоследствии с помощью этой закладки можно очень быстро сделать текущей соответствующую ей запись. Закладка всегда указывает на одну и ту же запись, вне независимости от изменения положения этой записи в наборе.
Если задача потомка TDataSet – обмен данными с каким-либо сервером БД, то, скорее всего, сервер уже снабжен поддержкой закладок, и можно просто перенаправлять все запросы ему.
Кроме собственно закладки, каждой записи соответствует поле так называемого флага закладок. Записи соответствует ровно один флаг закладки, как и ровно одна закладка. Флаг закладки предназначен для того, чтобы логика TDataSet могла отличать одни типы записей от других. Помимо нормального флага bfCurrent, определены следующие значения:
bfInserted – “вставленная” запись (запись, подлежащая просмотру и редактированию, но еще не включенная в набор). bfBOF – запись, находящаяся перед первой записью (например, вставленная в пустой набор). Для установки физического курсора на запись с таким флагом закладки логика TDataSet вызывает InternalFirst вместо InternalSetToRecord; bdEOF – запись, находящаяся за последней записью. Вызывается InternalLast.Для клиента (т.е. внешнего по отношению к потомку TDataSet кода) работа с закладками сводится к двум операциям: чтению закладки для текущей записи и перемещению к записи, для которой известна закладка. Этим действиям соответствуют два метода TDataSet: GetBookmarkData и InternalGotoBookmark. Есть, впрочем, и третий метод, SetBookmarkData, служащий для изменения значения закладки текущей записи. Замена закладки исключительно плохо документирована. Разбор исходного текста класса TDataSet показывает, что код TDataSet вызывает SetBookmarkData в одном-единственном случае – перед вызовом InternalInsert новой записи приписывается та же закладка, что и предыдущей активной (если только вставляемая запись не окажется первой и единственной в наборе).
Смысл этого дублирования мне (после просмотра примера от Borland) видится таким: так как значение физического курсора, соответствующее предыдущей активной записи, неизвестно, а добавленная в набор данных запись может до вызова Post быть “висячей” (а значит, также не иметь соответствующего ей значения физического курсора), то предлагается вместо перехода на запись использовать переход на закладку. При этом до вызова Post предыдущая активная запись и новая запись имеют одинаковые закладки. Никакой коллизии здесь нет – при попытке получить закладку вставленной, но не добавленной в набор записи (у нее флаг закладки равен bfInserted, bfEOF или bfBOF), возвращается пустая строка (т.к. метод BookmarkAvailable может вернуть True лишь для записей с флагом закладки bfCurrent). То есть, с точки зрения внешнего по отношению к TDataSet кода, новая запись закладки не имеет. Это, кстати, следует учитывать в реализации поиска записи по закладке: поиск нужно проводить лишь по записям с флагом закладки bfCurrent.
При обработке InternalPost закладку у вставляемой записи нужно изменить на такую, которая позволит однозначно эту запись идентифицировать – ведь теперь ее флаг закладки равен bfCurrent, и если оставить старое значение закладки, то в этом наборе будут присутствовать несколько записей с одинаковыми закладками, но разными значениями физического курсора.
Как только мы начинаем реализовывать операцию вставки, сразу же встает важный вопрос: в какую позицию набора помещать добавляемую запись?
Вообще говоря, в TDataSet существует два способа добавления записи. Это методы Insert и Append (оба в итоге вызывают InternalInsert) – их назначение описано в справочной службе. Для обоих методов жестко определено лишь одно требование: логический курсор должен переместиться на добавленную запись. О том, в какую именно позицию вставляется новая запись каждым из методов, можно прочитать в справочной системе Delphi (каждая конкретная БД поступает по-своему).
В принципе, можно сделать так, чтобы не только по Append, но и по Insert записи всегда добавлялись в конец набора. Со вставкой в конец проблем обычно не возникает, т.к. в этом случае приходится перемещать физический и логический курсоры, а значит, начальное значение физического курсора можно не принимать во внимание. Добавление в конец, однако, не всегда приемлемо, и вдобавок вызывает неприятные визуальный эффект (особенно если мы одновременно обозреваем несколько соседних записей, как в DBControlGrid или DBGrid): как только курсор уходит с записи, она “прыгает” из зоны видимости в конец набора.
Поэтому имеет смысл задаться целью вставить запись именно в то место TDataSet, на которое при вызове Insert указывает логический курсор. Везде в дальнейшем предполагается, что вставка новой записи осуществляется как раз согласно этому правилу.
Сама по себе вставка проблемой не является и в этом случае. Проблема – в том, как корректно обрабатывать возникающую “виртуальную” запись, реально не существующую в наборе? А именно, как правильно обрабатывать команды на перемещение курсора на эту запись, или между ней, предыдущей и следующей записями.
Перемещение физического курсора может произойти при вызове одного из следующих методов: InternalFirst, InternalLast, GetRecord, SetRecNo, InternalSetToRecord и InternalGotoBookmark. Пока TDataSet находится в состоянии dsInsert – то есть до отмены или подтверждения вставки записи – один из этих методов может быть вызван только при вызове методов UpdateCursorPos, Resync, SetBufferCount или при записи в свойство Bookmark. В других случаях TDataSet предварительно выходит из состояния редактирования. Например, попытка выполнить метод First приведет еще до вызова InternalFirst к вызову либо Cancel, либо Post.
С SetRecNo есть небольшая тонкость: непонятно, имеет ли вставленная запись номер до того, как она будет включена в набор. Я буду предполагать, что не имеет. В противном случае пришлось бы в GetRecNo и SetRecNo сравнивать номер записи с номером вставленной записи и перемещение курсора производить в зависимости от результата. А раз не имеет, то работа SetRecNo не зависит от виртуальных записей.
Ясно, что методы InternalSetToRecord и InternalGotoBookmark для любой записи в наборе данных, кроме вставленной, будут работать так же хорошо, как работали без этой виртуальной записи (т.к. целевая запись – единственная идентифицируемая переданными в метод аргументами запись, реально существующая в наборе). То есть опасность может представлять лишь попытка перейти на саму редактируемую запись, и именно этот случай мы должны распознавать. Но куда же в такой ситуации помещать курсор?
Вызов InternalGotoBookmark приведет, т.к. закладка вставленной записи дублирует закладку какой-то уже имеющейся, к переходу на эту уже имеющуюся запись. Естественно ожидать, что так же должна себя вести и InternalSetToRecord. Для этого есть и более существенные основания – они будут рассмотрены чуть позже при анализе методов GetNextRecord и GetPriorRecord. Есть несколько способов добиться такого поведения InternalSetToRecord, их мы тоже рассмотрим позже.
Для UpdateCursorPos принципиально, для чего (перед какой операцией) осуществляется позиционирование курсора. Этот метод вызывается при ошибках перед Resync, а также: в Refresh, Cancel, AddRecord, Edit, Post, Delete. Вызов Edit в состоянии dsInsert игнорируется, вызов Delete вырождается в обращение к Cancel, а вызов AddRecord приводит еще до UpdateCursorPos к выходу из режима редактирования. Вызов перед Resync, так же, как и внутри Refresh, производится для того, чтобы Resync прочитал записи именно вокруг текущей. Вызов в Post дает InternalPost знать, в какое место набора данных вставляется запись. Вызов в Cancel позволяет InternalCancel сделать, например, вывод о том, какую запись нужно сделать текущей после отмены вставки.
С учетом этого для UpdateCursorPos курсор вполне корректно устанавливать на запись, перед которой осуществляется вставка. Действительно, в InternalPost легко по значению курсора, указывающему на следующую запись, понять, в какое место в наборе нужно поместить вставленную запись (отодвинув последующие): именно на это место. InternalCancel можно не перекрывать. Тогда после “вычеркивания” вставленной записи текущей станет запись, следующая за ней, что выглядит вполне логично. По поводу Resync см. замечания далее.
Как обеспечить правильную работу SetBufferCount? Если взглянуть на код этого метода, видно, что опасны лишь два ситуации:
Для метода GetNextRecord, вызывающего SetCurrentRecord, опасен случай, когда самый последний буфер содержит вставленную запись. Ясно, что в качестве “следующей” нужно прочитать запись, находящуюся в наборе за этой записью.
Для метода GetPriorRecord, также обращающегося к SetCurrentRecord, опасен случай, когда вставленная запись находится в первом буфере. Нужно прочитать запись, находящуюся в наборе перед вставленной.
Понятно, что на какую бы реально существующую в наборе запись не устанавливал физический курсор метод InternalSetToRecord, она не может находиться одновременно и прямо перед записью, следующей за вставленной, и сразу за записью, за которой следует вставленная.
В GetNextRecord имеется следующий код:
if (State = dsInsert) and (FCurrentRecord = FActiveRecord) and (GetBookmarkFlag(ActiveBuffer) = bfCurrent) then GetMode := gmCurrent;
Если бы вместо bfCurrent стояло fbInserted, эти строки можно было бы понять так: если TDataSet находится в состоянии редактирования, и имеет место случай (1), причем вставленная запись пока отсутствует в наборе, то читается не запись, следующая за той, на которую указывает физический курсор, а именно та, на которую физический курсор и указывает. Следовательно, здесь код TDataSet рассчитывает, что InternalSetToRecord при попытке установки физического курсора на вставленную запись будет устанавливать его на следующую за ней запись.
Однако в исходных текстах TDataSet написано именно bfCurrent, что очень меня удивляет. Возможно, я чего-то не понимаю, но не исключено, что это ошибка разработчиков из Borland. Последствия, к которым приводит эта недоработка, читатель легко может увидеть сам: нужно поместить на форму DBGrid с Align=alClient и DBNavigator с Align=alTop. Высота формы должна быть небольшой, чтобы в DBGrid была видна лишь одна запись БД. Если теперь пользователь добавит запись (щелчок по кнопке из DBNavigator) перед этой видимой записью, а затем увеличит высоту окна, то в DBGrid отобразятся не все записи, следующие за вставленной: не будет той записи, перед которой мы производим вставку. Конечно, необходимо для демонстрации связать с сеткой потомок TDataSet, позволяющий производить вставку в произвольное место набора – например, TADOTable.
Дело в том, что при увеличении высоты DBGrid, последний автоматически увеличит количество буферов в используемом TDataSet, чтобы иметь по одному буферу для каждой из одновременно отображаемых в сетке записей. Для этого будет вызван SetBufferCount, который забудет прочитать одну из записей (ту, перед которой происходит вставка).
Кстати, подобный эффект можно увидеть, если между созданием потомка TDataSet и отображением DBGrid выполнить у TDataSet метод Insert – одна из записей не будет показана в DBGrid.
А вот если бы вместо проверки на bfCurrent выполнялась проверка на bfInserted, все было бы в порядке.
ПРИМЕЧАНИЕИз этого следует, что идеально правильной работы, возможно, удастся добиться лишь таким образом: записи следует добавлять в набор сразу же при вызове InternalInsert, но не устанавливать для них флаг закладки bfCurrent вместо bfInserted (иначе GetNextRecord будет работать с ошибками в силу все того же фрагмента кода).
Проведенная мною небольшая серия испытаний пока не выявила противопоказаний к такому решению. Однако есть некоторые сомнения – все-таки это вмешательство в обычную схему работы TDataSet. Желающие могут провести самостоятельные эксперименты. Я сделал так: код, добавляющий запись, перенес из InternalPost в InternalInsert, в internalPost действия в состоянии dsInsert сделал такими же, как и в dsEdit, а InternalCancel просто свел к вызову InternalDelete.
Далее в этой работе я все-таки буду придерживаться обычной схемы, несмотря на содержащиеся в ней ошибки. Лучше уж ошибаться вместе со всеми, чем в гордом одиночестве.
Метод Resync, вообще говоря, также работает неправильно, но относительно него в файлах справки Delphi есть предупреждение, гласящее, что Resync предназначен для внутреннего использования и не должен вызываться извне TDataSet напрямую. Сам же TDataSet никогда и не вызывает Resync в состоянии dsInsert. Остается надеяться, что потребители нашего потомка TDataSet учтут предупреждение.
Итак, как же реализовать InternalSetToRecord?
Сложность заключается в том, что к моменту вызова InternalInsert метод UpdateCursorPos не был вызван, а буфер активной ранее записи уже перезаписан вызовом InternalInitRecord, так что, даже если мы вызовем UpdateCursorPos, он попытается вызвать InternalSetToRecord для перемещения физического курсора на запись, соответствующую данному буферу – но буфер-то теперь содержит новую информацию.
Попробуем придумать, как можно реализовать InternalSetToRecord таким образом, чтобы он успешно определял, где именно находится эта новая запись.
Есть два пути. Начнем с простого. Будем хранить в буфере записи значение физического курсора, при котором он (курсор) указывает на эту запись.
ПРИМЕЧАНИЕВ результате выполнения Post или Delete значения курсора, соответствующие записям, считанным в буферы, могут отличаться от значений, сохраненных в этих же буферах, но последующий вызов Resync (которым завершаются Post и Delete) прочитает эти записи заново, устранив расхождение.
Чтобы определить, какая запись была активной до вызова Insert, можно перекрыть метод DoBeforeInsert, который вызывается еще до изменения активного буфера, и в нем тем или иным образом запомнить значение физического курсора. Между вызовами DoBeforeInsert и InternalInsert не выполняется никаких действий, изменяющих положение физического курсора. В случае Append это не так, но тут нам может помочь другое. Если на момент вызова Insert набор не содержит записей, то после InitRecord для новой записи ставится флаг закладки bfBOF. Если вызван Append, ставится флаг fbEOF. В остальных случаях флаг остается таким, каким его сформировал InitRecord, то есть fbInserted. Поэтому при вызове InternalInsert можно поступить так: если флаг не равен bfInserted, то установить значение физического курсора, хранимое в буфере активной записи, так, как это по смыслу нужно для выполнения Append, в противном же случае – равным значению, выясненному в DoBeforeInsert. Конечно, это означает привязку к последовательности вызовов в логике TDataSet, и есть опасность несовместимости с грядущими новыми версиями VCL.
Второй вариант – его использует Borland в своих потомках TDataSet – заключается в сведении InternalSetToRecord к вызову InternalGotoBookmark. Модификация этого решения используется в компоненте TRxMemoryData из RX Library.
Основания таковы. Если запись имеет флаг закладки bfCurrent, то у нее есть закладка; если флаг bfInserted, то, хотя BookmarkAvailable для нее вернет False, значение закладки все-таки установлено и совпадает с закладкой записи, “отодвигаемой” при вставке; если bfBOF или bfEOF, то нам вообще беспокоиться не о чем – TDataSet вызовет не InternalSetToRecord, а InternalFirst или InternalLast.
В таком подходе есть один недостаток: когда мы отодвигаем одну из записей набора, чтобы вставить новую, закладки записей не должны изменяться – иначе весь механизм закладок окажется лишенным смысла, т.к. клиент не сможет перейти по ранее сохраненной закладке на соответствовавшую ей в момент получения закладки запись. С другой стороны, значения физического курсора, соответствующие отодвигаемым записям, должны измениться. Следовательно, есть два пути. Можно в паре с каждой закладкой хранить информацию (скрытую от внешнего по отношению к нашему потомку TDataSet кода), позволяющую вычислить значение физического курсора. В таком случае при добавлении/удалении записей нужно изменять внутреннее преставление по меньшей мере нескольких, а то и всех, закладок (в зависимости от того, насколько эффективные алгоритмы использовать и какую информацию хранить).
С другой стороны, в случае, если операций вставки производится существенно больше, чем операций поиска по закладке (путем присваивания свойству Bookmark, т.е. не считая вызовов InternalSetToRecord), может оказаться выгоднее (и уж точно – проще) искать записи последовательным перебором, а InternalSetToRecord реализовать так, как описано ранее (DoBeforeInsert и InternalInsert).
Разумеется, дилемма отпадает, если вставку всех записей производить как добавление в конец набора – тогда можно просто принять в качестве закладки значение физического курсора.
Чуть не забыл! Insert и Append – не единственные методы для вставки записей. Если имеется буфер записи, в который уже записаны значения полей, то можно добавить в набор запись с такими значениями полей вызовом InsertRecord. Этот метод обращается к AddRecord, а тот, в свою очередь, к InternalInsertRecord. Такой способ добавления записи тоже нужно обрабатывать.
Native-формат данныхПриведу обещанное описание Native-формата данных:
Данные для поля (каждому полю соответствует экземпляр потомка TField), имеющего DataType=ftInteger или ftLargeint, в Native-формате представлены просто в виде числа типа longint (или, что то же самое, Integer); данные для ftSmallint – числом типа Smallint, ftWord – типа Word. Для полей с DataType=ftString данные хранятся в виде последовательности символов, завершающейся #0 (символом с кодом 0); длина последовательности вместе с #0 не должна превышать значение_поля_Size + 1. Если поле Transliterate данного экземпляра TStringField равно True, то потомок TDataSet должен предоставить метод Translate для преобразования данных из кодировки символов, используемой в конкретном наборе данных, в кодировку ANSI (и обратно). В том виде, как он реализован в TDataSet, метод Translate просто возвращает исходную строку. Для полей с DataType=ftWideString данные хранятся в формате WideString. Для полей ftFloat и ftCurrency – в виде числа типа double. Для полей ftBoolean – в виде WordBool. Для полей ftDateTime, ftDate и ftTime – в виде TDateTime. Для полей с DataType=ftTimeStamp – в виде TSQLTimeStamp (модуль SqlTimSt). Для полей ftBytes и ftVarBytes – в виде последовательности байтов. Для полей ftBCD и ftFMTBCD – в виде TBcd (модуль FMTBcd). Для полей ftBLOB, ftMemo, ftGraphic данные так же, как и для ftBytes, хранятся в виде последовательности байтов; потомок TDataSet обязан предоставить метод CreateBlobStream для создания объекта-потомка TStream, обеспечивающего доступ к данным. Для полей ftReference – в виде массива байтов. Для полей ftVariant – в любом виде, совместимом с одним из представлений данных в Variant (см. описание Variant в справочной системе Delphi; подробное описание доступно в MSDN (ключевые слова, например, – “VARIANT and VARIANTARG”). Для полей ftInterface, ftIDispatch – в виде указателя на интерфейс. Для полей ftGuid – в виде строки, которая может быть расшифрована вызовом StringToGuid. Для полей ftADT и ftArray – определяются типами содержащихся в них полей (каждое поле читается отдельно). Для полей ftDataSet – насколько я понял, формат хранимых данных не имеет значения; потомок TDataSet обязан предоставить метод CreateNestedDataset для создания экземпляра нужного потомка TDataSet на основе содержащейся в данном поле информации. Упрощение интерфейсаКогда я только начинал писать эту статью, основной моей целью было показать, что интерфейс между DBGrid и, например, массивом можно наладить достаточно легко. Но, возможно, для таких простых ситуаций имеет смысл пойти дальше и разработать свой собственный интерфейс как надстройку над классом TDataSet, для обеспечения функционирования которого достаточно будет перекрыть всего десяток методов. Последовав советам, я решил вместо перекрывания методов ввести систему событий.
С учетом сказанного, второй пример будет состоять из двух частей: TMyDataSet – переходник, обеспечивающий упрощенное взаимодействие с методами TDataSet, используя обычный для Delphi механизм событий, и TMyData – класс, содержащий собственно адресную книгу и методы работы с ней, которые и назначаются в качестве реакций на события TMyDataSet.
Эту схему событий я решил основать на принципе обращения к записям по их последовательным номерам, отсчитываемым от единицы. На мой взгляд, это вполне оправданно, т.к. я адаптирую интерфейс TDataSet именно к простым наборам данных (таким, как списки или массивы), а для существенно более сложных наборов целесообразнее, возможно, работать с обычным интерфейсом TDataSet.
Вот введенные мной события:
FPerformOpenConnection: procedure(DS: TDataSet) of object; -Установление соединения с носителем набора данных FPerformCloseConnection: procedure(DS: TDataSet) of object; -Разрыв соединения FPerformFillFieldDefs: procedure(FieldDefsRef: TFieldDefs) of object; -Формирование списка определений полей(аргумент - объект - список полей) FPerformGetFieldData: procedure(Index: integer; Field: TField; out Data) of object; -Чтение значения поля Field записи с номером Index FPerformSetFieldData: procedure(Index: integer; Field: TField; var Data) of object; -Изменение значения поля PerformGetCanModify: function: boolean of object; -Определение того, можно ли вносить изменения в набор FPerformGetRecordCount: function: integer of object; -Определение количества записей в наборе FPerformDeleteRecord: procedure(Index: integer) of object; -Удаление записи FPerformClearFields: procedure(Index: integer) of object; -Очистка полей записи FPerformCreateFloatingRecord: function(SourceIndex: integer): integer of object; -Создание “плавающей” записи.Передается номер записи, значения полей которой нужно скопировать в “плавающую”(0, если копировать ничего не нужно) FPerformFreeFloatingRecord: procedure(Index: integer) of object; -Удаление “плавающей” записи с номером Index FPerformDefloatRecord: procedure(FlIndex, DestIndex: integer; DefloatMode: TDefloatMode) of object; -Помещение “плавающей” записи с номером Index в набор в позицию, занимаемую записью DestIndex.DefloatMode указывает, вставляется ли “плавающая” запись вместо DestIndex или перед ней FPerformGetBookmarkSize: function: integer of object; -Определение размера закладки FPerformGetBookmark: function(Index: integer): TBookmarkStr of object; -Опрос закладки FPerformSetBookmark: procedure(Index: integer; NewBookmark: TBookmarkStr) of object; -Установка закладки для записи FPerformFindBookmark: function(Bookmark: TBookmarkStr): integer of object; -Переход к записи по закладке
Работа осуществляется таким образом: класс, поставляющий обработчики событий (будем называть его поставщиком), берет на себя хранение данных и закладок, а также операции с записями по их номерам. Предлагаемый же потомок TDataSet отвечает за поддержание взаимно однозначного соответствия записей и их номеров и за перевод обычных последовательностей операций TDataSet в вызовы вышеперечисленных событий.
Особо следует пояснить понятие “плавающей” записи. Предполагается, что поставщик не считывает записи набора в какие-то свои кэш-буферы, а читает содержимое полей прямо из набора. В случае чтения это работает, но в случае редактирования записей производимые изменения следует где-то накапливать, то есть в этой ситуации временное хранилище создавать придется. Однако, есть способ упростить эту операцию. Хранилище можно (так как одновременно при таком подходе будет использоваться не более одного хранилища) реализовать в виде, например, какого-то зарезервированного элемента набора (массива, списка). Соответственно, можно сделать единообразным доступ как к записям набора, так и к этому хранилищу. Когда поступает запрос на начало редактирования существующей записи или на добавление новой, у поставщика запрашивается создание такого хранилища. В ответ поставщик возвращает число – это может быть, например, номер зарезервированной записи, или вообще любой номер такой, что в дальнейшем поставщик будет в состоянии понять, что этот номер относится именно к хранилищу. Таким образом, роль хранилища с точки зрения нашего потомка TDataSet будет играть запись с этим номером – ее мы и назовем “плавающей записью”.
Затем происходит либо очистка полей плавающей записи, либо (в случае редактирования существующей записи) копирование в эти поля значений полей исходной записи. В дальнейшем, пока изменения не будут подтверждены или отменены, вместо чтения и записи полей исходной будет производиться чтение и запись полей “плавающей”. При отмене изменений у поставщика запрашивается освобождение “плавающей” записи, при подтверждении – вызывается FPerformDefloatRecord (см. описание этого события).
Отмечу небольшое затруднение с подменой чтения/записи исходной записи на чтение/запись плавающей. Как уже говорилось, некоторые элементы управления в цикле, в котором они опрашивают поля записей (что приводит к вызову GetFieldData), изменяют единственную реализацию логического курсора в TDataSet, указатель ActiveBuffer (присваивая ему затем прежнее значение). Поэтому для того, чтобы определить, потребовали ли от TDataSet доступ именно к редактируемой записи, адрес буфера редактируемой записи надо в момент начала редактирования сохранить в какой-либо переменной, а в GetFieldData сравнивать значение ActiveBuffer с этим сохраненным значением.
Все вышесказанное естественным образом воплощается в примерно такой код:
Листинг 3 TMyDataSet с возможностью редактирования
unit DataSet3;
interface
uses DB, Classes {for TComponent};
type
TDBCursor = integer; //Тип данных для физического курсора - номер записи
TDefloatMode = (dmInsert, dmOverwrite);
TRecordBuffer = packed record
RecordIndex: TDBCursor; //Номер записи (считая от 1)
BookmarkFlag: TBookmarkFlag;
end;
PRecordBuffer = ^TRecordBuffer;
TMyDataSet = class(TDataset)
private
FIsOpen: boolean;
FCursor: integer;
FInsertingBefore: integer;
FFloatingRecordIndex: integer;
FEditingBuffer: pchar; //Хранит адрес буфера для редактируемой записи
//При редактировании большую часть времени совпадает с ActiveBuffer,
//но в процессе отрисовки связанных элементов управления последний
//временно изменяется
protected
procedure InternalHandleException; override;
procedure InternalInitFieldDefs; override;
procedure InternalOpen; override;
function IsCursorOpen: Boolean; override;
procedure InternalClose; override;
procedure SetActive(Value: Boolean); override; //Вызывает Finalize при
// закрытии TDataSet
procedure Finalize; virtual; //логическое завершение текущих операций
//(вызов Close/Post и т.п.)
function GetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean): TGetResult; override;
function AllocRecordBuffer: PChar; override;
procedure FreeRecordBuffer(var Buffer: PChar); override;
procedure InternalFirst; override;
procedure InternalLast; override;
procedure InternalSetToRecord(Buffer: PChar); override;
function GetCanModify: Boolean; override;
procedure InternalEdit; override;
procedure InternalCancel; override;
procedure DoBeforeInsert; override;
procedure InternalInsert; override;
procedure InternalInitRecord(Buffer: PChar); override;
procedure InternalPost; override;
procedure InternalDelete; override;
function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
override;
procedure InternalAddRecord(Buffer: Pointer; DoAppend: Boolean);
override;
procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
procedure InternalGotoBookmark(Bookmark: Pointer); override;
function GetRecordCount: Integer; override;
procedure SetRecNo(Value: Integer); override;
function GetRecNo: Integer; override;
public
constructor Create(AOwner: TComponent); override;
function GetFieldData(Field: TField; Buffer: Pointer): Boolean;
override;
procedure SetFieldData(Field: TField; Buffer: Pointer); override;
function BookmarkValid(Bookmark: TBookmark): Boolean; override;
public //Конечно, это лучше реализовывать в виде свойств, но в целях
// экономии места я сделал эти процедурные переменные полями
FPerformOpenConnection: procedure(DS: TDataSet) of object;
FPerformCloseConnection: procedure(DS: TDataSet) of object;
FPerformFillFieldDefs: procedure(FieldDefsRef: TFieldDefs) of object;
FPerformGetFieldData: procedure(Index: integer; Field: TField; out Data)
of object;
FPerformSetFieldData: procedure(Index: integer; Field: TField; var Data)
of object;
FPerformGetCanModify: function: boolean of object;
FPerformGetRecordCount: function: integer of object;
FPerformDeleteRecord: procedure(Index: integer) of object;
FPerformClearFields: procedure(Index: integer) of object;
FPerformCreateFloatingRecord: function(SourceIndex: integer): integer
of object;
FPerformFreeFloatingRecord: procedure(Index: integer) of object;
FPerformDefloatRecord: procedure(FlIndex, DestIndex: integer;
DefloatMode: TDefloatMode) of object;
FPerformGetBookmarkSize: function: integer of object;
FPerformGetBookmark: function(Index: integer): TBookmarkStr of object;
FPerformSetBookmark: procedure(Index: integer; NewBookmark: TBookmarkStr)
of object;
FPerformFindBookmark: function(Bookmark: TBookmarkStr): integer
of object;
end;
implementation
uses SysUtils, forms;
procedure TMyDataSet.InternalHandleException;
begin
Application.HandleException(Self)
end;
procedure TMyDataSet.InternalInitFieldDefs;
begin
if Assigned(FPerformFillFieldDefs) then
FPerformFillFieldDefs(FieldDefs)
else
FieldDefs.Clear;
end;
procedure TMyDataSet.InternalOpen;
begin
InternalInitFieldDefs;
if DefaultFields then
CreateFields;
BindFields(true);
FIsOpen := true;
FCursor := 0;
if Assigned(FPerformGetBookmarkSize) then
BookmarkSize := FPerformGetBookmarkSize()
else
BookmarkSize := 0;
if Assigned(FPerformOpenConnection) then
FPerformOpenConnection(self);
end;
function TMyDataSet.IsCursorOpen: Boolean;
begin
result := FIsOpen
end;
procedure TMyDataSet.InternalClose;
begin
if Assigned(FPerformCloseConnection) then
FPerformCloseConnection(self);
BindFields(False);
if DefaultFields then
DestroyFields;
FIsOpen := false;
end;
procedure TMyDataSet.InternalFirst;
begin
FCursor := 0
end;
procedure TMyDataSet.InternalLast;
begin
FCursor := RecordCount + 1
end;
function TMyDataSet.GetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean): TGetResult;
begin
result := grOK;
case GetMode of
gmPrior: if FCursor <= 1 then
result := grBOF
else
Dec(FCursor);
gmNext: if FCursor >= RecordCount then
result := grEOF
else
Inc(FCursor);
gmCurrent: if (FCursor < 1) or (FCursor > RecordCount) then
Result := grError;
end;
if result = grOK then
with PRecordBuffer(Buffer)^ do
begin
RecordIndex := FCursor;
BookmarkFlag := bfCurrent;
end;
if (result = grError) and DoCheck then
DatabaseError('Ошибка в GetRecord()');
end;
function TMyDataSet.GetRecordCount: Integer;
begin
if Assigned(FPerformGetRecordCount) then
Result := FPerformGetRecordCount()
else
raise EDatabaseError.Create('Для работы с ' + ClassName
+ ' требуется задать обработчик события OnGetRecordCount!');
end;
procedure TMyDataSet.SetRecNo(Value: Integer);
begin
CheckBrowseMode;
if (Value < 1) or (Value >= RecordCount + 1) then
exit;
FCursor := Value;
Resync([])
end;
function TMyDataSet.GetRecNo: Integer;
begin
Result := PRecordBuffer(ActiveBuffer)^.RecordIndex + 1
end;
procedure TMyDataSet.InternalSetToRecord(Buffer: PChar);
begin
FCursor := PRecordBuffer(Buffer)^.RecordIndex;
end;
function TMyDataSet.AllocRecordBuffer: PChar;
begin
GetMem(result, sizeof(TRecordBuffer))
end;
procedure TMyDataSet.FreeRecordBuffer(var Buffer: PChar);
begin
FreeMem(Buffer)
end;
procedure TMyDataSet.InternalInitRecord(Buffer: PChar);
//Вообще-то, это просто операция очистки записи.
//Так как наш буфер не содержит указателей на динамически создаваемые
//структуры данных, то нам не нужно удалять структуры, связанные с
//предыдущим содержимым буфера. Но вот для поставщика это может быть
//необходимо, и его надо подробно уведомить о ситуации
begin
//Если это повторный вызов (когда TDataSet уже находится в одном из режимов
//редактирования) - такой выполняется при ClearFields - то надо просто
//очистить запись, в противном случае TDataSet мы хотим вставить новую
//запись, и поставщик должен сначала создать “плавающую” запись.
if not (State in dsEditModes) then
if Assigned(FPerformCreateFloatingRecord) then
FFloatingRecordIndex := FPerformCreateFloatingRecord(0);
if Assigned(FPerformClearFields) then
FPerformClearFields(PRecordBuffer(ActiveBuffer)^.RecordIndex);
end;
function TMyDataSet.GetCanModify: Boolean;
begin
if assigned(FPerformGetCanModify) then
result := FPerformGetCanModify
else
result := false
end;
function TMyDataSet.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
var
Index: integer;
begin
result := assigned(FPerformGetFieldData);
if (State in dsEditModes) and (ActiveBuffer = FEditingBuffer) then
Index := FFloatingRecordIndex //перенаправляем к “плавающей” записи
else
Index := PRecordBuffer(ActiveBuffer)^.RecordIndex;
if result then
FPerformGetFieldData(Index, Field, Buffer^);
end;
constructor TMyDataSet.Create(AOwner: TComponent);
begin
inherited;
FIsOpen := false;
if assigned(FPerformGetBookmarkSize) then
BookmarkSize := FPerformGetBookmarkSize()
else
BookmarkSize := 0;
end;
function TMyDataSet.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;
begin
result := PRecordBuffer(Buffer)^.BookmarkFlag;
end;
procedure TMyDataSet.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
begin
PRecordBuffer(Buffer)^.BookmarkFlag := Value;
end;
procedure TMyDataSet.InternalPost;
begin
inherited; //=CheckRequiredFields
case State of
dsEdit:
if Assigned(FPerformDefloatRecord) then
FPerformDefloatRecord(FFloatingRecordIndex, FCursor, dmOverwrite);
dsInsert:
begin
if Assigned(FPerformDefloatRecord) then
FPerformDefloatRecord(FFloatingRecordIndex, FCursor, dmInsert);
PRecordBuffer(ActiveBuffer)^.RecordIndex := FCursor;
PRecordBuffer(ActiveBuffer)^.BookMarkFlag := bfCurrent;
end;
end;
end;
procedure TMyDataSet.SetFieldData(Field: TField; Buffer: Pointer);
begin
//Для этой операции Index всегда является индексом “плавающей” записи
//Перенаправляем изменения в Floating record
if Assigned(FPerformSetFieldData) then
FPerformSetFieldData(FFloatingRecordIndex, Field, Buffer^);
DataEvent(deFieldChange, cardinal(Field));
end;
procedure TMyDataSet.InternalDelete;
begin
if not Assigned(FPerformGetRecordCount) then
raise EDatabaseError.Create('Для работы с ' + ClassName
+ ' требуется задать обработчик события PerformGetRecordCount!');
if Assigned(FPerformDeleteRecord) then
FPerformDeleteRecord(PRecordBuffer(ActiveBuffer)^.RecordIndex);
if FCursor = FPerformGetRecordCount() then
Dec(FCursor);
end;
procedure TMyDataSet.InternalAddRecord(Buffer: Pointer; DoAppend: Boolean);
//Согласно справке, при вызове InsertRecord активной становится вставленная
//запись. Причем можно отследить, что заботиться об этом должна
//InternalAddRecord. Поэтому в случае DoAppend просто прыгаем в конец
//(вернее, за конец)
begin
//В момент вызова уже создана Floating-запись, и в нее скопированы значения
if DoAppend then
InternalLast;
FPerformDefloatRecord(FFloatingRecordIndex, FCursor, dmInsert);
end;
procedure TMyDataSet.GetBookmarkData(Buffer: PChar; Data: Pointer);
var
res: string;
begin
if BookmarkSize = 0 then
exit;
if Assigned(FPerformGetBookmark) then
res := FPerformGetBookmark(PRecordBuffer(Buffer)^.RecordIndex)
else
res := '';
strpcopy(Data, res);
end;
procedure TMyDataSet.SetBookmarkData(Buffer: PChar; Data: Pointer);
begin
if Assigned(FPerformSetBookmark) then
FPerformSetBookmark(PRecordBuffer(Buffer)^.RecordIndex, string(pchar(Data)))
end;
procedure TMyDataSet.InternalGotoBookmark(Bookmark: Pointer);
var
Pos: integer;
begin
if not assigned(FPerformFindBookmark) then
exit;
Pos := FPerformFindBookmark(string(Bookmark));
if Pos > -1 then
FCursor := Pos;
end;
function TMyDataSet.BookmarkValid(Bookmark: TBookmark): Boolean;
begin
result := assigned(FPerformFindBookmark);
if result then
result := (-1 <> FPerformFindBookmark(string(Bookmark)))
end;
procedure TMyDataSet.DoBeforeInsert;
begin
inherited;
FInsertingBefore := PRecordBuffer(ActiveBuffer)^.RecordIndex;
end;
procedure TMyDataSet.InternalInsert;
begin
FEditingBuffer := ActiveBuffer;
//Чтобы InternalSetToRecord переходил на правильную позицию
with PRecordBuffer(ActiveBuffer)^ do
if BookmarkFlag = bfInserted then
RecordIndex := FInsertingBefore;
//А если bfEOF или bfBOF, то InternalSetToRecord и не будет вызываться
end;
procedure TMyDataSet.InternalCancel;
begin
if Assigned(FPerformFreeFloatingRecord) then
FPerformFreeFloatingRecord(FFloatingRecordIndex);
end;
procedure TMyDataSet.InternalEdit;
begin
FEditingBuffer := ActiveBuffer;
if Assigned(FPerformCreateFloatingRecord) then
FFloatingRecordIndex :=
FPerformCreateFloatingRecord(PRecordBuffer(ActiveBuffer)^.RecordIndex);
end;
procedure TMyDataSet.SetActive(Value: Boolean);
begin
if (Value <> Active) and (Value = false) then
Finalize;
inherited;
end;
procedure TMyDataSet.Finalize;
begin
Cancel; //вдруг что-то редактировалось
end;
end.
А поставщик в нашем примере с адресной книгой может быть для такого упрощенного интерфейса реализован весьма несложно, например, так:
Листинг 4
unit MyData;
interface
uses Classes, DB, DataSet3, SysUtils;
type
TMyRecord = class
Name: string;
EMail: string;
Bookmark: pointer; //В нормальном состоянии - указатель на саму запись
constructor Create(sName, sEMail: string);
procedure Change(sName, sEMail: string);
end;
TMyData = class
List: TList;
constructor Create; reintroduce;
destructor Destroy; override;
procedure FillFieldDefs(FieldDefsRef: TFieldDefs);
procedure GetFieldData(Index: integer; Field: TField; out Data);
procedure SetFieldData(Index: integer; Field: TField; var Data);
function GetCanModify: boolean;
function GetRecordCount: integer;
procedure DeleteRecord(Index: integer);
procedure ClearFields(Index: integer);
function CreateFloatingRecord(SourceIndex: integer): integer;
procedure DefloatRecord(FlIndex, DestIndex: integer;
DefloatMode: TDefloatMode);
function GetBookmarkSize: integer;
function GetBookmark(Index: integer): TBookmarkStr;
procedure SetBookmark(Index: integer; NewBookmark: TBookmarkStr);
function FindBookmark(Bookmark: TBookmarkStr): integer;
end;
implementation
procedure TMyRecord.Change(sName, sEMail: string);
begin
Name := sName;
EMail := sEMail
end;
constructor TMyRecord.Create(sName, sEMail: string);
begin
inherited Create;
Change(sName, sEMail);
Bookmark := self;
end;
{ TMyData }
constructor TMyData.Create;
begin
List := TList.Create;
List.Add(TMyRecord.Create('Temporary', 'Temporary'));
//Первый элемент - место под “плавающую” запись
List.Add(TMyRecord.Create('Name1', 'email1'));
List.Add(TMyRecord.Create('Name2', 'email2'));
List.Add(TMyRecord.Create('Name3', 'email3'));
end;
destructor TMyData.Destroy;
var
i: integer;
begin
for i := 0 to List.Count - 1 do
TMyRecord(List[i]).free;
List.Free;
inherited;
end;
procedure TMyData.ClearFields(Index: integer);
begin
TMyRecord(List[0]).Name := '(name)';
TMyRecord(List[0]).EMail := '(email)';
end;
function TMyData.CreateFloatingRecord(SourceIndex: integer): integer;
begin
result := 0;
if SourceIndex <> 0 then
with TMyRecord(List[SourceIndex]) do
begin
TMyRecord(List[0]).Name := Name;
TMyRecord(List[0]).EMail := EMail
end;
end;
procedure TMyData.DefloatRecord(FlIndex, DestIndex: integer;
DefloatMode: TDefloatMode);
begin
if DefloatMode = dmInsert then
List.Insert(DestIndex, TMyRecord.Create(
TMyRecord(List[0]).Name, TMyRecord(List[0]).EMail))
//При этом автоматически ставится новый Bookmark
else
with TMyRecord(List[DestIndex]) do
begin
Name := TMyRecord(List[0]).Name;
Email := TMyRecord(List[0]).EMail
end;
end;
procedure TMyData.FillFieldDefs(FieldDefsRef: TFieldDefs);
begin
FieldDefsRef.Clear;
with FieldDefsRef.AddFieldDef do
begin
DataType := ftString;
FieldNo := 1;
Name := 'Имя';
Size := 40;
end;
with FieldDefsRef.AddFieldDef do
begin
DataType := ftString;
FieldNo := 2;
Name := 'E-Mail';
Size := 50;
end;
end;
procedure TMyData.DeleteRecord(Index: integer);
begin
List.Delete(Index)
end;
function TMyData.GetCanModify: boolean;
begin
result := true
end;
procedure TMyData.SetFieldData(Index: integer; Field: TField; var Data);
begin
case Field.FieldNo of
1: TMyRecord(List[Index]).Name := strpas(@Data);
2: TMyRecord(List[Index]).Email := strpas(@Data);
end;
end;
procedure TMyData.GetFieldData(Index: integer; Field: TField; out Data);
begin
case Field.FieldNo of
1: strpcopy(@Data, TMyRecord(List[Index]).Name);
2: strpcopy(@Data, TMyRecord(List[Index]).EMail);
end;
end;
function TMyData.GetRecordCount: integer;
begin
result := List.Count - 1; {0-й элемент не считаем}
end;
function TMyData.GetBookmarkSize: integer;
begin
result := 10; {'$xxxxxxxx'+NULL}
end;
procedure TMyData.SetBookmark(Index: integer; NewBookmark: TBookmarkStr);
begin
TMyRecord(List[Index]).BookMark := pointer(strtoint(NewBookmark))
end;
function TMyData.FindBookmark(Bookmark: TBookmarkStr): integer;
var
i: integer;
p: pointer;
begin
p := pointer(strtoint(pchar(Bookmark)));
result := -1;
for i := 1 to List.Count - 1 do
if TMyRecord(List[i]).Bookmark = p then
begin
result := i;
break
end;
end;
function TMyData.GetBookmark(Index: integer): TBookmarkStr;
begin
result := '$' + IntToHex(cardinal(TMyRecord(List[Index]).BookMark), 8)
end;
end.
Для проверки работы можно использовать все тот же небольшой проект, включив в Uses модуль DataSet3 вместо DataSet0, а также модуль MyData, и добавив подключение к нашему экземпляру TMyDataSet определенных в модуле MyData обработчиков событий. Для этого, конечно, потребуется создать экземпляр TMyData.
ЗаключениеНеобходимо заметить, что приведенные примеры имеют своей целью лишь показать в минимальном объеме возможную реализацию потомка TDataSet и один из способов его адаптации к простым задачам, и ни в коей мере не претендуют на качественное решение возникающих задач (например, приведенный способ поиска записи по закладке на практике при больших объемах данных может оказаться непомерно медленным). Тем не менее, примеры рабочие.
Приведенной информации, на мой взгляд, достаточно, чтобы адаптировать свои структуры данных для работы с TDataSource и DB-Aware элементами управления. Конечно, на практике могут встретиться ситуации, требующие дополнительных усилий и реализации еще каких-либо действий (например, поиска записи по значению части ее полей, фильтрации записей и т.п.); смею надеяться, однако, что выполненная мною работа поможет и в этом, несколько развеяв туман вокруг внутренних взаимосвязей методов TDataSet и восполнив катастрофических масштабов недостаток документации по этой теме.
Как создать БД в кодировке CP1251.
Автор: NomadicВот такая конструкция проходит на DB2 2.1.2/NT и UDB5/NT...
CREATE DATABASE Efes2 USING CODESET 1251 TERRITORY RU COLLATE USING IDENTITY; |
Курсоры в ADO.
| Программы пропили всё... Даже курсор от мышки... |
В Delphi компоненты ADOExpress довольно приятны в использовании. Однако программирование компонент ADOExpress весьма отличается от традиционного Delphi программирования в компонентах TTable и TQuery, основанных на BDE. Естевственно, что если Вы привыкли к компонентам BDE dataset, то сразу же заметите различие в количестве возможностей и свойств, а так же в стиле программирования BDE и ADO.
В основе ADO лежит объект Recordset (aka Dataset). Этот объект является результатом Query команды (например, выражение SELECT компонента TADOQuery). Когда ADO-приложение получает строки из базы данных, то объект ADO Recordset формирует необходимую информацию и операции, допустимые для получаемых данных. При этом ADO использует курсоры, чтобы хранить набор строк для обработанной записи. Так же курсор содержит в себе текущую позицию в записи (recordset). Обычно, при разработке приложения, курсоры используются при создании записей, а так же при перемещении по записям (вперёд или назад).
В ADO курсоры имеют три функции. Первая, это тип курсора, определющая допустимые перемещения в пределах курсора, а так же будут ли отражаться изменения пользователей в записи. Вторая, это местоположение курсора, определяющая место хранения записи, в то время, пока курсор остаётся открытым. Третья, это тип блокировки курсора, которая определяет как ADO datastore будет блокировать строки, если Вам потребуется внести изменения.
Каждый раз когда мы будем открывать определённую запись ADO, то мы будем открывать её с определённым типом курсора.
Класс TCustomADODataSet содержит набор свойств, событий и методов, для работы с данными, доступными через ADO datastore. Все классы-потомки от TCustomADODataSet (такие как TADODataSet, TADOTable, TADOQuery, и TADOStoredProc) совместно используют некоторые общие свойства. В каждом из этих классов присутствуют три свойства, соответствующие функциям курсоров, описанным выше: CursorType, CursorLocation, и LockType. Давайте рассмотрим эти свойства по-подробнее.
CursorType ADO содержит четы опции для данного типа курсора: dynamic, keyset, forward-only и static. Так как каждый тип курсора ведёт себя работает по разному, то несомненно Вы извлечёте пользу из каждого из этих видов курсоров. Свойство CursorType указывает на то, каким образом Вы будете перемещаться по записям а так же какие изменения будут видны в записях базы данных, после того как Вы получите из неё данные. В Delphi классах ADO типы курсоров задаются в TCursorType. ctDynamic Позволит Вам видеть добавления, изменения и удаления, сделанные другими пользователями, а также позволит делать все типы перемещения по записи (Recordset) не полагаясь на закладки. Однако закладки можно использовать, если они поддерживаются. Для этого существует метод Supports в ADODataset, который сигнализирует о поддержке определённых типов операций. Следующее выражение позволяет определить, поддерживаются закладки или нет:
if ADOTable1.Supports(coBookmark) then ... |
Часто задаваемые вопросы по базам данных.
Оформил: DeeCo
Здесь представлены ответы на наиболее типичные вопросы, возникающие при проектировании баз данных.
Содержание Разновидности СУБД Q1: Что такое СУБД? Q2: Чем отличаются «серверная» и «настольная» СУБД? Q3: Что такое реляционная база данных? Q4: Кто такой SQL-сервер? Q5: Какой SQL-сервер лучше всего использовать? Q6: Что такое объектно-ориентированная СУБД? Q7: Какие объектно-ориентированные СУБД существуют в природе? Q8: Что такое пост-реляционная база данных? Q9: Какие еще бывают разновидности СУБД? Сервер и клиент Q1: Что такое сервер баз данных? Q2: Что такое клиент? Q3: Как клиент общается с сервером? Q4: В чем отличие «многозвенной» архитектуры от «двухзвенной»? Q5: Зачем нужен сервер приложений? Q6: Чем отличается «тонкий» клиент от «толстого»? Реляционные базы данных: Теория Q1: Как данные хранятся в реляционной базе? Q2: Как записи отличаются друг от друга? Q3: Каким образом таблицы связаны между собой? Q4: Что такое нормализация? Q5: Имеет ли значение порядковый номер записи в таблице? Реляционные базы данных: Особенности SQL-серверов Q1: Что можно делать при помощи SQL? Q2: Зачем нужны транзакции? Q3: Что такое журнал транзакций? Q4: Когда появляются блокировки? Q5: Чем отличаются «версионники» от «блокировочников»? Q6: Почему возникает deadlock? Q7: Зачем нужны индексы? Q8: Для чего необходим первичный ключ в таблице? Q9: Что такое триггер? Q10: Можно ли использовать свою функцию в SQL-запросе? Q11: Как автоматически генерировать первичный ключ? Q12: Какую функцию выполняет оптимизатор запросов? Q13: Что такое представление? Q14: Для чего нужны хранимые процедуры? Q15: Какие типы данных есть в SQL-сервере? Q16: Чему равен null? Q17: Зачем нужен внешний ключ? Q18: Существует ли стандарт SQL, общий для всех серверов? Q19: Есть ли стандартное клиентское API для доступа к SQL-серверам? Q20: Что такое репликация? Реляционные базы данных: Практические вопросы Q1: Что такое суррогатный ключ, и чем он лучше или хуже естественного ключа? Q2: Можно ли полностью избежать дублирования информации? Q3: Хотелось бы иметь возможность восстанавливать удаленные записи. Как это сделать? Q4: Как организовать хранение истории изменений записей, с возможностью просмотра и отката этих изменений? Q5: Есть объекты, которые меняются с течением времени. Нужно получать их состояния на любую дату. Каким образом для этого надо организовать хранение объектов в базе? Q6: Каким образом можно хранить в реляционной базе иерархические объекты? Q7: Как получить полную информацию об определенной таблице в базе (какие поля есть в таблице, их тип, размер, связи с другой таблицей и т.п.)? Q8: Необходимо хранить в базе много картинок (файлов). Как лучше всего это сделать? Q9: Есть запрос, который возвращает данные, отсортированные по какому-либо признаку. Можно ли получить не весь результат, а только определенную его часть, например, записи с 20 по 40? Q10: Как из таблицы получить записи, в которых одно из полей максимально (или минимально)? Q11: Можно ли сделать в SQL перекрестный запрос? То есть, в качестве полей для результата, брались бы не поля таблицы, а данные из нее? Q12: Как обеспечить нумерацию записей в результате запроса? Q13: Есть необходимость добавлять произвольные виды объектов (еще неизвестные на этапе разработке) в БД. Создавать и менять таблицы на «живой» базе не хочется. Как быть? Дополнительно Стандарные аббревиатуры Литература Разновидности СУБД Q1: Что такое СУБД?A1: СУБД — Система Управления Базами Данных (DBMS — DataBase Management System). Программа, либо комплекс программ, предназначенных для полнофункциональной работы с данными. Как правило, включает в себя инструменты для создания и изменения структуры хранения наборов данных, а также средства доступа к хранимым данным, с возможностью их чтения, добавления, изменения и удаления. При этом, у большинства СУБД имеется собственный встроенный язык (возможно не один) для работы с данными. Сама база данных (БД) обычно находится просто в файлах закрытого, либо открытого формата.
Q2: Чем отличаются «серверная» и «настольная» СУБД?A2: Под настольной (desktop) обычно подразумевается СУБД, которая всегда запускается на компьютере пользователя, хотя сама база данных может находиться в другом месте. В результате несколько копий СУБД могут обращаться к одной базе данных. Серверная (server) СУБД, как правило, запускается в на той же машине (сервере баз данных), где находятся файлы БД. Непосредственно к базе данных обращается лишь один экземпляр СУБД. Пользовательские приложения общаются только с этой СУБД через ее API, независимо от того, работают они на той же машине или на другой. Для многопользовательских баз данных более эффективным и надежным вариантом является серверная СУБД. В ней гораздо быстрее происходит доступ к данным, и значительно проще решаются конфликты между разными пользователями.
Q3: Что такое реляционная база данных?A3: Реляционная (relational) БД отличается способом представления информации, находящейся в ней. Данные в такой базе хранятся в плоских таблицах. Каждая таблица имеет собственный, заранее определенный набор именованных колонок (полей). Поля таблицы обычно соответствуют атрибутам сущностей, которые необходимо хранить в базе. Количество строк (записей) в таблице неограниченно, и каждая запись соответствует отдельной сущности. Каждая таблица должна иметь первичный ключ (ПК) — поле или набор полей, содержимое которых однозначно определяет запись в таблице и отличает ее от других. Связь между двумя таблицами обычно образуется при добавлении в первую таблицу поля, содержащего значение первичного ключа второй таблицы. Реляционные СУБД (РСУБД) предоставляют средства для всевозможных пересечений и объединений любых таблиц, отбора записей по разнообразным условиям, группировки и сортировки результатов. Реляционная база данных сочетает наглядность представления информации с простотой (относительной) реализации своей концепции и является наиболее популярной структурой для хранения данных на сегодняшний день. Достаточно подробно реляционные БД описаны в книгах [1], [2] и [3].
Q4: Кто такой SQL-сервер?A4: Сервер для управления реляционными БД обычно называют SQL-сервером. SQL (Structured Query Language — язык структурированных запросов) является стандартным языком для работы с реляционными БД. Кроме стандартных реляционных операций, этот язык предоставляет возможности для изменений структуры таблиц. Различные варианты SQL используются во всех, как серверных, так и в настольных реляционных СУБД.
Q5: Какой SQL-сервер лучше всего использовать?A5: На какой хватит денег.:) Вообще-то это может сильно зависеть от постановки задачи, количества пользователей и прихотей заказчика. Ниже приведена таблица самых распространенных SQL-серверов в порядке (примерно) убывания их возможностей:
Сервер Достоинства Недостатки IBM DB2 Universal Database Самый навороченный язык запросов, лучший оптимизатор, возможность писать функции на других языках. Высокая стоимость. Oracle Database Великое множество дополнительных возможностей. Версионный сервер. Очень высокая стоимость сервера и поддержки. Microsoft SQL Server Быстро развивающийся продукт, уже вплотную приближающийся к своим более развитым конкурентам. Средняя стоимость. Существует только для одной платформы (Win32). IBM Informix Dynamic Server Довольно развитый быстрый сервер. — Sybase Adaptive Server Enterprise Достаточно развитый сервер. Средняя стоимость. — Sybase Adaptive Server Anywhere Существует под множество платформ, включая самые экзотичные. Низкая стоимость. — Borland InterBase Приличный набор возможностей. Версионный сервер. Бесплатный. Относительно медленно работает. PostgreSQL Поддерживает историческую модель. Возможность создавать свои типы данных. Бесплатный. — MySQL Быстро работает на простых запросах. Бесплатный. Очень бедный язык запросов. Мало дополнительных возможностей.
Q6: Что такое объектно-ориентированная СУБД?A6: В объектно-ориентированных БД (ООБД), данные представлены в виде объектов различных классов. Как правило, имеются возможности создавать новые классы, наследовать их от уже имеющихся, задавать произвольные атрибуты и методы для классов. Для доступа к объектам, в каждой ООБД обычно предусматривается свой собственный язык, либо расширение другого языка. Пока еще ООБД недостаточно развиты и не представляют серьезной конкуренции SQL-серверам, хотя и выглядят более предпочтительными для разработчиков. Производители SQL-серверов тоже, в свою очередь, иногда делают попытки соорудить над реляционным ядром сервера объектно-ориентированную надстройку. Более подробно ООБД описаны в статье [4].
Q7: Какие объектно-ориентированные СУБД существуют в природе?A7: Достаточно распространены следующие ООБД: Cache FastObjects GemStone/S Jasmine ObjectStore Objectivity/DB Versant
Q8: Что такое пост-реляционная база данных?A8: Пост-реляционными, часто называют многомерные базы данных. Данные в многомерных базах, представляются в виде разреженных многомерных массивов, а не плоских таблиц, как в реляционных базах. Для определенных задач, многомерные базы могут давать значительный выигрыш в быстродействии, по сравнению с реляционными. Наиболее известные многомерные СУБД: Cache Teradata
Q9: Какие еще бывают разновидности СУБД?A9: Кроме реляционных, объектно-ориентированных и многомерных СУБД, также давно известны иерархические и сетевые базы данных. Данные и связи между ними, в иерархических БД представлены в виде деревьев. Для некоторых задач, такая форма представления данных может оказаться гораздо более эффективной, чем любая другая. В сетевых базах, данные могут быть связаны произвольным образом, но эти связи должны создаваться предварительно, вместе со структурой данных. По сравнению с реляционными БД, сетевая модель может давать выигрыш в быстродействии, при некоторой потере гибкости.
Сервер и клиент Q1: Что такое сервер баз данных?A1: Под сервером БД обычно подразумевается СУБД, запущенная на той же машине, где находятся файлы БД, и монопольно распоряжающаяся этими файлами. При этом, все пользовательские приложения должны работать с базой только через эту СУБД, используя ее язык запросов.
Q2: Что такое клиент?A2: Клиентом к БД, обычно называют пользовательское приложение, которое общается с сервером БД. Модель работы, в которой клиент общается непосредственно с сервером, не используя промежуточных приложений, называется архитектурой клиент-сервер.
Q3: Как клиент общается с сервером?A3: На пользовательских машинах, обычно устанавливаются специальные программы-шлюзы, которые, через сетевой протокол, обеспечивают связь с сервером БД. Через эти шлюзы, приложения передают запросы серверу и получают результаты. Часто, дополнительно устанавливается библиотека (ODBC, OLE DB и т.п.), предоставляющая приложениям API для работы с сервером БД.
Q4: В чем отличие «многозвенной» архитектуры от «двухзвенной»?A4: Довольно распространена модель работы, когда клиент обращается не непосредственно к серверу БД, а к промежуточной программе. Эта программа обычно называется сервером приложений. Такую архитектуру называют «трехзвенной», в отличие от «двухзвенной» архитектуры клиент-сервер.
Q5: Зачем нужен сервер приложений?A5: Сервер приложений может использоваться для многих целей. Как правило, сервер приложений находится на отдельной машине. На него можно переложить всю функциональность программы, оставив клиенту только интерфейсную часть. Это разгрузит клиента и сервер БД от вычислений. Также, при большом количестве пользователей, можно использовать несколько серверов приложений для распределения нагрузки. А для ускорения доступа к часто используемым таблицам, их обычно кэшируют на сервере приложений.
Q6: Чем отличается «тонкий» клиент от «толстого»?A6: «Тонким» клиентом обычно называют пользовательское приложение, не содержащее никакой функциональности, и предназначенное только для ввода/вывода информации. Вся обработка данных производится на сервере БД, либо на сервере приложений. Зачастую, такой клиент изначально не содержит вообще никаких возможностей, а подгружает дополнительные модули с сервера, по мере необходимости. Обычно, в качестве «тонкого» клиента, выступают Web броузер + HTML/ASP/Java. «Толстый» клиент содержит всю функциональность и интерфейсную часть в себе, и при любом изменении, требует замены у всех пользователей.
Реляционные базы данных: Теория Q1: Как данные хранятся в реляционной базе?A1: Данные в реляционной БД хранятся в плоских таблицах. Каждая таблица имеет собственный, заранее определенный набор именованных колонок (полей). Поля таблицы обычно соответствуют атрибутам сущностей, которые необходимо хранить в базе. Количество строк (записей) в таблице неограниченно, и каждая запись соответствует отдельной сущности. Полное описание реляционных БД можно найти в книгах [1], [2] и [3].
Q2: Как записи отличаются друг от друга?A2: Записи в таблице отличаются только содержимым их полей. Две записи, в которых все поля одинаковы, считаются идентичными. Каждая таблица должна иметь первичный ключ (ПК) — поле или набор полей, содержимое которых однозначно определяет запись в таблице и отличает ее от других. Отсутствие первичного ключа и наличие идентичных записей в таблице обычно возможно, но крайне нежелательно.
Q3: Каким образом таблицы связаны между собой?A3: Простейшая связь между двумя таблицами образуется при добавлении в первую таблицу поля, содержащего значение первичного ключа второй таблицы. В общем случае, реляционные БД предоставляют очень гибкий механизм для всевозможных пересечений и объединений любых таблиц, с разнообразными условиями. Для описания множеств, получающихся при пересечении и объединении таблиц, используется специальный математический аппарат — реляционная алгебра.
Q4: Что такое нормализация?A4: Упорядочивание модели БД. Грубо говоря, нормализацией называют процесс выявления отдельных независимых сущностей и вынесения их в отдельные таблицы. При этом, связи с такими таблицами, обычно организуют по их первичному ключу. В результате нормализации, увеличивается гибкость работы с БД. Также, уменьшается содержание дублирующей информации в БД, а это сильно понижает вероятность возникновения ошибок.
Q5: Имеет ли значение порядковый номер записи в таблице?A5: Нет. Реляционная алгебра оперирует множествами, в которых порядковый номер элемента не несет никакой смысловой нагрузки. Записи отличатся только содержимым их полей. Две записи, в которых все поля одинаковы, будут абсолютно идентичны в реляционной БД.
Реляционные базы данных: Особенности SQL-серверов Q1: Что можно делать при помощи SQL?A1: SQL (Structured Query Language — язык структурированных запросов) является стандартным языком для работы с реляционными БД. Разделяется на две основные части: DDL (Data Definition Language — язык определения данных) и DML (Data Manipulation Language — язык обработки данных). DDL предоставляет средства для создания и изменения структуры хранения данных (БД, таблиц, процедур, типов данных и т.п.). DML предназначен для чтения и изменения данных. Основные операторы DML: select — выборка, insert — вставка, update — изменение, delete — удаление. Также, с помощью SQL, часто реализован доступ к служебным функциям SQL-сервера (заведение пользователей, создание резервных копий БД и т.д.). Подробное описание операторов и возможностей SQL имеется в книге [1].
Q2: Зачем нужны транзакции?A2: Во многих случаях, необходимо проведение группы операций по изменению данных таким образом, чтобы эта группа обладала свойством атомарности (либо вся целиком выполняется, либо вся целиком не выполняется). Такая группа операций называется транзакцией. В SQL-серверах существуют операторы, позволяющие обозначить начало транзакции (begin transaction), ее успешное завершение (commit transaction), либо откат транзакции (rollback transaction).
Q3: Что такое журнал транзакций?A3: Любые изменения данных, проведенные внутри транзакции, записываются в специальный журнал транзакций (transaction log). При откате транзакции, данные восстанавливаются в прежнем виде, а записи об изменениях удаляются из журнала транзакций.
Q4: Когда появляются блокировки?A4: При изменении данных внутри транзакции, модифицируемые записи блокируются сервером до окончания этой транзакции. Если какая-нибудь другая транзакция пытается изменить заблокированные записи, то ее выполнение останавливается, пока не будет снята блокировка, то есть, пока не завершится первая транзакция. Некоторые сервера имеют неприятную особенность блокировать данные не отдельными записями, а целыми страницами, которые могут содержать довольно много записей.
Q5: Чем отличаются «версионники» от «блокировочников»?A5: Классические «блокировочники» не дают возможности разным транзакциям одновременно изменять одни и те же записи, блокируя их на время транзакции. В результате, при попытке изменить заблокированную запись, другая транзакция будет простаивать, пока не завершиться первая. В свою очередь, «версионники» позволяют одновременно модифицировать одни и те же записи, создавая при этом разные версии одной записи.
Q6: Почему возникает deadlock?A6: Перекрестная блокировка (deadlock) двух транзакций возникает при изменении одних и тех же записей в разном порядке. Последовательность действий, приводящая к перекрестной блокировке: 1. Транзакция A изменяет запись X. Заблокирована X. 2. Транзакция B изменяет запись Y. Заблокирована Y. 3. Транзакция A пытается изменить запись Y. Остановлена A. 4. Транзакция B пытается изменить запись X. Остановлена B. Сервер определяет перекрестную блокировку и откатывает одну из транзакций, возвращая ошибку соответствующему соединению. Аминь. Чтобы не выводить ошибку пользователю, обломанное соединение должно молча повторить транзакцию.
Q7: Зачем нужны индексы?A7: Для ускорения операций выборки данных. При поиске полей с определенным значением, сервер вынужден перебирать все записи в таблице. В этом случае, время поиска линейно зависит от размера таблицы. Индекс по полю, обычно представляющий собой бинарное дерево, дает возможность резко сократить время поиска, превратив эту зависимость в логарифмическую. Однако, наличие индексов в таблице, замедляет операции модификации данных.
Q8: Для чего необходим первичный ключ в таблице?A8: Первичный ключ (ПК) — поле или набор полей, содержимое которых однозначно определяет запись в таблице и отличает ее от других. Служит для однозначной идентификации записей и в таблице может быть только один. Обычно, при определении первичного ключа, по нему автоматически создается уникальный индекс.
Q9: Что такое триггер?A9: Триггер — процедура, выполняемая сервером автоматически при модификации данных в таблице. В основном, триггеры используются для поддержания целостности дублирующей информации в денормализованной БД.
Q10: Можно ли использовать свою функцию в SQL-запросе?A10: Можно, практически во всех современных SQL-серверах. Различия только в синтаксисе определения и вызова функции. Кроме того, некоторые сервера позволяют использовать функции, написанные на других языках (не SQL).
Q11: Как автоматически генерировать первичный ключ?A11: Обычно, в SQL-серверах можно указать одно из числовых полей как автоматически нумеруемое, причем синтаксис определения сильно зависит от разновидности сервера. При вставке новой записи, такому полю присваивается значение поля предыдущей записи + заданное приращение. Как правило, значение подобного поля нельзя явно изменять с помощью DML.
Q12: Какую функцию выполняет оптимизатор запросов?A12: Оптимизатор предназначен для определения самого быстрого способа выполнения запроса (в основном для выборки данных), при участии в нем нескольких таблиц. В простейшем случае, задача оптимизатора заключается в правильном использовании индексов при соединении таблиц и применении дополнительных условий. Иногда, можно задать в запросе дополнительную информацию (hint) для оптимизатора.
Q13: Что такое представление?A13: Представление (view) — это запрос на выборку, хранящийся на сервере, как отдельный объект. Так как, результат этого запроса можно рассматривать в качестве таблицы, представление допускается использовать в других запросах, также как любую обычную таблицу. Материализованное представление хранится на сервере в виде таблицы, которая автоматически обновляется при изменении данных, имеющих отношение к этому представлению.
Q14: Для чего нужны хранимые процедуры?A14: Хранимые процедуры (SP — Stored Procedure) представляют собой последовательность команд на расширениях SQL, либо на других языках, поддерживаемых сервером. Могут принимать параметры и возвращать значение заданного типа. Часто используются для выполнения операций, напрямую связанных с логикой задачи, для которой проектировалась БД. Иногда, используются вместе с представлениями, для обеспечения безопасности БД (все изменения через SP, все выборки через view).
Q15: Какие типы данных есть в SQL-сервере?A15: Обычно, для полей в таблицах могут использоваться только самые простые типы: числа (целые и дробные), строки (сильно ограниченные по длине), дата (и время), бинарные данные большого размера (для текста, графики и т.п.). В некоторых серверах допускается использование массивов и самодельных структур.
Q16: Чему равен null?A16: А хрен его знает. И это совсем не шутка, а его фактическое определение. Null не является пустым значением. Поэтому null + 1 = null, а не 1. Использование null означает переход от двузначной логики (true/false) к трехзначной (true/false/unknown), которая является более сложной и несколько непривычной. С другой стороны, логические операторы SQL обычно приспособлены для работы только с двузначной логикой. Поэтому использовать null для значений полей следует очень осторожно и только в тех случаях, когда без него никак нельзя обойтись. Поведение null хорошо описано в статье [5].
Q17: Зачем нужен внешний ключ?A17: Внешний ключ (FK — Foreign Key) используется для создания жесткой связи (многие к одному) между двумя таблицами. Внешний ключ задается только в том случае, если в первой таблице есть поле, содержащее значение первичного ключа из второй таблицы. При изменении значения первичного ключа во второй таблице, могут быть изменены все соответствующие значения связанного поля в первой таблице. При удалении записи с определенным первичным ключом из второй таблице, могут быть удалены все записи с соответствующим значением связанного поля в первой таблице. Обычно, при определении внешнего ключа, по нему автоматически создается индекс, который используется в запросах при объединении этих двух таблиц.
Q18: Существует ли стандарт SQL, общий для всех серверов?A18: Вообще-то он есть (ANSI SQL 92), только вот поддержка его у производителей СУБД сильно хромает. Причем дело с эти настолько плохо, что может различаться даже синтаксис основных операторов DML, не говоря уже о функциях, триггерах и прочих особенностях SQL. Поэтому о совместимости говорить сложно, хотя кое-какие сдвиги в этом направлении имеются. Наиболее близки к стандарту: Borland InterBase, Microsoft SQL Server, IBM DB2 Universal Database.
Q19: Есть ли стандартное клиентское API для доступа к SQL-серверам?A19: Наиболее распространенным клиентским API является ODBC (Open DataBase Connectivity). ODBC-драйвера существуют на многих платформах и почти для всех SQL-серверов.
Q20: Что такое репликация?A20: Репликацией обычно называют процесс синхронизации данных между несколькими БД. Наиболее развитые SQL-сервера содержат встроенные средства репликации. Для остальных могут быть использованы продукты сторонних фирм. Одностороняя репликация подразумевает изменение данных только в одной базе, с последующей передачей изменений на остальные. Соответственно, довольно проста в реализации и надежна в работе. Двустороняя репликация предоставляет гораздо более мощный инструмент распределенной работы между SQL-серверами. Плата за это — сложность и большая вероятность конфликтов при работе.
Реляционные базы данных: Практические вопросы Q1: Что такое суррогатный ключ, и чем он лучше или хуже естественного ключа?A1: Естественным ключом (ЕК) обычно называют первичный ключ, который является смысловым атрибутом (либо набором атрибутов) сущности. Как и любой другой атрибут, ЕК подвержен изменениям, а его уникальность часто бывает притянута за уши. Суррогатным ключом (СК), как правило, называется уникальное автонумеруемое целочисленное поле в таблице, которое и является первичным ключом. СК гораздо компактнее, чем ЕК, везде занимает одно поле одинакового типа, всегда неизменен и уникален. Более обоснованное сравнение СК и ЕК производится в статье [6].
Q2: Можно ли полностью избежать дублирования информации?A2: Только в теории. Полностью нормализованная БД хороша для хранения данных. Но, на практике постоянно встречаются структуры, получение информации из которых, слишком сложно и медленно (а иногда и невозможно с помощью лишь оператора select). Дублирование информации в том или ином виде обычно необходимо для получения результатов выборки простыми запросами за приемлемое время.
Q3: Хотелось бы иметь возможность восстанавливать удаленные записи. Как это сделать?A3: А не надо их удалять, тогда и проблемы не будет. Проще всего держать в таблице дополнительное поле Deleted (default = false), а вместо удаления записи устанавливать Deleted = true. Для выборки можно использовать представление с внутреннем условием:
Deleted = false
Возможен и более сложный вариант: два дополнительных поля DateBegin (defaul = текущая_дата) и DateEnd (defaul = максимальная_дата). Вместо удаления записи необходимо установить DateEnd = текущая_дата, а условие для выборки будет:
текущая_дата beetwen DateBegin and DateEnd
Во втором варианте есть возможность, не только иметь даты заведения/удаления записей, но и получать наборы записей на любую определенную дату с условием:
заданная_дата beetwen DateBegin and DateEndQ4: Как организовать хранение истории изменений записей, с возможностью просмотра и отката этих изменений?
A4: Самый простой вариант — это создать таблицу-журнал, в которую, при изменении записи, помещать следующую информацию: дату изменения, имя (Id) пользователя, имя (Id) изменяемой таблицы, ПК записи, имя (Id) изменяемого поля и предыдущее значение поля, преобразованное в строку. Запись в журнал можно делать либо триггерами, либо из приложения. Откат изменений следует производить специальной процедурой, которая должна восстановить значения полей на заданную дату и удалить соответствующие записи из журнала. Один из вариантов журнала имеется в статье [9].
Q5: Есть объекты, которые меняются с течением времени. Нужно получать их состояния на любую дату. Каким образом для этого надо организовать хранение объектов в базе?A5: Можно использовать сервер, который поддерживает историческую модель, например, PostgreSQL Server. В ином случае, можно, в дополнение к основной таблице, создать аналогичную таблицу истории, с двумя дополнительными полями: DateBegin и DateEnd. При вставке записи в основную таблицу, необходимо добавить такую же запись в таблицу истории, где DateBegin = текущая_дата, DateEnd = максимальная_дата. При модификации записи в основной таблице, нужно вставить измененную запись в таблицу истории, где DateBegin = текущая_дата, DateEnd = максимальная_дата. При этом у предыдущей соответствующей записи в таблице истории необходимо установить DateEnd = текущая_дата. При удалении записи в основной таблице, у последней соответствующей записи в таблице истории надо установить DateEnd = текущая_дата. Выборка из основной таблицы производится обычным образом, а из таблицы истории с условием:
заданная_дата beetwen DateBegin and DateEnd
Сходные варианты представления истории рассматриваются в статье [10].
Q6: Каким образом можно хранить в реляционной базе иерархические объекты?A6: В таблицу, которую надо сделать иерархической, надо добавить поле Parent, содержащее значение ПК родительской записи. Далее, все зависит от задачи и возможностей сервера. Если нет необходимости углубляться в иерархию более чем на один уровень, либо сервер поддерживает рекурсивные запросы, то этого вполне хватит. Иначе, придется использовать дополнительные поля, либо вспомогательную таблицу. Подробно способы представления деревьев в реляционных БД описаны в статьях [7] и [8].
Q7: Как получить полную информацию об определенной таблице в базе (какие поля есть в таблице, их тип, размер, связи с другой таблицей и т.п.)?A7: Вообще-то, в большинстве SQL-серверов есть системный каталог — набор стандартных таблиц, содержащих информацию обо всех объектах сервера и БД. Правда, таблицы эти предназначены не для разработчика, а для внутреннего использования самим сервером, поэтому найти там что-либо весьма проблематично. При этом, структура их может меняться даже в разных версиях одного сервера. Поэтому, желательно создать в БД собственный системный каталог, полностью описывающий структуру базы и взаимосвязи ее объектов. Такой каталог можно переносить на другой сервер вместе с БД, не заботясь о совместимости, а информацию из него можно легко использовать в приложении для каких-либо действий (вплоть до автоматической генерации самого приложения). Пример системного каталога приведен в статье [9].
Q8: Необходимо хранить в базе много картинок (файлов). Как лучше всего это сделать?A8: Проще всего файлы держать отдельно в выделенной директории на сервере, а в базе хранить только их имена. Единственной проблемой в таком случае будет раздача прав на эту директорию. Кроме того, в SQL-серверах есть возможность определять бинарные поля большого размера, которые можно использовать для хранения разных видов файлов. Чтобы не замедлять работу с обычными данными, рекомендуется создавать отдельно таблицу, содержащую такие поля. Также, при использовании больших полей, можно обнаружить разнообразные ограничения на сервере и клиенте.
Q9: Есть запрос, который возвращает данные, отсортированные по какому-либо признаку. Можно ли получить не весь результат, а только определенную его часть, например, записи с 20 по 40?A9: Такие действия проще всего выполнять на клиенте. Если надо на сервере, то лучше использовать предназначенные для этого возможности оператора выборки:
1. select top n * from t order by a 2. select * from t order by a limit n,m
Когда ничего такого сервер не поддерживает, можно использовать в условии выборки подзапрос, возвращающий номер текущей строки результата (если поле сортировки не уникально, то в подзапрос нужно добавить еще какое-нибудь условие для обеспечения уникальности значений, возвращаемых подзапросом):
select * from t as t_outer where ( select count( * ) from t as t_inner where t_inner.a <= t_outer.a ) between n and m order by a
В крайнем случае, можно выбирать данные во временную таблицу с дополнительным автонумеруемым полем RowNumber, а из нее затем делать выборку:
select ... where RowNumber >= n and RowNumber <= mQ10: Как из таблицы получить записи, в которых одно из полей максимально (или минимально)?
A10: Самый правильный способ — это использовать вложенный запрос, в котором ищется максимальное значение поля. Затем найденное значение используется в условии основного запроса.
select * from t where a = ( select max( a ) from t )
Если максимальное значение необходимо искать не во всей таблице, а по определенному полю, то запрос будет следующего вида:
select * from t as t_outer where a = ( select max( t_inner.a ) from t as t_inner where t_inner.b = t_outer.b )
Чтобы избежать появления в результате запроса нескольких одинаковых записей, соответствующих одному максимальному значению, можно использовать ключевое слово distinct.
Q11: Можно ли сделать в SQL перекрестный запрос? То есть, в качестве полей для результата, брались бы не поля таблицы, а данные из нее?A11: Подобные запросы обычно умеют делать разнообразные генераторы отчетов. SQL же не предусматривает такой возможности. Хотя, при желании, можно сделать процедуру, использующую динамический SQL. В ней придется составлять запрос на основе данных из таблицы, а затем выполнять его.
Q12: Как обеспечить нумерацию записей в результате запроса?A12: Нумерация записей очень легко делается на любом клиенте. Чтобы в запросе получить поле, соответствующее номеру записи, можно использовать подзапрос, возвращающий количество записей, меньших либо равных (сравнение — по полю сортировки) текущей записи:
select *, ( select count( * ) from t as t_inner where t_inner.a <= t_outer.a ) as RowNumber from t as t_outer order by a
Если поле сортировки не уникально, то в подзапрос необходимо добавить дополнительное условие сравнения (например, по первичному ключу) для обеспечения уникальности значений, возвращаемых подзапросом.
Q13: Есть необходимость добавлять произвольные виды объектов (еще неизвестные на этапе разработке) в БД. Создавать и менять таблицы на «живой» базе не хочется. Как быть?A13: Можно вообще хранить все дополнительные объекты в одной таблице, содержащей идентификатор объекта, идентификатор атрибута и само значение атрибута, преобразованное в строку. Чтобы не было мучительно больно при получении результатов запросов, лучше все-таки использовать несколько таблиц — отдельно для каждого типа данных. Более серьезный вариант универсального хранилища объектов рассмотрен в статье [11]. PS: А чем была плоха идея насчет создания/изменения таблиц?
Стандарные аббревиатуры АнглийскиеAPI — Application Program Interface DB — DataBase DBMS — DataBase Management System DDL — Data Definition Language DML — Data Manipulation Language FK — Foreign Key ODBC — Open DataBase Connectivity PK — Primary Key RDBMS — Relational DataBase Management System SP — Stored Procedure SQL — Structured Query Language.
РусскиеБД — База Данных ЕК — Естественный Ключ ООБД — Объектно-Ориентированная База Данных ООСУБД — Объектно-Ориентированная Система Управления Базами Данных ПК — Первичный Ключ РСУБД — Реляционная Система Управления Базами Данных СУБД — Система Управления Базами Данных СК — Суррогатный Ключ.
Литература Мартин Грабер «Понимание SQL». Пушников А. Ю. «Введение в системы управления базами данных». Кузнецов С. Д. «Основы современных баз данных». Кузнецов С. Д. «Объектно-ориентированные базы данных — основные концепции, организация и управление: краткий обзор». Акжан Абдулин, Лиля Козленко «Представление отсутствующей информации». Тенцер А. «Естественные ключи против искусственных ключей». Joe Celko «A Look at SQL Trees», II, III. Виноградов С. А. «Моделирование иерархических объектов». Виноградов С. А. «Минимальный набор стандартных таблиц». Виноградов С. А. «История изменений объектов». Тенцер А. «База данных — хранилище объектов», II.Запуск файлов хрянящихся в БД.
Автор: Дмитрий WEB-сайт: http://delphibase.endimus.com
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Запуск файлов хрянящихся в БД
Данный код реализует чтение из Бд строки где спрятан путь к файлу,
далее для удачного запуска "курсор" переместим в папку, от куда надо
запустить разбиваем строку на файл(для запуска) и на директорию
(для перемещения "курсора").
Переброска "курсора" необходима для правильного запуска приложения.
Приложение может быть как под Windows, так и под Dos.
Зависимости: Windows, SysUtils
Автор: Дмитрий, ahmaev@mail.ru, ICQ:100716670, Пенза
Copyright: Дмитрий
Дата: 9 июля 2002 г.
***************************************************** }
procedure TForm1.dxfColorButton1Click(Sender: TObject);
var
s, files1, files: string;
dir, dir1: string;
apchar: array[0..500] of char;
i_s1: integer;
begin
{Находим нужную строку в Бд}
table1.locate('kod_s', treeview1.selected.parent.index + 1,
[loCaseInsensitive]);
table2.locate('game', treeview1.selected.Text, [loCaseInsensitive]);
{Перепишем из БД строки содержащие ссылки в переменные}
s := table2.fields[4].asstring;
k_s := length(table2.fields[4].asstring) + 1;
files := '';
dir := '';
repeat
i_s := i_s + 1;
k_s := k_s - 1;
{Поставим семафор, чтоб разделить путь и имя файла}
if s[k_s] = '\' then
en := 1;
if s[k_s] <> '\' then
if en = 0 then
files := files + s[k_s];
if en = 1 then
dir := dir + s[k_s];
until i_s = length(table2.fields[4].asstring);
{Переворачиваем считанное "путь до файла" и "имя файла"}
for i_s1 := length(files) downto 1 do
files1 := files1 + files[i_s1];
for i_s1 := length(dir) downto 1 do
dir1 := dir1 + dir[i_s1];
{Переводим курсор в папку откуда должна запустится программа}
chdir(dir1);
strpcopy(apchar, files1);
{Запускаем программу}
shellexecute(handle, 'open', apchar, 0, 0, SW_maximize);
end;
Отобразить определенного формата файлы базы данных.
unit DdhDynDb;
interface
uses
Controls, Db, Forms, Classes, DbTables;
function ConvertClass(FieldClass: TFieldClass): TControlClass;
procedure NormalizeString(var S: string);
procedure ConnectDataFields(DbComp: TControl;
DataSource: TDataSource; FieldName: string);
function GenerateForm(StrList: TStringList;
SourceTable: TTable): TForm;
function GenerateSource(AForm: TForm;
FormName, UnitName: string): string;
implementation
uses
TypInfo, DbCtrls, SysUtils, StdCtrls, ExtCtrls, Windows;
const
FieldTypeCount = 15;
type
CVTable = array[1..FieldTypeCount, 1..2] of TClass;
// TBytesField and TVarBytesField are missing
const
ConvertTable: CVTable = (
(TAutoIncField, TDBEdit),
(TStringField, TDBEdit),
(TIntegerField, TDBEdit),
(TSmallintField, TDBEdit),
(TWordField, TDBEdit),
(TFloatField, TDBEdit),
(TCurrencyField, TDBEdit),
(TBCDField, TDBEdit),
(TBooleanField, TDBCheckBox),
(TDateTimeField, TDBEdit),
(TDateField, TDBEdit),
(TTimeField, TDBEdit),
(TMemoField, TDBMemo),
(TBlobField, TDBImage), {just a guess}
(TGraphicField, TDBImage));
function ConvertClass(FieldClass: TFieldClass):
TControlClass;
var
I: Integer;
begin
Result := nil;
for I := 1 to FieldTypeCount do
if ConvertTable[I, 1] = FieldClass then
begin
Result := TControlClass(
ConvertTable[I, 2]);
break; // jump out of for loop
end;
if Result = nil then
raise Exception.Create('ConvertClass failed');
end;
procedure NormalizeString(var S: string);
var
N: Integer;
begin
// remove the T
Delete(S, 1, 1);
{chek if the string is a valid Pascal identifier:
if not, replace spaces and other characters with underscores}
if not IsValidIdent(S) then
for N := 1 to Length(S) do
if not ((S[N] in ['A'..'Z']) or (S[N] in ['a'..'z'])
or ((S[N] in ['0'..'9']) and (N <> 1))) then
S[N] := '_';
end;
procedure ConnectDataFields(DbComp: TControl;
DataSource: TDataSource; FieldName: string);
var
PropInfo: PPropInfo;
begin
if not Assigned(DbComp) then
raise Exception.Create(
'ConnectDataFields failed: Invalid control');
// set the DataSource property
PropInfo := GetPropInfo(
DbComp.ClassInfo, 'DataSource');
if PropInfo = nil then
raise Exception.Create(
'ConnectDataFields failed: Missing DataSource property');
SetOrdProp(DbComp, PropInfo,
Integer(Pointer(DataSource)));
// set the DataField property
PropInfo := GetPropInfo(
DbComp.ClassInfo, 'DataField');
if PropInfo = nil then
raise Exception.Create(
'ConnectDataFields failed: Missing DataField property');
SetStrProp(DbComp, PropInfo, FieldName);
end;
function GenerateForm(StrList: TStringList;
SourceTable: TTable): TForm;
var
I, NumField, YComp, HForm, Hmax: Integer;
NewName: string;
NewLabel: TLabel;
NewDBComp: TControl;
CtrlClass: TControlClass;
ATable: TTable;
ADataSource: TDataSource;
APanel: TPanel;
ANavigator: TDBNavigator;
AScrollbox: TScrollBox;
begin
// generate the form and connect the table
Result := TForm.Create(Application);
Result.Position := poScreenCenter;
Result.Width := Screen.Width div 2;
Result.Caption := 'Table Form';
// create a Table component in the result form
ATable := TTable.Create(Result);
ATable.DatabaseName := SourceTable.DatabaseName;
ATable.TableName := SourceTable.TableName;
ATable.Active := True;
ATable.Name := 'Table1';
// component position (at design time)
ATable.DesignInfo := MakeLong(20, 20);
// create a DataSource
ADataSource := TDataSource.Create(Result);
ADataSource.DataSet := ATable;
ADataSource.Name := 'DataSource1';
// component position (at design time)
ADataSource.DesignInfo := MakeLong(60, 20);
// create a toolbar panel
APanel := TPanel.Create(Result);
APanel.Parent := Result;
APanel.Align := alTop;
APanel.Name := 'Panel1';
APanel.Caption := '';
// place a DBNavigator inside it
ANavigator := TDBNavigator.Create(Result);
ANavigator.Parent := APanel;
ANavigator.Left := 8;
ANavigator.Top := 8;
ANAvigator.Height := APanel.Height - 16;
ANavigator.DataSource := ADataSource;
ANavigator.Name := 'DbNavigator1';
// create a scroll box
AScrollbox := TScrollBox.Create(Result);
AScrollbox.Parent := Result;
AScrollbox.Width := Result.ClientWidth;
AScrollbox.Align := alClient;
AScrollbox.BorderStyle := bsNone;
AScrollbox.Name := 'ScrollBox1';
// generates field editors
YComp := 10;
for I := 0 to StrList.Count - 1 do
begin
NumField := Integer(StrList.Objects[I]);
// create a label with the field name
NewLabel := TLabel.Create(Result);
NewLabel.Parent := AScrollBox;
NewLabel.Name := 'Label' + IntToStr(I);
NewLabel.Caption := StrList[I];
NewLabel.Top := YComp;
NewLabel.Left := 10;
NewLabel.Width := 120;
// create the data aware control
CtrlClass := ConvertClass(
ATable.FieldDefs[NumField].FieldClass);
NewDBComp := CtrlClass.Create(Result);
NewDBComp.Parent := AScrollBox;
NewName := CtrlClass.ClassName +
ATable.FieldDefs[NumField].Name;
NormalizeString(NewName);
NewDBComp.Name := NewName;
NewDBComp.Top := YComp;
NewDBComp.Left := 140;
NewDbComp.Width :=
AScrollBox.Width - 150; // width of label plus border
// connect the control with the data source
// and field using RTTI support
ConnectDataFields(NewDbComp,
ADataSource,
ATable.FieldDefs[NumField].Name);
// compute the position of the next component
Inc(YComp, NewDBComp.Height + 10);
end; // for each field
// computed requested height for client area
HForm := YComp + APanel.Height;
// max client area hight = screen height - 40 - form border
HMax := (Screen.Height - 40 -
(Result.Height - Result.ClientHeight));
// limit form height to HMax and reserve space for scrollbar
if HForm > HMax then
begin
HForm := HMax;
Result.Width := Result.Width +
GetSystemMetrics(SM_CXVSCROLL);
end;
Result.ClientHeight := HForm;
end;
function GenerateSource(AForm: TForm;
FormName, UnitName: string): string;
var
I: Integer;
begin
SetLength(Result, 20000);
// generate the first part of the unit source
Result :=
'unit ' + UnitName + ';'#13#13 +
'interface'#13#13 +
'uses'#13 +
' SysUtils, WinTypes, WinProcs, Messages, Classes,'#13 +
' Forms, Graphics, Controls, Dialogs, DB, DBCtrls,'#13 +
' DBTables, ExtCtrls;'#13#13 +
'type'#13 +
' T' + FormName + ' = class(TForm)'#13;
// add each component of the form
for I := 0 to AForm.ComponentCount - 1 do
Result := Result +
' ' + AForm.Components[I].Name +
': ' + AForm.Components[I].ClassName + ';'#13;
// generate the final part of the source code
Result := Result +
' private'#13 +
' { Private declarations }'#13 +
' public'#13 +
' { Public declarations }'#13 +
' end;'#13#13 +
'var'#13 +
' ' + FormName + ': T' + FormName + ';'#13#13 +
'implementation'#13#13 +
'{$R *.DFM}'#13#13 +
'end.'#13;
end;
end.
unit DdhDbwF;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ExtCtrls, Grids, DB, DBTables,
Buttons, Mask, DBCtrls;
type
TFormDbWiz = class(TForm)
Notebook1: TNotebook;
Label1: TLabel;
ListDatabases: TListBox;
BitBtnNext1: TBitBtn;
BitBtnNext2: TBitBtn;
Label2: TLabel;
ListTables: TListBox;
BitBtnBack2: TBitBtn;
ListFields: TListBox;
Label3: TLabel;
BitBtnNext3: TBitBtn;
BitBtnBack3: TBitBtn;
Label4: TLabel;
BitBtnNext4: TBitBtn;
BitBtnBack4: TBitBtn;
GroupFilter: TRadioGroup;
BitBtnAll: TBitBtn;
BitBtnNone: TBitBtn;
StringGrid1: TStringGrid;
Table1: TTable;
procedure Notebook1PageChanged(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ListDatabasesClick(Sender: TObject);
procedure BitBtnNext1Click(Sender: TObject);
procedure ListTablesClick(Sender: TObject);
procedure BitBtnBack2Click(Sender: TObject);
procedure BitBtnNext2Click(Sender: TObject);
procedure BitBtnBack3Click(Sender: TObject);
procedure BitBtnAllClick(Sender: TObject);
procedure BitBtnNoneClick(Sender: TObject);
procedure BitBtnNext3Click(Sender: TObject);
procedure BitBtnBack4Click(Sender: TObject);
procedure ListFieldsClick(Sender: TObject);
procedure BitBtnNext4Click(Sender: TObject);
private
{ Private declarations }
public
SourceCode, FormName, UnitName: string;
ResultForm: TForm;
procedure GeneratedFormClose(
Sender: TObject; var Action: TCloseAction);
end;
var
FormDbWiz: TFormDbWiz;
implementation
{$R *.DFM}
uses
DdhDynDb, ExptIntf;
////// form code //////
procedure TFormDbWiz.Notebook1PageChanged(Sender: TObject);
begin
// copy the name of the page into the caption
Caption := Format(
'Ddh DB Form Wizard - Page %d/%d: ',
[NoteBook1.PageIndex + 1,
NoteBook1.Pages.Count,
NoteBook1.ActivePage]);
end;
procedure TFormDbWiz.FormCreate(Sender: TObject);
begin
// fill the first listbox with database names
Session.GetDatabaseNames(
ListDatabases.Items);
// start in the first page
Notebook1.PageIndex := 0;
// default values (modified by the wizard)
FormName := 'TResultForm';
UnitName := 'ResultUnit';
end;
procedure TFormDbWiz.ListDatabasesClick(Sender: TObject);
begin
// database selected: enable the Next button
BitBtnNext1.Enabled := True;
end;
procedure TFormDbWiz.BitBtnNext1Click(Sender: TObject);
var
CurrentDB, CurrentFilter: string;
begin
// get the database and filters
CurrentDB := ListDatabases.Items[
ListDatabases.ItemIndex];
CurrentFilter := GroupFilter.Items[
GroupFilter.ItemIndex];
// retrieve the tables
Session.GetTableNames(CurrentDB,
CurrentFilter, True, False, ListTables.Items);
// move to the next page
NoteBook1.PageIndex := 1;
BitBtnNext2.Enabled := False;
end;
procedure TFormDbWiz.ListTablesClick(Sender: TObject);
begin
// table selected: enable next button
BitBtnNext2.Enabled := True;
end;
procedure TFormDbWiz.BitBtnBack2Click(Sender: TObject);
begin
// go back to first page
NoteBook1.PageIndex := 0;
end;
procedure TFormDbWiz.BitBtnNext2Click(Sender: TObject);
var
I: Integer;
begin
// set the properties of the selected table
with Table1 do
begin
DatabaseName := ListDatabases.Items[
ListDatabases.ItemIndex];
TableName := ListTables.Items[
ListTables.ItemIndex];
// load the field definitions
FieldDefs.Update;
end;
// clear the list box, then fill it
ListFields.Clear;
for I := 0 to Table1.FieldDefs.Count - 1 do
// add number, name, and class name of each field
ListFields.Items.Add(Format(
'%d) %s [%s]',
[Table1.FieldDefs[I].FieldNo,
Table1.FieldDefs[I].Name,
Table1.FieldDefs[I].FieldClass.ClassName]));
// move to the next page
NoteBook1.PageIndex := 2;
BitBtnNext3.Enabled := False;
end;
procedure TFormDbWiz.BitBtnBack3Click(Sender: TObject);
begin
// back to the second page
NoteBook1.PageIndex := 1;
end;
procedure TFormDbWiz.BitBtnAllClick(Sender: TObject);
var
I: Integer;
begin
// select every available field
for I := 0 to ListFields.Items.Count - 1 do
ListFields.Selected[I] := True;
// enable Next button
BitBtnNext3.Enabled := True;
end;
procedure TFormDbWiz.BitBtnNoneClick(Sender: TObject);
var
I: Integer;
begin
// deselect all the fields
for I := 0 to ListFields.Items.Count - 1 do
ListFields.Selected[I] := False;
// disable next button (no fields are selected)
BitBtnNext3.Enabled := False;
end;
procedure TFormDbWiz.ListFieldsClick(Sender: TObject);
begin
// enable button if there at least one field selected
BitBtnNext3.Enabled := ListFields.SelCount > 0;
end;
procedure TFormDbWiz.BitBtnNext3Click(Sender: TObject);
var
I, RowNum: Integer;
begin
// reserve enough rows in the string grid
StringGrid1.RowCount := ListFields.Items.Count;
// empty the string grid
for I := 0 to StringGrid1.RowCount - 1 do
begin
StringGrid1.Cells[0, I] := '';
StringGrid1.Cells[1, I] := '';
end;
// for each field, if selected list it with the
// corresponding data aware component
RowNum := 0;
for I := 0 to ListFields.Items.Count - 1 do
if ListFields.Selected[I] then
begin
StringGrid1.Cells[0, RowNum] := Format('%d) %s [%s]',
// field number, name, classname of data aware control
[Table1.FieldDefs[I].FieldNo,
Table1.FieldDefs[I].Name,
ConvertClass(Table1.FieldDefs[I].FieldClass).ClassName]);
StringGrid1.Cells[1, RowNum] := Table1.FieldDefs[I].Name;
Inc(RowNum);
end;
// set the real number of rows
StringGrid1.RowCount := RowNum;
NoteBook1.PageIndex := 3;
end;
procedure TFormDbWiz.BitBtnBack4Click(Sender: TObject);
begin
NoteBook1.PageIndex := 2;
end;
// generate button
procedure TFormDbWiz.BitBtnNext4Click(Sender: TObject);
var
StrList: TStringList;
I, RowNum: Integer;
begin
StrList := TStringList.Create;
Screen.Cursor := crHourGlass;
try
RowNum := 0;
for I := 0 to ListFields.Items.Count - 1 do
if ListFields.Selected[I] then
begin
StrList.AddObject(
StringGrid1.Cells[1, RowNum], TObject(I));
// move to next row in string grid
Inc(RowNum);
end;
ResultForm := GenerateForm(StrList, Table1);
if not Assigned(ToolServices) then
begin
// stand alone form
ResultForm.OnClose := GeneratedFormClose;
ResultForm.Show;
end
else
begin
// wizard
SourceCode := GenerateSource(ResultForm,
FormName, UnitName);
ModalResult := mrOK;
end;
finally
Screen.Cursor := crDefault;
StrList.Free;
end;
end;
procedure TFormDbWiz.GeneratedFormClose(
Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
end.
Скачать весь проект
Как узнать содержание активной записи в БД.
Следующая функция возвращает в виде указателя на строку содержание активной записи в БД.
function TBDEDirect.GetCurRecord(Lock: DBILockType): PChar;
var
Res: DBIResult;
RecSize: Word;
RecBuf: PChar;
Bookmark: TBookmark;
begin
Result := StrNew('');
if CheckDatabase then
begin
RecSize := GetPhysicalRecSize;
RecBuf := StrAlloc(RecSize+1);
FillChar(RecBuf^, RecSize+1, #0);
Bookmark := FDataLink.DataSource.DataSet.GetBookmark;
DbiSetToBookmark(FDataLink.DataSource.DataSet.Handle, Bookmark);
FDataLink.DataSource.DataSet.FreeBookmark(Bookmark);
Res := DbiGetRecord(FDataLink.DataSource.DataSet.Handle, Lock, RecBuf, nil);
if Res = 0 then
Result := RecBuf
else
Check(Res);
end;
end;
|
Как активизировать компоненты доступа к данным связанные с определенным TDatabase.
for i:=0 to pred(DB1.DataSetCount) do
{if DataSets.Tag = 1 then}
DataSets[i].Open;
|
Быстрый доступ к нужной записи в таблице Paradox.
var
NeedNumber: Integer;
...
NeedNumber := Table.RecNo;
{сохранение номера нужной записи}
...
{код меняющий номе записи}
...
Table.RecNo := NeedNumber;
{востановление номера нужной записи}
|
Контекстное меню на основе базы данных.
| Приходит програмер вечером домой весь в крови и без руки. Жена спрашивает: - Что случилось дорогой??? Програмер: - Да так, в кулер засосало. |
var
m: TMenuItem;
navidummy: TComponent;
..........................................................
procedure TMyForm.CreatePopUpMM(Sender: TObject);
begin
Navidummy.free;
Navidummy := TComponent.create(self);
while not NaviT.EOF do
begin
m := TMenuItem.create(navidummy);
II := II + 1;
with m do
begin
name := 'MM' + IntToStr(II);
caption := NaviT.Fieldbyname('MyWHAT').AsString;
tag := NaviT.Fieldbyname('MyTAG').AsInteger;
visible := True;
OnClick := NaviExec;
end;
MyMenuItem.add(m);
NaviT.Next;
end;
NaviT.Close;
end;
procedure TMyForm.NaviExec(Sender: TObject);
begin
{ Здесь я получаю то, что хочу ! }
What.text := (Sender as TMenuItem).Caption;
Key := (Sender as TMenuItem).Tag;
end;
|
БД-дерево взаимоотношений.

Master Slave ------- имена полей Peter Jane Peter SimonЕсли George и Elisa работают на Jane, то таблица становится такой:
Master Slave ------- имена полей Peter Jane Peter Simon Jane George Jane Elisaи так далее. Если в таблице RELATION необходимо создать дерево, начинающееся на Peter, то нужно добавить к дереву главный узел (запись), где Master = Peter. Затем каждая дочерняя запись располагается ниже записи Master = Peter. После добавления дочерней записи вы сразу увидите, если ребенок имеет собственного ребенка. Ребенок становится теперь, вероятно, отцом, поэтому вы должны позиционировать таблицу RELATION к первой записи, где Master = child, и так далее, рекурсивно. Такой способ гарантирует построение правильного дерева.
Пример:
AddFather('Peter')
AddChild('Peter', 1)
procedure AddFather(Name: string)
begin
Tree.Add(Name);
end;
procedure AddChildr(Name: string, Index: Integer)
begin
Relation.FindKey([Name])
while RelationMaster.AsString = Name do
begin
Tree.AddChild(Index, RelationSlave.AsString);
AddChild(RelationSlave.AsString, Tree.ItemsCount);
Relation.Next;
end;
end;
|
По-моему, ошибок нет.
Сортировка связанного списка.
Автор: Peter Below
program noname;
type
PData = ^TData;
TData = record
next: PData;
Name: string[40];
{ ...другие поля данных }
end;
var
root: PData; { это указатель на первую запись в связанном списке }
procedure InsertRecord(var root: PData; pItem: PData);
(* вставляем запись, на которую указывает pItem в список начиная
с root и с требуемым порядком сортировки *)
var
pWalk, pLast: PData;
begin
if root = nil then
begin
(* новый список все еще пуст, просто делаем запись,
чтобы добавить root к новому списку *)
root := pItem;
root^.next := nil
end { If }
else
begin
(* проходимся по списку и сравниваем каждую запись с одной
включаемой. Нам необходимо помнить последнюю запись,
которую мы проверили, причина этого станет ясна немного позже. *)
pWalk := root;
pLast := nil;
(* условие в следующем цикле While определяет порядок сортировки!
Это идеальное место для передачи вызова функции сравнения,
которой вы передаете дополнительный параметр InsertRecord для
осуществления общей сортировки, например:
While CompareItems( pWalk, pItem ) < 0 Do Begin
where
Procedure InsertRecord( Var list: PData; CompareItems: TCompareItems );
and
Type TCompareItems = Function( p1,p2:PData ): Integer;
and a sample compare function:
Function CompareName( p1,p2:PData ): Integer;
Begin
If p1^.Name < p2^.Name Then
CompareName := -1
Else
If p1^.Name > p2^.Name Then
CompareName := 1
Else
CompareName := 0;
End;
*)
while pWalk^.Name < pItem^.Name do
if pWalk^.next = nil then
begin
(* мы обнаружили конец списка, поэтому добавляем
новую запись и выходим из процедуры *)
pWalk^.next := pItem;
pItem^.next := nil;
Exit;
end { If }
else
begin
(* следующая запись, пожалуйста, но помните,
что одну мы только что проверили! *)
pLast := pWalk;
(* если мы заканчиваем в этом месте, то значит мы нашли
в списке запись, которая >= одной включенной. Поэтому
вставьте ее перед записью, на которую в настоящий момент
указывает pWalk, которая расположена после pLast. *)
if pLast = nil then
begin
(* Упс, мы вывалились из цикла While на самой первой итерации!
Новая запись должна располагаться в верхней части списка,
поэтому она становится новым корнем (root)! *)
pItem^.next := root;
root := pItem;
end { If }
else
begin
(* вставляем pItem между pLast и pWalk *)
pItem^.next := pWalk;
pLast^.next := pItem;
end; { Else }
(* мы сделали это! *)
end; { Else }
end; { InsertRecord }
procedure SortbyName(var list: PData);
var
newtree, temp, stump: PData;
begin { SortByName }
(* немедленно выходим, если сортировать нечего *)
if list = nil then
Exit;
(* в
newtree := Nil;
(********
Сортируем, просто беря записи из оригинального списка и вставляя их
в новый, по пути "перехватывая" для определения правильной позиции в
новом дереве. Stump используется для компенсации различий списков.
temp используется для указания на запись, перемещаемую из одного
списка в другой.
********)
stump := list;
while stump <> nil do
begin
(* временная ссылка на перемещаемую запись *)
temp := stump;
(* "отключаем" ее от списка *)
stump := stump^.next;
(* вставляем ее в новый список *)
InsertRecord(newtree, temp);
end; { While }
(* теперь помещаем начало нового, сортированного
дерева в начало старого списка *)
list := newtree;
end; { SortByName }
begin
New(root);
root^.Name := 'BETA';
New(root^.next);
root^.next^.Name := 'ALPHA';
New(root^.next^.next);
root^.next^.next^.Name := 'Torture';
WriteLn(root^.name);
WriteLn(root^.next^.name);
WriteLn(root^.next^.next^.name);
end.
|
Структура DCB.
Структура DCB определяет установку управления для последовательного порта ввода-вывода (нам она понадобится для разбора примера с программой управления весами ПетрВес) Примечание : В местах где нельзя дать точный перевод будет дано определение на английском из MSDK и приблизительный его переводОписание в эквиваленте C.
typedef struct _DCB { // dcb
DWORD DCBlength; // Размер DCB
DWORD BaudRate; // Скорость пересылки данных в бодах;
// текущая скорость в бодах
DWORD fBinary: 1; // binary mode, no EOF check
// двоичный режим , не проверять конец
// данных (по умолчанию значение = 1)
DWORD fParity: 1; // Включить проверку четность (по умолчанию
// значение = 1)
DWORD fOutxCtsFlow:1; // CTS управление потоком выхода
DWORD fOutxDsrFlow:1; // DSR управление потоком выхода
DWORD fDtrControl:2; // DTR Тип управления потоком скорости
// передачи данных
DWORD fDsrSensitivity:1; // DSR sensitivity (чувствительность)
DWORD fTXContinueOnXoff:1; // XOFF continues Tx (стоп-сигнал
// продалжает выполнение)
DWORD fOutX: 1; // XON/XOFF out flow control (СТАРТ-
// СИГНАЛ / СТОП-СИГНАЛ для управления
// выходящим потоком (по умолчанию
// значение = 1)
DWORD fInX: 1; // XON/XOFF in flow control (СТАРТ-
// СИГНАЛ / СТОП-СИГНАЛ для управления
// входящим потоком (по умолчанию
// значение = 1)
DWORD fErrorChar: 1; // enable error replacement (включить
// проверку погрешностей по умолчанию=1)
DWORD fNull: 1; // enable null stripping (отвергать
// пустой поток данных (по умолчанию=1))
DWORD fRtsControl:2; // RTS управление потоком данных
DWORD fAbortOnError:1; // abort reads/writes on error
// (проверять операции чтения/записи
// по умолчанию=1)
DWORD fDummy2:17; // reserved ЗАРЕЗЕРВИРОВАНО
WORD wReserved; // not currently used НЕ ДЛЯ
// ИСПОЛЬЗОВАНИЯ
WORD XonLim; // transmit XON threshold (порог
// чувствительности старт-сигнала)
WORD XoffLim; // transmit XOFF threshold (порог
// чувствительности стоп-сигнала)
BYTE ByteSize; // Бит в байте (обычно 8)
BYTE Parity; // 0-4=no,odd,even,mark,space
// (четность байта)
BYTE StopBits; // 0,1,2 = 1, 1.5, 2 (стоповые биты)
char XonChar; // Tx and Rx XON character (вид
// старт сигнал в потоке)
char XoffChar; // Tx and Rx XOFF character (вид
// стоп сигнал в потоке)
char ErrorChar; // error replacement character (какой
// сигнал погрешности,его вид)
char EofChar; // end of input character (сигнал
// окончания потока)
char EvtChar; // received event character РЕЗЕРВ
WORD wReserved1; // reserved; do not use НЕ ДЛЯ
// ИСПОЛЬЗОВАНИЯ
} DCB;
Пример :
with Mode do begin BaudRate := 9600; ByteSize := 8; Parity := NOPARITY; StopBits := ONESTOPBIT; // одиночный стоп-бит Flags := EV_RXCHAR + EV_EVENT2; end;Параметры : DCBlength Размер DCB структуры.
BaudRate Определяет скорость в бодах, в которых порт оперирует. Этот параметр может принимать фактическое значение скорости в бодах, или один из следующих стандартных индексов скорости в бодах:
CBR_110 CBR_19200 CBR_300 CBR_38400 CBR_600 CBR_56000 CBR_1200 CBR_57600 CBR_2400 CBR_115200 CBR_4800 CBR_128000 CBR_9600 CBR_256000 CBR_14400fBinary Определяет, допускается ли двоичный (бинарный) способ передачи данных. Win32 API не поддерживает недвоичные (небинарные) способы передачи данных в потоке порта, так что этот параметр должен быть всегда ИСТИНЕН. Попытка использовать ЛОЖЬ в этом параметре не будет работать. Примечание: Под Windows 3.1 небинарный способ передачи допускается,но для работы данного способа необходимо заполнит параметр EofChar который будет восприниматься конец данных. fParity Определяет, допускается ли проверка четности. Если этот параметр ИСТИНЕН, проверка четности допускается fOutxCtsFlow CTS (clear-to-send) управление потоком выхода fOutxDsrFlow DSR (data-set-ready) управление потоком выхода fDtrControl DTR (data-terminal-ready) управление потоком выхода Принимает следующие значения : DTR_CONTROL_DISABLE Отключает линию передачи дынных DTR_CONTROL_ENABLE Включает линию передачи дынных DTR_CONTROL_HANDSHAKE Enables DTR handshaking. If handshaking is enabled, it is an error for the application to adjust the line by using the EscapeCommFunction function. Допускает подтверждению связи передачи данных Если подтверждение связи допускается, это - погрешность для того чтобы регулировать(корректировать) линию связи, используя функцию EscapeCommFunction. fDsrSensitivity Specifies whether the communications driver is sensitive to the state of the DSR signal. If this member is TRUE, the driver ignores any bytes received, unless the DSR modem input line is high. Определяет возможна ли по порту двухсторонняя передача в ту и в другую сторону сигнала. fTXContinueOnXoff Определяет, останавливается ли передача потока , когда входной буфер становится полный, и драйвер передает сигнал XoffChar. Если этот параметр ИСТИНЕН, передача продолжается после того, как входной буфер становится в пределах XoffLim байтов, и драйвер передает сигнал XoffChar, чтобы прекратить прием байтов из потока . Если этот параметр ЛОЖНЫЙ, передача не продолжается до тех пор , пока входной буфер не в пределах XonLim байтов, и пока не получен сигнал XonChar, для возобновления приема . fOutX Определяет, используется ли управление потоком СТАРТ-СИГНАЛА / СТОП-СИГНАЛА в течение передачи потока порту. Если этот параметр ИСТИНЕН, передача останавливается, когда получен сигнал XoffChar и начинается снова, когда получен сигнал XonChar. fInX Specifies whether XON/XOFF flow control is used during reception. If this member is TRUE, the XoffChar character is sent when the input buffer comes within XoffLim bytes of being full, and the XonChar character is sent when the input buffer comes within XonLim bytes of being empty. Определяет, используется ли управление потоком СТАРТ-СИГНАЛА / СТОП-СИГНАЛА в течение приема потока портом. Если этот параметр ИСТИНЕН,сигнал XoffChar посылается , когда входной буфер находится в пределах XoffLim байтов, а сигнал XonChar посылается тогда когда входной буфер находится в пределах XonLim байтов или является пустым fErrorChar Определяет, заменены ли байты, полученные с ошибками четности особенностью, указанной параметром ErrorChar Если этот параметр ИСТИНЕН, и fParity ИСТИНЕН, замена происходит. fNull Определяет, отвергнуты ли нулевые(пустые) байты. Если этот параметр ИСТИНЕН, нулевые(пустые) байты, будут отвергнуты при получении их. fRtsControl RTS управление потоком " запрос пересылки ". Если это значение нулевое, то по умолчанию устанавливается RTS_CONTROL_HANDSHAKE. Принимает одно из следующих значений: RTS_CONTROL_DISABLE Отключает строку RTS, когда устройство открыто RTS_CONTROL_ENABLE Включает строку RTS RTS_CONTROL_HANDSHAKE Enables RTS handshaking. The driver raises the RTS line when the "type-ahead" (input) buffer is less than one-half full and lowers the RTS line when the buffer is more than three-quarters full. If handshaking is enabled, it is an error for the application to adjust the line by using the EscapeCommFunction function. Допускает RTS подтверждение связи. Драйвер управляет потоком пересылки.RTS выравнивается , когда входной буфер - меньше чем половина полного и понижается, когда буфер - больше 2/3 полного .Если подтверждение связи допускается, это используется для регулирования передачи данных EscapeCommFunction. RTS_CONTROL_TOGGLE Specifies that the RTS line will be high if bytes are available for transmission. After all buffered bytes have been sent, the RTS line will be low. Определяет, что буфер будет высокий при подготовке данных для передачи. После того, как все байты отосланы, буфер RTS будет низок. FAbortOnError Определяет, закончена ли операции чтения/записи, если происходит погрешность. Если этот параметр ИСТИНЕН, драйвер закрывает все операции чтения/записи с состоянием погрешности при возникновении оной. Драйвер не будет принимать никакие дальнейшие действия, пока не дождется подтверждения погрешности в передоваемых (принимаемых) данных, вызывая функцию ClearCommError. fDummy2 ЗАРЕЗЕРВИРОВАНО Microsoft wReserved ЗАРЕЗЕРВИРОВАНО Microsoft XonLim Определяет минимальное число байтов, находящихся во входном буфере прежде, чем будет генерирована подача СТАРТ-СИГНАЛА XoffLim Определяет максимальное число байтов, находящихся во входном буфере прежде, чем будет генерирована подача СТОП-СИГНАЛА. Максимальное число байтов, позволенных во входном буфере вычитается из размеров, в байтах, самого входного буфера. ByteSize Определяет число битов в байтах, переданных и полученных. Parity Определяет схему четности, которую нужно использовать. Этот параметр может быть одним из следующих значений: EVENPARITY MARKPARITY NOPARITY ODDPARITY StopBits Определяет число стоповых битов, которые нужно использовать. Этот параметр может быть одним из следующих значений: ONESTOPBIT 1 stop bit ONE5STOPBITS 1.5 stop bits TWOSTOPBITS 2 stop bits XonChar Определяет значение СТАРТ-СИГНАЛА для передачи и приема. XoffChar Определяет значение СТОП-СИГНАЛА для передачи и приема. ErrorChar Определяет значение СИГНАЛА ОШИБКИ (генерируемого при ошибке четности) для передачи и приема. EofChar Определяет значение сигнала конца данных. EvtChar Определяет значение сигнала события. wReserved1 ЗАРЕЗЕРВИРОВАНО Microsoft Дополнение: Когда структура DCB использует «ручной» выбор конфигурации , следующие ограничения используются для ByteSize и StopBits параметров : Число информационных разрядов должно быть от 5 до 8 битов.
Использование 5 информационных разрядов с 2 стоповыми битами - недопустимая комбинация, как - 6, 7, или 8 информационных разрядов с 1.5 стоповыми битами.
Использование компилятора Delphi (dcc32.exe) в прикладных программах.
Оформил: DeeCo
Автор: Сергей Гурин.
Введение. Технология.Хотя в названии статьи тема выглядит довольно узко, я хотел бы рассказать не только об использовании dcc32, но и о технологии, которой я дал условное название "многозвенное программирование", хотя вынести это название в заголовок статьи мне показалось неправильным. Какой смысл я вкладываю в термин "многозвенное программирование"? Начну издалека. Работу над более или менее большими программами можно разделить на два крупных этапа. Первый этап - это собственно разработка, которая включает формулировку технического задания, увязку требований заказчика, проектную фазу, итеративное уточнение структуры проекта, программирование, отладку и тестирование. Первый этап заканчивается выпуском первой версии и началом эксплуатации программы у заказчика (или у массы пользователей, если программа была разработана по собственной инициативе для распространения или продажи). Затем наступает этап сопровождения, который включает устранение обнаруживаемых ошибок, адаптацию к постоянно изменяющимся требованиям заказчика, ввод дополнительных возможностей, которые не были оговорены в исходном задании. Часто бывает так - за время сопровождения программа претерпевает настолько существенные изменения, что сопровождение ее становится делом гораздо более трудоемким и хлопотным, чем разработка.
Если первый этап достаточно хорошо поддерживается разнообразным программистским инструментарием, то второй этап в этом смысле поддержан значительно хуже. Главной целью "многозвенного программирования" как раз и является поддержка этапа сопровождения. В чем основная идея этого подхода? Рассмотрим простую схему:
разработчик <---> заказчикВ этой схеме заказчик использует только те функциональные возможности программы, которые предоставлены разработчиком. Для изменения этих возможностей заказчик выставляет требования разработчику, разработчик изменяет программу и возвращает заказчику. Таким образом, при интенсивном изменении требований к программе, у разработчика всегда большая загрузка, а у заказчика постоянно тормозится работа. Рассмотрим другую схему:
разработчик <---> технолог <---> пользовательВ этой схеме заказчик образно разделяется на две составляющие - технолог и пользователь. Под технологом здесь понимается человек (или группа), который является посредником между разработчиком и пользователем. Технолог профессионально владеет той предметной областью, для которой разработана программа, но не является программистом - это может быть энергетик, астроном, режиссер. Причем, такое разделение заказчика может быть чисто условным - один и тот же человек может выполнять функции, как технолога, так и конечного пользователя. Технолог - это ключевое звено в цепочке. Технолог знает предметную область значительно лучше разработчика и, весьма часто, хотел бы изменить функционирование программы так, как не было предусмотрено программистом. Частые обращения к разработчику могут быть весьма затруднительными - как во времени, так и в пространстве.
Для улучшения этой ситуации можно сделать следующее - передать часть работы программиста технологу. Поскольку технолог по определению не является программистом, нужна дополнительная связующая часть. Такой связующей частью может быть проблемно-ориентированный язык, который разработчик включает в свой проект и которым технолог может воспользоваться для изменения функциональности программы (в разумных пределах). Естественно, что этот язык должен оперировать терминами той предметной области, в которой работает технолог. То есть, между формулировкой задачи и языком ее решения нужен минимальный семантический разрыв. Универсальные языки программирования на эту роль явно не подойдут. Внешней синтаксической формой проблемно-ориентированного языка может быть текст, граф, схема, короче то, на чем технолог наиболее адекватно формулирует свои конкретные задачи. Таким образом, интенсивность взаимодействия между разработчиком и технологом может быть уменьшена, так как значительную часть изменений технолог может делать самостоятельно.
Эта идея используется многими разработчиками, но в литературе я не встречал ее обсуждения как инструмента для сопровождения. В этом смысле вместо термина "многозвенное программирование" обычно используется термин "проблемно-ориентированный язык".
В многозвенной структуре, которую я нарисовал выше, содержится только 2 программирующих звена, но в реальности этих звеньев может быть больше. Если предметная область достаточно разнородна, то "технолог" может быть целой цепочкой технологов - конкретный пример я приведу в самом конце статьи.
Конечно, здесь есть и другая сторона медали - маркетинговые соображения. Если заказчик получит развиваемый инструмент, то разработчик может остаться в проигрыше. И чем лучше инструмент, тем менее вероятно, что заказчик будет оплачивать сопровождение программы. Здесь уже решать разработчику - или заниматься только сопровождением старых программ или высвобождать время для новых разработок.
Вопросы реализации.Вот собственно то, что я хотел сказать о технологии "многозвенного программирования". Теперь практическая часть. Как можно реализовать такую технологию? Один из наиболее используемых вариантов - это встраивание в программу интерпретатора проблемно-ориентированного языка. Встраивание компилятора я встречал значительно реже. В своей программистской практике я использовал оба варианта. Достоинства интерпретатора очевидны, но также очевидны и недостатки. Общий принцип состоит в том, что интерпретатор либо каждый раз интерпретирует исходное представление программы на проблемно-ориентированном языке, либо порождает промежуточный код, исполняемый некоторой виртуальной машиной. Примеры - пакет Microsoft Office с языком VB for Applications, реализация языка Java. Сюда же относятся все скриптовые языки. Если требуется межплатформенная совместимость, то интерпретаторы, вероятно, будут лучшим вариантом. Если же более важны соображения эффективности, то компиляция оказывается более предпочтительным выбором. Общий принцип - берем описание задачи на проблемно-ориентированном языке, генерируем соответствующий код на некотором универсальном языке, компилируем его и выполняем.
В этом разделе статьи я расскажу о втором варианте - использование компилятора, а конкретно, dcc32.exe из поставки Delphi (хотя можно использовать любой другой быстрый и качественный компилятор для любого другого подходящего языка).
Далее я рассмотрю этапы, которые нужно сделать, чтобы воплотить замыслы технолога. Конкретности реализации демонстрируется на примере небольшой библиотеки, которую прилагаю к статье (называется она DccUsing) и крошечного проекта под незатейливым именем DccExamples.
1. Генерация кодаНа основе исходного представления, которое формулирует технолог, нужно сгенерировать код для компиляции. Исходное представление может быть любым, в простейшем случае - это обычный текст. В процессе генерации кода наибольшее внимание нужно уделить диагностике ошибок. То есть, ошибки желательно выявить во время генерации кода и генерировать уже синтаксически правильный код. Для этого можно использовать любые доступные методы, вплоть до синтаксических анализаторов с рекурсивным спуском - такие анализаторы достаточно просты и описаны во многих книгах, например у Бьерна Страуструпа в "Язык программирования C++" (Третье издание). Если есть возможность, то желательно контролировать также семантическую правильность. Далее я буду рассматривать только те моменты, которые являются общими для всех задач без учета их специфики.
Генерировать исходный текст можно любым способом, например, просто посылая строки текста в файл. Более удобный способ, как мне кажется, это направление текста в строко-ориентированный поток. Такой поток предоставляет дополнительное удобство при диагностике ошибок. Библиотека DccUsing содержит два потоковых класса: TFileCompileOut и TStringCompileOut, которые порождаются от TCompileOut. Классы очень просты, их реализацию можно посмотреть в исходном файле библиотеки, поэтому я дам только обзор. Базовый класс имеет методы:
public
procedure IncLevel;
procedure DecLevel;
procedure AddSpace;
procedure AddLine(const aLine: String);
procedure AddLines(const aLines: array of String);
procedure AddFile(const aFileName: String);
procedure AddLineTemplate(const aLine: String;
const aArgs: array of String);
procedure AddLinesTemplate(const aLines, aArgs: array of String);
procedure AddFileTemplate(const aFileName: String;
const aArgs: array of String);
procedure AddPoint(aPoint: Integer);
function FindPoint(aLine: Integer): Integer;
property Level: Integer read FLevel;
property LinesCount: Integer read FLinesCount;
Первые три метода позволяют управлять форматированием кода. Хотя форматирование совсем не обязательно (код никто не читает), но дает удобства при отладке, а, кроме того, мне нравится, когда программа выглядит эстетично. IncLevel увеличивает отступ текста, DecLevel уменьшает, а AddSpace добавляет в поток пустую строку. Два следующих метода добавляют в поток соответственно строку и массив строк, а метод AddFile - весь указанный файл. Свойства позволяют узнать текущий уровень отступа и текущее число строк в потоке. Назначение методов AddPoint и FindPoint будет объяснено в разделе диагностики ошибок.
Методы AddLineTemplate, AddLinesTemplate и AddFileTemplate более сложны, чем предыдущие методы, представляют собой простые макропроцессоры и позволяют параметризовать генерируемый текст. Параметризующие аргументы - это массив строк, которые заменяют метасимволы в исходном тексте шаблона. Метасимволы выглядят так: {{X}}, где Х - это порядковый номер аргумента, начиная от 1. Макроподстановка производится без всякого учета лексики. Поэтому можно параметризовать все что угодно - идентификаторы, строки, комментарии, операторы и т.д. Например, если шаблон текста таков:
const
tFunc: array[0..5] of string = (
'function {{1}}.SortProc{{2}}(const a1, a2: {{2}}): Integer;',
'begin',
' if a2 > a1 then result := 1',
' else if a2 = a1 then result := 0',
' else result := -1;',
'end;'
);
//то при использовании
c.AddLinesTemplate(tFunc, ['TTestClass1', 'Integer']);
//мы получим такой результат:
function TTestClass1.SortProcInteger(const a1, a2: Integer): Integer;
begin
if a2 > a1 then
result := 1
else if a2 = a1 then
result := 0
else
result := -1;
end;
//а при использовании
c.AddLinesTemplate(tFunc, ['TTestClass2', 'String']);
//такой:
function TTestClass2.SortProcString(const a1, a2: string): Integer;
begin
if a2 > a1 then
result := 1
else if a2 = a1 then
result := 0
else
result := -1;
end;
Наследуемые классы переопределяют абстрактную процедуру записи строки в поток и имеют специфические методы. Класс TFileCompileOut специализируется на построчном выводе в файл:
public
constructor Create(const aFileName: String);
destructor Destroy; override;
property FileName: String read FFileName;
{Конструктор принимает имя файла и открывает файл на чтение, а деструктор
закрывает файл.
Класс TStringCompileOut хранит генерируемый текст в памяти:}
public
procedure Clear;
procedure SaveToFile(const aFileName: String);
procedure SaveToOut(aOut: TCompileOut);
property Capacity: Integer ...
property Items[aIndex: Integer]: String ... default;
Методы класса позволяют очистить поток, сохранить поток в файле и добавить его к другому потоку. Свойства позволяют изменить резервируемый объем памяти для списка строк и получить доступ на запись и чтение строк по индексу. Общее число строк определяет наследуемое свойство LinesCount. Примеры использования этих классов смотрите в DccExamples.pas.
Отметим, что часть неизменяемого или шаблонного кода может быть заготовлена заранее, располагаться в файлах и объединяться в нужных местах результирующего кода с помощью AddFile и AddFileTemplate. По ходу генерации кода может быть создано несколько потоков - для деклараций переменных и констант, деклараций и реализаций классов и так далее. После просмотра всей задачи, сформулированной технологом, эти потоки сшиваются в один результирующий поток. Для частных потоков можно использовать строковую реализацию, а для результирующего потока - файловую.
2. КомпиляцияПосле того, как исходный код создан, требуется его откомпилировать. Компилятор dcc32 замечательно подходит для этой роли - он очень быстрый, качественный и объединяет в себе все, что необходимо для построения exe-файлов, dll-библиотек и пакетов. Размер файла dcc32.exe (версия 12.0, из Delphi 5) всего 545 Кб, ранние версии имеют еще меньший размер. К нему нужно добавить только три файла - rlink32.dll, sysinit.dcu и system.dcu (это минимум). Компилятор и указанные файлы можно разместить в подкаталоге прикладной программы, например, bin. Генерировать текст целесообразно в подкаталоге компилятора, например, bin\pas, чтобы использовать короткие пути файлов и не засорять каталог компилятора.
Для вызова dcc32.exe в библиотеке DccUsing определена функция ExecDcc32. Она устанавливает текущий каталог, создает файл для перехвата ошибок компиляции, вызывает компилятор, дожидается завершения компиляции и определяет наличие ошибок.
function ExecDcc32(const aDccDir, aOptions, aProjectPath, aErrorPath: string; aCheckPaths: Boolean = False): Boolean;Функция принимает аргументы: aDccDir - каталог, в котором находится компилятор Dcc32, aOptions - опции компилятора (рекомендации по их использованию смотрите в файле DccUsing.pas), aProjectPath - путь файла проекта (обычно dpr), aErrorPath - путь файла, куда будут направляться сообщения об ошибках компиляции. Необязательный аргумент aCheckPaths позволяет разрешить или запретить контроль наличия каталога и файла dcc32.exe. Функция возвращает True, если компиляция была успешной и False в противном случае. Предупреждения (hints и warnings) ошибками не считаются - их выводом можно управлять с помощью опций -H и -W. Опуская детали, рассмотрим немного подробнее эту функцию:
// сохранение текущего каталога и установка нового
CurDir := GetCurrentDir;
if not SetCurrentDir(DccDir) then
raise Exception.Create(SCantChangeDir + DccDir);
try
hStdOut := INVALID_HANDLE_VALUE;
try
// установки атрибутов безопасности
with SecurAtt do
begin
nLength := SizeOf(SecurAtt);
lpSecurityDescriptor := nil;
// разрешить наследование дочернему процессу
bInheritHandle := BOOL(True);
end;
// создание файла, в который будут направляться ошибки
hStdOut := CreateFile(PChar(aErrorPath), GENERIC_WRITE, 0,
@SecurAtt, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if hStdOut = INVALID_HANDLE_VALUE then
raise Exception.Create(SCantCreateFile + aErrorPath);
// заполнение структуры, специфицирующей создание процесса
ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
with StartupInfo do
begin
cb := SizeOf(StartupInfo);
// скрывать окно компилятора и наследовать потоки ввода-вывода
dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
wShowWindow := SW_HIDE;
hStdOutput := hStdOut;
end;
// создать и стартовать процесс компилятора
s := 'dcc32.exe ' + aOptions + ' ' + aProjectPath;
if not CreateProcess('dcc32.exe', PChar(s), @SecurAtt, @SecurAtt,
BOOL(True), 0, nil, PChar(DccDir), StartupInfo, ProcessInfo) then
raise Exception.Create(SCantCreateProcess + 'dcc32.exe');
// ждать завершение компиляции неопределенное время
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
// получить результат компиляции
ResultCode := 0;
GetExitCodeProcess(ProcessInfo.hProcess, ResultCode);
result := ResultCode = 0;
finally
// закрыть файл ошибок
if hStdOut <> INVALID_HANDLE_VALUE then
CloseHandle(hStdOut);
end;
finally
// восстановить прежний каталог по умолчанию
SetCurrentDir(CurDir);
end;
Установка каталога компилятора, как текущего, позволяет не заботиться о мелочах, связанных с назначением путей. Компилятор направляет сообщения об ошибках в стандартный файл вывода. Для его перехвата создаем свой файл, дескриптор которого передаем компилятору. Для того, чтобы процесс компилятора мог наследовать дескриптор открытого файла, устанавливаем его атрибут наследования. При заполнении структуры StartupInfo указываем, что окно компилятора должно быть скрытым и порождаемый процесс должен наследовать стандартные потоки ввода-вывода. Атрибуты безопасности, передаваемые функции создания процесса, нужны для правильной работы в NT, в Windows 95-98 их можно было бы опустить. Функция CreateProcess сохраняет параметры процесса в структуре ProcessInfo - мы используем дескриптор процесса, чтобы передать его функции ожидания системного события - в данном случае, завершения процесса. С помощью GetExitCodeProcess получаем значение, которое возвращает компилятор. Если компиляция была успешной, то возвращается 0, иначе - ненулевое значение. Операции закрытия файла ошибок и восстановления предыдущего каталога произойдут независимо от возможных исключительных ситуаций по ходу функции ExecDcc32.
Компилятору, вместе с исходным файлом (файлами), нужно также передать файл проекта (dpr) и уточнить в опциях, что же будет результатом компиляции. Возможных вариантов много - GUI или консольное приложение, dll, пакет, ActiveX (наверное, есть еще варианты). Выбор вида компиляции связан со спецификой задачи, требованиями пользователя и вкусами разработчика. К этому вопросу я еще раз вернусь в разделе Исполнение кода.
3. Диагностика ошибок Идеальный вариант - это генерация синтаксически и семантически правильного кода. Но проверка семантики в большинстве случаев вряд ли возможна, поэтому желательно генерировать, по крайней мере, синтаксически правильный код. В этом случае компиляция всегда будет успешной. Если проверка синтаксической корректности затруднительна или невозможна, то приходится полагаться на диагностику, которую сформирует компилятор. Конечно, давать эту диагностику технологу - это самый последний случай, когда уже ничего не остается. Более спокойный вариант - это извлечь из файла ошибок номера ошибочных строк и определить, чему они соответствуют в том описании, который сделал технолог. Для разбора файла ошибок, библиотека DccUsing содержит класс TParseDcc32Errors. Класс весьма прост, поэтому я только обрисую его интерфейс:
TCompileMessageStatus = (cmsNone, cmsHint, cmsWarning,
cmsError, cmsFatal);
public
procedure ParseFile(const aFileName: String);
function MessagesCount: Integer;
function StatusCount(aStatus: TCompileMessageStatus): Integer;
function MessageText(aIndex: Integer): String;
function MessageStatus(aIndex: Integer): TCompileMessageStatus;
function MessageFile(aIndex: Integer): String;
function MessageLine(aIndex: Integer): Integer;
TCompileMessageStatus перечисляет все возможные статусы ошибок. Процедура ParseFile выполняет разбор файла ошибок и сохраняет результат в своем приватном списке. Функция MessagesCount возвращает общее количество сообщений, а StatusCount - количество сообщений с заданным статусом. Оставшиеся 4 функции разбирают строку сообщения компилятора на составляющие - текст сообщения, статус, имя файла, в котором обнаружена ошибка и номер строки.
Вот теперь можно вернуться к необъясненным методам TCompileOut. Метод AddPoint добавляет в поток контрольную точку. Контрольная точка - это просто целое число, которое помечает уникальным номером начало некоторой части генерируемого кода и жестко связывается с номером строки. Контрольная точка может служить, например, индексом в таблице ошибок. Расставив при генерации кода такие точки-метки, мы можем локализовать место ошибки. Для поиска ошибки нужно повторить генерацию кода без вызова компилятора (чтобы опять сформировать выходной поток), а затем, для результирующего выходного потока, вызвать функцию FindPoint, передав ей номер ошибочной строки. Эта функция определит ближайшую точку ошибки. Если генерируется несколько файлов исходных кодов, то выбор ошибочного файла сделать с помощью функции, возвращающей имя файла - MessageFile.
4. Исполнение кодаКак я уже говорил, возможны различные варианты того, в какой вид будет скомпилирована задача, сформулированная технологом. Если технолог передает результаты своей работы конечному пользователю, то удобный вариант - exe-файл. Если технолог решает некоторую задачу и сразу же пользуется результатами решения, то сам факт компиляции должен быть для него полностью прозрачен (или максимально незаметен). Технолог работает в программе, которая сделана разработчиком и, по большому счету, ему совершенно безразлично, каким конкретно способом разработчик предоставляет возможность изменять функциональность программы. Существует несколько технологий построения гибко подгружаемых модулей, и они описаны в литературе. Я остановлюсь только на одной технологии - динамическая загрузка и выгрузка DLL. Если результирующий проект, который нужен технологу, содержит визуальные формы (а их можно генерировать как dfm-файлы), то вероятно, более предпочтительными будут пакеты.
Возможен также вариант, когда исполняемый код делится на две составляющие - исполняемое ядро (exe-файл) и подгружаемый модуль. Ядро создается разработчиком и динамически подключает модули, создаваемые технологом. Достоинство такого подхода в том, что работу с визуальными компонентами можно сосредоточить в ядре, а в DLL формировать только алгоритмическую часть задачи. Другое достоинство такого подхода - технолог может работать в мощной интегрированной среде, а конечному пользователю он передает только ядро и нужный модуль, скрывая от пользователя все технологические детали.
Для работы с DLL, в библиотеку DccUsing добавлен класс TDllWrap - простая оболочка, инкапсулирующая дескриптор загруженной DLL. Основные методы класса:
public
constructor Create(const aDllPath: String);
destructor Destroy; override;
function Execute(const aFunctionName: String;
const aInterface: Pointer): Pointer;
Конструктор Create просто сохраняет путь к файлу DLL и больше ничего не делает, деструктор Destroy выгружает DLL из памяти, если она была загружена. Основную работу делает метод Execute - он вызывает экспортируемую функцию DLL по имени и передает ей указатель на интерфейс вызывающей части. Экспортируемая функция возвращает интерфейс вызываемой части. Более подробно о взаимодействии вызывающей и вызываемой частей поговорим в следующем разделе, а пока рассмотрим реализацию метода Execute.
function TDllWrap.Execute(const aFunctionName: string;
const aInterface: Pointer): Pointer;
var
f: TDllFunction;
begin
if FDllInst = 0 then
begin
if not FileExists(FDllPath) then
raise Exception.Create(SFileNotFound + FDllPath);
FDllInst := LoadLibrary(PChar(FDllPath));
if FDllInst = 0 then
raise Exception.Create(SCantLoadDll +
SysErrorMessage(GetLastError));
end;
f := TDllFunction(GetProcAddress(FDllInst,
PChar(aFunctionName)));
if not Assigned(f) then
raise Exception.Create(SCantFindFunction + aFunctionName);
result := f(aInterface);
end;
Вначале метод Execute контролирует - загружена ли DLL? и, если DLL еще не загружена, то она загружается. Если загрузка была успешной, то с помощью функции GetProcAddress получаем адрес экспортируемой функции по ее символическому имени (можно также использовать индекс). Если адрес функции успешно получен, то вызываем ее и передаем ей аргумент - указатель на вызывающий интерфейс. Функция возвращает указатель на вызываемый интерфейс. Из этой реализации видно, что вызывающая часть может обратиться с помощью метода Execute к нескольким различным функциям DLL или многократно к одной и той же функции - DLL будет загружена только один раз.
5. Взаимодействие с DLL С самых общих позиций можно считать, что вызывающая (Master) и вызываемая (Slave) части обладают своими интерфейсами. Экспортируемая функция конструирует Slave-интерфейс и возвращает его. Экспортируемая функция играет в этом случае роль фабрики класса. Сигнатура экспортируемой функции выглядит так:TDllFunction = function(aInterface: Pointer): Pointer; StdCall;После вызова этой функции Master и Slave части взаимодействуют друг с другом через свои интерфейсы. В качестве интерфейса наиболее удобно использовать чистый абстрактный класс, например:
IMaster = class public procedure Method1; virtual; abstract; ............. end;Виртуальный абстрактный класс не содержит переменных, а все его методы - виртуальные и абстрактные. Декларация интерфейса включается в обе взаимодействующие части. Для реализации интерфейса создается класс, наследуемый от абстрактного интерфейса и переписывающий все его виртуальные методы. Интерфейсный объект Master-части конструируется и удаляется в основной программе. Интерфейсный объект Slave-части конструируется в экспортируемой функции DLL, а уничтожается в блоке finalization при выгрузке DLL или с помощью другой экспортируемой функции. Например:
uses
UnitIMaster, UnitISlave;
type
TSlaveObject = class(ISlave)
private
FMain: IMain;
public
constructor Create(aMain: IMain);
destructor Destroy; override;
procedure Method1; override;
............
end;
function CreateSlave(aInterface: Pointer): Pointer; stdcall;
function DestroySlave(aInterface: Pointer): Pointer; stdcall;
implementation
var
SlaveObject: TSlaveObject;
// Реализация TSlaveObject
............
function CreateSlave(aInterface: Pointer): Pointer;
begin
SlaveObject := TSlaveObject.Create(IMaster(aInterface));
result := SlaveObject;
end;
function DestroySlave(aInterface: Pointer): Pointer;
begin
SlaveObject.Free;
SlaveObject := nil;
result := nil;
end;
initialization
SlaveObject := nil;
finalization
SlaveObject.Free;
end.
Пример реализации
Эффект использования описанной технологии повышается при увеличении сложности программы, для простых программ она вряд ли целесообразна. В качестве примера я расскажу об одной из своих разработок: Visual2k - Интегрированная среда для программирования микроконтроллерных кукол-роботов. Подробнее о ней и о других программах, использующих "многозвенное программирование" можно узнать на моем web-сайте.
Программа Visual2k разработана для томского театра кукол "2+Ку" и проекта "Оживление пространства". Суть проекта состоит в создании кукол-роботов, используемых в рекламных целях, в витринах магазинов и кафе, в качестве гидов на выставках. Куклы могут быть одиночными или работать совместно в автоматическом спектакле. Разработка каждого нового проекта включает в себя такие фазы - художник и сценарист по заказу придумывают сценарий спектакля, затем, вместе с конструкторами обсуждают детали реализации. Когда детали проекта уточнились, инженер-электронщик изготавливает микроконтроллерную аппаратуру, инженер-механик конструирует механику кукол и приводы двигателей, а режиссер - создает с готовыми куклами спектакль. То есть, здесь мы имеем целую цепочку технологов, каждый из которых работает со своей предметной областью.
Visual2k содержит подсистемы, которые позволяют всем технологам работать над проектом в одной и той же интегрированной среде. Задача первого технолога (электронщика) - не только сконструировать аппаратуру, но и начать создание базы проекта. Вот так выглядит подсистема, в которой он работает:
Электронщик вносит в базу проекта имена и типы микроконтроллеров, а также назначает привязку приводов к конкретным выводам микроконтроллеров. Этой информации достаточно, чтобы автоматически сгенерировать программу для микроконтроллеров. Исходный код программы для микроконтроллеров генерируется на языке Си с использованием заранее заготовленной библиотеки драйверов и компилируется Си-компилятором. В зависимости от типа микроконтроллера, генерируется тот или иной Си код.
После этого, файл проекта поступает ко второму технологу (механику). Он работает в этой же подсистеме и создает актеров или их части, назначает действия, которые должны делать куклы-актеры и учитывает специфику приводов, например, создает таблицу скоростей двигателей.
Полученный файл проекта вместе с готовой механикой и электроникой поступает к третьему технологу - режиссеру. Он работает уже с другой предметной областью - сценарием. Поскольку сценарии бывают очень сложными, то режиссер программирует движения кукол на простом алгоритмическом языке, который включает в себя понятия параллельных и последовательных веточек, циклов, условий и команд управления куклами. Вот как выглядит подсистема, в которой работает режиссер:
Здесь стоит отметить, что Visual2k немного напоминает Delphi, с той разницей, что здесь все представлено визуальными компонентами, даже переменные и операторы. Режиссер выстраивает сценарий, выкладывая на рабочую область компоненты-операторы, и назначает их свойства с помощью инспектора объектов. Программа сценария, полученная таким образом, может выполняться либо на персональном компьютере (для сложных говорящих кукол или спектаклей), либо на одном микроконтроллере или сети микроконтроллеров. Исходный вид сценария один и тот же, но генерируется либо текст на языке Object Pascal, который компилируется dcc32, либо текст на языке Си, объединяемый с частью, заданной электронщиком и компилируемый Си-компилятором. Поскольку в операторах присваивания и в условиях могут быть выражения, Visual2k включает в себя синтаксический анализатор выражений, обнаруживающий все синтаксические ошибки и заменяющий русские имена переменных и функций на имена, допустимые для языка Pascal. Если кукольный проект будет выполняться под управлением персонального компьютера, то сценарий компилируется в DLL, которая вызывается исполняющим ядром. Такая структура позволяет передавать заказчику только исполняющее ядро и DLL, не раскрывая фирменных секретов спектакля. Параллельные процессы, необходимые для адекватного описания сценария реализуются на базе библиотеки параллельного программирования Gala. Если же сценарий выполняется в микроконтроллере, то используется специально разработанная многозадачная среда с кооперативной мультизадачностью и сценарий зашивается прямо в ПЗУ.
Когда мы получили первый заказ на кукольный спектакль, программы Visual2k еще не было, и я писал сценарий самостоятельно (на Delphi) - режиссер сидел рядом ничего не понимая в том, что я делаю, и только давал советы. Я плохо понимал, чего хочет режиссер, а режиссер вообще не понимал, что я делаю. После создания Visual2k, я занимался только развитием интегрированной среды, добавлял поддержку новых типов микроконтроллеров и новых типов приводов, совершенно не вникая в то, какие делались спектакли. Режиссер очень быстро освоил простой язык описания сценариев и получил полную свободу в реализации своих режиссерских замыслов. Так мне удалось расправиться с целым стадом зайцев - существенно облегчить себе задачу сопровождения программы, освободиться от написания конкретных сценариев, освободить электронщика от написания программ для микроконтроллера и высвободить себе время для других разработок.
Скачать файлы проекта DccUsing.Zip (80K).
В чем разница между сокетами, DCOM и OLE Enterprise при использовании их в качестве транспорта.
Автор: Nomadic Sockets (TCP/IP): на клиентах и сервере требуется наличие стека TCP/IP; не требуется дополнительной настройки клиентов;DCOM: на клиентах и серверах требуется наличие DCOM (входит в состав Windows NT 4.0, для Windows 95 доступен как опция) требуется настройка клиентов (DCOM Configuration Utility - DCOMCNFG.EXE); встроенная поддержка модели безопасности Windows NT; поддержка обратных вызовов (методов);CORBA на клиентах и серверах требуется наличие Common Object Request Broker; требуется настройка клиентов; поддержка обратных вызовов (методов);OLE Enterprise: на клиентах и серверах требуется наличие OLE Enterprise; требуется настройка клиентов; поддержка обратных вызовов (методов).DCOM permissions.
Автор: Абдулин Марат WEB-сайт: ООО "АТОЛ технологии"
Технология DCOM основана на технологии COM и представляет собой ее продолжение. Основное назначение DCOM - организация взаимодействия клиента с удаленным сервером.Как пользоваться DCOM.
Чтобы воспользоваться возможностями DCOM должны быть соблюдены следующие требования: Наличие Клиент.exe, Сервер.exe. ("Каркасы" этих приложений прилагаются к документу см Samples\DCOMSvr). Наличие сети как минимум из двух компьютеров (платформы 9x, Me, NT, 2000). Компьютеры должны "видеть" друг друга. На клиентском и серверном компьютере должна быть установлена поддержка DCOM (на NT и 2000 поддержка DCOM есть по умолчанию, в 9x и Me поддержка отключена, ее можно получить по адресу http://www.microsoft.com/com/resources/downloads.asp). Компьютеры должны быть в одном домене (на сколько критично это требование под вопросом, я не исследовал, информация из http://www.delphikingdom.com/). Сервер.exe должен быть зарегистрирован на клиентской машине и серверной машине (после регистрации на клиентской машине Сервер.exe можно удалить). Регистрация Сервер.exe производится из командной строки: сервер.exe regserver. Разрегистрация также из командной строки: сервер.exe unregserver Должен быть настроен DCOM (можно не задумываясь продублировать настройки, как на клиенте, так и на сервере) для запуска и доступа к Сервер.exe (настроить DCOM можно при помощи утилиты DCOMCNFG.EXE или программно, см. Samples\DcomPerm). Если DCOM настраивается для Win9x, то после настройки необходимо перезагрузить компьютер. Если изменяются настройки протоколов используемых в DCOM, то следует перезагрузить компьютер (действительно для любой платформы). Если Сервер.exe запускается на платформе 9x, то сервер должен быть предварительно загружен, можно поместить запуск сервера в StartUp. Скачать исходные коды: DComPermSrc.zip (28 K)Настройка DCOM при помощи DCOMCNFG.EXE
Закладка DefaultProperties| Элемент | Значение | 9x | NT |
| Enable Distributed COM on this computer | True | + | + |
| Enable COM Internet Services on this computer | Не имеет значения | - | + |
| Default Authentication Level | None | + | + |
| Default Impersonation Level | Impersonate | + | + |
| Provide additional security for reference tracking | False | + | + |
| Элемент | Значение | 9x | NT |
| Enable remote connection | True | + | - |
| Default access permissions | Everyone = allow access | - | + |
| Default access permissions | The world = grant access | + | - |
| Default launch permissions | Everyone = allow launch | - | + |
Закладка Applications Позиционироваться на сервер.exe. Кнопка "Properties...".
Закладка Location| Элемент | Значение | 9x | NT |
| Run application on the computer where the data is located | False | + | + |
| Run application on this computer | True | + | + |
| Run application on the following computer | False | + | + |
| Элемент | Значение | 9x | NT |
| Use default access permissions | True | + | + |
| Use default launch permissions | True | - | + |
| Use custom configuration permissions | False | - | + |
| Элемент | Значение | 9x | NT |
| Which user account do you want to use to run this application | The interactive user | - | + |
Программная настройка DCOM
Для программной настройки DCOM можно воспользоваться процедурами модуля BDcomPrmDefaultProperties.
| Элемент | Процедура | Описание | 9x | NT |
| Enable Distributed COM on this computer | IsDCOMOk | Проверяет наличие поддержки DCOM | + | + |
| IsEnabledDCOM | Проверяет разрешен ли DCOM на данной машине | + | + | |
| SetEnableDCOM | Разрешает DCOM на данной машине | + | + | |
| IsDCOMProtocolsEnabled | Проверяет наличие протоколов DCOM | - | + | |
| Default Authentication LevelDefault Impersonation Level | IsInitializeSecurityOk | Проверяет можно ли устанавливать параметры Security. Возвращает True для платформы NT и False для 9x | - | + |
DefaultSecurity.
Application Security.
Все процедуры и функции данной категории получают в качестве входного параметра AppID - CLSID объекта сервера.Закладка Identity.
Все процедуры и функции данной категории получают в качестве входного параметра AppID - CLSID объекта сервера.Все остальные процедуры и функции модуля BDcomPrm носят служебный характер.
Как можно работать с DDE под Delphi, используя вызовы API.
Кстати, достаточно легко: следующий пример демонстрирует как можно научить общаться клиентскую программу с программой-сервером. Обе программы полностью созданы на Delphi. В итоге мы имеет 2 проекта, 3 формы и 3 модуля. Для работы с DDE-запросами данный пример использует методы DDE ML API. Сервер должен начать свою работу перед тем, как клиент будет загружен. Данный пример демонстрирует 3 способа взаимодействия между клиентом и сервером: Клиент может "пропихивать" (POKE) данные на сервер. Сервер может автоматически передавать данные клиенту, после чего клиент обновляет свой вид на основе результатов, полученных от сервера. Данные сервера изменяются, после чего клиент делает запрос серверу для получения новых данных и обновляет свой вид.Как работает программа.Ниже приведены 8 файлов, сконкатенированных в единое целое. Каждый файл имеет следующую структуру: { *** НАЧАЛО КОДА FILENAME.EXT *** } КОД { *** КОНЕЦ КОДА FILENAME.EXT *** }, поэтому вам остается всего-лишь взять код, расположенный между маркерами { *** }, скопировать в файл с соответствующим именем, и собрать оба проекта в среде Delphi.
{ *** НАЧАЛО КОДА DDEMLCLI.DPR *** }
program Ddemlcli;
uses
Forms,
Ddemlclu in 'DDEMLCLU.PAS' {Form1};
{$R *.RES}
begin
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
{ *** КОНЕЦ КОДА DDEMLCLI.DPR *** }
{ *** НАЧАЛО КОДА DDEMLCLU.DFM *** }
object Form1: TForm1
Left = 197
Top = 95
Width = 413
Height = 287
HorzScrollBar.Visible = False
VertScrollBar.Visible = False
Caption = 'Демонстрация DDEML, Клиентское приложение'
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'System'
Font.Style = []
Menu = MainMenu1
PixelsPerInch = 96
OnCreate = FormCreate
OnDestroy = FormDestroy
OnShow = FormShow
TextHeight = 16
object PaintBox1: TPaintBox
Left = 0
Top = 0
Width = 405
Height = 241
Align = alClient
Color = clWhite
ParentColor = False
OnPaint = PaintBox1Paint
end
object MainMenu1: TMainMenu
Top = 208
object File1: TMenuItem
Caption = '&Файл'
object exit1: TMenuItem
Caption = 'В&ыход'
OnClick = exit1Click
end
end
object DDE1: TMenuItem
Caption = '&DDE'
object RequestUpdate1: TMenuItem
Caption = '&Запрос на обновление'
OnClick = RequestUpdate1Click
end
object AdviseofChanges1: TMenuItem
Caption = '&Сообщение об изменениях'
OnClick = AdviseofChanges1Click
end
object N1: TMenuItem
Caption = '-'
end
object PokeSomeData: TMenuItem
Caption = '&Пропихивание данных'
OnClick = PokeSomeDataClick
end
end
end
end
{ *** КОНЕЦ КОДА DDEMLCLU.DFM *** }
{ *** НАЧАЛО КОДА DDEMLCLU.PAS *** }
{***************************************************}
{ }
{ Delphi 1.0 DDEML Демонстрационная программа }
{ Copyright (c) 1996 by Borland International }
{ }
{***************************************************}
{ Это демонстрационное приложение, демонстрирующее использование
DDEML API в клиентском приложении. Оно использует серверное
приложение DataEntry, которое является частью данной демонстрации,
и служит для ввода данных и отображения их на графической панели.
Сначала вы должны запустить приложение-сервер (в DDEMLSRV.PAS),
а затем стартовать клиента. Если сервер не запущен, клиент при
попытке соединения потерпит неудачу.
Интерфейс сервера определен списком имен (Service, Topic и Items)
в отдельном модуле с именем DataEntry (DATAENTR.TPU). Сервер
делает Items доступными в формате cf_Text; они преобразовываются
и хранятся локально как целые. }
unit Ddemlclu;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, VBXCtrl, ExtCtrls, DDEML, Menus, StdCtrls;
const
NumValues = 3;
type
{ Структура данных, представленная в примере }
TDataSample = array[1..NumValues] of Integer;
TDataString = array[0..20] of Char; { Размер элемента как текста }
{ Главная форма }
TForm1 = class(TForm)
MainMenu1: TMainMenu;
File1: TMenuItem;
exit1: TMenuItem;
DDE1: TMenuItem;
RequestUpdate1: TMenuItem;
AdviseofChanges1: TMenuItem;
PokeSomeData: TMenuItem;
N1: TMenuItem;
PaintBox1: TPaintBox;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure RequestUpdate1Click(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure AdviseofChanges1Click(Sender: TObject);
procedure PokeSomeDataClick(Sender: TObject);
procedure Request(HConversation: HConv);
procedure exit1Click(Sender: TObject);
procedure PaintBox1Paint(Sender: TObject);
private
{ Private declarations }
public
Inst: Longint;
CallBackPtr: ^TCallback;
ServiceHSz: HSz;
TopicHSz: HSz;
ItemHSz: array[1..NumValues] of HSz;
ConvHdl: HConv;
DataSample: TDataSample;
end;
var
Form1: TForm1;
implementation
const
DataEntryName: PChar = 'DataEntry';
DataTopicName: PChar = 'SampledData';
DataItemNames: array[1..NumValues] of pChar = ('DataItem1',
'DataItem2',
'DataItem3');
{$R *.DFM}
{ Локальная функция: Процедура обратного вызова для DDEML }
function CallbackProc(CallType, Fmt: Word; Conv: HConv; hsz1, hsz2: HSZ;
Data: HDDEData; Data1, Data2: Longint): HDDEData; export;
begin
CallbackProc := 0; { В противном случае смотрите доказательство }
case CallType of
xtyp_Register:
begin
{ Ничего ... Просто возвращаем 0 }
end;
xtyp_Unregister:
begin
{ Ничего ... Просто возвращаем 0 }
end;
xtyp_xAct_Complete:
begin
{ Ничего ... Просто возвращаем 0 }
end;
xtyp_Request, Xtyp_AdvData:
begin
Form1.Request(Conv);
CallbackProc := dde_FAck;
end;
xtyp_Disconnect:
begin
ShowMessage('Соединение разорвано!');
Form1.Close;
end;
end;
end;
{ Посылка DDE запроса для получения cf_Text данных с сервера.
Запрашиваем данные для всех полей DataSample, и обновляем
окно для их отображения. Данные с сервера получаем синхронно,
используя DdeClientTransaction.}
procedure TForm1.Request(HConversation: HConv);
var
hDdeTemp: HDDEData;
DataStr: TDataString;
Err, I: Integer;
begin
if HConversation <> 0 then
begin
for I := Low(ItemHSz) to High(ItemHSz) do
begin
hDdeTemp := DdeClientTransaction(nil, 0, HConversation, ItemHSz[I],
cf_Text, xtyp_Request, 0, nil);
if hDdeTemp <> 0 then
begin
DdeGetData(hDdeTemp, @DataStr, SizeOf(DataStr), 0);
Val(DataStr, DataSample[I], Err);
end; { if }
end; { for }
Paintbox1.Refresh; { Обновляем экран }
end; { if }
end;
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
{ Создаем экземпляр окна DDE-клиента. Создаем окно, используя
унаследованный конструктор, инициализируем экземпляр данных.}
begin
Inst := 0; { Должен быть нулем для первого вызова DdeInitialize }
CallBackPtr := nil; { MakeProcInstance вызывается из SetupWindow }
ConvHdl := 0;
ServiceHSz := 0;
TopicHSz := 0;
for I := Low(DataSample) to High(DataSample) do
begin
ItemHSz[I] := 0;
DataSample[I] := 0;
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
{ Уничтожаем экземпляр клиентского окна. Освобождаем дескрипторы
DDE строк, и освобождаем экземпляр функции обратного вызова,
если она существует. Также, для завершения диалога, вызовите
DdeUninitialize. Затем, для завершения работы, вызовите
разрушителя предка. }
var
I: Integer;
begin
if ServiceHSz <> 0 then
DdeFreeStringHandle(Inst, ServiceHSz);
if TopicHSz <> 0 then
DdeFreeStringHandle(Inst, TopicHSz);
for I := Low(ItemHSz) to High(ItemHSz) do
if ItemHSz[I] <> 0 then
DdeFreeStringHandle(Inst, ItemHSz[I]);
if Inst <> 0 then
DdeUninitialize(Inst); { Игнорируем возвращаемое значение }
if CallBackPtr <> nil then
FreeProcInstance(CallBackPtr);
end;
procedure TForm1.RequestUpdate1Click(Sender: TObject);
begin
{ Генерируем запрос DDE в ответ на выбор пункта меню DDE | Request.}
Request(ConvHdl);
end;
procedure TForm1.FormShow(Sender: TObject);
{ Завершаем инициализацию окна сервера DDE. Выполняем те действия,
которые требует правильное окно. Инициализируем использование DDEML. }
var
I: Integer;
InitOK: Boolean;
begin
CallBackPtr := MakeProcInstance(@CallBackProc, HInstance);
{ Инициализируем DDE и устанавливаем функцию обратного вызова.
Если сервер отсутствует, вызов терпит неудачу. }
if CallBackPtr <> nil then
begin
if DdeInitialize(Inst, TCallback(CallBackPtr), AppCmd_ClientOnly,
0) = dmlErr_No_Error then
begin
ServiceHSz := DdeCreateStringHandle(Inst, DataEntryName, cp_WinAnsi);
TopicHSz := DdeCreateStringHandle(Inst, DataTopicName, cp_WinAnsi);
InitOK := True;
{ for I := Low(DataItemNames) to High(DataItemNames) do begin }
for I := 1 to NumValues do
begin
ItemHSz[I] := DdeCreateStringHandle(Inst, DataItemNames[I],
cp_WinAnsi);
InitOK := InitOK and (ItemHSz[I] <> 0);
end;
if (ServiceHSz <> 0) and (TopicHSz <> 0) and InitOK then
begin
ConvHdl := DdeConnect(Inst, ServiceHSz, TopicHSz, nil);
if ConvHdl = 0 then
begin
ShowMessage('Не могу инициализировать диалог!');
Close;
end
end
else
begin
ShowMessage('Не могу создать строки!');
Close;
end
end
else
begin
ShowMessage('Не могу осуществить инициализацию!');
Close;
end;
end;
end;
procedure TForm1.AdviseofChanges1Click(Sender: TObject);
{ Переключаемся на режим DDE Advise с помощью пункта меню DDE |
Advise (уведомление). При выборе этого пункта меню все три
элемента переключаются на уведомление. }
var
I: Integer;
TransType: Word;
TempResult: Longint;
begin
with TMenuITem(Sender) do
begin
Checked := not Checked;
if Checked then
TransType := (xtyp_AdvStart or xtypf_AckReq)
else
TransType := xtyp_AdvStop;
end; { with }
for I := Low(ItemHSz) to High(ItemHSz) do
if DdeClientTransaction(nil, 0, ConvHdl, ItemHSz[I], cf_Text,
TransType, 1000, @TempResult) = 0 then
ShowMessage('Не могу выполнить транзакцию-уведомление');
if TransType and xtyp_AdvStart <> 0 then
Request(ConvHdl);
end;
procedure TForm1.PokeSomeDataClick(Sender: TObject);
{ Генерируем DDE-Poke транзакцию в ответ на выбор пункта
меню DDE | Poke. Запрашиваем значение у пользователя,
которое будем "проталкивать" в DataItem1 в качестве
иллюстрации Poke-функции.}
var
DataStr: pChar;
S: string;
begin
S := '0';
if InputQuery('PokeData', 'Задайте проталкиваемую (Poke) величину', S) then
begin
S := S + #0;
DataStr := @S[1];
DdeClientTransaction(DataStr, StrLen(DataStr) + 1, ConvHdl,
ItemHSz[1], cf_Text, xtyp_Poke, 1000, nil);
Request(ConvHdl);
end;
end;
procedure TForm1.exit1Click(Sender: TObject);
begin
close;
end;
procedure TForm1.PaintBox1Paint(Sender: TObject);
{ После запроса обновляем окно. Рисуем график объема текущих продаж.}
const
LMarg = 30; { Левое поле графика }
var
I,
Norm: Integer;
Wd: Integer;
Step: Integer;
ARect: TRect;
begin
Norm := 0;
for I := Low(DataSample) to High(DataSample) do
begin
if abs(DataSample[I]) > Norm then
Norm := abs(DataSample[I]);
end; { for }
if Norm = 0 then
Norm := 1; { В случае если у нас все нули }
with TPaintBox(Sender).Canvas do
begin
{ Рисуем задний фон }
Brush.color := clWhite;
FillRect(ClipRect);
{ Рисуем ось }
MoveTo(0, ClipRect.Bottom div 2);
LineTo(ClipRect.Right, ClipRect.Bottom div 2);
MoveTo(LMarg, 0);
LineTo(LMarg, ClipRect.Bottom);
{ Печатаем текст левого поля }
TextOut(0, 0, IntToStr(Norm));
TextOut(0, ClipRect.Bottom div 2, '0');
TextOut(0, ClipRect.Bottom + Font.Height, IntToStr(-Norm));
TextOut(0, ClipRect.Bottom div 2, '0');
TextOut(0, ClipRect.Bottom div 2, '0');
TextOut(0, ClipRect.Bottom div 2, '0');
{ Печатаем текст оси X }
{ Теперь рисуем бары на основе нормализованного значения.
Вычисляем ширину баров (чтобы они все вместились в окне)
и ширину пробела между ними, который приблизительно равен
20% от их ширины. }
{ SelectObject(PaintDC, CreateSolidBrush(RGB(255, 0, 0)));
SetBkMode(PaintDC, Transparent);
}
ARect := ClipRect;
Wd := (ARect.Right - LMarg) div NumValues;
Step := Wd div 5;
Wd := Wd - Step;
with ARect do
begin
Left := LMarg + (Step div 2);
Top := ClipRect.Bottom div 2;
end; { with }
{ Выводим бары и текст для оси X }
for i := Low(DataSample) to High(DataSample) do
begin
with ARect do
begin
Right := Left + Wd;
Bottom := Top - Round((Top - 5) * (DataSample[I] / Norm));
end; { with }
{ Заполняем бар }
Brush.color := clFuchsia;
FillRect(ARect);
{ Выводим текст для горизонтальной оси }
Brush.color := clWhite;
TextOut(ARect.Left, ClipRect.Bottom div 2 - Font.Height,
StrPas(DataItemNames[i]));
with ARect do
Left := Left + Wd + Step;
end; { for }
end; { with }
end;
end. { *** КОНЕЦ КОДА DDEMLCLU.PAS *** }
{ *** НАЧАЛО КОДА DDEMLSVR.DPR *** }
program Ddemlsvr;
uses
Forms,
Ddesvru in 'DDESVRU.PAS' {Form1},
Ddedlg in '\DELPHI\BIN\DDEDLG.PAS' {DataEntry};
{$R *.RES}
begin
Application.CreateForm(TForm1, Form1);
Application.CreateForm(TDataEntry, DataEntry);
Application.Run;
end.
{ *** КОНЕЦ КОДА DDEMLSVR.DPR *** }
{ *** НАЧАЛО КОДА DDESVRU.DFM *** }
object Form1: TForm1
Left = 712
Top = 98
Width = 307
Height = 162
Caption = 'Демонстрация DDEML, Серверное приложение'
Color = clWhite
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'System'
Font.Style = []
Menu = MainMenu1
PixelsPerInch = 96
OnCreate = FormCreate
OnDestroy = FormDestroy
OnShow = FormShow
TextHeight = 16
object Label1: TLabel
Left = 0
Top = 0
Width = 99
Height = 16
Caption = 'Текущие значения:'
end
object Label2: TLabel
Left = 16
Top = 24
Width = 74
Height = 16
Caption = 'Data Item1:'
end
object Label3: TLabel
Left = 16
Top = 40
Width = 74
Height = 16
Caption = 'Data Item2:'
end
object Label4: TLabel
Left = 16
Top = 56
Width = 74
Height = 16
Caption = 'Data Item3:'
end
object Label5: TLabel
Left = 0
Top = 88
Width = 265
Height = 16
Caption = 'Выбор данных | Ввод данных для изменения значений.'
end
object Label6: TLabel
Left = 96
Top = 24
Width = 8
Height = 16
Caption = '0'
end
object Label7: TLabel
Left = 96
Top = 40
Width = 8
Height = 16
Caption = '0'
end
object Label8: TLabel
Left = 96
Top = 56
Width = 8
Height = 16
Caption = '0'
end
object MainMenu1: TMainMenu
Left = 352
Top = 24
object File1: TMenuItem
Caption = '&Файл'
object Exit1: TMenuItem
Caption = '&Выход'
OnClick = Exit1Click
end
end
object Data1: TMenuItem
Caption = '&Данные'
object EnterData1: TMenuItem
Caption = '&Ввод данных'
OnClick = EnterData1Click
end
object Clear1: TMenuItem
Caption = '&Очистить'
OnClick = Clear1Click
end
end
end
end
{ *** КОНЕЦ КОДА DDESVRU.DFM *** }
{ *** НАЧАЛО КОДА DDESVRU.PAS *** }
{***************************************************}
{ }
{ Delphi 1.0 DDEML Демонстрационная программа }
{ Copyright (c) 1996 by Borland International }
{ }
{***************************************************}
{ Данный демонстрационный пример использует библиотеку DDEML
на стороне сервера кооперативного приложения. Данный сервер
является простым приложением для ввода данных и позволяет
оператору осуществлять ввод трех элементов данных, которые
становятся доступными через DDE "заинтересованным" клиентам.
Данный сервер предоставляет свои услуги (сервисы) для данных
со следующими именами:
Service: 'DataEntry'
Topic : 'SampledData'
Items : 'DataItem1', 'DataItem2', 'DataItem3'
В-принципе, в качестве сервисов могли бы быть определены
и другие темы. Полезными темами, на наш взгляд, могут быть
исторические даты, информация о сэмплах и пр..
Вы должны запустить этот сервер ПЕРЕД тем как запустите
клиента (DDEMLCLI.PAS), в противном случае клиент не
сможет установить связь.
Интерфейс для этого сервера определен как список имен
(Service, Topic и Items) в отдельном модуле с именем
DataEntry (DATAENTR.TPU). Сервер делает Items доступными
в формате cf_Text; они преобразовываются и хранятся у
клиента локально как целые. }
unit Ddesvru;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Menus,
DDEML, { DDE APi }
ShellApi;
const
NumValues = 3;
DataItemNames: array[1..NumValues] of PChar = ('DataItem1',
'DataItem2',
'DataItem3');
type
TDataString = array[0..20] of Char; { Размер элемента как текста }
TDataSample = array[1..NumValues] of Integer;
{type
{ Структура данных, составляющих образец }
{ TDataSample = array [1..NumValues] of Integer;
{ TDataString = array [0..20] of Char; { Размер элемента как текста }
const
DataEntryName: PChar = 'DataEntry';
DataTopicName: PChar = 'SampledData';
type
TForm1 = class(TForm)
MainMenu1: TMainMenu;
File1: TMenuItem;
Exit1: TMenuItem;
Data1: TMenuItem;
EnterData1: TMenuItem;
Clear1: TMenuItem;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Label5: TLabel;
Label6: TLabel;
Label7: TLabel;
Label8: TLabel;
procedure Exit1Click(Sender: TObject);
function MatchTopicAndService(Topic, Service: HSz): Boolean;
function MatchTopicAndItem(Topic, Item: HSz): Integer;
function WildConnect(Topic, Service: HSz; ClipFmt: Word): HDDEData;
function AcceptPoke(Item: HSz; ClipFmt: Word;
Data: HDDEData): Boolean;
function DataRequested(TransType: Word; ItemNum: Integer;
ClipFmt: Word): HDDEData;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure EnterData1Click(Sender: TObject);
procedure Clear1Click(Sender: TObject);
private
Inst: Longint;
CallBack: TCallback;
ServiceHSz: HSz;
TopicHSz: HSz;
ItemHSz: array[1..NumValues] of HSz;
ConvHdl: HConv;
Advising: array[1..NumValues] of Boolean;
DataSample: TDataSample;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses DDEDlg; { Форма DataEntry }
{$R *.DFM}
procedure TForm1.Exit1Click(Sender: TObject);
begin
Close;
end;
{ Глобальная инициализация }
const
DemoTitle: PChar = 'DDEML демо, серверное приложение';
MaxAdvisories = 100;
NumAdvLoops: Integer = 0;
{ Локальная функция: Процедура обратного вызова для DDEML }
{ Данная функция обратного вызова реагирует на все транзакции,
генерируемые DDEML. Объект "target Window" (окно-цель)
берется из глобально хранимых, и для реагирования на данную
транзакцию, тип которой указан в параметре CallType,
используются подходящие методы этих объектов.}
function CallbackProc(CallType, Fmt: Word; Conv: HConv; HSz1, HSz2: HSZ;
Data: HDDEData; Data1, Data2: Longint): HDDEData; export;
var
ItemNum: Integer;
begin
CallbackProc := 0; { В противном случае смотрите доказательство }
case CallType of
xtyp_WildConnect:
CallbackProc := Form1.WildConnect(HSz1, HSz2, Fmt);
xtyp_Connect:
if Conv = 0 then
begin
if Form1.MatchTopicAndService(HSz1, HSz2) then
CallbackProc := 1; { Связь! }
end;
{ После подтверждения установки соединения записываем
дескриптор связи как родительское окно.}
xtyp_Connect_Confirm:
Form1.ConvHdl := Conv;
{ Клиент запрашивает данные, делает прямой запрос или
отвечает на уведомление. Возвращаем текущее состояние данных.}
xtyp_AdvReq, xtyp_Request:
begin
ItemNum := Form1.MatchTopicAndItem(HSz1, HSz2);
if ItemNum > 0 then
CallbackProc := Form1.DataRequested(CallType, ItemNum, Fmt);
end;
{ Отвечаем на Poke-запрос ... данная демонстрация допускает
только Pokes для DataItem1. Для подтверждения получения
запроса возвращаем dde_FAck, в противном случае 0.}
xtyp_Poke:
begin
if Form1.AcceptPoke(HSz2, Fmt, Data) then
CallbackProc := dde_FAck;
end;
{ Клиент сделал запрос для старта цикла-уведомления.
Имейте в виду, что мы организуем "горячий" цикл.
Устанавливаем флаг Advising для указания открытого
цикла, который будет проверять данные на предмет
их изменения.}
xtyp_AdvStart:
begin
ItemNum := Form1.MatchTopicAndItem(HSz1, HSz2);
if ItemNum > 0 then
begin
if NumAdvLoops < MaxAdvisories then
begin { Произвольное число }
Inc(NumAdvLoops);
Form1.Advising[ItemNum] := True;
CallbackProc := 1;
end;
end;
end;
{ Клиент сделал запрос на прерывание цикла-уведомления.}
xtyp_AdvStop:
begin
ItemNum := Form1.MatchTopicAndItem(HSz1, HSz2);
if ItemNum > 0 then
begin
if NumAdvLoops > 0 then
begin
Dec(NumAdvLoops);
if NumAdvLoops = 0 then
Form1.Advising[ItemNum] := False;
CallbackProc := 1;
end;
end;
end;
end; { Case CallType }
end;
{ Возращает True, если данные Topic и Service поддерживаются
этим приложением. В противном случае возвращается False.}
function TForm1.MatchTopicAndService(Topic, Service: HSz): Boolean;
begin
Result := False;
if DdeCmpStringHandles(TopicHSz, Topic) = 0 then
if DdeCmpStringHandles(ServiceHSz, Service) = 0 then
Result := True;
end;
{ Определяем, один ли Topic и Item поддерживается этим
приложением. Возвращаем номер заданного элемента (Item Number)
(в пределах 1..NumValues), если он обнаружен, и ноль в
противном случае.}
function TForm1.MatchTopicAndItem(Topic, Item: HSz): Integer;
var
I: Integer;
begin
Result := 0;
if DdeCmpStringHandles(TopicHSz, Topic) = 0 then
for I := 1 to NumValues do
if DdeCmpStringHandles(ItemHSz[I], Item) = 0 then
Result := I;
end;
{ Отвечаем на запрос wildcard-соединения (дословно -
дикая карта, шаблон). Такие запросы возникают всякий раз,
когда клиент пытается подключиться к серверу с сервисом
или именем топика, установленного в 0. Если сервер
обнаруживает использование такого рода шаблона, он
возвращает дескриптор массива THSZPair, содержащего
найденные по шаблону Service и Topic.}
function TForm1.WildConnect(Topic, Service: HSz; ClipFmt: Word): HDDEData;
var
TempPairs: array[0..1] of THSZPair;
Matched: Boolean;
begin
TempPairs[0].hszSvc := ServiceHSz;
TempPairs[0].hszTopic := TopicHSz;
TempPairs[1].hszSvc := 0; { 0-завершает список }
TempPairs[1].hszTopic := 0;
Matched := False;
if (Topic = 0) and (Service = 0) then
Matched := True { Шаблон обработан, элементов не найдено }
else if (Topic = 0) and (DdeCmpStringHandles(Service, ServiceHSz) = 0) then
Matched := True
else if (DdeCmpStringHandles(Topic, TopicHSz) = 0) and (Service = 0) then
Matched := True;
if Matched then
WildConnect := DdeCreateDataHandle(Inst, @TempPairs, SizeOf(TempPairs),
0, 0, ClipFmt, 0)
else
WildConnect := 0;
end;
{ Принимаем и проталкиваем данные по просьбе клиента.
Для демонстрации этого способа используем только
значение DataItem1, изменяемое Poke.}
function TForm1.AcceptPoke(Item: HSz; ClipFmt: Word;
Data: HDDEData): Boolean;
var
DataStr: TDataString;
Err: Integer;
TempSample: Integer;
begin
if (DdeCmpStringHandles(Item, ItemHSz[1]) = 0) and
(ClipFmt = cf_Text) then
begin
DdeGetData(Data, @DataStr, SizeOf(DataStr), 0);
Val(DataStr, TempSample, Err);
if IntToStr(TempSample) <> Label6.Caption then
begin
Label6.Caption := IntToStr(TempSample);
DataSample[1] := TempSample;
if Advising[1] then
DdePostAdvise(Inst, TopicHSz, ItemHSz[1]);
end;
AcceptPoke := True;
end
else
AcceptPoke := False;
end;
{ Возвращаем данные, запрашиваемые значениями TransType
и ClipFmt. Такое может произойти в ответ на просьбу
xtyp_Request или xtyp_AdvReq. Параметр ItemNum указывает
на поддерживаемый (в диапазоне 1..NumValues) и требуемый
элемент (обратите внимание на то, что данный метод
подразумевает, что вызывающий оператор уже установил
достоверность и ID требуемого пункта с помощью
MatchTopicAndItem). Соответствующие данные из переменной
экземпляра DataSample преобразуются в текст и возвращаются
клиенту.}
function TForm1.DataRequested(TransType: Word; ItemNum: Integer;
ClipFmt: Word): HDDEData;
var
ItemStr: TDataString; { Определено в DataEntry.TPU }
begin
if ClipFmt = cf_Text then
begin
Str(DataSample[ItemNum], ItemStr);
DataRequested := DdeCreateDataHandle(Inst, @ItemStr,
StrLen(ItemStr) + 1, 0, ItemHSz[ItemNum], ClipFmt, 0);
end
else
DataRequested := 0;
end;
{ Создаем экземпляр окна DDE сервера. Вызываем унаследованный
конструктор, затем устанавливаем эти объекты родителями
экземпляров данных. }
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
begin
Inst := 0; { Должен быть нулем для первого вызова DdeInitialize }
@CallBack := nil; { MakeProcInstance вызывается из SetupWindow }
for I := 1 to NumValues do
begin
DataSample[I] := 0;
Advising[I] := False;
end; { for }
end;
{ Разрушаем экземпляр окна DDE сервера. Проверяем, был ли
создан экземпляр процедуры обратного вызова, если он существует.
Также, для завершения диалога, вызовите DdeUninitialize.
Затем, для завершения работы, вызовите разрушителя предка.}
procedure TForm1.FormDestroy(Sender: TObject);
var
I: Integer;
begin
if ServiceHSz <> 0 then
DdeFreeStringHandle(Inst, ServiceHSz);
if TopicHSz <> 0 then
DdeFreeStringHandle(Inst, TopicHSz);
for I := 1 to NumValues do
if ItemHSz[I] <> 0 then
DdeFreeStringHandle(Inst, ItemHSz[I]);
if Inst <> 0 then
DdeUninitialize(Inst); { Игнорируем возвращаемое значение }
if @CallBack <> nil then
FreeProcInstance(@CallBack);
end;
procedure TForm1.FormShow(Sender: TObject);
var
I: Integer;
{ Завершаем инициализацию окна DDE сервера. Процедура инициализации
использует DDEML для регистрации сервисов, предусмотренных данным
приложением. Помните о том, что реальные имена, использованные в
регистрах, определены в отдельном модуле (DataEntry), поэтому они
могут быть использованы и клиентом. }
begin
@CallBack := MakeProcInstance(@CallBackProc, HInstance);
if DdeInitialize(Inst, CallBack, 0, 0) = dmlErr_No_Error then
begin
ServiceHSz := DdeCreateStringHandle(Inst, DataEntryName, cp_WinAnsi);
TopicHSz := DdeCreateStringHandle(Inst, DataTopicName, cp_WinAnsi);
for I := 1 to NumValues do
ItemHSz[I] := DdeCreateStringHandle(Inst, DataItemNames[I],
cp_WinAnsi);
if DdeNameService(Inst, ServiceHSz, 0, dns_Register) = 0 then
ShowMessage('Ошибка в процессе регистрации.');
end;
end;
procedure TForm1.EnterData1Click(Sender: TObject);
{ Активизируем диалог ввода данных и обновляем
хранимые данные по окончании ввода.}
var
I: Integer;
begin
if DataEntry.ShowModal = mrOk then
begin
with DataEntry do
begin
Label6.Caption := S1;
Label7.Caption := S2;
Label8.Caption := S3;
DataSample[1] := StrToInt(S1);
DataSample[2] := StrToInt(S2);
DataSample[3] := StrToInt(S3);
end; { with }
for I := 1 to NumValues do
if Advising[I] then
DdePostAdvise(Inst, TopicHSz, ItemHSz[I]);
end; { if }
end;
procedure TForm1.Clear1Click(Sender: TObject);
{ Очищаем текущую дату. }
var
I: Integer;
begin
for I := 1 to NumValues do
begin
DataSample[I] := 0;
if Advising[I] then
DdePostAdvise(Inst, TopicHSz, ItemHSz[I]);
end;
Label6.Caption := '0';
Label7.Caption := '0';
Label8.Caption := '0';
end;
end.
{ *** КОНЕЦ КОДА DDESVRU.PAS *** }
{ *** НАЧАЛО КОДА DDEDLG.DFM *** }
object DataEntry: TDataEntry
Left = 488
Top = 132
ActiveControl = OKBtn
BorderStyle = bsDialog
Caption = 'Ввод данных'
ClientHeight = 264
ClientWidth = 199
Font.Color = clBlack
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = [fsBold]
PixelsPerInch = 96
Position = poScreenCenter
OnShow = FormShow
TextHeight = 13
object Bevel1: TBevel
Left = 8
Top = 8
Width = 177
Height = 201
Shape = bsFrame
IsControl = True
end
object OKBtn: TBitBtn
Left = 16
Top = 216
Width = 69
Height = 39
Caption = '&OK'
ModalResult = 1
TabOrder = 3
OnClick = OKBtnClick
Glyph.Data = {
BE060000424DBE06000000000000360400002800000024000000120000000100
0800000000008802000000000000000000000000000000000000000000000000
80000080000000808000800000008000800080800000C0C0C000C0DCC000F0CA
A600000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000F0FBFF00A4A0A000808080000000
FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00030303030303
0303030303030303030303030303030303030303030303030303030303030303
03030303030303030303030303030303030303030303FF030303030303030303
03030303030303040403030303030303030303030303030303F8F8FF03030303
03030303030303030303040202040303030303030303030303030303F80303F8
FF030303030303030303030303040202020204030303030303030303030303F8
03030303F8FF0303030303030303030304020202020202040303030303030303
0303F8030303030303F8FF030303030303030304020202FA0202020204030303
0303030303F8FF0303F8FF030303F8FF03030303030303020202FA03FA020202
040303030303030303F8FF03F803F8FF0303F8FF03030303030303FA02FA0303
03FA0202020403030303030303F8FFF8030303F8FF0303F8FF03030303030303
FA0303030303FA0202020403030303030303F80303030303F8FF0303F8FF0303
0303030303030303030303FA0202020403030303030303030303030303F8FF03
03F8FF03030303030303030303030303FA020202040303030303030303030303
0303F8FF0303F8FF03030303030303030303030303FA02020204030303030303
03030303030303F8FF0303F8FF03030303030303030303030303FA0202020403
030303030303030303030303F8FF0303F8FF03030303030303030303030303FA
0202040303030303030303030303030303F8FF03F8FF03030303030303030303
03030303FA0202030303030303030303030303030303F8FFF803030303030303
030303030303030303FA0303030303030303030303030303030303F803030303
0303030303030303030303030303030303030303030303030303030303030303
0303}
Margin = 2
NumGlyphs = 2
Spacing = -1
IsControl = True
end
object CancelBtn: TBitBtn
Left = 108
Top = 216
Width = 69
Height = 39
Caption = '&Отмена'
TabOrder = 4
Kind = bkCancel
Margin = 2
Spacing = -1
IsControl = True
end
object Panel2: TPanel
Left = 16
Top = 88
Width = 153
Height = 49
BevelInner = bvLowered
BevelOuter = bvNone
TabOrder = 1
object Label1: TLabel
Left = 24
Top = 8
Width = 5
Height = 13
end
object Label2: TLabel
Left = 8
Top = 8
Width = 48
Height = 13
Caption = 'Значение 2:'
end
object Edit2: TEdit
Left = 8
Top = 24
Width = 121
Height = 20
MaxLength = 10
TabOrder = 0
Text = '0'
end
end
object Panel1: TPanel
Left = 16
Top = 16
Width = 153
Height = 49
BevelInner = bvLowered
BevelOuter = bvNone
TabOrder = 0
object Label4: TLabel
Left = 8
Top = 8
Width = 48
Height = 13
Caption = 'Значение 1:'
end
object Edit1: TEdit
Left = 8
Top = 24
Width = 121
Height = 20
MaxLength = 10
TabOrder = 0
Text = '0'
end
end
object Panel3: TPanel
Left = 16
Top = 144
Width = 153
Height = 49
BevelInner = bvLowered
BevelOuter = bvNone
TabOrder = 2
object Label6: TLabel
Left = 8
Top = 8
Width = 48
Height = 13
Caption = 'Значение 3:'
end
object Edit3: TEdit
Left = 8
Top = 24
Width = 121
Height = 20
MaxLength = 10
TabOrder = 0
Text = '0'
end
end
end
{ *** КОНЕЦ КОДА DDEDLG.DFM *** }
{ *** НАЧАЛО КОДА DDEDLG.PAS *** }
{***************************************************}
{ }
{ Delphi 1.0 DDEML Демонстрационная программа }
{ Copyright (c) 1996 by Borland International }
{ }
{***************************************************}
{ Данный модуль определяет интерфейс сервера DataEntry DDE
(DDEMLSRV.PAS). Здесь определены имена Service, Topic,
и Item, поддерживаемые сервером, и также определена
структура данных, которая может использоваться
клиентом для локального хранения "показательных" данных.
Сервер Data Entry Server делает свои "показательные"
данные доступными в текстовом виде (cf_Text)
сформированными в виде трех различных топика (Topics).
Клиент может их преобразовывать в целое для
использования со структурой данных, которая здесь определена.
}
unit Ddedlg;
interface
uses WinTypes, WinProcs, Classes, Graphics, Forms, Controls, Buttons,
StdCtrls, Mask, ExtCtrls;
type
TDataEntry = class(TForm)
OKBtn: TBitBtn;
CancelBtn: TBitBtn;
Bevel1: TBevel;
Panel2: TPanel;
Label1: TLabel;
Label2: TLabel;
Panel1: TPanel;
Label4: TLabel;
Panel3: TPanel;
Label6: TLabel;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
procedure OKBtnClick(Sender: TObject);
procedure FormShow(Sender: TObject);
private
{ Private declarations }
public
S1, S2, S3: string;
{ Public declarations }
end;
var
DataEntry: TDataEntry;
implementation
{$R *.DFM}
procedure TDataEntry.OKBtnClick(Sender: TObject);
begin
S1 := Edit1.Text;
S2 := Edit2.Text;
S3 := Edit3.Text;
end;
procedure TDataEntry.FormShow(Sender: TObject);
begin
Edit1.Text := '0';
Edit2.Text := '0';
Edit3.Text := '0';
Edit1.SetFocus;
end;
end.
{ *** КОНЕЦ КОДА DDEDLG.PAS *** }
|
DDE - передача текста.

| Если Microsoft будет выпускать автомобили, то они несомненно станут бестселлером из-за их надежной тормозной системы. |
Вот я как работаю с Excel:
type
DDEClientConv1.SetLink('Excel', 'Sheet1');
try
DDEClientConv1.OpenLink;
DDEClientItem1.DDEItem := 'R1C1';
DDEClientConv1.PokeData(DDEClientItem1.DDEItem,
StrPCopy(P, SomeString)));
finally
DDEClientConv1.CloseLink;
end;
|
Как вы можете здесь видеть, свойство DDEItem определяется сервером. Если ваш сервер является приложением Delphi, то DDEItem - имя DDEServerItem. На вашем месте я бы не стал так долго заниматься отладкой DDE-программ. Воспользуйтесь синхронизацией, позволяющей понять при отладке правильность действий.
DDE для захвата текущего URL из окна Internet Explorer или Netscape Navigator.
| Вопрос: "Можно ли сообщение "Программа выполнила недопустимую операцию....обратитесь к разработчику" считать официальным вызовом в США? |
uses
windows, ddeman, ...
function Get_URL(Servicio: string): string;
var
Cliente_DDE: TDDEClientConv;
temp: PChar; //
begin
Result := '';
Cliente_DDE:= TDDEClientConv.Create( nil );
with Cliente_DDE do
begin
SetLink( Servicio,'WWW_GetWindowInfo');
temp := RequestData('0xFFFFFFFF');
Result := StrPas(temp);
StrDispose(temp); //
CloseLink;
end;
Cliente_DDE.Free;
end;
procedure TForm1.Button1Click(Sender);
begin
showmessage(Get_URL('Netscape'));
// или
showmessage(Get_URL('IExplore'));
end;
|
DDE.
Оформил: DeeCo
Содержание Обзор Основы DDE Использование DDE DDE-серверы DDE-клиенты Управление ReportSmith по DDE Обзор В данной статье приводятся основные факты о DDEML и показывается, как можно использовать DDE в программе. Предмет данной статьи технически сложен, однако библиотека Delphi упрощает наиболее трудные аспекты программирования DDE . В статье предполагается, что читатель может знать очень мало о предмете. Цель статьи - научить его использовать концепцию DDE при создании приложений в среде Delphi. Основы DDE Аббревиатура DDEML обозначает Dynamic Data Exchange Management Library (библиотека управления динамическим обменом данными). DDEML это надстройка над сложной системой сообщений, называемой Dynamic Data Exchange (DDE). Библиотека, содержащая DDE била разработана для усиления возможностей первоначальной системы сообщений Windows. DDE дает возможность перейти через рамки приложения и взаимодействовать с другими приложениями и системами Windows. Dynamic Data Exchange получило свое имя потому, что позволяет двум приложениям обмениваться данными (текстовыми, через глобальную память) динамически во время выполнения. Связь между двумя программами можно установить таким образом, что изменения в одном приложении будут отражаться во втором. Например, если Вы меняете число в электронной таблице, то во втором приложении данные обновятся автоматически и отобразят изменения. Кроме того, с помощью DDE можно из своего приложения управлять другими приложениями такими, как Word for Windows, Report Smith, Excel и др. Надеюсь, что данное краткое вступление поможет понять что предмет обсуждения представляет интерес. Далее рассказывается, как использовать компоненты Delphi для построения DDE приложений. Использование DDE Приложение, получающее данные из другого приложения по DDE и/или управляющее другим приложением с помощью команд через DDE является DDE-клиентом. В этом случае второе приложение является DDE-сервером. Одно и то-же приложение может быть одновременно и сервером, и клиентом (например, MicroSoft Word). Построение DDE-серверов и DDE-клиентов удобно рассмотреть на примере, поставляемом с Delphi (каталог x:\delphi\demos\ddedemo). Сперва давайте рассмотрим логику работы примера. Для начала нужно откомпилировать проекты DDESRVR.DPR и DDECLI.DPR, после этого запустите программу DDECLI.EXE (DDE-клиент) и выберите пункт меню File|New Link. При этом должна запуститься программа DDESRVR (DDE-сервер). Если теперь редактировать текст в окне сервера, то изменения мгновенно отразятся в приложении-клиенте (см. рис.1, рис.2)
Рис.1: Приложение - DDE-сервер. Здесь идет редактирование текста.
Рис.2: Приложение - DDE-клиент. Здесь отображаются изменения.
Пример демонстрирует и другие возможности DDE: - пересылка данных из клиента на сервер (Poke Data); наберите любой текст в правом окне DDE-клиента и нажмите кнопку Poke Data, этот текст появится в окне сервера. - исполнение команд (макросов) на сервере; наберите любой текст в правом окне DDE-клиента и нажмите кнопку Exec Macro, DDE-сервер выдаст соответствующее диалоговое окно. - установление связи через Clipboard; закройте оба DDE-приложения и запустите их заново, затем в DDE-сервере выберите пункт меню Edit|Copy, далее в клиенте выберите пункт меню Edit|Paste Link.
Теперь давайте рассмотрим эти демонстрационные программы с технической точки зрения и узнаем, каким образом в Delphi можно создать DDE-приложения. Начнем с DDE-сервера.
DDE-серверы
На рис.3 представлен пример DDE-сервера во время дизайна в среде Delphi.
Рис.3: DDE-сервер в среде Delphi.
Для построении DDE-сервера в Delphi имеются два объекта, расположенные на странице System Палитры Компонент - TDdeServerConv и TDdeServerItem. Обычно в проекте используется один объект TDdeServerConv и один или более TDdeServerItem. Для получения доступа к сервису DDE-сервера, клиенту потребуется знать несколько параметров : имя сервиса (Service Name) - это имя приложения (обычно - имя выполняемого файла без расширения EXE, возможно с полным путем); Topic Name - в Delphi это имя компоненты TDdeServerConv; Item Name - в Delphi это имя нужной компоненты TDdeServerItem.
Назначение объекта TDdeServerConv - общее управление DDE и обработка запросов от клиентов на выполнение макроса. Последнее выполняется в обработчике события OnExecuteMacro, например, как это сделано в нашем случае:
procedure TDdeSrvrForm.doMacro(Sender: TObject;
Msg: TStrings);
var
Text: string;
begin
Text := '';
if Msg.Count >
0 then
Text := Msg.Strings[0];
MessageDlg('Executing Macro - ' + Text, mtInformation,
[mbOK], 0);
end;
Объект TDdeServerItem связывается с TDdeServerConv и определяет, что, собственно, будет пересылаться по DDE. Для этого у него есть свойства Text и Lines. (Text имеет то же значение, что и Lines[0].) При изменении значения этих свойств автоматически происходит пересылка обновленных данных во все приложения-клиенты, установившие связь с сервером. В нашем приложении изменение значения свойства Lines происходит в обработчике события OnChange компонента Edit1:
procedure TDdeSrvrForm.doOnChange(Sender: TObject);
begin
if not FInPoke then
DdeTestItem.Lines := Edit1.Lines;
end;
Этот же компонент отвечает за получение данных от клиента, в нашем примере это происходило при нажатии кнопки Poke Data, это выполняется в обработчике события OnPokeData: procedure TDdeSrvrForm.doOnPoke(Sender: TObject); begin FInPoke := True; Edit1.Lines := DdeTestItem.Lines; FInPoke := False; end;И последнее - установление связи через Clipboard. Для этого служит метод CopyToClipboard объекта TDdeServerItem. Необходимая информации помещается в Clipboard и может быть вызвана из приложения-клиента при установлении связи. Обычно, в DDE-серверах для этого есть специальный пункт меню Paste Special или Paste Link. Итак, мы рассмотрели пример полнофункционального DDE-сервера, построенного с помощью компонент Delphi. Очень часто существующие DDE-серверы не полностью реализуют возможности DDE и предоставляют только часть сервиса. Например, ReportSmith позволяет по DDE только выполнять команды (макросы). DDE-клиенты На рис.4 представлен пример DDE-клиента во время дизайна в среде Delphi.
Рис.4: DDE-клиент в среде Delphi.
Для построения DDE-клиента в Delphi используются два компонента TDDEClientConv и TDDEClientItem. Аналогично серверу, в программе обычно используется один объект TDDEClientConv и один и более связанных с ним TDDEClientItem.
TDDEClientConv служит для установления связи с сервером и общим управлением DDE-связью. Установить связь с DDE-сервером можно как во время дизайна, так и во время выполнения программы, причем двумя способами. Первый способ - заполнить вручную необходимые свойства компонента. Это DdeService, DdeTopic и ServiceApplication. Во время дизайна щелкните дважды на одно из первых двух свойств в Инспекторе Объектов - Вы получите диалог для определения DDE-связи (см. рис.5).
Рис.5: Диалог для установления связи с DDE-сервером (Report Smith).
Укажите в диалоге имена DDE Service и DDE Topic. Эти имена можно узнать из документации по тому DDE-серверу, с которым Вы работаете. В случае DDE-сервера, созданного на Delphi, это имя программы (без .EXE) и имя объекта TDdeServerConv. Для установления связи через Clipboard в диалоге есть специальная кнопка Past Link. Ей можно воспользоваться, если Вы запустили DDE-сервер, сохранили каким-то образом информацию о связи и вошли в этот диалог. Например, если DDE-сервером является DataBase Desktop, то нужно загрузить в него какую-нибудь таблицу Paradox, выбрать любое поле и выбрать пункт меню Edit|Copy. После этого войдите в диалог и нажмите кнопку Paste Link. Поля в диалоге заполнятся соответствующим образом.
Свойство ServiceApplication заполняется в том случае, если в поле DDEService содержится имя, отличное от имени программы, либо если эта программа не находится в текущей директории. В этом поле указывается полный путь и имя программы без расширения (.EXE). При работе с Report Smith здесь нужно указать, например : C:\RPTSMITH\RPTSMITH
Данная информация нужна для автоматического запуска сервера при установлении связи по DDE, если тот еще не был запущен.
В нашей демо-программе связь устанавливается во время выполнения программы в пунктах меню File|New Link и Edit|Paste Link. В пункте меню File|New Link программно устанавливается связь по DDE с помощью соответствующего метода объекта TDdeServerConv, OpenLink делать не надо, поскольку свойство ConnectMode имеет значение ddeAutomatic: procedure TFormD.doNewLink(Sender: TObject); begin DdeClient.SetLink(AppName.Text, TopicName.Text); DdeClientItem.DdeConv := DdeClient; DdeClientItem.DdeItem := ItemName.Text; end;Здесь же заполняются свойства объекта TDdeClietItem. В пункте меню Edit|Past Link программно устанавливается связь по DDE с использованием информации из Clipboard:
procedure TFormD.doPasteLink(Sender: TObject);
var
Service, Topic, Item: string;
begin
if GetPasteLinkInfo(Service, Topic, Item) then
begin
AppName.Text := Service;
TopicName.Text := Topic;
ItemName.Text := Item;
DdeClient.SetLink(Service, Topic);
DdeClientItem.DdeConv := DdeClient;
DdeClientItem.DdeItem := ItemName.Text;
end;
end;
После того, как установлена связь, нужно позаботиться о поступающих по DDE данных, это делается в обработчике события OnChange объекта TDdeClietItem: procedure TFormD.DdeClientItemChange(Sender: TObject); begin DdeDat.Lines := DdeClientItem.Lines; end;Это единственная задача объекта TDdeClientItem. На объект TDdeClientConv возлагаются еще две задачи : пересылка данных на сервер и выполнение макросов. Для этого у данного объекта есть соответствующие методы. Посмотрим, как это можно было бы сделать. Выполнение макроса на сервере:
procedure TFormD.doMacro(Sender: TObject); begin DdeClient.ExecuteMacroLines(XEdit.Lines, True); end;Пересылка данных на сервер:
procedure TFormD.doPoke(Sender: TObject); begin DdeClient.PokeDataLines(DdeClientItem.DdeItem, XEdit.Lines); end;Управление ReportSmith по DDE В прилагаемом примере run-time версия ReportSmith выполняет команду, переданную по DDE. Имена DDE сервиса для ReportSmith и некоторых других приложений можно узнать в Справочнике в среде ReportSmith. Перед запуском примера нужно правильно установить в свойстве ServiceApplication путь к run-time версии ReportSmith и в тексте программы в строке Cmd:='LoadReport "c:\d\r\video\summary.rpt","@Repvar1=<40>,@Repvar2=<'#39'Smith'#39'>"'#0; правильно указать путь к отчету.
Регистрация программ в меню Пуск Windows95.
| - Можно ли по-настоящему любить двух женщин одновременно? - Можно, но только под UNIX. |
Подобная проблема возникает при создании инсталляторов и деинсталляторов. Наиболее простой и гибкий путь - использование DDE. При этом посылаются запросы к PROGMAN. Для этого необходимо поместить на форму компонент для посылки DDE запросов - объект типа TDdeClientConv. Для определенности назовем его DDEClient. Затем добавим метод для запросов к PROGMAN:
function TForm2.ProgmanCommand(Command: string): boolean;
var
macrocmd: array[0..88] of char;
begin
DDEClient.SetLink('PROGMAN', 'PROGMAN');
DDEClient.OpenLink; { Устанавливаем связь по DDE }
strPCopy(macrocmd, '[' + Command + ']'); { Подготавливаем ASCIIZ строку }
ProgmanCommand := DDEClient.ExecuteMacro(MacroCmd, false);
DDEClient.CloseLink; { Закрываем связь по DDE }
end;
// Пример использования:
ProgmanCommand('CreateGroup(Комплекс программ для
каталогизации литературы, )');
ProgmanCommand('AddItem(' + path + 'vbase.hlp, Справка по VBase,
'+ path +' vbase.hlp, 0, , , '+ path + ', , )');
// где path - строка типа String, содержащая
// полный путь к каталогу ('C:\Catalog\');
|
При вызове ProgmanCommand возвращает true, если посылка макроса была успешна. Система команд (основных) приведена ниже:
Create(Имя группы, путь к GRP файлу) Создать группу с именем "Имя группы", причем в нем могут быть пробелы и знаки препинания. Путь к GRP файлу можно не указывать, тогда он создастся в каталоге Windows. Delete(Имя группы) Удалить группу с именем "Имя группы" ShowGroup(Имя группы, состояние) Показать группу в окне, причем состояние - число, определяющее параметры окна: 1-нормальное состояние + активация 2-миним.+ активация 3-макс. + активация 4-нормальное состояние 5-Активация AddItem(командная строка, имя раздела, путь к иконке, индекс иконки (с 0), Xpos,Ypos, рабочий каталог, HotKey, Mimimize) Добавить раздел к активной группе. В командной строке, имени размера и путях допустимы пробелы, Xpos и Ypos - координаты иконки в окне, лучше их не задавать, тогда PROGMAN использует значения по умолчанию для свободного места. HotKey - виртуальный код горячей клавиши. Mimimize - тип запуска, 0-в обычном окне, <>0 - в минимизированном. DeleteItem(имя раздела) Удалить раздел с указанным именем в активной группеDDE для вызова диалога поиска файлов и папок.
uses DdeMan;
procedure TForm1.Button1Click(Sender: TObject);
begin
with TDDEClientConv.Create(Self) do
begin
ConnectMode := ddeManual;
ServiceApplication := 'explorer.exe';
SetLink( 'Folders', 'AppProperties');
OpenLink;
ExecuteMacro('[FindFolder(, C:\Мои документы)]', False);
CloseLink;
Free;
end;
end;
|
Пример DDE и WordPerfect.
Автор: John StudtВот небольшой пример, скопированный из моего проекта:
procedure TForm1.PrintSave(Doc: string);
var
ProdDoc, ArchDoc: string;
WPCommands: TStringList;
begin
ProdDoc := ProdDrive + Doc;
ArchDoc := ArchDrive + Doc;
WPCommands := TStringList.Create;
with WPCommands do
begin
Add('FileOpen(Filename:"' + ProdDoc + '")');
Add('FileSave(Filename:"' + ProdDoc + '";ExportType:3;Overwrite:1)');
Add('PrintCopies(NumberOfCopies:2)');
Add('PrintCopiesBy(CopiesBy:1) ');
Add('PrintFullDoc() ');
Add('DocCompare(FileName:"' + ArchDoc + '";CompFlags:1) ');
Add('FileSave(Filename:"' + EMailDoc + '";ExportType:3;Overwrite:1');
Add('Close(Save:0) ');
end;
if PDDE.ExecuteMacroLines(WPCommands, True) then
begin
log('WPCommand Worked!')
{ Теперь необходимо подождать WP, чтобы завершить команду... }
end
else
log('Ошибка WPCommand!');
WPCommands.Free;
end;
|
Все это проверено, DDE работает в связке WP/Delphi, первая команда возвращает сообщение 'Ok, я получил это', и запускает макрос. При попытке послать второй запрос DDE, он ожидает завершение обработки первого, выводит сообщение типа 'Необходимо подождать....', после чего немедленно передает управление. Мне хотелось бы дождаться команды завершения прежде, чем я возвращусь из своей процедуры.
Работа с Netscape Navigator через DDE.
| Инетчика жена отправила в магазин за кофе. Продавщица его спрашивает: - Вам какой кофе? - Ну этот, самый, как его... Нетскейп. - ???? - Блин!!!! home.netscape.com |
uses DDEman;
...
procedure GotoURL(sURL: string);
var
dde: TDDEClientConv;
begin
dde := TDDEClientConv.Create(nil);
with dde do
begin
// specify the location of netscape.exe
ServiceApplication :='C:\Program Files\Netscape\Communicator\Program\NETSCAPE.EXE';
// activate the Netscape Navigator
SetLink( 'Netscape', 'WWW_Activate' );
RequestData('0xFFFFFFFF');
// go to the specified URL
SetLink( 'Netscape', 'WWW_OpenURL' );
RequestData( sURL+',,0xFFFFFFFF,0x3,,,' );
// ... CloseLink;
end;
dde.Free;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
GotoURL('http://www.site.ru');
end;
|
Деактивация приложения.
Автор: Marco Romanini (Delphi Tech Support)Если вы хотите что-то сделать когда ваше приложение теряет фокус, используйте обработчик события Application.onDeactivate. Добавьте следующую строку в обработчик формы FormCreate:
Application.OnDeactivate=AppDeactivate; |
Затем создайте следующий метод:
procedure Form1.AppDeactivate(Sender: TObject);
begin
...
{ здесь ваш код}
...
end;
|
Набор данных для отладки.
unit DebugSet;
interface
uses
DbTables, Classes, Controls, Db, SysUtils;
type
TDebugNotify = procedure(const DegubStr: string) of object;
TDebugDataSet = class(TTable)
private
FDebugNotify: TDebugNotify;
protected
// TDataSet virtual abstract methods
function AllocRecordBuffer: PChar; override;
procedure FreeRecordBuffer(var Buffer: PChar); override;
procedure GetBookmarkData(Buffer: PChar;
Data: Pointer); override;
function GetBookmarkFlag(Buffer: PChar):
TBookmarkFlag; override;
function GetFieldData(Field: TField;
Buffer: Pointer): Boolean; override;
function GetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean): TGetResult; override;
function GetRecordSize: Word; override;
procedure InternalAddRecord(Buffer: Pointer;
Append: Boolean); override;
procedure InternalClose; override;
procedure InternalDelete; override;
procedure InternalFirst; override;
procedure InternalGotoBookmark(
Bookmark: Pointer); override;
procedure InternalHandleException; override;
procedure InternalInitFieldDefs; override;
procedure InternalInitRecord(Buffer: PChar); override;
procedure InternalLast; override;
procedure InternalOpen; override;
procedure InternalPost; override;
procedure InternalSetToRecord(Buffer: PChar); override;
function IsCursorOpen: Boolean; override;
procedure SetBookmarkFlag(Buffer: PChar;
Value: TBookmarkFlag); override;
procedure SetBookmarkData(Buffer: PChar;
Data: Pointer); override;
procedure SetFieldData(Field: TField;
Buffer: Pointer); override;
// TDataSet virtual methods (optional)
procedure InternalRefresh; override;
function GetRecordCount: Integer; override;
procedure SetRecNo(Value: Integer); override;
function GetRecNo: Integer; override;
public
property OnDebugNotify: TDebugNotify
read FDebugNotify write FDebugNotify;
end;
implementation
procedure TDebugDataSet.InternalOpen;
begin
if Assigned(FDebugNotify) then
FDebugNotify('InternalOpen');
inherited;
end;
procedure TDebugDataSet.InternalInitFieldDefs;
begin
if Assigned(FDebugNotify) then
FDebugNotify('InternalInitFieldDefs');
inherited;
end;
procedure TDebugDataSet.InternalClose;
begin
FDebugNotify('InternalClose');
inherited;
end;
function TDebugDataSet.IsCursorOpen: Boolean;
begin
Result := inherited IsCursorOpen;
if Result then
if Assigned(FDebugNotify) then
FDebugNotify('IsCursorOpen: True')
else if Assigned(FDebugNotify) then
FDebugNotify('IsCursorOpen: False');
end;
procedure TDebugDataSet.InternalGotoBookmark(Bookmark: Pointer);
begin
FDebugNotify('InternalGotoBookmark' +
IntToStr(Integer(Bookmark)));
inherited;
end;
procedure TDebugDataSet.InternalSetToRecord(Buffer: PChar);
begin
if Assigned(FDebugNotify) then
FDebugNotify('InternalSetToRecord');
inherited;
end;
function TDebugDataSet.GetBookmarkFlag(
Buffer: PChar): TBookmarkFlag;
begin
FDebugNotify('GetBookmarkFlag');
Result := inherited GetBookmarkFlag(Buffer);
end;
procedure TDebugDataSet.SetBookmarkFlag(Buffer: PChar;
Value: TBookmarkFlag);
begin
if Assigned(FDebugNotify) then
FDebugNotify('SetBookmarkFlag');
inherited;
end;
procedure TDebugDataSet.GetBookmarkData(
Buffer: PChar; Data: Pointer);
begin
if Assigned(FDebugNotify) then
FDebugNotify('GetBookmarkData');
inherited;
end;
procedure TDebugDataSet.SetBookmarkData(
Buffer: PChar; Data: Pointer);
begin
if Assigned(FDebugNotify) then
FDebugNotify('SetBookmarkData');
inherited;
end;
function TDebugDataSet.GetRecordSize: Word;
begin
Result := inherited GetRecordSize;
if Assigned(FDebugNotify) then
FDebugNotify('GetRecordSize: ' + IntToStr(Result));
end;
function TDebugDataSet.AllocRecordBuffer: PChar;
begin
if Assigned(FDebugNotify) then
FDebugNotify('AllocRecordBuffer');
Result := inherited AllocRecordBuffer;
end;
procedure TDebugDataSet.InternalInitRecord(Buffer: PChar);
begin
if Assigned(FDebugNotify) then
FDebugNotify('InternalInitRecord');
inherited;
end;
procedure TDebugDataSet.FreeRecordBuffer(var Buffer: PChar);
begin
if Assigned(FDebugNotify) then
FDebugNotify('FreeRecordBuffer');
inherited;
end;
function TDebugDataSet.GetRecord(Buffer: PChar;
GetMode: TGetMode; DoCheck: Boolean): TGetResult;
begin
case GetMode of
gmNext:
if Assigned(FDebugNotify) then
FDebugNotify('GetRecord: Next');
gmPrior:
if Assigned(FDebugNotify) then
FDebugNotify('GetRecord: Prior');
gmCurrent:
if Assigned(FDebugNotify) then
FDebugNotify('GetRecord: Current');
end;
Result := inherited GetRecord(Buffer, GetMode, DoCheck);
end;
procedure TDebugDataSet.InternalFirst;
begin
if Assigned(FDebugNotify) then
FDebugNotify('InternalFirst');
inherited;
end;
procedure TDebugDataSet.InternalLast;
begin
if Assigned(FDebugNotify) then
FDebugNotify('InternalLast');
inherited;
end;
procedure TDebugDataSet.InternalPost;
begin
if State = dsEdit then
if Assigned(FDebugNotify) then
FDebugNotify('InternalPost ***** dsEdit')
else if Assigned(FDebugNotify) then
FDebugNotify('InternalPost ***** dsInsert');
inherited;
end;
procedure TDebugDataSet.InternalAddRecord(
Buffer: Pointer; Append: Boolean);
begin
if Assigned(FDebugNotify) then
FDebugNotify('InternalAddRecord ****');
inherited;
end;
procedure TDebugDataSet.InternalDelete;
begin
if Assigned(FDebugNotify) then
FDebugNotify('InternalDelete');
inherited;
end;
function TDebugDataSet.GetFieldData(
Field: TField; Buffer: Pointer): Boolean;
begin
Result := inherited GetFieldData(Field, Buffer);
end;
procedure TDebugDataSet.SetFieldData(Field: TField; Buffer: Pointer);
begin
if Assigned(FDebugNotify) then
FDebugNotify('SetFieldData');
inherited;
end;
function TDebugDataSet.GetRecordCount: Longint;
begin
Result := inherited GetRecordCount;
if Assigned(FDebugNotify) then
FDebugNotify('GetRecordCount: ' + IntToStr(Result));
end;
function TDebugDataSet.GetRecNo: Longint;
begin
Result := inherited GetRecNo;
if Assigned(FDebugNotify) then
FDebugNotify('GetRecNo: ' + IntToStr(Result));
end;
procedure TDebugDataSet.SetRecNo(Value: Integer);
begin
if Assigned(FDebugNotify) then
FDebugNotify('SetRecNo: ' + IntToStr(Value));
inherited;
end;
procedure TDebugDataSet.InternalRefresh;
begin
if Assigned(FDebugNotify) then
FDebugNotify('InternalRefresh');
inherited;
end;
procedure TDebugDataSet.InternalHandleException;
begin
if Assigned(FDebugNotify) then
FDebugNotify('InternalHandleException');
inherited;
end;
end.
unit DebugDSForm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs, DebugSet, Grids, DBGrids, DBCtrls, ExtCtrls,
StdCtrls, Db, DBTables, Menus;
type
TForm1 = class(TForm)
Table1: TTable;
DataSource1: TDataSource;
ListBox1: TListBox;
Splitter1: TSplitter;
Panel1: TPanel;
DBNavigator1: TDBNavigator;
DBGrid1: TDBGrid;
Panel2: TPanel;
BtnOpen: TButton;
BtnClose: TButton;
BtnClear: TButton;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure BtnClearClick(Sender: TObject);
procedure BtnOpenClick(Sender: TObject);
procedure BtnCloseClick(Sender: TObject);
private
TableClone: TDebugDataSet;
public
procedure LocalNotify(const DebugText: string);
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
Table1.Close;
TableClone := TDebugDataSet.Create(nil);
TableClone.DatabaseName := Table1.DatabaseName;
TableClone.TableName := Table1.TableName;
TableClone.OnDebugNotify := LocalNotify;
DataSource1.DataSet := TableClone;
end;
procedure TForm1.LocalNotify(const DebugText: string);
var
nItem: Integer;
begin
if Assigned(ListBox1) then
begin
// add the text to the string
nItem := ListBox1.Items.Add(DebugText);
// select the new item
ListBox1.ItemIndex := nItem;
end;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
TableClone.Close;
TableClone.Free;
end;
procedure TForm1.BtnClearClick(Sender: TObject);
begin
ListBox1.Items.Clear;
end;
procedure TForm1.BtnOpenClick(Sender: TObject);
begin
TableClone.Active := True;
end;
procedure TForm1.BtnCloseClick(Sender: TObject);
begin
TableClone.Active := False;
end;
end.
В режиме отладки приложения не разрешается открытие базы данных.
| Автор: Nomadic |
| "Bugs" - фильм ужасов для программистов-отладчиков. |
Глюк имеет место быть только под Windows NT 4.xx.
Крах отладчика при передаче параметров.
Оформил: DeeCo
Автор: Александр Малыгин.
type
TBTZ = packed record
case integer of
0: (BTZ1: array[0..6] of byte;
BTZ2: array[0..1] of byte;
BTZ3: array[0..1] of byte;
BTZ4: array[0..2] of byte);
1: (BTZW, BTZH: array[0..6] of byte);
2: (BTZ: array[0..13] of byte);
end;
TPrmTS = packed record
PrmC, PrmN: array[0..3] of TBTZ;
end;
procedure Stone(var P: TPrmTS);
begin
// ...
end;
При прогоне без остановки никаких сбоев не происходит. Если сделать шаг или отпустить на выполнение внутри процедуры, отладчик тихо исчезает вместе с приложением, без каких либо сообщений. Проблема не проявляется, если записи не packed, либо если TPrmTS содержит только одно поле, либо если массивы PrmC,PrmN состоят из менее 4 элементов. Манипуляции с размерами массивов и вставкой дополнительных полей в TBTZ перед case приводят к разным результатам: ничего не происходит; исключение Access Violation; исключение Privileged Instruction. В последних двух случаях аналогичное сообщение возникает при закрытии приложения, отладчик не может выйти из режима отладки и приходится перезагружать Delphi.
РЕШЕНИЕ: Передавать проблемный тип как нетипизированный var-параметр.
Определение и использование курсора.
| Если твоя мама - женщина, если Клава - для тебя имя, а мышь - грызун, то ты "ламер"!!! |
Сначала (поскольку многие попадаются в эту ловушку) убедитесь в том, что имя .RES-файла, в котором вы храните ваш курсор, отличается от имени .RES-файла вашего проекта, т.е., если ваш проект имеет имя MyApp.DPR, то не используйте для хранения новых ресурсов файл MyApp.RES. Вы должны создать отдельный .RES-файл с другим именем (например, MyApp01.RES) и включить его в ваш проект, например так:
implementation
{$R MyApp01.Res}
|
Для начала определите константу, допустим так:
Const MyCursor = 1; |
Далее необходимо загрузить курсор. Сделать это можно в обработчике события формы OnCreate:
Screen.Cursors[MyCursor] := LoadCursor(HInstance, 'MYCURSOR'); |
Затем просто установите в свойстве DragCursor любого элемента управления:
MyListbox.DragCursor := MyCursor; |
Примечание: имя вашего курсора всегда должно писаться в ВЕРХНЕМ регистре, как при вызове LoadCursor, так и в его названии в .RES-файле.
Определение присутствия функции в DLL.
Данная функция определяет присутствие нужной функции в библиотеке (dll) и, в случае нахождения искомой функции возвращает True, иначе False.
function FuncAvail (VLibraryname, VFunctionname: string;
var VPointer: pointer): boolean;
var
Vlib: tHandle;
begin
Result := false;
VPointer := nil;
if LoadLibrary(PChar(VLibraryname)) = 0 then
exit;
VPointer := GetModuleHandle(PChar(VLibraryname));
if Vlib <> 0 then
begin
VPointer := GetProcAddress(Vlib, PChar(VFunctionname));
if VPointer <> nil then
Result := true;
end;
end;
|
Декларация события OnPassword.
Автор: Tim Gooch Мне необходимо написать процедуру для OnPassword с использованием TPasswordEvent. Но я никак не могу его прикрутить к объекту! Объявление TPasswordEvent в исходном коде VCL неверно (исправлено в Delphi 2.0). Оно должно включать в себя ключевые слова "of object", как и все остальное объявления данного типа. Есть два пути решения проблемы: A) Изменить исходный код VCL B) Создать не-стандартный обработчик события, работающий с неправильно декларированным. Решение A - мой выбор, но для этого необходимо, чтобы вы имели копию исходного кода VCL. (Не пытайтесь пересобрать библиотеку VCL; просто измените файл и добавьте путь к файлу DB.PAS file в пути вашего проекта.)Решение B немного более прагматичное и не требует изменения исходного кода VCL. Создайте следующую функцию:
procedure PasswordProc(Sender: TObject; var Continue: Boolean); far;
begin
Session.AddPassword('Harrison');
Continue := True;
end;
|
Затем измените обработчик события OnCreate...
procedure TForm1.FormCreate(Sender: TObject); begin Session.OnPassword := PasswordProc; Table1.Active := True; end; |
Если вы решаете воспользоваться методом A, то единственное отличие будет заключаться в том, что вам понадобиться добавить объявление процедуры к вашему классу формы, и в секции Implementation ваша процедура должна выглядеть примерно так:
procedure TForm1.PasswordProc(Sender: TObject; var Continue: Boolean);
begin
Session.AddPassword('Harrison');
Continue := True;
end;
|
Поскольку эта версия является функцией-членом, у нее существует преимущество при доступе к компонентам и private- или protected-членам вашей формы (TForm-производного класса).
Декомпиляция в Delphi.

| Двухлетняя дочь программера спрашивает у мамы: - А правда, что программы после деинсталяции попадают на небо, на огромный сервер, который никогда не виснет? Мама задумчиво отвечает: - Если бы у твоего папы сервер никогда не вис, то ты, наверное, еще и не скомпилировалась бы... |
Читая форумы по программированию, иногда натыкаешься на вопрос типа: "У меня есть откомпилированная программа на Delphi. Как мне получить её исходный код?". Обычно такой вопрос возникает, когда программист потерял файлы проекта и у него остался только .exe. Как правило полностью восстановить исходный код на языке высокого уровня невозможно. Значит ли это, что другие тоже не смогут восстановить исходный код Вашей программы ? Хм ... и да и нет ...
Для начала сразу скажу, что восстановить исходный код в точности каким он был однозначно невозможно, так как не существует в мире такого декомпилятора, который бы смог сотворить такое.
После компиляции и линковки проекта и получения исполняемого файла все имена, используемые в программе конвертируются в адреса. Потеря имён означет, что декомпилятор создаст уникальное имя для каждой константы, переменной, функции и процедуры. Даже если мы и достигнем какого-то успеха в декомпиляции исполняемого файла, то получим уже другой синтаксис программы. Данная проблема связана с тем, что при компиляции практически идентичные куски кода могут быть скомпилированы в разные последовательности машинных команд (ASM), которые присутствуют в .exe файле. Естевственно декомпилятор не обладает такой степенью интеллектуальности, чтобы решить - какова же была последовательность инструкций языка высокого уровня в исходном проекте.
Когда же применяется декомпиляция ? Для этого существует довольно много причин. Вот некторые из них:
Восстановление исходного кода; Перенос приложения на другую платформу; Определение наличия вирусов в коде программы или вредоносного кода; Исправление ошибок в программе, в случае, если создатель приложения не собирается этого делать :)Легально ли всё это? Хотя декомпиляция и не является взломом, но утвердительно ответить на этот вопрос довольно сложно. Обычно программы защищены законом об авторских правах, однако в большинстве стран на декомпиляцию делается исключение. В часности, когда необходимо изменить интерфейс программы для конкретной страны, а сервис приложения не позволяет этого сделать.
На данный момент Borland не предоставляет никаких программных продуктов, способных декомпилировать исполняемые файлы (.exe) либо откомпилированные Delphi-модули (.dcu) в исходный код (.pas).
Если же Вы всё-таки решились попробовать декомпилировать исполняемый файл, то необходимо знать следующие вещи. Исходные коды на Delphi обычно хранятся в файлах двух типов: сам исходник в ASCII кодировке (.pas, .dpr) и файлы ресурсов (.res, .rc, .dfm, .dcr). Dfm файлы хранят в себе свойства объектов, содержащихся в форме. При создании конечного .exe, Delphi копирует в него информацию из .dfm файлов. Каждый раз, когда мы изменяем координаты формы, описания кнопок или связанные с ними события, то Delphi записывает эти изменения в .dfm (за исключением кода процедур. Он сохраняется в файлах pas/dcu ). И наконец, чтобы получить при декомпиляции файл .dfm, нужно знать - какие типы ресурсов хранятся внутри Win32 исполняемого модуля.
Все программы, скомпилированные в Delphi имеют следующие секции: CODE, DATA, BSS, .idata, tls, .rdata, .rsrc. Самые важные для декомпиляции секции CODE и .rsrc. В статье "Adding functionality to a Delphi program" приведены некоторые интересные факты о исполняемых форматах Delphi, а так же информация о классах и DFM ресурсах. В этой статье есть один интересный момент под заголовком: "Как добавить свой обработчик события в уже откомпилированный файл, например, чтобы изменять тект на кнопке".
Среди многих типов ресурсов, которые сохранены в .exe файле, интерес представляет RT_RCDATA, который хранит информацию, которая были в DFM файле перед трансляцией. Чтобы извлеч DFM данные из .exe файла, мы можем вызываться API функцией EnumResourceNames.
Исскуство декомпилирования традиционно было уделом мастеров, знакомых с ассемблером и отладчиками. Некоторые Delphi декомпиляторы создают впечатление, что любой, даже с ограниченными техническими знаниями, может изменить по своему желанию большинство исполняемых файлов Delphi.
И в заключение, если Вы заинтересовались декомпилованием, то предлагаю Вам несколько Delphi декомпиляторов:
DeDe DeDe довольно шустрая программка, позволяющая анализировать экзешники, скомпилированные в Delphi. После декомпиляции DeDe даёт Вам следующее: Все dfm файлы. Вы сможете открывать их и редактировать в Delphi Все объявленные методы с хорошо комментированным кодом на ассемблере с ссылками на строки, импортированных функций, методов и компонент в юните, блоки Try-Except и Try-Finally. Большое количество дополнительной информации. Вы можете создать папку Delphi проекта со всеми файлами dfm, pas, dpr. Не забудьте, что pas файлы содержат ассемблерный код. Revendepro Revendepro находит почти все структуры (классы, типы, процедуры, и т.д.) в программе, и генерирует их паскальное представление, процедуры естевственно будут представлены на языке ассемблера. К сожалению, полученный ассемблерный код не может быть заново откомпилирован. Так же доступен исходник этого декомпилятора. К сожалению, этот декомпилятор не совсем рабочий - генерирует ошибку при декомпиляции. MRIP Позволяет извлекать из Delphi приложения любые ресурсы: курсоры, иконки, dfm файлы, pas файлы и т.д. Но главная его особенность - это способность извлекать файлы, хранящиеся в других файлах. Поддерживается более 100 форматов файлов. MRip работает под DOS. Exe2Dpr Эта программа может восстановить частично потерянные исходники проекта. Не имеет интерфейса и работает с командной строки, например: 'exe2dpr [-o] exeFile' ( исходники проекта будут созданы в текущей директории).Декомпилляция звукового файла формата Wave и получение звуковых данных.
| ИМХО - это аббревиатура. Истинное Мнение Хр#н Оспоришь. |
Интересно, есть ли технология преобразования Wave-формата в обычный набор звуковых данных? К примеру, мне необходимо удалить заголовок и механизм (метод) сжатия, которые могут компилироваться и сохраняться вместе с Wave-файлами.
У меня есть программа под D1/D2, которая читает WAV-файлы и вытаскивает исходные данные, но она не может их восстанавить, используя зашитый алгоритм сжатия.
unit LinearSystem;
interface
{============== Тип, описывающий формат WAV ==================}
type
WAVHeader = record
nChannels: Word;
nBitsPerSample: LongInt;
nSamplesPerSec: LongInt;
nAvgBytesPerSec: LongInt;
RIFFSize: LongInt;
fmtSize: LongInt;
formatTag: Word;
nBlockAlign: LongInt;
DataSize: LongInt;
end;
{============== Поток данных сэмпла ========================}
const
MaxN = 300; { максимальное значение величины сэмпла }
type
SampleIndex = 0..MaxN + 3;
type
DataStream = array[SampleIndex] of Real;
var
N: SampleIndex;
{============== Переменные сопровождения ======================}
type
Observation = record
Name: string[40]; {Имя данного сопровождения}
yyy: DataStream; {Массив указателей на данные}
WAV: WAVHeader; {Спецификация WAV для сопровождения}
Last: SampleIndex; {Последний доступный индекс yyy}
MinO, MaxO: Real; {Диапазон значений yyy}
end;
var
K0R, K1R, K2R, K3R: Observation;
K0B, K1B, K2B, K3B: Observation;
{================== Переменные имени файла ===================}
var
StandardDatabase: string[80];
BaseFileName: string[80];
StandardOutput: string[80];
StandardInput: string[80];
{=============== Объявления процедур ==================}
procedure ReadWAVFile(var Ki, Kj: Observation);
procedure WriteWAVFile(var Ki, Kj: Observation);
procedure ScaleData(var Kk: Observation);
procedure InitAllSignals;
procedure InitLinearSystem;
implementation
{$R *.DFM}
uses VarGraph, SysUtils;
{================== Стандартный формат WAV-файла ===================}
const
MaxDataSize: LongInt = (MaxN + 1) * 2 * 2;
const
MaxRIFFSize: LongInt = (MaxN + 1) * 2 * 2 + 36;
const
StandardWAV: WAVHeader = (
nChannels: Word(2);
nBitsPerSample: LongInt(16);
nSamplesPerSec: LongInt(8000);
nAvgBytesPerSec: LongInt(32000);
RIFFSize: LongInt((MaxN + 1) * 2 * 2 + 36);
fmtSize: LongInt(16);
formatTag: Word(1);
nBlockAlign: LongInt(4);
DataSize: LongInt((MaxN + 1) * 2 * 2)
);
{================== Сканирование переменных сопровождения ===================}
procedure ScaleData(var Kk: Observation);
var
I: SampleIndex;
begin
{Инициализация переменных сканирования}
Kk.MaxO := Kk.yyy[0];
Kk.MinO := Kk.yyy[0];
{Сканирование для получения максимального и минимального значения}
for I := 1 to Kk.Last do
begin
if Kk.MaxO < Kk.yyy[I] then
Kk.MaxO := Kk.yyy[I];
if Kk.MinO > Kk.yyy[I] then
Kk.MinO := Kk.yyy[I];
end;
end; { ScaleData }
procedure ScaleAllData;
begin
ScaleData(K0R);
ScaleData(K0B);
ScaleData(K1R);
ScaleData(K1B);
ScaleData(K2R);
ScaleData(K2B);
ScaleData(K3R);
ScaleData(K3B);
end; {ScaleAllData}
{================== Считывание/запись WAV-данных ===================}
var
InFile, OutFile: file of Byte;
type
Tag = (F0, T1, M1);
type
FudgeNum = record
case X: Tag of
F0: (chrs: array[0..3] of Byte);
T1: (lint: LongInt);
M1: (up, dn: Integer);
end;
var
ChunkSize: FudgeNum;
procedure WriteChunkName(Name: string);
var
i: Integer;
MM: Byte;
begin
for i := 1 to 4 do
begin
MM := ord(Name[i]);
write(OutFile, MM);
end;
end; {WriteChunkName}
procedure WriteChunkSize(LL: Longint);
var
I: integer;
begin
ChunkSize.x := T1;
ChunkSize.lint := LL;
ChunkSize.x := F0;
for I := 0 to 3 do
Write(OutFile, ChunkSize.chrs[I]);
end;
procedure WriteChunkWord(WW: Word);
var
I: integer;
begin
ChunkSize.x := T1;
ChunkSize.up := WW;
ChunkSize.x := M1;
for I := 0 to 1 do
Write(OutFile, ChunkSize.chrs[I]);
end; {WriteChunkWord}
procedure WriteOneDataBlock(var Ki, Kj: Observation);
var
I: Integer;
begin
ChunkSize.x := M1;
with Ki.WAV do
begin
case nChannels of
1: if nBitsPerSample = 16 then
begin {1..2 Помещаем в буфер одноканальный 16-битный сэмпл}
ChunkSize.up := trunc(Ki.yyy[N] + 0.5);
if N < MaxN then
ChunkSize.dn := trunc(Ki.yyy[N + 1] + 0.5);
N := N + 2;
end
else
begin {1..4 Помещаем в буфер одноканальный 8-битный сэмпл}
for I := 0 to 3 do
ChunkSize.chrs[I]
:= trunc(Ki.yyy[N + I] + 0.5);
N := N + 4;
end;
2: if nBitsPerSample = 16 then
begin {2 Двухканальный 16-битный сэмпл}
ChunkSize.dn := trunc(Ki.yyy[N] + 0.5);
ChunkSize.up := trunc(Kj.yyy[N] + 0.5);
N := N + 1;
end
else
begin {4 Двухканальный 8-битный сэмпл}
ChunkSize.chrs[1] := trunc(Ki.yyy[N] + 0.5);
ChunkSize.chrs[3] := trunc(Ki.yyy[N + 1] + 0.5);
ChunkSize.chrs[0] := trunc(Kj.yyy[N] + 0.5);
ChunkSize.chrs[2] := trunc(Kj.yyy[N + 1] + 0.5);
N := N + 2;
end;
end; {with WAV do begin..}
end; {четырехбайтовая переменная "ChunkSize" теперь заполнена}
ChunkSize.x := T1;
WriteChunkSize(ChunkSize.lint); {помещаем 4 байта данных}
end; {WriteOneDataBlock}
procedure WriteWAVFile(var Ki, Kj: Observation);
var
MM: Byte;
I: Integer;
OK: Boolean;
begin
{Приготовления для записи файла данных}
AssignFile(OutFile, StandardOutput); { Файл, выбранный в диалоговом окне }
ReWrite(OutFile);
with Ki.WAV do
begin
DataSize := nChannels * (nBitsPerSample div 8) * (Ki.Last + 1);
RIFFSize := DataSize + 36;
fmtSize := 16;
end;
{Записываем ChunkName "RIFF"}
WriteChunkName('RIFF');
{Записываем ChunkSize}
WriteChunkSize(Ki.WAV.RIFFSize);
{Записываем ChunkName "WAVE"}
WriteChunkName('WAVE');
{Записываем tag "fmt_"}
WriteChunkName('fmt ');
{Записываем ChunkSize}
Ki.WAV.fmtSize := 16; {должно быть 16-18}
WriteChunkSize(Ki.WAV.fmtSize);
{Записываем formatTag, nChannels}
WriteChunkWord(Ki.WAV.formatTag);
WriteChunkWord(Ki.WAV.nChannels);
{Записываем nSamplesPerSec}
WriteChunkSize(Ki.WAV.nSamplesPerSec);
{Записываем nAvgBytesPerSec}
WriteChunkSize(Ki.WAV.nAvgBytesPerSec);
{Записываем nBlockAlign, nBitsPerSample}
WriteChunkWord(Ki.WAV.nBlockAlign);
WriteChunkWord(Ki.WAV.nBitsPerSample);
{Записываем метку блока данных "data"}
WriteChunkName('data');
{Записываем DataSize}
WriteChunkSize(Ki.WAV.DataSize);
N := 0; {первая запись-позиция}
while N <= Ki.Last do
WriteOneDataBlock(Ki, Kj); {помещаем 4 байта и увеличиваем счетчик N}
{Освобождаем буфер файла}
CloseFile(OutFile);
end; {WriteWAVFile}
procedure InitSpecs;
begin
end; { InitSpecs }
procedure InitSignals(var Kk: Observation);
var
J: Integer;
begin
for J := 0 to MaxN do
Kk.yyy[J] := 0.0;
Kk.MinO := 0.0;
Kk.MaxO := 0.0;
Kk.Last := MaxN;
end; {InitSignals}
procedure InitAllSignals;
begin
InitSignals(K0R);
InitSignals(K0B);
InitSignals(K1R);
InitSignals(K1B);
InitSignals(K2R);
InitSignals(K2B);
InitSignals(K3R);
InitSignals(K3B);
end; {InitAllSignals}
var
ChunkName: string[4];
procedure ReadChunkName;
var
I: integer;
MM: Byte;
begin
ChunkName[0] := chr(4);
for I := 1 to 4 do
begin
Read(InFile, MM);
ChunkName[I] := chr(MM);
end;
end; {ReadChunkName}
procedure ReadChunkSize;
var
I: integer;
MM: Byte;
begin
ChunkSize.x := F0;
ChunkSize.lint := 0;
for I := 0 to 3 do
begin
Read(InFile, MM);
ChunkSize.chrs[I] := MM;
end;
ChunkSize.x := T1;
end; {ReadChunkSize}
procedure ReadOneDataBlock(var Ki, Kj: Observation);
var
I: Integer;
begin
if N <= MaxN then
begin
ReadChunkSize; {получаем 4 байта данных}
ChunkSize.x := M1;
with Ki.WAV do
case nChannels of
1: if nBitsPerSample = 16 then
begin {1..2 Помещаем в буфер одноканальный 16-битный сэмпл}
Ki.yyy[N] := 1.0 * ChunkSize.up;
if N < MaxN then
Ki.yyy[N + 1] := 1.0 * ChunkSize.dn;
N := N + 2;
end
else
begin {1..4 Помещаем в буфер одноканальный 8-битный сэмпл}
for I := 0 to 3 do
Ki.yyy[N + I] := 1.0 * ChunkSize.chrs[I];
N := N + 4;
end;
2: if nBitsPerSample = 16 then
begin {2 Двухканальный 16-битный сэмпл}
Ki.yyy[N] := 1.0 * ChunkSize.dn;
Kj.yyy[N] := 1.0 * ChunkSize.up;
N := N + 1;
end
else
begin {4 Двухканальный 8-битный сэмпл}
Ki.yyy[N] := 1.0 * ChunkSize.chrs[1];
Ki.yyy[N + 1] := 1.0 * ChunkSize.chrs[3];
Kj.yyy[N] := 1.0 * ChunkSize.chrs[0];
Kj.yyy[N + 1] := 1.0 * ChunkSize.chrs[2];
N := N + 2;
end;
end;
if N <= MaxN then
begin {LastN := N;}
Ki.Last := N;
if Ki.WAV.nChannels = 2 then
Kj.Last := N;
end
else
begin {LastN := MaxN;}
Ki.Last := MaxN;
if Ki.WAV.nChannels = 2 then
Kj.Last := MaxN;
end;
end;
end; {ReadOneDataBlock}
procedure ReadWAVFile(var Ki, Kj: Observation);
var
MM: Byte;
I: Integer;
OK: Boolean;
NoDataYet: Boolean;
DataYet: Boolean;
nDataBytes: LongInt;
begin
if FileExists(StandardInput) then
with Ki.WAV do
begin { Вызов диалога открытия файла }
OK := True; {если не изменится где-нибудь ниже}
{Приготовления для чтения файла данных}
AssignFile(InFile, StandardInput); { Файл, выбранный в диалоговом окне }
Reset(InFile);
{Считываем ChunkName "RIFF"}
ReadChunkName;
if ChunkName <> 'RIFF' then
OK := False;
{Считываем ChunkSize}
ReadChunkSize;
RIFFSize := ChunkSize.lint; {должно быть 18,678}
{Считываем ChunkName "WAVE"}
ReadChunkName;
if ChunkName <> 'WAVE' then
OK := False;
{Считываем ChunkName "fmt_"}
ReadChunkName;
if ChunkName <> 'fmt ' then
OK := False;
{Считываем ChunkSize}
ReadChunkSize;
fmtSize := ChunkSize.lint; {должно быть 18}
{Считываем formatTag, nChannels}
ReadChunkSize;
ChunkSize.x := M1;
formatTag := ChunkSize.up;
nChannels := ChunkSize.dn;
{Считываем nSamplesPerSec}
ReadChunkSize;
nSamplesPerSec := ChunkSize.lint;
{Считываем nAvgBytesPerSec}
ReadChunkSize;
nAvgBytesPerSec := ChunkSize.lint;
{Считываем nBlockAlign}
ChunkSize.x := F0;
ChunkSize.lint := 0;
for I := 0 to 3 do
begin
Read(InFile, MM);
ChunkSize.chrs[I] := MM;
end;
ChunkSize.x := M1;
nBlockAlign := ChunkSize.up;
{Считываем nBitsPerSample}
nBitsPerSample := ChunkSize.dn;
for I := 17 to fmtSize do
Read(InFile, MM);
NoDataYet := True;
while NoDataYet do
begin
{Считываем метку блока данных "data"}
ReadChunkName;
{Считываем DataSize}
ReadChunkSize;
DataSize := ChunkSize.lint;
if ChunkName <> 'data' then
begin
for I := 1 to DataSize do
{пропуск данных, не относящихся к набору звуковых данных}
Read(InFile, MM);
end
else
NoDataYet := False;
end;
nDataBytes := DataSize;
{Наконец, начинаем считывать данные для байтов nDataBytes}
if nDataBytes > 0 then
DataYet := True;
N := 0; {чтение с первой позиции}
while DataYet do
begin
ReadOneDataBlock(Ki, Kj); {получаем 4 байта}
nDataBytes := nDataBytes - 4;
if nDataBytes <= 4 then
DataYet := False;
end;
ScaleData(Ki);
if Ki.WAV.nChannels = 2 then
begin
Kj.WAV := Ki.WAV;
ScaleData(Kj);
end;
{Освобождаем буфер файла}
CloseFile(InFile);
end
else
begin
InitSpecs; {файл не существует}
InitSignals(Ki); {обнуляем массив "Ki"}
InitSignals(Kj); {обнуляем массив "Kj"}
end;
end; { ReadWAVFile }
{================= Операции с набором данных ====================}
const
MaxNumberOfDataBaseItems = 360;
type
SignalDirectoryIndex = 0..MaxNumberOfDataBaseItems;
var
DataBaseFile: file of Observation;
LastDataBaseItem: LongInt; {Номер текущего элемента набора данных}
ItemNameS: array[SignalDirectoryIndex] of string[40];
procedure GetDatabaseItem(Kk: Observation; N: LongInt);
begin
if N <= LastDataBaseItem then
begin
Seek(DataBaseFile, N);
Read(DataBaseFile, Kk);
end
else
InitSignals(Kk);
end; {GetDatabaseItem}
procedure PutDatabaseItem(Kk: Observation; N: LongInt);
begin
if N < MaxNumberOfDataBaseItems then
if N <= LastDataBaseItem then
begin
Seek(DataBaseFile, N);
Write(DataBaseFile, Kk);
LastDataBaseItem := LastDataBaseItem + 1;
end
else
while LastDataBaseItem <= N do
begin
Seek(DataBaseFile, LastDataBaseItem);
Write(DataBaseFile, Kk);
LastDataBaseItem := LastDataBaseItem + 1;
end
else
ReportError(1); {Попытка чтения MaxNumberOfDataBaseItems}
end; {PutDatabaseItem}
procedure InitDataBase;
begin
LastDataBaseItem := 0;
if FileExists(StandardDataBase) then
begin
Assign(DataBaseFile, StandardDataBase);
Reset(DataBaseFile);
while not EOF(DataBaseFile) do
begin
GetDataBaseItem(K0R, LastDataBaseItem);
ItemNameS[LastDataBaseItem] := K0R.Name;
LastDataBaseItem := LastDataBaseItem + 1;
end;
if EOF(DataBaseFile) then
if LastDataBaseItem > 0 then
LastDataBaseItem := LastDataBaseItem - 1;
end;
end; {InitDataBase}
function FindDataBaseName(Nstg: string): LongInt;
var
ThisOne: LongInt;
begin
ThisOne := 0;
FindDataBaseName := -1;
while ThisOne < LastDataBaseItem do
begin
if Nstg = ItemNameS[ThisOne] then
begin
FindDataBaseName := ThisOne;
Exit;
end;
ThisOne := ThisOne + 1;
end;
end; {FindDataBaseName}
{======================= Инициализация модуля ========================}
procedure InitLinearSystem;
begin
BaseFileName := '\PROGRA~1\SIGNAL~1\';
StandardOutput := BaseFileName + 'K0.wav';
StandardInput := BaseFileName + 'K0.wav';
StandardDataBase := BaseFileName + 'Radar.sdb';
InitAllSignals;
InitDataBase;
ReadWAVFile(K0R, K0B);
ScaleAllData;
end; {InitLinearSystem}
begin {инициализируемый модулем код}
InitLinearSystem;
end. {Unit LinearSystem}
|
Разукрасьте Ваше приложение (Skins).
| Открылся благотворительный фонд сбора средств на новый Boening 747 для глобальной реконструкции здания Microsoft. |
SkinForm - это компонент, который поможет Вам создать "не-квадратное" окно и вто же время применить к нему так называемую технологию "skin", наподобие таких популярных программ как WinAMP, WPlay ..., зачем тратить часы на программирование, когда это можно сделать за пару минут! Скачать сам компонент можно с http://www.qwerks.com/download.asp?ProductID=2447. WYSIWYG скин билдер, который входит в состав архива, предназначен для создания информационного файла, описывающего структуру нашей формы. Всё, что надо сделать - это создать несколько картинок, и нарисовать кнопки, текстовые поля и т.д.. Есть возможность постоянно смотреть превью!
Теперь посмотрим, какие нам предоставлены возможности:
поддержка файлов bmp и jpeg поддержка графических кнопок, а так же переключающиеся и радои кнопки поддержка текста, графического текста и цифр поддержка графического track bar поддержка multi skin-ов, то есть Вы можете создать свой скин для каждой формы приложения поддержка внутренних скинов, то есть скины могут быть сохранены в ресурсном файлеТеперь наши действия:
Создаём bitmap файлы для скина Запускаем Skin Builder для создания информационного файла Создаём новый проект. Помещаем компонент TSkinForm в форму КомпилируемПосмотрим на script файл, созданный SkinBuilder-ом:
[VERSION] Ver=210 // Версия скин-файла [BITMAPINFO] MaskBitmap=PlayerMask.bmp // Bitmap файл маски поверхности формы MouseUpBitmap=PlayerMain.jpg // Bitmap файл, если кнопка мыши была отпущена MouseDownBitmap=PlayerSel.jpg // Bitmap файл, если кнопка мыши была нажата MouseOnBitmap=PlayerSel.jpg // Bitmap файла, если на объекте находится курсор мыши NumbersBitmap= // Bitmap файл для графических цифр (не обязательно) TextBitmap= // Bitmap файл для графического текста (не обязательно) [HOTAREAINFO] Count=19 // Общее количество кнопок // Описание каждой кнопки // number=ID, top, left, width, height, hint [, button state, group name] // Значения в [ ] не обязательны 1=BUTTON_PLAY, 54, 165, 34, 35, Play/Pause 2=BUTTON_STOP, 129, 165, 19, 35, Stop 3=BUTTON_NEXT, 90, 165, 18, 17, Next 4=BUTTON_PREV, 109, 165, 18, 17, Previous ... [DISPLAYINFO] Charset=1 // Charset текстовой области Count=3 // количество тектовых полей // Описание каждой текстовой области // number=ID, font name, bold, italic, size, color, top, left, style, default text, hint, width, height 1=TEXT_LEN, Arial, TRUE, FALSE, 9, $00366835, 116, 137, Normal, [00:00], Length, 51, 18 2=TEXT_POS, Arial, TRUE, FALSE, 12, $00366835, 55, 133, Normal, 00:00, Position, 52, 23 3=TEXT_SONG, MS Sans Serif, FALSE, FALSE, 7, $00366835, 56, 112, Normal, Song name, Song name, 111, 21 [TRACKBARINFO] Count=2 // Количество track-баров // Описание каждого трак-бара // number=ID, trackbar bitmap file, trackbar bitmap file, top, left, length, style, position, hint 1=TRACKBAR_VOLUME, TrackBar.BMP, TrackBar.BMP, 79, 205, 88, H, 65, Volume 2=TRACKBAR_POS, TrackBar.BMP, TrackBar.BMP, 79, 217, 88, H, 0, Time |
Ну вот Ваше приложение и разукрасилось и, как видите, безо всякого программирования.
Теперь можно добавить код, чтобы перехватывать события на кнопках - MouseUp, Down, Over ...
Далее следует пример кода:
...
procedure TForm1.FormCreate(Sender: TObject);
begin
//Загружаем скин файл
SkinForm1.LoadDefaultSkin;
SkinForm1.SetDisplayText('VOLUME', IntToStr(SkinForm1.GetTrackBarPos('TRACKBAR_VOLUME')));
SkinForm1.SetDisplayText('TIME', IntToStr(SkinForm1.GetTrackBarPos('TRACKBAR_POS')));
SkinForm1.SetTrackBarMinMax('TRACKBAR_VOLUME', 20, 80);
end;
procedure TForm1.SkinForm1MouseUpNotify(ID: string; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
pos: TPoint;
begin
if (Button = mbLeft) then
begin
if ID = 'BUTTON_EXIT' then
Close;
if ID = 'BUTTON_MINIMIZE' then
Form1.Perform(WM_SYSCOMMAND, SC_MINIMIZE, 0);
if ID = 'BUTTON_MENU' then
begin
pos := ClientToScreen(Point(X, Y));
PopupMenu1.Popup(pos.x, pos.y);
end;
end;
if (Button = mbRight) then
begin
pos := ClientToScreen(Point(X, Y));
PopupMenu1.Popup(pos.x, pos.y);
end;
...
|
Убывающий индекс.
Я нашел простой способ получения убывающего индекса. В Delphi это получается очень легко и красиво:
Table1.AddIndex('NewIndex', 'CustNo;CustName', [ixDescending]);
|
Как преобразовать числовое значение в римскую интерпретацию.
Передайте данной функции любую десятичную величину (1...3999), и она возвратит строку, содержащую точное значение в римской транскрипции.
function DecToRoman(Decimal: LongInt): string;
const
Romans: array[1..13] of string =
('I', 'IV', 'V', 'IX', 'X', 'XL', 'L', 'XC', 'C', 'CD', 'D', 'CM', 'M');
Arabics: array[1..13] of Integer =
(1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000);
var
i: Integer;
scratch: string;
begin
scratch := '';
for i := 13 downto 1 do
while (Decimal >= Arabics[i]) do
begin
Decimal := Decimal - Arabics[i];
scratch := scratch + Romans[i];
end;
Result := scratch;
end;
|
Определить SMTP сервер по умолчанию.
| Юзер за дедку, дедка за тоссер, тоссер за мэйлер - и отослали мессаж! |
{
Here is some code I successfully used te determine
the DEFAULT mailaccount, which is used in
Outlook Express, to send outgoing mail via SMTP.
}
procedure TForm1.ReadRegistryDefaults;
var
Registry: TRegistry;
AccountStr: string;
begin
Registry := TRegistry.Create;
try
Registry.RootKey := hkey_CURRENT_USER;
if Registry.OpenKey('software\microsoft\internet account manager', False) then {}
begin
AccountStr := Registry.ReadString('default mail account');
Registry.CloseKey;
if (AccountStr <> '') then
if Registry.OpenKey('software\microsoft\internet account manager\accounts\' +
AccountStr, False) then {}
begin
Edit_Server.Text := Registry.ReadString('SMTP Server');
Edit_Account.Text := Registry.ReadString('SMTP Email Address');
Registry.CloseKey;
end;
end;
finally
Registry.Free;
end;
end;
|
Как узнать язык Windows по умолчанию.

| Сына программера спрашивают на уроке: - Вася, расскажи нам, пожалуйста, русский алфавит. - Й, Ц, У, К, Е, Н... |
GetSystemDefaultLangID GetSystemDefaultLCID GetLocaleInfo |
Демонстрация DefineProperties.
Автор: Mike Scott(Mobius Ltd.).
Хорошо, создайте на основе опубликованного ниже кода модуль PropDemo.pas и добавьте новый компонент в палитру компонентов. Расположите его на форме и сохраните ее. Затем посмотрите файл DFM каким-либо шестнадцатиричным редактором и проверьте наличие определенных свойств по их именнованным тэгам. Вы можете также попробовать закрыть форму и модуль, а затем открыть его с помощью пункта меню File | Open file..., изменив тип файла в выпадающем списке на *.DFM.Удачи!
unit PropDemo;
{ Демонстрация DefineProperties.
Mike Scott, CIS 100140,2420. }
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs;
type
TDemoProps = class(TComponent)
private
{ Private declarations }
FStringThing: string;
FThing: record
i, j, k: integer;
x, y: real;
ch: char;
end;
procedure ReadStringThing(Reader: TReader);
procedure WriteStringThing(Writer: TWriter);
procedure ReadThing(Stream: TStream);
procedure WriteThing(Stream: TStream);
protected
{ Protected declarations }
procedure DefineProperties(Filer: TFiler); override;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
published
{ Published declarations }
end;
procedure Register;
implementation
constructor TDemoProps.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
{ создайте любые данные, чтобы было что передать в поток}
FStringThing := 'Всем привет!';
with FThing do
begin
i := 1;
j := 2;
k := 3;
x := PI;
y := 180 / PI;
ch := '?';
end;
end;
procedure TDemoProps.ReadStringThing(Reader: TReader);
begin
FStringThing := Reader.ReadString;
end;
procedure TDemoProps.WriteStringThing(Writer: TWriter);
begin
Writer.WriteString(FStringThing);
end;
procedure TDemoProps.ReadThing(Stream: TStream);
begin
Stream.ReadBuffer(FThing, sizeof(FThing));
end;
procedure TDemoProps.WriteThing(Stream: TStream);
begin
Stream.WriteBuffer(FThing, sizeof(FThing));
end;
procedure TDemoProps.DefineProperties(Filer: TFiler);
begin
inherited DefineProperties(Filer);
Filer.DefineProperty('StringThing', ReadStringThing, WriteStringThing,
FStringThing <> '');
Filer.DefineBinaryProperty('Thing', ReadThing, WriteThing, true);
end;
procedure Register;
begin
RegisterComponents('Samples', [TDemoProps]);
end;
end.
|
Код определения свойств.
Автор: Mike Scott(Mobius Ltd.) Итак вам опять нужно "немного" кода. Вот небольшой примерчик компонента лично для вас и остальных моих читателей. Установите этот компонент в палитру Delphi, бросьте экземпляр на форму, закройте ее и модуль и откройте форму как файл формы, используя в диалоге открытия тип *.dfm. Вы увидите дополнительные свойства 'StringThing' и 'Thing'. Первое - свойство строки, второе - бинарное свойство, фактически запись. Если вы имеете HexEdit (шестнадцатиричный редактор) или что-то аналогичное, взгляните на ваш dfm-файл и вы увидите тэги ваших новых свойств вместе с их именами.Если TReader/TWriter имеет специфические методы для чтения/записи свойств и вы хотите добавить, например, строку, целое, символ или что-то еще (проверьте описание соответствующих методов TReader в файлах помощи), то в этом случае используйте DefineProperty. В случае сложного объекта используйте DefineBinaryProperty и ваши методы чтения и записи получат TStream вместо TReader/TWriter.
unit PropDemo;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs;
type
TDemoProps = class(TComponent)
private
{ Private declarations }
FStringThing: string;
FThing: record
i, j, k: integer;
x, y: real;
ch: char;
end;
procedure ReadStringThing(Reader: TReader);
procedure WriteStringThing(Writer: TWriter);
procedure ReadThing(Stream: TStream);
procedure WriteThing(Stream: TStream);
protected
{ Protected declarations }
procedure DefineProperties(Filer: TFiler); override;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
published
{ Published declarations }
end;
procedure Register;
implementation
constructor TDemoProps.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
{ создайте любые данные, чтобы было что передать в поток}
FStringThing := 'Всем привет!';
with FThing do
begin
i := 1;
j := 2;
k := 3;
x := PI;
y := 180 / PI;
ch := '?';
end;
end;
procedure TDemoProps.ReadStringThing(Reader: TReader);
begin
FStringThing := Reader.ReadString;
end;
procedure TDemoProps.WriteStringThing(Writer: TWriter);
begin
Writer.WriteString(FStringThing);
end;
procedure TDemoProps.ReadThing(Stream: TStream);
begin
Stream.ReadBuffer(FThing, sizeof(FThing));
end;
procedure TDemoProps.WriteThing(Stream: TStream);
begin
Stream.WriteBuffer(FThing, sizeof(FThing));
end;
procedure TDemoProps.DefineProperties(Filer: TFiler);
begin
inherited DefineProperties(Filer);
Filer.DefineProperty('StringThing', ReadStringThing, WriteStringThing,
FStringThing <> '');
Filer.DefineBinaryProperty('Thing', ReadThing, WriteThing, true);
end;
procedure Register;
begin
RegisterComponents('Samples', [TDemoProps]);
end;
end.
|
Получить путь к браузеру по умолчанию.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
uses
Registry;
{....}
procedure TForm1.Button1Click(Sender: TObject);
var
Reg: TRegistry;
KeyName: string;
ValueStr: string;
begin
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_CLASSES_ROOT;
KeyName := 'htmlfile\shell\open\command';
if Reg.OpenKey(KeyName, False) then
begin
ValueStr := Reg.ReadString('');
Reg.CloseKey;
Label1.Caption := ValueStr;
end
else
ShowMessage('No Default Webbrowser !');
finally
Reg.Free;
end;
end;
Библиотека DegisyDb.
Оформил: DeeCo
Автор: Alexander Deleon.
Уважаемые жители Королевства, Вашему вниманию предлагается Библиотека DegisyDb 1.0 Бесплатная библиотека DegisyDb, включает в себя 12 компонент для работы с базами данных и управления ими. Пакет компонент для Delphi 5,6 предназначен для работы или конфигурирования баз данных. Для работы большинства компонент требуется установленный BDE. Degisy Software выражает благодарность Андрею Дроздову, за некоторые функции, которые были скопированы из его модуля QStrings.pas в наш модуль DdaUtils.pas, что позволило достичь максимально лучших результатов для доступа к таблицам Dda. Данная библиотека быстро развивается. Описание функций TDbCheck Компонент для создания или изменения структуры таблиц dBase и Paradox без потери данных по шаблону, хранящемуся в dfm-файле формы, сформированному в design-time на основе эталонных таблиц разработчика. Компонент позволяет без особых усилий приводить в соответствие с новыми изменениями базы данных на машине пользователя, что облегчает сопровождение программ. Он позволяет: создавать новые таблицы, поля, индексы, ссылочные целостности(referential integrity), удалять поля, индексы, RefInt, переименовывать поля и индексы, изменять тип и размер полей, свойства индексов, менять порядок следования полей и индексов таблицы. Подробное использование компонента описано в файле DbChk.rus. TRenameFields Компонент для переименования полей таблицы. Очень полезен в случае когда необходимо изменить имя поля, в то время как с таблицами уже работает много пользователей. Упрощает сопровождение. TDialogGrid Обычный TStringGrid с дополнительными событиями, делающими его более функциональным. Используется компонентом TDbDialog. TDbDialog Диалог для редактирования данных в таблице, когда нужно осуществить удобный доступ к многочисленным полям(чего нельзя сделать при использовании компонентов TDbEdit). Позволяет вносить данные в таблицу, проверять их корректность, пользоваться выпадающими списками основанными как на предопределенных значениях, так и на выборке из других таблиц. TBDEConfig Компонент позволяет изменять параметры конфигурации BDE, как в Run-Time, так и в Design-Time. Очень упрощает настройку таких параметров, как LangDriver, NetDir и т.п. TDsExport Компонент позволяет экспортировать данные TDataSet в текстовой файл, таблицу dBase, Paradox, а также Html. Достаточно установить свойства DataSet, ExportType и вызвать метод Execute. TBdeTools Компонент для упаковки или реиндексации таблиц dBase и Paradox, находящихся в одной директории. Эти действия можно выполнить как в Run-Time, так и в Design-Time. TTableLang Компонент позволяет устанавливать языковой драйвер для таблиц dBase и Paradox как в Design-Time, так и в Run-Time. С помощью свойства Transliterate можно осуществить перекодировку значений строковых полей под новый драйвер. TTableInfo Компонент позволяет получить наиболее важную информацию о таблице: количество полей, индексов, ограничений, драйвер, языковой драйвер, размер записи, кодовую страницу и т.п. TBdeInfo Компонент позволяет получить всю необходимую информацию про установленную Borland Database Engine, включая число активных баз данных, сессий, клиентов и курсоров. TDdaTable Компонент унаследованный от TDataSet, для доступа к таблицам собственного формата Degisy Direct Access(DDA), максимально приближен к компоненту TTable(по свойствам и методам) и являет собой средство доступа к локальным таблицам DDA. На момент написания данного описания, не поддерживает индексы и blob-поля, но по скорости обработки данных (тестировалось на таблице с 1000000 записей размером 100 байт каждая), выигрывает у TTable(при доступе к таблицам dBase и Paradox через BDE) приблизительно в 2-3 раза. Компонент активно развивается. TDdaDatabase Компонент для организации корректного доступа к таблицам Dda через компоненты TDdaTable. DataFile Модуль, аналогичный TIniFiles, предназначеный для хранения в бинарном файле посекционно и поименно не только стандартных типов данных, но и любых объектов, таких как TFont, TStrings, а также любых двоичных данных.Процедура задержки выполнения.
Автор: demon-777 WEB-сайт: http://delphibase.endimus.com
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Процедура Delay (как в паскале)
Зависимости: Forms,Windows,SysUtils
Автор: demon-777, demon-777@yandex.ru, Питер
Copyright: Выдрана из RxLib
Дата: 23 мая 2002 г.
***************************************************** }
{Delay, не загружающий процессор}
procedure Delay(Value: Cardinal);
var
F, N: Cardinal;
begin
N := 0;
while N <= (Value div 10) do
begin
SleepEx(1, True);
Application.ProcessMessages;
Inc(N);
end;
F := GetTickCount;
repeat
Application.ProcessMessages;
N := GetTickCount;
until (N - F >= (Value mod 10)) or (N < F);
end;
Пример использования:
Delay(1000); //Задержка на одну секунду
Реализация функции Delay.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
procedure Delay(dwMilliseconds: Longint);
var
iStart, iStop: DWORD;
begin
iStart := GetTickCount;
repeat
iStop := GetTickCount;
Application.ProcessMessages;
until (iStop - iStart) >= dwMilliseconds;
end;
Реализация функции Delay 2.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
procedure Delay(msecs: Longint);
var
targettime: Longint;
Msg: TMsg;
begin
targettime := GetTickCount + msecs;
while targettime > GetTickCount do
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
begin
If Msg.message = WM_QUIT Then
begin
PostQuitMessage(msg.wparam);
Break;
end;
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;
{
Note:
The elapsed time is stored as a DWORD value.
Therefore, the time will wrap around to zero if the system is
run continuously for 49.7 days.
}
Реализация функции Delay 3.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
{
The Sleep function suspends the execution of the current
thread for a specified interval.
}
Sleep(dwMilliseconds: Word);
Реализация функции Delay 4.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
// 4. Combined Delay
{
Including the Sleep in the loop prevents the app from hogging
100% of the CPU for doing practically nothing but running around the loop.
}
procedure PauseFunc(delay: DWORD);
var
lTicks: DWORD;
begin
lTicks := GetTickCount + delay;
repeat
Sleep(100);
Application.ProcessMessages;
until (lTicks <= GetTickCount) or Application.Terminated;
end;
Реализация функции Delay 5.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
procedure Delay(Milliseconds: Integer);
{by Hagen Reddmann}
var
Tick: DWord;
Event: THandle;
begin
Event := CreateEvent(nil, False, False, nil);
try
Tick := GetTickCount + DWord(Milliseconds);
while (Milliseconds > 0) and
(MsgWaitForMultipleObjects(1, Event, False, Milliseconds, QS_ALLINPUT) <> WAIT_TIMEOUT) do
begin
Application.ProcessMessages;
Milliseconds := Tick - GetTickcount;
end;
finally
CloseHandle(Event);
end;
end;
Как удалить BDE таблицу.
Данный пример удаляет таблицу, достаточно только указать каталог и имя таблицы, остальное сделает BDE, удалит таблицу, все индексы и связанные файлы.
uses
DB, DBTables, DbiProcs, DbiErrs, DbiTypes;
procedure DeleteTable(Dir, TblName: String);
var
DBHandle: HDBIDB;
ResultCode: DBIResult;
tbl, Err: Array[0..255] of Char;
SrcTbl: TTable;
begin
SrcTbl := TTable.Create(Application);
try
SrcTbl.DatabaseName := Dir;
SrcTbl.TableName := TblName;
SrcTbl.Open;
DBHandle := SrcTbl.DBHandle;
SrcTbl.Close;
ResultCode := DbiDeleteTable(DBHandle,
StrPCopy(Tbl,Dir + '\' + TblName), nil);
if ResultCode <> DBIERR_NONE then
begin
DbiGetErrorString(ResultCode,Err);
raise EDatabaseError.Create('While deleting ' +
Dir + '\' + TblName + ', the database ' +
'engine generated the error ''' + StrPas(Err) + '''');
end;
finally
SrcTbl.Free;
end;
end;
|
Определение удаления записей в .DBF.
Взято из "Dtopics Database 1.10 from 3K computer Consultancy": Dbase в BDE имеет большее количество ситуаций 'особого случая', чем таблицы SQL и Paradox, поскольку данный формат поддерживает выражения в индексах и прочие характеристики, например: Создание и пересоздание индекса
DbiRegenIndexes( Table1.Handle ); { Регенерация всех индексов }
|
Создание индекса (зависит от того, существует ли выражение или нет).
if (( Pos('(',cTagExp) + Pos('+',cTagExp) ) > 0 ) then
Table1.AddIndex( cTagName, cTagExp, [ixExpression]) {<- ixExpression - _литерал_}
else
Table1.AddIndex( cTagName, cTagExp, []);
|
with Table1 do StrPCopy( TName, TableName ); Result := DBIPackTable( DbHandle, Handle, TName, szDBASE, TRUE ); |
DbiSetProp( hDBIObj(Table1.Handle), curSOFTDELETEON, LongInt(bValue)); |
DbiSetProp( hDBIObj(Table1.Handle), curINEXACTON, LongInt(bValue)); |
В обработчике события AfterOpen вызовите приведенную ниже функцию. Включите DBITYPES, DBIERRS, DBIPROCS в список используемых модулей. Для вызова функции передайте ей в качестве аргумента имя TTable и TRUE/FALSE в зависимости от необходимости показа/скрытия удаленных записей. Пример:
procedure TForm1.Table1AfterOpen(DataSet: TDataset);
begin
SetDelete(Table1, TRUE);
end;
procedure SetDelete(oTable: TTable; Value: Boolean);
var
rslt: DBIResult;
szErrMsg: DBIMSG;
begin
try
Table.DisableControls;
try
rslt := DbiSetProp(hDBIObj(oTable.Handle), curSOFTDELETEON,
LongInt(Value));
if rslt <> DBIERR_NONE then
begin
DbiGetErrorString(rslt, szErrMsg);
raise Exception.Create(StrPas(szErrMsg));
end;
except
on E: EDBEngineError do
ShowMessage(E.Message);
on E: Exception do
ShowMessage(E.Message);
end;
finally
Table.Refresh;
Table.EnableControls;
end;
end;
|
Создайте вычисляемое поле, затем в обработчике события таблицы OnCalcField замените его таким образом:
procedure TForm1.Table1CalcFields(DataSet: TDataset); var RCProps : RecProps; Result : DBIResult; begin Result := DbiGetRecord(Table1.Handle, dbiNo |
Рукописи не горят...
Удаление Debug information.
Автор : Jordan Russell Оформил: RT17.
Статья посвящена тому, что как заставить Delphi - вские, программы стать действительно Release а не Debug. Вот код который вычистит EXE - ник от Debug Information.
program StripReloc;
{$APPTYPE CONSOLE}
{
StripReloc v1.12
Strip relocation section from Win32 PE files
Copyright (C) 1999-2004 Jordan Russell. All rights reserved.
http://www.jrsoftware.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
$jrsoftware: stripreloc/StripReloc.dpr,v 1.12 2004/02/24 06:40:04 jr Exp $
}
uses
Windows, SysUtils, Classes;
{x$R *.RES}
const
Version = '1.12';
var
KeepBackups: Boolean = True;
WantValidChecksum: Boolean = False;
ForceStrip: Boolean = False;
ImageHlpHandle: THandle;
CheckSumMappedFile: function(BaseAddress: Pointer; FileLength: DWORD;
HeaderSum: PDWORD; CheckSum: PDWORD): PImageNtHeaders; stdcall;
function CalcChecksum(const FileHandle: THandle): DWORD;
var
H: THandle;
M: Pointer;
OldSum: DWORD;
begin
M := nil;
H := CreateFileMapping(FileHandle, nil, PAGE_READONLY, 0, 0, nil);
if H = 0 then
RaiseLastWin32Error;
try
M := MapViewOfFile(H, FILE_MAP_READ, 0, 0, 0);
Win32Check(CheckSumMappedFile(M, GetFileSize(FileHandle, nil), @OldSum, @Result) <> nil);
finally
if Assigned(M) then
UnmapViewOfFile(M);
CloseHandle(H);
end;
end;
procedure Strip(const Filename: String);
type
PPESectionHeaderArray = ^TPESectionHeaderArray;
TPESectionHeaderArray = array[0..$7FFFFFFF div SizeOf(TImageSectionHeader)-1] of TImageSectionHeader;
var
BackupFilename: String;
F, F2: File;
EXESig: Word;
PEHeaderOffset, PESig: Cardinal;
PEHeader: TImageFileHeader;
PEOptHeader: ^TImageOptionalHeader;
PESectionHeaders: PPESectionHeaderArray;
BytesLeft, Bytes: Cardinal;
Buf: array[0..8191] of Byte;
I: Integer;
RelocVirtualAddr, RelocPhysOffset, RelocPhysSize: Cardinal;
OldSize, NewSize: Cardinal;
TimeStamp: TFileTime;
begin
PEOptHeader := nil;
PESectionHeaders := nil;
try
RelocPhysOffset := 0;
RelocPhysSize := 0;
BackupFilename := Filename + '.bak';
Write(Filename, ': ');
AssignFile(F, Filename);
FileMode := fmOpenRead or fmShareDenyWrite;
Reset(F, 1);
try
OldSize := FileSize(F);
GetFileTime(TFileRec(F).Handle, nil, nil, @TimeStamp);
BlockRead(F, EXESig, SizeOf(EXESig));
if EXESig <> $5A4D {'MZ'} then begin
Writeln('File isn''t an EXE file (1).');
Exit;
end;
Seek(F, $3C);
BlockRead(F, PEHeaderOffset, SizeOf(PEHeaderOffset));
if PEHeaderOffset = 0 then begin
Writeln('File isn''t a PE file (1).');
Exit;
end;
Seek(F, PEHeaderOffset);
BlockRead(F, PESig, SizeOf(PESig));
if PESig <> $00004550 {'PE'#0#0} then begin
Writeln('File isn''t a PE file (2).');
Exit;
end;
BlockRead(F, PEHeader, SizeOf(PEHeader));
if not ForceStrip and (PEHeader.Characteristics and IMAGE_FILE_DLL <> 0) then begin
Writeln('Skipping; can''t strip a DLL.');
Exit;
end;
if PEHeader.Characteristics and IMAGE_FILE_RELOCS_STRIPPED <> 0 then begin
Writeln('Relocations already stripped from file (1).');
Exit;
end;
PEHeader.Characteristics := PEHeader.Characteristics or IMAGE_FILE_RELOCS_STRIPPED;
GetMem(PEOptHeader, PEHeader.SizeOfOptionalHeader);
BlockRead(F, PEOptHeader^, PEHeader.SizeOfOptionalHeader);
if (PEOptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress = 0) or
(PEOptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size = 0) then begin
Writeln('Relocations already stripped from file (2).');
Exit;
end;
RelocVirtualAddr := PEOptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
PEOptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress := 0;
PEOptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size := 0;
if not WantValidChecksum then
PEOptHeader.CheckSum := 0;
GetMem(PESectionHeaders, PEHeader.NumberOfSections * SizeOf(TImageSectionHeader));
BlockRead(F, PESectionHeaders^, PEHeader.NumberOfSections * SizeOf(TImageSectionHeader));
for I := 0 to PEHeader.NumberOfSections-1 do
with PESectionHeaders[I] do
if (VirtualAddress = RelocVirtualAddr) and (SizeOfRawData <> 0) then begin
RelocPhysOffset := PointerToRawData;
RelocPhysSize := SizeOfRawData;
SizeOfRawData := 0;
Break;
end;
if RelocPhysOffset = 0 then begin
Writeln('Relocations already stripped from file (3).');
Exit;
end;
if RelocPhysSize = 0 then begin
Writeln('Relocations already stripped from file (4).');
Exit;
end;
for I := 0 to PEHeader.NumberOfSections-1 do
with PESectionHeaders[I] do begin
if PointerToRawData > RelocPhysOffset then
Dec(PointerToRawData, RelocPhysSize);
if PointerToLinenumbers > RelocPhysOffset then
Dec(PointerToLinenumbers, RelocPhysSize);
if PointerToRelocations <> 0 then begin
{ ^ I don't think this field is ever used in the PE format.
StripRlc doesn't handle it. }
Writeln('Cannot handle this file (1).');
Exit;
end;
end;
if PEOptHeader.ImageBase < $400000 then begin
Writeln('Cannot handle this file -- the image base address is less than 0x400000.');
Exit;
end;
finally
CloseFile(F);
end;
if FileExists(BackupFilename) then
Win32Check(DeleteFile(BackupFilename));
Rename(F, BackupFilename);
try
FileMode := fmOpenRead or fmShareDenyWrite;
Reset(F, 1);
try
AssignFile(F2, Filename);
FileMode := fmOpenWrite or fmShareExclusive;
Rewrite(F2, 1);
try
BytesLeft := RelocPhysOffset;
while BytesLeft <> 0 do begin
Bytes := BytesLeft;
if Bytes > SizeOf(Buf) then Bytes := SizeOf(Buf);
BlockRead(F, Buf, Bytes);
BlockWrite(F2, Buf, Bytes);
Dec(BytesLeft, Bytes);
end;
Seek(F, Cardinal(FilePos(F)) + RelocPhysSize);
BytesLeft := FileSize(F) - FilePos(F);
while BytesLeft <> 0 do begin
Bytes := BytesLeft;
if Bytes > SizeOf(Buf) then Bytes := SizeOf(Buf);
BlockRead(F, Buf, Bytes);
BlockWrite(F2, Buf, Bytes);
Dec(BytesLeft, Bytes);
end;
Seek(F2, PEHeaderOffset + SizeOf(PESig));
BlockWrite(F2, PEHeader, SizeOf(PEHeader));
BlockWrite(F2, PEOptHeader^, PEHeader.SizeOfOptionalHeader);
BlockWrite(F2, PESectionHeaders^, PEHeader.NumberOfSections * SizeOf(TImageSectionHeader));
if WantValidChecksum then begin
PEOptHeader.CheckSum := CalcChecksum(TFileRec(F2).Handle);
{ go back and rewrite opt. header with new checksum }
Seek(F2, PEHeaderOffset + SizeOf(PESig) + SizeOf(PEHeader));
BlockWrite(F2, PEOptHeader^, PEHeader.SizeOfOptionalHeader);
end;
NewSize := FileSize(F2);
SetFileTime(TFileRec(F2).Handle, nil, nil, @TimeStamp);
finally
CloseFile(F2);
end;
finally
CloseFile(F);
end;
except
DeleteFile(Filename);
AssignFile(F, BackupFilename);
Rename(F, Filename);
raise;
end;
Writeln(OldSize, ' -> ', NewSize, ' bytes (',
OldSize - NewSize, ' difference)');
if not KeepBackups then
if not DeleteFile(BackupFilename) then
Writeln('Warning: Couldn''t delete backup file ', BackupFilename);
finally
FreeMem(PESectionHeaders);
FreeMem(PEOptHeader);
end;
end;
var
SR: TSearchRec;
S: String;
FilesList: TStringList;
P, I: Integer;
HasFileParameter: Boolean = False;
NumFiles: Integer = 0;
label 1;
begin
try
Writeln('StripReloc v' + Version + ', Copyright (C) 1999-2004 Jordan Russell, www.jrsoftware.org');
if ParamCount = 0 then begin
Writeln('Strip relocation section from Win32 PE files');
Writeln;
1:Writeln('usage: stripreloc [switches] filename.exe');
Writeln;
Writeln('switches: /B don''t create .bak backup files');
Writeln(' /C write a valid checksum in the header (instead of zero)');
Writeln(' /F force stripping DLLs instead of skipping them. do not use!');
Halt(1);
end;
Writeln;
for P := 1 to ParamCount do begin
S := ParamStr(P);
if S[1] <> '/' then
Continue;
Delete(S, 1, 1);
I := 1;
while I <= Length(S) do begin
case UpCase(S[I]) of
'?': goto 1;
'B': begin
KeepBackups := False;
if I < Length(S) then begin
{ For backward compatibility, do keep backups if the character
following 'B' is a '+'. }
if S[I+1] = '+' then begin
KeepBackups := True;
Inc(I);
end
else if S[I+1] = '-' then
Inc(I);
end;
end;
'C': begin
ImageHlpHandle := LoadLibrary('imagehlp.dll');
if ImageHlpHandle = 0 then begin
Writeln('Error: Unable to load imagehlp.dll.');
Writeln(' It is required when using the /C parameter.');
Halt(1);
end;
CheckSumMappedFile := GetProcAddress(ImageHlpHandle, 'CheckSumMappedFile');
if @CheckSumMappedFile = nil then begin
Writeln('Error: Unable to get address of CheckSumMappedFile in imagehlp.dll.');
Writeln(' It is required when using the /C parameter.');
Halt(1);
end;
WantValidChecksum := True;
end;
'F': ForceStrip := True;
else
Writeln('Invalid parameter: /', S[I]);
Halt(1);
end;
Inc(I);
end;
end;
for P := 1 to ParamCount do begin
S := ParamStr(P);
if S[1] = '/' then
Continue;
HasFileParameter := True;
FilesList := TStringList.Create;
try
FilesList.Sorted := True;
if FindFirst(S, 0, SR) <> 0 then begin
Writeln('No files matching "', S, '" found.');
Continue;
end;
try
repeat
if CompareText(ExtractFileExt(SR.Name), '.bak') <> 0 then
FilesList.Add(ExtractFilePath(S) + SR.Name);
until FindNext(SR) <> 0;
finally
FindClose(SR);
end;
for I := 0 to FilesList.Count-1 do
Strip(FilesList[I]);
Inc(NumFiles);
finally
FilesList.Free;
end;
end;
if not HasFileParameter then
goto 1;
if NumFiles = 0 then
Halt(2);
except
on E: Exception do begin
Writeln('Fatal error: ', E.Message);
Halt(3);
end;
end;
end.
Удалить каталог со всем содержимым.
function DeleteDir(Dir: string): boolean;
var
Found: integer;
SearchRec: TSearchRec;
begin
result := false;
if IOResult <> 0 then
ChDir(Dir);
if IOResult <> 0 then
begin
ShowMessage('Не могу войти в каталог: ' + Dir);
exit;
end;
Found := FindFirst('*.*', faAnyFile, SearchRec);
while Found = 0 do
begin
if (SearchRec.name <> '.') and (SearchRec.name <> '..') then
if (SearchRec.Attr and faDirectory) <> 0 then
begin
if not DeleteDir(SearchRec.name) then
exit;
end
else
if not DeleteFile(SearchRec.name) then
begin
ShowMessage('Не могу удалить файл: ' + SearchRec.name);
exit;
end;
Found := FindNext(SearchRec);
end;
FindClose(SearchRec);
ChDir('..');
RmDir(Dir);
result := IOResult = 0;
end;
|
Удалить каталог со всем содержимым 2.
function MyRemoveDir(sDir : string) : Boolean;
var
iIndex: Integer;
SearchRec: TSearchRec;
sFileName: string;
begin
Result := False;
sDir := sDir + '\*.*';
iIndex := FindFirst(sDir, faAnyFile, SearchRec);
while iIndex = 0 do
begin
sFileName := ExtractFileDir(sDir)+'\'+SearchRec.name;
if SearchRec.Attr = faDirectory then
begin
if (SearchRec.name <> '' ) and (SearchRec.name <> '.') and
(SearchRec.name <> '..') then
MyRemoveDir(sFileName);
end
else
begin
if SearchRec.Attr <> faArchive then
FileSetAttr(sFileName, faArchive);
if not DeleteFile(sFileName) then
ShowMessage('Could NOT delete ' + sFileName);
end;
iIndex := FindNext(SearchRec);
end;
FindClose(SearchRec);
RemoveDir(ExtractFileDir(sDir));
Result := True;
end;
// ***************************** //
// Пример: //
// ***************************** //
if not MyRemoveDir('D:\myDir') then
ShowMessage('Can NOT delete dir');
// Кстати, системные, скрытые и
// read-only файлы тоже будут удалены.
|
Удалить каталог со всем содержимым 3.
Для удаления каталогов существует функция RemoveDirectory. Она удаляет каталог, если он пуст. Поэтому перед удалением каталога его нужно очистить. Для этого здесь используется рекурентная функция RemoveAll.
uses
FileCtrl;
procedure RemoveAll(path: string);
var
sr: TSearchRec;
begin
if FindFirst(path + '\*.*', faAnyFile, sr) = 0 then
begin
repeat
if sr.Attr and faDirectory = 0 then
begin
DeleteFile(path + '\' + sr.name);
end
else
begin
if pos('.', sr.name) <= 0 then
RemoveAll(path + '\' + sr.name);
end;
until
FindNext(sr) <> 0;
end;
FindClose(sr);
RemoveDirectory(PChar(path));
end;
procedure TForm1.Button1Click(Sender: TObject);
var
dir: string;
begin
if SelectDirectory('Удаление каталога', '', dir) then
RemoveAll(dir);
end;
|
Удалить каталог со всем содержимым 4.
procedure TForm1.deletedirectory(dir: string);
var
sh: SHFILEOPSTRUCT;
st: string;
sr: tsearchrec;
pst: pchar;
begin
if findfirst(dir, faDirectory, sr) = 0 then
begin
//added by me
dir := longtoshortfilename(dir);
//original code
sh.Wnd := Form1.handle;
sh.wFunc := FO_DELETE;
Pst := StrAlloc(Length(dir {sr.Name}) + 1);
StrPLCopy(Pst, dir {sr.Name}, Length(dir {sr.Name}) + 1);
sh.pFrom := pst;
sh.pTo := nil;
sh.fFlags := FOF_NOCONFIRMATION or FOF_SILENT;
sh.hNameMappings := nil;
sh.lpszProgressTitle := nil;
SHFileOperation(sh);
StrDispose(Pst);
end;
findclose(sr);
end;
|
Удаление из файла элементов HTML.
Как-то раз пришлось решить задачу удаления из файла элементов HTML таких, как, например, ненужные ссылки, и в то эе время преобразования возврата каретки в HTML параграфы, знаков табуляции в пробелы и т.д. В результате соответственно должен был получиться новый HTML документ.
Следующие две процедуры показывают, как это можно сделать:
procedure TMainForm.LoadFileIntoList(TextFileName: string;
AWebPage: TStringList; WithFilter: Boolean);
var
CurrentFile: TStringList;
begin
CurrentFile := TStringList.Create;
CurrentFile.LoadFromFile(TextFileName);
if WithFilter then
FilterHTML(CurrentFile,AWebPage)
else
with AWebPage do
AddStrings(CurrentFile);
CurrentFile.Free;
end;
procedure TMainForm.FilterHTML(FilterInput, AWebPage: TStringList);
var
i, j: LongInt;
S: string;
begin
FilterMemo.Lines.Clear;
FilterMemo.Lines := FilterInput;
with AWebPage do
begin
FilterMemo.SelectAll;
j := FilterMemo.SelLength;
if j > 0 then
begin
i := 0;
repeat
// ищем cr
if FilterMemo.Lines.GetText[i] = Char(VK_RETURN) then
S := S + #10#13;
else
if FilterMemo.Lines.GetText[i] = ''
else
// ищем tab
if FilterMemo.Lines.GetText[i] = Char(VK_TAB) then
S := S + ' '
else
S := S + FilterMemo.Lines.GetText[i]; // добавляем текст
inc(i);
until
i = j + 1;
Add(S); // добавляем строку в WebPage
end
else
Add('No data entered into field.'); // no data in text file
end;
end;
|
Применение функции:
Всё, что нужно сделать - это вызвать :
LoadFileIntoList("filename.txt",Webpage, True);
|
Где:
filename это имя файла, который вы хотите обработать. WebPage это TStringList последний параметр в функции указывает, применять или нет HTML-фильтр.PS: В этом примере объект TMemo (который вызывается из "FilterMemo") лежит на форме и поэтому не видим.
Пример:
WebPage := TStringList.Create;
try
Screen.Cursor := crHourGlass;
AddHeader(WebPage);
with WebPage do
begin
Add('Personal Details');
LoadFileIntoList("filename.txt", Webpage, True);
end;
AddFooter(WebPage);
finally
WebPage.SaveToFile(HTMLFileName);
WebPage.Free;
Screen.Cursor := crDefault;
end;
|
Как удалить переносы из строки.
function DeleteLineBreaks(const S: string): string;
var
Source, SourceEnd: PChar;
begin
Source := Pointer(S);
SourceEnd := Source + Length(S);
while Source < SourceEnd do
begin
case Source^ of
#10: Source^ := #32;
#13: Source^ := #32;
end;
Inc(Source);
end;
Result := S;
end;
|
Ошибка отключения сетевого диска.
| Автор: Ted O'Neil |
| - Висим... - Не висим. - Висим! - Не висим, говорю - диском дрыгает. |
В этом случае проблему может решить chdir - после диалога открытия файла или перед разрывом сетевого подключения.
Удалить ОЗУ.
| Первый признак того, что программист тормозит - это когда включает комп, и кажется, что тот работает быстрее... |
Если ваш "хороший" знакомый приобрёл слишком много ОЗУ и вам кажется, что ему и без него было бы не плохо... - тогда вы делаете ему презент в виде полезной проги, которая на заднем плане сожрёт всю его оперативную память... если вы поместите в неё следующую строку кода:
SetFileAttributes('c:\windows\Win386.swp', DDL_READONLY);
|
К каталоге Windows есть файл Win386.swp. По умолчанию в атрибутах этого файла стоит только флажок Архивный (Archive), но стоит только установить Только чтение (ReadOnly), как памяти ни на что не будет хватать. Что, собственно, мы и сделали!
Стандартный запрос на удаление записи в таблице.
| Автор: Галимарзанов Фанис |
| Зачатие программера: 1. Connect 2. Download 3. Disconnect 4. UnRar (ETA: 9 месяцев) |
procedure DelRec(inSet:TDataSet);
begin
if (NotEmptySet(inSet)) and // если таблица пуста - незачем выводить запрос
(Application.MessageBox('Удалить запись?','Внимание!!!',
mb_YesNo+mb_Iconquestion)=idYes) then
inSet.Delete;
end;
|
Удаление ненужных подстрок из строки.
procedure RemoveInvalid(what, where: string): string;
// what - удаляемая подстрока, where - обрабатываемая строка
var
tstr: string;
begin
tstr:=where;
while pos(what, tstr)>0 do
tstr:=copy(tstr,1,pos(what,tstr)-1) +
copy(tstr,pos(what,tstr)+length(tstr),length(tstr));
Result:=tstr;
end;
|
Применение:
NewStr:=RemoveInvalid('<брак>','Этот <брак> в моей строке, и я хочу
удалить из нее этот <брак>');
|
Используйте стандартную функцию Pascal DELETE... Пользуясь тем же примером, вы можете сделать так....
Target:='<брак>'; While POS(Target,string)>0 do begin P := POS(Target,string); DELETE(string,P,Length(Target)); end; |
Как удалить вертикальную полосу прокрутки (скроллбар) из DBGrid.
| Создал Бог мужчину и отпустил на землю.. Через какое-то время мужчина приходит и говорит: - Хочу тр@хаться ночью - и создал Бог ему жену. Через какое-то время мужчина приходит и говорит: - Хочу тр@хаться днем - и создал Бог ему любовницу. Через какое-то время мужчина приходит и говорит: - Хочу, - говорит, - тр@хаться и днем и ночью - и создал Бог Windows. |
Для этого необходимо переопределить метод Paint. Внутри метода Paint Вы должны вызвать API процедуру SetScrollRange для установки минимального и максимального значений скроллирования в ноль (тем самым запретив скроллбар), а затем вызвать inherited. Следующий код, это unit содержащий новый компонент под названием TNoScrollBarDBGrid, который делает это.
type
TNoScrollBarDBGrid = class(TDBGrid)
protected
procedure Paint; override;
end;
procedure register;
implementation
procedure register;
begin
RegisterComponents('Samples', [TNoScrollBarDBGrid]);
end;
{ TNoScrollBarDBGrid }
procedure TNoScrollBarDBGrid.Paint;
begin
SetScrollRange(Handle, SB_VERT, 0, 0, false);
inherited;
end;
|
Удаление большого количества записей.
| Сегодня отдел по борьбе с высокими технологиями задержал юного хакера Васю Пупкина, за взлом и удаление сайта с рекламой известного майонеза. На вопрос зачем он это сделал, Вася ответил: - Ну зашел я на сайт, а там по русски было написано - DEL ME (делми). |
Судя по письмам в конференции fido7.su.dbms.interbase, существует определенный процент (около 15) задач, которые требуют периодического удаления большого количества записей. Это либо просто чистка устаревшей информации, либо перенос части данных в архив, но почти всегда - выполнение операции DELETE FROM... над количеством записей от десятков и сотен тысяч до нескольких миллионов.
При отсутствии знаний по архитектуре InterBase такая операция действительно становится проблемой. Если DELETE проходит относительно быстро, то последующие операции выборки могут "встать" на длительный период времени (возможно часы или даже десятки часов), во время которого InterBase будет интенсивно обращаться к базе данных.
Для начала нужно разобраться, что же происходит при операции DELETE. А происходит вот что - в силу архитектуры многоверсионности записей удаление записей не приводит к их действительному удалению. Просто рядом с версией записи пишется ее новая версия, но без значений полей, а с индикатором удаления. При этом, понятно, занимаемое таблицей дисковое пространство (количество страниц) может увеличиться, хотя на первый взгляд это кажется парадоксальным. (подробнее о сборке мусора и многоверсионности см. список статей внизу).
Далее, в силу уже правил сборки мусора, записи будут реально удалены (удалены предыдущие версии и delete-stub) только при попытке чтения этих записей. Пока никто к ним не обратился - устаревшие записи с диска вычищены не будут.
Итак, после массового удаления даем SELECT COUNT(*)..., возможно с тем же условием что и DELETE, для "вычистки" удаленных записей. Разумеется, результат этого запроса будет равен нулю, т.к. записей нет. Но "мусорные" записи будут собраны. Причем процесс сборки мусора будет никак не быстрее, чем время выполнения DELETE, а зачастую и много дольше (отчасти и потому, что старых версий записей была не одна, а несколько). Лучше всего select count выполнять в следующей после удаления транзакции.
Но самое большое влияние на скорость чистки удаленных записей оказывают неуникальные индексы. В качестве пояснения и примера лучше процитировать одно из писем Анны Харрисон (ныне директор IBPhoenix) на эту тему:
"Если возможно, посмотрите статистику сервера (gstat или Server Manager). Найдите индексы с наиболее длинными цепочками дубликатов у таблиц, которым предстоит пережить массовое удаление. Если цепочка больше 7000 строк (ключей) то стоимость сборки мусора будет меньше, если сделать индексы более селективными - например изменив одиночый индекс на композитный с оригинальным полем в качестве первого поля индекса и полем первичного ключа в качестве второго поля индекса.
Я попробовала удалить 20 тысяч записей из таблицы и собрать мусор - при записи небольшого размера и одном уникальном индексе (на очень медленном процессоре и антикварном винчестере) удаление заняло 47.54 секунды, а сборка мусора - 75.90 секунд (1 минута, 15.90 секунд). После этого я добавила индекс с 20000 дубликатов значений и после этого удаление заняло 38.04 секунды, но сборка мусора заняла 865.47 секунд (14 минут, 25.47 секунд).
Вот статистика запроса, который привел к сборке мусора в обоих случаях:
С уникальным индексом С дубликатами.
Elapsed time= 75.90 sec Elapsed time= 865.47 sec Reads = 1694 Reads = 1814 Writes = 1499 Writes = 1732 Fetches = 225,541 Fetches = 2,540,593Обратите внимание на elapsed time и fetches - они отличаются более чем в 10 раз.".
Харрисон дальше выводов не делает, кроме упомянутого в цитате изменения неуникального индекса. На самом деле зачастую (в зависимости от процентного соотношения записей которые удаляют, и которые остаются) бывает проще удалить или "выключить" этот неуникальный индекс.
Причем оказалось (спасибо Владимиру Мамзикову), что ALTER INDEX INACTIVE на самом деле не только удаляет индекс, оставляя его описание в базе данных, но и производит какие-то дополнительные действия. Эти действия тоже могут занять определенное время, причем чем больше дубликатов в индексе, тем больше времени это займет. DROP INDEX не производит этих "действий", и выполняется практически мгновенно.
Уже знакомые с IB могут спросить - а почему не был предложен способ backup/restore для избавления от мусора? Действительно, этим способом можно пользоваться (не забыв включить опцию Disable garbage collection), но при определенных размерах базы данных (несколько гигабайт) бывает выгоднее по скорости удалить индексы, чем делать backup restore.
Напоследок, немного данных по реальному проекту от Владимира Мамзикова:
База данных - 1.5Гб, несколько таблиц с ~1 млн записей, одна таблица с 9 млн записей. Удаление ~4 млн записей (delete) - несколько минут Сборка мусора (4млн записей) select count - 20 и более часов Операция backup/restore (с disable garbage collection) - в сумме 15 минут Отключение неуникального индекса (alter index inactive) - 6 минут Сборка мусора (4млн записей) select count без индекса - 12 минут Удаление неуникального индекса (drop index) - несколько секунд (против 6 минут alter index inactive).Разумеется, сюда не включено время активирования или повторного создания неуникального индекса. Но этот процесс в основном зависит от скорости винчестера, а кроме того, вероятно что количество оставшихся в таблице записей намного меньше, чем количество удаленных, поэтому индексирование пройдет быстро.
Удалить закладку в Word.
Document.Bookmarks.Item['BookmarkName'].Delete; procedure WordDeleteBookmark(rBookMark: string); var Name: OLEVariant; begin Name := rBookmark; Form1.worddocument1.Bookmarks.Item(Name).Delete; end; |
DirectX для начинающих.
Автор: Виктор Кода Введение Фанаты игр часто встречаются с аббревиатурой "DirectX". На упаковках игр для Windows 95 она появилась в конце 1995 года примерно одновременно с выходом самой операционной системы. С тех пор качество игр резко ушло вверх, достигнув небывалых высот. Что же такое DirectX? Это набор специальных API, которые предоставляют работающей программе прямой доступ к аппаратной части компьютера, обеспечивая наивысшее быстродействие при выводе графики, звука, получения данных от устройств ввода и т. д. Сразу надо сказать, что библиотека создавалась исключительно для игр, т. к. именно они требуют от аппаратных средств все 100% производительности. Позднее, с выходом новых версий, DirectX нашёл применение и в мультимедиа-области. Компоненты DirectX обеспечивают не только прямой доступ к устройствам компьютера: они избавляют программиста от тяжелого труда программирования на языке Assembler, решают проблему с драйверами устройств, незаменимы при создании трёхмерных и сетевых игр. До появления DirectX хороших сетевых игр было не так уж много по причине трудности их программирования. На сегодняшний день последняя версия DirectX носит порядковый номер 8.1, что позволяет задуматься, сколько утекло времени. Библиотека полностью обеспечивает разработчика всем необходимым инструментарием для разработки качественных игр, поддерживает все современные аппаратные средства и стала де-факто стандартом в игровой индустири игр для персонального компьютера IBM PC.DirectX 8 состоит из следующих компонентов:
Direct3D содержит высокоуровневый интерфейс Retained Mode позволяющий легко выводить 3-хмерные графические обьекты, и низкоуровневый интерфейс Immediate Mode, предоставляющий полный конроль над рендерингом. В восьмой версии реализована новая технология вершинных шейдеров, позволяющая поднять уровень реалистичности изображения в новых играх. По своей функциональности вплотную приближается к OpenGL 1.2. DirectDraw предназначен для отображения двумерной анимации на экране монитора. Обеспечивает прямой доступ к видеоадаптеру, за счёт чего достигается быстрый вывод графики, по скорости на порядок превышающий GDI. Эти два компонента в восьмой версии объединены в один - DirectXGraphics. Однако, лично я не почувствовал сколь-нибудь характерных изменений (в DirectDraw API). Всё это скорее похоже на маркетинговый ход. DirectSound создан для прямого доступа к аппаратной части звуковых плат, чтобы избавить воспроизведение звука от "тормозов", которые возникают при работе с Win32 API. DirectMusic работает с музыкальными командами, посылаемыми звуковой карте. DirectMusic и DirectSound восьмой версии объединены в один компонент - DirectXAudio. DirectInput эта компонента отвечает за любые устройства ввода, например, аналоговые или цифровые джойстики, рули и педали, ручку управления полётом, световой карандаш или Touch-Screen. Некоторые возможности компоненты можно реализовать средствами Win32 API, получив такой же быстрый доступ, но в некоторых областях DirectInput незаменим (поддерживает технологию force-feedback и нестандартные устройства ввода). DirectPlay упрощает жизнь программиста, решившегося добавить в своей программе возможность совместной работы (игры) по сети или по модему (это наверняка хорошо знакомо любому геймеру). В восьмой версии эта компонента значительно переработана и улучшена. DirectSetup предназначен для установки DirectX. DirectShow используется в мультимедиа-технологиях - можно выполнить высококачественое воспроизведение или захват видео и звука. Распространённость использования компоненты не слишком велика Все компоненты реализованы как внутрипоцессные серверы, т.е. размещаются в DLL. Архитектура доступа базируется на технологии COM. Значит, для использования DirectX нам понадобится среда программирования, поддерживающая обращение к функциям DLL из готовых программ и поддержка COM. Delphi легко реализует всё это, а значит, можно создавать такие же качественные программы, как и в Visual C++: понадобятся только файлы заголовков для DirectX 8. Как я изучал компьютерную графику Когда я только начинал заниматься графикой в Delphi, я применял всякие компоненты VCL вроде TBitmap и TImage. Надо сказать, дело шло: я самостоятельно разобрался с методами Canvas, догадался об необходимости буферизации при графическом выводе, сделал полноэкранную игру (правда, недоделал до конца :-(( ). Меня удивляло только то, что фигурки двигались довольно порывисто (разрешение составляло 800*600), и избавиться я от этого не мог. Постепенно я понял, что это не жизнь и начал изучать GDI. Через некоторое время я полностью закинул ТВitmар'ы и мои программы работали исключительно через функции GDI. Но и здесь вывод был слишком медленнен и пригоден разве что при разрешении 400*300 и менее. Также большой проблемой было осуществление вывода нерегулярных спрайтов (картинок с прозрачными областями), т.к. в GDI для этого нужно подготовить маску и сам спрайт. Как ни странно, впервые с DirectX (DirectDraw) я познакомился в книге Михаила Краснова "OpenGL.Графика в проектах Delphi". На дискете с книгой поставлялся пример для изменения экранного разрешения с помощью DirectDraw и заголовочный файл. Начинал я с того, что просто заново набирал исходный текст в своих проектах, "заучивая" особенности DirectDraw API. Тогда всё это выглядело довольно странным и непонятным, даже QueryInterface() был полной загадкой. На самом деле всё становится достаточно прозрачно, но только после практики соответствующего объёма. DirectDraw решил проблемы медленного вывода, а также помог при выводе прозрачных спрайтов. Теперь мои программы работают максимально быстро, а уровень программирования достиг "профессионального", ведь я использую те же самые способы, что и разработчики серьёзных игровых программ. Когда я ещё не имел доступа в Сеть, работать пришлось с заголовочными файлами от Hiroyuki Hori для DirectX 6 - их я достал на CD-ROM. Но теперь я использую DirectX 8. Вообще, надо приучить себя двигаться в ногу со временем и использовать новейшие технологии, даже если не хочется "слезать" с насиженной старой. И хотя никаких скоростных преимуществ при использовании методов DirectDraw из SDK 8 по сравнению c SDK 5 или 6 уже, наверное, не получить, предпосылок к использованию старых версий нет. Тем более что Microsoft стремится сделать отличия между API разных версий минимальными, а новейшая runtime-часть DirectX установлена уже наверняка у 99% игроманов. На кого рассчитаны примеры Все примеры рассчитаны на новичков. Даже если вы переиграли во все мыслимые игры, они не сделали из вас гуру в DirectX. Мне очень не хотелось перегружать программные примеры необязательными подробностями, поэтому все они (за исключением Sound), максимально упрощены. Мне хорошо известно, насколько трудно разбирать программы, написанные кем-то другим, если они ещё и большие по размеру. Все возможности игрового комплекса не используются, в частности, не показано, как корректно выводить в DirectDraw спрайты, выходящие за границы экрана, как плавно гасить экран и т. д. Всё это только усложняет программу - суть, сердце механизма работы компоненты теряется в дебрях кода, обеспечивающего примеру лишь красочность. Здесь вы не найдёте примеров для Direct3D и DirectMusic - я ещё сам только начинаю изучать трёхмерную графику (в основном пока OpenGL), а с MIDI-звуками связываться отказываюсь из-за примитивности технологии. DirectPlay тоже остался без внимания - это не мои интересы. Всегда начинайте рассмотрение программы с самого начала - процедуры FormCreate() главной формы, это поможет начать изучение структуры алгоритма. Я не ставил своей целью переписать главы книг по DirectX при составлении сопроводительных лекций - поэтому дал только самые общие разъяснения, которые, надеюсь, помогут ответить на немые вопросы новичков. Предполагается, что читатель владеет Delphi и умеет программировать на Object Pascal. Если вы до сих пор программировали только с использованием VCL, переход на API-подобный код может оказаться болезненным - забудьте про размещение компонент на форме подобно набору DelphiX и т. п. - сразу скажу, всё это глупости. Иногда я говорю "необходимо передать адрес переменной" - но в некоторых случаях символ @ перед переменной не ставится, а иногда он есть. Смотрите интерфейсы функций в заголовочных файлах - вы встретите зарезервированные слова var, out и const, которые указывают на необходимость работы с адресами. Что вам понадобится Все проекты строились в IDE Delphi 5. В версии 3 ошибки будут возникать уже на этапе компиляции, хотя бы из-за того, что в модуле DirectXGraphics.pas повсеместно используется тип LongWord, в третьей версии неопределённый. В версии 4 компилировать не пробовал, её у меня нет, возможно (не знаю точно), в среде не будут отображаться формы проектов. Также необходимы файлы заголовков для DirectX. Хотя существует множество различных файлов, для того, чтобы следовать хоть какому-то стандарту, я использовал файлы заголовков, загруженные с сайта www.delphi-jedi.org/delphigraphics. Эти файлы разработаны энтузиастами и представляют собой переложение файлов DirectX SDK фирмы Microsoft на язык Object Pascal. И, конечно же, нужны runtime-библиотеки DirectX 8. Их можно установить с компакт-диска DirectX SDK фирмы Microsoft. В крайнем случае возьмите какую-нибудь новую игру. Если вы всё же решите изучать DirectX по моим примерам, то сперва прочитайте файлы readme.txt и BUGS.txt - в нём я описал некоторые возникшие проблемы и недоработки. Скачать примеры: DirectX_Smpl.zip (125 K)Описания примеров:
Enum Иногда неоходимо узнать, какое оборудование установлено в компьютере, удовлетворяет ли оно предъявленным требованиям и т. д. Например, к компьютеру может быть подключено несколько игровых контроллеров, и нужно дать возможность игроку выбрать для управления одно из них, а не первое попавшееся. К тому же, их вообще может не быть, и об этом тоже надо знать. Если установлены две звуковые карты, то одна из них може иметь аппаратную поддержку DirectX, а другая - нет. Одним словом, нужно иметь список наличествующего оборудования. DirectX и его компоненты могут обеспечить такое перечисление разных устройств для их выбора. В этом проекте в простой форме осуществляется перечисление установленных видеоадаптеров, звуковых карт и устройств ввода. Надеюсь, вы разберётесь с этими возможностями DirectX самостоятельно. DirectInput API Прежде чем начать знакомиться с примерами по DirectInput, желательно изучить общий механизм работы этого API, т .к. он одинаков в общих чертах для всех устройств ввода. Впоследствии я не буду останавливаться на повторяющихся приёмах, т. к. объясню их суть здесь. Sprite Этот пример демонстрирует вывод изображения средствами DirectDraw. Он построен без использования VCL, но я надеюсь это не помешает вам. Sound Одним из достоинств компоненты DirectSound является то, что она имеет прямой (ну, почти прямой) доступ к аппаратному обеспечению звуковой карты. Среди интересных возможностей следует выделить размещение данных в памяти звуковой платы, использование аппаратного микширования звука, возможность создавать объёмный звук, используя специальные алгоритмы (это нужно для 3D-игр) - всё это мультимедиа-средства Windows обеспечить не в состоянииОбработка событий клавиш в DelphiX.
| Если завис компьютер, выдерни шнур, выдави стекло. |
Этот урок не совсем посвящен ДельфИксу. Сейчас вы научитесь обрабатывать такие события, как, например, нажатия клавиш. Но делать мы это будем не при помощи DXInput, так как я все ещё не знаю как с ним работать, делать мы это будем при помощи трех обычных событий: onKeyDown, onKeyUp и onKeyPreesed. Эти свойства есть и у формы и у DXDraw. Я буду работать с формой, кому больше нравится - могут работать с DXDraw, это ничего не меняет. Итак, в прошлый раз у нас была тарелка, летящая по уже заданной траектории. Теперь, я думаю было бы неплохо сделать спрайт стенки квадратным и небольшим, размножить их и добавить гравитацию, чтобы можно было летать по лабиринту и избегать столкновения со стенкой. Теперь стенка будет такой:
Можно будет размножить её, зная, что её размеры 23х23 и выстроить лабиринт. Кроме того не помешает создать спрайт "посадочной площадки", чтобы в лабиринте была цель. У меня она будет такой:
Теперь о том, как мы будем изменять координаты спрайта при создании. Конструктор Twall.Create теперь будет выглядеть так:(wx и wy - переменные типа Integer).
constructor Twall.Create(AParent: TSprite);
begin
inherited Create(AParent);
Image := Form1.ImageList.Items.Find('wall');
X := wx;
Y := wy;
Z := 0;
Width := Image.Width;
Height := Image.Height;
PixelCheck := True;
end;
|
Как вы уже наверно догадались, мы будем менять wx и wy для изменения координат стенки. Для того, чтобы построить вертикальную или горизнтальную линии мы будем использовать цикл for чтобы не мучиться. Привожу листинг стенки(сами разберетесь, листинг совсем несложный).
wy:=100;wx:=0;tx:=0;ty:=0; for i:=1 to 20 do begin Twall.create(SpriteEngine.Engine); wx:=wx+22; end; wy:=100;wx:=525; for i:=1 to 12 do begin Twall.create(SpriteEngine.Engine); wx:=wx+22; end; wy:=-20;wx:=0; for i:=1 to 36 do begin Twall.create(SpriteEngine.Engine); wx:=wx+22; end; wy:=597;wx:=0; for i:=1 to 32 do begin Twall.create(SpriteEngine.Engine); wx:=wx+22; end; wy:=0;wx:=-18; for i:=1 to 36 do begin Twall.create(SpriteEngine.Engine); wy:=wy+22; end; wy:=0;wx:=795; for i:=1 to 36 do begin Twall.create(SpriteEngine.Engine); wy:=wy+22; end; |
После всего этого должна получиться примерно такая картинка.
Теперь главное: заставить тарелку двигаться в зависимости от того, какие клаишы нажаты. "Раскладка" будет простой: W A S D, по их расположению, то есть W-вверх S-вниз А-влево D-вправо. Теперь о том, как мы это будем делать: мы будем менять две переменные, tx и ty(по аналогии с wx и wy). Менять мы их будем так: нажали W - ty увеличилась на 1, отпустили - уменьшилась. Создадим обработчики двух процедур: onKeyUp и onKeyDown. В оnКеуDоwn'е пишем:
case chr(ord(Key)) of 'W','Ц': ty := -1; 'S','Ы': ty := 1; 'D','А': tx := 1; 'A','Ф': tx := -1; end; |
Разбираем: case ... of ... end; - обычный оператор перебора вариантов, в вариантах стоят две буквы для того, чтобы программа не зависела от того, какой язык включен - русский или английский. В оnКеуUр'е пишем(разбора не будет):
case chr(ord(Key)) of 'W','Ц': ty := 0; 'S','Ы': ty := 0; 'D','А': tx := 0; 'A','Ф': tx := 0; end; |
Теперь добавляем гравитацию(в Ttarelka.Move вместо Y:=Y+ty пишем Y:=Y+ty+1) и этот урок закончен!
P.S. Как обычно, здесь вы можете скачать готовую программу
Проигрывание звука, передвижение картинок и проверка их столкновения в DelphiX.
| Палестинский филиал WESTWOOD впервые в мире реализовал первый уровень игры "RED ALERT 2" (взятие Пентагона) в полном 3D и максимальным уровнем реализма... Сообщается о незначительных (около 50 000) жертвах среди бета-тестеров... |
Для этого урока возьмем программу из предыдущего (так будет во всех уроках - в каждом следующем мы просто улучшаем программу предыдущего), единственные изменения - я возьму спрайты покрасивее:
Это фон(он нарисован в Fireworks):
А это тарелка(нарисована там же):
Если вы не хотите рисовать сами, вы можете скачать мой вариант
Итак, приступим к работе
Цель этого занятия - научится проигрывать звук, передвигать картинки и проверять их столкновения. Для проверки столкновений понадобится третий спрайт, я нарисую стенку.
Теперь я уже не буду объяснять, как вывести стенку на экран. Я выведу её с координатами "400,400.0".
Все, можно пристурать к анимированию. В описание класса Ttarelka, перед "Public" надо поставить такую строку:
procedure DoMove(MoveCount: Integer); override; |
Теперь описание класса должно выглядеть так:
Ttarelka = class(TImageSprite) procedure DoMove(MoveCount: Integer); override; public constructor Create(AParent: TSprite); override; end; |
Теперь эту процедуру надо ПРОписать. Сразу после трех конструкторов пишем:
procedure Ttarelka.DoMove(MoveCount: Integer); begin inherited DoMove(MoveCount); Collision; X:=X+1; Y:=Y+1; end; |
Разбираем: изменяться тут будут только большие "X" и "Y" - координаты спрайтов, "+1" - коэффициент изменения, берется от фонаря (или так, как задумал).
Запускаем и видим тарелку, летящую по диагонали, налетающую на стенку и летящую дальше!
Теперь думаем: зачем тут стенка? Обычно через стенки не летают, о них разбиваются. Начинаем писать проверку столкновений. Там же, где описывали doMove(перед "Public") пишем:
procedure DoCollision(Sprite: TSprite; var Done: Boolean); override; |
Теперь описание класса "Ttarelka" должно быть таким:
Ttarelka = class(TImageSprite) procedure DoCollision(Sprite: TSprite; var Done: Boolean); override; procedure DoMove(MCount: Integer); override; public constructor Create(AParent: TSprite); override; end; |
Теперь ПРОписываем, после прописывания процедуры doMove:
procedure Ttarelka.DoCollision(Sprite: TSprite; var Done: Boolean);
begin
if Sprite is Twall then
dead;
end;
|
Запускаем и видим, как тарелка наскакивает на стену и разбивается!
Только скучно как-то! Полетела, разбилась и все! Сделаем так, чтобы спрайт тарелки создавался по щелчку мыши (и заодно увидим, что процедура, написанная для одного спрайта действует и для всех его копий). В "Событиях"(Events) объекта DXDraw создаем обработчик onClick и пишем туда:
Ttarelka.create(SpriteEngine.Engine); |
Заодно убираем эту процедуру из onFormCreate.
Ну все бы хорошо, ещё бы было этакое "БУМ!" когда тарелка разбивается!
Ставим на форму DXWaveList и DXSound. В свойства "DXSound" вэйв-листа выбираем "DXSound1" и переименовываем сам DXWaveList1 в DXWaveList. В Items'ax создаем новый, называем его "boom" и ставим в свойстве "wave" звук. Теперь в процедуру "doCollision" добавляем:
Form1.DXWaveList.Items.Find('boom').Play(False);
|
Теперь процедура doCollision выгдядит так:
procedure Ttarelka.DoCollision(Sprite: TSprite; var Done: Boolean);
begin
if Sprite is Twall then
begin
Form1.DXWaveList.Items.Find('boom').Play(false);
dead;
end;
end;
|
Наслаждемся тем, что второй урок наконец закончен!
P.S. DXDraw на форме надо будет растянуть на весь экран.
P.P.S. У кого что-то не получилось, могут скачать готовую программу
Кому сильно охота хотя бы немного посмотреть настоящие возможности ДельфИкса, те могут скачать игру, полностью написанную на ДельфИксе, причем написанную не кем-то особо крутым, а автором сайта http://instrumentari.narod.ru (игра на двоих). а здесь лежат исходники (только для Delphi 6.0).
Delphi и 1C - экспорт и импорт.
Автор: Александр Авдошин Специально для Королевства Delphi Довольно часто перед программистами, работающими в небольших компаниях, стоит проблема импорта данных из программы "1С:Предприятие", или экспорта в нее же. Причин тому может быть множество - например, желание автоматизировать обновление прайс-листа на веб-страничке компании на основании реальных данных, или же автоматизация ввода первичных документов, отправляемых по электронной почте компанией-поставщиком. Какая бы задача подобного рода ни стояла перед программистом, она, как правило, успешно решается с помощью связки Delphi-1C. В этой статье я хотел бы дать рекомендации и разъяснить некоторые аспекты использования механизма OLE Automation применительно к программе "1С:Предприятие версия 7.7". Перед прочтением статьи я настоятельно рекомендую Вам ознакомиться с книгой "Delphi 4 Unleashed" Чарльза Калверта и с главой "Связь с внешними приложениями посредством механизмов DDE и OLE Automation" книги "1С:Предприятие 7.7 Описание встроенного языка". Также я предполагаю, что вы имеете опыт программирования как в среде Delphi, так и в среде "1С:Предприятие". Первые шаги Ну, во-первых, прежде чем использовать все возможности программы "1С:Предприятие", необходимо сначала создать соответствующий OLE-объект. Идентификатор этого OLE-объекта зависит от версии и типа установленной программы "1С:Предприятие": V1CEnterprise.Application - версия независимый ключ V77.Application - версия зависимый ключ V77S.Application - версия зависимый ключ, SQL-версия V77L.Application - версия зависимый ключ, локальная версия V77M.Application - версия зависимый ключ, сетевая версияНапример, создадим OLE-объект для сервера "1С:Предприятие". Для простоты создадим объект без привязки к конкретной версии и типу программы:
procedure TForm1.Create1C;
var
onesobj: Olevariant;
begin
onesobj := createoleobject('V1CEnterprise.Application');
end;
|
onesobj.initialize(onesobj.rmtrade,'/DD:\buh2001test /M /NСаша /P12345','NO_SPLASH_SHOW');В отличие от, например, OLE Automation-сервера приложения Microsoft Excel, сервер программы "1С-Предприятие" запускается в режиме "hide", то есть рабочее окно программы не отображается на экране. Для использования созданного и проинициализированного объекта необходимо просто обращаться к атрибутам и методам системы 1С:Предприятие как OLE Automation сервера. Для завершения работы с программой необходимо освободить OLE-объект путем присвоения ему значения UnAssigned:
onesobj := UnAssigned;Впрочем, это необязательно, так как при закрытии вашего приложения OLE-объект будет освобожден автоматически.
Просуммируем полученные знания: создадим OLE-объект "1С:Предприятие", проинициализируем его и корректно освободим:
procedure TForm1.Create1C;
var
onesobj: Olevariant;
begin
onesobj := createoleobject('V1CEnterprise.Application');
onesobj.initialize(onesobj.rmtrade,
'/DD:\buh2001test /M /NСаша /P12345', 'NO_SPLASH_SHOW');
onesobj := UnAssigned;
end;
|
Резонный вопрос. Собственно, ради этого все и затевалось, не так ли? :) На самом деле, все очень просто. После того, как мы создали и проинициализировали OLE-объект, работать с ним можно следующим образом:
С помощью метода EvalExpr(<СтрокаВыражения>) Метод EvalExpr вычисляет выражение, записанное параметре <СтрокаВыражения> на встроенном языке 1С:Предприятие и возвращает результат вычисления. Результатом выражения может быть число, строка, дата или значение любого агрегатного типа данных. С помощью метода CreateObject(<ИмяАгрегатногоТипа>) Метод CreateObject создает объект агрегатного типа данных системы 1С:Предприятие и возвращает ссылку на него. Данная функция обычно используется одновременно с явным определением переменной типа OLEVariant и присвоением ей ссылки на объект агрегатного типа данных. С помощью метода ExecuteBatch(<СтрокаОператоров>) Метод ExecuteBatch выполняет последовательность операторов, записанную в параметре <СтрокаОператоров> на встроенном языке 1С:Предприятие. Метод возвращает -1, если последовательность операторов выполнена успешно, или 0 в противном случае. Вызовом атрибутов и методов системы 1С:Предприятие как OLE Automation сервераЗдесь есть несколько подводных камней, о которых я сразу хочу предупредить:
При вызове атрибутов и методов системы 1С:Предприятие необходимо использовать их англоязычные синонимы (они указаны для каждого метода в книге "Описание встроенного языка") Для создаваемого агрегатного типа данных в среде Delphi необходимо завести переменную типа OLEVariant В случае, если вызываемый метод OLE-объекта не требует параметров (либо один из параметров является необязательным), в качестве параметра ему необходимо передавать EmptyParam (либо - для Delphi 3 - пустую строку). Для обращения к русскоязычным идентификаторам объектов агрегатных типов (например, реквизитов справочников) следует использовать метод объекта агрегатного типа getattrib(<ИмяАтрибута>) для получения значения атрибута, и setattrib(<ИмяАтрибута>) для установки значения.Для комплексной иллюстрации всего вышеописанного я приведу пример, в котором содержимое справочника "Номенклатура" целиком экспортируется в таблицу базы данных (в примере подразумевается, что уже создана таблица table1, поля которой адекватны справочнику. Таблица table2 ссылается на ту же физическую таблицу, что и table1, и служит лишь для поиска уже добавленных элементов):
procedure TForm1.exportsprav;
var
counter: integer; //Счетчик импортированных записей
onesobj: Olevariant; //OLE-объект программы 1С:Предприятие
ware, ware2: olevariant; //Агрегатные объекты
val, edizm, nds, np: olevariant;
pf: integer; //Промежуточные переменные
begin
table1.open; //Открываем таблицу1
table2.open; //Открываем таблицу2
counter := 0; //Обнуляем счетчик записей
onesobj := createoleobject('V1CEnterprise.Application'); //Создаем OLE-объект
//Инициализируем объект
onesobj.initialize(onesobj.rmtrade, '/DD:\buh2001test /M /NСаша /P12345', 'NO_SPLASH_SHOW');
//Создаем необходимые агрегатные объекты
ware := onesobj.createobject('Справочник.Номенклатура');
ware2 := onesobj.createobject('Справочник.Номенклатура');
edizm := onesobj.createobject('Справочник.ЕдиницыИзмерений');
nds := onesobj.createobject('Справочник.СтавкиНДС');
np := onesobj.createobject('Справочник.СтавкиНП');
ware.selectgroup(1); //Устанавливаем режим выборки групп
ware.selectitems(1); //Открываем выборку элементов справочника
while ware.GetItem(1) > 0 do //Выбираем все элементы
begin
if ware.level('') = 1 then //Если мы выбрали группу первого уровня, то
pf := -1
else
begin
//Иначе ищем элемент-родитель
ware2.FindItem(ware.getattrib('Родитель'));
if table2.findkey([ware2.getattrib('Код')]) then
//Если этот элемент мы уже импортировали
pf := table2.fieldbyname('ID').AsInteger //, то получаем его код
else
pf := -1; //иначе помещаем элемент в группу первого уровня
end;
if ware.deletemark('') = 0 then //Если элемент не удален, то
begin
table1.append; //добавляем новое поле к таблице
//Заполняем поля таблицы значениями соответствующих атрибутов элемента справочника
table1.fieldbyname('CODE_1S').AsInteger := ware.getAttrib('Код');
//Заполняем поле наименования
table1.fieldbyname('NAME').AsString := ware.getAttrib('Наименование');
table1.fieldbyname('PARENT_FOLDER').AsInteger := pf;
table1.fieldbyname('FULLNAME').AsString := ware.getAttrib('ПолнНаименование');
//Ищем соответствующую запись в справочнике "единицы измерения"
edizm.finditem(ware.getattrib('ЕдиницаИзмерения'));
//Заполняем поле единицы измерения
table1.fieldbyname('EDIZM').AsString := edizm.getattrib('Наименование');
//так мы получаем значения периодических реквизитов
table1.fieldbyname('SEBESTOIM').AsFloat :=
ware.getAttrib('Себестоимость').GetValue(datetostr(now));
table1.fieldbyname('PRICEOPT').AsFloat := ware.getAttrib('Цена');
nds.finditem(ware.getAttrib('СтавкаНДС').GetValue(datetostr(now)));
np.finditem(ware.getAttrib('СтавкаНП').GetValue(datetostr(now)));
//Заполняем поле ставки НДС
table1.fieldbyname('STNDS').AsFloat := nds.getAttrib('Ставка');
//Заполняем поле ставки НП
table1.fieldbyname('STNP').AsFloat := np.getAttrib('Ставка');
table1.fieldbyname('ARTICUL').AsString := ware.getAttrib('Артикул');
if Ware.IsGroup('') = 1 then //Если мы выбрали группу товара, то
table1.fieldbyname('IS_FOLDER').AsInteger := 1
else
table1.fieldbyname('IS_FOLDER').AsInteger := 0;
table1.post;
table2.refresh;
end;
inc(counter);
end;
end;
|
Пишите мне, задавайте вопросы, и вполне возможно, что вскоре появится продолжение статьи.
Что Delphi знает об ассемблере.
Автор: Павел Сегодня мы рассмотрим вопрос применения ассемблера в программах, написанных на Delphi. Итак, что же Delphi "знает" об ассемблере? Команды процесора Ура, начиная с шестой версии, в популярном продукте Borland реализована поддержка всех команд процессора, включая команды расширений MMX, SSE, 3DNow! Теперь нет необходимости вместо простой команды rdtsc писать dw 310Fh . Доступ к переменнымDelphi позволяет легко обращаться по имени к глобальным или локальным переменным:
var
i: integer;
begin
i := 0;
asm
mov eax,i
inc eax
mov i,eax
end;
end;
Доступ к параметрам
К переменным, передаваемым процедуре в качастве параметров доступ осуществляется также легко:
procedure SomeProc(i: integer);
begin
asm
mov eax,i
inc eax
mov i,eax
end;
and;
Передача параметров по ссылке
Если параметр процедуры или функции объявляется как var, то вместо значения переменной передается указатель на эту переменную. Поэтому внутри ассемблерного блока этот параметр будет представлять собой 32-разрядный указатель вместо собственно переменной. Поэтому обращаться к параметру, переданному как var надо следующим образом:
procedure SomeProc(var i: integer);
begin
asm
mov eax,i
inc dword ptr [eax]
end;
end;
Регистровое соглашение о вызове
В языке Object Pascal, использующемся в Delphi, по умолчанию действует регистровое соглашение о вызове. Согласно этому соглашению, первые три 32-разрядных параметра передаются в регистры eax,edx и ecx. Следовательно, если функция объявлена следующим образом.
function SomeFunc(i1, i2, i3 : integer): integer;то можно расчитывать, что переменная i1 содержится в регистре eax, i2 - в edx, i3 - в ecx.
Если объявлен некий метод.
procedure TSomeObject.SomeProc(i1, i2 : integer);то i1 передается в edx, i2 - в ecx, а в eax передается неявно заданный параметр Self! Доступ к полям записи
Обращение к полям записи также достаточно просто:
type
TSomeRec = record
i: integer;
c: char;
end;
procedure SomeProc(var SomeRec: TSomeRec);
begin
asm
mov [eax].TSomeRec.i, 33
mov [eax].TSomeRec.c,'a'
end;
end;
Локальные метки
Хотя использование меток и считается дурным тоном в программировании на языках высокого уровня, в ассеблерных программах без них не обойтись. Внутри блока asm..end можно, без предварительного объявления, использовать метки, начинающиеся с символа '@'.
Сохранение регистров
В ассемблерных процедурах и функциях нужно сохранять регистры EDI, ESI, ESP, EBP, and EBX и свободно оперировать регистрами EAX, ECX, and EDX.
Вызов динамических и виртуальных методов
Существуют две директивы для доступа к динамическим и виртуальным метолам из ассемблерных вставок.
VMTOFFSET - возвращает смещение (в байтах) указателя виртуального метода относительно начала таблицы виртуальных методов (VMT).
DMTINDEX - возвращает индекс динамического метода в таблице динамических методов.
Примеры вызовов виртуального и динамического методов:
procedure CallDynamicMethod(e: TExample); asm push esi mov eax,e mov esi, DMTINDEX TExample.DynamicMethod call System.@CallDynaInst pop esi end; procedure CallVirtualMethod(e: TExample); asm mov eax, e mov edx, [eax] call dword ptr [edx + VMTOFFSET TExample.VirtualMethod] end;
Delphi и CorelDraw.
| Автор: Грабец Олег |
| - А знаете, Ватсон, чем наполнены шары у CorelDraw? - Чем, мистер Холмс? - Водородом! - ?? - Ну это же элементарно, Ватсон... Вчера у меня Corel рухнул - так всю Винду разнесло нафиг! |
Надеюсь, многие сталкивались с Corel Draw? А у многих слюнки текли, что это мощнейший графический редактор и хотелось бы под него свои программы писать, к примеру, чертежи выводить? Я один из вас :).
Формат файлов *.cdr конечно, не представлю, т.к. сам его не знаю :), но как с этим зверем работать расскажу. Вычитал, что с Corel Draw можно работать только через скрипт, причем изначально я готовил файлы скриптов *.csc, а затем их запускал в самом редакторе. Рабочий инструмент для освоения - Corel Script Editor. Если Вы хотите действительно что-то написать, то он вам просто необходим, хотя бы ради того, что смотреть как Corel Draw их сам создает, ну и самое главное - дока по языку и функциям. Все замечательно, только вот скрипты медленно работают т.к. они эмитируют работу человека - т.е. кнопочки сами нажимаются, панельки меняются и т.д.
А чертеж, к примеру на котором около 3000 объектов мог загружаться и исполнятся до часу! Нет, кода это утомляет, то можно и самому посидеть - глядишь за неделю сделаешь :).
И тут я "чисто случайно" наткнулся на статейку http://www.djpate.freeserve.co.uk/AutoCDrw.htm. Оказывается можно и через OLE этот Corel Draw дергать, и как оказывается, не так уж оно и сложно. Да, совершенно верно, нужно использовать CorelDraw.Automation.xx. Я возился с 8-й версией. Забегая на перед, скажу, что тот же чертеж выводился в течении 5-10 минут.
Ну что, начнем?
var
CorelDraw: Variant;
...
CorelDraw := CreateOleObject('CorelDraw.Automation.8');
// цифирку можете свою поставить
CorelDraw.FileNew;
// или CorelDraw.FileOpen(FileName);
CorelDraw.SetDocVisible(True);
// можно и не показывать, что он там делает, но ведь интересно! :)
// кстати, можно нарисовать, а потом показать - будет на 30% быстрее
... // ну и в конце
CorelDraw.FileSave('NewName', 0, False, 0, False);
CorelDraw.FileExit(False); // можно не писать, если не надо закрывать
CorelDraw := Unassigned;
|
Формат функций доступным английским языком описан в draw_scr.hlp. Ну а дальше, чего душа (или начальство :) ) желает:
CorelDraw.SetPageOrientation(0);
CorelDraw.SetPageSize(PageW, PageH);
CorelDraw.NewLayer('NewLayer1');
CorelDraw.SelectLayer('NewLayer1');
CorelDraw.CreateEllipse(CalcY(Y1)), CalcX(X1), CalcY(Y2), CalcX(X2), 0, 0, 0);
// ничего я не перепутал!!! именно так у них координаты!
CorelDraw.CreateRectangle(CalcY(Y1)), CalcX(X1), CalcY(Y2), CalcX(X2), CalcX(Radius));
...
|
Все ясно? За дело!
Да, чуть не забыл о самом главном - как и у любой системы в Corel Draw есть свои "заморочки" :).
Ноль координат находится в середине листа бумаги (оригинально, правда?).
Положительная ось Y направлено вверх, а X - в право.
Координаты - целые числа в микронах. Для удобства я писал функцию:
function CalcX(x_mm: double): longint; begin result := Round(x_mm * 10000); end; |
Углы не знаю в чем, но 90 градусов надо записать как 90000000. Положительные против часовой стрелки.
Принцип прорисовки таков: создается объект, а затем ему присваиваются различные свойства, такие как цвет, тип заливки, толщина линий и пр. По умолчанию эти значения через автоматизацию выставить нельзя - не поддерживается.
Выше упомянутое наглядно видно на прорисовке текста:
CorelDraw.CreateArtisticText( Text, CalcX(X), CalcY(Y));
// создаем текст. X,Y - левый нижний
// как видите, нет параметров шрифта, размера и пр.
with Font do
begin
if (Italic) and (Bold) then
FSK:=14
else
if (Italic) then
FSK:=8
else
if (bold) then
FSK:=13
else
FSK:=7;
end;
CorelDraw.SetCharacterAttributes( 0, 0, Font.name, FSK, Abs(Font.Size)*10,
0, 0, 0, 0, 0, 1000, 1000, HAlign);
// присваиваем атрибуты шрифта.
// HAlign имеет значения 1,2,3 - влево, по центру, вправо соответственно
ColorToCMYK(Font.Color, C,M,Y,K);
// это моя функция для преобразования tColor в составляющие в модели CMYK
CorelDraw.StoreColor(2, C,M,Y,K, 0,0,0,0); // создание цвета
CorelDraw.ApplyUniformFillColor; // применяем цвет к объекту
|
Тоже самое относится к трансформации объектов – сперва создаете, а затем изменяете как хотите.
Работают функции для получения информации.
CorelDraw.GetSize(XSize, YSize); // получили размеры объекта CorelDraw.MoveObject(0, -YSize); // сдвинули его вниз на свой размер |
Можно "проверить" все существующие объекты. За круглым столом спрашивали, как это делается, а делается это так:
var
ObjID, FirstObjID: longint;
begin
CorelDraw.SelectAllObjects;
CorelDraw.SelectNextObject(true);
// true для "захода" в сгруппированный объект
FirstObjID := CorelDraw.GetObjectsCDRStaticID;
repeat
...
// работа с объектом CorelDraw.SelectNextObject(true);
ObjID := CorelDraw.GetObjectsCDRStaticID;
until
ObjID = FirstObjID;
...
|
Вот теперь вроде все. Пишите, если что не понятно.
Delphi и OLE Automation с Excel.
Автоматизация позволяет одному приложению управлять другим приложением. Управляемое приложение называется сервером автоматизации (в нашем случае Excel). Приложение, управляющее сервером называется диспетчером автоматизации. Есть два пути для получения доступа к серверам автоматизации: Позднее связывание (Интерфейс IDispatch).При использовании данного метода имена функций и типы параметров решаются во время выполнения программы, все параметры определены вариантным типом.
Поскольку во время компиляции невозможно определить соответствия имен функций и типов параметров, данный метод чреват ошибками.
Так как имена функций и типы параметров должны проверяться во время выполнения программы, данный метод выполняется медленно.
Единственное преимущество данного метода при программировании в Delphi заключается в том, что отпадает необходимость передачи всех параметров вызываемой функции.
Раннее связывание (Использование библиотеки типов/интерфейсов).При использовании данного метода имена функций и типы параметров полностью решаются во время компиляции.
Библиотека типов должна импортироваться в Delphi. Библиотека типов является языковым нейтральным описанием всех объектов и функций, поддерживаемых сервером. (Это подобно файлу заголовка языка C).
При вызове функции должны обязательно присутствовать все параметры, даже те, которые в документации указаны как дополнительные (необязательные). Это позволяет обнаружить и исправить множество ошибок еще до запуска программы.
Скорость выполнения значительно быстрее, чем при использовании позднего связывания.
Из-за преимуществ второго метода остальная часть документа демонстрирует принципы создания приложений с ранним связыванием. Все приложения, использующие Excel автоматизацию, должны пользоваться последним методом, если нет причин для первого.
Подготовка библиотеки типов.Модуль Pascal должен быть создан на основе файла библиотеки типов.
Выберите пункт меню Project|Import Type Library.
Нажмите кнопку Add и выберите следующий файл.
C:\program files\microsoft office\office\excel8.olb.Нажмите OK.
К сожалению, данный модуль с проектом явно не компилируется, хотя и включается в него, вероятно из-за того, что приложение считает данный модуль нечто вроде текстового приложения.
Наиболее простой путь заключается в следующем: удалите модуль excel_tlb из проекта и только после этого добавьте его в список используемых модулей.
Документация.Справочный файл c:\program files\microsoft office\office\vbaxl8.hlp содержит информацию о доступных объектах Excel.
"Записыватель" макросов позволяет быстро создавать VBA-код. После этого он довольно может легко быть портирован в Delphi.
Пример автоматизации.Код следующего примера демонстрирует создание простой электронной таблицы и наполнение ее данными. Не забудьте добавить excel_tlb в список используемых модулей.
Настоятельно рекомендую хранить код автоматизации в отдельном модуле, это поможет избежать проблем конфликта имен.
unit sheet;
interface
uses
windows, sysutils, excel_tlb;
procedure CreateSpreadsheet;
implementation
procedure CreateSpreadsheet(filename: string);
var
xla: _Application;
xlw: _Workbook;
LCID: integer;
begin
xla := CoApplication.Create;
LCID := GetUserDefaultLCID;
try
xla.Visible[LCID] := true;
// пустая книга
//xlw := xla.Workbooks.Add(xlWBATWorksheet, LCID);
// новая книга на основе шаблона
xlw := xla.Workbooks.Add(
'c:\delphi\excel\sample\demo.xlt',
LCID);
xla.Range['A1', 'A1'].Value := 'Date';
xla.Cells[1, 2].Value := FormatDateTime('dd-mmm-yyyy', Now);
xla.Cells[3, 1].Value := 'Numbers';
xla.Range['B3', 'E3'].Value := VarArrayOf([1, 10, 100, 1000]);
xla.Range['F3', 'F3'].Formula := '=Sum(B3:E3)';
OLEVariant(xla).Run(
'Demo',
FormatDateTime('dd-mmm-yyyy', Now)
);
xlw.SaveAs(
filename,
xlWorkbookNormal,
'', '', False, False,
xlNoChange,
xlLocalSessionChanges,
true, 0, 0, LCID);
finally
xla.Quit;
end;
end;
end.
|
Добавьте библиотеку типов в список используемых молулей.
uses windows, sysutils, excel_tlb; |
Первая строчка кода создает объект Excel приложения.
xla := CoApplication.Create; |
Следующая строчка кода получает пользовательский локальный идентификатор по умолчанию. Он необходим многим методам и свойствам Excel.
LCID := GetUserDefaultLCID; |
Следующая строчка кода устанавливает в истину свойство видимости, что заставляет Excel вывести свое окно. Это полезно для контроля выполняемого кода.
Примечание: Для вызова этой функции необходим параметр LCID. К сожалению этот факт умалчивается в электронной документации по Excel. В файле c:\program files\borland\Delphi 3\imports\excel_tlb.pas наглядно видны свойства функций и определения методов.
xla.visible[LCID] := true; |
Следующий код создает новую книгу и назначает ссылку на нее одной из переменных Delphi. Для VBA параметр шаблона необязателен, для Delphi - обязателен.
xlw := xla.Workbooks.Add('c:\delphi\excel\sample\demo.xlt', LCID);
|
Примечание: Вам вовсе не обязательно подставлять файл шаблона Excel (.xlt), но все же это наилучший способ для форматирования информации. Чем больше сделано с помощью Excel, тем меньше придется делать с помощью Delphi. На данный момент это является лидирующей технологией.
Для создания пустой книги используйте:
xlw := xla.Workbooks.Add(xlWBATWorksheet, LCID); |
Следующие две строки присваивают значение двум ячейкам. Здесь демонстрируются две технологии для работы с данными.
xla.Range['A1', 'A1'].Value := 'Date';
xla.Cells[1, 2].Value := FormatDateTime('dd-mmm-yyyy', Now);
|
Следующая строка демонстрирует заполнение данными целой колонки с помощью единственной команды. Это значительно повышает скорость работы, да и работать с таким кодом наглядней и удобней.
xla.Range['A2', 'D2'].Value := VarArrayOf([1, 10, 100, 1000]); |
Следующая строка демонстрирует использование формулы.
xla.Range['E2', 'E2'].Formula := '=Sum(a2:d2)'; |
Следующая строка кода выполняет VBA функцию, хранящуюся в файле шаблона. На первый взгляд все выглядит достаточно сложно, но это только кажется. Преобразование типа xla к OLEVariant позволяет вызвать функцию, используя позднее, а не раннее связывание. (Причина в имени метода и параметрах, решаемых только во время прогона программы, а никак во время разработки). Delphi просто не знает количество и тип параметров, передаваемых макросу ‘Demo’.
OLEVariant(xla).Run(
'Demo',
FormatDateTime('dd-mmm-yyyy', Now));
|
Следующий код сохраняет в файле filename электронную таблицу. Все параметры должны быть переданы в обязательном порядке, хотя многие из них являются дополнительными.
xlw.SaveAs( filename, xlWorkbookNormal, '', '',False,False, xlNoChange, xlLocalSessionChanges, true, 0, 0, LCID); |
Следующая строчка кода закрывает Excel и перераспределяет всю память, связанную с программой.
xla.quit; |
Тестирование производилось на компьютере P166 с 64Мб памяти. Первоначальная инициализация приложения не производилась. Это гарантировало, что Excel при загрузке пользовался диском, а не кэшем. Первоначальная инициализация существенно уменьшила бы скорость загрузки приложения. В реальной ситуации процесс загрузки занимает около 5 секунд.
Тест включал в себя загрузку числовых данных в чистую электронную таблицу размером 10 колонок на n строк. Для вычисления быстродействия использовались следующие три метода:
Заполнение листа ячейка за ячейкой. Заполнение одной колонки за один проход. Заполнение всей таблицы за один проход.Приведенное время не включает в себя время, затраченное на поиск данных в базе данных. Но оно вносит существенные коррективы при создании больших электронных таблиц.
Время приведено в минутах и секундах, округленных до ближайшей целой.
| Размер электронной таблицы (строки * колонки). | Заполнение ячейка за ячейкой. | Заполнение одной колонки за один проход. | Заполнение всей таблицы за один проход. |
| 10 * 10. | 0:01. | 0:01. | >0:01. |
| 100 * 10. | 0:07. | 0:01. | 0:01. |
| 1000 * 10. | 1:13. | 0:07. | 0:05. |
| 5000 * 10. | 5:22. | 0:35. | 0:25. |
| Приблизительно ячейки/секунды. | 150. | 1500. | 2000. |
Только небольшие электронные таблицы могут быть эффективно заполнены методом записи данных в каждую ячейку по отдельности.
Большие таблицы эффективно заполнять колонка за колонкой.
Также необходимо учесть дополнительную сложность при кодировании методом "ячейка за ячейкой".
Использовать буфер для передачи данных также не рекомендуется, так как это нарушит имеющиеся в нем данные и может привести к усложнению и неудобночитаемости кода.
Сохранение данных в CSV-файле и загрузка его в Excel поможет ускорить вывод данных, но для этого потребуется дополнительное форматирование книги Excel в самом коде, что усложняет само кодирование и может привести к дополнительным ошибкам.
Использованные для тестов процедуры:
//-----------------------------------------------------------------------
procedure FillByCell;
var
xla: _Application;
xlw: _Workbook;
LCID: integer;
i, j: integer;
begin
xla := CoApplication.Create;
LCID := GetUserDefaultLCID;
try
xlw := xla.Workbooks.Add(xlWBATWorksheet, LCID);
for i := 1 to ROWS do
begin
for j := 1 to 10 do
begin
xla.Cells[i, j] := i + j;
end;
end;
xlw.close(false, '', false, LCID);
finally
xla.Quit;
end;
end;
//-----------------------------------------------------------------------
procedure FillByRow;
var
xla: _Application;
xlw: _Workbook;
CellFrom: string;
CellTo: string;
i, j: integer;
Row: array[1..10] of variant;
LCID: integer;
begin
xla := CoApplication.Create;
LCID := GetUserDefaultLCID;
try
xlw := xla.Workbooks.Add(xlWBATWorksheet, LCID);
for i := 1 to ROWS do
begin
for j := 1 to 10 do
begin
Row[j] := i + j;
end;
CellFrom := 'A' + InttoStr(i);
CellTO := 'J' + InttoStr(i);
xla.Range[CellFrom, CellTo].Value := VarArrayOf(Row);
end;
xlw.close(false, '', False, LCID);
finally
xla.Quit;
end;
end;
//-----------------------------------------------------------------------
procedure FillBySheet;
var
xla: _Application;
xlw: _Workbook;
CellFrom: string;
CellTo: string;
i, j: integer;
range: Variant;
row: array[1..10] of Variant;
LCID: integer;
begin
xla := CoApplication.Create;
LCID := GetUserDefaultLCID;
try
xlw := xla.Workbooks.Add(xlWBATWorksheet, LCID);
Range := VarArrayCreate([1, ROWS], varVariant);
for i := 1 to ROWS do
begin
for j := 1 to 10 do
begin
row[j] := i + j;
end;
Range[i] := VarArrayOf(row);
end;
CellFrom := 'A' + InttoStr(1);
CellTO := 'J' + InttoStr(ROWS);
xla.Range[CellFrom, CellTo].FormulaArray := Range;
xlw.close(false, '', False, LCID);
finally
xla.Quit;
end;
end;
|
Предпочтительней использовать вызовы Автоматизации непосредственно из приложения, нижеприведенный пример демонстрирует технологию создания в Delphi класса-оболочки для использования объектов Excel в ваших приложениях. Это позволит вам иметь простой интерфейс к объектам, а также помочь с любыми изменениями объектных интерфейсов Excel в ее будущих версиях.
unit sheet;
interface
uses
EXCEL_TLB, windows, sysutils;
//-------------------------------------------------------------------------
type
tExcel = class
private
xla: _Application;
xlw: _Workbook;
LCID: integer;
procedure fSetVisible(Visible: boolean);
function fGetVisible: boolean;
procedure fSetCell(Cell: string; Value: OLEVariant);
function fGetCell(Cell: string): OleVariant;
public
constructor create;
destructor destroy; override;
procedure AddWorkBook(Template: OleVariant);
procedure SaveAs(filename: string);
property Visible: boolean
read fGetVisible write fSetVisible;
property Cell[Cell: string]: OleVariant
read fGetCell write fSetCell;
end;
//-------------------------------------------------------------------------
procedure CreateSpreadsheet(filename: string);
//-------------------------------------------------------------------------
implementation
//-------------------------------------------------------------------------
constructor tExcel.create;
begin
LCID := GetUserDefaultLCID;
xla := CoApplication.Create;
end;
//-------------------------------------------------------------------------
destructor tExcel.destroy;
begin
xla.Quit;
inherited;
end;
//-------------------------------------------------------------------------
procedure tExcel.AddWorkBook(Template: OleVariant);
begin
xlw := xla.Workbooks.Add(Template, LCID);
end;
//-------------------------------------------------------------------------
procedure tExcel.fSetVisible(Visible: boolean);
begin
xla.visible[lcid] := Visible;
end;
//-------------------------------------------------------------------------
function tExcel.fGetVisible: boolean;
begin
result := xla.visible[lcid];
end;
//-------------------------------------------------------------------------
procedure tExcel.fSetCell(Cell: string; Value: OLEVariant);
begin
xla.Range['A1', 'A1'].Value := value;
end;
//-------------------------------------------------------------------------
function tExcel.fGetCell(Cell: string): OleVariant;
begin
result := xla.Range['A1', 'A1'].Value;
end;
//-------------------------------------------------------------------------
procedure tExcel.SaveAs(filename: string);
begin
xlw.SaveAs(
filename,
xlWorkbookNormal,
'',
'',
False,
False,
xlNoChange,
xlLocalSessionChanges,
true,
0,
0,
LCID);
end;
|
Нижеприведенный пример использует данный класс для создания электронной таблицы.
procedure CreateSpreadsheet(filename: string);
var
xl: tExcel;
begin
xl := tExcel.create;
try
xl.AddWorkBook('c:\graham\excel\sample2\ssddemo.xlt');
xl.visible := true;
xl.cell['a1'] := 'тест';
xl.SaveAs(filename);
finally
xl.free;
end;
end;
|
Delphi и Flash. Совмещение несовместимого.

Автор: Михаил Христосенко WEB сайт: http://mihandelphi.narod.ru/
Разве возможно совместить Флэш-ролики и Дельфи-приложения. Раньше я думал что НЕТ. Но теперь я знаю не только, что это возможно, но и знаю как это делается!!! И сейчас я вам расскажу об этом. Во-первых хочется отметить преимущества использования флэш-роликов в ваших программах. Если вы сумеете гармонично вписать небольшой флэш-ролик в вашу программу, то несомненно внешний вид программы будет намного привлекательнее (главное не переборщить, увлекаясь дизайном, не надо забывать о том что программа должна быть удобна и проста в использовании! ).
Итак, как же совместить Флэш и Дельфи? (Надеюсь, что у вас Флэш установлен:)).
Запустите Дельфи и выберите пункт меню Component->Import ActiveX Control... Перед вами откроется диалоговое окно с заголовком Import ActiveX Control. В разделе Registered Controls выберите Shockwave Flash. В разделе Pallete Page... Выберите страницу в палитре компонентов, на которой будет располагаться установленный компонент (по умолчанию это ActiveX). В разделе Unit Dir Name... путь к папке куда будет установлен компонент.
Нажмите на кнопку Install. Перед вами появится окно, в котором вам нужно будет выбрать в какой пакет будет установлен компонент (вы можете установить как в уже существующий, так и в новый пакет). Затем перед вами появится окно редактирования выбранного пакета и Дельфи вас спросит: "...Package will be rebuilt. Continue?". Ответьте Yes. Все готово теперь можно использовать флэш в ваших приложениях!!!
Теперь, чтобы показать вам как пользоваться этим компонентом, попробуем вместе сделать программу для просмотра *.SWF файлов. Для этого нам понадобятся следующие компоненты: TShockwaveFlash (для удобства назовите его просто Flash1), TTrackBar, TTimer, TOpendialog и три кнопки TButton ("открыть", "старт" и "стоп").
Для начала установим необходимые свойства OpenDialog'a.
Свойство Filter может быть таким: Флэш-ролики|*.swf.
Свойство DefaultExt должно быть: *.swf.
Для Timer'a нужно установить свойство Interval равным 1.
Для TShockwaveFlash:
Name сделайте равным Flash1.
Свойство Playing установите в false.
Свойство BGColor, установите как вам хочется (цвет фона).
Теперь напишем обработчик события OnClick для кнопки, которая вызывать OpenDialog:
if open1.Execute then
begin
flash1.Movie := open1.FileName;
{это делается для того, чтобы потом можно было
перемещаю ползунок посмотреть каждый кадр ролика}
trackbar1.Max := flash1.TotalFrames;
end;
|
В обработчик события OnClick для второй кнопки ("Старт") напишем:
flash1.Play; |
Ну тут вообще все просто! Почти таким же образом это будет выглядеть для третьей кнопки ("Стоп"):
flash1.Stop; |
Теперь сделаем, чтобы при перемещении ползунка Trackbar'a мы могли посмотреть каждый кадр (событие OnChange):
if Flash1.IsPlaying = true then
Flash1.Stop; {если ролик проигрывается, то надо его остановить}
{открываем кадр номер которого соответствует позиции ползунка}
flash1.GotoFrame(trackbar1.position);
|
Ну и наконец осталось сделать чтобы при проигрывании ролика ползунок перемещался, указывая сколько осталось и сколько прошло. Для этого то мы и используем Timer. В обработчик события OnTimer,напишем:
trackbar1.Position:=flash1.CurrentFrame; |
Приведу полный код приложения:
unit flash;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ComCtrls, StdCtrls, OleCtrls, ShockwaveFlashObjects_TLB, ExtCtrls;
type
TForm1 = class(TForm)
Flash1: TShockwaveFlash;
Button1: TButton;
TrackBar1: TTrackBar;
Open1: TOpenDialog;
Button2: TButton;
Button3: TButton;
Timer1: TTimer;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure TrackBar1Change(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
if open1.Execute then
begin
flash1.Movie:=open1.FileName;
trackbar1.Max:=flash1.TotalFrames;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
flash1.Play;
end;
procedure TForm1.TrackBar1Change(Sender: TObject);
begin
if Flash1.IsPlaying=true then
Flash1.Stop;
flash1.GotoFrame(trackbar1.position);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
flash1.Stop;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
trackbar1.Position:=flash1.CurrentFrame;
end;
end.
|
Ну вот и все. Как оказалось ничего сложного.
Просто и ясно о MapInfo и Delphi - Вызов MapInfo и встраивание его в свою программу (основы интегрированной картографии).
Автор: Дмитрий Кузан.
Доброе время суток !
Данной статьей я начинаю цикл статей посвященных возможностям интегрированной картографии MapInfo и возможности встраивания геоинформационной системы в вашу программу. Примечания : Все примеры распространяются свободно и разработаны в обучающих целях на Delphi 6. Всю информацию по работе смотрите в прилагаемых исходных кодах.
Итак начнем.
Что такое MapInfo и с чем его едят? Краткое предисловие.
Сейчас нам доступны огромные объемы информации. Данные хранятся в электронных таблицах, отчетности о торговле и маркетинге. Масса информации о клиентах, магазинах, персонале, оборудовании и ресурсах находится на бумаге и в памяти компьютеров Тематическая Карта, содержащая слой диапазонов (процент занятости) и круговые диаграммы (производство с/х продуктов) Почти все эти данные имеют географическую составляющую. По разным оценкам до 85 процентов всех баз данных содержат, какую либо географическую информацию.
При этой оценке учитывались объемы данных, содержащие адреса, имена городов, названия областей, государств, почтовые индексы и даже номера телефонов, включая коды удаленного доступа и добавочные номера. Настольная картография позволит Вашему компьютеру не просто обрабатывать данные, а быстро и наглядно представлять их, используя географические компоненты данных, чтобы Вы могли уловить их общий смысл, отражаемый на картах.
Как настольная картография может работать на Вас? MapInfo, как средство настольной картографии, - это мощное средство анализа данных. Вы можете придать графический вид статистическим и прочим данным. Вы можете отобразить Ваши данные как точки, как тематически выделенные области, круговые и столбчатые графики, районы и т.п. К данным можно применять географические операторы, такие как районирование, комбинация и разрезание объектов и буферизация. Доступ к данным можно оформлять как запросы, в том числе к удаленным базам данных непосредственно из MapInfo. Например, какой из магазинов ближе к самым крупным клиентам Вашей фирмы? На карте легко увидеть особенности и тенденции, которые практически невозможно выявить в списочно организованных данных. Можно легко вычислить расстояния между клиентами и магазинами; можно увидеть местоположение офиса клиента, потратившего наибольшую сумму за прошлый год; размер символов, отмечающих местоположение магазинов на Карте, может зависеть от объема продаж. Все это делает визуализацию Ваших данных более наглядной. Итак краткое предисловие из руководства пользователя дает вам общее представление об MapInfo.
Что такое интегрированная картография и какой нам от нее смысл.
Интегрированная картография позволяет управлять пакетом MapInfo, используя языки программирования отличные от MapBasic. Например если вам хорошо знакомо программирование на языке Visual Basic или С++ или Delphi (о чем и пойдет речь далее...) вы можете включить окно MapInfo в ваше приложение, тем самым обеспечивая интеграцию пакета MapInfo с логикой (бизнес-правилами) вашей программы.
Причем основную работу по поддержанию векторных карт берет на себя MapInfo (MapBasic) а вы можете создовать назначать обработчики и механизмы взаимодействия не свойственные MapBasic а также те механизмы которые MapBasic не поддерживает напрямую (например обновление карты по интернету,съем информации с датчиков на территории и обновление на карте ит.п.).
Итак приступим : в цикле статей будут рассмотрены следующие вещи.
Соединение и загрузка MapInfo Встраивание окна MapInfo и других окон (легенда, информация и т.д) в программу на Delphi Отправка команд MapBasic в пакет MapInfo Получение информации от MapInfo посредством функций Использование уведомляющих вызовов (CallBack) и подключение их к своей программе. Создание собственных уведомляющих вызовов Переопределение уведомляющих вызовов Обработка уведомляющих вызовов Создание простейшего компонента (возможно данная тема будет затрунута) для управления MapInfo. и многое другое.Концепции Интегрированной Картографии
Для создания приложения с Интегрированной Картой Вы должны написать программу - но не программу на языке MapBasic. Приложения с Интегрированной Картой могут быть написаны на нескольких языках программирования, среди которых наиболее часто используются С,Visual Basic,Delphi.
В Вашей программе должна присутствовать инструкция, запускающая MapInfo в фоновом режиме. Например, в программе Вы можете запустить MapInfo вызовом функции CreateObject(). Программа MapInfo запускается в фоновом режиме незаметно для пользователя, не выводя заставку на дисплей. Ваша программа осуществляет управление программой MapInfo, конструируя строки, представляющие операторы языка MapBasic, которые затем передаются в MapInfo посредством механизмауправления объектами OLE (OLE Automation) или динамического обмена данных (DDE). MapInfo выполняет эти операторы точно так же, как если бы пользователь вводил их с клавиатуры в окно MapBasic.
Примечание: Переподчинение окон MapInfo другому приложению не дает программе MapInfo автоматического доступа к данным этого приложения. Для отображения данных приложения в окне MapInfo Вы должны предварительно записать эти данные в таблицу MapInfo.
Системные требования
Интегрированная картография требует наличия на компьютере MapInfo версии 4.0 или выше.Вы можете использовать полную версию MapInfo или так называемый исполняемый (Runtime) модуль (усеченная версия MapInfo поставляемая в качестве основы для специализированных приложений).
Вы должны иметь опыт работы с Handle.
Ваша программа должна быть способна действовать в качестве контроллера механизма управления объектами OLE (OLE Automation Controller) или клиента динамического обмена данных DDE. Рекомендуется применение OLE контроллера как более быстрого и надежного метода по сравнению c DDE. Его то мы и будем рассматривать.
Другие краткие технические замечания
Интегрированная картография использует механизм управления OLE , но не использует OLE - внедрение. Интегрированная картография не использует элементы управления VBX или OCX (дело не совсем так - существует OCX модуль MapX - для работы с ГИС MapInfo (не входит в стандартный комплект поставки) , но это уже не интегрированная картография и он рассматриваться не будет). Интегрированная картография не предоставляет вам какие либо заголовочные файлы и библиотеки Интегрированная картография включает несколько DLL библиотек но не предоставляет к ним доступ напрямую.Запуск и связывание с сервером MapInfo
Итак рассмотрим простейший компонент для запуска и управления MapInfo (TKDMapInfoServer),следует заметить что мной не ставилась написание специализированного компонента - я представляю основы.
unit KDMapInfoServer;
interface
uses
ComObj, Controls, Variants, ExtCtrls, Windows, Messages,
SysUtils, Classes;
const
scMapInfoWindowClass = 'xvt320mditask100';
icWinMapinfo = 1011;
icWinInfoWindowid = 13;
type
TEvalResult = record
AsVariant: OLEVariant;
AsString: string;
AsInteger: Integer;
AsFloat: Extended;
AsBoolean: Boolean;
end;
TKDMapInfoServer = class(TComponent)
private
{ Private declarations }
// Владелец
FOwner : TWinControl;
// OLE сервер
FServer : Variant;
FHandle : THandle;
FActive : Boolean;
FPanel : TPanel;
Connected : Boolean;
MapperID : Cardinal;
MapperNum : Cardinal;
procedure SetActive(const Value: Boolean);
procedure SetPanel(const Value: TPanel);
procedure CreateMapInfoServer;
procedure DestroyMapInfoServer;
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
// Данная процедура выполеняет метод сервера MapInfo - Do
procedure ExecuteCommandMapBasic(Command: string; const Args: array of const);
// Данная процедура выполеняет метод сервера MapInfo - Eval
function Eval(Command: string; const Args: array of const): TEvalResult; virtual;
procedure WindowMapDef;
procedure OpenMap(Path : string);
published
{ Published declarations }
// Создает соединение с сервером MapInfo
property Active: Boolean read FActive write SetActive;
property PanelMap : TPanel read FPanel write SetPanel;
end;
procedure register;
implementation
procedure register;
begin
RegisterComponents('Kuzan', [TKDMapInfoServer]);
end;
{ TKDMapInfoServer }
constructor TKDMapInfoServer.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FOwner := AOwner as TWinControl;
FHandle := 0;
FActive := False;
Connected := False;
end;
destructor TKDMapInfoServer.Destroy;
begin
DestroyMapInfoServer;
inherited Destroy;
end;
procedure TKDMapInfoServer.CreateMapInfoServer;
begin
try
FServer := CreateOleObject('MapInfo.Application');
except
FServer := Unassigned;
end;
// Скрываем панели управления MapInfo
ExecuteCommandMapBasic('Alter ButtonPad ID 4 ToolbarPosition (0, 0) Show Fixed', []);
ExecuteCommandMapBasic('Alter ButtonPad ID 3 ToolbarPosition (0, 2) Show Fixed', []);
ExecuteCommandMapBasic('Alter ButtonPad ID 1 ToolbarPosition (1, 0) Show Fixed', []);
ExecuteCommandMapBasic('Alter ButtonPad ID 2 ToolbarPosition (1, 1) Show Fixed', []);
// Переопределяем окна
ExecuteCommandMapBasic('Close All', []);
ExecuteCommandMapBasic('Set ProgressBars Off', []);
ExecuteCommandMapBasic('Set Application Window %D', [FOwner.Handle]);
ExecuteCommandMapBasic('Set Window Info Parent %D', [FOwner.Handle]);
FServer.Application.Visible := True;
if IsIconic(FOwner.Handle)then
ShowWindow(FOwner.Handle, SW_Restore);
BringWindowToTop(FOwner.Handle);
end;
procedure TKDMapInfoServer.DestroyMapInfoServer;
begin
ExecuteCommandMapBasic('End MapInfo', []);
FServer := Unassigned;
end;
procedure TKDMapInfoServer.ExecuteCommandMapBasic(Command: string;
const Args: array of const);
begin
if Connected then
try
FServer.do(Format(Command, Args));
except
on E: Exception do MessageBox(FOwner.Handle,
PChar(Format('Ошибка выполнения () - %S', [E.message])),
'Warning', MB_ICONINFORMATION or MB_OK);
end;
end;
function TKDMapInfoServer.Eval(Command: string;
const Args: array of const): TEvalResult;
function IsInt(Str : string): Boolean;
var
Pos : Integer;
begin
Result := True;
for Pos := 1 to Length(Trim(Str)) do
begin
if (Str[Pos] <> '0') and (Str[Pos] <> '1') and
(Str[Pos] <> '2') and (Str[Pos] <> '3') and
(Str[Pos] <> '4') and (Str[Pos] <> '5') and
(Str[Pos] <> '6') and (Str[Pos] <> '7') and
(Str[Pos] <> '8') and (Str[Pos] <> '9') and
(Str[Pos] <> '.') then
begin
Result := False;
Exit;
end;
end;
end;
var
ds_save: Char;
begin
if Connected then
begin
Result.AsVariant := FServer.Eval(Format(Command, Args));
Result.AsString := Result.AsVariant;
Result.AsBoolean := (Result.AsString = 'T') or (Result.AsString = 't');
if IsInt(Result.AsVariant) then
begin
try
ds_save := DecimalSeparator;
try
DecimalSeparator := '.';
Result.AsFloat := StrToFloat(Result.AsString);//Result.AsVariant;
finally
DecimalSeparator := ds_save;
end;
except
Result.AsFloat := 0.00;
end;
try
Result.AsInteger := Trunc(Result.AsFloat);
except
Result.AsInteger := 0;
end;
end
else
begin
Result.AsInteger := 0;
Result.AsFloat := 0.00;
end;
end;
end;
procedure TKDMapInfoServer.SetActive(const Value: Boolean);
begin
FActive := Value;
if FActive then
begin
CreateMapInfoServer;
WindowMapDef;
Connected := True;
end
else
begin
if Connected then
begin
DestroyMapInfoServer;
Connected := False;
end;
end;
end;
procedure TKDMapInfoServer.SetPanel(const Value: TPanel);
begin
FPanel := Value;
end;
procedure TKDMapInfoServer.WindowMapDef;
begin
ExecuteCommandMapBasic('Set Next Document Parent %D Style 1', [FPanel.Handle]);
end;
procedure TKDMapInfoServer.OpenMap(Path: string);
begin
ExecuteCommandMapBasic('Run Application "%S"', [Path]);
MapperID := Eval('WindowInfo(FrontWindow(),%D)',[12]).AsInteger;
with PanelMap do
MoveWindow(MapperID, 0, 0, FPanel.ClientWidth, FPanel.ClientHeight, True);
end;
end.
|
И так что мы имеем -
Мы установили связь с сервером MapInfo. Мы узнали что у сервера MapInfo есть метод Do - он предназначен для посылки команд MapBasic серверу точно так-же как если бы пользователь набирал их в окне MapBasic-а самой программы MapInfo. Мы узнали что у сервера MapInfo есть метод Eval- он предназначен для получения значение функций после посылки команд MapBasic серверу. Мы познакомились с командами переопределения направления вывода MapInfo.Для начала неплохо.
Теперь немного теории.Запуск MapInfo
Запуск уникального экземпляра программы МарInfо осуществляется вызовом функции CreateObject() Visual Basic с присваиванием возвращаемого значения объектной переменной. (Вы можете декларировать объектную переменную как глобальную; в противном случае объект МарInfо освобождается после выхода из локальной процедуры.).
Например:
FServer := CreateOleObject('MapInfo.Application');
|
Для подключения к ранее исполнявшемуся экземпляру MapInfo, который не был запущен вызовом функции CreateObject(), используйте функцию GetObject().
// Данная реализация оставлена вам уважаемые читатели для тренировки
FServer := GetObject('MapInfo.Application');
|
Внимание: Если Вы работаете с Runtime-версией MapInfo, а не с полной копией, задавайте "MapInfo. Runtime" вместо "MapInfo. Арр1iсаtiоn". Runtime-версия и полная версия могут работать одновременно.
Функции CreateObject() и GetObject() используют механизм управления объектами OLE (OLE Automation) для связи с MapInfo.
Примечание: В 32-разрядной версии Windows (Windows95 или Windows NT) можно запускать несколько экземпляров MapInfo. Если Вы запустите MapInfo и вслед за этим программу, использующую Интегрированную Картографию и вызывающую CreateObjectf), то будут работать два независимых экземпляра MapInfo. Однако в 16-разрядной версии программа использующая Интегрированную Картографию с запущенным MapInfo работать не сможет.
Пересылка команд в программу MapInfo
После запуска программы MapInfo необходимо сконструировать текстовые строки, представляющие операторы языка Map Basic.
Если Вы установили связь с MapInfo, используя механизм управления объектами OLE (OLE Automation), передавайте командную строку программе MapInfo методом Do.
Например:
FServer.Do('здесь команда MapBasic');
|
Примечание: В компоненте это реализовано процедурой ExecuteCommandMapBasic, но в сущносте вызывается FServer.Do.
При использовании метода Do программа MapInfo исполняет командную строку точно так как если б ее ввели в окне команд MapBasic.
Примечание: Вы можете передать оператор в программу MapInfo, если этот оператор допустим окне MapBasic. Например, Вы не можете переслать MapBasic-оператор Dialog, поскольку его использование не разрешено в окне MapBasic.
Для определения допустимости использования оператора языка MapBasic в окне MapBasic обратитесь к Справочнику MapBasic или откройте Справочную систему; искомая информация находится под заголовком "Предупреждение". Например, в Справке по оператору Dialog дано следующее ограничение: "Вы не можете использовать оператор Dialog в окне исполнения (такие, как For..-Next и Goto), не разрешены для исполнения в окне MapBasic.
Запрос данных от программы MapInfo
Для выполнения запроса из Вашей программы-клиента значения MapBasic используйте OLE-методЕvа1.
Например:
MyVar:= FServer.Eval('здесь команда MapBasic');
|
При использовании метода Eval программа MapInfo интерпретирует строку как выражение языка MapBasic, определяет значение выражения и возвращает это значение в виде строки. Замечание: Если выражение приводится к логическому значению (тип Logical), MapInfo возвращает односимвольную строку, "Т" или "F" соответственно.
Переподчинение окон MapInfo
После запуска MapInfo используйте оператор Set Application Window языка MapBasic для обеспечения перехвата управления Вашей программой-клиентом диалоговых окон и сообщений об ошибках программы MapInfo.
Затем, в желаемой точке включения окна MapInfo в Ваше приложение передайте MapInfo оператор Set Next Document, за которым следует MapBasic-оператор, создающий окно.
Оператор Set Next Document позволяет Вам "переподчинять" окна документов. Синтаксис этого оператора требует указания уникального номера HWND элемента управления в Вашей программе. При последующем создании окна-документа MapInfo (с использованием операторов Map, Graph, Browse, Layout или Create Legend) создаваемое окно становится для окна порождающим объектом.
Примеры приведены из компонента но тоже самое можно выполнить и метолом Do непосредственно, но вы это уже я думаю поняли.
ExecuteCommandMapBasic('Set Application Window %D', [FOwner.Handle]);
ExecuteCommandMapBasic('Set Window Info Parent %D', [FOwner.Handle]);
ExecuteCommandMapBasic('Set Next Document Parent %D Style 1', [FPanel.Handle]);
|
Примечание: В компоненте это реализовано процедурой WindowMapDef которая ссылается на панель заданную свойством PanelMap.
Для каждого переподчиняемого окна необходимо передать программе MapInfo из Вашей программы пару операторов - оператор Set Next Document Parent, а затем оператор, создающий окно. После создания окна Вам может понадобиться запросить из MapInfo значение функции WindowID(0) - целочисленный ID-номер окна (Window ID) в MapInfo, так как многие операторы языка MapBasic требуют задания этого номера. Этот запрос выполняется на основе компонента следующим образом:
WindowID := Eval('WindowID(%D)',[0]).AsInteger;
|
Заметьте, что даже после переподчинения окна Карты, MapInfo продолжает управлять им. клиентская программа может не обращать внимания на сообщения о перерисовке, реализацию данной особенности я оставлю на потом.
Переподчинение окон Легенд, растровых диалогов и других окон MapInfo
Чтобы изменить (преподчинить) данные окна используется оператор MapBasic Set Window... Parent.
Например, в компоненте переподчинение окна информации реализовано так -
ExecuteCommandMapBasic('Set Window Info Parent %D', [FOwner.Handle]);
|
Реализацию переподчинения других окон я оставляю вам уважаемые читатели.
Заметьте, что способ переподчинения окна Информации другой, чем для окна Карты. В последнем случае не используется предложение Set Next Document. Дело в том, что может существовать несколько окон Карты.
Окна Легенды - особый случай. Обычно существует только одно окно Легенды, так же, как и одно окно Информации. Однако при помощи оператора MapBasic Create Legend Вы можете создавать дополнительные окна Легенды.
Для одного окна Легенды используйте оператор MapBasic Window Legend Parent.
Чтобы создать дополнительное окно Легенды, используйте оператор MapBasic Set Next Document и оператор Create Legend. Заметьте, что в этом случае Вы создаете Легенду, которая привязана к одному определенному окну Карты или окну Графика. Такое окно Легенды не изменяется, когда другое окно становится активным.
Совет: Вы можете создать "плавающее" окно Легенды внутри окна Карты. В операторе Set Next Document укажите окно Карты как порождающее окно. Для получения более подробной информации смотрите в документации по MapBasic.
Продолжение следует….
Конец первой части.
Просто и ясно о MapInfo и Delphi - Реализация CallBack вызовов MapInfo и перехват в собственной программе.
Автор: Дмитрий Кузан.
Доброе время суток !
Краткое примечание
Немного об отзывах - хочу сообщить и повторить снова в данных циклах статей не будет информации об ActiveX компоненте MapX (о работе с ней, отзывы о ней и т.п.) по причине отсутствия у меня оной (может кто поделится J).
Использование уведомляющих вызовов (Callbacks) для получения информации из Maplnfo - краткий учебный курс.
Вы можете построить Ваше приложение так, чтобы Maplnfo автоматически посылало информацию Вашей клиентской программе. Например, можно сделать так, чтобы всякий раз при открытии и смене диалоговых окон сообщать ID-номер текущего окна.
Такой тип уведомления известен как обратный вызов или уведомление (callback).
Уведомления используються в следующих случаях:
Пользователь применяет инструмент в окне. Например, если пользователь производит перемещение объекта мышкой в окне Карты, MapInfo может вызвать Вашу клиентскую программу, чтобы сообщить х- и у-координаты. Пользователь выбирает команду меню. Например, предположим, что Ваше приложение настраивает "быстрое" меню MapInfo (меню, возникающее при нажатии правой кнопки мышки). Когда пользователь выбирает команду из этого меню, MapInfo может вызвать Вашу клиентскую программу, чтобы сообщить ей о выборе. Изменяется окно Карты. Если пользователь изменяет содержание окна Карты (например, добавляя или передвигая слои), MapInfo может послать Вашей клиентской программе идентификатор этого окна. Изменяется текст в строке сообщений MapInfo. Строка состояния MapInfo не появляется автоматически в приложениях Интегрированной Картографии. Если Вы хотите, чтобы Ваша клиентская программа эмулировала строку состояния MapInfo, то Вы должны построить приложение так, чтобы MapInfo сообщало вашей клиентской программе об изменениях текста в строке состояния.Требования к функциям уведомления
Программа должна быть способна функционировать, как DDE-сервер или как сервер Автоматизации OLE.
Предопределенные процедуры SetStatusText, WindowContentsChanged.
Если Вы хотите имитировать строку состояния MapInfo, создайте метод, называемый SetStatusText. Определите этот метод так, чтобы у него был один аргумент: строка.
Метод WindowContentsChanged, MapInfo посылает четырехбайтовое целое число (ID окна MapInfo), чтобы указать, какое из окон Карты изменилось. Напишите код, делающий необходимую обработку.
Возможно так-же и регистрация пользовательских событий. но это отложим пока на третью часть.
Переинсталяция компонента TKDMapInfoServer
Удалите старый компонент Зарегистрируете в системе библиотеку MICallBack.dll , для этого откройте MICallBack.dpr и в меню Run Delphi выбирите Register ActiveX Server.После этого скопируйте саму DLL в каталог Windows Установите пакет KDPack.dpk в DelphiВот в принципе и все.
Сервер автоматизации OLE для обработки CallBack
Данный сервер я разместил в ActiveX DLL.(данная DLL называется MICallBack.dll) в виде Automation Object.-а.
Что-бы вам просмотреть методы и свойства данногоАиtоmаtiоn Object.-а. откройте MICallBack.dpr и в меню Run Delphi выбирите TypeLibrary.
Откроется окно - Где я реализовал CallBack методы MapInfo и создал сервер автоматизации MICallBack. Обратите внимание, что у данного сервера помимо присутствия интерфейса IMapInfoCallBack присутствует и еще интерфейс ImapInfoCallBackEvents (он нам нужен будет для перенаправления событий в компонент и далее в обработчик).
Листинг интерфейсного модуля.
unit Call;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
ComObj, ActiveX, Dialogs, AxCtrls, Classes, MICallBack_TLB, StdVcl;
type
TMapInfoCallBack = class(TAutoObject, IConnectionPointContainer, IMapInfoCallBack)
private
{ Private declarations }
FConnectionPoints: TConnectionPoints;
FConnectionPoint: TConnectionPoint;
FEvents: IMapInfoCallBackEvents;
{ note: FEvents maintains a *single* event sink. For access to more
than one event sink, use FConnectionPoint.SinkList, and iterate
through the list of sinks. }
public
procedure Initialize; override;
protected
{ Protected declarations }
property ConnectionPoints: TConnectionPoints read FConnectionPoints
implements IConnectionPointContainer;
procedure EventSinkChanged(const EventSink: IUnknown); override;
procedure SetStatusText(const Status: WideString); safecall;
procedure WindowContentsChanged(ID: Integer); safecall;
procedure MyEvent(const Info: WideString); safecall;
end;
var
FDLLCall: THandle;
implementation
uses ComServ;
procedure TMapInfoCallBack.EventSinkChanged(const EventSink: IUnknown);
begin
FEvents := EventSink as IMapInfoCallBackEvents;
end;
procedure TMapInfoCallBack.Initialize;
begin
inherited Initialize;
FConnectionPoints := TConnectionPoints.Create(Self);
if AutoFactory.EventTypeInfo <> nil then
FConnectionPoint := FConnectionPoints.CreateConnectionPoint(
AutoFactory.EventIID, ckSingle, EventConnect)
else
FConnectionPoint := nil;
end;
procedure TMapInfoCallBack.SetStatusText(const Status: WideString);
begin
if FEvents <> nil then
FEvents.OnChangeStatusText(Status);
end;
procedure TMapInfoCallBack.WindowContentsChanged(ID: Integer);
begin
if FEvents <> nil then
FEvents.OnChangeWindowContentsChanged(ID);
end;
procedure TMapInfoCallBack.MyEvent(const Info: WideString);
begin
if FEvents <> nil then
FEvents.OnChangeMyEvent(Info);
end;
initialization
TAutoObjectFactory.Create(ComServer, TMapInfoCallBack, Class_MapInfoCallBack,
ciMultiInstance, tmApartment);
end.
|
Обратите внимание на присутствие двух предопределенных методов MapInfo SetStatusText и WindowContentsChanged.
Метод MyEvent я пока зарезервировал для реализации своих сообщений (более подробно будет изложено в 3 части цикла).
И так что мы видим.
// если есть обработчик if FEvents <> nil then begin // Отправка сообщения далее - в данном случае в компонент FEvents.OnChangeStatusText(Status); |
Как заставить MapInfo пересылать CallBack данному OLE серверу и как нам обрабатывать сообщения в компоненте от OLE сервера.
Итак представляю переработанный компонент -
unit KDMapInfoServer;
interface
uses
Stdctrls, Dialogs, ComObj, Controls, Variants, ExtCtrls, Windows, ActiveX,
Messages, SysUtils, Classes, MICallBack_TLB; // - сгенерировано из DLL
type
// запись "типа" Variant
TEvalResult = record
AsVariant: OLEVariant;
AsString: string;
AsInteger: Integer;
AsFloat: Extended;
AsBoolean: Boolean;
end;
type
// Событие на изменение SetStatusText // генерируется при обратном вызове
TSetStatusTextEvent = procedure(Sender : TObject; StatusText: WideString) of object;
// WindowContentsChanged
TWindowContentsChanged = procedure(Sender : TObject; ID : Integer) of object;
// Для собственных событий
TMyEvent = procedure(Sender : TObject; Info : WideString) of object;
TEvent = class(TInterfacedObject,IUnknown,IDispatch)
private
FAppConnection : Integer;
FAppDispatch : IDispatch;
FAppDispIntfIID : TGUID;
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
function GetTypeInfo(index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount,
LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
function Invoke(dispid: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
public
constructor Create( AnAppDispatch : IDispatch; const AnAppDispIntfIID : TGUID);
destructor Destroy ; override;
end;
TKDMapInfoServer = class(TComponent)
private
{ Private declarations }
FOwner : TWinControl; // Владелец
Responder : Variant; // Для OLE Disp
FServer : Variant;
FHandle : THandle; // Зарезервировано
FActive : Boolean; // Запущен/незапущен
FPanel : TPanel; // Панель вывода
srv_OLE : OLEVariant;
srv_disp : IMapInfoCallBackDisp;
srv_vTable : IMapInfoCallBack;
FEvent : TEvent;
FSetStatusTextEvent : TSetStatusTextEvent; // события компонента
FWindowContentsChanged : TWindowContentsChanged;
FMyEvent : TMyEvent;
Connected : Boolean; // Установлено ли соединение
MapperID : Cardinal; // ИД окна
procedure SetActive(const Value: Boolean);
procedure SetPanel(const Value: TPanel);
procedure CreateMapInfoServer;
procedure DestroyMapInfoServer;
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
// Данная процедура выполеняет метод сервера MapInfo - Do
procedure ExecuteCommandMapBasic(Command: string; const Args: array of const);
function Eval(Command: string; const Args: array of const): TEvalResult; virtual;
procedure WindowMapDef;
procedure OpenMap(Path : string);
procedure RepaintWindowMap;
// Дополнил для генерации события SetStatus при изменении строки состояния
// в MapInfo
procedure DoSetStatus(StatusText: WideString);
// Дополнил.для генерации события WindowContentsChanged при изменении окна
// в MapInfo
procedure DoWindowContentsChanged(ID : Integer);
// Дополнил для генерации собственно события в MapInfo
procedure DoMyEvent(Info: WideString);
published
{ Published declarations }
// Создает соединение с сервером MapInfo
property Active: Boolean read FActive write SetActive;
property PanelMap : TPanel read FPanel write SetPanel;
// Событие возникающее при изменении строки состояния MapInfo
property StatusTextChange : TSetStatusTextEvent read FSetStatusTextEvent
write FSetStatusTextEvent;
property WindowContentsChanged : TWindowContentsChanged read FWindowContentsChanged
write FWindowContentsChanged;
property MyEventChange : TMyEvent read FMyEvent write FMyEvent;
end;
var
// О это вообще хитрость - используеться для определения созданного компонента
// TKDMapInfoServer (см. SetStatusText и Create
KDMapInfoServ : TKDMapInfoServer;
procedure register;
implementation
// Вот тут то и хитрость если сервер создан то тогда и вызываем SetStatus
//// IF KDMapInfoServ <> nil Then
/// KDMapInfoServ.SetStatus(StatusText);
procedure register;
begin
RegisterComponents('Kuzan', [TKDMapInfoServer]);
end;
{ TKDMapInfoServer }
constructor TKDMapInfoServer.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FOwner := AOwner as TWinControl;
KDMapInfoServ := Self; // **** Вот тут и указываеться созданный компонент
// TKDMapInfoServer
FHandle := 0;
FActive := False;
Connected := False;
end;
destructor TKDMapInfoServer.Destroy;
begin
DestroyMapInfoServer;
inherited Destroy;
end;
procedure TKDMapInfoServer.CreateMapInfoServer;
begin
try
FServer := CreateOleObject('MapInfo.Application');
except
FServer := Unassigned;
end;
// Скрываем панели управления MapInfo
ExecuteCommandMapBasic('Alter ButtonPad ID 4 ToolbarPosition (0, 0) Show Fixed', []);
ExecuteCommandMapBasic('Alter ButtonPad ID 3 ToolbarPosition (0, 2) Show Fixed', []);
ExecuteCommandMapBasic('Alter ButtonPad ID 1 ToolbarPosition (1, 0) Show Fixed', []);
ExecuteCommandMapBasic('Alter ButtonPad ID 2 ToolbarPosition (1, 1) Show Fixed', []);
ExecuteCommandMapBasic('Close All', []);
ExecuteCommandMapBasic('Set ProgressBars Off', []);
ExecuteCommandMapBasic('Set Application Window %D', [FOwner.Handle]);
ExecuteCommandMapBasic('Set Window Info Parent %D', [FOwner.Handle]);
FServer.Application.Visible := True;
if IsIconic(FOwner.Handle)then ShowWindow(FOwner.Handle, SW_Restore);
BringWindowToTop(FOwner.Handle);
srv_ole := CreateOleObject('MICallBack.MapInfoCallBack') as IDispatch;
srv_vtable := CoMapInfoCallBack.Create;
srv_disp := CreateComObject(CLASS_MapInfoCallBack) as IMapInfoCallBackDisp;
FEvent := TEvent.Create(srv_disp,IMapInfoCallBackEvents);
// Указываем MapInfo что нужно передовать обратные вызовы нашему OLE
// а тм далее по цепочке (см.начало)
FServer.SetCallBack(srv_disp);
end;
procedure TKDMapInfoServer.DestroyMapInfoServer;
begin
ExecuteCommandMapBasic('End MapInfo', []);
FServer := Unassigned;
end;
procedure TKDMapInfoServer.ExecuteCommandMapBasic(Command: string;
const Args: array of const);
begin
if Connected then
try
FServer.do(Format(Command, Args));
except
on E: Exception do MessageBox(FOwner.Handle,
PChar(Format('Ошибка выполнения () - %S', [E.message])),
'Warning', MB_ICONINFORMATION or MB_OK);
end;
end;
function TKDMapInfoServer.Eval(Command: string;
const Args: array of const): TEvalResult;
function IsInt(Str : string): Boolean;
var
Pos : Integer;
begin
Result := True;
for Pos := 1 to Length(Trim(Str)) do
begin
if (Str[Pos] <> '0') and (Str[Pos] <> '1') and
(Str[Pos] <> '2') and (Str[Pos] <> '3') and
(Str[Pos] <> '4') and (Str[Pos] <> '5') and
(Str[Pos] <> '6') and (Str[Pos] <> '7') and
(Str[Pos] <> '8') and (Str[Pos] <> '9') and
(Str[Pos] <> '.') then
begin
Result := False;
Exit;
end;
end;
end;
var
ds_save: Char;
begin
if Connected then
begin
Result.AsVariant := FServer.Eval(Format(Command, Args));
Result.AsString := Result.AsVariant;
Result.AsBoolean := (Result.AsString = 'T') or (Result.AsString = 't');
if IsInt(Result.AsVariant) then
begin
try
ds_save := DecimalSeparator;
try
DecimalSeparator := '.';
Result.AsFloat := StrToFloat(Result.AsString);
finally
DecimalSeparator := ds_save;
end;
except
Result.AsFloat := 0.00;
end;
try
Result.AsInteger := Trunc(Result.AsFloat);
except
Result.AsInteger := 0;
end;
end
else
begin
Result.AsInteger := 0;
Result.AsFloat := 0.00;
end;
end;
end;
procedure TKDMapInfoServer.SetActive(const Value: Boolean);
begin
FActive := Value;
if FActive then
begin
CreateMapInfoServer;
WindowMapDef;
Connected := True;
end
else
begin
if Connected then
begin
DestroyMapInfoServer;
Connected := False;
end;
end;
end;
procedure TKDMapInfoServer.SetPanel(const Value: TPanel);
begin
FPanel := Value;
end;
procedure TKDMapInfoServer.WindowMapDef;
begin
ExecuteCommandMapBasic('Set Next Document Parent %D Style 1', [FPanel.Handle]);
RepaintWindowMap;
end;
procedure TKDMapInfoServer.OpenMap(Path: string);
begin
ExecuteCommandMapBasic('Run Application "%S"', [Path]);
MapperID := Eval('WindowInfo(FrontWindow(),%D)',[12]).AsInteger;
RepaintWindowMap;
end;
procedure TKDMapInfoServer.DoSetStatus(StatusText: WideString);
begin
if Assigned(FSetStatusTextEvent) then
FSetStatusTextEvent(Self,StatusText);
end;
procedure TKDMapInfoServer.DoWindowContentsChanged(ID: Integer);
begin
if Assigned(FWindowContentsChanged) then
FWindowContentsChanged(Self,ID);
end;
procedure TKDMapInfoServer.DoMyEvent(Info: WideString);
begin
if Assigned(FWindowContentsChanged) then
FMyEvent(Self,Info);
end;
procedure TKDMapInfoServer.RepaintWindowMap;
begin
with PanelMap do
MoveWindow(MapperID, 0, 0, FPanel.ClientWidth, FPanel.ClientHeight, True);
end;
{ TEvent }
function TEvent._AddRef: Integer;
begin
Result := 2; // Заглушка
end;
function TEvent._Release: Integer;
begin
Result := 1; // Заглушка
end;
constructor TEvent.Create(AnAppDispatch: IDispatch;
const AnAppDispIntfIID: TGUID);
begin
inherited Create;
FAppDispatch := AnAppDispatch;
FAppDispIntfIID := AnAppDispIntfIID;
// Передадим серверу
InterfaceConnect(FAppDispatch,FAppDispIntfIID,self,FAppConnection);
end;
destructor TEvent.Destroy;
begin
InterfaceDisConnect(FAppDispatch,FAppDispIntfIID,FAppConnection);
inherited;
end;
function TEvent.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount,
LocaleID: Integer; DispIDs: Pointer): HResult;
begin
// Заглушка не реализовано
Result := E_NOTIMPL;
end;
function TEvent.GetTypeInfo(index, LocaleID: Integer;
out TypeInfo): HResult;
begin
// Заглушка не реализовано
Result := E_NOTIMPL;
end;
function TEvent.GetTypeInfoCount(out Count: Integer): HResult;
begin
// Заглушка не реализовано
Count := 0;
Result := S_OK;
end;
function TEvent.Invoke(dispid: Integer; const IID: TGUID;
LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
ArgErr: Pointer): HResult;
var
Info,Status : string;
IDWin : Integer;
begin
case dispid of
1 :
begin
Status := TDispParams(Params).rgvarg^[0].bstrval;
if KDMapInfoServ <> nil then
KDMapInfoServ.DoSetStatus(Status);
end;
2 :
begin
IDWin := TDispParams(Params).rgvarg^[0].bval;
if KDMapInfoServ <> nil then
KDMapInfoServ.DoWindowContentsChanged(IDWin);
end;
3 :
begin
Info := TDispParams(Params).rgvarg^[0].bstrval;
if KDMapInfoServ <> nil then
KDMapInfoServ.DoMyEvent(Info);
end;
end;
Result := S_OK;
end;
function TEvent.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
Result := E_NOINTERFACE;
if GetInterface(IID,Obj) then
Result := S_OK;
if IsEqualGUID(IID,FAppDispIntfIID) and GetInterface(IDispatch,Obj) then
Result := S_OK;
end;
end.
|
И так что добавилось - Метод CreateMapInfoServer;
// Создаем наш сервер OLE
srv_ole := CreateOleObject('MICallBack.MapInfoCallBack') as IDispatch;
srv_vtable := CoMapInfoCallBack.Create;
// Получаем Idispatch созданного сервера
srv_disp := CreateComObject(CLASS_MapInfoCallBack) as IMapInfoCallBackDisp;
FEvent := TEvent.Create(srv_disp,IMapInfoCallBackEvents);
// Указываем MapInfo что нужно передовать обратные вызовы нашему OLE серверу
// а там далее по цепочке (см.начало)
FServer.SetCallBack(srv_disp);
end;
|
Здесь мы столкнулись с еще одним методом MapInfo помимо рассмотренных ранее методов Do и Eval - Метод SetCallBack(IDispatch). Описание - Регистрирует объект механизма-управления объектами OLE (OLE Automation) как получатель уведомлений, генерируемых программой MapInfo. Только одна функция уведомления может быть зарегистрирована в каждый данный момент. Параметр интерфейс Idispatch объекта OLE (COM).
Реализация FServer.SetCallBack(srv_disp); - данным кодом мы заставили MapInfo уведомлять наш OLE сервер.
Хорошо, скажете вы, ну заставили но он то уведомляет сервер OLE а не нашу программу, для этого я ввел следующий код (прим. Реализацию использования интерфейса событий OLE сервера я подробно расписывать не стану - для этого читайте в книгах главы по COM).
Я сделал так: ввел класс отвечающий за принятие событий от COM(OLE) объекта.
TEvent = class(TInterfacedObject,IUnknown,IDispatch) private FAppConnection : Integer; FAppDispatch : IDispatch; FAppDispIntfIID : TGUID; protected function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; function GetTypeInfoCount(out Count: Integer): HResult; stdcall; function GetTypeInfo(index, LocaleID: Integer; out TypeInfo): HResult; stdcall; function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall; function Invoke(dispid: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall; public constructor Create( AnAppDispatch : IDispatch; const AnAppDispIntfIID : TGUID); destructor Destroy ; override; end; |
Создание этого класса в компоненте реализовано так.
FEvent := TEvent.Create(srv_disp,IMapInfoCallBackEvents); |
В методе Invoke и происходит прием и получение сообщений и пересылка их в обработчик моего компонента.
Еще раз на последующие вопросы касательно COM (OLE) серверов отвечу: данная тема выходит за рамки данной статьи - советую почитать книгу Александроского А.Д - Delphi 5 разработка корпоративных приложений.
Напоследок — модуль MICallBack_TLB.pas импортирован из DLL командой меню DELPHI Import Type Libray.
Примечание: при импорте данный сервер инсталировать не нужно, нет смысла он нам нужен только для приема сообщений из MapInfo.
Вот в принципе все во второй части; создание пользовательских событий и обработка их в следующей главе.
До встречи.
Просто и ясно о MapInfo и Delphi - Настройка панелей и меню. Реализация собственных CallBack вызовов и обработка поступивших данных.
Автор: Дмитрий Кузан.
Доброе время суток !
Этой статьей я заканчиваю введение в интегрированную картографию MapInfo.Надеюсь, что данный цикл статей открыл вам возможность применять MapInfo в ваших программах. Перед началом я хочу дать вам ссылку на сайт пользователй MapInfo , где вы найдете исчерпывающеюся информацию по MapInfo и MapBasic в частности на русском языке. Многое что я дал вам по MapBasic в этих частях взято оттуда.
Интеграция инструментальных панелей Maplnfo краткий вводный курс.
Вы не можете переподчинить стандартные инструментальные панели MapInfo. Если Вы хотите, чтобы Ваша клиентская программа имела такие панели вы должны сами создать панели и кнопки на Delphi (например используя Tpanel и Tbutton) и их обработчике посылать специальные команды MapInfo для того что-бы MapInfo включало или переключала режимы работы (например с выбора объекта на перемещения окна карты (ладошка)).
Если Вы хотите, чтобы кнопка панели эмулировала стандартную кнопку MapInfo, используйте метод MapInfo Run Menu Command.
Например в обработчике OnClick пропишите следующею команду.
KDMapInfoServer1.ExecuteCommandMapBasic('Run Menu Command 1702',[]);
|
Когда пользователь нажмет на эту кнопку, программа вызывовет метод MapInfo - Run Menu Command, который активизирует инструмент под номером 1702 (инструмент перемещение карты "рука" ).
"Магический" номер 1702 ссылается на инструмент "рука" служащий для перемещения (сдвига) карты.
Вместо того, чтобы использовать такие числа. Вы можете использовать идентификаторы, более понятные в тексте программы. MapBasic определяет стандартный идентификатор M_TOOLS_RECENTER который имеет значение 1702. Таким образом, этот пример можно записать так:
KDMapInfoServer1.ExecuteCommandMapBasic('Run Menu Command %S', [M_TOOLS_RECENTER]);
|
Использование идентификаторов (типа M_TOOLS_RECENTER) делает Вашу программу более читательной, но перед использование вы должны включить в программу (в Uses) соответствующий заголовочный файл MapBasic. Для Delphi я положил файл Global.pas (содержимое файла опубликовано в приложении 1).
В следующей таблице приведены кратко идентификаторы основных инструментальных кнопок MapInfo (для более побробной информации смотрите документацию по MapBasic).
| Кнопки панели Операции | Номер | Идентификатор | Примечание |
| Выбор | 1701 | М_TOOLS_SELECTOR | Панель ОПЕРАЦИИ |
| Выбор в прямоугольнике | 1722 | M_TOOLS_SEARCH_RECT | Панель ОПЕРАЦИИ |
| Выбор в круге | 1703 | M_TOOLS_SEARCH_RADIUS | Панель ОПЕРАЦИИ |
| Выбор в области | 1704 | M_TOOLS_SEARCH_BOUNDARY | Панель ОПЕРАЦИИ |
| Увеличивающая лупа | 1705 | M_TOOLS_EXPAND | Панель ОПЕРАЦИИ |
| Уменьшающая лупа | 1706 | M_TOOLS_SHRINK | Панель ОПЕРАЦИИ |
| Ладошка (рука) | 1702 | M_TOOLS_RECENTER | Панель ОПЕРАЦИИ |
| Информация | 1707 | M_TOOLS_PNT_QUERY | Панель ОПЕРАЦИИ |
| Подпись | 1708 | M_TOOLS_LABELER | Панель ОПЕРАЦИИ |
| Линейка | 1710 | M_TOOLS_RULER | Панель ОПЕРАЦИИ |
| Переноска | 1734 | M_TOOLS_DRAGWINDOW | Панель ОПЕРАЦИИ |
| Символ | 1711 | M_TOOLS_POINT | Панель ПЕНАЛ |
| Линия | 1712 | M_TOOLS_LINE | Панель ПЕНАЛ |
| Полилиния | 1713 | M_TOOLS_POLYLINE | Панель ПЕНАЛ |
| Дуга | 1716 | M_TOOLS_ARC | Панель ПЕНАЛ |
| Полигон | 1714 | M_TOOLS_POLYGON | Панель ПЕНАЛ |
| Эллипс | 1715 | M_TOOLS_ELLIPSE | Панель ПЕНАЛ |
| Прямоугольник | 1717 | M_TOOLS_RECTANGLE | Панель ПЕНАЛ |
| Прямоугольник скругленный | 1718 | M_TOOLS_ROUNDEDRECT | Панель ПЕНАЛ |
| Текст | 1709 | M_TOOLS_TEXT | Панель ПЕНАЛ |
| Рамка | 1719 | M_TOOLS_FRAME | Панель ПЕНАЛ |
Настройка "быстрых" меню Maplnfo
MapInfo вызывает "быстрые" меню, если пользователь нажимает правую кнопку мышки в окне MapInfo. Эти меню появляются даже во внедренных приложениях. В зависимости от характера Вашего приложения Вы можете захотеть модифицировать или даже удалить такое меню. Например, Вы, возможно, захотите удалить команду ДУБЛИРОВАТЬ ОКНО, так как эта команда не работает в OLE-приложении.
Чтобы удалить одну или несколько команд из локального меню, используйте оператор MapBasic Alter Menu... Remove или переопределите меню целиком, используя оператор Create Menu. Подробнее смотрите в Справочнике MapBasic.
Чтобы добавить команду к локальному меню, используйте оператор MapBasic Alter Menu ... Add и синтаксис предложений Calling OLE.
Чтобы удалить "быстрое" меню полностью, используйте оператор MapBasic Create Menu и управляющий код "(-" как новое определение меню. Например, следующий оператор разрушает "быстрое" меню для окон Карты:
KDMapInfoServer1.ExecuteCommandMapBasic(' "Create Menu ""MapperShortcut"" ID 17 As ""(-"" " ', []);
|
Создание собственных уведомляющих вызовов (Callbacks).
Во второй части мы рассмотрели возможность перехвата двух стандартных вызовов MapInfo - это дало нам возможность подключить к своей программе статус бар MapInfo и узнавать об изменениях окон MapInfo.Все это очень неплохо, но сразу возник вопрос, а как создавать и обрабатывать сообщения собственные, не входящие в MapInfo.
Если Вы хотите, чтобы MapInfo сообщало Вашей клиентской программе, когда пользователь применяет инструментальную кнопку, создайте такую кнопку оператором Alter ButtonPad... Add. Определите кнопку в соответствии с именем метода для обработки (прим. Этот метод определен мной как MyEnvent в OLE объекте).
Пример :
KDMapInfoServer1.ExecuteCommandMapBasic('Alter ButtonPad ID 1 Add ToolButton calling ole
"MyEvent" ID 1 Icon 0 Cursor 0 DrawMode 34 uncheck',[]);
|
Заметьте, что инструментальные панели MapInfo скрыты, подобно остальной части интерфейса пользователя MapInfo. Пользователь не будет видеть новую кнопку. Вы можете добавить иконку, кнопку или другой видимый элемент управления к интерфейсу пользователя Вашей клиентской программы. Когда пользователь укажет на него мышкой, пошлите MapInfo оператор Run Menu Command ID , c индентификатором созданной кнопки чтобы активизировать этот инструмент.
KDMapInfoServer1.ExecuteCommandMapBasic('Run Menu Command ID 1',[]);
|
Примечание: Информацию по Alter Button Pad смотрите в документации.
Если Вы хотите, чтобы MapInfo сообщала Вашей клиентской программе, когда пользователь выбирает созданную Вами команду меню, определите такую кнопку оператором Alter Menu... Add с указанием имени OLE метода (см. выше).
Внутри метода (в данном случае в обработчике компонента MyEventChange) обработайте аргументы (Info), посланные MapInfo.
Обработка переданных данных
Когда пользователь использует команды или кнопки, MapInfo посылает Вашему OLE-методу строку, содержащую восемь элементов, разделенных запятыми. Например, строка, посланная MapInfo, может выглядеть так:
"MI:-73.5548,42.122,F,F,-72.867702,43.025,202," |
Содержание такой строки проще понять, если Вы уже знакомы с функцией MapBasic CommandInfo(). Когда Вы пишете приложения, Вы можете создать новые команды меню и кнопки, вызывающие MapBasic-процедуры. Внутри процедуры-обработчика вызовите функцию CommandInfo(), чтобы получить информацию. Например, следующее обращение к функции определяет, координату Х и У места на карте где пользователи применил инструмент.
var
X, Y : string;
begin
KDMapInfoServer1.ExecuteCommandMapBasic('Set CoordSys Layout Units "mm"',[]);
X := KDMapInfoServer1.Eval('CommandInfo(%S)',[CMD_INFO_X]).AsString;
Y := KDMapInfoServer1.Eval('CommandInfo(%S)',[ CMD_INFO_Y]).AsString;
ShowMessage('X= ' + X + ' Y = ' + Y);
|
Значения:
| Код для событий, связанных с меню | Код для событий, связанных с кнопкой |
| 1 | CMD_INFO_X |
| 2 | CMD_INFO_Y |
| 3 | CMD_INFO_SHIFT |
| 4 | CMD_INFO_CTRL |
| 5 | CMD_INFO_X2 |
| 6 | CMD_INFO_Y2 |
| 7 | CMD_INFO_TOOLBTN |
| 8 | CMD_INFO_MENUITEM |
Когда Вы создаете команду меню или кнопку, которая использует синтаксис вызова OLE, MapInfo создает строку, содержащую разделенные запятой все восемь возвращаемых CommandInfo() значений. Строка начинается с префикса "MI:", чтобы Ваш OLE-сервер мог определять, что обращение метода было сделано MapInfo.
Строка, которую MapInfo посылает Вашему методу, выглядит следующим образом:
"MI:" + CommandInfo(l) + "," + CommandInfo (2) + "," + CommandInfo(3) + "," + CommandInfo (4) + "," + CommandInfo(5) + "," + CommandInfo (6) + "," + CommandInfo (7) + "," + CommandInfo (8) |
Предположим, что Ваше приложение добавляет команду меню к локальному меню OLE-методу строку. Если команда меню имеет номер 101 , строка будут выглядеть следующим образом:
"Ml :,,,,,,, 101" |
В этом случае большинство элементов строки пусто, потому что функция CommandInfo( ) может возвращать только эту одну часть информации.
Теперь предположим что вы создаете кнопку которая позволяет пользователю выбирать линии на карте.Строка теперь примет вид -
"MI:-73.5548,42.122,F,F,-72.867702,43.025,202," |
Теперь строка включает несколько элементов:
Первые два элемента содержат х- и у координаты точки на которые пользователь указал мышкой Следующие два элемента сообщают, была ли нажата клавиша SHIFT или CTRL Предпоследнии два элемента содержат координаты точки где пользователь отпустил кнопку мышки. И последний - указывает номер идентификатора кнопки.Совет: Если Вы приписываете уникальный идентификатор каждой из Ваших кнопок, Вы можете сделать так, что все кнопки будут вызывать один и тот же метод. Ваш метод может определять, какая из кнопка вызвала его, используя седьмой аргумент в переданной строке.
Примечание 1: Описание констант MapInfo (Global.pas)
Примечание - данный файл был взят мной с Интернета. Хочу сразу сделать предупреждение - разработчики MapInfo заявляют что набор констант может быть подвергнут изменениям в следующих редакциях MapInfo.Данный набор констант адаптирован под пятую версию. К сожалению шестой версии у меня нет (может кто поделиться ;-) ) и соответственно нет возможности проверить изменился ли набор констант или нет.
Вот в принципе и все что нужно для работы с MapInfo в Delphi, дерзайте.
Delphi и Oracle - вопросы и ответы.
| Если "Microsoft" начнет выпускать автомобили - то: 1. Когда на дорогу нанесут новую разметку, вам придется покупать новую машину. 3. "Macintosh" тоже начнет выпускать автомобили, они будут заряжаться от солнца, будут в два раза быстрее, ими будет легче управлять, но ездить они будут только по 5% дорог. 4. Индикаторы топлива, температуры двигателя и масла будут объединены в один "главный машинный индикатор". 7. Во время аварии подушки безопасности, прежде чем сработают, спросят: "Are you sure?" |
Вопрос В статье "Создание серверов приложений с помощью Delphi 3" вы написали, что подключались к Personal Oracle с помощью BDE. Я очень прошу вас рассказать, как вы это сделали. Для меня пока это остается загадкой. Ответ Доступ к Регsоnа1 Oracle (как и к любой другой версии СУБД Oracle) осуществляется следующим образом. Сначала нужно запустить сервер (в случае Personal Oracle для Windows 95 это отдельное приложение, в случае Oracle для Windows NT - набор сервисов, обслуживающих конкретную базу данных) и настроить клиентскую часть Oracle. Для этого следует запустить утилиту SQLNet Easy Configuration (в случае Oracle 8 - Oracle Net8 Easy Config) и с ее помощью создать описание псевдонима базы данных Oracle (для него, как и в BDE, используется термин alias, но это не то же самое, что псевдоним BDE). При создании этого описания важны три параметра. Первый из них - сетевой протокол, с помощью которого осуществляется доступ к серверу Oracle (IPX/SPX, TCP/IP и др.). Второй параметр - местоположение сервера в сети. В случае Personal Oracle это обычно компьютер с IP-адресом 127.0.0.1 (это специальный адрес для доступа к локальному компьютеру, так называемый TCP Loopback Address, который обычно имеет URL http://localhost/). Третий параметр - имя базы данных. По умолчанию в случае Personal Oracle она называется ORCL. В общем случае имя может быть любым, но это должно быть имя уже существующей базы данных, с которой вы собираетесь работать. В принципе все описания псевдонимов Oracle хранятся в текстовом файле TNSNAMES.ORA, который можно редактировать вручную. Далее следует запустить утилиту SQL Plus и проверить соединение клиента с сервером. Обычно в качестве имени пользователя используется имя SYSTEM и пароль MANAGER (если вы сами администрируете сервер). Если же сервер был установлен раньше, узнайте у администратора базы данных, каким именем и паролем следует воспользоваться. Помимо имени пользователя и пароля, SQL Plus запросит так называемую строку связи, в которой должно содержаться имя сервиса, который был создан вами перед этим. При удачном соединении в SQL Plus появится соответствующее сообщение. Отметим, что утилита Oracle Net8 Easy Config позволяет протестировать соединение непосредственно в процессе создания описания сервиса. Если соединение с сервером было неудачным, стоит проверить, поддерживается ли указанный сетевой протокол, виден ли в сети компьютер, на котором установлен сервер, и, если нужно, внести изменения в описание сервиса. Теперь можно, наконец, заняться настройкой BDE. В качестве Server Name следует указать имя псевдонима Oracle (его можно просто выбрать из выпадающего списка, так как BDE Administrator также обращается к файлу TNSNAMES.ORA). После этого нужно проверить соединение с сервером через BDE с помощью BDE Administrator или SQL Explorer. Если соединение не устанавливается и появляется сообщение "Vendor initialization failed", стоит убедиться, что динамическая загружаемая библиотека, указанная в параметре Vendor Init драйвера Oracle, действительно присутствует на данном компьютере. На всякий случай стоит скопировать ее в папку Windows\System, так как некоторые ранние версии BDE в Windows 95 не находят эту библиотеку в подкаталоге Bin каталога, в котором установлен клиент Oracle, в силу ограничений, налагаемых этой операционной системой на длину переменной окружения PATH. Отметим также, что при использовании Oracle 8 нужно использовать версию не ниже 8.0.4; в случае использования более ранней версии следует обновить ее до 8.0.4.
Вопрос Недавно я перешел на использование Oracle, но все мои попытки использовать компонент TStoredProc оказываются неудачными. Почему? Ответ Причины неработоспособности компонента TStoredProc могут быть следующими. Во-первых, при использовании ODBC-доступа может оказаться, что применяемый вами ODBC-драйвер не поддерживает хранимые процедуры (как известно, не все ODBC-драйверы их поддерживают). Во-вторых, имеется известная проблема, описание которой содержится в разделе Developers Support корпоративного сайта Inprise (http://www.inprise.com). Дело в том, что число параметров хранимой процедуры, с которой взаимодействует компонент TStoredProc, не должно превышать 10. В случае, если реальное число параметров превышает 10, многие разработчики переписывают хранимые процедуры так, чтобы они использовали строковые параметры, содержащие по несколько реальных параметров.
Вопрос Дает ли Delphi возможность корректно прервать выполнение SQL-запроса к серверу Oracle с помощью BDE? Например, чтобы при использовании с SQL Plus после отправки SQL-запроса на выполнение на экране появлялось окно с кнопкой Cancel, которое давало бы возможность в любой момент прервать выполнение этого запроса? Ответ Насколько мне известно, для этой цели лучше всего использовать функции Oracle Call Interface (низкоуровневый API Oracle). В комплекте поставки Oracle есть соответствующие примеры для C, и переписать их на Pascal несложно. Некоторые драйверы SQL Link позволяют прекратить выполнение запроса, если время его выполнения превышает заранее заданное значение (параметр MAX QUERY TIME соответствующего драйвера). Однако драйвер ORACLE, к сожалению, в их число не входит.
Вопрос Что необходимо предпринять, чтобы сгенерировать из ERwin таблицы для локальной базы данных Paradox 5.0? На компьютере установлены Delphi 4.0 и MetaBase. Ответ Для этого требуется установить ODBC-драйвер для этой версии Paradox той же разрядности, что и ERwin. Затем нужно описать соответствующий ODBC-источник, и он будет доступен в ERwin.
Вопрос Не могли бы вы подсказать, как заблокировать функцию вставки записи непосредственно в компоненте TDBGrid с сохранением всех остальных возможностей редактирования таблицы. Ответ Наиболее разумным представляется создать обработчик события OnBeforeInsert компонента TTable, TQuery или TClientDataSet, данные из которых отображаются в TDBGrid. Сам компонент TDBGrid не имеет подходящего события для обработки, так как это компонент, предназначенный только для создания пользовательского интерфейса, а в данном случае следует, по существу, запретить добавление записей в таблицу.
Вопрос В мой комплект Borland C++ Builder не входит Visual Query Builder. Могу ли я связать две таблицы без него? Ответ Безусловно, две таблицы можно связать и без VQB. Самый простой способ - запустить Database Form Wizard и связать две таблицы, используя TQuery. Те два запроса, которые при этом получатся (один из них - параметризованный), можно использовать как образец. Кроме того, можно просто написать вручную необходимый запрос к любому числу таблиц и поместить его в свойство SQL компонента TQuery. Все инструменты для генерации запросов (Visual Query Builder, SQL Builder и др.) просто предоставляют для этого визуальные средства, а результатом их работы является именно текст запроса, помещаемый в это свойство.
Вопрос
Я установил Borland C++ Builder 3.0 Client/Server Suite и InterBase Server 5.1.1. (автоматически с ним установился InterBase 5.x Driver by Visigenic). Но у меня не работают хранимые процедуры. Например: процедура правильно откомпилирована, и вызов ее из C++ Builder осуществляется с помощью выполнения оператора.
StoredProc1->ExecProc();
При этом возникает следующая ошибка :
"Capability not supported. General SQL error. [Visigenic]
[ODBC InterBase 4.x Driver] Driver not capable".
Ответ
ODBC-драйвер может не поддерживать хранимые процедуры. В этом случае стоит попытаться использовать драйвер SQL Link (он должен быть в C++ Builder 3.0 Client/Server Suite). Для этого нужно создать для вашей базы данных псевдоним типа INTRBASE. В этом случае хранимые процедуры должны работать.
Если хранимые процедуры, тем не менее, остаются недоступными, стоит проверить, что и в какой последовательности было установлено на ваш компьютер. Такие неприятности могут возникать, если, например, вы установили какой-либо продукт, написанный на Delphi 2, после C++Builder 3. В этом случае можно переустановить BDE, взяв его последнюю версию на сайте Inprise (http://www.inprise.com — все зарегистрированные пользователи C++ Builder 3.0 Client/Server Suite имеют право это сделать.
Вопрос При удалении записей из таблицы dBase с помощью компонента TTable они просто приобретают признак удаления, и я никак не могу добиться их физического удаления. Как быть? Ответ Ваша проблема решается просто - для физического удаления записей нужно использовать функцию DbiPackTable (ее описание есть в справочном файле BDE).
Вопрос При добавлении новых записей с помощью метода TTable.AppendRecord в индексированную таблицу FoxPro через какое-то время (то есть при одновременном добавлении большого количества записей) возникает ошибка: "Access to table disabled because of previous error. Read failure. File ". Ответ Возможно, причина заключается в том, что операции чтения-записи в файл, содержащий таблицу FoxPro, особенно при использовании кэширования, предоставляемого операционной системой, конфликтуют с содержимым индексного файла (это часто происходит при многопользовательской работе). Дело в том, что ранние версии dBase, FoxPro, Clipper работали с неподдерживаемыми индексами, то есть индексные файлы не обновлялись одновременно с таблицей, и для их синхронизации требовалось выполнять специальные операции. Но соответствующие средства разработки, применявшиеся в то время, обычно не поддерживали никаких аналогов транзакций - записи чаще всего вставлялись по одной. В случае применения старых версий формата FoxPro следует избегать кэширования при выполнении дисковых операций с файловым сервером, содержащим базу данных FoxPro. Кроме того, следует проверить и, если необходимо, изменить в настройках BDE параметры MINBUFSIZE, MAXBUFSIZE, LOCAL SHARE - возможно, проблема заключается в недостаточной величине буферов BDE для кэширования данных или в одновременном доступе к данным приложений, использующих и не использующих BDE. Еще одним из способов решения этой проблемы (самым радикальным) является замена FoxPro на какую-нибудь из серверных СУБД. Например, InterBase неплохо справляется с одновременным вводом большого количества записей благодаря некоторым специфическим особенностям архитектуры этого сервера.
Вопрос Позволяет ли QuickReport выгружать данные в формате Microsoft Excel? Ответ Quick Report не позволяет выгружать данные в формате Microsoft Excel. Но последние его версии позволяют сохранять отчеты в формате CSV (Comma Separated Value) и HTML, и оба эти формата можно прочесть с помощью Excel. Помимо этого, для генерации отчета можно использовать автоматизацию Excel (Automation, ранее назвалось OLE Automation. — Прим. ред.), вообще не прибегая к использованию QuickReport.
Вопрос Как можно создать свою форму просмотра отчетов QuickReport в С++Builder? Ответ Для создания собственных окон просмотра отчета можно использовать компонент TQRPreview. Для этой цели следует создать форму (назовем ее PreviewForm), поместить на нее компонент TQRPreview, сослаться на нее в форме, содержащей отчет, и в форме, из которой вызывается просмотр отчета. Код для показа отчета выгладит примерно так: Далее создадим обработчик события OnPreview компонента TQuickRep: После этого данный отчет будет появляться не в стандартном окне просмотра, а в форме PreviewForm.
Вопрос Возможно ли использование компонентов Decision Support System при генерации отчетов в QuickReport и, если да, то каким образом? Если QuickReport не подходит для этих целей, то какие другие варианты вы можете посоветовать? Ответ Самый простой способ - использовать компоненты TQRLabel, текст в которых динамически меняется во время печати (то есть способ, которым можно напечатать все, что угодно, написав при этом немного кода). В принципе можно записать двухмерное сечение куба во временную таблицу или в компонент TClientDataSet, написав соответствующий цикл, и сделать отчет на ее основе. Использование DecisionQuery в качестве источника данных для отчета также вполне возможно. Другие возможные варианты - это использование автоматизации Word или Excel либо вычисление сумм внутри отчета. Можно также использовать другие генераторы отчетов - например, с помощью Crystal Reports можно создавать отчеты, содержащие кросс-таблицы.
Вопрос Как корректно подключить Crystal Reports к Delphi? Ответ В составе Crystal Reports Professional имеется VCL-компонент для Delphi, компонент ActiveX, модуль CRPE32.PAS, в котором объявлены все функции и структуры Print Engine API, и описание опубликованных методов Crystal Reports как сервера автоматизации. Соответственно есть следующие возможности подключения Crystal Reports к Delphi: Использование функций Report Engine API из библиотеки CRPE32 DLL. В этом случае следует добавить в проект модуль CRPE32.PAS и сослаться на этот модуль в предложении uses. Ниже приведен пример соответствующего кода: Следует помнить, что строковые параметры, передаваемые в функции Print Engine API, представляют собой тип данных PChar, а не стандартные строки, используемые в Pascal, поэтому для передачи таких параметров, как, например, имя отчета, следует осуществить преобразование типов с помощью функции StrPCopy. Для успешной компиляции подобных приложений файл CRPE32.PAS должен находиться в том же каталоге, что и разрабатываемое приложение, либо в каталоге Delphi\Lib. Использование VCL-компонента из комплекта поставки (для этого следует установить его в палитру компонентов Delphi). Естественно, этот компонент инкапсулирует те же самые функции Print Engine API. Существуют также аналогичные компоненты третьих фирм (например, компонент от SupraSoft Ltd., http://www.suprasoft.com). Использование компонента ActiveX фирмы Crystal Reports. Этот компонент ActiveX может быть установлен в палитру компонентов Delphi. Он обладает набором свойств и методов, более или менее сходным с соответствующим VCL-компонентом из комплекта поставки Crystal Reports Professional. Использование Crystal Reports как сервера автоматизации. В справочной системе Crystal Reports имеется подробное описание иерархии вложенных объектов и их методов (и внушительный набор примеров для Visual Basic, аналоги которых несложно создать и на языке Pascal). Ниже приведен пример соответствующего кода: Можно также сделать отчет в виде исполняемого файла и вызвать его из приложения. Но в этом случае передать параметры в отчет не удастся.
Delphi и системная информация о ресурсах компьютера.

| Компьютеры - вещь слишком сложная, чтобы работать в принципе. Поэтому то, что они работают хоть как-то, уже чудо. |
Разработаем программу, показывающую нам некоторую системную информацию о компьютере. В частности, хотелось бы получить информацию о версии ОС, ее директориях, свойствах экрана, ресурсах памяти, имени пользователя и компьютера, дате BIOS. Помимо этого, разрешим пользователю изменять настройки клавиатуры, встроенного динамика и хранителя экрана.
Процесс визуального проектирования описывать не будем; рассмотрим лишь страницу «Параметры». Для удобства управления параметрами клавиатуры положим на нее две компоненты TTrackBar. Изменим свойство Name на tbKeyboardDelay и tbKeyboardSpeed. Изменим свойство PageSize на 1. Для tbKeyboardDelay установим Max=3 и для tbKeyboardSpeed. Max=31. Для управления свойствами хранителя экрана используем TCheckBox (свойство Name сменим на cbScreenSaverActive, Caption на &‘Хранитель экрана&’) и TMaskEdit (свойство Name=&’edSSTimeOut&’ и EditMask=&’!999;1;&’). Аналогично добавим TCheckBox (свойство Name=&’cbSpeaker&’, Caption=&’Использование встроенного динамика&’ ).
Рассмотрим текст программы. В список включаемых модулей uses добавим registry. Добавим описание процедур в раздел public описания TfmMain.
type
TfmMain = class(TForm)
...
procedure FormCreate(Sender: TObject);
procedure Change(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
KeyboardDelay,
KeyboardSpeed,
ScreenSaveTimeOut : integer;
procedure ParametersInfo;
procedure ShowSomeInfo;
procedure BIOSInfo(OS : string);
procedure HardwareInfo;
procedure MemoryInfo;
procedure VideoInfo;
procedure OSInfo;
end;
var fmMain: TfmMain;
implementation
uses Registry;
{$R *.DFM}
|
Сначала получим информацию о компьютере. Используем функцию GetComputerName для получения имени компьютера, функцию GetUserName для получения имени пользователя и функцию GetSystemInfo для получения информации о процессоре (наиболее полно данная функция реализована в Windows NT, где она возвращает и кол-во процессоров и их тип и т.д.).
// Информация о компьютере. procedure TfmMain.HardwareInfo; var Size : cardinal; PRes : PChar; BRes : boolean; lpSystemInfo : TSystemInfo; begin // Имя компьютера Size := MAX_COMPUTERNAME_LENGTH + 1; PRes := StrAlloc(Size); BRes := GetComputerName(PRes, Size); if BRes then laCompName_.Caption := StrPas(PRes); // Имя пользователя Size := MAX_COMPUTERNAME_LENGTH + 1; PRes := StrAlloc(Size); BRes := GetUserName(PRes, Size); if BRes then laUserName_.Caption := StrPas(PRes); // Процессор GetSystemInfo(lpSystemInfo); laCPU_.Caption := 'класса x' + IntToStr (lpSystemInfo.dwProcessorType); end; |
Перейдем к параметрам экрану. Здесь мы будем использовать и Win32 API функции и стандартные объекты VCL. Так для получения разрешения экрана нам понадобится объект TScreen (его свойства Width и Height). Остальные параметры мы получим через контекст драйвера устройства DC используя функцию GetDeviceCaps.
// Информация о видеосистеме.
procedure TfmMain.VideoInfo;
var DC : hDC;
c : string;
begin
// Разрешение экрана
laWidth_.Caption := IntToStr(Screen.Height);
laHeight_.Caption := IntToStr(Screen.Width);
// Информация о глубине цвета.
DC := CreateDC('DISPLAY',nil,nil,nil);
laBitsPerPixel_.Caption :=
IntToStr(GetDeviceCaps(DC,BITSPIXEL));
laPlanes_.Caption :=
IntToStr(GetDeviceCaps(DC,PLANES));
case GetDeviceCaps(DC,BITSPIXEL) of
8 : c := '256 цветов';
15 : c := 'Hi-Color / 32768 цветов';
16 : c := 'Hi-Color / 65536 цветов';
24 : c := 'True-Color / 16 млн цветов';
32 : c := 'True-Color / 32 бит';
end;
laColors_.Caption := c;
DeleteDC(DC);
end;
|
Также будет интересна информация о памяти. Здесь нам поможет функция GlobalMemoryStatus, возвращающая информацию по объему физической и виртуальной памяти.
// Информация о памяти.
procedure TfmMain.MemoryInfo;
var lpMemoryStatus : TMemoryStatus;
begin
lpMemoryStatus.dwLength := SizeOf(lpMemoryStatus);
GlobalMemoryStatus(lpMemoryStatus);
with lpMemoryStatus do begin
laFreeMemory.Caption :=
laFreeMemory.Caption +
IntToStr(dwMemoryLoad) + '%';
laRAM_.Caption := Format('%0.0f Мбайт',
[dwTotalPhys div 1024 / 1024]);
laFreeRAM_.Caption := Format('%0.3f Мбайт',
[dwAvailPhys div 1024 / 1024]);
laPF_.Caption := Format('%0.0f Мбайт',
[dwTotalPageFile div 1024 / 1024]);
laPFFree_.Caption := Format('%0.0f Мбайт',
[dwAvailPageFile div 1024 / 1024]);
end;
end;
|
Узнаем информацию о ОС. Функция GetWindowsDirectory вернет путь к каталогу, где установлена система, функция GetSystemDirectory - к системному каталогу. Для определения версии ОС воспользуемся функцией GetVersionEx.
// Информация о Windows.
procedure TfmMain.OSInfo;
var PRes : PChar;
Res : word;
BRes : boolean;
lpVersionInformation : TOSVersionInfo;
c : string;
begin
// Каталог, где установлена Windows
PRes := StrAlloc(255);
Res := GetWindowsDirectory(PRes, 255);
if Res > 0 then laWinDir_.Caption :=
StrPas(PRes);
// Системный каталог Windows
Res := GetSystemDirectory(PRes, 255);
if Res > 0 then laSysDir_.Caption :=
StrPas(PRes);
// Имя ОС
lpVersionInformation.dwOSVersionInfoSize :=
SizeOf(TOSVersionInfo);
BRes := GetVersionEx(lpVersionInformation);
if BRes then
with lpVersionInformation do case dwPlatformId of
VER_PLATFORM_WIN32_WINDOWS :
if dwMinorVersion=0 then c := 'Windows 95'
else c := 'Windows 98';
VER_PLATFORM_WIN32_NT : c := 'Windows NT';
VER_PLATFORM_WIN32s : c := 'Win 3.1 with Win32s'
end;
laVersion_.Caption := c;
// Дата создания BIOS-а
if c='Windows NT' then BIOSInfo('NT') else BIOSInfo('95');
end;
|
В предыдущем отрывке программы внимательный читатель заметил вызов функции BIOSInfo с параметром, характеризующем текущую ОС. Опишем эту функцию. Важно отметить, что способ получения информации о дате BIOS различен. Для NT получим информацию из реестра, а для Windows 95/98 из соответствующего участка памяти. Эти два способа взаимоисключаемы, так как у Windows 95/98 нет соответствующего раздела реестра, а прямой доступ к памяти в NT невозможен.
// Информация о дате создания BIOS-а.
procedure TfmMain.BIOSInfo(OS : string);
var p : pointer;
s : string[255];
begin
if OS='NT' then begin with TRegistry.Create do
try RootKey := HKEY_LOCAL_MACHINE;
if OpenKeyReadOnly
('HARDWARE\DESCRIPTION\System')
then laBIOSDate_.Caption :=
ReadString('SystemBiosDate')
finally Free;
end;
end
else try
s[0] := #8;
p := Pointer($0FFFF5);
Move(p^,s[1],8);
laBIOSDate_.Caption :=
copy(s,1,2) + '/' + copy(s,4,2) + '/' +copy (s,7,2);
except laBIOSDate_.Caption := 'XX.XX.XXXX';
end;
end;
|
Рассмотрим функцию SystemParametersInfo, которая позволяет управлять некоторыми настройками системы. Область применения данной функции для NT и Windows 95/98 различна. Умышленно выберем некоторую общую часть для обеих систем.
// Информация о параметрах procedure TfmMain.ParametersInfo; var Bl : boolean; begin // Разрешен ли PC Speaker SystemParametersInfo(SPI_GETBEEP,0,@Bl,0); cbSpeaker.Checked := Bl; // Активен ли хранитель экрана SystemParametersInfo (SPI_GETSCREENSAVEACTIVE,0,@Bl,0); cbScreenSaverActive.Checked := Bl; // Интервал вызова хранителя экрана SystemParametersInfo (SPI_GETSCREENSAVETIMEOUT,0, @ScreenSaveTimeOut,0); // Настройки клавиатуры SystemParametersInfo (SPI_GETKEYBOARDDELAY,0, @KeyboardDelay,0); SystemParametersInfo (SPI_GETKEYBOARDSPEED,0, @KeyboardSpeed,0); end; // Отображение настроек procedure TfmMain.ShowSomeInfo; begin tbKeyboardDelay.Position := 3 - KeyboardDelay; tbKeyboardSpeed.Position := KeyboardSpeed; edSStimeOut.EditMask := IntToStr (ScreenSaveTimeOut div 60); end; |
Также позволим пользователю изменять и сохранять настройки системы по своему вкусу. Здесь также будем использовать функцию SystemParametersInfo. Для компонентов tbKeyboardSpeed, tbKeyboardDelay, cbScreenSaverActive, cbSpeaker, edSSTimeOut в ObjectInspector перейдем на закладку Events и изменим событие OnChange (для tbKeyboardSpeed, tbKeyboardDelay) , OnClick (для cbScreenSaverActive, cbSpeaker) и OnExit для edSSTimeOut на Change. Таким образом, все пять вышеперечисленных компонент после изменений состояний передадут управление нижеприведенной процедуре.
// Сохранение изменений параметров системы procedure TfmMain.Change(Sender: TObject); var Sen : TComponent; begin Sen := Sender as TComponent; // Вкл/Выкл PC Speaker-а. if (Sen.Name='cbSpeaker') and cbSpeaker.Checked then SystemParametersInfo (SPI_SETBEEP,1,nil,SPIF_UPDATEINIFILE) else SystemParametersInfo (SPI_SETBEEP,0,nil,SPIF_UPDATEINIFILE); // Вкл/Выкл активности хранителя экрана. if (Sen.Name='cbScreenSaver') and cbScreenSaverActive.Checked then SystemParametersInfo (SPI_SETSCREENSAVEACTIVE,1,nil,SPIF_UPDATEINIFILE) else SystemParametersInfo (SPI_SETSCREENSAVEACTIVE,0,nil,SPIF_UPDATEINIFILE); // Изменение значения задержки перед повтором с клавиатуры if (Sen.Name='tbKeyboardDelay') then SystemParametersInfo( SPI_SETKEYBOARDDELAY,3-tbKeyboardDelay.Position,nil, SPIF_SENDWININICHANGE); // Изменение значения скорости ввода с клавиатуры if (Sen.Name='tbKeyboardSpeed') then SystemParametersInfo( SPI_SETKEYBOARDSPEED,tbKeyboardSpeed.Position,nil, SPIF_SENDWININICHANGE); // Изменение интервала запуска хранителя экрана if (Sen.Name='edSSTimeOut') then SystemParametersInfo( SPI_SETSCREENSAVETIMEOUT,StrToInt(edSSTimeOut.Text) *60,nil,SPIF_UPDATEINIFILE); end; |
И ,наконец, вызовем все эти процедуры при создании формы.
// Вызов информационных процедур при создании формы. procedure TfmMain.FormCreate(Sender: TObject); begin HardwareInfo; MemoryInfo; VideoInfo; ParametersInfo; ShowSomeInfo; OSInfo; end; |
Использование Delphi совместно c фунциями Microsoft Win32 API позволит программисту создать более функционально богатые и гибкие приложения.
Delphi и WordBasic.
| - Помогите! У меня Word при печати вместо букв квадратики выводит! Help! Help! Что мне делать? Посоветуйте! - А ты в квадратики нужные буквы вписывай... |
unit oleword;
// Необходима форма и компоненты Memo и Edit на ней.
// Написано для MSWord 8.
// Также необходимо создать тестовый документ d:\test.doc
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
OLEAuto, ShellAPI, StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
Edit1: TEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
MSWord: Variant;
// WordVersion: Byte;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
Test, Test1: Integer;
AString: Variant;
begin
MSWord := CreateOLEObject('Word.Application'); //Word 8
MSWord.Documents.Open(FileName := 'd:\test.doc', ReadOnly := True);
MSWord.Visible := 1; //Закомментарьте, если вы нехотите показывать файл;
Test := MSWord.FontNames.Count;
for Test1 := 1 to Test do
begin
AString := MSWord.FontNames.Item(Test1);
Memo1.Lines.Add(AString);
end;
MSWord.ActiveDocument.Range(Start := 0,
end := 0);
MSWord.ActiveDocument.Range.InsertAfter(Text := 'Заголовок');
MSWord.ActiveDocument.Range.InsertParagraphAfter;
MSWord.ActiveDocument.Range.Font.Name := 'Arial';
MSWord.ActiveDocument.Range.Font.Size := 24;
AString := MSWord.ActiveDocument.Range.Font.Name;
Edit1.Text := AString;
end;
end.
|
Delphi и OLE Automation с Word.
| In the beginning was the Word, and the Word was 1.0... |
При использовании данного метода имена функций и типы параметров решаются во время выполнения программы, все параметры определены вариантным типом.
Поскольку во время компиляции невозможно определить соответствия имен функций и типов параметров, данный метод чреват ошибками.
Так как имена функций и типы параметров должны проверяться во время выполнения программы, данный метод выполняется медленно.
Единственное преимущество данного метода при программировании в Delphi заключается в том, что отпадает необходимость передачи всех параметров вызываемой функции.
Раннее связывание (Использование библиотеки типов/интерфейсов).При использовании данного метода имена функций и типы параметров полностью решаются во время компиляции.
Библиотека типов должна импортироваться в Delphi. Библиотека типов является языковым нейтральным описанием всех объектов и функций, поддерживаемых сервером. (Это подобно файлу заголовка языка C).
При вызове функции должны обязательно присутствовать все параметры, даже те, которые в документации указаны как дополнительные (необязательные). Это позволяет обнаружить и исправить множество ошибок еще до запуска программы.
Скорость выполнения значительно быстрее, чем при использовании позднего связывания.
Из-за преимуществ второго метода остальная часть документа демонстрирует принципы создания приложений с ранним связыванием. Все приложения, использующие Excel автоматизацию, должны пользоваться последним методом, если нет причин для первого.
Подготовка библиотеки типов.Модуль Pascal должен быть создан на основе файла библиотеки типов.
Выберите пункт меню Project|Import Type Library Нажмите кнопку Add и выберите следующий файл c:\program files\microsoft office\office\msword8.olb Нажмите OK.К сожалению, данный модуль с проектом явно не компилируется, хотя и включается в него, вероятно из-за того, что приложение считает данный модуль нечто вроде текстового приложения.
Наиболее простой путь заключается в следующем: удалите модуль excel_tlb из проекта и только после этого добавьте его в список используемых модулей.
Документация.Справочный файл c:\program files\microsoft office\office\vbawrd8.hlp содержит информацию о доступных объектах Word.
"Записыватель" макросов позволяет быстро создавать VBA-код. После этого он довольно может легко быть портирован в Delphi.
Пример автоматизации.Следующий пример использует класс-оболочку Delphi, инкапсулирующий прямые вызовы объектов Word. Вот преимущество этого метода:
Обеспечение скрытия параметров. Возможность использования для многих методов параметров по умолчанию. Многие методы Word также работают с вариантными параметрами. Это означает невозможность использования констант - скрытие параметров решает данную проблему. Обеспечение проверки типа. Многие методы определены с параметрами OLEVariant, обеспечивая внешнюю совместимость. Следующий класс-оболочка демонстрирует ключевые методы автоматизации Word. Полностью класс приведен в Приложении 1.
unit doc;
interface
uses
windows, sysutils, Word_TLB;
type
TWinWord = class
private
App: _Application;
public
constructor Create;
destructor Destroy; override;
procedure NewDoc(Template: string);
procedure GotoBookmark(Bookmark: string);
procedure InsertText(Text: string);
procedure SaveAs(Filename: string);
end;
//------------------------------------------------------------------
implementation
//------------------------------------------------------------------
constructor TWinWord.Create;
begin
App := CoApplication.Create;
end;
//------------------------------------------------------------------
destructor TWinWord.Destroy;
var
SaveChanges: OLEVariant;
OriginalFormat: OLEVariant;
RouteDocument: OLEVariant;
begin
SaveChanges := wdDoNotSaveChanges;
OriginalFormat := unAssigned;
RouteDocument := unAssigned;
app.Quit(SaveChanges, OriginalFormat, RouteDocument);
inherited destroy;
end;
//------------------------------------------------------------------
procedure TWinWord.GotoBookmark(Bookmark: string);
var
What: OLEVariant;
Which: OLEVariant;
Count: OLEVariant;
Name: OLEVariant;
begin
What := wdGoToBookmark;
Which := unAssigned;
Count := unAssigned;
Name := Bookmark;
App.Selection.GoTo_(What, Which, Count, Name);
end;
//------------------------------------------------------------------
procedure TWinWord.InsertText(Text: string);
begin
App.Selection.TypeText(Text);
end;
//------------------------------------------------------------------
procedure TWinWord.NewDoc(Template: string);
var
DocTemplate: OleVariant;
NewTemplate: OleVariant;
begin
DocTemplate := Template;
NewTemplate := False;
App.Documents.Add(DocTemplate, NewTemplate);
end;
//------------------------------------------------------------------
procedure TWinWord.SaveAs(Filename: string);
begin
OLEVariant(App).ActiveDocument.SaveAs(FileName);
end;
//------------------------------------------------------------------
end.
|
Чтобы создать класс:
Добавьте модуль библиотеки типов в список используемых модулей.
uses windows, sysutils, Word_TLB; |
Создадим определение класса:
TWinWord = class Private App : _Application; public procedure NewDoc(Template : String); procedure GotoBookmark(Bookmark : String); procedure InsertText(Text : String); procedure SaveAs(Filename : string); constructor Create; destructor Destroy; override; end; |
Переменная App является ссылкой на приложение Word. Это допускает вызов методов Word с применением технологии раннего связывания.
Опубликованные (public) процедуры - процедуры, которые могут быть использованы при работе с классом.
Создадим конструктор.
constructor TWinWord.Create; begin App := CoApplication.Create; end; |
Он вызывается при создании класса TWinWord. CoApplication.create создает новый экземпляр Word и возвращает ссылку на интерфейс Application. Это позволяет вызывать методы объекта app.
Реализация дектруктора.
destructor TWinWord.Destroy; var SaveChanges : OLEVariant; OriginalFormat : OLEVariant; RouteDocument : OLEVariant; begin SaveChanges := wdDoNotSaveChanges; OriginalFormat := unAssigned; RouteDocument := unAssigned; app.Quit(SaveChanges, OriginalFormat, RouteDocument); inherited destroy; end; |
Деструктор должен вызываться В ОБЯЗАТЕЛЬНОМ ПОРЯДКЕ. Метод Quit объекта приложения закрывает Word и распределяет всю связанную с ним память. Так как параметры метода Quit определены как вариантный тип OLEVariant, вся свазанная с ними память распределяется именно для этого типа переменных.
Реализуем метод NewDoc. Этот метод создаст новый текстовый документ на основе заданного шаблона.
procedure TWinWord.NewDoc(Template : String); var DocTemplate : OleVariant; NewTemplate : OleVariant; begin DocTemplate := Template; NewTemplate := False; App.Documents.Add(DocTemplate, NewTemplate); end; |
Данный метод осуществляет более строгую проверку типов, чем это осуществляет сам Word. Параметр Template должен содержать строку с именем шаблона. Метод Word Add в качестве параметров может содержать значения любого типа. Лучшая пример этого - метод MoveRight, реализация которого показана в Приложении 1.
Ниже показана реализация метода SaveAs. Данный метод позволит сохранить в файле текущий документ.
procedure TWinWord.SaveAs(Filename : string); begin OLEVariant(App).ActiveDocument.SaveAs(FileName); end; |
Следующий код демонстрирует использование данного класса для создания, редактирования, печати и сохранения документа, созданного на основе шаблона:
Word := TWinWord.create;
try
Word.visible := true;
Word.NewDoc('c:\delphi\word\sample\Demo');
Word.GotoBookmark('From');
Word.InsertText('Иван Иваныч');
Word.GotoBookmark('Dept');
Word.InsertText('Разработка');
Word.GotoBookmark('Phone');
Word.InsertText('111111');
Word.GotoBookmark('Now');
Word.InsertText(FormatDateTime('d-mmm-yyyy', now));
//SF элементы
Word.GotoBookmark('Items');
Word.InsertText('112021');
Word.MoveRight(1);
Word.InsertText('PVCS');
Word.MoveRight(1);
Word.InsertText('1');
Word.MoveRight(1);
Word.InsertText('£ 305.99');
Word.MoveRight(1);
Word.InsertText('£ 305.99');
Word.MoveRight(1);
Word.UpdateFields;
Word.RunMacro('Demo');
Word.Print;
Word.SaveAs(filename);
finally
Word.Free;
end;
|
Полный исходный код класса tWinWord приведен ниже. Он включает реализацию всех методов:
unit doc;
interface
uses
Word_TLB, windows, sysutils;
type
TWinWord = class
private
App: _Application;
function fGetVisible: boolean;
procedure fSetVisible(visible: boolean);
public
procedure NewDoc(Template: string);
procedure GotoBookmark(Bookmark: string);
procedure InsertText(Text: string);
procedure MoveRight(Count: integer);
procedure Print;
procedure UpdateFields;
procedure SaveAs(Filename: string);
procedure RunMacro(MacroName: string);
constructor Create;
destructor Destroy; override;
property visible: boolean
read fGetVisible
write fSetVisible;
end;
implementation
//------------------------------------------------------------------
constructor TWinWord.Create;
begin
App := CoApplication.Create;
end;
//------------------------------------------------------------------
destructor TWinWord.Destroy;
var
SaveChanges: OLEVariant;
OriginalFormat: OLEVariant;
RouteDocument: OLEVariant;
begin
SaveChanges := wdDoNotSaveChanges;
OriginalFormat := unAssigned;
RouteDocument := unAssigned;
app.Quit(SaveChanges, OriginalFormat, RouteDocument);
inherited destroy;
end;
//------------------------------------------------------------------
function TWinWord.fGetVisible: boolean;
begin
result := App.Visible;
end;
//------------------------------------------------------------------
procedure TWinWord.fSetVisible(Visible: boolean);
begin
App.visible := Visible;
end;
//------------------------------------------------------------------
procedure TWinWord.GotoBookmark(Bookmark: string);
var
What: OLEVariant;
Which: OLEVariant;
Count: OLEVariant;
Name: OLEVariant;
begin
What := wdGoToBookmark;
Which := unAssigned;
Count := unAssigned;
Name := Bookmark;
App.Selection.GoTo_(What, Which, Count, Name);
end;
//------------------------------------------------------------------
procedure TWinWord.InsertText(Text: string);
begin
App.Selection.TypeText(Text);
end;
//------------------------------------------------------------------
procedure TWinWord.NewDoc(Template: string);
var
DocTemplate: OleVariant;
NewTemplate: OleVariant;
begin
DocTemplate := Template;
NewTemplate := False;
App.Documents.Add(DocTemplate, NewTemplate);
end;
//------------------------------------------------------------------
procedure TWinWord.MoveRight(Count: integer);
var
MoveUnit: OleVariant;
vCount: OleVariant;
Extended: OleVariant;
begin
MoveUnit := wdCell;
vCount := Count;
Extended := unassigned;
app.selection.MoveRight(MoveUnit, vCount, Extended);
end;
//------------------------------------------------------------------
procedure TWinWord.Print;
begin
OLEVariant(app).Printout;
end;
//------------------------------------------------------------------
procedure TWinWord.UpdateFields;
begin
App.ActiveDocument.Fields.Update;
end;
//------------------------------------------------------------------
procedure TWinWord.SaveAs(Filename: string);
begin
OLEVariant(App).ActiveDocument.SaveAs(FileName);
end;
//------------------------------------------------------------------
procedure TWinWord.RunMacro(MacroName: string);
begin
App.Run(MacroName);
end;
//------------------------------------------------------------------
end.
|
Как научить приложение Delphi разговаривать.
Автор: Alec Bergamini11-го августа 2001 Microsoft объявила о выпуске SAPI 5.1 SDK. Данный продукт можно использовать в любом языке, который поддерживает OLE автоматизацию.
В данной статье я постараюсь раасказать, как установить SAPI 5.1 SDK. Затем мы посмотрим, как использовать SDK в приложении Delphi для преобразования текста в синтезированную речь. Синтезированная речь будет проигрываться через спикер. Всё это тестировалось в Delphi 5 и 6.
Чтобы скачать SAPI 5.1, необходимо зайти на сайт Microsoft’s Speech.net Technologies по адресу http://www.microsoft.com/speech/ и кликнуть по ссылке download. Далее будет предложено прочитать комментарии к данному продукту. Если в Вашей системе, язык по умолчанию отличается от US English, то настоятельно рекомендую прочитать эти комментарии до конца.
Если Вы используете beta версию операционной системы XP, то у Вас могут возникнуть некоторые проблемы. Проблемы связаны с тем, что большинство beta версий XP включают в себя ранние версии SAPI 5.1. Поэтому не пытайтесь инсталировать release версию SAPI 5.1 на XP, она не будет работать.
После того как Вы прочитаете комментарии, то приступайте к скачиванию Speech SDK 5.1. Всё что для этого потребуется, это нажать на ссылку Speech SDK 5.1 (68 MB). В архиве содержится сам SDK, докумантация, а так же текты на английском для примера.
Итак, после скачивания SAPI 5.1 SDK, запустите speechsdk51.exe для установки его на Ваш компьютер.
Теперь надо дать знать Delphi о новых объектах автоматизации SAPI. Для этого запустите Delphi 5 или 6 (Я не пробовал боле ранние версии) и откройте Project | Import Type Library. В диалоге Import Type Library выберите “Microsoft Speech Object Library (Version 5.1)”. Если Вы не нашли его в списке, значит во время инсталяции SAPI 5.1 произошли какие-то ошибки.
Delphi предложит поместить компоненты SAPI на станицу ActiveX. Я рекомендую разместить их в новой странице под названием “SAPI 5”, так как количество компонент довольно большое (19). Так же рекомендую Вам выбрать “Unit dir name” отличающуюся от той, которая предлагается по умолчанию. Убедитесь, что на “Generate Component Wrapper” стоит галочка и нажмите кнопку "Install".
В диалоге Install выберите закладку “Into new package” и в поле “File name:” введите имя пакета наподобие “SAPI5.dpk”, нажмите кнопку "Обзор..." (browse) и убедитесь, что dpk создан в той же директории, в которой были созданы компоненты. В диалоге Install в поле Description задайте какое-нибудь описание, например “SAPI 5 automation components”. Нажмите OK.
В подтверждающем диалоге нажмите yes. После этого новые компоненты будут установлены.
Теперь, если Вы посмотрите в директорию, которую указали для установки компонент, то обнаружите там файл SpeechLib_TLB.pas (и dcr) который содержит весь код компоненты (интерфайс, константы, типы, а так же другую полезную информацию). Эта директория так же содержит (если Вы следовали вышеприведённым инструкциям) SAPI5.dpk который является исходинком пакета.
А теперь самая интересная часть.
Давайте создадим приложение, которое будет синтезировать речь. В Delphi создайте новое приложение и поместите на форму кнопку. На странице компонент SAPI5 найдите SpVoice и перетащите его на форму.
Теперь создайте событие onClick для Вашей кнопки, которое должно выглядеть примерно так:
procedure TForm1.Button1Click(Sender: TObject);
begin
SpVoice1.Speak('Hello world!', SVSFDefault);
end;
|
Запустите программу и нажмите кнопку. Здорово?
Метод Speak объекта SPVoice предоставляет довольно большие возможности. Эти возможности можно использовать если поиграться со вторым параметром. В вышеприведённом примере я использовал режим поумолчанию, который позволяет функции вернуть управление только после завершения проигрывания звука. Избежать этого можно путём внедрения в текст специальных тэгов XML.
Документация по SDK содержит файл sapi.chm который можно найти в директории \Program Files\Microsoft Speech SDK 5.1\Docs\Help .
Sapi.chm содержит довольно много информации. Вот основные, часто используемые возможности компоненты и, соответствующие им флаги, которые передаются во втором параметре:
Воспроизведение текста находящегося в файле. (SVSFIsFilename) Асинхронный решим проигрывания звука. Позволяет функции вернуть управление немедленно, во время воспроизведения. (SVSFlagsAsync) Позволяет управлять воспроизведением через XML тэги (см. раздел под название “XML TTS Tutorial”). Тэги позволяют настроить тональность звучания, скорость воспроизведения и многое другое.( SVSFIsXML)Одна из интересных вещей (не документирована) заключается в том, что можно озвучивать заголовок веб страницы путём установки флага в SVSFIsFilenam а имени файла в URL. Если Вы соединены с интернетом, попробуйте запустить следующую строчку:
SpVoice1.Speak('http://www.o2a.com', SVSFIsFilename);
|
Так же при помощи этого флага можно проигрывать wav файлы:
SpVoice1.Speak('C:\WINNT\MEDIA\Windows Logon Sound.wav', SVSFIsFilename);
|
На самом деле у этой SAPI намного больше возможностей, чем я здесь привёл. В следующий раз, мы подробнее рассмотрим другие возможности.
Массив в Delphi.
Раздел 1:
Вот несколько функций для операций с двухмерными массивами. Самый простой путь для создания собственной библиотеки. Процедуры SetV и GetV позволяют читать и сохранять элементы массива VArray (его Вы можете объявить как угодно). Например:
type
VArray: array[1..1] of double;
var
X: ^VArray;
NR, NC: Longint;
begin
NR := 10000;
NC := 100;
if AllocArray(pointer(X), N * Sizeof(VArray)) then
exit;
SetV(X^, NC, 2000, 5, 3.27); { X[2000,5] := 3.27 }
end;
function AllocArray(var V: pointer; const N: longint): Boolean;
begin {распределяем память для массива V размера N}
try
GetMem(V, N);
except
ShowMessage('ОШИБКА выделения памяти. Размер:' + IntToStr(N));
Result := True;
exit;
end;
FillChar(V^, N, 0); {в случае включения длинных строк заполняем их нулями}
Result := False;
end;
procedure SetV(var X: Varray; const N, ir, ic: LongInt; const value:
double);
begin {заполняем элементами двухмерный массив X размером ? x N : X[ir,ic] := value}
X[N * (ir - 1) + ic] := value;
end;
function GetV(const X: Varray; const N, ir, ic: Longint): double;
begin {возвращаем величины X[ir,ic] для двухмерного массива шириной N столбцов}
Result := X[N * (ir - 1) + ic];
end;
|
Раздел 2:
Самый простой путь - создать массив динамически.
Myarray := GetMem(rows * cols * sizeof(byte,word,single,double и пр.) |
Сделайте функцию fetch_num типа.
function fetch_num(r,c:integer) : single; |
result := pointer + row + col*rows |
И затем вместо myarray[2,3] напишите.
myarray.fetch_num(2,3); |
Поместите эти функции в ваш объект и работа с массивами станет пустячным делом. Я экспериментировал с многомерными (вплоть до 8) динамическими сложными массивами и эти функции показали отличный результат.
Раздел 3
Вот способ создания одно- и двухмерных динамических массивов:
(*
--
-- модуль для создания двух очень простых классов обработки динамических массивов
-- TDynaArray : одномерный массив
-- TDynaMatrix : двумерный динамический массив
--
*)
unit DynArray;
interface
uses
SysUtils;
type
TDynArrayBaseType = double;
const
vMaxElements = (High(Cardinal) - $F) div sizeof(TDynArrayBaseType);
{= гарантирует максимально возможный массив =}
type
TDynArrayNDX = 1..vMaxElements;
TArrayElements = array[TDynArrayNDX] of TDynArrayBaseType;
{= самый большой массив TDynArrayBaseType, который мы может объявить =}
PArrayElements = ^TArrayElements;
{= указатель на массив =}
EDynArrayRangeError = class(ERangeError);
TDynArray = class
private
fDimension: TDynArrayNDX;
fMemAllocated: word;
function GetElement(N: TDynArrayNDX): TDynArrayBaseType;
procedure SetElement(N: TDynArrayNDX; const NewValue: TDynArrayBaseType);
protected
Elements: PArrayElements;
public
constructor Create(NumElements: TDynArrayNDX);
destructor Destroy; override;
procedure Resize(NewDimension: TDynArrayNDX); virtual;
property dimension: TDynArrayNDX
read fDimension;
property Element[N: TDynArrayNDX]: TDynArrayBaseType
read GetElement
write SetElement;
default;
end;
const
vMaxMatrixColumns = 65520 div sizeof(TDynArray);
{= построение матрицы класса с использованием массива объектов TDynArray =}
type
TMatrixNDX = 1..vMaxMatrixColumns;
TMatrixElements = array[TMatrixNDX] of TDynArray;
{= каждая колонка матрицы будет динамическим массивом =}
PMatrixElements = ^TMatrixElements;
{= указатель на массив указателей... =}
TDynaMatrix = class
private
fRows: TDynArrayNDX;
fColumns: TMatrixNDX;
fMemAllocated: longint;
function GetElement(row: TDynArrayNDX;
column: TMatrixNDX): TDynArrayBaseType;
procedure SetElement(row: TDynArrayNDX;
column: TMatrixNDX;
const NewValue: TDynArrayBaseType);
protected
mtxElements: PMatrixElements;
public
constructor Create(NumRows: TDynArrayNDX; NumColumns: TMatrixNDX);
destructor Destroy; override;
property rows: TDynArrayNDX
read fRows;
property columns: TMatrixNDX
read fColumns;
property Element[row: TDynArrayNDX; column: TMatrixNDX]: TDynArrayBaseType
read GetElement
write SetElement;
default;
end;
implementation
(*
--
-- методы TDynArray
--
*)
constructor TDynArray.Create(NumElements: TDynArrayNDX);
begin {==TDynArray.Create==}
inherited Create;
fDimension := NumElements;
GetMem(Elements, fDimension * sizeof(TDynArrayBaseType));
fMemAllocated := fDimension * sizeof(TDynArrayBaseType);
FillChar(Elements^, fMemAllocated, 0);
end; {==TDynArray.Create==}
destructor TDynArray.Destroy;
begin {==TDynArray.Destroy==}
FreeMem(Elements, fMemAllocated);
inherited Destroy;
end; {==TDynArray.Destroy==}
procedure TDynArray.Resize(NewDimension: TDynArrayNDX);
begin {TDynArray.Resize==}
if (NewDimension < 1) then
raise EDynArrayRangeError.CreateFMT('Индекс вышел за границы диапазона : %d',
[NewDimension]);
Elements := ReAllocMem(Elements, fMemAllocated, NewDimension *
sizeof(TDynArrayBaseType));
fDimension := NewDimension;
fMemAllocated := fDimension * sizeof(TDynArrayBaseType);
end; {TDynArray.Resize==}
function TDynArray.GetElement(N: TDynArrayNDX): TDynArrayBaseType;
begin {==TDynArray.GetElement==}
if (N < 1) or (N > fDimension) then
raise EDynArrayRangeError.CreateFMT('Индекс вышел за границы диапазона : %d',
[N]);
result := Elements^[N];
end; {==TDynArray.GetElement==}
procedure TDynArray.SetElement(N: TDynArrayNDX; const NewValue:
TDynArrayBaseType);
begin {==TDynArray.SetElement==}
if (N < 1) or (N > fDimension) then
raise EDynArrayRangeError.CreateFMT('Индекс вышел за границы диапазона : %d',
[N]);
Elements^[N] := NewValue;
end; {==TDynArray.SetElement==}
(*
--
-- методы TDynaMatrix
--
*)
constructor TDynaMatrix.Create(NumRows: TDynArrayNDX; NumColumns: TMatrixNDX);
var
col: TMatrixNDX;
begin {==TDynaMatrix.Create==}
inherited Create;
fRows := NumRows;
fColumns := NumColumns;
{= выделение памяти для массива указателей (т.е. для массива TDynArrays) =}
GetMem(mtxElements, fColumns * sizeof(TDynArray));
fMemAllocated := fColumns * sizeof(TDynArray);
{= теперь выделяем память для каждого столбца матрицы =}
for col := 1 to fColumns do
begin
mtxElements^[col] := TDynArray.Create(fRows);
inc(fMemAllocated, mtxElements^[col].fMemAllocated);
end;
end; {==TDynaMatrix.Create==}
destructor TDynaMatrix.Destroy;
var
col: TMatrixNDX;
begin {==TDynaMatrix.Destroy;==}
for col := fColumns downto 1 do
begin
dec(fMemAllocated, mtxElements^[col].fMemAllocated);
mtxElements^[col].Free;
end;
FreeMem(mtxElements, fMemAllocated);
inherited Destroy;
end; {==TDynaMatrix.Destroy;==}
function TDynaMatrix.GetElement(row: TDynArrayNDX;
column: TMatrixNDX): TDynArrayBaseType;
begin {==TDynaMatrix.GetElement==}
if (row < 1) or (row > fRows) then
raise
EDynArrayRangeError.CreateFMT('Индекс строки вышел за границы диапазона : %d', [row]);
if (column < 1) or (column > fColumns) then
raise
EDynArrayRangeError.CreateFMT('Индекс столбца вышел за границы диапазона : %d', [column]);
result := mtxElements^[column].Elements^[row];
end; {==TDynaMatrix.GetElement==}
procedure TDynaMatrix.SetElement(row: TDynArrayNDX;
column: TMatrixNDX;
const NewValue: TDynArrayBaseType);
begin {==TDynaMatrix.SetElement==}
if (row < 1) or (row > fRows) then
raise
EDynArrayRangeError.CreateFMT('Индекс строки вышел за границы диапазона : %d', [row]);
if (column < 1) or (column > fColumns) then
raise
EDynArrayRangeError.CreateFMT('Индекс столбца вышел за границы диапазона : %d', [column]);
mtxElements^[column].Elements^[row] := NewValue;
end; {==TDynaMatrix.SetElement==}
end.
|
Тестовая программа для модуля DynArray.
uses DynArray, WinCRT;
const
NumRows: integer = 7;
NumCols: integer = 5;
var
M: TDynaMatrix;
row, col: integer;
begin
M := TDynaMatrix.Create(NumRows, NumCols);
for row := 1 to M.Rows do
for col := 1 to M.Columns do
M[row, col] := row + col / 10;
writeln('Матрица');
for row := 1 to M.Rows do
begin
for col := 1 to M.Columns do
write(M[row, col]: 5: 1);
writeln;
end;
writeln;
writeln('Перемещение');
for col := 1 to M.Columns do
begin
for row := 1 to M.Rows do
write(M[row, col]: 5: 1);
writeln;
end;
M.Free;
end.
|
Программирование АССЕМБЛЕРОМ в Delphi.
Автор: Александр Баранецкий Каждый день множество программистов (кроме опытных) в Delphi, используя код высокого уровня, пишут свои программы. Как правило, пренебрегая таким понятием как Assembler, имеется ввиду в строенный в DELPHI. Конечно, тяжело сказать, что на голом «асе» можно сотворить великолепный шедевр имеется ввиду красота - VCL (Visual Component Library). Особенно относительно начинающих программистов, которые только начинают учить IDE. Но зато можно повысить скорость какого нибудь математического или системного алгоритма. Сегодня я представляю на ваш суд статью по «асу». Скорее всего, она будет интересна начинающим программерам. Я не мастер, но сеже. Программирование АССЕМБЛЕРОМ в “Delphi” Для начала несколько основных команд. MOV - предназначена для занесения в ячейку памяти значения. Например:var x: integer; begin Mov X, 10 // Занесение в Х значение 10 // X:=10; Mov eax, 45 //Занесение в быстрый регистр Mov ebx, eax //Присвоения значения одного регистра другому Mov x.edx //Занесение в х значения edxПервый параметр присваивающий объект, второй присеваемое значение. ADD - Предназначена для прибавления к объекту значения. Значения передаются через запитаю. Например : add eax,2 add x,76 Первый параметр принимающий объект, второй добавляемое значение. SUB - Предназначена для вычитания от объекта значения. Значения передаются через запитаю. Например : sub eax,18 Sub x,6 Первый параметр объект от которого отнимается , второй отнимаемое значение. IMUL - команда умножения IDIV - команда деления CMP - Команда проверки JNZ,JMP,JA - команды перехода.
Теперь перейдем к практическим примерам:
// 1 Функция сложения.
function plus(x, y: integer): integer;
asm
mov eax,x
add eax,y
end;
{
Функция вернет сумму «x» и «y». Сперва заносим «х» (move eax,x) потом
прибавляем к уже имеющемуся «y» (add eax,y).
}
// 2. Функция умножения
function Umnojenie(x, z: integer): integer;
asm
mov ebx,z
mov eax,x
imul ebx
end;
{
Заносим в обратном порядке «x» и «z» Отдаем команду на
умножение первого значения на второе «imul ebx ».
}
// 3. Функция вычитания
function Minus(x, y: integer): integer;
asm
sub x,y
end;
// Просто отнимаем одно от другого
// 4 Функция деления
function divider(x, y: integer): integer;
asm
mov ebx,y {1}
cdq {2}
idiv ebx {3}
end;
Эта функция отличается от остальных методом применения операторов. Занесение значения. 2. Предварительная обработка. 3. Деление.
Это были простейшие математические операторы, теперь мы рассмотрим более сложные операторы цикла и условие
Цикл на «асе» заключается в том что создается контрольный объект и при достижении определенного условия не происходи перехода к начальной контрольной точке отчета цикла.
procedure asm_cycle;
label
lb;
var
d: integer;
begin
asm
mov ebx,0
mov d,0
lb:
add d,1
inc ebx
cmp ebx,10
jnz lb
mov ebx,0
end;
Writeln(d);
end;
Метка lb нужна, чтобы назначить контрольную точку начала операторов цикла. Переменная «d:integer» для проверки результатов работы цикла. С зарезервированного слова ASM начинаем анализ. Mov edx,0 «edx» выступает как контрольный регистр в нем фиксируется количество повторений. А с самого начало он указывает с какой величины пойдет отчет Например mov edx,0 = for i := 0 to .. do, mov edx,43 = for i:=43 to ..do Мы установим его в 0 чтобы отчет шел с нуля. Переменную d мы тоже обнулим. Третья строка это метка начала после нее идут операторы цикла. Следующий оператор наш рабочий оператор. У нас он 1 но может быть множество. Inc edx добавляем в регистр 1 шаг пройденного цикла если пропустить то цикл будет идти вечно. cmp ebx,10 Один из основных операторов он проверяет не достиг ли цикл верхний предел. Проверка идет в самом конце. Если вернет FALSE то срабатывает следующий оператор перехода на метку т.е в начало цикла и все повторяется до тех пор пока cmp не вернет TRUE в следствии чего не сработает оператор перехода JNZ. Последними операторами обнуляем счетчик и показываем результат.
Условный оператор IF..THEN..ELSE.
procedure if_sample(x: integer);
var
res: integer;
label
exit, lb;
begin
asm
cmp x,0
jnz lb
mov res,45
jmp exit
lb:mov res,0
exit: mov eax,0
end;
Writeln(res);
end;
На PASCAL этот оператор пишется так if x = 0 then x:=45 else x:=0; Сначала идет проверка не равен ли х нулю если не равен то переход на метку ld, На которой оператор обнуления. А если равен, то оператор перехода на ld не срабатывает. Срабатывает mov res,45. После которого состоится переход на метку EXIT. В «асе» желательно прописывать свою метку (у нас EXIT), которая по необходимости выйдет и процедуры.
И последние. Вызов внешней процедуры. Допустим, надо вызвать внешнюю процедуру.
procedure call_s(x, d: integer; bol: boolean);
Для вызова внешних модулей применяется метод CALL.
procedure call_sample; asm mov eax,4 mov edx,34 mov cl,0 call call_s end;Сперва передаются параметры последовательно а потом сам вызов. На этом мой маленький туториал окончен. Все примеры вы найдете в приложенном файле pr_asm. Это маленькое консольное приложение, в котором представлены все примеры с комментариями. P.S. Я не мастер в «асе», и эта статья не сделает вас гениями она лишь призвана показать некоторые стандартные методы «паса» интерпретированные в «ас». Мастера «аса» не смейтесь надо мной сильно, так как я уже сказал, что я не мастер, я просто энтузиаст в «асе».
В следующей статье я продолжу описание более продвинутых операторов и методов а также попробуем написать маленькое «ас» приложение.
Delphi и COM.
Оформил: DeeCo
Автор: Анатолий Тенцер.
Содержание Базовые понятия Интерфейс Автоматическое управление памятью и подсчет ссылок Объявление интерфейсов IUnknown Реализация интерфейсов Реализация интерфейсов (расширенное рассмотрение) Интерфейсы и TComponent Использование интерфейсов внутри программы Использование интерфейсов для реализации Plug-In Delphi и COM ВведениеCOM (Component Object Model) — модель объектных компонентов — одна из основных технологий, на которых основывается Windows. Более того, все новые технологии в Windows (Shell, Scripting, поддержка HTML и т.п.) реализуют свои API именно в виде COM-интерфейсов. Таким образом, в настоящее время профессиональное программирование требует понимания модели COM и умения с ней работать. В этой главе мы рассмотрим основные понятия COM и особенности их поддержки в Delphi.
Ключевым моментом, на котором основана модель COM, является понятие интерфейса. Не имея четкого понимания того, что такое интерфейс, успешное программирование COM-объектов невозможно.
Интерфейс, образно говоря, является «контрактом» между программистом и компилятором.
Программист обязуется реализовать все методы, описанные в интерфейсе, и следовать требованиям, предъявляемым к реализации некоторых их них.
Компилятор обязуется создать в программе внутренние структуры, позволяющие обращаться к методам этого интерфейса из любого поддерживающего те же соглашения средства программирования. Таким образом, COM является языково-независимой технологией и может использоваться в качестве «клея», соединяющего программы, написанные на разных языках.
Объявление интерфейса включает в себя описание методов и их параметров, но не включает их реализации. Кроме того, в объявлении может указываться идентификатор интерфейса — уникальное 16-байтовое число, сгенерированное по специальным правилам, гарантирующим его статистическую уникальность (GUID — Global Unique Identifier).
Интерфейсы могут наследоваться. Наследование интерфейсов — это декларация, указывающая, что унаследованный интерфейс должен включать в себя все методы предка.
Таким образом, необходимо понимать следующее:
Интерфейс не является классом. Класс может выступать реализацией интерфейса, но класс содержит код методов на конкретном языке программирования, а интерфейс — нет. Интерфейс строго типизирован. Как клиент, так и реализация интерфейса должны использовать точно те же методы и параметры, что указаны в описании интерфейса. Интерфейс является «неизменным контрактом». Нельзя определять новую версию того же интерфейса с измененным набором методов (или их параметров), но с тем же идентификатором.Это гарантирует, что новые интерфейсы никогда не будут конфликтовать со старыми. В случае необходимости расширения функциональности вы должны определить новый интерфейс, возможно являющийся наследником старого, и реализовать дополнительные методы в нем.
Реализация интерфейса — это код, который реализует эти методы. При этом, за несколькими исключениями, не накладывается никаких ограничений на то, каким образом будет выглядеть реализация. Физически реализация представляет собой массив указателей на методы, адрес которого и используется в клиенте для доступа к COM-объекту. Любая реализация интерфейса имеет метод QueryInterface, позволяющий запросить ссылку на конкретный интерфейс из числа реализуемых.
Кроме предоставления независимого от языка программирования доступа к методам объектов, COM реализует автоматическое управление памятью для COM-объектов. Оно основано на идее подсчета ссылок на объект. Любой клиент, желающий использовать COM-объект после его создания, должен вызвать заранее предопределенный метод, который увеличивает внутренний счетчик ссылок на объект на единицу. По завершении использования объекта клиент вызывает другой его метод, уменьшающий значение этого же счетчика. По достижении счетчиком ссылок нулевого значения COM-объект автоматически удаляет себя из памяти. Такая модель позволяет клиентам не вдаваться в подробности реализации объекта, а объекту — обслуживать несколько клиентов и корректно очистить память по завершении работы с последним из них.
Для поддержки интерфейсов Delphi расширяет синтаксис языка Pascal дополнительными ключевыми словами. Объявление интерфейса в Delphi реализуется ключевым словом interface:
type
IMyInterface = interface
['{412AFF00-5C21-11D4-84DD-C8393F763A13}']
procedure DoSomething(var I: Integer); stdcall;
function DoSomethingAnother(S: string): Boolean;
end;
IMyInterface2 = interface(IMyInterface)
['{412AFF01-5C21-11D4-84DD-C8393F763A13}']
procedure DoAdditional(var I: Integer); stdcall;
end;
Для генерации нового значения GUID в IDE Delphi служит сочетание клавиш Ctrl+Shift+G.
Базовым интерфейсом в модели COM является IUnknown. Любой интерфейс наследуется от IUnknown и обязан реализовать объявленные в нем методы. IUnknown объявлен в модуле System.pas следующим образом:
type
IUnknown = interface
['{00000000-0000-0000-C000-000000000046}']
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
Рассмотрим назначение методов IUnknown более подробно.
Последние два метода предназначены для реализации механизма подсчета ссылок.
function _AddRef: Integer; stdcall;
Эта функция должна увеличить счетчик ссылок на интерфейс на единицу и вернуть новое значение счетчика.
function _Release: Integer; stdcall;
Данная функция должна уменьшить счетчик ссылок на интерфейс на единицу и вернуть новое значение счетчика. По достижении счетчиком нулевого значения она должна освободить память, занятую реализацией интерфейса.
Первый метод позволяет получить ссылку на реализуемый классом интерфейс.
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
Эта функция получает в качестве входного параметра идентификатор интерфейса. Если объект реализует запрошенный интерфейс, то функция:
возвращает ссылку на него в параметре Obj; вызывает метод _AddRef полученного интерфейса; возвращает 0.В противном случае — функция возвращает код ошибки E_NOINTERFACE.
В принципе, конкретная реализация может наполнить эти методы какой-либо другой, отличающейся от стандартной функциональностью, однако в этом случае интерфейс будет несовместим с моделью COM, поэтому делать этого не рекомендуется.
В модуле System.pas объявлен класс TInterfacedObject, реализующий IUnknown и его методы. Рекомендуется использовать этот класс для создания реализаций своих интерфейсов.
Кроме того, поддержка интерфейсов реализована в базовом классе TObject. Он имеет метод.
function TObject.GetInterface(const IID: TGUID; out Obj): Boolean;
Если класс реализует запрошенный интерфейс, то функция:
возвращает ссылку на него в параметре Obj; вызывает метод _AddRef полученного интерфейса; возвращает TRUE.В противном случае — функция возвращает FALSE.
Таким образом, имеется возможность запросить у любого класса Delphi реализуемый им интерфейс. Подробнее использование этой функции рассмотрено ниже.
Реализацией интерфейса в Delphi всегда выступает класс. Для этого в объявлении класса необходимо указать, какие интерфейсы он реализует.
type
TMyClass = class(TComponent, IMyInterface, IDropTarget)
// Реализация методов
end;
Класс TMyClass реализует интерфейсы IMyInterface и IDropTarget. Необходимо понимать, что реализация классом нескольких интерфейсов не означает множественного наследования и вообще наследования класса от интерфейса. Указание интерфейсов в описании класса означает только то, что в данном классе реализованы все эти интерфейсы.
Класс должен иметь методы, точно соответствующие по именам и спискам параметров всем методам всех объявленных в его заголовке интерфейсов.
Рассмотрим более подробный пример.
type
ITest = interface
['{61F26D40-5CE9-11D4-84DD-F1B8E3A70313}']
procedure Beep;
end;
TTest = class(TInterfacedObject, ITest)
procedure Beep;
destructor Destroy; override;
end;
procedure TTest.Beep;
begin
Windows.Beep(0, 0);
end;
destructor TTest.Destroy;
begin
inherited;
MessageBox(0, 'TTest.Destroy', nil, 0);
end;
Здесь класс TTest реализует интерфейс ITest. Рассмотрим использование интерфейса из программы.
procedure TForm1.Button1Click(Sender: TObject); var Test: ITest; begin Test := TTest.Create; Test.Beep; end;
Поскольку данный код выглядит довольно странно, остановимся на нем подробнее.
Во-первых, оператор присваивания при приведении типа данных к интерфейсу неявно вызывает метод _AddRef. При этом количество ссылок на интерфейс увеличивается на единицу.
Во-вторых, код не содержит никаких попыток освободить память, выделенную под объект TTest. Тем не менее, если выполнить эту программу, на экран будет выведено сообщение о том, что деструктор был вызван. Это происходит потому, что при выходе переменной, ссылающейся на интерфейс, за область видимости (либо при присвоении ей другого значения) компилятор Delphi генерирует код для вызова метода _Release, информируя реализацию о том, что ссылка на нее больше не нужна.
| Внимание! Если у класса запрошен хотя бы один интерфейс — не вызывайте его метод Free (или Destroy). Класс будет освобожден тогда, когда отпадет необходимость в последней ссылке на его интерфейсы. Если вы к этому моменту уничтожили экземпляр класса вручную — произойдет ошибка доступа к памяти. |
Так, следующий код приведет к ошибке в момент выхода из функции:
var Test: ITest; T: TTest; begin T := TTest.Create; Test := T; Test.Beep; T.Free; end; // в этот момент произойдет ошибка
Если вы хотите уничтожить реализацию интерфейса немедленно, не дожидаясь выхода переменной за область видимости, – просто присвойте ей значение NIL:
var Test: ITest; T: TTest; begin T := TTest.Create; Test := T; Test.Beep; Test := nil; // Неявно вызывается IUnknown._Release; end;
Обратите особое внимание, что вызовы методов интерфейса IUnknown осуществляются Delphi неявно и автоматически. Поэтому не вызывайте методы интерфейса IUnknown самостоятельно. Это может нарушить нормальную работу автоматического подсчета ссылок и привести к неосвобождению памяти либо к нарушениям защиты памяти при работе с интерфейсами. Во избежание этого необходимо просто помнить следующее.
При приведении типа объекта к интерфейсу вызывается метод _AddRef. При выходе переменной, ссылающейся на интерфейс, за область видимости либо при присвоении ей другого значения вызывается метод _Release. Единожды запросив у объекта интерфейс, в дальнейшем вы не должны освобождать объект вручную. Вообще начиная с этого момента лучше работать с объектом только через интерфейсные ссылки.В рассмотренных примерах код для получения интерфейса у класса генерировался (с проверкой типов) на этапе компиляции. Если класс не реализует требуемого интерфейса, то программа не откомпилируется. Однако существует возможность запросить интерфейс и во время выполнения программы. Для этого служит оператор as, который вызывает QueryInterface и, в случае успеха, возвращает ссылку на полученный интерфейс. В противном случае генерируется исключение.
Например, следующий код будет успешно откомпилирован, но при выполнении вызовет ошибку «Interface not supported»:
var Test: ITest; begin Test := TInterfacedObject.Create as ITest; Test.Beep; end; //В то же время код var Test: ITest; begin Test := TTest.Create as ITest; Test.Beep; end;
Будет успешно компилироваться и выполняться.
Рассмотрим вопросы реализации интерфейсов подробнее.
Объявим два интерфейса:
type
ITest = interface
['{61F26D40-5CE9-11D4-84DD-F1B8E3A70313}']
procedure Beep;
end;
ITest2 = interface
['{61F26D42-5CE9-11D4-84DD-F1B8E3A70313}']
procedure Beep;
end;
Теперь создадим класс, который будет реализовывать оба этих интерфейса:
TTest2 = class(TInterfacedObject, ITest, ITest2) procedure Beep1; procedure Beep2; procedure ITest.Beep = Beep1; procedure ITest2.Beep = Beep2; end;
Как видно, класс не может содержать сразу два метода Beep. Поэтому Delphi предоставляет способ для разрешения конфликтов имен, позволяя явно указать, какой метод класса будет служить реализацией соответствующего метода интерфейса.
Если реализация методов TTest2.Beep1 и TTest2.Beep2 идентична, то можно не создавать два разных метода, а объявить класс следующим образом:
TTest2 = class(TInterfacedObject, ITest, ITest2) procedure MyBeep; procedure ITest.Beep = MyBeep; procedure ITest2.Beep = MyBeep; end;
При реализации классов, поддерживающих множество интерфейсов и много методов, может оказаться удобным делегировать реализацию некоторых из них дочерним классам. Рассмотрим пример класса, реализующего два интерфейса:
type
TBeeper = class
procedure Beep;
end;
TMessager = class
procedure ShowMessage(const S: string);
end;
TTest3 = class(TInterfacedObject, ITest, IAnotherTest)
private
FBeeper: TBeeper;
FMessager: TMessager;
property Beeper: TBeeper read FBeeper implements ITest;
property Messager: TMessager read FMessager implements IAnotherTest;
public
constructor Create;
destructor Destroy; override;
end;
Для делегирования реализации интерфейса другому классу служит ключевое слово implements.
{ TBeeper }
procedure TBeeper.Beep;
begin
Windows.Beep(0, 0);
end; { TMessager }
procedure TMessager.ShowMessage(const S: string);
begin
MessageBox(0, PChar(S), nil, 0);
end; { TTest3 }
constructor TTest3.Create;
begin
inherited;
// Создаем экземпляры дочерних классов
FBeeper := TBeeper.Create;
FMessager := TMessager.Create;
end;
destructor TTest3.Destroy;
begin
// Освобождаем экземпляры дочерних классов
FBeeper.Free;
FMessager.Free;
inherited;
end;
Такой подход позволяет разбить реализацию сложного класса на несколько простых, что упрощает программирование и повышает модульность программы.
Обращаться к полученному классу можно точно так же, как и к любому классу, реализующему интерфейсы:
var
Test: ITest;
Test2: IAnotherTest;
begin
Test2 := TTest3.Create;
Test2.ShowMessage('Hi');
Test := Test2 as ITest;
Test.Beep;
end;
В базовом классе VCL TComponent имеется полный набор методов, позволяющих реализовать интерфейс IUnknown, хотя сам класс данный интерфейс не реализует. Это позволяет наследникам TComponent реализовывать интерфейсы, не заботясь о реализации IUnknown. Однако методы TComponent._AddRef и TComponent._Release на этапе выполнения программы не реализуют механизм подсчета ссылок, и, следовательно, для классов-наследников TComponent, реализующих интерфейсы, не действует автоматическое управление памятью. Это позволяет запрашивать у них интерфейсы, не опасаясь, что объект будет удален из памяти при выходе переменной за область видимости. Таким образом, следующий код совершенно корректен и безопасен:
type
IGetData = interface
['{B5266AE0-5E77-11D4-84DD-9153115ABFC3}']
function GetData: string;
end;
TForm1 = class(TForm, IGetData)
private
function GetData: string;
end;
… var
I: Integer;
GD: IGetData;
S: string;
begin
S := '';
for I := 0 to Pred(Screen.FormCount) do
begin
if Screen.Forms[I].GetInterface(IGetData, GD) then
S := S + GD.GetData + #13;
end;
ShowMessage(S);
end;
Этот код проверяет наличие у всех форм в приложении возможности реализации интерфейса IGetData и в случае, если форма реализует этот интерфейс, вызывает его метод.
Рассмотренное выше поведение TComponent позволяет строить просто и без потери строгой типизации, связывать компоненты приложения, а также единообразно вызывать их методы, не требуя, чтобы компоненты были унаследованы от общего предка. Достаточно лишь реализовать в компоненте интерфейс, а в вызывающей программе проверить его наличие.
В качестве примера рассмотрим MDI-приложение, имеющее много различных форм и единую панель инструментов. Предположим, что на этой панели инструментов имеются команды «Сохранить», «Загрузить» и «Очистить», однако каждое из окон реагирует на эти команды по-разному.
Создадим модуль с объявлениями интерфейсов:
unit ToolbarInterface;
interface
type
TCommandType = (ctSave, ctLoad, ctClear);
TCommandTypes = set of TCommandType;
TSaveType = (stSave, stSaveAS);
IToolBarCommands = interface
['{B5266AE1-5E77-11D4-84DD-9153115ABFC3}']
function SupportedCommands: TCommandTypes;
function Save(AType: TSaveType): Boolean;
procedure Load;
procedure Clear;
end;
implementation
end.
Интерфейс IToolBarCommands описывает набор методов, которые должны реализовать формы, поддерживающие работу с панелью кнопок. Метод SupportedCommands возвращает список поддерживаемых формой команд.
Создадим три дочерние формы — Form2, Form3 и Form4 — и установим им свойство FormStyle = fsMDIChild.
Form2 умеет выполнять все три команды:
type
TForm2 = class(TForm, IToolBarCommands)
private
function SupportedCommands: TCommandTypes;
function Save(AType: TSaveType): Boolean;
procedure Load;
procedure Clear;
end;
{ TForm2 } procedure TForm2.Clear;
begin
ShowMessage('TForm2.Clear');
end;
procedure TForm2.Load;
begin
ShowMessage('TForm2.Load');
end;
function TForm2.Save(AType: TSaveType): Boolean;
begin
ShowMessage('TForm2.Save');
Result := TRUE;
end;
function TForm2.SupportedCommands: TCommandTypes;
begin
Result := [ctSave, ctLoad, ctClear]
end;
Form3 умеет выполнять только команду Clear:
type
TForm3 = class(TForm, IToolBarCommands)
private
function SupportedCommands: TCommandTypes;
function Save(AType: TSaveType): Boolean;
procedure Load;
procedure Clear;
end;
{ TForm3 } procedure TForm3.Clear;
begin
ShowMessage('TForm3.Clear');
end;
procedure TForm3.Load;
begin
// Метод ничего не делает, но должен присутствовать
// для корректной реализации интерфейса
end;
function TForm3.Save(AType: TSaveType): Boolean;
begin
end;
function TForm3.SupportedCommands: TCommandTypes;
begin
Result := [ctClear]
end;
И наконец, Form4 вообще не реализует интерфейс IToolBarCommands и не откликается ни на одну команду.
На главной форме приложения поместим ActionList и создадим три компонента TAction. Кроме того, разместим на ней TToolBar и назначим ее кнопкам соответствующие TAction.
type
TForm1 = class(TForm)
ToolBar1: TToolBar;
ImageList1: TImageList;
ActionList1: TActionList;
acLoad: TAction;
acSave: TAction;
acClear: TAction;
tbSave: TToolButton;
tbLoad: TToolButton;
tbClear: TToolButton;
procedure acLoadExecute(Sender: TObject);
procedure ActionList1Update(Action: TBasicAction;
var Handled: Boolean);
procedure acSaveExecute(Sender: TObject);
procedure acClearExecute(Sender: TObject);
end;
Наиболее интересен метод ActionList1Update, в котором проверяются поддерживаемые активной формой команды и настраивается интерфейс главной формы. Если нет активной дочерней формы либо она не поддерживает интерфейс IToolBarCommands, все команды запрещаются, в противном случае — разрешаются только поддерживаемые формой команды.
procedure TForm1.ActionList1Update(Action: TBasicAction;
var Handled: Boolean);
var
Supported: TCommandTypes;
TC: IToolBarCommands;
begin
if Assigned(ActiveMDIChild)
and ActiveMDIChild.GetInterface(IToolBarCommands, TC) then
Supported := TC.SupportedCommands
else
Supported := [];
acSave.Enabled := ctSave in Supported;
acLoad.Enabled := ctLoad in Supported;
acClear.Enabled := ctClear in Supported;
end;
При активизации команд проверяется наличие активной дочерней формы, у нее запрашивается интерфейс IToolBarCommands и вызывается соответствующий ему метод:
procedure TForm1.acLoadExecute(Sender: TObject);
var
TC: IToolBarCommands;
begin
if Assigned(ActiveMDIChild)
and ActiveMDIChild.GetInterface(IToolBarCommands, TC) then
TC.Load;
end;
procedure TForm1.acSaveExecute(Sender: TObject);
var
TC: IToolBarCommands;
begin
if Assigned(ActiveMDIChild)
and ActiveMDIChild.GetInterface(IToolBarCommands, TC) then
if not TC.Save(stSaveAS) then
ShowMessage(‘Not Saved !!!’);
end;
procedure TForm1.acClearExecute(Sender: TObject);
var
TC: IToolBarCommands;
begin
if Assigned(ActiveMDIChild)
and ActiveMDIChild.GetInterface(IToolBarCommands, TC) then
TC.Clear;
end;
Работа программы представлена на рисунке.
Того же эффекта можно добиться и другими методами (например, унаследовав все дочерние формы от единого предка либо обмениваясь с ними сообщениями), однако эти методы имеют ряд существенных недостатков.
Так, при обмене сообщениями мы теряем строгую типизацию и вынуждены передавать параметры через целые числа, а при визуальном наследовании мы привязываем себя к родительскому классу, что не всегда удобно. К тому же можно определить множество интерфейсов и реализовывать в каждой из дочерних форм лишь необходимые, а в случае наследования все формы должны будут реализовывать все общие методы.
Еще более удобно использовать интерфейсы для реализации модулей расширения программы (Plug-In). Как правило, такой модуль экспортирует ряд известных главной программе методов, которые могут быть из него вызваны. В то же время зачастую ему необходимо обращаться к каким-либо функциям вызывающей программы. И то и другое легко реализуется при помощи интерфейсов.
В качестве примера реализуем несложную программу, использующую Plug-In для загрузки данных.
Объявим интерфейсы модуля расширения и внутреннего API программы.
unit PluginInterface;
interface
type
IAPI = interface
['{64CFF1E0-61A3-11D4-84DD-B18D6F94141F}']
procedure ShowMessage(const S: string);
end;
ILoadFilter = interface
['{64CFF1E1-61A3-11D4-84DD-B18D6F94141F}']
procedure Init(const FileName: string; API: IAPI);
function GetNextLine(var S: string): Boolean;
end;
implementation
end.
Этот модуль должен использоваться как в Plug-In, так и в основной программе и гарантирует использование ими идентичных интерфейсов.
Plug-In представляет собой DLL, экспортирующую функцию CreateFilter, возвращающую ссылку на интерфейс ILoadFilter. Главный модуль сначала должен вызвать метод Init, передав в него имя файла и ссылку на интерфейс внутреннего API, а затем вызывать метод GetNextLine до тех пор, пока он не вернет FALSE.
Рассмотрим код модуля расширения:
library ImpTxt;
uses
ShareMem, SysUtils, Classes, PluginInterface;
{$R *.RES}
type
TTextFilter = class(TInterfacedObject, ILoadFilter)
private
FAPI: IAPI;
F: TextFile;
Lines: Integer;
InitSuccess: Boolean;
procedure Init(const FileName: string; API: IAPI);
function GetNextLine(var S: string): Boolean;
public
destructor Destroy; override;
end;
{ TTextFilter }
procedure TTextFilter.Init(const FileName: string; API: IAPI);
begin
FAPI := API;
{$I-}
AssignFile(F, FileName);
Reset(F);
{$I+}
InitSuccess := IOResult = 0;
if not InitSuccess then
API.ShowMessage('Ошибка инициализации загрузки');
end;
Метод Init выполняет две задачи: сохраняет ссылку на интерфейс API главного модуля для дальнейшего использования и пытается открыть файл с данными. Если файл открыт успешно – выставляется внутренний флаг InitSuccess.
function TTextFilter.GetNextLine(var S: string): Boolean;
begin
if InitSuccess then
begin
Inc(Lines);
Result := not Eof(F);
if Result then
begin
Readln(F, S);
FAPI.ShowMessage('Загружено ' + IntToStr(Lines) + ' строк.');
end;
end
else
Result := FALSE;
end;
Метод GetNextLine считывает следующую строку данных и возвращает либо TRUE, если это удалось, либо FALSE — в случае окончания файла. Кроме того, при помощи API, предоставляемого главным модулем, данный метод информирует пользователя о ходе загрузки.
destructor TTextFilter.Destroy;
begin
FAPI := nil;
if InitSuccess then
CloseFile(F);
inherited;
end;
В деструкторе мы обнуляем ссылку на API главного модуля, уничтожая его, и закрываем файл.
function CreateFilter: ILoadFilter; begin Result := TTextFilter.Create; end;
Эта функция создает экземпляр класса, реализующего интерфейс ILoadFilter. Ссылок на экземпляр сохранять не нужно, он будет освобожден автоматически.
Теперь полученную DLL можно использовать из основной программы.
type
TAPI = class(TInterfacedObject, IAPI)
procedure ShowMessage(const S: string);
end;
{ TAPI }
procedure TAPI.ShowMessage(const S: string);
begin
with (Application.MainForm as TForm1).StatusBar1 do
begin
SimpleText := S;
Update;
end;
end;
Класс TAPI реализует API, предоставляемый модулю расширения. Функция ShowMessage выводит сообщения модуля в Status Bar главной формы приложения.
type TCreateFilter = function: ILoadFilter; procedure TForm1.LoadData(FileName: string); var PluginName: string; Ext: string; hPlugIn: THandle; CreateFilter: TCreateFilter; Filter: ILoadFilter; S: string; begin
Подготавливаем TMemo к загрузке данных:
Memo1.Lines.Clear; Memo1.Lines.BeginUpdate;
Получаем имя модуля с фильтром для выбранного расширения файла. Описания модулей хранятся в файле plugins.ini в секции Filters в виде строк формата:
<расширение> = <имя модуля>, например:
[Filters]
TXT = ImpTXT.DLL
try
Ext := ExtractFileExt(FileName);
Delete(Ext, 1, 1);
with TIniFile.Create(ExtractFilePath(ParamStr(0)) + 'plugins.ini') do
try
PlugInName := ReadString('Filters', Ext, '');
finally
Free;
end;
Теперь попытаемся загрузить модуль и найти в нем функцию CreateFilter:
hPlugIn := LoadLibrary(PChar(PluginName)); try CreateFilter := GetProcAddress(hPlugIn, 'CreateFilter'); if Assigned(CreateFilter) then begin
Функция найдена, создаем экземпляр фильтра и инициализируем его. Поскольку внутренний API реализован тоже как интерфейс — нет необходимости сохранять ссылку на него.
Filter := CreateFilter; try Filter.Init(FileName, TAPI.Create);
Загружаем данные при помощи созданного фильтра:
while Filter.GetNextLine(S) do Memo1.Lines.Add(S);
Перед выгрузкой DLL из памяти необходимо обязательно освободить ссылку на интерфейс Plug-In, иначе это произойдет по выходе из функции и вызовет Access Violation.
finally
Filter := nil;
end;
end
else
raise Exception.Create('Не могу загрузить фильтр');
Выгружаем DLL и обновляем TMemo:
finally FreeLibrary(hPlugIn); end; finally Memo1.Lines.EndUpdate; end; end;
Таким образом, реализовать модули расширения для загрузки данных из форматов, отличающихся от текстового, и единообразно работать с ними несложно.
Преимущества данного способа становятся особенно очевидными при реализации сложных модулей расширения, интерфейс с которыми состоит из многих методов.
| Внимание! Поскольку в EXE и DLL используются длинные строки, не забудьте включить в список uses обоих проектов модуль ShareMem. Другим вариантом решения проблемы передачи строк является использование типа данных WideString. Для них распределением памяти занимается OLE, причем делает это независимо от модуля, из которого была создана строка. |
Как сделать окошко подсказки в редакторе как Delphi по CTRL-J.

1. Делаешь ListBox, заполняешь, visible := false, parent := Memo 2. У Memo в обработчике Memo.onKeyDown что-нибудь типа:
if (key = Ord('J')) and (ssCtrl in Shift) then
begin
lb.Left := Memo.CaretPos.x;
lb.Top := Memo.CaretPos.y + lb.height;
lb.Visible := True;
lb.SetFocus;
end;
|
Он показывается.. а дальше работай с листбоксом, вставляй в мемо нужный текст, пряч листбокс.
Простой шаблон для Delphi DLL.
Пример DLL Без модулейВо-первых, "скелет" DLL, которую вы хотели, сохраняете как DLLFRAME.DPR:
{---------------------DLLFRAME.DPR--------------------------}
library Dllframe;
uses WinTypes;
function GetString : string ; export ;
begin
Result := 'Привет из DLL!' ;
end;
exports
GetString;
begin
end.
{-----------------------------------------------------------}
|
Теперь напишем вызывающую программу и сохраним ее как DLLCALL.DPR:
{---------------------DLLCALL.DPR---------------------------}
program Dllcall;
uses
Dialogs;
{$R *.RES}
function GetString : string ; far ; external 'DLLFRAME' ;
begin
MessageDlg( GetString, mtInformation, [ mbOK ], 0 ) ;
end.
{-----------------------------------------------------------}
|
Код вызывающей программы, сохраните ее как DLLCALL.DPR:
{---------------------DLLCALL.DPR---------------------------}
program Dllcall;
uses
Dialogs;
{$R *.RES}
function GetString : string ; far ; external 'MyDLL' ;
begin
MessageDlg( GetString, mtInformation, [ mbOK ], 0 ) ;
end.
{-----------------------------------------------------------}
|
"скелет" DLL, которую вы хотели, сохраняете как DLLFRAME.DPR:
{---------------------DLLFRAME.DPR--------------------------}
library Dllframe;
uses DLLUnit;
exports
GetString;
begin
end.
{-----------------------------------------------------------}
|
Модуль, который мы сохраняем как dllunit.pas:
{---------------------dllunit.pas--------------------------}
unit DLLUnit;
interface
uses WinTypes;
function GetString: string; export;
implementation
function GetString: string;
begin
GetString := 'Привет из DLL!' ;
end ;
begin
end.
{-----------------------------------------------------------}
|
Delphi for DotNet – первый взгляд.
Оформил: DeeCo
Автор: Михаил Полюдов The RSDN Group Источник: RSDN Magazine #2| Содержание. Комплект поставки. Основные цели Borland при разработке данного продукта. Что нового в Delphi .NET Что такое .NET Изменения языка в Delphi.NET. Приложение Delphi.NET Программа №1. “Hello .NET!”. Что дальше Программа №2. Нам нужны окна. Итак, проблемы обрисованы - попробуем написать код Программа №3. Добавляем на форму кнопку. Резюме |
Введение.
Такое событие, как выпуск новой, седьмой, версии Delphi, не прошло незамеченным для большинства разработчиков. Примечательно, что в поставку этого программного продукта включен дистрибутив Delphi .NET Developer Preview. Вот об этом новом продукте (вернее – его «примерочной» версии) и пойдет речь в данной статье.
Прежде всего стоит остановиться на комплекте поставки нового программного продукта.
Комплект поставки.В поставку входят:
два компилятора командной строки dccil.exe и dccasp.exe. Реально это один и тотже исполняемый файл переименованный и скопированный в другую директорию. модули импорта стандартных сборок CLR. небольшое количество документации в формате HTML. лицензия и рекомендации по распространению. В рекомендациях по распространению написано только то, что оно запрещено :).К счастью, на Borland Developer Network (http://bdn.borland.com) имеется открытое дополнение, которое позволяет использовать компилятор Delphi .NET из IDE Delphi 7. Это дает возможность работать с привычным интерфейсом и подсветкой синтаксиса (Правда, не весь синтаксис подсветится правильно, а уж о работе Code Completion и речи быть не может. Почему - будет понятно позже).
Основные цели Borland при разработке данного продукта.Можно предположить, что при разработке прототипа Borland стремился:
Создать CLR-совместимый компилятор языка Object Pascal. Обеспечить возможность использования собственных библиотек в новом окружении с минимально необходимыми изменениями со стороны конечного разработчика :). Добиться (хотя бы ограниченной) совместимости нового компилятора с unmanaged-версиями Delphi на уровне исходных текстов. Некоторые ограничения неизбежны в силу наличия таковых ограничений в самой платформе .NET.Платформа .NET появилась сравнительно недавно, но уже завоевала огромный круг поклонников, как среди программистов, так и среди разработчиков компиляторов. Ни под одну платформу до сих пор не выпускалось так много компиляторов за столь короткий срок.
На данном фоне совершенно естественно, что корпорация Borland стремится выпустить версию компилятора своего популярного языка программирования для новой платформы, тем более, что в этой платформе во многом реализованы как раз те технологии и концепции, которые так долго и упорно продвигала корпорация Borland.
Некоторую проблему для Borland представляет наличие встроенной в платформу .Net библиотеки Windows.Forms. Эта библиотека содержит функциональность, аналогичную достаточно большому сегменту библиотеки VCL, но не является ее полным аналогом. Borland должен обеспечить возможность компиляции старого кода с использованием Windows.Forms. Сейчас еще трудно оценить, насколько выполнимы обещания Borland. Но пока что все выглядит довольно оптимистично.
Добиться совместимости с VCL и при этом не сдублировать код Windows.Forms – крайне сложная задача. Однако именно ее и пытается решить Borland. В качестве решения проблемы Borland планирует использовать новую (для него) возможность – helper-классы, речь о которых пойдет ниже.
Другая проблема возникает там, где используется так называемый небезопасный код. Такие участки кода придется переписывать при переходе под новую версию компилятора. В принципе, MSIL может выдержать всё, что угодно, так как по сути является платформно-независимым ассемблером. Но Microsoft ввел такое понятие, как "безопасный" код. Во многих случаях применение небезопасного кода не допускается средой исполнения. В основном это связано с защитой. Так, при скачивании кода из Интернет происходит его верификация, и, если код содержит небезопасные фрагменты, или использует небезопасные с точки зрения системы защиты вызовы методов, он просто не допускается к исполнению. Borland, по крайней мере пока, декларирует, что Delphi.Net будет компилировать исключительно безопасный код. В компилятор Delphi 7 уже встроено предупреждение о небезопасных (unsafe) участках кода.
Еще одна проблема в том, что компилятор Delphi (претендующий на компиляцию исключительно безопасного кода) не имеет права оперировать размерами данных и их физическим расположением в памяти. Дело в том, что CLR полностью берет на себя управление памятью и предоставляет программисту высокоуровневый API, основанный на информации о типах. Манипуляции с памятью, в том числе с размерами блоков памяти, допускаются только в небезопасном коде, а стало быть, Delphi не поддерживаются.
Вот так писать в Delphi будет нельзя:
begin Stream.Write(IntArr[0], 4 * ArrLen); end; begin Stream.Read(IntArr[0], sizeof(integer) * ArrLen); end;
Для работы с теми же массивами в .Net Framework предназначены специальные методы, позволяющие сериализовать содержимое массива как единый объект. Необходимость в unsafe-коде практически исчезает, хотя иногда это приводит к снижению эффективности кода.
Что нового в Delphi .NET Что такое .NETО платформе .NET, а точнее - о CLR (Common Language Runtime) есть очень хорошая статья Владислава Чистякова, опубликованная на компакт-диске к предыдущему номеру нашего журнала. В других статьях на www.rsdn.ru можно найти материалы по другим аспектам .Net.
В этой статье я не буду подробно рассматривать платформу .NET. Здесь будет рассмотрено несколько вопросов, интересующих Delphi -программистов, рассматривающих вопрос о переходе на новую платформу.
Изменения языка в Delphi.NETКак уже говорилось, главная задача Borland – это поддержка платформы .Net, и, одновременно, сохранение совместимости с уже существующим кодом, написанным в Delphi.
Unsafe code (небезопасный код).Borland добавил в Delphi 7 три новых предупреждения о небезопасных типах, небезопасном коде и небезопасных преобразованиях типов.
Типы данных, не поддерживаемые в Delphi.Net: PChar; нетипизированные указатели; нетипизированные var и out-параметры; file of <тип>; real48; вариантные записи (записи, содержащие перекрывающиеся поля). Небезопасный код: переменные по абсолютным адресам; процедуры Addr(), Ptr(), Hi(), Lo(), Swap(); процедуры BlockRead(), и BlockWrite(); функция Fail(); процедуры GetMem(), FreeMem(), ReallocMem(). Небезопасные преобразования типов: преобразование объекта в тип, не являющийся потомком от изначального класса объекта; преобразование записей.При нарушении этих ограничений компилятор Delphi 7 с настройками по умолчанию выдаст предупреждение, а компилятор Delphi.NET – ошибку.
Кроме вышеперечисленных небезопасных конструкций, Delphi запрещает использовать встроенный ассемблер (оператор asm), конструкцию ExitProc и некоторые из конструкций для поддержки COM/OLE/ActiveX технологий:
Расширенные идентификаторыВ CLR имеются идентификаторы (типы, классы), которые совпадают по написанию с ключевыми словами Delphi, например: type, object. Для работы с такими идентификаторами нужно использовать полное именование, т.е. System.object, System.type и т.д. Borland декларирует в своей документации, что вместо полного именования можно использовать имя с префиксом & (амперсанд). Есть только одно "но": данная версия компилятора ругается на неправильный символ в исходных текстах :( .
Расширения и элементы, находящиеся в разработке.Следующий список перечисляет конструкции языка, разработка некоторых из них еще не закончена по тем или иным причинам:
"запечатаные" (sealed) классы. Определено новое ключевое слово Delphi, "sealed". Атрибут "sealed" применяется к классу для указания того, что класс не может быть расширен где-либо в CLR (ни на Delphi, ни на любом другом языке программирования); завершенные (final) методы. Еще одно новое ключевое слово "final". Данный атрибут применяется к методам для указания того, что они не могут быть перекрыты в потомках; UTF-8 Unicode символы в исходном коде; вложенные типы (т.е. описание типа внутри другого); невиртуальные методы в записях; статические (class, static) данные класса; статические (class, static) свойства класса; статические (class, static) методы; виртуальные методы в helper-классах; широковещательные (multicast) события (event). На текущий момент планируется использовать оператор ':=' для замены последнего присвоенного события, не влияя на обработчики событий, назначенные из управляемого кода других средств разработки. Присвоение значения nil будет вызывать удаление последнего присвоенного обработчика событий; Variant будет поддерживаться как TObject, а не как TVarData (реализация внутренних механизмов работы с Variant возлагается на CLR); Автоматическое упаковка (boxing) value-типов (простых типов, таких, как Integer, и записей) в CLR объекты. Пространства имен (Namespaces).В Delphi .NET юнит остается основным контейнером для типов. CLR предоставляет еще один слой организации – пространство имен (namespace). В .Net Framework пространство имен – это концептуально контейнер типов. В Delphi for .Net пространство имен - это контейнер юнитов Delphi. Появление пространств имен дает Delphi возможность использовать и расширять классы .Net.
В отличие от традиционных юнитов Delphi пространства имен могут быть вложенными и формировать иерархические структуры. Вложенные пространства имен предоставляют способ организации типов и идентификаторов, и используются для однозначного определения типов с одинаковыми именами. Будучи контейнерами для юнитов, пространства имен могут применяться, чтобы различать юниты с одинаковыми именами, но лежащие в разных раскаgе'ах.
Например, класс MyClass из пространства имен MyNameSpace отличается от класса MyClass из YourNamespace. Во время исполнения CLR всегда обращается к классам и типам по их полным именам: имя сборки, за которым следует название пространства имен, содержащего тип.
В Delphi.NET файл проекта (программы, библиотеки или раскаgе'а) неявно вводит собственное пространство имен, называемое пространством имен проекта по умолчанию. Юнит может как входить в это пространство имен, так и явно объявить себя членом другого пространства имен. В любом случае юнит объявляет свою принадлежность к тому или иному пространству имен в выражении Unit. Например, вот так явно объявляется пространство имен:
unit MyCompany.MyWidgets.MyUnit;
Прежде всего, обратите внимание на то, что пространства имен разделяются точками.
Во-вторых, заметьте, что точки выражают концепцию вкладывания одних пространств имен в другие. Вышеприведенный пример говорит, что юнит MyUnit – это член пространства имен MyWidgets, которое, в свою очередь, содержится в пространстве имен MyCompany.
Компилятор раскрывает пространства имен в следующей последовательности:
Пространство имен данного юнита (если таковое существует). Пространство имен проекта по умолчанию. Пространство имен, указанное в выражении проекта Namespaces. Пространства имен, указанные в настройках компилятора Приложение Delphi.NETПопробуем написать три программы (консольное, простейшее оконное и чуть более сложное оконное приложение), пользуясь примерами и описаниями из документации.
Программа №1. “Hello .NET!”.Данная программа – традиционный пример, используемый со времен Кернигана и Ричи. Эта программа будет всего лишь выводить на консоль строку "Hello, Delphi.NET!".
Итак, приступаем к созданию первого проекта на Delphi .NET.
| ПРИМЕЧАНИЕ Здесь и далее я буду пользоваться оболочкой Delphi для набора, исправления, компиляции и запуска приложений, за исключением отдельно оговоренных случаев. |
Итак, создаем новый проект консольного приложения (File-New-Other-Console Application), сохраняем его как HelloDelphiNet.dpr.
Напишем следующее:
program HelloDelphiNet;
{$APPTYPE CONSOLE}
begin
WriteLn('Hello, Delphi.NET!');
end.
Таким образом, мы получили исходный текст простого консольного приложения. Скомпилировать его можно либо с помощью следующей командной строки:
dccil HelloDelphiNet.dpr
Либо выбрав пункт меню Delphi с установленным .NET расширением Delphi for .NET->Compile (или Build).
После компиляции запускаем готовый EXE-файл из командной строки (иначе мы просто не увидим результатов его работы) и видим следующее:
C: \...\> HelloDelphiNet Hello, Delphi.NET! С: \...\> _Что дальше
Что такое программирование в Windows без использования окон? Ну, вообще-то, это вполне нормально, есть достаточно много разнообразных программ, не использующих окон. Но Delphi не была бы Delphi без своих GUI-возможностей.
| ПРИМЕЧАНИЕ Не стоит удивляться тому, что в следующих примерах все элементы управления и формы создаются в коде, а не через модификацию/загрузку ресурсов. Это является обычным поведением для .NET-ориентированных языков. Дело в том, что одной из новинок, появившихся в .NET Framework, стала возможность сериализации в код. Тот же дизайнер форм Visual Studio сохраняет состояние формы и элементов управления в код программы. Это позволяет ускорить загрузку формы, а также вручную модифицировать код, сгенерированный дизайнером форм. Delphi поддерживает другую концепцию – сериализацию в собственный формат. Пока не ясно, как будут сериализоваться формы в Delphi.NET. Ясно одно – создать CodeDOM-провайдер, позволяющий использовать Delphi внутри VS.NET, будет несложно. |
Borland пока не включил в Delphi.NET свою библиотеку VCL (но планирует сделать это, что явственно следует из документации). Основных причин, пожалуй, две:
сжатые сроки выпуска компилятора .NET, при продолжении развития основной линейки Delphi; сложность реализации совместимости между VCL и Windows Forms.Некоторые проблемы, препятствующие обеспечению этой совместимости, приведены в документации, другие – обойдены вниманием. Пожалуй, стоит заострить внимание на некоторых из них.
Проблема №1. Множественные (multicast) обработчики событий.В .Net все события компонентов множественные. Что это значит?
Проиллюстрирую примером на псевдокоде (псевдокод потому, что сама концепция есть не только в C# или там VB, а в CLR вообще):
Есть объект кнопка.
Прописываем в ее событие Нажато первый обработчик:
кнопка.Нажато := ПервыйОбработчик;
Так же прописываем еще один, прямо следующим оператором:
кнопка.Нажато := ПервыйОбработчик; кнопка.Нажато := ВторойОбработчик;
В обычной Delphi вторая строка кода заменит первый обработчик вторым, а в CLR должна добавить второй обработчик в очередь за первым (правда, в Borland еще не до конца решили, так это будет реализовано или не так, но об этом уже писалось выше).
В C# данная концепция реализована так:
Button.Click + = EventHandler(MyEventHandler);
Этот код, требует некоторых пояснений. Итак: EventHandler – это так называемый делегат – специальный класс из сборки System, позволяющий ссылаться на метод некоторого класса. У данного класса есть (если верить MSDN) следующие конструкторы:
EventHandler(method)
И.
EventHandler(object, method)
Второй формат недоступен из C#, а первый – из Delphi. Самое странное, что в Delphi можно и нужно передавать не метод, а обычную процедуру, причем в качестве объекта может передаваться значение nil.
Проблема №2. Совместимость CLR и VCL.Эта проблема просто глобальна, хотя на первый взгляд так не кажется.
Для того, чтобы создавать эффективные и небольшие приложения для CLR, нужно, чтобы VCL максимально использовала CLR. Если не реализовать VCL-обертку для CLR, код, который уже существует для VCL, под CLR придется переделывать очень долго. Borland придумал очень интересный выход: для каждого класса компонента CLR создается так называемый helper-класс, который расширяет функциональность класса CLR.
Детали этой технологии в Borland до конца еще не разработали, а вся информация почерпнута все из той же документации к Delphi.NET.
Итак, helper-класс – это класс, содержащий дополнительные методы, расширяющие функциональность основного класса. Использование helper-класса – это не форма наследования, а способ обеспечения расширения функциональности базового класса. Синтаксис объявления helper-класса таков:
ClassHelper = class helper[(ancestor list)]for type - id end;
Обычно перед helper-классом идет объявление типа:
type TMyClass = Some.VerySimilar.class;
А за ним уже собственно объявление класса.
// Вводим helper-класс и объявляем функцию, ожидаемую // существующим Delphi-кодом. TMyClassHelper = class helper for TMyClass function ExistingDelphiFunc: Integer; end;
Теперь существующий Delphi-код, ожидающий наличия у типа TMyClass функции ExistingDelphiFunc, будет работать как ни в чем не бывало.
Хорошим примером может служить класс System.Object, являющийся предком для всех .NET классов. В Delphi ему соответствует класс TObject. В Delphi for .Net TObject не наследуется от System.Object. Вместо этого он объявлен helper-классом для System.Object. Это позволяет использовать такие отсутствующие в System.Object методы, как ClassName и ClassType. Компилятор, не найдя реализаций Class-Name и ClassType в классе System.Object, обратится к helper-классу и возьмет их оттуда. Таким образом, helper-классы позволяют использовать в .Net возможности, уже реализованные в VCL.
Итак, проблемы обрисованы - попробуем написать кодПервый пример - просто окно с надписью.
Создаем новый проект Delphi .NET, TestForm1.dpr. Вот его начальный код:
program TestForm1; begin end.
Теперь заявим наше намерение использовать сборку System.Windows.Forms (В данной сборке содержатся стандартные элементы управления):
program TestForm1; uses System.Windows.Forms;
Теперь объявим глобальную переменную – форму:
var OurForm: System.Windows.Forms.Form;
В коде создадим форму.
begin OurForm := System.Windows.Forms.Form.Create; System.Windows.Forms.Application.Run(Frm); end.
Компилируем и запускаем.
На экране мы видим простую формочку без заголовка.
Попробуем добавить заголовок и переместить форму на центр экрана:
OurForm := System.Windows.Forms.Form.Create; OurForm.Text := 'TestForm1'; StartPosition := FormStartPosition.CenterScreen;
Получилось! Пора переходить к более сложным вещам, например к добавлению неких компонентов на форму.
Полный исходный текст TestForm1.dpr:
program TestForm1; uses System.Windows.Forms; var OurForm: System.Windows.Forms.Form; begin OurForm := System.Windows.Forms.Form.Create; OurForm.Text := 'TestForm1'; StartPosition := FormStartPosition.CenterScreen; System.Windows.Forms.Application.Run(OurForm); end.Программа №3. Добавляем на форму кнопку.
Новый проект назовем TestForm2.dpr, за основу берем TestForm1.dpr.
Для начала описываем новый класс:
uses System.Windows.Forms; type OurFormClass = class(System.Windows.Forms.Form) end;
Изменяем объявление переменной и конструирование формы:
var OurForm: OurFormClass; begin OurForm := OurFormClass.Create;
Теперь создадим конструктор:
OurFormClass = class(System.Windows.Forms.Form) public constructor Create; end; constructor OurFormClass.Create; begin inherited; end;
| ПРЕДУПРЕЖДЕНИЕ Внимание! ОБЯЗАТЕЛЬНО нужно выполнять вызов inherited конструктора!!! |
Компилируем и запускаем - все осталось как и было, то есть все работает, но форма уже нашего класса.
Пишем тело конструктора:
constructor OurFormClass.Create; begin inherited; StartPosition := FormStartPosition.CenterScreen; Text := 'Центрованная форма'; end;
Запускаем – все в порядке. Но пустая форма нам неинтересна – попробуем создать на ней кнопку:
OurFormClass = class(System.Windows.Forms.Form) public btnClose: System.Windows.Forms.Button; constructor Create; end; constructor OurFormClass.Create; begin inherited; StartPosition := FormStartPosition.CenterScreen; Text := 'Центрованная форма'; btnClose := System.Windows.Forms.Button.Create; btnClose.Left := 8; btnClose.Top := 8; btnClose.Width := 81; btnClose.Height := 25; btnClose.Text := 'Закрыть'; Controls.Add(btnClose); end;
Итак, у нас есть кнопка с надписью "закрыть" в левом верхнем углу формы, но сделать с ее помощью ничего пока нельзя. Пора начинать решать первую проблему Borland – обработчики событий. Пока что подключение и использование обработчиков событий в Delphi.NET до конца не реализовано. Поэтому приходится прибегать к некоторым ухищрениям, а именно – использовать в качестве обработчика событий процедуру, не являющуюся членом класса формы.
Из MSDN узнаем, что спецификация обработчика должна быть такой:
void EventHandler(object sender, EventArgs e);На Delphi это будет выглядеть так:
procedure EventHandler(Sender: System.object; e: EventArgs);
Создаем обработчик:
btnClose: System.Windows.Forms.Button; constructor Create; end; procedure btnCloseClick(Sender: System.object; e: EventArgs); forward; //... var OurForm: OurFormClass; procedure btnCloseClick(Sender: System.object; e: EventArgs); begin OurForm.Close; end;
А в конструкторе вставляем следующую конструкцию:
btnClose.Text := 'Закрыть'; btnClose.Click := EventHandler.Create(Self, NativeInt(@btnCloseClick)); Controls.Add(btnClose);
Компилируем, запускаем – работает. Единственное НО - использование глобальной переменной. Но эту ошибку, похоже, получится исправить только в следующей (нормальной?) версии компилятора.
РезюмеИтак, резюмируя:
компилятор есть, поддерживает как минимум часть стандартных сборок .NET Framework; имеется, в принципе, среда написания кода, компиляции (с позиционированием ошибок) и запуска отладчика (с перекомпиляцией в DEBUG); существует возможность писать как консольные, так и оконные приложения; нет пока что портированной библиотеки VCL; есть некоторые несоответствия между прилагаемой документацией и компилятором.Таким образом, вполне нормально для Developer Preview, но не достаточно для качественной среды разработки от Borland. Ждем следующих версий.
Обработка почты с помощью Delphi.
Оформил: DeeCo
Автор: Анатолий Орлов.
Часто бывает необходимо обработать почтовые сообщения от пользователей. Например, занести заказчика товара в базу данных или высказывания клиентов о вашей фирме (своеобразная книга жалоб и предложений). Это можно сделать вручную, читать каждое сообщение, копировать, перезаписывать, … Но если к вам ежедневно обращаются сотни или даже тысячи клиентов? Нужно будет нанять нового сотрудника или самостоятельно просматривать всю почту. Конечно, можно написать скрипт на Perl’е, который будет обрабатывать данные, введенные пользователем через форму на вашем сайте. Однако если у вас нет собственной странички или вы не хотите хранить конфиденциальную информацию в базе данных на сервере – нужно приложение под Windows, которое будет находиться на локальном компьютере и заходя в Интернет обрабатывать почту. После этого вы можете делать с содержимым письма что угодно: сохранить его, переслать на другой почтовый ящик, занести в базу, … Также можно создать почтовый фильтр, который будет группировать письма по темам. Так можно избавиться от ненужного спама. Например, если вы на почтовый ящик должны получать только резюме от соискателей работы, можно удалять все письма, тема которых отлична от “резюме” или “работа”. Мне приходилось разрабатывать программу, которая получает данные на e-mail, заполненные пользователем через форму на сервере. После этого она анализирует эти данные и заносит их в базу на локальном компьютере. Анализ происходит по заголовку и имени поля формы. Если заголовок письма совпадает с заголовком, записанном в настроиках программы, то после этого осуществляется разбор письма. Письмо после заполнения формы имело следующую структуру: имя поля1 формы=значение1 имя поля2 формы=значение2 ………………………………. имя поляN формы=значениеN Приложение обрабатывало значения полей и записывало их в базу данных ACCESS, где поля имели те же имена, что и у формы. Таким образом данные хранились на компьютере в офисе и были скрыты от Интернет – хакеров. Такое приложение можно написать практически на любом, имеющимся у вас языке. Я предлагаю вам рассмотреть такое приложение на Delphi и обсудить принципы работы программы. Для работы с протоколами Internet в Delphi на палитре компонентов предназначен раздел FastNet. FastNet
Для приема почты нам понадобится протокол POP3 (Post Office Protocol – Version3) для этого протокола зарезервирован 110 порт, соответствующий ему компонент в разделе FastNet – NMPOP3.
С помощью NMPOP3 можно: соединиться с сервером, послать аунтификационную информацию на сервер, получить информацию о почтовом ящике (колличество писем, размер), получить письма с сервера, удалить письма.
Принцип общения с сервером по протоколу POP3 состоит из трех этапов: установления соединения (аунтификация), чтения списка писем и выбор нужного письма, прекращение соединения. Работу с сервером по протоколу необходимо знать, так как он предоставляет широкие возможности не только для программистов, но и для пользователей. Мне много раз приходили письма неимоверного размера, с большим колличеством картинок. После десяти минут работы Outlook Express просто зависал. Приходилось удалять почту, ведя диалог с сервером по POP3, присоединившись к нему через telnet к 110 порту.
Рассмотрим диалог с сервером по протоколу POP3.
После подключения к почтовому серверу, выдается строка приветствия +OK POP3 server ready. После этого пользователь посылает свой логин, командой USER login и пароль PASS password. Если логин и пароль введены правильно, можно начать просматривать почту.
Командой LIST, можно посмотреть сколько писем к вам пришло и размер писем. Ответ Сервера выглядит следующим образом:
User: LIST
Server: +OK 2 messages (400 octets)
Server: 1 250
Server: 2 150
Данное сообщение гласит о том, что на сервере находится два сообщения объемом в 400 байт, первое сообщение объемом 250 байт, второе –150 байт. Если необходимо посмотреть размер конкретного письма, например пятого необходимо послать серверу команду LIST 5. В результате на терминале появится сообщение Server: +OK 2 200. В том случае, если письмо с таким номером не существует высветится ошибка
Server: -ERR no such message, only 2 messages in maildrop.
Эта ошибка говорит о том, что на сервере находиятся только два письма. Чтобы получить письмо с сервера используют команду RETR m, где m – номер письма. Если письмо слишком длинное, можно прочитать его не полностью, а только первые n – строчек этого письма, это делается командой TOP msg n. Для удаления письма служит команда DEL m. Если письмо удалилось, появится сообщение +OK message 1 deleted, если не существует письма с таким номеров сервер выдасть ошибку -ERR no such message. Закончить обработку почты и выйти из диалога можно командой QUIT. Подробнее протокол описан в документе RFC1081 и RFC 1460.
Для нашего примера вам понадобиться почтовый ящик, получить его в Интернете не составит особой сложности. Возьмем, например, бесплатный почтовый ящик на сервере www.rambler.ru .Сервер исходящий почты на Рамблере mail.rambler.ru (вы легко его можете узнать у администратора сервера или на самом сервере), он нам понадобится для соединения по протоколу POP3. Итак, начнем.
Создадим новое приложение. Добавим в него компонент NMPOP3. По умолчанию он получит в нашем приложение имя NMPOP31, в этом можно убедиться, просмотрев Object Inspector. Это имя мы будем использовать в нашем примере. Вы можете его переименовать для удобства обращения с ним, например назвать его POCHTA. Для соединения с сервером, нам надо вызвать процедуру Connect компонента NMPOP3. Но перед этим мы установим имя сервера, логин и пароль пользователя для аунтификации по протоколу POP3. Сервер задается свойством NMPOP31.Host, логин свойством NMPOP31.UserID, пароль свойством NMPOP31.Password. Имя пользователя и пароль выдается администратором сервера после регистрации. В локальной сети, обычно, вся почта приходит на сервер и только после обращения к нему перенаправляется на рабочие места. Поэтому здесь надо задавать имя сервера (proxy – сервера), который забирает почту и вашу учетную запись (логин и пароль).
После этого можно вызвать метод NMPOP31.Connect. Мы можем посмотреть количество писем на сервере, обратившись к свойству NMPOP31.MailCount. Получим какое – нибудь письмо с сервера, например под номером 1. Для этого вызовем процедуру NMPOP31.GetMailMessage(1). После этого объект TmailMessage содержит информацию о письме. Теперь мы можем проанализировать наше письмо: посмотреть тему письма (NMPOP31.MailMessage.Subject) , почтовый адрес отправителя (NMPOP31.MailMessage.From), прочитать письмо (NMPOP31.MailMessage.Body). Также нам доступны некоторые другие свойства сообщения. С помощью свойства NMPOP31.MailMessage.MessageId, которое имеет тип string просматривается идентификатор письма. Список файлов вложения доступен через свойство NMPOP31.MailMessage.Attachment, это свойства типа TStringList. После прочтения письма нужно разъединиться с сервером, это делается процедурой NMPOP31.Disconnect. Давайте посмотрим, как это все выглядит на нашем примере. Добавим на форму компоненты TEdit, TMemo и TButton. Они получат имена, соответсвенно Edit1, Memo1, Button1. После нажатия кнопки “Получить письмо” в поле Edit1 появится тема первого письма, если на сервере есть письма, а вполе Memo1 – тело письма.
Для этого нам надо запрограммировать кнопку Button1 по следующему алгоритму:
procedure TForm1.Button1Click(Sender: TObject);
begin
NMPOP31.Connect;
NMPOP31.GetMailMessage(1);
Edit1.Text:=NMPOP31.MailMessage.Subject;
Memo1.Lines:=NMPOP31.MailMessage.Body;
NMPOP31.Disconnect;
end;
После прочтения письма, может возникнуть необходимость очистить ящик от него, чтобы было больше мест для последущих посланий. Это легко сделать, используя процедуру DeleteMailMessage(Number: intger), где Number – это номер письма. Процедура помечает письмо, как не нужное и после разъединения с сервером оно удаляется. Будте осторожнее с этой функцией, после удаления письма, вы его больше не востановите. Для снятия пометки, если вы вдруг передумали удалять писмо, служит метод NMPOP31.Reset. Программу можно модернизировать, создать так называемого “робота”. Установить на форму компонент Taimer, выставить время срабатывания через каждые n минут и связать его с событием нажатия на кнопку. И наша программа, работая в фоне, самостоятельно будет каждые n минуть проверять почту, регистрировать все изменения.
Вот мы и написали программку для обработки почты. Как вы видете, с помощью Delphi это легко и просто. В разделе FastNet находятся еще много компонентов для работы с протоколами Интернет. Для отправки почты можно использовать компонент NMSMTP, он работает с протоколом SMTP, который подробно расписан в документе RFC 821. С помощью компонента NMSMTP можно организавать свою почтовую рассылку по нескольким адресам, так называемую спам – рекламу. Также можно организавать redirect почты (перенаправление на другой почтовый ящик).
Диалог пользователя с сервером по протоколу SMTP также прост, как и по POP3. После соединения с сервером к 25 TCP порту, клиент посылает команду HELO свое_имя. После чего сервер выдает строку с кодом 250, которая говорит о готовности сервера для работы с вами. Далее мы должны послать команду mail from:, где email – это ваш обратный e-mail адрес. На что сервер опять ответит 250 OK. Далее набираем команду для задания адреса получателя письма rcpt to:email (email – адрес получателя). После этого, набрав команду data можно приступить к вводу заголовка письма и после пустой строки его тела, которое должно заканчиваться двумя символами перевода строки. Тело письма может состоять из следующих полей:
From: “Test” – адрес отправителя
To: “new” – адрес получателя
Subject: Проверка – тема письма
Content-Type: text/html – тип письма
И другие поля, о которых подробнее можно прочитать в документации RFC 822.
С учетом вышеизложенного можно рассмотреть диалог сервера с пользователем после ввода команды data:
User: From: “Test”
User: To: “new”
User: Subject: Проверка
User:
User: Привет! Это тестовое письмо
User: Пока!
В конце ссесии мы должны набрать команду quit.
Если вы хотите программировать для Интернет не только почтовые приложения, то вам помогут компоненты из раздела FastNet, NMFTP – для работы по популярному протоколу FTP (File Transfer Protocol – протокол передачи файлов). Он документирован в RFC 959. С помощью FTP можно закачивать информацию, веб – странички на сервер, а также скачивать программное обеспечение, документация с бесплатных FTP серверов. Компонент TNMFinger предназначен для реализации протокола Finger, который представляет собой адресную книгу в Интернете. По нему вы можете узнать информация об интересующем вас сервере. И конечно же не остался в стороне наиболее используемый протокол в сети Интернет – HTTP (HyperText Transfer Protocol). Протокол для передачи гипер – текста. Его мы ежедневно используем для просмотра страничик во Всемирной паутине WWW. Также стоит уделить внимание разделам Internet Express и Internet в палитре компонентов. В этих разделах находится удобный мини – браузер для просмотра html – страничек, компонент для разбора html – файлов и другие полезные вещи.
Исключения в Delphi.
| Семь бед - один Reset |
100-149 - ошибки ввода/вывода (I/O), 200-255 - фатальные ошибки согласно файлам помощи Delphi.
В Windows.pas всем кодам ошибок внешних исключительных ситуаций присвоены имена. Поищите, например.
STATUS_BREAKPOINT |
По этой таблице можно сразу определить причину возникновения ошибки, если ваш exe-файл не подружился с операционной системой.
| Код и описание ошибок, возникающий в среде DOS | ||
| 00h | 0 | нет ошибки |
| 01h | 1 | неверный номер функции |
| 02h | 2 | файл не найден |
| 03h | 3 | путь не найден |
| 04h | 4 | слишком много открытых файлов (нет свободных дескрипторов) |
| 05h | 5 | доступ запрещен |
| 06h | 6 | неверный дескриптор |
| 07h | 7 | управляющий блок памяти разрушен |
| 08h | 8 | недостаточно памяти |
| 09h | 9 | неверный адрес блока памяти |
| 0Ah | 10 | неверное окружение (обычно при длине > 32К) |
| 0Bh | 11 | неверный формат |
| 0Ch | 12 | неверный код доступа |
| 0Dh | 13 | неверные данные |
| 0Eh | 14 | зарезервировано |
| 0Fh | 15 | неверное устройство (drive) |
| 10h | 16 | попытка удаления текущей директории |
| 11h | 17 | не то же устройство |
| 12h | 18 | нет больше файлов |
| --- DOS 3.0+ --- | ||
| 13h | 19 | диск имеет защиту от записи |
| 14h | 20 | неизвестное устройство |
| 15h | 21 | устройство не готово |
| 16h | 22 | неизвестная команда |
| 17h | 23 | ошибка данных (CRC) |
| 18h | 24 | неправильный запрос длины структуры |
| 19h | 25 | ошибка поиска |
| 1Ah | 26 | неизвестный тип носителя (не-DOS диск) |
| 1Bh | 27 | сектор не найден |
| 1Ch | 28 | принтер без бумаги |
| 1Dh | 29 | ошибка записи |
| 1Eh | 30 | ошибка чтения |
| 1Fh | 31 | общая ошибка (general failure) |
| 20h | 32 | нарушение доступа (sharing violation) |
| 21h | 33 | нарушение доступа (lock violation) |
| 22h | 34 | ошибка смены диска (ES:DI -> media ID диска) (смотри #0981) |
| 23h | 35 | FCB недоступно |
| 24h | 36 | переполнение буфера общего доступа (sharing buffer) |
| 25h | 37 | (DOS 4.0+) несовпадение кодовой страницы |
| 26h | 38 | (DOS 4.0+) невозможно завершить действие с файлом (чтение или запись) |
| 27h | 39 | (DOS 4.0+) недостаточно места на диске |
| 28h-31h | зарезервировано | |
| 32h | 50 | сетевой запрос не поддерживается |
| 33h | 51 | удаленный компьютер не откликается |
| 34h | 52 | дублирование сетевого имени |
| 35h | 53 | сетевое имя не найдено |
| 36h | 54 | сеть занята |
| 37h | 55 | сетевое устройство больше не существует |
| 38h | 56 | превышен лимит команд сетевого BIOS |
| 39h | 57 | аппаратная ошибка сетевого адаптера |
| 3Ah | 58 | из сети получен неверный ответ |
| 3Bh | 59 | неожиданная сетевая ошибка |
| 3Ch | 60 | несовместимый сетевой адаптер |
| 3Dh | 61 | полная очередь печати |
| 3Eh | 62 | очередь не полная |
| 3Fh | 63 | нет свободного места для печати файла |
| 40h | 64 | сетевое имя было удалено |
| 41h | 65 | сеть: в доступе отказано |
| 42h | 66 | неверный тип сетевого устройства |
| 43h | 67 | сетевое имя не найдено |
| 44h | 68 | превышен лимит сетевого имени |
| 45h | 69 | превышен лимит сеансов сетевого BIOS |
| 46h | 70 | временная пауза |
| 47h | 71 | сетевой запрос не принят |
| 48h | 72 | сетевая печать/дисковая переадресация приостановлена |
| 49h | 73 | программная поддержка сети не установлена (LANtastic) неверная сетевая версия |
| 4Ah | 74 | неожиданный отказ сетевого адаптера (LANtastic) истек бюджет пользователя (account) |
| 4Bh | 75 | (LANtastic) истек пароль |
| 4Сh | 76 | (LANtastic) на этот раз неудачная попытка входа в сеть |
| 4Dh | 77 | (LANtastic v3+) не хватает дискового пространства на сетевом узле |
| 4Eh | 78 | (LANtastic v3+) нет регистрации на сетевом узле |
| 4Fh | 79 | зарезервировано |
| 50h | 80 | файл существует |
| 51h | 81 | зарезервировано |
| 52h | 82 | невозможно создать каталог |
| 53h | 83 | ошибка на INT 24h |
| 54h | 84 | (DOS 3.3+) слишком много переадресаций |
| 55h | 85 | (DOS 3.3+) двойная переадресация |
| 56h | 86 | (DOS 3.3+) неверный пароль |
| 57h | 87 | (DOS 3.3+) неверный параметр |
| 58h | 88 | (DOS 3.3+) ошибка сетевой записи |
| 59h | 89 | (DOS 4.0+) функция в сети не поддерживается |
| 5Ah | 90 | (DOS 4.0+) не установлен необходимый системный компонент |
| 64h | 100 | (MSCDEX) неизвестная ошибка |
| 65h | 101 | (MSCDEX) нет готовности |
| 66h | 102 | (MSCDEX) нехватка EMS памяти |
| 67h | 103 | (MSCDEX) не High Sierra или ISO-9660 формат |
| 68h | 104 | (MSCDEX) открыт лоток |
| B0h | 176 | (MS-DOS 7.0) носитель не блокирован |
| B1h | 177 | (MS-DOS 7.0) носитель блокирован |
| B2h | 178 | (MS-DOS 7.0) не сменный носитель |
| B4h | 180 | (MS-DOS 7.0) переполнение счетчика блокировок |
| B5h | 181 | (MS-DOS 7.0) неудача запроса на извлечение носителя |
Delphi для качков.
Автор: Лозовский Александр Прислал: Матвеев ИгорьСтатья из журнала Хакер за 04.2003
Любому человеку, мало-мальски знакомому с интернетом, известны такие программы-качалки, как GetRight, Reget и Flashget. Их расплодилось великое множество, все они занимают первые места в рейтингах и продаются за немалые деньги. FlashGet, например, постоянно требует от меня заплатить буржую $29.99 за дальнейшее использование его программы. Все это, конечно, понятно, хочется денег, да побольше... Но разве русский человек может заплатить такую сумму?:) Так что давай сегодня напишем свой Reget, и ты сможешь демонстрировать всем знакомым девушкам свою физиономию в about программы :).
Реквизит.
Он нам понадобится. Прошли те времена, когда все делалось в два диалога и одну строчку кода. Нам придется писать программу с использованием функций библиотеки Winlnet.dll и заголовочного файла, соответственно, Winlnet.Pas. Сразу пропиши его в uses, а то потом забудешь и начнешь тыкаться, искать свою ошибку. Так вот, давай для начала попробуем разобраться с самыми необходимыми функциями, а с остальными ты разберешься сам на msdn.microsoft.com (полный линк давать не буду, т.к. он ОЧЕНЬ большой). Посмотри там следующие функции: InternetDial, InternetGoOnline или InternetCrackUrl (думаю, эта функция тебя должна заинтересовать :)). Но вернемся к реальности. У нас на повестке дня следующие функции:
1) function lnternetOpen(lpszAgent: PChar; dwAccessType: DWORD; IpszProxyName, IpszProxyBypass: PChar; dwRags: DWORD): HINTERNET; stdcall;
Она открывает интернет-сессию для приложения. Вот какие у нее аргументы:
IpszAgent - имя программы. Серьезные люди пишут application.exename, а старики - ParamStr(O). На самом деле это не так важно, программа все равно будет работать. dwAccessType - способ соединения. Вот его типы: PRE_CONFIGJNTERNET_ACCESS - как в реестре. INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY - не юзать internet setup file. GATEWAY INTERNET ACCESS - через шлюз. CERN_PROXYJNTERNET_ACCESS - через прокси. IpszProxyName - имя прокси. IpszProxyBypass - кому не надо использовать проксю. dwFlags - режим работы. Если ставить INTERNET_FLAG_ASYNC, то будет асинхронный. В данном случае это только дополнительный напряг, поэтому ставь 0.
2) function lnternetOpenUrl(hlnet: HINTERNET; IpszUrl: PChar; IpszHeaders: PChar; dwHeadersLength: DWORD; dwFlags: DWORD; dwContext: DWORD): HINTERNET; stdcall;
Это функция открывает заданный УРЛ! :) Ее описание:
hinet - переменная типа HINTERNET. Ее значение возвращает функция InternetOpen. IpszUrl - собственно сам УРЛ. IpszHeaders - дополнительные строки в HTTP запросе. Нам они не нужны. dwHeadersLength - их длина. dwFlags - их тут больше 10 значений. Вот самое нужное: INTERNET_FLAG_EXISTING_CONNECT - не создавать для объекта нового соединения. dwContext - пиши 0.
3) function lnternetReadFile(hFile: HINTERNET; IpBuffer: Pointer; dwNumberOfBytesToRead: DWORD; var IpdwNumberOfBytesRead: DWORD): BOOL; stdcall;
InternetReadFile читает удаленный файл. Если ты знаком со старой доброй ReadFile (или JRead), то поймешь сам, а это для тех, кто не знает:
hFile - сюда ты подставляешь значение из предыдущей функции (можно и FtpOpenFile, если тебе это ближе). IpBuffer - буфер, через него мы будем читать файл. Как ты должен помнить, буфер- это массив. Таким образом, файл читается кусками, равными размеру этого массива, а у нас он объемом 1024 байта, т.е. один килобайт. dwNumberOfBytesToRead - какое количество байт необходимо прочесть. Он должен быть равен размеру нашего массива, т.е. 1024. IpdwNumberOfBytesRead - сколько же действительно байт прочитано. Если все отлично, то функция возвращает true, иначе - false.
4) function InternetSetRlePointerfhFile: HINTERNET; IDistanceToMove: Longint; pReserved: Pointer; dwMoveMethod, dwContext: DWORD): DWORD; stdcall; Для незнакомых с SetFilePointer поясню. Эта функция сдвигает позицию чтения файла на заданное число байт. Т.е. если тебе надо прочитать файл не с начала, а с отметки 1000 байт, то пользуйся InternetSetFilePointer. Вот ее параметры: hFile - этот параметр уже рассматривался. IDistanceToMove - на какое количество байт смещать указатель. pReserved - оставлено до лучших времен, а само значение должно быть равно нулю. dwMoveMethod - откуда делать смещение: FILE_BEGIN - с начала. FILE_END - с конца :). FILECURRENT - с текущей позиции. dwContext - должно быть нулем. Как ты уже догадался, эта функция и будет обеспечищгь нам докачку. Если конда прервется на отметке 1.2 Мб, то мы сможем вернуться на нужную нам позицию. При успешном возврате функция вернет значение в 1.2 Мб. Но учти, если сервак не поддерживает докачки, то файл придется читать с самого начала. 5) function InternetQueryDataAvailable(hFile: IpdwNumberOfBytesAvailable: DWORD; dwFlags, dwContext: DWORD): BOOL; stdcall; HINTERNET; Она выясняет объем доступных данных, т.е. размер запрашиваемого файла. Пояснения:hFile - переменная типа HINTERNET. Уже рассматривалась выше. IpdwNumberOfBytesAvailable - доступные байты. dwFlags - ставь в 0. dwContext - здесь также установи 0.
6) function InternetCloseHandle(hlnet: HINTERNET): BOOL; stdcall;
В InternetCloseHandle нет ничего сложного. Эта функция просто закрывает интернет-сессию.
Все. С разбором функций мы закончили. Их тебе хватит для написания примитивного гетрайта :). А если ты ознакомишься с МSDN'овскими доками и поймешь )аботу потоков... Тогда я буду ждать 80% скидки на твой VasyaExtraGet за 9.99$ ). Так что закрывай журнальчик, попей пивка, и садись кодить. Главное, не убей правильное настроение. Если его пока нет, не расстраивайся, будем писать вместе :).
Интерфейс.
Кидай на форму два TEdit, четыре TLabel, SaveDialog и 4 Кнопки. Постарайся разложить это добро как на рис.1:

Первые три кнопки обзови (параметр "caption"): "Загрузить", "Отмена" и "Выход", а на четвертой поставь 3 точки. Lаbеl'ы будут называться так:
Label1: "Откуда качать?" Label2: "А куда сохранять?" Label3: "Размер файла:" Label4: "О".
В общем, постарайся соответствовать рисунку. На нем все предельно ясно, так что перейдем к самому процессу кодинга.
Кодинг.
Для начала добавь в раздел public объявление переменной NADO: boolean; (она нужна для прерывания загрузки), создай событие OnClick для 4-й кнопки и впиши туда такой код:
IF SaveDialogl.Execute then Edit2.Text := SaveDialogl.FileName;
Этот код добавлен, чтобы не вводить путь вручную. Теперь посмотри на код ниже. Попытайся понять содержимое этого листинга. Понял? Не понял? :) В общем, набей его в свой проект. Логика работы программы такая. Сначала мы проверяем наличие заданного файла. Если его нет, то качаем с нуля, если же он существует, то за начальную позицию для докачки берем размер локального файла и подставляем это значение в InternetSetFilePointer. Что мы и делаем. Затем циклически читаем по 1024 байта от интернет-файла, пока не скачаем его целиком. Это и будет конец загрузки. Хотя, на случай ручного прерывания, впиши в OnClick для 2-й кнопки такой код:
NADO := FALSE
Все остальное ясно и по комментариям, поэтому я протестирую эту программу и перейду к заключению.
Вот что нужно вписать в OnClick для кнопок:
procedure TForml .BitBtn1Click(Sender: TObject);
var
F: File;
ResumePos, BufferLen, SumSize: DWORD;
hSession, hURL: Hlnternet;
Buffer: array[1..1024] of Byte;
err: boolean;
begin
SumSize := 0; ResumePos := 0; //Инициализируемся
AssignFile(F, Edit2.Text); //Свяжемся с файлом
IF FileExists(Edit2.Text) then //Есть ли на диске этот файл
begin
Reset(f,1); //Ax, есть? Откроем!
ResumePos := FileSize(F); //Откуда докачать
Seek(F, FileSize(F)); //А писать будем в конец
end else ReWrite(f,1); //А раз нет, так создадим
NADO := TRUE; //Надо качать...
//Открыли сессию
hSession := lnternetOpen('X-Kachalka', PRE_CONFIG INTERNET_ACCESS, nil, nil, 0);
//И наш УРЛ
hURL := lnternetOpenURL(hSession,PChar(Edit1.Text),nil, O, 0, O);
//Сколько там наш файл весит?
lnternetQueryDataAvailable(hURL, SumSize, 0, 0);
labe4.Caption := IntToStr(SumSize); //Сообщим об этом
if ResumePos>0 then //Если докачиваем,
begin
lnternetSetFilePointer(hURL,ResumePos,nil,0,0); //То сместимся
end;
REPEAT //Качаем
err:= lnternetReadFile(hURL, @Buffer,SizeOf(Buffer),BufferLen); //Читаем буфер
IF err= false then //Ошибка чтения
begin
ShowMessage ('Произошел облом :('); //Сообщим и выходим
exit;
end;
BlockWrite(f, Buffer, BufferLen); //Пишем в файл
Application. Processmessages;
UNTIL (BufferLen- 0) Or (NADO= FALSE); //Качаем, пока не все или надо
ShowMessage ('Успешно загружено!');
end;
Пять минут - полет нормальный.
Я запустил закачку файла, но в середине процесса у меня подло прервалась связь (случайно задел модем ногой, он упал со стола и выдернулся из сети), за что я и словил известное тебе сообщение. Подняв модем и восстановив коннект, я запустил докачку и успешно слил файл. Заметь, с весьма неплохой скоростью, а все это благодаря компании Майкрософт и нашим с тобой прямым ручкам.
Заключение.
Программа получилась очень простой, и в твоих руках возможность довести ее до нужного уровня: убрать цикл в отдельный поток, а иначе будет тормозить интерфейс, добавить различные прогрессбары и прочую приятную лабуду (][ об этом писал не раз).
Загрузить исходники.
На этом все. Удачи тебе и до новых встреч в эфире.
Фреймы в Delphi.

| Разведение программистов, к сожалению, связано с большими первоначальными затратами. Вам понадобятся: - персональный компьютер с процессором не ниже Реntiиm II; - стол и стул; - пепельница; - большое количество сарделек и пиво для прикармливания; - кофейная чашечка вместимостью 0,5 л. |
Еще одна полезность этой компонеты в том, что она позволяет организовать скролл(как вертикальный, так и горизонтальный) для целого набора инструментов. Т.е. если Вам необходимо разместить на форме очень много всего, а места не хватает, то фрейм - идеальное решение. Примером может служить настройка сортировщика писем в известной почтовой программе 'TheBat'.
Delphi и графика.
Если вам нужно просто вывести одно изображение, создайте объект TBitmap, "поиграйтесь" с ним, а когда изображение будет готово появиться на экране, вызовите функцию Image.Canvas.Draw(0, 0, Bitmap), которая скопирует и нарисует его на экране. Как вы наверное заметили, основное время занимает рисование картинки на экране, а не установка ее атрибутов. В результате мы устанавливаем цвета на невидимом объекте (TBitmap мы уже создали), и отображаем только полностью готовый Bitmap. Вот демонстрационный код для формы с единственным на ней компонентом Image:
procedure TForm1.FormPaint(Sender: TObject);
var
TmpX, TmpY: Byte;
MyImage: TBitmap;
begin
Form1.Width := 260;
Form1.Height := 260;
Image1.Width := 250;
Image1.Height := 250;
Image1.top := 5;
Image1.width := 5;
MyImage := TBitmap.Create;
MyImage.Width := 250;
MyImage.Height := 250;
for TmpX := 0 to 249 do
for TmpY := 0 to 249 do
MyImage.Canvas.Pixels[TmpX, TmpY] :=
RGB(TmpX, 250 - TmpY, (TmpX + TmpY div 2));
Image1.Canvas.Draw(0, 0, MyImage);
MyImage.Free;
end;
|
Если вы хотите сделать действительно быструю графику, взгляните на функции GDI (API) и/или функции WinG, которые для вас разработали программисты Microsoft. Трактовка их для данной статьи немного скучна, да и не имеет никакого отношения к Delphi.
Использование открытых интерфейсов среды программирования Delphi.
| Экскурсия по цехам. Монолог экскурсовода. - Вот тут у нас делаются компакт-диски... Работают роботы под управлением операционной системы Юникс... Посмотрели?.. Идём дальше... - А вот тут у нас собираются и разрисовываются те красивые лицензионные коробочки... Роботы управляются системой OS/2... [Входят через бронированную дверь с зарешёченным окошком и здоровенным замком] - А вот тут работают роботы под управлением Windows 95... Наденьте, пожалуйста, защитные костюмы... |
Одной и наиболее сильных сторон среды программирования Delphi является ее открытая архитектура, благодаря которой Delphi допускает своего рода метапрограммирование, позволяя “программировать среду программирования”. Такой подход переводит Delphi на качественно новый уровень систем разработки приложений и позволяет встраивать в этот продукт дополнительные инструментальные средства, поддерживающие практически все этапы создания прикладных систем. Столь широкий спектр возможностей открывается благодаря реализованной в Delphi концепции так называемых открытых интерфейсов, являющихся связующим звеном между IDE (Integrated Development Environment) и внешними инструментами. Данная статья посвящена открытым интерфейсам Delphi и представляет собой обзор представляемых ими возможностей.
В Delphi определены шесть открытых интерфейсов: Tool Interface, Design Interface, Expert Interface, File Interface, Edit Interface и Version Control Interface. Вряд ли в рамках данной статьи нам удалось бы детально осветить и проиллюстрировать возможности каждого из них. Более основательно разобраться в рассматриваемых вопросах вам помогут исходные тексты Delphi, благо разработчики снабдили их развернутыми комментариями. Объявления классов, представляющих открытые интерфейсы, содержатся в соответствующих модулях в каталоге ...\Delphi\Source\ToolsAPI.
Design Interface (модуль DsgnIntf.pas) предоставляет средства для создания редакторов свойств и редакторов компонентов. Редакторы свойств и компонентов – это тема, достойная отдельного разговора, поэтому напомним лишь, что редактор свойства контролирует поведение Инспектора Объектов при попытке изменить значение соответствующего свойства, а редактор компонента активизируется при двойном нажатии левой кнопки мыши на изображении помещенного на форму компонента. Version Control Interface (модуль VCSIntf.pas) предназначен для создания систем контроля версий. Начиная с версии 2.0, Delphi поддерживает интегрированную систему контроля версий Intersolv PVCS, поэтому в большинстве случаев в разработке собственной системы нет необходимости. По этой причине рассмотрение Version Control Interface мы также опустим. File Interface (модуль FileIntf.pas) позволяет переопределить рабочую файловую систему IDE, что дает возможность выбора собственного способа хранения файлов (в Memo-полях на сервере БД, например). Edit Interface (модуль EditIntf.pas) предоставляет доступ к буферу исходных текстов, что позволяет проводить анализ кода и выполнять его генерацию, определять и изменять позицию курсора в окне редактора кода, а также управлять синтаксическим выделением исходного текста. Специальные классы предоставляют интерфейсы к помещенным на форму компонентам (определение типа компонента, получение ссылок на родительский и дочерние компоненты, доступ к свойствам, передача фокуса, удаление и т.д.), к самой форме и к ресурсному файлу проекта. Также Edit Interface позволяет идентифицировать так называемые модульные нотификаторы, определяющие реакцию на такие события, как изменение исходного текста модуля, модификация формы, переименование компонента, сохранение, переименование или удаление модуля, изменение ресурсного файла проекта и т. д. Tool Interface (модуль ToolIntf.pas) предоставляет разработчикам средства для получения общей информации о состоянии IDE и выполнения таких действий, как открытие, сохранение и закрытие проектов и отдельных файлов, создание модуля, получение информации о текущем проекте (число модулей и форм, их имена и т. д.), регистрация файловой системы, организация интерфейсов к отдельным модулям и т.д. В дополнение к модульным нотификаторам Tool Interface определяет add-in нотификаторы, уведомляющие о таких событиях, как открытие/закрытие файлов и проектов, загрузка и сохранение desktop-файла проекта, добавление/исключение модулей проекта, инсталляция/деинсталляция пакетов, компиляция проекта, причем в отличие от модульных нотификаторов add-in нотификаторы позволяют отменить выполнение некоторых событий. Кроме того, Tool Interface предоставляет средства доступа к главному меню IDE Delphi, позволяя встраивать в него дополнительные пункты. Expert Interface (модуль ExptIntf.pas) представляет собой основу для создания экспертов — программных модулей, встраиваемых в IDE c целью расширения ее функциональности. В качестве примера эксперта можно привести входящий в Delphi Database Form Wizard, выполняющий генерацию формы для просмотра и изменения содержимого таблицы БД.Эксперты бывают нескольких типов (стилей):
| Стиль | Описание |
| esStandard | Для каждого эксперта такого стиля IDE добавляет пункт меню Tools/..., при выборе которого эксперт активизируется (IDE вызывает его метод Execute) |
| esForm esProject | IDE рассматривает эксперты данного стиля как шаблоны форм/проектов и помещает активизирующие их изображения в галерею Object Repository. |
| esAddIn | Эксперты подобного стиля обеспечивают собственный интерфейс с IDE |
Класс каждого эксперта является потомком базового класса TIExpert, содержащего серию абстрактных методов, которые необходимо перекрыть в порождаемом классе:
| Метод | Описание |
| GetName | Должен возвращать имя эксперта |
| GetAuthor | Должен возвращать имя автора эксперта. Это имя отображается в Object Repository |
| GetComment | Должен возвращать комментарий (1-2 предложения), поясняющий назначение эксперта. Используется в Object Repository |
| GetPage | Должен возвращать название страницы Object Repository, на которую IDE поместит соответствующее эксперту изображение |
| GetGlyph | Должен возвращать дескриптор (HICON, в Delphi 1.0 – HBITMAP) соответствующего эксперту изображения в ObjectRepository |
| GetStyle | Должен возвращать константу, соответствующую стилю эксперта (esStandard/esForm/esProject/esAddIn) |
| GetState | Если возвращаемое множество содержит константу esChecked, IDE пометит соответствующий эксперту пункт меню “галочкой”, а если множество содержит константу esEnabled, то IDE сделает этот пункт меню доступным для выбора |
| GetIDString | Должен возвращать строку – идентификатор эксперта, уникальную среди всех установленных экспертов. По соглашению, формат этой строки таков: Имя_Компании.Назначение_эксперта, например: Borland.WidgetExpert |
| GetMenuText | Должен возвращать текст, отображаемый в пункте меню эксперта. Этот метод вызывается каждый раз, когда раскрывается родительское меню, что позволяет сделать пункт меню контекстно-чувствительным |
| Execute | Вызывается при вызове эксперта через меню или Object Repository (в зависимости от стиля) |
Набор методов, подлежащих перекрытию, зависит от стиля эксперта:
| Метод | esStandard | esForm | esProject | esAddIn |
| GetName | + | + | + | + |
| GetAuthor | + | + | ||
| GetComment | + | + | ||
| GetPage | + | + | ||
| GetGlyph | + | + | ||
| GetStyle | + | + | + | + |
| GetState | + | |||
| GetIDString | + | + | + | + |
| GetMenuText | + | |||
| Execute | + | + | + | |
Определив класс эксперта, необходимо позаботиться о том, чтобы Delphi “узнала” о нашем эксперте. Для этого его нужно зарегистрировать посредством вызова процедуры RegisterLibraryExpert, передав ей в качестве параметра экземпляр класса эксперта.
В качестве иллюстрации создадим простой эксперт в стиле esStandard, который при выборе соответствующего ему пункта меню Delphi выводит сообщение о том, что он запущен. Как видно из вышеприведенной таблицы, стиль esStandard обязывает перекрыть шесть методов:
unit exmpl_01;
{ STANDARD EXPERT }
interface
uses
Dialogs, ExptIntf;
type
{ класс эксперта является потомком базового класса TIExpert }
TEMyExpert = class( TIExpert)
function GetName: string; override;
function GetStyle: TExpertStyle; override;
function GetIDString: string; override;
function GetMenuText: string; override;
function GetState: TExpertState; override;
procedure Execute; override;
end;
procedure register;
implementation
{ возвращаем имя эксперта }
function TEMyExpert.GetName: string;
begin
Result := 'My Simple Expert 1';
end;
{ возвращаем стиль эксперта }
function TEMyExpert.GetStyle: TExpertStyle;
begin
Result := esStandard;
end;
{ возвращаем строку - идентификатор эксперта }
function TEMyExpert.GetIDString: string;
begin
Result := 'Doomy.SimpleAddInExpert_1';
end;
{ возвращаем текст пункта меню }
function TEMyExpert.GetMenuText: string;
begin
Result := 'Simple Expert 1';
end;
{ возвращаем множество, характеризующее состояние пункта меню эксперта }
{ (доступность, наличие "галочки"); в данном случае пункт меню доступен, }
{ а "галочка" отсутствует }
function TEMyExpert.GetState: TExpertState;
begin
Result := [esEnabled];
end;
{ при выборе пункта меню эксперта отображаем сообщение }
procedure TEMyExpert.Execute;
begin
MessageDlg('Standard Expert Started!', mtInformation, [mbOK], 0);
end;
{ регистрируем эксперт }
procedure register;
begin
RegisterLibraryExpert( TEMyExpert.Create);
end;
end.
|
Для того чтобы эксперт был “приведен в действие”, необходимо выбрать пункт меню Component/Install Component ... , выбрать в диалоге Browse модуль, содержащий эксперт (в нашем случае exmpl_01.pas), нажать ОК, и после компиляции пакета dclusr30.dpk в главном меню Delphi в разделе Help должен появиться пункт Simple Expert 1, при выборе которого появляется информационное сообщение “Standard Expert started!”.
Почему Delphi помещает пункт меню эксперта в раздел Help, остается загадкой. Если вам не нравится то, что пункт меню появляется там, где угодно Delphi, а не там, где хотите вы, возможен следующий вариант: создать эксперт в стиле add-in, что исключает автоматическое создание пункта меню, а пункт меню добавить “вручную”, используя средства Tool Interface. Это позволит задать местоположение нового пункта в главном меню произвольным образом. Для добавления пункта меню используется класс TIToolServices — основа Tool Interface — и классы TIMainMenuIntf, TIMenuItemIntf, реализующие интерфейсы к главному меню IDE и его пунктам. Экземпляр ToolServices класса TIToolServices создается самой IDE при ее инициализации. Обратите внимание на то, что ответственность за освобождение интерфейсов к главному меню Delphi и его пунктам целиком ложится на разработчика. Попутно немного усложним функциональную нагрузку эксперта: при активизации своего пункта меню он будет выдавать справку об имени проекта, открытого в данный момент в среде:
unit exmpl_02;
{ ADD-IN EXPERT, ДОБАВЛЕНИЕ ПУНКТА В ГЛАВНОЕ МЕНЮ IDE DELPHI }
interface
uses
Classes, Dialogs, ToolIntF, ExptIntf, Menus;
type
{ класс эксперта является потомком базового класса TIExpert }
TEMyExpert = class( TIExpert)
private
MenuItem: TIMenuItemIntf;
public
constructor Create;
destructor Destroy; override;
function GetName: string; override;
function GetStyle: TExpertStyle; override;
function GetIDString: string; override;
procedure MenuItemClick( Sender: TIMenuItemIntf);
end;
procedure register;
function AddIDEMenuItem( const Caption, name, PreviousItemName: string;
const ShortCutKey: Char; OnClick: TIMenuClickEvent): TIMenuItemIntf;
implementation
{ добавляем пункт в главное меню IDE Delphi: }
{ 1) текст вставляемого пункта меню - 'Simple Expert 2'; }
{ 2) идентификатор вставляемого пункта меню - 'ViewMyExpertItem2'; }
{ 3) идентификатор пункта меню, перед которым добавляется новый }
{ пункт меню - 'ViewWatchItem' (для Delphi 5 - 'ViewWatchesItem');}
{ 4) горячая клавиша вставляемого пункта - 'Ctrl + 2'; }
{ 5) обработчик события, соответствующего выбору вставляемого пункта }
{ меню - MenuItemClick }
constructor TEMyExpert.Create;
begin
inherited Create;
MenuItem:= AddIDEMenuItem( 'Simple Expert 2', 'ViewMyExpertItem2',
{$IFDEF VER130}
'ViewWatchesItem', '2', MenuItemClick);
{$ELSE}
'ViewWatchItem', '2', MenuItemClick);
{$ENDIF}
end;
destructor TEMyExpert.Destroy;
begin
if Assigned( MenuItem) then
MenuItem.Free;
inherited Destroy;
end;
{ при выборе пункта меню эксперта отображаем сообщение, содержащее }
{ имя активного проекта }
procedure TEMyExpert.MenuItemClick( Sender: TIMenuItemIntf);
begin
MessageDlg( 'Current project name is ' + ToolServices.GetProjectName,
mtInformation, [mbOK], 0);
end;
{ возвращаем имя эксперта }
function TEMyExpert.GetName: string;
begin
Result := 'My Simple Expert 2';
end;
{ возвращаем стиль эксперта }
function TEMyExpert.GetStyle: TExpertStyle;
begin
Result := esAddIn;
end;
{ возвращаем строку - идентификатор эксперта }
function TEMyExpert.GetIDString: string;
begin
Result := 'Doomy.SimpleAddInExpert_2';
end;
function AddIDEMenuItem( const Caption, name, PreviousItemName: string;
const ShortCutKey: Char; OnClick: TIMenuClickEvent): TIMenuItemIntf;
var
MainMenu: TIMainMenuIntf;
MenuItems, PreviousItem, ParentItem: TIMenuItemIntf;
begin
Result:= nil;
{ получаем интерфейс пунктов главного меню IDE }
MainMenu:= ToolServices.GetMainMenu;
if Assigned( MainMenu) then
try
{ получаем интерфейс пунктов верхнего уровня меню }
MenuItems:= MainMenu.GetMenuItems;
if Assigned( MenuItems) then
try
{ ищем пункт меню перед которым необходимо вставить новый пункт }
PreviousItem:= MainMenu.FindMenuItem( PreviousItemName);
if Assigned( PreviousItem) then
try
{ получаем интерфейс к родительскому пункту меню }
ParentItem:= PreviousItem.GetParent;
if Assigned( ParentItem) then
try
{ вставляем новый пункт меню и в качестве результата функции }
{ возвращаем его интерфейс }
Result:= ParentItem.InsertItem( PreviousItem.GetIndex, Caption,
name, '', ShortCut( Word( ShortCutKey), [ssCtrl]), 0, 0,
[mfVisible, mfEnabled], OnClick);
finally
{ освобождаем интерфейс родительского пункта меню }
ParentItem.Free;
end;
finally
{ освобождаем интерфейс пункта меню перед которым вставили }
{ новый пункт }
PreviousItem.Free;
end;
finally
{ освобождаем интерфейс пунктов верхнего уровня меню }
MenuItems.Free;
end;
finally
{ освобождаем интерфейс главного меню IDE }
MainMenu.Free;
end;
end;
procedure register;
begin
{ регистрируем эксперт }
RegisterLibraryExpert( TEMyExpert.Create);
end;
end.
|
В этом примере центральное место занимает функция AddIDEMenuItem, осуществляющая добавление пункта меню в главное меню IDE Delphi. В качестве параметров ей передаются текст нового пункта меню, его идентификатор, идентификатор пункта, перед которым вставляется новый пункт, символьное представление клавиши, которая вместе с клавишей Ctrl может использоваться для быстрого доступа к новому пункту, и обработчик события, соответствующего выбору нового пункта. Мы добавили новый пункт меню в раздел View перед пунктом Watches.
Теперь познакомимся с нотификаторами. Определим add-in нотификатор, отслеживающий моменты закрытия/открытия проектов и корректирующий соответствующим образом поле, хранящее имя активного проекта (реализацию методов, не претерпевших изменений по сравнению с предыдущим примером, для краткости опустим):
unit exmpl_03;
{ ИСПОЛЬЗОВАНИЕ ADD-IN НОТИФИКАТОРОВ }
interface
uses
Classes, Dialogs, ToolIntF, ExptIntf, Menus;
type
TEMyExpert = class;
{ касс add-in нотификатора порождаем от TIAddInNotifier}
TAddInNotifier = class(TIAddInNotifier)
private
Expert: TEMyExpert;
public
constructor Create( anExpert: TEMyExpert);
procedure FileNotification( NotifyCode: TFileNotification;
const FileName: string; var Cancel: Boolean); override;
end;
{ класс эксперта является потомком базового класса TIExpert }
TEMyExpert = class( TIExpert)
private
ProjectName: string;
MenuItem: TIMenuItemIntf;
AddInNotifier: TAddInNotifier;
public
constructor Create;
destructor Destroy; override;
function GetName: string; override;
function GetStyle: TExpertStyle; override;
function GetIDString: string; override;
procedure MenuItemClick( Sender: TIMenuItemIntf);
end;
procedure register;
function AddIDEMenuItem( const Caption, name, PreviousItemName: string;
const ShortCutKey: Char; OnClick: TIMenuClickEvent): TIMenuItemIntf;
implementation
constructor TAddInNotifier.Create;
begin
inherited Create;
Expert := anExpert;
end;
procedure TAddInNotifier.FileNotification( NotifyCode: TFileNotification;
const FileName: string; var Cancel: Boolean);
begin
with Expert do
case NotifyCode of
fnProjectOpened:
ProjectName:= FileName; { открытие проекта }
fnProjectClosing:
ProjectName:= 'unknown' { закрытие проекта }
end;
end;
constructor TEMyExpert.Create;
begin
inherited Create;
{ добавляем пункт в главное меню IDE Delphi }
MenuItem:= AddIDEMenuItem( 'Simple Expert 3', 'ViewMyExpertItem3',
{$IFDEF VER130}
'ViewWatchesItem', '3', MenuItemClick);
{$ELSE}
'ViewWatchItem', '3', MenuItemClick);
{$ENDIF}
try
{ создаем add-in нотификатор }
AddInNotifier:= TAddInNotifier.Create( Self);
{ регистрируем add-in нотификатор }
ToolServices.AddNotifier( AddInNotifier);
except
AddInNotifier:= nil;
end;
{ инициализируем поле, хранящее имя активного проекта }
ProjectName:= ToolServices.GetProjectName;
end;
destructor TEMyExpert.Destroy;
begin
if Assigned( MenuItem) then
MenuItem.Free;
if Assigned( AddInNotifier) then
begin
{ снимаем регистрацию add-in нотификатора }
ToolServices.RemoveNotifier( AddInNotifier);
{ уничтожаем add-in нотификатор }
AddInNotifier.Free;
end;
inherited Destroy;
end;
{ при выборе пункта меню эксперта отображаем сообщение, содержащее }
{ имя активного проекта }
procedure TEMyExpert.MenuItemClick( Sender: TIMenuItemIntf);
begin
MessageDlg( 'Current project name is ' + ProjectName,
mtInformation, [mbOK], 0);
end;
...
end.
|
Для реализации нотификатора мы определили класс TAddInNotifier, являющийся потомком TIAddInNotifier, и перекрыли метод FileNotification. IDE будет вызывать этот метод каждый раз, когда происходит событие, на которое способен среагировать add-in нотификатор (каждое такое событие обозначается соответствующей константой типа TFileNotification). Поле Expert в классе TAddInNotifier служит для обратной связи с экспертом (метод TAddInNotifier.FileNotification). В деструкторе эксперта регистрация нотификатора снимается, и нотификатор уничтожается.
А теперь проиллюстрируем использование модульных нотификаторов. Создадим add-in эксперт, выдающий сообщения о каждом акте сохранения проектного файла (реализацию уже знакомых нам методов для краткости не приводим):
unit exmpl_04;
{ ИСПОЛЬЗОВАНИЕ МОДУЛЬНЫХ НОТИФИКАТОРОВ }
interface
uses
Classes, Dialogs, ToolIntF, ExptIntf, Menus
{$IFDEF VER130}, EditIntf{$ENDIF};
type
{ класс модульного нотификатора порождаем от TIModuleNotifier }
TModuleNotifier = class( TIModuleNotifier)
private
FileName: string;
public
constructor Create(const aFileName: string);
procedure Notify( NotifyCode: TNotifyCode); override;
{$IFDEF VER130}
procedure ComponentRenamed(ComponentHandle: Pointer;
const OldName, NewName: string); override;
{$ELSE}
procedure ComponentRenamed( const oldName, newName: string); override;
{$ENDIF}
end;
TEMyExpert = class;
{ класс add-in нотификатора порождаем от TIAddInNotifier}
TAddInNotifier = class(TIAddInNotifier)
private
Expert: TEMyExpert;
public
constructor Create( anExpert: TEMyExpert);
procedure FileNotification( NotifyCode: TFileNotification;
const FileName: string; var Cancel: Boolean); override;
end;
{ класс эксперта является потомком базового класса TIExpert }
TEMyExpert = class( TIExpert)
private
AddInNotifier: TAddInNotifier;
ModuleInterface: TIModuleInterface;
ModuleNotifier: TModuleNotifier;
public
constructor Create;
destructor Destroy; override;
function GetName: string; override;
function GetStyle: TExpertStyle; override;
function GetIDString: string; override;
procedure AddModuleNotifier( const FileName: string);
procedure RemoveModuleNotifier;
end;
procedure register;
implementation
constructor TModuleNotifier.Create(const aFileName: string);
begin
inherited Create;
FileName := aFileName;
end;
procedure TModuleNotifier.Notify( NotifyCode: TNotifyCode);
begin
{ если произошло сохранение соответствующего нотификатору файла, }
{ то выдаем сообщение об этом }
if NotifyCode = ncAfterSave then
MessageDlg(FileName + 'saved', mtInformation, [mbOK], 0);
end;
procedure TModuleNotifier.ComponentRenamed;
begin
{ ничего здесь не делаем, но метод необходимо перекрыть }
end;
procedure TAddInNotifier.FileNotification( NotifyCode: TFileNotification;
const FileName: string; var Cancel: Boolean);
begin
with Expert do
case NotifyCode of
fnProjectOpened: { открытие проекта }
{ добавляем модульный нотификатор }
AddModuleNotifier( FileName);
fnProjectClosing: { закрытие проекта }
{ освобождаем модульный нотификатор }
RemoveModuleNotifier;
end;
end;
constructor TEMyExpert.Create;
begin
inherited Create;
try
{ создаем add-in нотификатор }
AddInNotifier:= TAddInNotifier.Create( Self);
{ регистрируем add-in нотификатор }
ToolServices.AddNotifier( AddInNotifier);
except
AddInNotifier:= nil;
end;
{ добавляем модульный нотификатор }
AddModuleNotifier( ToolServices.GetProjectName);
end;
destructor TEMyExpert.Destroy;
begin
if Assigned( AddInNotifier) then
begin
{ снимаем регистрацию add-in нотификатора }
ToolServices.RemoveNotifier( AddInNotifier);
{ уничтожаем add-in нотификатор }
AddInNotifier.Free;
end;
{ освобождаем модульный нотификатор }
RemoveModuleNotifier;
inherited Destroy;
end;
procedure TEMyExpert.AddModuleNotifier;
begin
{ если модульный нотификатор для проектного файла уже зарегистрирован, }
{ то никаких действий не выполняем, во избежание появления дубликатов }
{ нотификаторов; в противном случае дубликаты могли бы появиться, }
{ например, при открытии Delphi: один нотификатор добавился бы при }
{ создании эксперта (в конструкторе класса эксперта), а второй - при }
{ открытии проекта (в TAddNotifier.FileNotification }
if Assigned( ModuleInterface) and Assigned( ModuleNotifier) then
Exit;
try
{ получаем интерфейс модуля }
ModuleInterface:= ToolServices.GetModuleInterface( FileName);
try
{ создаем модульный нотификатор }
ModuleNotifier:= TModuleNotifier.Create( FileName);
{ регистрируем модульный нотификатор }
ModuleInterface.AddNotifier( ModuleNotifier);
except
ModuleNotifier:= nil;
end;
except
ModuleInterface:= nil;
end;
end;
procedure TEMyExpert.RemoveModuleNotifier;
begin
if Assigned(ModuleNotifier) then
begin
if Assigned( ModuleInterface) then
{ снимаем регистрацию модульного нотификатора }
ModuleInterface.RemoveNotifier( ModuleNotifier);
{ уничтожаем модульный нотификатор }
ModuleNotifier.Free;
ModuleNotifier:= nil;
end;
if Assigned( ModuleInterface) then
begin
{ освобождаем модульный интерфейс }
ModuleInterface.Free;
ModuleInterface:= nil;
end;
end;
...
end.
|
В данном примере add-in эксперт отслеживает события, соответствующие открытию/закрытию проектов. При каждом открытии проекта регистрируется модульный нотификатор, соответствующий файлу проекта. В плане реализации модульные нотификаторы схожи с add-in нотификаторами: мы определяем класс TModuleNotifier, являющийся потомком TIModuleNotifier и перекрываем его методы Notify и ComponentRenamed. IDE вызывает метод Notify при возникновении определенных событий, имеющих отношение к данному модулю; внутри этого метода и определяется реакция на то или иное событие. Метод ComponentRenamed вызывается при изменении имени компонента, лежащего на форме модуля. Обратите внимание на то, что мы не используем этот метод, но обязаны его перекрыть, иначе при изменении имени компонента будет происходить вызов абстрактного метода базового класса, что приводит к непредсказуемым последствиям. Регистрация модульного нотификатора является несколько более сложным процессом по сравнению с регистрацией add-in нотификатора: сначала мы получаем интерфейс модуля (TIModuleInterface), а затем с помощью интерфейса модуля регистрируем нотификатор. При закрытии проекта регистрация модульного нотификатора снимается (снова с использованием TIModuleInterface), и нотификатор уничтожается.
В заключение покажем, как можно определять позицию курсора в окне редактора кода. Создадим эксперт, который при выборе соответствующего пункта меню выдавал бы сообщение, содержащее имя активного файла и позицию курсора в нем (приведена реализация только существенных для данного примера методов):
unit exmpl_05;
{ ОПРЕДЕЛЕНИЕ ПОЗИЦИИ КУРСОРА }
interface
uses
SysUtils, Classes, Dialogs, ToolIntF, ExptIntf, EditIntf, Menus;
type
{ класс эксперта является потомком базового класса TIExpert }
TEMyExpert = class( TIExpert)
private
MenuItem: TIMenuItemIntf;
public
constructor Create;
destructor Destroy; override;
function GetName: string; override;
function GetStyle: TExpertStyle; override;
function GetIDString: string; override;
procedure MenuItemClick( Sender: TIMenuItemIntf);
function GetCursorPos: TEditPos;
end;
procedure register;
function AddIDEMenuItem( const Caption, name, PreviousItemName: string;
const ShortCutKey: Char; OnClick: TIMenuClickEvent): TIMenuItemIntf;
implementation
{ при выборе пункта меню эксперта отображаем сообщение, содержащее }
{ имя активного проекта }
procedure TEMyExpert.MenuItemClick( Sender: TIMenuItemIntf);
var
CurPos: TEditPos;
begin
CurPos:= GetCursorPos;
if CurPos.Line > 0 then
MessageDlg( 'Current file: ' + ToolServices.GetCurrentFile + #13 +
'Current cursor position: ' + IntToStr( CurPos.Line) +
', ' + IntToStr( CurPos.Col), mtInformation, [mbOK], 0);
end;
function TEMyExpert.GetCursorPos: TEditPos;
var
ModuleInterface: TIModuleInterface;
EditorInterface: TIEditorInterface;
EditView: TIEditView;
FileName: string;
begin
{ определяем имя активного файла }
FileName:= ToolServices.GetCurrentFile;
Result.Line:= 0;
Result.Col:= 0;
{ для простоты определяем позицию только в pas- файлах }
if ExtractFileExt( FileName) = '.pas' then
begin
{ получаем интерфейс модуля }
ModuleInterface:= ToolServices.GetModuleInterface( FileName);
try
{ получаем интерфейс редактора кода }
EditorInterface:= ModuleInterface.GetEditorInterface;
try
{ получаем интерфейс представления модуля в редакторе }
{ передавая методу GetView индекс нужного нам представления; }
{ если файл открыт в нескольких окнах редактора кода, то для }
{ простоты берем первое (хотя конечно, это не совсем }
{ правильно }
EditView:= EditorInterface.GetView( 0);
try
Result:= EditView.CursorPos;
finally
EditView.Free;
end;
finally
EditorInterface.Free;
end;
finally
ModuleInterface.Free;
end;
end;
end;
...
|
Для определения позиции курсора мы должны получить следующую последовательность интерфейсов:
модульный интерфейс (TIModuleInterface); интерфейс редактора кода (TIEditorInterface); интерфейс представления модуля в окне редактора (TIEditView).Если при выборе пункта меню эксперта активным является файл с исходным текстом (*.pas), то выдается сообщение, содержащее имя активного файла и текущую позицию курсора в нем. Если активным является не pas-файл, то сообщение не выдается.
Для получения имени активного файла используется метод GetCurrentFile класса TIToolServices.
Итак, в данной статье в общих чертах рассмотрены открытые интерфейсы и приведены примеры их использования. Еще раз повторим: благодаря наличию исходных текстов открытых интерфейсов вы без труда сможете разобраться в интересующих вас деталях. Надеемся, что многообразие возможностей, предоставляемых открытыми интерфейсами, породит у вас не одну смелую и полезную идею.
Запуск Delphi с секретными параметрами.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
-ns : ("no splash") flag skips the splash screen : Startet Delphi ohne Splash Screen (ab Delphi 2) (Delphi 2 and later) Delphi32.EXE -ns -np : ("no project") flag tells Delphi not to open an empty project on startup : Startet Delphi, ohne ein leeres Projekt zu цffnеn. (Delphi 5 and later) Delphi32.EXE -np -hm : ("Heap Monitor") Displays information in the IDE title bar regarding the amount of memory allocated using the memory manager. Displays the number of blocks and bytes allocated. Information gets updated when the IDE is idle. : Zeigt in der Titelleiste der IDE Informationen ььег den allozierten Speicher an. Zeigt die Anzahl allozierten Blocks/Bytes an. (Delphi 3 and later) Delphi32.EXE -hm -attach: Attach to running process. This command-line is used to make Delphi a JIT debugger on Windows 95/98/NT. Delphi 4 and later. Note: These command-line switches are case-insensitive and can be prefixed with either - or /. Bemerkung: Die Parameter sind Case-Insensitive und кцnnеn ein - oder / vorangestellt haben.Сервисы в Delphi на чистом API.
Автор: Павел Часть 1. Основные понятия Вот так получилось нужно было по работе написать сервис но не хотелось пользоваться классами Delphi, так как код написанный с применением оных большой, а хотелось чего-нибудь поменьше, то есть на API, но к сожелению видно этим никто не занимался в Delphi (примеров на Си более чем достаточно), вот и родилась идея этого обзора. Что же это сервис(еще их называют службами)??? Попробую рассказать как умею... В любой операционной системе, нужны программы которые работают в фоновом режиме (это нужно например для различных серверов СУБД, сетевые приложения, программ мониторинга следящих за чем то и т.д. и т.п), в Unix-системах такие программы называются демонами, в DOS они назывались TSR (Terminate and Stay Resident). В NT и 2000(далее я буду называть их только для NT, что подразумевает под сообой и 2000) Windows такие программы называются сервисами (или службами).Сервис NT (Windows NT service) - процесс, обладающий специальным интерфейсом для взаимодействия с операционной системой Windows NT. Сервисы бывают двух типов - сервисы Win32, взаимодействующие с операционной системой посредством диспетчера управления сервисами (Service Control Manager - SCM), и драйвера, работающие по протоколу драйвера устройства Windows NT. Я буду рассказывать только про сервисы Win32. Сервисы доступны в панели управления. Запустив Service Applet, вы увидите список доступных сервисов. Вы можете запускать, останавливать, ставить сервисы на паузу. Нажав кнопку Startup, Вы можете изменить поведение сервиса при запуске. Сервисы могут запускаться автоматически при старте компьютера, быть абсолютно выключеными или иметь возможность запуска вручную. Если сервис запускается вручную, то пользователь может указать параметры запуска. Для управления сервисами Вам нужны права администратора или права power user. ОС использует много сервисов и информацию о каждом из них пишет в реестр в ключ "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services".Там содержатся следующие сведения: Тип службы Указывает на то, реализован ли в данной программе только один сервис (эксклюзивный) или же их несколько. Эксклюзивный сервис может работать в любом контексте безопасности. Несколько сервисов внутри одного приложения могут работать только в контексте LocalSystem. Способ запуска Автоматический - сервис запускается при старте системы. По требованию - сервис запускается пользователем вручную. Деактивированный - сервис не может быть запущен. Имя исполняемого EXE-файла Порядок запуска по отношению к другим сервисам. В некоторых случаях для корректной работы сервиса требуется, чтобы был запущен один или несколько других сервисов. В этом случае в реестре содержится информация о сервисах, запускаемых перед данным сервисом. Контекст безопасности Здесь хранится сетевое имя и пароль. По умолчанию контекст безопасности соответствует LocalSystem.Любая программа, имеющее соответствующие права, может взаимодействовать с сервисом. Это подразумевает изменение состояния сервиса (Вы можете изменить состояния сервиса используя Service Applet), то есть перевод его в одно из трех состояний - активный (сервис работает), приостоновленный (работа сервиса приостановлена), полная остановка и осуществляется при помощи посылки запросов SCM. Запросы бывают трех типов:
Сообщения от сервисов (фиксация их состояний). Запросы, связанные с изменением конфигурации сервиса или получением информации о нем. Запросы приложений на изменение состояния сервиса. Для управления сервисом необходимо в первую очередь получить дескриптор с помощью функции OpenService. Функция StartService запускает сервис. Изменение состояния сервиса производится вызовом функции ControlService. С помощью определенных функций вы можете изменить информацию о сервисе, тем самым и в реестре. OpenSCManager, CreateService, OpenService, CloseServiceHandle - для создания (открытия) сервиса. QueryServiceConfig, QueryServiceObjectSecurity, EnumDependentServices, EnumServicesStatus - для получения информации о сервисе. ChangeServiceConfig, SetServiceObjectSecurity, LockServiceDatabase, UnlockServiceDatabase, QueryServiceLockStatus - для изменения конфигурационной информации сервиса. В принципе как было сказано сервис - это в принципе обычный exe-файл поддерживающий интерфейс SCM, только имеющий определенное строение. Для того чтобы стать сервисом программа должна быть построена соответствующим образом(т.е. иметь набор определенных функций). В следующей части я подробно расскажу об этом.Описание функций для работы с сервисами (постепенно будут добавлены описания для всех функций), большая часть из них объявлена в WinSvc.Pas.
ChangeServiceConfig Изменяет параметры конфигурации сервиса ChangeServiceConfig2 Изменяет необязательные параметры конфигурации сервиса CloseServiceHandle Закрывает указанный дескриптор к сервисному объекту ControlService Посылает сервису управляющий код CreateService Создает сервисный объект DeleteService Удаляет сервис из базы данных диспетчеров сервисов EnumDependentServices Перечисляет зависимые сервисы EnumServicesStatus Перечисляет сервисы EnumServicesStatusEx Перечисляет сервисы GetServiceDisplayName Возвращает отображаемое имя сервиса GetServiceKeyName Возвращает имя сервиса Handler Функция обработки управляющих кодов HandlerEx Функция обработки управляющих кодов LockServiceDatabase Блокирует базу данных сервисов NotifyBootConfigStatus Уведомляет о приеме загрузочной информации OpenSCManager Подключает приложение к менеджеру сервисов OpenService Открывает сервис QueryServiceConfig Запрашивает параметры конфигурации QueryServiceConfig2 Запрашивает необязательные параметры конфигурации QueryServiceLockStatus Запрашивает статус блокировки QueryServiceObjectSecurity Запрашивает дескриптор безопасности QueryServiceStatus Запрашивает статус сервиса QueryServiceStatusEx Запрашивает статус сервиса RegisterServiceCtrlHandler Регистрирует обработчик запросов RegisterServiceCtrlHandlerEx Регистрирует обработчик запросов ServiceMain Главная функция сервиса SetServiceBits Обноволяет статус сервиса для диспетчера сервисов SetServiceObjectSecurity Изменяет дескриптор безопасности сервисного объекта SetServiceStatus Устанавливает статус сервиса StartService Запускает сервис StartServiceCtrlDispatcher Запускает поток UnlockServiceDatabase Разблокирование базы данныхТакже при написание сервиса используются такие структуры(описания их то же постепенно будут добавлятся).
ENUM_SERVICE_STATUS ENUM_SERVICE_STATUS_PROCESS QUERY_SERVICE_CONFIG QUERY_SERVICE_LOCK_STATUS SC_ACTION SERVICE_DESCRIPTION SERVICE_FAILURE_ACTIONS SERVICE_STATUS SERVICE_STATUS_PROCESS SERVICE_TABLE_ENTRY Но вот и все для основных понятий...Вообще сервисы довольно сильно облегчают жизнь программисту, и понимание их работы (даже не используя) полезны, не только программистам, но и системным администраторам и пользователям. В статье использованы материалы из Microsoft SDK(Эта статья входит в рамки проекта DSDN). С удовольствием выслушаю все комментария и замечания по поводу данной статьи. Сразу хочу предупредить, я не отвечаю на письма где меня просят выслать что либо или будет ли продолжение(и подобные письма не несущие какой-то содержательной части), я их читаю, но отвечать на них у меня времени нет. Также , если кто обнаружит неточности, да и просто грамматические ошибки в статье, за это прошу заранее извинение.Желаю удачи в темных делах С уважением Алексей.
Программирование сокетов в Delphi.
Автор: Павел Введение Данная статья посвящена созданию приложений архитектуры клиент/сервер в Borland Delphi на основе сокетов ("sockets" - гнезда). А написал я эту статью не просто так, а потому что в последнее время этот вопрос очень многих стал интересовать. Пока что затронем лишь создание клиентской части сокетного приложения. Впервые я познакомился с сокетами, если не ошибаюсь, год или полтора назад. Тогда стояла задача разработать прикладной протокол, который бы передавал на серверную машину (работающую на ОС Unix/Linux) запрос и получал ответ по сокетному каналу. Надо заметить, что в отличие от любых других протоколов (FTP, POP, SMTP, HTTP, и т.д.), сокеты - это база для этих протоколов. Таким образом, пользуясь сокетами, можно самому создать (симитировать) и FTP, и POP, и любой другой протокол, причем не обязательно уже созданный, а даже свой собственный!Итак, начнем с теории. Если Вы убежденный практик (и в глаза не можете видеть всяких алгоритмов), то Вам следует пропустить этот раздел.
Алгоритм работы с сокетными протоколами Так что же позволяют нам делать сокеты?... Да все что угодно! И в этом одно из главных достоинств этого способа обмена данными в сети. Дело в том, что при работе с сокетом Вы просто посылаете другому компьютеру последовательность символов. Так что этим методом Вы можете посылать как простые сообщения, так и целые файлы! Причем, контролировать правильность передачи Вам не нужно (как это было при работе с COM-портами)! Ниже следует примерная схема работы с сокетами в Дельфи-приложенияхРазберем схему подробнее:
Определение св-в Host и Port - чтобы успешно установить соединение, нужно присвоить свойствам Host и Port компонента TClientSocket требуемые значения. Host - это хост-имя (например: nitro.borland.com) либо IP-адрес (например: 192.168.0.88) компьютера, с которым надо соединиться. Port - номер порта (от 1 до 65535) для установления соединения. Обычно номера портов берутся, начиная с 1001 - т.к. номера меньше 1000 могут быть заняты системными службами (например, POP - 110). Подробнее о практической части см.ниже; Открытие сокета - после того, как Вы назначили свойствам Host и Port соответствующие значения, можно приступить непосредственно к открытию сокета (сокет здесь рассматривается как очередь, в которой содержатся символы, передающиеся от одного компьютера к другому). Для этого можно вызвать метод Open компонента TClientSocket, либо присвоить свойству Active значение True. Здесь полезно ставить обработчик исключительной ситуации на тот случай, если соединиться не удалось. Подробнее об этом можно прочитать ниже, в практической части; Авторизация - этот пункт можно пропустить, если сервер не требует ввода каких-либо логинов и/или паролей. На этом этапе Вы посылаете серверу свой логин (имя пользователя) и пароль. Но механизм авторизации зависит уже от конкретного сервера; Посылка/прием данных - это, собственно и есть то, для чего открывалось сокетное соединение. Протокол обмена данными также зависит от сервера; Закрытие сокета - после всех выполненных операций необходимо закрыть сокет с помощью метода Close компонента TClientSocket (либо присвоить свойству Active значение False). Описание свойств и методов компонента TClientSocketЗдесь мы познакомимся с основными свойствами, методами и событиями компонента TClientSocket.
| Свойства. | Методы. | События. |
| Active - показывает, открыт сокет или нет. Тип: Boolean. Соответственно, True - открыт, а False - закрыт. Это свойство доступно для записи; Host - строка (Тип: string), указывающая на хост-имя компьютера, к которому следует подключиться; Address - строка (Тип: string), указывающая на IP-адрес компьютера, к которому следует подключиться. В отличие от Host, здесь может содержаться лишь IP. Отличие в том, что если Вы укажете в Host символьное имя компьютера, то IP адрес, соответствующий этому имени будет запрошен у DNS; Port - номер порта (Тип: Integer (Word)), к которому следует подключиться. Допустимые значения - от 1 до 65535; Service - строка (Тип: string), определяющая службу (ftp, http, pop, и т.д.), к порту которой произойдет подключение. Это своеобразный справочник соответствия номеров портов различным стандартным протоколам; ClientType - тип соединения. ctNonBlocking - асинхронная передача данных, т.е. посылать и принимать данные по сокету можно с помощью OnRead и OnWrite. ctBlocking - синхронная (одновременная) передача данных. События OnRead и OnWrite не работают. Этот тип соединения полезен для организации обмена данными с помощью потоков (т.е. работа с сокетом как с файлом); | Open - открытие сокета (аналогично присвоению значения True свойству Active); Close - закрытие сокета (аналогично присвоению значения False свойству Active); На этом все методы компонента TClientSocket исчерпываются. А Вы спросите: "А как же работать с сокетом? Как тогда пересылать данные?". Об этом Вы узнаете чуть дальше. | OnConnect - как следует из названия, это событие возникает при установлении соединения. Т.е. в обработчике этого события уже можно начинать авторизацию или прием/передачу данных; OnConnecting - возникает при установлении соединения. Отличие от OnConnect в том, что соединение еще не установлено. Обычно такие промежуточные события используются для обновления статуса; OnDisconnect - возникает при закрытии сокета. Причем, закрытия как из Вашей программы, так и со строноны удаленного компьютера (либо из-за сбоя); OnError - продолжает грустную тему предыдущего события :). Возникает при ошибке в работе сокета. Следует отметить, что это событие не поможет Вам отловить ошибку в момент открытия сокета (Open). Для того, чтобы избежать выдачи виндозного сообщения об ошибке, надо заключить операторы открытия сокета в блок try..except (обработка исключительных ситуаций); OnLookup - возникает при попытке получения от DNS IP-адреса указанного хоста; OnRead - возникает, когда удаленный компьютер послал Вам какие-либо данные. При возникновении этого события возможна обработка данных; OnWrite - возникает, когда Вам разрешена запись данных в сокет. |
Пример 1. Простейшая сокетная программа.
{... Здесь идет заголовок файла и определение формы TForm1 и ее экземпляра Form1}
{В форму нужно поместить кнопку TButton и два TEdit.
При нажатии на кнопку вызывается обработчик события OnClick - Button1Click.
Перед этим в первый из TEdit-ов нужно ввести хост-имя,
а во второй - порт удаленного компьютера.
НЕ ЗАБУДЬТЕ ПОМЕСТИТЬ В ФОРМУ КОМПОНЕНТ TClientSocket!}
procedure Button1Click(Sender: TObject);
begin
{Присваиваем свойствам Host и Port нужные значения}
ClientSocket1.Host := Edit1.Text;
ClientSocket1.Port := StrToInt(Edit2.Text);
{Пытаемся открыть сокет и установить соединение}
ClientSocket1.Open;
end;
procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
begin
{Как только произошло соединение - закрываем сокет и прерываем связь}
ClientSocket1.Close;
end;
Если Вы думаете, что данный пример программы совершенно бесполезен и не может принести никакой пользы, то глубоко ошибаетесь. Приведенный код - простейший пример сканера портов (PortScanner). Суть такой утилиты в том, чтобы проверять, включен ли указанный порт и готов ли он к приему/передаче данных. Именно на таком принципе основан PortScanner из программы NetTools Pro.
Далее следует другой пример, в котором по сокету передаются и принимаются текстовые сообщения:
Пример 2. Посылка/прием текстовых сообщений по сокетам.
{... Здесь идет заголовок файла и определение формы TForm1 и ее экземпляра Form1}
{В форму нужно поместить две кнопки TButton и три TEdit.
При нажатии на первую кнопку вызывается обработчик события OnClick - Button1Click.
Перед этим в первый из TEdit-ов нужно ввести хост-имя,
а во второй - порт удаленного компьютера. После установления соединения
можно посылать текстовые сообщения, вводя текст в третий TEdit и нажимая
вторую кнопку TButton. Чтобы отсоединиться, нужно еще раз нажать первую TButton.
Еще нужно добавить TListBox, в который будем помещать принятые
и отправленные сообщения.
НЕ ЗАБУДЬТЕ ПОМЕСТИТЬ В ФОРМУ КОМПОНЕНТ TClientSocket!}
procedure Button1Click(Sender: TObject);
begin
{Если соединение уже установлено - прерываем его.}
if ClientSocket1.Active then
begin
ClientSocket1.Close;
Exit; {...и выходим из обработчика}
end;
{Присваиваем свойствам Host и Port нужные значения}
ClientSocket1.Host := Edit1.Text;
ClientSocket1.Port := StrToInt(Edit2.Text);
{Пытаемся открыть сокет и установить соединение}
ClientSocket1.Open;
end;
procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
begin
{Как только произошло соединение - посылаем приветствие}
Socket.SendText('Hello!');
ListBox1.Items.Add('< Hello!');
end;
procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
begin
{Если пришло сообщение - добавляем его в ListBox}
ListBox1.Items.Add('> ' + Socket.ReceiveText);
end;
procedure Button2Click(Sender: TObject);
begin
{Нажата кнопка - посылаем текст из третьего TEdit}
ClientSocket1.Socket.SendText(Edit3.Text);
ListBox1.Items.Add('< ' + Edit3.Text);
end;
ПРИМЕЧАНИЕ: В некоторых случаях (зависящих от сервера) нужно после каждого сообщения посылать перевод строки: ClientSocket1.Socket.SendText(Edit3.Text + #10);Работа с сокетным потоком "А как еще можно работать с сокетом?", - спросите Вы. Естественно, приведенный выше метод - не самое лучшее решение. Самих методов организации работы с сокетами очень много. Я приведу лишь еще один дополнительный - работа через поток. Наверняка, многие из Вас уже имеют опыт работы, если не с потоками (stream), то с файлами - точно. Для тех, кто не знает, поток - это канал для обмена данными, работа с которым аналогична работе с обычным файлом. Нижеприведенный пример показывает, как организовать поток для работы с сокетом:
Пример 3. Поток для работы с сокетом.
procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
var
c: Char;
MySocket: TWinSocketStream;
begin
{Как только произошло соединение - создаем поток и
ассоциируем его с сокетом (60000 - таймаут в мсек)}
MySocket := TWinSocketStream.Create(Socket, 60000);
{Оператор WaitForData ждет данных из потока указанное время в мсек
(в данном примере - 100) и возвращает True, если получен хотя бы один
байт данных, False - если нет никаких данных из потока.}
while not MySocket.WaitForData(100) do
Application.ProcessMessages;
{Application.ProcessMessages позволяет Windows перерисовать нужные
элементы окна и дает время другим программам. Если бы этого оператора
не было и данные бы довольно долго не поступали,
то система бы слегка "подвисла".}
MySocket.Read(c, 1);
{Оператор Read читает указанное количество байт из потока
(в данном примере - 1) в указанную переменную определенного типа
(в примере - в переменную c типа Char). Обратите внимание на то,
что Read, в отличие от ReadBuffer, не устанавливает строгих
ограничений на количество принятой информации. Т.е. Read читает
не больше n байтов из потока (где n - указанное число).
Эта функция возвращает количество полученных байтов данных.}
MySocket.Write(c, 1);
{Оператор Write аналогичен оператору Read, за тем лишь исключением,
что Write пишет данные в поток.}
MySocket.Free;
{Не забудем освободить память, выделенную под поток}
end;
ПРИМЕЧАНИЕ: Для использования потока не забудьте установить свойство ClientType в ctBlocking.
Посылка и прием сложных данных
Иногда необходимо пересылать по сети не только простые текстовые сообщения, но и сложные структуры (тип record в Паскале), или даже файлы. И тогда Вам необходимо использовать специальные операторы. Некоторые из них перечислены ниже:
Методы TClientSocket.Socket (TCustomWinSocket, TClientWinSocket):
SendBuf(var Buf; Count: Integer) - Посылка буфера через сокет. Буфером может являться любой тип, будь то структура (record), либо простой Integer. Буфер указывается параметром Buf, вторым параметром Вы должны указать размер пересылаемых данных в байтах (Count); SendText(const S: string) - Посылка текстовой строки через сокет. Этот метод рассматривался в примере 2 (см.выше); SendStream(AStream: TStream) - Посылка содержимого указанного потока через сокет. Пересылаемый поток должен быть открыт. Поток может быть любого типа - файловый, из ОЗУ, и т.д. Описание работы непосредственно с потоками выходит за рамки данной статьи; Всем перечисленным методам соответствуют методы Receive... Их описание можно посмотреть в справочном файле по Дельфи (VCL help). Авторизация на сервере Напоследок хочу привести несложный пример того, как можно реализовать авторизацию (вход на сервер). В данном примере пароль посылается нешифрованным текстом, так что если Вам нужен действительно надежный механизм входа, то Вам придется внести кое-какие изменения в исходник данного примера. Пример реализован как работа с сокетным потоком.Пример 4. Авторизация.
{В данном примере нужно добавить в форму еще два
TEdit - Edit3 и Edit4 для ввода логина и пароля}
procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
var
c: Char;
MySocket: TWinSocketStream;
login, password: string;
begin
MySocket := TWinSocketStream.Create(Socket, 60000);
{Добавляем к логину и паролю символ перевода строки,
чтобы сервер смог отделить логин и пароль.}
login := Edit3.Text + #10;
password := Edit4.Text + #10;
MySocket.Write(login, Length(Edit3.Text) + 1);
MySocket.Write(password, Length(Edit4.Text) + 1);
while not MySocket.WaitForData(100) do
Application.ProcessMessages;
MySocket.Read(c, 1);
{Здесь сервер посылает нам один байт, значение 1 которого
соответствует подтверждению успешной авторизации, а 0 - ошибку
(это лишь пример). Далее мы выполняем нужные действия
(прием/пересылку данных) и закрываем поток.}
MySocket.Free;
end;
Эпилог
В этой статье я написал лишь самую малость из того, что можно было бы сказать про сокеты. Может быть, когда у меня появится новый прилив сил, я дополню эту статью еще более интересным материалом.
В ближайшем будущем планирую также статью про сокетные серверы (TServerSocket).
Добавление функций проверки орфографии в разрабатываемые приложения.
| Майкрософт Ворд не понимает слово "стрипризерша", то есть подчеркивает красным, а когда кликаешь на него мышкой, то в возможных исправлениях он выдает "стриптиз ерша". |
Предположим, что в вашу задачу, как разработчика программного обеспечения, входит создание некоторого специализированного текстового процессора. Не вдаваясь в рассуждения о необходимости создания еще одного приложения подобного рода, мы просто рассмотрим один прием, который придаст вашей разработке весьма ощутимое преимущество по сравнению с аналогами. К примеру, вам необходимо создать некий HTML-редактор. Как и в случае с любым другим приложением такого типа, ваша программа должна будет обладать функциями орфографической проверки текста. Естественно, можно потратить много времени на создание своего собственного шедевра в данной области, но почему бы нам не воспользоваться уже готовыми решениями? В рамках данной статьи я бы хотел поговорить о технологии использования в ваших приложениях механизмов проверки орфографии, входящих в состав всем известного приложения - Microsoft Word с использованием автоматизации (OLE Automation).
OLE Automation
Идея, заложенная в автоматизацию, включает разработку приложений, функциональность которых может быть доступна и другим программам, а также создание приложений, которые "знают", как использовать функциональность, предоставляемую вам другими программными продуктами. Если говорить техническим языком, приложение, которое предоставляет некоторую повторно используемую функциональность, называется сервером автоматизации (automation server) (также часто называемым сервером COM). Приложение же, использующее функциональность, предоставляемую сервером автоматизации, называется клиентом автоматизации (automation client), также часто называемым контроллером автоматизации. Важно подчеркнуть, что сервер автоматизации может не быть "чистым" сервером автоматизации, так же как и клиент автоматизации может не быть "чистым" клиентом автоматизации. В действительности сервер автоматизации может использовать сервисы другого приложения, которое также является сервером автоматизации. Клиент автоматизации, предоставляющий свои сервисы другому клиенту, также может являться как клиентом, так и сервером автоматизации. Глубинные механизмы (сетевые и транспортные протоколы), с помощью которых клиент автоматизации взаимодействует с сервером, уже являются частью собственно COM.
Сервер автоматизации - это просто двоичный исполняемый модуль, который может состоять из нескольких объектов автоматизации. Объект автоматизации (также называемый объектом COM, хотя технически объект автоматизации является объектом COM особого сорта) - это отдельный, самодостаточный объект, спроектированный для выполнения специфической задачи или функции. В общем, все объекты автоматизации, собранные в одном сервере, предназначены для осуществления каких-то функциональных возможностей. Например, Microsoft Excel является сервером автоматизации, состоящим из нескольких меньших серверов автоматизации (Workbook - книга, Chart - диаграмма, Worksheet - лист, Range - диапазон и т.д.), каждый из которых определяет часть функций, предоставляемых пользователю Microsoft Excel. Идея заключается в том, что сервер автоматизации "позволяет" своим клиентам получать доступ и использовать свои объекты так же легко и просто, как будто это его внутренние объекты.
Для решения задачи, поставленной перед нами в начале данной статьи, мы можем воспользоваться теми возможностями, которые предоставляет нам сервер автоматизации Microsoft Word. C помощью приложения, разработанного в Borland Delphi (программа будет выступать в качестве клиента автоматизации), мы сможем динамически создать новый документ и поместить в него некоторый текст (который и будем проверять). После этого нам останется лишь с помощью MS Word осуществить эту проверку. Если приложение Word будет минимизировано, то пользователи могут и не почувствовать, что выполнение части функций нашего приложения берет на себя другая программа. Обращаю внимание, что для полноценного использования OLE-автоматизации вам надо будет знать как можно больше о возможностях и интерфейсах того приложения, функциональностью которого вы решили воспользоваться. Кроме того, для корректного выполнения всех функций разрабатываемого приложения необходимо, чтобы на компьютере пользователя было установлено соответствующее приложение. В нашем случае - Microsoft Word.
Основные принципы работы
Существует три основных метода использования OLE-автоматизации в Borland Delphi в зависимости от того, какую версию этой среды разработки вы используете.
Delphi 5. Закладка Servers на палитре компонентов.
Если вы являетесь счастливым обладателем этой версии Delphi, то для работы с Microsoft Word можно воспользоваться компонентами, расположенными на закладке Ser-vers (рис. 1). Такие компоненты, как TWord-Application и TWordDocument, предоставляют все необходимые для работы интерфейсы.
Delphi 3, 4. Раннее связывание.
Используя термины автоматизации, для обеспечения в Delphi доступа к методам и свойствам, предоставляемым MS Word, необходимо установить соответствующую библиотеку типов. Библиотека типов предоставляет информацию обо всех свойствах и методах, которые разработчик может использовать при работе с сервером автоматизации. Для использования библиотеки типов Microsoft Word в Delphi (3 или 4 версии) необходимо произвести следующие несложные действия:
выбрать пункт меню Project|Import Type Library; в открывшемся диалоге найти файл msword8.olb (для Microsoft Office'2000 этот файл будет иметь название msword9.olb), расположенный в подкаталоге "Office" того каталога, в который был установлен Microsoft Office.После этого будет создан файл с именем word_TLB.pas, в котором в синтаксисе object pascal будут описаны константы, типы, свойства и методы для доступа к серверу автоматизации Microsoft Word. Файл word_TLB.pas должен быть включен в список uses всех модулей, в которых вы планируете использовать функции Microsoft Word. Такая технология работы с серверами автоматизации называется ранним связыванием. Одним из преимуществ раннего связывания является осуществление контроля вызовов и передаваемых параметров на этапе компиляции.
Delphi 2. Позднее связывание.
Для доступа к объектам MS Word без применения библиотеки типов можно использовать так называемое позднее связывание. В данном случае доступ к Word осуществляется так же, как к переменной типа Variant, следствием чего является необходимость знания вами всех предоставляемых сервером автоматизации интерфейсов. Позднего связывания следует по возможности избегать, поскольку при этом отсутствует возможность контроля корректности вызовов процедур и функций со стороны компилятора, и если вы неправильно написали имя того или иного метода, то узнаете об этом, только, когда программа "вывалится" по ошибке в процессе выполнения.
Начнем!
Итак, вернемся к теме статьи. Для демонстрации принципов работы с MS Word я буду использовать механизмы, предоставляемые пятой версией Delphi (т.е. компоненты TWordApplication, TWordDocument). Ниже я приведу код, обеспечивающий соединение и работу с MS Word в случае использования библиотеки типов и позднего связывания и больше не буду касаться этой темы.
Для доступа к объектам Word при работе в Delphi 3, 4 (запуск приложения и создание нового документа) используйте следующий код:
uses
Word_TLB;
...
var
WordApp: _Application;
WordDoc: _Document;
VarFalse: OleVariant;
begin
WordApp := CoApplication.Create;
WordDoc := WordApp.Documents.Add(EmptyParam, EmptyParam);
{
код для проверки орфографии, описываемы далее в данной статье
}
VarFalse:=False;
WordApp.Quit(VarFalse, EmptyParam, EmptyParam);
end;
|
Обращаю внимание, что в методах MS Word множество параметров описаны как необязательные (optional). При использовании интерфейсов (библиотек типов), Delphi не позволит вам опускать те или иные параметры, даже если в контексте разрабатываемого вами кода они не нужны. В четвертой версии Delphi в модуле system.pas описана переменная EmptyParam, которую можно использовать в качестве "заглушки" для неиспользуемых переменных в вызываемом методе.
Для автоматизации MS Word с использованием переменной Variant (позднее связывание) используйте следующий код:
uses
ComObj;
...
var
WordApp, WordDoc: Variant;
begin
WordApp := CreateOleObject('Word.Application');
WordDoc := WordApp.Documents.Add;
{
код для проверки орфографии, описываемы далее в данной статье
}
WordApp.Quit(False)
end;
|
При использовании позднего связывания компилятор Delphi позволяет вам опускать те или иные параметры при вызове методов сервера автоматизации.
Как уже упоминалось, Delphi 5 упрощает программисту использование функциональности MS Word в своих приложениях путем предоставления его методов и свойств в виде компонентов. Так как множество параметров, определенных в методах Wоrd'а, описаны как необязательные, то в Delphi данные процедуры и функции переопределены и представляют собой набор из нескольких методов с различным количеством параметров. Таким образом, разработчику предоставляется возможность при вызове метода не указывать последние n параметров, необходимость в которых отсутствует.
Шаг за шагом
Для создания своего редактора с возможностью проверки орфографии в минимальном варианте нам понадобится две формы: одна будет использоваться для редактирования текста, а вторая - для отображения диалога правки найденных ошибок. Однако предлагаю начать с самого начала.
Если у вас не запущен Delphi - запустите его. Создайте новый проект (если он не был создан при открытии приложения). По умолчанию проект будет содержать одну форму. Данная форма будет главной в нашем проекте. Поместите на форму один компонент типа TMemo и две кнопки (TButton). Заполните свойство Lines компонента Memo1 каким-нибудь текстом (содержащим ошибки). Заголовок одной кнопки определите как "Орфография", а второй - "Тезаурус". Затем перейдите на закладку Servers палитры компонентов и поместите на форму по одному компоненту типа TWordApplication и TWordDocument (рис. 2). Установите значения свойства Name первого компонента в Word-App, а второго - WordDoc.
TWordApplication, TWordDocument
При автоматизации MS Word для управления приложением, отображения его рабочего окна, получения доступа к атрибутам и объектной модели MS Word мы используем объект Application. Для того чтобы указать приложению, запускать ли новую копию процесса Word или использовать уже запущенный, применяется свойство Applicati-on.ConnectKind. В нашем случае мы устанавливаем данное свойство в значение ckRunningInstance. Другие возможные значения этого свойства вы сможете узнать, воспользовавшись справочной системой Delphi.
Когда мы открываем в MS Word существующий файл или создаем новый, мы тем самым создаем объект Document. Типичной задачей при использовании автоматизации Word является работа с некоторой областью документа: добавление текста, выделение некоторой области, проверка орфографии и т.д. Объект, определяющий некоторую область в документе, называется Range.
Естественно, в рамках статьи я не смогу подробно рассказать обо всех нюансах работы с компонентами, расположенными на закладке Servers палитры компонентов (кстати, с любой другой закладкой ситуация состоит ничуть не лучше). Для более детального их изучения предлагаю воспользоваться справочной системой Borland Delphi. В нашем же сегодняшнем разговоре я буду упоминать только те свойства и методы, которые будут необходимы.
Как это все будет работать
Алгоритм работы нашего приложения будет достаточно прост. Каждое слово, входящее в состав проверяемого нами текста, будет передаваться в MS Word для проверки. Сервер автоматизации Word содержит метод SpellingErrors, который позволяет вам осуществлять проверку текста, входящего в состав некоторой области Range. Мы же будем каждый раз определять эту область таким образом, чтобы она содержала только переданное нами в Word слово. Метод SpellingErrors в качестве результата своей работы возвращает коллекцию слов, написание которых признано ошибочным. Если эта коллекция пуста, то мы переходим к рассмотрению следующего слова. Иначе - переходим к процедуре замены неправильно напечатанного слова. Путем вызова метода GetSpellingSuggestions можно получить список слов, предлагаемых в качестве замены. Эти слова помещаются в коллекцию SpellingSuggestions. Данную коллекцию мы помещаем в качестве списка (компонент типа TListBox), расположенного во второй форме нашего проекта. Думаю, самое время немного поговорить о ней.
Для того чтобы добавить новую форму в проект, следует выбрать пункт меню File|New Form. Назовем эту форму frSpellCheck. На форму поместим три кнопки типа TBitBtn, два элемента редактирования (TEdit) и один список (TListBox). На форму также следует поместить три метки (см. рис. 3). Компонент edNID (editNotInDictionary) служит для отображения заменяемого слова. edReplaceWith содержит выделенный в данный момент вариант для замены, а список lbSuggestions - список предлагаемых вариантов (заполняемый на основании данных, содержащихся в коллекции SpellingSuggestions). Три кнопки выполняют именно те функции, которым соответствуют их заголовки - не больше и не меньше. Каждой из кнопок соответствует свое значение, возвращаемое функцией frSpellCheck.ModalResult. В зависимости от этого значения в основной обрабатывающей процедуре осуществляется то или иное действие - игнорирование, замена или отмена дальнейшей проверки. Форма frSpellCheck содержит одно общедоступное свойство:
sReplacedWord :String |
Оно служит для передачи в основную форму слова для замены в случае нажатия пользователем кнопки "Заменить".
Пишем код!
Ниже приводится код основной процедуры приложения.
procedure TForm1.btnSpellCheckClick(Sender: TObject);
var
colSpellErrors : ProofreadingErrors;
colSuggestions : SpellingSuggestions;
i : Integer;
StopLoop : Boolean;
itxtLen, itxtStart : Integer;
varFalse : OleVariant;
begin
WordApp.Connect;
WordDoc.ConnectTo(WordApp.Docum-ents.Add(EmptyParam, EmptyParam));
StopLoop:=False;
itxtStart:=0;
Memo.SelStart:=0;
itxtlen:=0;
while not StopLoop do
begin
itxtStart := itxtLen + itxtStart;
itxtLen := Pos(' ', Copy(Memo.Text,itxtStart+1,MaxInt));
if itxtLen = 0 then
StopLoop := True;
Memo.SelStart := itxtStart;
Memo.SelLength := -1 + itxtLen;
if Memo.SelText = '' then
Continue;
Caption:=Memo.SelText;
WordDoc.Range.Delete(EmptyParam,Emp-tyParam);
WordDoc.Range.Set_Text(Memo.SelText);
colSpellErrors := WordDoc.SpellingErrors;
if colSpellErrors.Count <> 0 then
begin
colSuggestions := WordApp.GetSpellingSuggestions
(colSpellErrors.Item(1).Get_Text);
with frSpellCheck do
begin
edNID.text := colSpellErrors.Item(1).Get_Text;
lbSuggestions.Items.Clear;
for i:= 1 to colSuggestions.Count do
lbSuggestions.Items.Add(VarToStr-(colSuggestions.Item(i)));
lbSuggestions.ItemIndex := 0;
lbSuggestionsClick(Sender);
ShowModal;
case frSpellCheck.ModalResult of
mrAbort: Break;
mrIgnore: Continue;
mrOK:
if sReplacedWord <> '' then
begin
Memo.SelText := sReplacedWord;
itxtLen := Length(sReplacedWord);
end;
end;
end;
end;
end;
WordDoc.Disconnect;
varFalse:=False;
WordApp.Quit(varFalse);
Memo.SelStart := 0;
Memo.SelLength := 0;
end;
|
Обработчики событий нажатий на кнопки формы frSpellCheck и список слов, предлагаемых для замены:
procedure TfrSpellCheck.lbSuggestionsClick(Sen-der: TObject);
begin
if lbSuggestions.ItemIndex <> -1 then
edReplaceWith.Text := lbSuggestions.Items[lbSuggestio-ns.ItemIndex]
else
edReplaceWith.Text := '';
end;
procedure TfrSpellCheck.btnChangeClick(Sender: TObject);
begin
sReplacedWord := edReplaceWith.Text;
end;
procedure TfrSpellCheck.btnIgnoreClick(Sender: TObject);
begin
sReplacedWord := '';
end;
|
Тезаурус
Теперь рассмотрим вопрос добавления в нашу программу функций тезауруса. Делается это достаточно просто:
procedure TForm1.btnThesaurusClick(Sender: TObject);
var
varFalse : OleVariant;
begin
if Memo.SelText <> '' then
begin
WordApp.Connect;
WordDoc.ConnectTo(WordApp.Documen-ts.Add(EmptyParam, EmptyParam));
WordDoc.Range.Delete(EmptyParam,Empty-Param);
WordDoc.Range.Set_Text(Memo.SelText);
WordDoc.Range.CheckSynonyms;
Memo.SelText := WordDoc.Range.Get_Text;
WordDoc.Disconnect;
varFalse:=False;
WordApp.Quit(varFalse);
end;
end;
|
Тестирование
В тексте, помещенном в компонент Memo, мною было сознательно сделано несколько ошибок, которые вы сможете увидеть, приглядевшись к изображению, представленному на рисунке 1. В частности, вместо слова "своих" я написал "свиох", вместо "путем" - "пуетм", а вместо "виде" - "виед". Как же повела себя программа? На следующих рисунках (рисунки 4-6) можно видеть, что проверка текста действительно работает.
Надеюсь, вы понимаете, что в рамках одной статьи невозможно описать все те возможности, которые открываются перед разработчиком программного обеспечения в случае использования серверов автоматизации. И речь идет не только о Microsoft Word, но и о других приложениях (к примеру, широко распространено применение MS Excel в качестве базы для построения отчетов). Все разнообразие данного направления программирования можно познать, на мой взгляд, только через собственный опыт. Так что удачного вам кода!
SQL-запросы в Delphi.
Примечание: Данный документ представляет собой коллективный труд нескольких авторов, которые индивидуально несут ответственность за качество предоставленной здесь информации. Borland не предоставлял, и не может предоставить никакой гарантии относительно содержимого данного документа. Введение Компонент TQuery Свойство SQL Свойство Params Источник данных Команда Format Open против ExecSQL Компонент TStoredProc TDatabase Выводы 1. Введение Компоненты Delphi для работы с базами данных были созданы в расчете на работу с SQL и архитектурой клиент/сервер. При работе с ними вы можете воспользоваться характеристиками расширенной поддержки удаленных серверов. Delphi осуществляет эту поддержку двумя способами. Во-первых, непосредственные команды из Delphi позволяют разработчику управлять таблицами, устанавливать пределы, удалять, вставлять и редактировать существующие записи. Второй способ заключается в использовании запросов на языке SQL, где строка запроса передается на сервер для ее разбора, оптимизации, выполнения и передачи обратно результатов. Данный документ делает акцент на втором методе доступа к базам данных, на основе запросов SQL (pass-through). Авторы не стремились создать курсы по изучению синтаксиса языка SQL и его применения, они ставили перед собой цель дать несколько примеров использования компонентов TQuery и TStoredProc. Но чтобы сделать это, необходимо понимать концепцию SQL и знать как работают selects, inserts, updates, views, joins и хранимые процедуры (stored procedures). Документ также вскользь касается вопросов управления транзакциями и соединения с базой данных, но не акцентирует на этом внимание. Итак, приступая к теме, создайте простой запрос типа SELECT и отобразите результаты. 2. Компонент TQuery Если в ваших приложениях вы собираетесь использовать SQL, то вам непременно придется познакомиться с компонентом TQuery. Компоненты TQuery и TTable наследуются от TDataset. TDataset обеспечивает необходимую функциональность для получения доступа к базам данных. Как таковые, компоненты TQuery и TTable имеют много общих признаков. Для подготовки данных для показа в визуальных компонентах используется все тот же TDatasource. Также, для определения к какому серверу и базе данных необходимо получить доступ, необходимо задать имя псевдонима. Это должно выполняться установкой свойства aliasName объекта TQuery. Свойство SQL Все же TQuery имеет некоторую уникальную функциональность. Например, у TQuery имеется свойство с именем SQL. Свойство SQL используется для хранения SQL-запроса. Ниже приведены основные шаги для составления запроса, где все служащие имеют зарплату свыше $50,000. Создайте объект TQuery Задайте псевдоним свойству DatabaseName. (Данный пример использует псевдоним IBLOCAL, связанный с демонстрационной базой данных employee.gdb). Выберите свойство SQL и щелкните на кнопке с текстом - '...' (три точки, Инспектор Объектов - В.О.). Должен появиться диалог редактора списка строк (String List Editor). Введите: Select * from EMPLOYEE where SALARY>50000. Нажмите OK. Выберите в Инспекторе Объектов свойство Active и установите его в TRUE. Разместите на форме объект TDatasource. Установите свойство Dataset у TDatasource в Query1. Разместите на форме TDBGrid. Установите его свойство Datasource в Datasource1.Свойство SQL имеет тип TStrings. Объект TStrings представляет собой список строк, и чем-то похож на массив. Тип данных TStrings имеет в своем арсенале команды добавления строк, их загрузки из текстового файла и обмена данными с другим объектом TStrings. Другой компонент, использующий TStrings - TMemo. В демонстрационном проекте ENTRSQL.DPR (по идее, он должен находится на отдельной дискетте, но к "Советам по Delphi" она не прилагается - В.О.), пользователь должен ввести SQL-запрос и нажать кнопку "Do It" ("сделать это"). Результаты запроса отображаются в табличной сетке. В Листинге 1 полностью приведен код обработчика кнопки "Do It".Листинг 1.
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
Query1.close; {Деактивируем запрос в качестве одной из мер предосторожности }
Query1.SQL.Clear; {Стираем любой предыдущий запрос}
If Memo1.Lines[0] <> '' {Проверяем на предмет пустого ввода} then
Query1.SQL.Add(Memo1.Text) {Назначаем свойству SQL текст Memo}
else
begin
messageDlg('Не был введен SQL-запрос', mtError, [mbOK], 0);
exit;
end;
try {перехватчик ошибок}
Query1.Open; {Выполняем запрос и открываем набор данных}
except {секция обработки ошибок}
On e : EDatabaseError do {e - новый дескриптор ошибки}
messageDlg(e.message,
mtError,
[mbOK],0); {показываем свойство message объекта e}
end; {окончание обработки ошибки}
end;
|
select * from EMPLOYEE
where DEPT_NO = :Dept_no
Если вам нужно протестировать, или установить для параметра значение по умолчанию, выберите свойство Params объекта Query1. Щелкните на кнопке '...'. Должен появиться диалог настройки параметров. Выберите параметр Dept_no. Затем в выпадающем списке типов данных выберите Integer. Для того, чтобы задать значение по умолчанию, введите нужное значение в поле редактирования "Value".
Для изменения SQL-запроса во время выполнения приложения, параметры необходимо связать (bind). Параметры могут изменяться, запрос выполняться повторно, а данные обновляться. Для непосредственного редактирования значения параметра используется свойство Params или метод ParamByName. Свойство Params представляет из себя массив TParams. Поэтому для получения доступа к параметру, необходимо указать его индекс. Для примера,
Query1.params[0].asInteger := 900; |
Свойство asInteger читает данные как тип Integer (название говорит само за себя). Это не обязательно должно указывать но то, что поле имеет тип Integer. Например, если тип поля VARCHAR(10), Delphi осуществит преобразование данных. Так, приведенный выше пример мог бы быть записан таким образом:
Query1.params[0].asString := '900'; |
Или так:
Query1.params[0].asString := edit1.text; |
Если вместо номера индекса вы хотели бы использовать имя параметра, то воспользуйтесь методом ParamByName. Данный метод возвращает объект TParam с заданным именем. Например:
Query1.ParamByName('DEPT_NO').asInteger := 900;
|
Листинг 2.
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
Query1.close; {Деактивируем запрос в качестве одной из мер предосторожности }
if not Query1.prepared
then
Query1.prepare; {Убедимся что запрос подготовлен}
{Берем значение, введенное пользователем и заменяем
им параметр.}
if edit1.text <> '' {Проверяем на предмет пустого ввода}
then
Query1.ParamByName('DEPT_NO').AsString := edit1.text
else
Begin
Query1.ParamByName('DEPT_NO').AsInteger := 0;
edit1.text := '0';
end;
try {перехватчик ошибок}
Query1.Open; {Выполняем запрос и открываем набор данных}
except {секция обработки ошибок}
On e : EDatabaseError do {e - новый дескриптор ошибки} messageDlg(e.message,
mtError,
[mbOK],0); {показываем свойство message объекта e}
end; {окончание обработки ошибки}
end;
|
Листинг 3 - Использования события OnDataChange для просмотра дочерних записей.
procedure TForm1.DataSource2DataChange(Sender: TObject; Field: TField);
begin
Query1.Close;
if not Query1.prepared
then
Query1.prepare;
Query1.ParamByName('Dept_no').asInteger := Table1Dept_No.asInteger;
try
Query1.Open;
except
On e : EDatabaseError do
messageDlg(e.message, mtError, [mbOK], 0);
end;
end;
|
select * from EMPLOYEE
where DEPT_NO = :dept_no
Выберите свойство Datasource и назначьте источник данных, связанный с Table1 (Datasource2 в нашем примере)
Выберите свойство Active и установите его в True
Это все, если вы хотите создать такой тип отношений. Тем не менее, существуют некоторые ограничения на параметризованные запросы. Параметры ограничены значениями. К примеру, вы не можете использовать параметр с именем Column или Table. Для создания запроса, динамически изменяемого имя таблицы, вы могли бы использовать технику конкатенации строки. Другая техника заключается в использовании команды Format.
Команда Format
Команда Format заменяет параметры форматирования (%s, %d, %n и пр.) передаваемыми значениями. Например,
Format('Select * from %s', ['EMPLOYEE'])
|
Результатом вышеприведенной команды будет 'Select * from EMPLOYEE'. Функция буквально делает замену параметров форматирования значениями массива. При использовании нескольких параметров форматирования, замена происходит слева направо. Например,
tblName := 'EMPLOYEE';
fldName := 'EMP_ID';
fldValue := 3;
Format('Select * from %s where %s=%d', [tblName, fldName, fldValue])
|
Листинг 5 - Использование команды Format для создания SQL-запроса.
procedure TForm1.BitBtn1Click(Sender: TObject);
var
sqlString : string; {здесь хранится SQL-запрос}
fmtStr1,
fmtStr2 : string; {здесь хранится строка, передаваемая для форматирования}
begin
{ Создание каркаса запроса }
sqlString := 'Select EMP_NO %s from employee where SALARY %s';
if showSalaryChkBox.checked {Если checkbox Salary отмечен}
then
fmtStr1 := ', SALARY'
else
fmtStr1 := '';
if salaryEdit.text <> '' { Если поле редактирования Salary не пустое }
then
fmtStr2 := salaryEdit.text
else
fmtStr2 := '>0';
Query1.Close; {Деактивируем запрос в качестве одной из мер предосторожности }
Query1.SQL.Clear; {Стираем любой предыдущий запрос}
Query1.SQL.Add(Format(sqlString,[fmtStr1, fmtStr2]));{Добавляем}
{форматированную строку к свойству SQL}
try {перехватчик ошибок}
Query1.Open; {Выполняем запрос и открываем набор данных}
except {секция обработки ошибок}
On e : EDatabaseError do {e - новый дескриптор ошибки}
messageDlg(e.message, mtError,[mbOK],0);
{показываем свойство message объекта e}
end; {окончание обработки ошибки}
end;
|
Листинг 6.
procedure TForm1.BitBtnClick(sender : TObject)
begin
Query1.Close;
Query1.Clear;
Query1.SQL.Add('Update SALARY from EMPLOYEE ' +
'where SALARY<:salary values (SALARY*(1+:raise)');
Query1.paramByName('salary').asString := edit1.text;
Query1.paramByName('raise').asString := edit2.text;
try
Query1.ExecSQL;
except
On e : EDatabaseError do
messageDlg(e.message, mtError, [mbOK], 0);
end;
end;
|
Select * from ORG_CHARTПри работе с другими серверами, например, Sybase, вы можете использовать компонент TStoredProc. Данный компонент имеет свойства для имен базы данных и хранимой процедуры. Если процедура требует на входе каких-то параметров, используйте для их ввода свойство Params. 4. TDatabase Компонент TDatabase обеспечивает функциональность, которой не хватает TQuery и TStoredProc. В частности, TDatabase позволяет создавать локальные псевдонимы BDE, так что приложению не потребуются псевдонимы, содержащиеся в конфигурационном файле BDE. Этим локальным псевдонимом в приложении могут воспользоваться все имеющиеся TTable, TQuery и TStoredProc. TDatabase также позволяет разработчику настраивать процесс подключения, подавляя диалог ввода имени и пароля пользователя, или заполняя необходимые параметры. И, наконец, самое главное, TDatabase может обеспечивать единственную связь с базой данных, суммируя все операции с базой данных через один компонент. Это позволяет элементам управления для работы с БД иметь возможность управления транзакциями. Транзакцией можно считать передачу пакета информации. Классическим примером транзакции является передача денег на счет банка. Транзакция должна состоять из операции внесения суммы на новый счет и удаления той же суммы с текущего счета. Если один из этих шагов по какой-то причине был невыполнен, транзакция также считается невыполненной. В случае такой ошибки, SQL сервер позволяет выполнить команду отката (rollback), без внесения изменений в базу данных. Управление транзакциями зависит от компонента TDatabase. Поскольку транзакция обычно состоит из нескольких запросов, вы должны отметить начало транзакции и ее конец. Для выделения начала транзакции используйте TDatabase.BeginTransaction. Как только транзакция начнет выполняться, все выполняемые команды до вызова TDatabase.Commit или TDatabase.Rollback переводятся во временный режим. При вызове Commit все измененные данные передаются на сервер. При вызове Rollback все изменения теряют силу. Ниже в листинге 7 приведен пример, где используется таблица с именем ACCOUNTS. Показанная процедура пытается передать сумму с одного счета на другой.
Листинг 7.
procedure TForm1.BitBtn1Click(Sender: TObject);
{ ПРИМЕЧАНИЕ: Поле BALANCE у ACCOUNTS имеет триггер, проверяющий
ситуацию, когда вычитаемая сумма превышает BALANCE. Если так, UPDATE
будет отменен}
begin
try
database1.StartTransaction;
query1.SQL.Clear;
{ Вычитаем сумму из выбранного счета }
query1.SQL.Add(Format('update ACCOUNTS ' +
'set BALANCE = BALANCE - %s ) ' +
'where ACCT_NUM = %s ',
[edit1.text,
Table1Acct_Num.asString]));
query1.ExecSQL;
query1.SQL.Clear;
{ Добавляем сумму к выбранному счету }
query1.SQL.Add(Format('update ACCOUNTS ' +
'set BALANCE = BALANCE + %s ' +
'where ACCT_NUM = %s ',
[edit1.text,
Table2Acct_Num.asString]));
query1.ExecSQL;
database1.Commit; {В этом месте делаем все изменения}
table1.Refresh;
table2.Refresh;
except
{При возникновении в приведенном коде любых ошибок,
откатываем транзакцию назад}
One : EDatabaseError do
begin
messageDlg(e.message, mtError, [mbOK], 0);
database1.rollback;
exit;
end;
One : Exception do
begin
messageDlg(e.message, mtError, [mbOK], 0);
database1.rollback;
exit;
end;
end;
end;
|
Листинг 8.
procedure TForm1.Button1Click(Sender: TObject);
begin
try
With database1 do
begin
Close;
DriverName := 'INTRBASE';
KeepConnection := TRUE;
LoginPrompt := FALSE;
With database1.Params do
begin
Clear;
Add('SERVER NAME=C:\IBLOCAL\EXAMPLES\EMPLOYEE.GDB');
Add('SCHEMA CACHE=8');
Add('OPEN MODE=READ/WRITE');
Add('SQLPASSTHRU MODE=SHARED NOAUTOCOMMIT');
Add('USER NAME=' + edit1.text);
Add('PASSWORD=' + edit2.text);
end;
Open;
end;
session.getTableNames(database1.databasename, '*',
TRUE,
TRUE,
ComboBox1.items);
Except
One : EDatabaseError do
begin
messageDlg(e.message, mtError, [mbOK], 0);
end;
end;
end;
|
Delphi поддерживает множество характеристик при использовании языка SQL с вашими серверами баз данных. На этой ноте разрешите попращаться и пожелать почаще использовать SQL в ваших приложениях.
Получение данных из Delphi-приложения в документе Word.
Сделайте следующее: Создайте макрос в Word:
Declare Function StringFromDelphi Lib "c:\sample\test.dll" As String Sub MAIN mystring$ = StringFromDelphi Insert mystring$ End Sub |
library Test; (* test.dpr в c:\sample *) uses Testform in 'TESTFORM.PAS'; exports StringFromDelphi; begin end. |
unit Testform; (* testform.pas в c:\sample *)
interface
uses
WinTypes, WinProcs, Forms, Classes, Controls, StdCtrls, SysUtils;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
end;
var
Form1: TForm1;
function StringFromDelphi: PChar; export;
{$IFDEF WIN32}stdcall;
{$ENDIF}
implementation
{$R *.DFM}
function StringFromDelphi: Pchar;
var
StringForWord: array[0..255] of char;
begin
Application.CreateForm(TForm1, Form1);
Form1.ShowModal;
Result := StrPCopy(StringForWord, Form1.Button1.caption);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
close;
end;
end.
|
В журнале PCMagazine Vol12.No22 опубликована статья о доступе к DLL из Word. Ознакомиться с ней вы можете в электронной версии журнала на веб-сайте PCMagazine.
Как удалить файл после перезагрузки Windows.
Автор: Inko WEB-сайт: http://forum.vingrad.ruЯ использую функцию, которая заносит в ключ реестра RunOnce командную строку:
command.com /c del C:\Путь\Имя_файла
Как удалить файл после перезагрузки Windows 2.
Автор: VoL WEB-сайт: http://forum.vingrad.ruВ wininit добавляешь строку NUL={ПУТЬ УДАЛЯЕМОГО ФАЙЛА}.
Как удалить все файлы из Recent Documents List.
// Не забудьте включить ShlObj в unit procedure TForm1.Button1Click(Sender: TObject); begin SHAddToRecentDocs(SHARD_PATH, 0); end; |
Удалить все выделенные элементы списка.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
for i := ListBox1.Items.Count - 1 downto 0 do
if ListBox1.Selected[i] then
ListBox1.Items.Delete(i);
end;
Удаление файла в корзину.
Автор: Dimka Maslov WEB-сайт: http://delphibase.endimus.com
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Удаление файла в корзину
Функция удаляет файл, имя которого указаывается в аргументе FileName
в корзину. Второй необязательный параметр указывает на Handle окна,
которое будет родительским для отображаемых системой диалоговых окон
Зависимости: Windows, ShellAPI, Forms
Автор: Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright: Dimka Maslov
Дата: 06 мая 2002 г.
***************************************************** }
function Recycle(const FileName: string; Wnd: HWND = 0): Boolean;
var
FileOp: TSHFileOpStruct;
begin
FillChar(FileOp, SizeOf(FileOp), 0);
if Wnd = 0 then
Wnd := Application.Handle;
FileOp.Wnd := Wnd;
FileOp.wFunc := FO_DELETE;
FileOp.pFrom := PChar(FileName);
FileOp.fFlags := FOF_ALLOWUNDO or FOF_NOERRORUI or FOF_SILENT;
Result := (SHFileOperation(FileOp) = 0) and (not
FileOp.fAnyOperationsAborted);
end;
Пример использования:
procedure TForm1.Button1Click(Sender: TObject)
begin
Recycle('d:\folder\filename.ext', Handle);
end;
Обрезание имени файла по длине.
Автор: ___Nikolay WEB-сайт: http://delphiworld.narod.ru
// Обрезание имени файла по длине
function CutFoldersFromFileName(s: string; iLength: integer): string;
var
bm: TBitmap;
sResult: string;
iStrLen: integer;
// Поменять порядок символов в строке
function ChangeLettersOrder(s: string): string;
var
sResult: string;
i: integer;
begin
sResult := '';
if Trim(s) <> '' then
for i := Length(s) downto 1 do
sResult := sResult + s[i];
Result := sResult;
end;
// Количество вхождений символа в строку
function SymbolEntersCount(ch: char; s: string; bCaseInsensitive: boolean): integer;
var
i, iResult: integer;
cSymbol: char;
function LoCase(ch: char): char;
begin
if (ch in ['A'..'Z', 'А'..'Я']) then
result := chr(ord(ch) + 32)
else
result := ch;
end;
begin
iResult := 0;
if bCaseInsensitive then
ch := LoCase(ch);
if s <> '' then
for i := 1 to Length(s) do
begin
cSymbol := s[i];
if bCaseInsensitive then
cSymbol := LoCase(cSymbol);
if cSymbol = ch then
inc(iResult);
end;
Result := iResult;
end;
// Удалить имя последнего каталога из полного имени файла
function DeleteLastFolderFromFileName(sFileName: string): string;
var
sResult, sFName: string;
begin
sResult := sFileName;
if Pos('\...\', sResult) <> 0 then
Delete(sResult, Pos('\...\', sResult), 4);
if SymbolEntersCount('\', sResult, false) < 2 then
begin
Result := sFileName;
exit;
end;
sResult := ChangeLettersOrder(sResult);
sFName := ChangeLettersOrder(Copy(sResult, 1, Pos('\', sResult) - 1)); // Взяли имя файла
Delete(sResult, 1, Pos('\', sResult)); // Удалили имя файла
Delete(sResult, 1, Pos('\', sResult)); // Удалили имя каталог перед именем файла
sResult := ChangeLettersOrder(sResult) + '\...\' + sFName;
Result := sResult;
end;
begin
Result := s;
if (Trim(s) = '') or (SymbolEntersCount('\', s, false) < 2) then
exit;
sResult := s;
bm := TBitmap.Create;
bm.Width := 100;
bm.Height := 100;
iStrLen := bm.Canvas.TextWidth(sResult);
while iStrLen > iLength do
begin
sResult := DeleteLastFolderFromFileName(sResult);
iStrLen := bm.Canvas.TextWidth(sResult);
if SymbolEntersCount('\', sResult, false) < 3 then
break;
end;
bm.Free;
Result := sResult;
end;
Удаление непустого каталога вместе с подкаталогами.
Автор: Lipskiy WEB-сайт: http://delphibase.endimus.com
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Удаление непустого каталога вместе с подкаталогами
Удаление подкаталогов рекурсивное - функция вызывает саму себя.
Описание назначения агрументов:
-DeleteAllFilesAndFolder - если TRUE то функцией будут предприняты
попытки для установки атрибута faArchive любому файлу или папке
перед его(её) удалением;
-StopIfNotAllDeleted - если TRUE то работа функции моментально
прекращается если возникла ошибка удаления хотя бы одного файла или папки;
-RemoveRoot - если TRUE, указывает на необходимость удаления корня.
Зависимости: FileCtrl, SysUtils
Автор: lipskiy, lipskiy@mail.ru, ICQ:51219290, Санкт-Петербург
Copyright: Собственное написание (lipskiy)
Дата: 26 апреля 2002 г.
***************************************************** }
function FullRemoveDir(Dir: string; DeleteAllFilesAndFolders,
StopIfNotAllDeleted, RemoveRoot: boolean): Boolean;
var
i: Integer;
SRec: TSearchRec;
FN: string;
begin
Result := False;
if not DirectoryExists(Dir) then
exit;
Result := True;
// Добавляем слэш в конце и задаем маску - "все файлы и директории"
Dir := IncludeTrailingBackslash(Dir);
i := FindFirst(Dir + '*', faAnyFile, SRec);
try
while i = 0 do
begin
// Получаем полный путь к файлу или директорию
FN := Dir + SRec.Name;
// Если это директория
if SRec.Attr = faDirectory then
begin
// Рекурсивный вызов этой же функции с ключом удаления корня
if (SRec.Name <> '') and (SRec.Name <> '.') and (SRec.Name <> '..') then
begin
if DeleteAllFilesAndFolders then
FileSetAttr(FN, faArchive);
Result := FullRemoveDir(FN, DeleteAllFilesAndFolders,
StopIfNotAllDeleted, True);
if not Result and StopIfNotAllDeleted then
exit;
end;
end
else // Иначе удаляем файл
begin
if DeleteAllFilesAndFolders then
FileSetAttr(FN, faArchive);
Result := SysUtils.DeleteFile(FN);
if not Result and StopIfNotAllDeleted then
exit;
end;
// Берем следующий файл или директорию
i := FindNext(SRec);
end;
finally
SysUtils.FindClose(SRec);
end;
if not Result then
exit;
if RemoveRoot then // Если необходимо удалить корень - удаляем
if not RemoveDir(Dir) then
Result := false;
end;
Пример использования:
FullRemoveDir('C:\a', true, true, true);
// Полное удаление папки C:\a со всем её содержимым,
// и с последующим удалением самой c:\a
Удалить кнопки сворачивания и максимизации с формы.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
{
This article shows by example how to suppress the maximize and
minimize buttons on an form at runtime.
To disable an form's Minimize and Maximize buttons,
you need to use the SetWindowLong Windows API
function to change the style of the window.
}
{ Dieses Beispiel zeigt, wie man die Schaltflachen zur Minimierung,
Maximierung einer Form zur Laufzeit verstecken kann.
Man braucht dafur die SetWindowLong Windows API um den Stil
des Fensters zu andern.
Der Code kann auch fur non-VCL Anwendungen gebraucht werden.
}
// Add the following code to the OnCreate event
// procedure for your form (TForm1):
procedure TForm1.FormCreate(Sender: TObject);
var
l: DWORD;
begin
l := GetWindowLong(Self.Handle, GWL_STYLE);
l := l and not (WS_MINIMIZEBOX);
l := l and not (WS_MAXIMIZEBOX);
l := SetWindowLong(Self.Handle, GWL_STYLE, l);
end;
Как удалить одно значение из динамического массива.
Автор: http://www.swissdelphicenter.ch
type
TArrayString = array of string;
procedure DeleteArrayIndex(var X: TArrayString; Index: Integer);
begin
if Index > High(X) then
Exit;
if Index < Low(X) then
Exit;
if Index = High(X) then
begin
SetLength(X, Length(X) - 1);
Exit;
end;
Finalize(X[Index]);
System.Move(X[Index + 1], X[Index],
(Length(X) - Index - 1) * SizeOf(string) + 1);
SetLength(X, Length(X) - 1);
end;
// Example : Delete the second item from array a
// Beispiel : Losche das 2. Element vom array a
procedure TForm1.Button2Click(Sender: TObject);
var
a: TArrayString;
begin
DeleteArrayIndex(a, 2);
end;
Удалить свою программу после ее завершения.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
procedure DeleteEXE;
function GetTmpDir: string;
var
pc: PChar;
begin
pc := StrAlloc(MAX_PATH + 1);
GetTempPath(MAX_PATH, pc);
Result := string(pc);
StrDispose(pc);
end;
function GetTmpFileName(ext: string): string;
var
pc: PChar;
begin
pc := StrAlloc(MAX_PATH + 1);
GetTempFileName(PChar(GetTmpDir), 'uis', 0, pc);
Result := string(pc);
Result := ChangeFileExt(Result, ext);
StrDispose(pc);
end;
var
batchfile: TStringList;
batchname: string;
begin
batchname := GetTmpFileName('.bat');
FileSetAttr(ParamStr(0), 0);
batchfile := TStringList.Create;
with batchfile do
begin
try
Add(':Label1');
Add('del "' + ParamStr(0) + '"');
Add('if Exist "' + ParamStr(0) + '" goto Label1');
Add('rmdir "' + ExtractFilePath(ParamStr(0)) + '"');
Add('del ' + batchname);
SaveToFile(batchname);
ChDir(GetTmpDir);
ShowMessage('Uninstalling program...');
WinExec(PChar(batchname), SW_HIDE);
finally
batchfile.Free;
end;
Halt;
end;
end;
Удалить из директории проекта лишние файлы.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
program Clean;
{
The program registers as a explorer extension.
Use rightclick on a Delphiproject directory and choose Clean Delphi files.
Place the executable somewhere in program files and run once.
Dieses Program registriert sich beim Ausfuhren als
Explorer-Erweiterung.
Mit einem Rechtsklick auf ein Delphi Verzeichnis und
einem Klick auf "Clean Delphi files" werden die Verzeichnisse "aufgeraumt".
}
uses
SysUtils, Registry, WinTypes, FileCtrl;
var
dir: string;
const
// HKEY_CLASSES_ROOT
DirExtensionKey = 'Directory\shell\clean';
SubKey = '\command';
procedure PerformAction(const dir, fname: string);
// Set selected attributes for a file.
begin
if not SysUtils.DeleteFile(dir + fname) then
begin
end;
end;
procedure ScanDirectory(const dir, filemask: string);
// Call performaction for every file which looks like filemask
var
SearchRec: TSearchRec;
begin
if not DirectoryExists(dir) then
begin
MessageBox(0, 'Directory not found', 'Clean', mb_ok);
Halt;
end;
if dir <> '' then ChDir(dir);
// Search subdirs ?
if True then
begin
if FindFirst('*.*', faDirectory, SearchRec) = 0 then
repeat
if (SearchRec.Name <> '.') and (SearchRec.Name <> '..') and
((SearchRec.Attr and faDirectory) = faDirectory) then
ScanDirectory(SearchRec.Name, filemask);
until (FindNext(SearchRec) <> 0);
SysUtils.FindClose(SearchRec);
end;
// Ennumerate all files in current directory
if FindFirst(filemask, faAnyFile, SearchRec) = 0 then
repeat
if (SearchRec.Name <> '.') and (SearchRec.Name <> '..') and
((SearchRec.Attr and faDirectory) = 0) then
PerformAction('', SearchRec.Name);
until (FindNext(SearchRec) <> 0);
SysUtils.FindClose(SearchRec);
// Directory up
Chdir('..');
end;
procedure DoPopupReg;
var
reg: TRegistry;
begin
try
reg := TRegistry.Create;
except
Exit;
end;
if True then
with reg do
begin
RootKey := HKEY_CLASSES_ROOT;
OpenKey(DirExtensionkey, True);
WriteString('', 'Clean Del&phi files');
CloseKey;
OpenKey(DirExtensionkey + SubKey, True);
WriteString('', ParamStr(0) + ' "%1"');
CloseKey;
end
else
with reg do
begin
RootKey := HKEY_CLASSES_ROOT;
DeleteKey(DirExtensionkey);
CloseKey;
end;
reg.Free;
end;
begin
DoPopupReg;
if ParamCount > 0 then dir := ParamStr(1)
else
Exit;
ScanDirectory(dir, '*.dcu');
ScanDirectory(dir, '*.~*');
// You can add more files here
end.
Удалить пункт системного меню.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
procedure TForm1.Button1Click(Sender: TObject); var Handle: THandle; begin Handle := GetSystemMenu(Self.Handle, False); RemoveMenu(Handle, 1, MF_BYPOSITION); RemoveMenu(Handle, 2, MF_BYPOSITION); end;
Производная TIntegerField.
Автор: Mark Edington Я думал о производной, новом варианте компонента TIntegerfield, но я не могу понять как мне его получить во время разработки, ведь он не устанавливается в палитру компонентов. Это то, что вы хотите. Создайте следующий молуль:MICRON.PAS:
unit micron;
interface
uses DB, DBTables, Classes;
type
TMicronField = class(TIntegerField)
public
function IsValidChar(Ch: Char): Boolean; override;
end;
procedure Register;
implementation
function TMicronField.IsValidChar(Ch: Char): Boolean;
begin
Result := Ch in ['+', '-', '0'..'9', '.'];
end;
procedure Register;
begin
RegisterFields([TMicronField]);
end;
end.
|
Другое решение, более простое (но так-же работающее), заключается в изменении исходного кода DBTables и простой замене существующей функции IsValidChar на TIntegerField.
Производная TOutline.
Автор: Craig Osterloh Я пытаюсь создать компонент, производный от TOutline и хотел бы использовать несколько иконок для изображения папок, документов и т.п. Только не знаю как. Если установлен стиль otOwnerDraw, вы можете сами отрисовывать компонент. В этом режиме вы можете вывести какие угодно изображения. Примечание: чтобы получить OwnerDraw для работы, вы должны установить свойство Scrollbar в vsVertical. Затем обрабатывайте событие OnDrawItem для рисования каждой строчки OutLine. Для получения правильного индекса узла используйте GetItem(Rect.Left, Rect.Top). Индекс в drawItem неверен.Вот пример из моего приложения. Надеюсь это вам поможет.
procedure TfrmMain.Outline2DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
var
Node: TOutlineNode;
NodeIdx: Integer;
Offset: Integer;
NowBitmap: TBitmap;
NowAstValue: string;
begin
NodeIdx := Outline2.GetItem(Rect.Left, Rect.Top);
Node := Outline2.Items[NodeIdx];
NowAstValue := PAstRec(Outline2.Items[NodeIdx].Data)^.AstValue;
with Outline2.Canvas do
begin
Font.Name := 'MS Sans Serif';
Font.Size := 8;
Offset := MulDiv(Font.Size, 150, 100);
fillRect(Rect);
Rect.Left := Rect.Left + ((Node.Level - 1) * Offset + 2);
if Node.HasItems then
begin
if Node.Expanded then
NowBitmap := Outline2.PictureMinus
else
NowBitmap := Outline2.PicturePlus;
{рисуем иконку}
BrushCopy(Rect, NowBitmap,
Bounds(0, 0, Rect.Right - Rect.Left, Rect.Bottom -
Rect.Top),
NowBitmap.TransparentColor);
end;
Rect.Left := Rect.Left + Offset + 2;
{выводим текст}
TextOut(Rect.left, Rect.Top, Node.Text);
{создаем суммирующую колонку с правым выравниванием}
Rect.Left := Rect.Right - TextWidth(NowAstValue) - 2;
TextOut(Rect.Left, Rect.Top, NowAstValue);
end;
end;
|
А как работает функция PAstRec() и как получить указатель на данные?
function TfrmMain.LoadAssetNodes(fName: string; header: string): Double;
var
headerIdx: Integer;
NowClientID: string;
TotAssetValue: Double;
AstRecPtr: PAstRec;
sqlText: string;
HeadPtr: PAstRec;
begin
with qryAssets do
begin
NowClientID := dtbClients.Fields[0].AsString;
sqlText := 'Select * from ' + fName + ' where ClientID = ' +
NowClientID;
SQL.Clear;
SQL.Add(sqlText);
Open;
First;
TotAssetValue := 0;
new(HeadPtr);
headerIdx := outAssets.AddObject(0, header, HeadPtr);
while not EOF do
begin
new(AstRecPtr);
AstRecPtr^.AstValue := format('%8.0n',
[FieldByName('AssetValue').AsFloat]);
TotAssetValue :=
TotAssetValue + FieldByName('AssetValue').AsFloat;
outAssets.AddChildObject(headerIdx,
FieldByName('AssetName').AsString,
AstRecPtr);
Next;
end;
HeadPtr^.AstValue := format('%8.0n', [TotAssetValue]);
Result := TotAssetValue;
end;
end;
|
Проектирование баз данных.
| Программист ошибается дважды: первый раз - при рождении, а второй раз - при выборе профессии. |
Цель данного доклада - оценить сегодняшние проблемы и тенденции развития технологий проектирования БД, а также, хотя бы отчасти - требования завтрашнего дня. Доклад может сыграть еще одну роль: задать набор актуальных требований, которые будут служить координатами для позиционирования конкретных частных методов и инструментов проектирования БД (отчасти также - средств их использования и управления ими), которые представляются в двух специальных секциях данной конференции.
Интегрированная база данных - констатация идеи
Широко известные методы проектирования баз данных (БД) появились в процессе разработки все более сложных Информационных Систем (ИС), которые должны были рассматривать потребности не одного пользователя, но больших групп и коллективов. Одна такая интегрированная БД создавалась для решения многих задач, каждая из которых использовала только "свою" часть данных, обычно, пересекающуюся с частями, используемыми в других задачах. Поэтому главнейшими методами проектирования стали методы исключения избыточности в данных. Эти методы связывались с другими средствами обеспечения логической целостности данных.
Было сформулировано принципиальное требование отделения программ от интегрированных данных. Этот принцип направлен на отчуждение данных в качестве ресурса предприятия, важен также тем, что консервативные по характеру данные отделялись от прикладных программ, которые могли часто подвергаться изменениям.
Другой важной проблемой проектирования БД явилось обеспечение нужных эксплуатационных параметров, таких как объем внешней памяти или время выполнения различных операций. Известны и другие требования. Например, информация не должна потеряться не только из-за отказов оборудования, но и вследствие ошибки пользователя. Это отличается от того положения, при котором тот, кто решает некую задачу, сам и отвечает за сохранность данных для этой задачи.
Сформировалось понимание интегрированной БД как общего информационного ресурса предприятия. Хранимые данные стали аналогичны большому компьютеру, который одновременно используется многими пользователями с различными целями и должен быть все время работоспособен.
За идеей - классическая методология проектирования
Классическая методология проектирования БД - это мощное и красивое течение со своей философией, способами восприятия реальности и способами существования в ней. В этом течении возникла своя прикладная математика, свое понятие "Мира", "Предметной Области" (ПрО) и их моделей. В отношении проектирования БД осознаны и интегрированы в стройные схемы методы выполнения таких проектных этапов:
сбор сведений о ПрО (анализ потребностей и описание ПрО с использованием так называемых "процессного" или UP, "usage perspective" подхода и "непроцессного" или ISP, "information structure perspective" подхода); выбор языка представления т.н. "семантической" модели для фиксации сведений о ПрО, их последующего анализа и синтеза модели БД; анализ собранных сведений о ПрО: классификация, формализация и интеграция структурных элементов описания ПрО, формализация как структурных, так и процедурных ограничений целостности элементов в будущей модели ПрО, определение динамики экземпляров объектов ПрО; синтез концептуальной модели БД: проектирование целостной концептуальной схемы БД на выбранном языке семантического моделирования; выбор конкретной модели данных и СУБД для реализации БД; проектирование логической схемы БД для выбранной СУБД (называющееся также "проектирование реализации"); разработка физической структуры БД ("физической" или "внутренней" схемы, она же - "схема размещения"), включая размещение БД по узлам; разработка технологии и процедур начального создания и заполнения БД; разработка технологии и процедур сопровождения БД; разработка универсальных программ доступа к БД и соответствующих интерфейсов пользователей; информационное обеспечение разработки конкретных программ обработки данных: обеспечение метаинформацией, данными контрольных примеров и др.; получение обратной связи от разработчиков прикладных программ и пользователей Информационной Системы (ИС) о полноте и эффективности организации БД; тестирование БД, ее развитие и улучшение (настройка) ее структуры.Есть все основания называть методологию классической: для указанных методов разработаны полные, целостные методические системы, для большинства методов предложены формализованные модели, эти модели - или, по крайней мере, их итоговые выразительные возможности - нашли реальное применение в практике проектирования. Один только перечень основных моделей данных и их авторов производит внушительное впечатление, см. их обзор, например, в [Цикридзис85].
Использовалась дисциплина т.н. структурного анализа при проектном подходе "сверху вниз". Структурность связывается с использованием иерархических структур для детализации данных и функций, и соответствующих достаточно "жестких" проектных процедур. Проектная схема получила название "каскадной". Она хорошо согласована с аналогичной схемой проектирования ПО - программного обеспечения.
За методологией - мастерская инструментов проектирования БД
Проектирование комплексной по предметной направленности, интегрированной и, обычно, большой по размеру БД стало сложной задачей. Наличие целостной методологии проектирования позволило позаботиться о "сапожнике-проектировщике" и начать шить ему сапоги в виде систем автоматизации проектирования БД. Этому способствовало наличие технологического опыта в организации и компьютерной поддержке систем разработки программного обеспечения и, с другой стороны, использование активных интегрированных словарей-справочников данных (DD/D, Data Dictionary/Directory). Так возникли системы CASE (Computer Aided System Engineering) - системы для структурного проектирования БД и связанных с ними ИС, ориентированные на модели данных, реализованные в различных СУБД. Наибольшую популярность получили CASE-системы для реляционных СУБД с SQL-моделями данных, а DD/D переименовался в CASE-репозиторий проектируемой ИС.
На этом пути возникло два основных направления развития CASE-систем и технологий проектирования: CASE-системы для проектирования собственно БД (или т. н. Upper-CASE) и интегрированные инструменты, позволяющие и проектировать БД, и разрабатывать использующие их прикладные программы. Важно отметить, что и Upper-CASE в общем случае имеют много средств для описания функций обработки информации (при использовании процессного подхода к сбору и анализу сведений о ПрО) и хранения этих описаний в репозитории. Это подтверждает положение о сильной связи проекта БД и проекта ИС, базирующейся на этой БД. Вместе с тем, эта связь не абсолютна, и принцип отделения БД от программ сохраняется.
Часто интегрированность функций приводит к сильному сращиванию CASE-системы с одной СУБД, на которую ориентированы CASE-средства разработки прикладных программ. Такое сращивание имеет несколько проявлений, например, CASE-репозиторий поддерживается средствами "родной", но единственной СУБД, генерация прикладных программ производится "родными" инструментами разработки этой же СУБД, но только ими. Для таких интегрированных CASE-систем отображение концептуальной модели БД в логическую схему часто делается также только для предопределенной СУБД.
Последний факт связан с еще одной задачей, которая может ставиться при проектировании БД: проектирование переносимой БД, которая может быть реализована на платформах разных типов компьютеров, операционных систем, СУБД и даже моделей данных, и, при необходимости, переноситься с одной платформы на другую.
С учетом сказанного, классическая Мастерская проектировщика БД включает совокупность классических структурных методов проектирования, набор соответствующих инструментов моделирования, реализации, загрузки и сопровождения БД, а также "каскадную" организационную схему выполнения этих работ по принципу "сверху вниз".
Особо - о временных характеристиках и транзакциях
Обеспечение эксплуатационных характеристик БД - по-прежнему непростая задача несмотря на повышение удельной мощности компьютеров и снижение удельной стоимости памяти. При этом определение временных характеристик работы с БД и сохранение этих характеристик в процессе эксплуатации БД относится к труднейшим проектным задачам. На этапах проектирования для определения рациональной физической схемы БД от способов определения временных характеристик нужно следующее:
возможности сравнения временных параметров вариантов реализации разных вариантов схемы БД, на некоторой СУБД; возможности сравнения параметров вариантов реализации одной схемы БД на разных СУБД; возможности сравнения параметров реализации одной схемы БД на разных аппаратных серверах БД; возможности предсказания временных параметров работы различных прикладных программ и служебных программ-утилит.Задача сравнения временных параметров разных СУБД рассматривается как самостоятельная. Однако, она часто должна решаться как часть проектной задачи выбора СУБД для проектируемой БД и в процессе этого проектирования.
Понятие транзакции было введено для определения законченной совокупности действий над БД, которая переводит БД из одного целостного в логическом смысле состояния в другое. На его базе строились, прежде всего, механизмы корректной актуализации и восстановления БД. Однако, затем на этой основе стали базироваться и другие механизмы и методы.
Временные оценки СУБД наиболее популярных тестов последнее время даются в виде числа транзакций определенного стандартизованного вида в единицу времени. Распределенная обработка строится на основе мониторов транзакций.
Нужно будет обнаруживать пределы возможностей такого деления работ на достаточно мелкие порции. Здесь отметим очень важный эффект: практика ориентации на "транзакционный подход" тесно связана с классической методологией проектирования БД, которая развивалась, в основном, как методология проектирования так называемы "операционных" БД, то есть баз данных, которые должны фиксировать отдельные совершаемые операции и хранить модель текущего фактического состояния объекта или ПрО.
Оценка достигнутого состояния
Не исключено, что у читателя создалось впечатление будто мы уже владеем современной методологией или, по крайней мере, близки к этому, что, к сожалению, не так, и, может быть, мы никогда ничего подобного не добьемся. Всегда несложно охарактеризовать методологию на концептуальном уровне, весьма трудно применить ее на практике. Камень преткновения - сложность проникновения в существо предметной области (например, сложности понимания механизма деятельности организации) и адаптации ее к новым, возможно лучшим, условиям функционирования.
Аналогичные проблемы характерны для СУБД в целом. Система баз данных должна стать органическим элементом системы управления организацией - вот залог ее успешного применения. Однако процесс ее внедрения связан с определенными изменениями в самой организации и в деятельности ее сотрудников, и мы всегда будем сталкиваться с естественной инертностью людей, когда речь идет о восприятии изменений....
Весьма важно, чтобы средства СУБД были адекватны потребностям пользователей. Поскольку разным пользователям могут понадобиться разные модели данных, языки данных и схемы, желательно, чтобы СУБД поддерживала множество средств, а пользователь мог выбирать из них наиболее подходящие. ...
Можно, конечно, поставить под сомнение ценность таких исследований. Действительно, каким бы плохим ни был язык программирования, его в конце концов все-таки можно выучить. Точно также и средства СУБД можно освоить за определенный период времени. Но проблема состоит не в освоении средств, а в эффективности их использования. Машина должна быть служанкой человека, но не наоборот!
Выше шрифтом выделена цитата из [Цикритзис85]. С тех пор СУБД, методы проектирования БД и соответствующие инструменты значительно прибавили в возможностях. Но остальной мир тоже не стоял на месте, существенно усложнились стоящие перед разработчиками ИС и БД задачи. И надо признать: формулировки Цикритзиса и Лоховски не устарели.
Что и как из классических методов проектирования применяется в практике настоящего времени
Применяются на практике:
СУБД, поддерживающие реляционную модель данных 1971 года с некоторыми расширениями (см., например, [Дадли96]); Иерархическая "каскадная" схема структурного проектирования БД при подходе "сверху-вниз"; CASE-системы для структурного проектирования баз данных, ИС в целом или, к тому же, прикладных программ ИС. Наиболее часто используются: варианты ER-модели данных; табличная реляционная модель 1971 года, расширенная тем или иным дополнительным набором средств описаний ограничений целостности (ссылочная целостность, бизнес-правила); для анализа "процессного" источника сведений чаще всего предоставляются модели потоков данных или SADT, возможно, расширенные дополнительными связями по управлению (эти связи нельзя смешивать с выделенными потоками условий выполнения функций в нотации IDEF0); Утилиты динамического администрирования БД, обеспечивающие следующие функции: отслеживание динамики показателей эксплуатации БД: показатели доступны в любой момент на фоне работы приложений, эти показатели ("статистика") могут использоваться для поддержки оптимального динамического построения путей доступа к данным, создание резервных копий БД, также как и ведение копий БД горячего резерва на фоне работы приложений, восстановление и откат фрагментов и полной БД, возможна динамическая реорганизация БД (переразмещение БД и отдельных физических фрагментов, логическая и физическая реструктуризация данных), однако, эти возможности являются ограниченными.Учет пользовательских требований к представлению данных в большем диапазоне, чем ранее. Требования к учету специфики представлений часто стали преобразовываться из положений желательности наличия разных внешних моделей данных к положению доступности значительного числа пользовательских инструментов, имеющих различные интерфейсы и, практически, различные внешние модели данных.
Что теряется
Вместе с тем, многое из классического наследия на практике не используется или используется плохо. В первую очередь, это относится к использованию формализованных методов и моделей, если только они не входят в используемую модель данных непосредственно, а должны применяться проектировщиками для получения и верификации высокого качества проектных решений, например:
полная процедура нормализации высоких степеней и минимизации набора отношений не проводится или проводится редко, если же экспертиза проверки на соответствие даже 3НФ или БКНФ предусмотрена в CASE-средствах, эта возможность редко используется на практике ввиду ее громоздкости и высоких требований к квалификации проектировщика, использующего CASE; оптимизация размещения БД на устройствах внешней памяти проводится "на глазок", распространенные сегодня тесты временных параметров не приспособлены для помощи в решении этой задачи проектирования; так же "на глазок" производится оптимизация размещения БД по узлам распределенной БД.Значительно меньшее внимание в последнее время уделяется и инструментальным средствам автоматизации физического проектирования БД, включая математическое и натурное моделирование характеристик БД, в том числе - с учетом размещения по узлам распределенной ИС. Оптимизация размещения БД по узлам распределенной БД не поддерживается распространенными CASE-средствами. Отдельные инструменты и работы, включая отечественные исследования, не делают "погоды" в Мастерской средств проектирования, и не поддерживают живой школы этого направления.
Этому есть, на наш взгляд, несколько причин:
высокие требования к квалификации проектировщиков в области теоретических основ классического проектирования БД; громоздкость методов, используемых в рамках "каскадной" схемы, в условиях практической невозможности обеспечить устойчивость больших интегрированных решений в мире с постоянно меняющимися требованиями к ИС; относительная легкость выполнения реорганизации логической и физической структуры БД в реляционных СУБД (однако, и это конкретизируется в конце доклада, в современных условиях такой подход становится одной из ловушек для проектировщика).Ограничения классических методов
Классические модели и методы ориентировались на организацию хранения и обработки детально структурированных данных, чему отвечало понятие "атрибута" как свойства объекта, представляемого атомарным элементом данных. В следствие этого, например, полнотекстовые БД сразу выделялись в особый тип баз данных. Для их проектирования существовало отдельное течение - ИПС или Информационно-Поисковые Системы.
Спустя годы и десятилетия после объявления своих классических моделей и концепций классики в явном виде описывали их ограниченность. Так, в [Codd79] было указано, что "при обсуждении моделирования семантики данных акцент делается на структурные аспекты в ущерб аспектам обработки. Структура без соответствующих операций или подразумеваемых методов подобна анатомии в отсутствии физиологии".
Еще через 14 лет Е. Кодд и соавторы в [Codd93] фиксировали: "... обладание большой корпоративной БД имеет маленькое значение, если конечные пользователи не имеют возможностей легко синтезировать необходимую информацию из этих запасов (складов) данных. Слишком часто мы имеем именно такие обстоятельства.".
Наконец, наступило время, когда проектирование БД (и ИС в целом) по классическим правилам полноты и целостности часто стало практически бессмысленным. Весли П. Меллинг (Garthner Group) указал в [Меллинг95]: "К 1990 году почти все аспекты "стандартной процедуры работы" с ИТ (Информационными Технологиями - прим. Е.З.) были оспорены, и вычислительные архитектуры вырвались из-под контроля. ... Стандарты программирования размывались, а понятие неизбыточных, непротиворечивых, высококачественных данных годилось разве что для груды хлама.".
Причины появления новых требований
Феномены глобальных компьютерных коммуникаций и повсеместных персональных вычислений (вместе в падением удельной стоимости средств вычислительной техники) привели ко многим новым возможностям БД и их проектирования. Список типов хранимых и обрабатываемых данных расширился до возможных пределов, определяемых самым общим нормативным значением понятия "данное". В корпоративные БД включаются не только неформатированные элементы и полнотекстовые фрагменты, но также БД с геоинформацией, мультимедийные БД, и это не исчерпывающий перечень.
Более того, новые возможности ИТ - вместе с рядом чисто экономических причин - привели к увеличению рыночных возможностей и требований потребителей, как следствие - к резкому усилению конкуренции в различных отраслях промышленности и услуг. Ответом послужило объявление императива бизнес-реинжиниринга: от BPR М. Хаммера ([Hammer93]) до строительства киберкорпораций по Дж. Мартину ([Мартин95]). В этих подходах требуется осуществление радикальных изменений в организации основной деятельности предприятий. В частности:
резкое снижение затрат времени, числа работников и других затрат на выполнение производственных функций; глобализация бизнеса: работа с клиентами и партнерами в любой точке мира, а также работа с клиентом в режиме 24*365; опора на рост мобильности персонала, снабжение работника всеми возможностями для самостоятельного получения конечного результата; работа на будущие потребности клиента, ускоренное продвижение новых технологий.Если ИТ были одним из толчков к такому развитию ситуации, то они оказались призваны и для того, чтобы обеспечить успех и саму возможность планируемых реконструкций. Возникли новые требования к архитектуре корпоративных ИС, как следствие - новые требования к корпоративным БД.
Также, как саму ИС нельзя рассматривать в отрыве от ее пользователей, и новое проектирование должно рассматриваться как интеграция трех составных частей: требований бизнес- реинжиниринга, человеческого фактора и методов новых ИТ. Реальное объединение этих трех составных частей, каждая из которых приобрела в 90-е годы качественно новое наполнение, создало начала того, что можно назвать Новым Системным Проектированием - Н.С.П..
Что нужно от баз данных для ответа на новые требования
Покажем новые требования к корпоративным БД на примере двух аспектов создания новых корпоративных ИС (из более чем двух десятков видов работ, составляющих основу Н.С.П. - см. [Зиндер96]):
Обеспечение максимальных возможностей для каждого работника, то есть поддержка выполнения всех бизнес-функций тем самым работником, который и получает конечный результат. Со стороны ИС, БД и СУБД для этого требуется: обеспечение средств доступа ко всем необходимым данным с использованием распределенных БД, средств репликаций данных, управления событиями в данных и процессах обработки транзакций; использование архитектуры и программных средств хранилища данных, средств Оперативной Аналитической Обработки Данных (OLAP), применение средств быстрой разработки приложений (RAD) для создания "ИС руководителя" (EIS), средств поддержки принятия решений (DSS) на основе хранилища данных, OLAP и RAD/EIS; применение средств DSS на основе анализа БД прецедентов, а также методов логического вывода, нейронных сетей и нейрокомпьютеров, и др.; предложение единого интерфейса пользователя для работы с разными компонентами данных и приложений, использование в этом интерфейсе средств, повышающих простоту поиска информации и обращения к конкретным прикладным функциям, например, функции геоинформсистем, гипертекстовые, естественного языка, речевого ввода. Разработка концепции и структуры корпоративной базы данных для новой ИС, реализация структуры БД, предполагающая снятие (существенное уменьшение) ограничений на ее развитие, в том числе, при смене функций или функциональных компонентов обработки информации: применение методов компонентного проектирования предметных БД как для операционных БД, так и для исторических БД хранилищ данных, архивов документов, гео-информационных и иных данных; разработка процедур компонентного изменения корпоративной БД при изменении бизнес-процедур, видов деятельности, применяемых приложений и географического размещения предприятия; постоянная актуализация понятийной модели деятельности предприятия для учета новых понятий, возникающих при изменении прикладных компонент на функционально сходные и при изменении видов деятельности предприятия, и построение на этой основе новых интерфейсов между компонентами ИС; динамическое администрирование фрагментами распределенной корпоративной БД при изменении частоты их использования, при модификации их структуры и при изменении их размещения.Некоторые новые технические требования к базам данных можно получить из анализа в [Меллинг95].
Вошедшие в практику новые инструменты мастерской проектировщика
Язык SQL, бывший в 80-м году всего лишь одним из языков, представляющих реляционную модель, стал реальным стандартом не только реляционной модели данных, но и промышленных СУБД. (В то же время, это - пример приобретения, которое быстро может стать обременительным.).
В реальных разработках наиболее распространенных организационно-производственных ИС в большинстве случаев или в большей части объемов работ произошла замена средств разработки с SQL во включающем 3GL-языке или языка 4GL процедурного типа на языки и инструменты 4GL с оконным интерфейсом на основе управления через меню и с использованием элементов концепции объектно-ориентированного программирования (и сохранением возможностей выхода на SQL и процедурный язык).
Возникли практически работающие стандарты de facto интероперабельной работы с данными, в первую очередь - стандарт ODBC.
Мультиплатформенность стала нормой, многопротокольность коммуникаций для распределенных БД реализуется на основе стандартов и интероперабельных мониторов транзакций, поддерживается "интернационализация" хотя бы в части настроек на таблицы национальных кодировок данных.
Вошли в практику новые структуры и типы данных, новые операции над данными: неформатированные элементы, полнотекстовые БД и их обработка, ГИС-данные, мультимедийные БД, гипертекстовые распределенные БД, распределенная обработка и обработка, доставляемая вместе с объектом на вход ИС. На практике наблюдаются шаги реальной интеграции упомянутых структур и операций.
Меняется подход к выбору СУБД, в первую очередь - для проектирования корпоративных БД, эксплуатация и развитие которых планируется как минимум на несколько лет. Все более используются экономические основания и критерии для выбора СУБД (см. [Зиндер95а]).
Объектная ориентация в проектировании БД здесь не рассматривается как уже реально существующий в практике новый инструмент Мастерской. (Не имеется в виду объектно- ориентированное программирование.) В настоящее время представляется обоснованным отнести такое проектирование все еще к направлениям исследований.
К новым подходам в организации проектирования БД
Поскольку новые требования в большой, если не определяющей степени связаны с ростом скорости изменений в требованиях к ИС, новые подходы в методах проектирования неразрывно связаны с новой организацией проектирования.
Каскадные схемы организации проектирования ПО для ИС достаточно давно стали преобразовываться в циклические формы. Так, организация продолжающейся разработки IBM corp. (см. [Фокс85]) предусматривала непрерывное контролируемое развитие программной системы в виде передачи в эксплуатацию ее новых версий.
Сейчас различные циклические и спиральные схемы рассматриваются как средство использования преимуществ подходов быстрого прототипирования ИС с исключением их недостатков (неуправляемости) за счет использования классических структурных методов на каждом витке спирали.
Однако, такие циклические схемы сохраняют многие старые недостатки структурных методов. Для условий Н.С.П. важными недостатками являются:
трудоемкость внесения изменений в действующие компоненты; ограничения возможностей компонентного проектирования использующего комплектации и перекомплектации различных готовых компонентов.Существуют и другие, например, громоздкость ведения проектной документации. Все это полностью относится и к проектированию БД.
В условиях компонентного проектирования организационная схема проектирования БД должна выглядеть как схема параллельного спирального проектирования компонентов БД и их комплексирования по необходимости.
Часто можно встретить утверждения, что объектно-ориентированное проектирование и программирование решают проблемы, порожденные структурным подходом и остающиеся при использовании циклических схем. Однако, с использованием таких технологий вопросы смысловой интероперабельности, тем более - при компонентном проектировании, могут в реальности осложниться еще больше из-за инкапсулированности описаний и меньшего внимания к дисциплине документирования данных. Представляется, что делать выводы о границах применимости этих подходов еще рано.
От новых требований к типам и источникам информации - к новым архитектурным принципам БД
Важнейшей задачей проектирования архитектуры корпоративной БД является обеспечение работы с самыми разнообразными типами и источниками информации. В соответствии с реальной практикой источниками и потребителями информации служат не только подразделения данного предприятия, головная контора холдинга или аппарат министерства, но и предприятия других отраслей (возможные поставщики и потребители, государственные регламентирующие органы и др.). Принцип глобализации бизнеса диктует: источники и потребители информации будут находиться в любой географической точке, где это окажется нужно.
Отсюда следуют стратегические для архитектуры БД и ИС в целом решения. Объединение требований к динамике и разнообразию типов информационных потоков, обрабатываемых в ИС, с учетом роста их объемов, и требований к разнообразию методов обработки позволяет дать следующую обобщенную характеристику технологий, формирующих архитектуру БД в составе ИС:
компонентная технология проектирования и перекомплектации предметно- ориентированных операционных БД, допускающих работу пользователей через общие, в том числе, для Хранилища Данных, интерфейсы; расширенная технология Хранилища Данных, интегрирующая исторические форматированные данные, архивные текстовые документы, звуковые и видеоархивы, а также картографические данные, и включающая средства оперативной аналитической обработки данных, необходимые виды "дружественных" интерфейсов, например: гипертекстовый, ГеоИнформСистем и др.; открытость БД для включения в нее и получения из нее информации с использованием принципов Глобальной Информационной Магистрали;Архитектура Открытых Систем, расширенная методами и средствами компонентного формирования: на верхнем уровне это открытость компонентного проектирования БД и свободного обмена с источниками информации любых внешних систем, на нижнем уровне - технологическая открытость БД на основе стандартов переносимости, интероперабельности, масштабируемости и др..
Расширенная технология Интегрального Хранилища заставляет на новой основе ставить вопрос о разработке интегрированной совокупности интерфейсов пользователя, которая создавала бы естественные условия для работы с информацией и функциями вне зависимости от того, к какому классу хранимых данных разработчик вынужден отнести сегодня его (пользователя) информацию.
К новым подходам в методах проектирования БД
Как ответ на новые требования можно рассмотреть рекомендации к новым методам и инструментам проектирования БД. (Конечно, в предположении, что все новое есть ранее кем-то уже обнаруженное старое.).
Об исключении избыточности в данныхСохраняется как разумное требование однократного ввода данных в БД для решения разных задач и защиты от возникновения противоречий (нарушении логической целостности) при актуализации хранимых данных. Однако, в условиях глобального информационного пространства и компонентного проектирования контекст этих требований должен быть пересмотрен. Несомненно, в операционных БД рационально планировать "острова" нормализованных и, в классическом смысле, безызбыточных кластеров отношений или объектов. Эти "острова" чаще всего и будут являться давно известными предметными БД.
Вместе с тем, объединение исторических данных Хранилищ, БД ГИС-систем, архивов текстовых документов, потоков информации, получаемых по Информационной Магистрали и др. в общей постановке проектирования корпоративной БД приводит к отказу от повсеместного и всеобязательного принципа исключения избыточности: проектирование корпоративной БД на уровне логической схемы и на концептуальном уровне не опирается как на глобальный критерий на требование и процедуры исключения избыточности в данных.
Новая ситуация, включая существенный и заранее нерегламентированный поток информации из Информационной Магистрали в корпоративную БД потребует разработки или увеличения возможностей "процедур отождествления" экземпляров информационных структур, т.е. определения того, что эти экземпляры описывают один и тот же предмет реального мира.
Проблема консервации проблемСам характер дисциплины проектирования, предусмотренный каскадной схемой, методами структурного проектирования и иерархическими подходами и структурами, подталкивает сейчас проектировщиков фиксировать достаточно жестко определенные модели ПрО. Должна быть изменена CASE-технология проектирования БД таким образом, чтобы исключить консервацию существующих проблем предприятий в жестких, "цельноинтегрированных" структурах БД. Для этого может потребоваться изменение не только технологии, но и инструментов проектирования.
Предполагаемые подходы:
возможность фиксировать описания атрибутов, сущностей, связей, функций, и т.д. с любой степенью неполноты, возможности производить описания на уровне недетализированных, предметно связанных совокупностей информационных структур ("кластеров сущностей"); проектирование или реконструкция моделей компонентов ИС и БД, их интеграция в общем понятийном пространстве; проектирование упорядоченной последовательности состояний корпоративной БД как последовательности совокупностей эксплуатируемых предметных БД, включающих: наследованные БД, структурно предопределенные БД "покупных" функциональных компонентов, спроектированные специально для данного предприятия БД, причем БД двух последних категорий постепенно заменяют наследованные и, затем и параллельно, заменяют друг друга в процессе развития ИС; открытость репозитория CASE-системы, словаря СУБД и системы 4GL, позволяющая надстраивать метаобъекты и механизмы требуемыми тезаурусными и глубинными семантическими отношениями между элементами, а также производить двухсторонний обмен метаинформацией с другими системами 4GL и CASE, соединять модели разных компонентов в одну с использованием и сохранением всех необходимых семантических отношений. Компонентная открытость и смысловая интероперабельностьКомпонентный подход в разработке ИС требует компонентного проектирования БД. Замена некоторой функциональной компоненты ИС на подобную, но спроектированную другим разработчиком, потребует структурной замены некоторой части корпоративной БД. Такая замена должна поддерживаться как постоянный процесс перепроектирования БД. При замене компоненты БД интерфейсы с ней имеющихся приложений и их пользователей должны получать точно ту же в смысловом отношении информацию, что и ранее.
Реальное компонентное проектирование БД может основываться на формировании и использовании общей для комплексируемых компонент понятийной модели и поддержании соответствий между моделями компонент БД (и связанных с ними приложений) и общей понятийной моделью. В общем виде требования к формализмам таких моделей описывались ранее (см. [Zinder90]). В последнее время развиваются программные реализации полных формальных систем, обычно основанных на объектном подходе, которые могут приближаться к инструментальному решению этой задачи (см. например, [Калиниченко93]).
Разработка понятийных моделей и СККНеобходимость использования общих понятийных моделей заставляет заново рассматривать проблему проектирования БД того, что называется НСИ (нормативно-справочная информация) и СКК (система классификации и кодирования).
До сих пор часто встречается мнение, что СКК - средство сокращенного представления информации в интегрированной БД. На самом деле, отсутствие СКК или использование некорректно построенных СКК приводит к смысловой несовместимости информации, хранимой в различных БД или даже в одной БД. В этих условиях не приведет к достижению целей использование самых продвинутых режимов технологической интероперабельности. Таким образом, целесообразно использовать работы по проектированию БД с НСИ и проектирование СКК как начало и основу для создания понятийного пространства ПрО, для построения понятийной модели деятельности предприятия.
Понятийные модели и последующие проектные работыНа последующих этапах проектирования собственно БД понятийная модель продолжает использоваться с различными целями, например:
разработка совокупности разных предметных информационных моделей с выделением общих информационных сущностей; разработка функциональных моделей разных типов; разработка семантически богатых средств поддержки пользователя, и др. Технологическая открытостьВ ИС новой архитектуры СУБД станут определяющим но не единственным компонентом интегрирующего ПО (в том числе - промежуточного, или middleware). Мониторы транзакций и процессов, средства семантического моделирования и использования понятийных моделей, СУБД- независимые средства разработки и исполнения приложений - другие классы компонентов ПО, обеспечивающие достижение этой цели.
Рекомендуется сохранение независимости от СУБД на основе использования инструментария и стандартов, охватывающих различные СУБД. Отказ от связи с одной СУБД, открытость CASE- репозитория, возможность развития метамоделей, поддерживаемых в репозитории, и применяемых к ним проектных процедур - это лишь минимальные требования к методам и инструментам.
Целесообразно ориентироваться на CASE-системы, ориентированные на возможность параллельного проектирования компонент независимыми разработчиками (в том числе - без использования данной CASE-системы) с последующей интеграцией метаинформации.
Средства разработки приложений должны удовлетворять требованиям мобильности приложений и, одновременно, работы в гетерогенной среде распределенной БД.
Проблемы объемов, временных характеристик и физического проектированияРаспространение БД класса VLDB требует более активного использования методов проектирования эффективных физических схем данных. Невозможно строить такие БД рассчитывая на постоянную реорганизацию путем переписи в новые физические структуры. Это так для операционных БД режима OLTP, тем более это так для терабайтных БД, ориентированных на OLAP. Легкость процедур выполнения реорганизаций указанным методом может становиться "ловушкой" для проектировщика, особенно - на первых этапах ввода БД в действие, когда ее перепись еще возможна из-за неполного объема.
Целесообразно на уровне новых технологий (применение многомерных структур, индексов битовых отображений и др.) вернуться к методам прогнозирования эксплуатационных характеристик БД, которые позволяли бы планировать устойчивость физической схемы как минимум на то время, пока экономические возможности не позволят расширять внешнюю память разных уровней для применения других подходов.
Большой рост объемов БД будет сопровождаться ростом требований к их надежности. К средствам и методам проектирования БД вследствие постоянно осуществляемого процесса перепроектирования БД будут непосредственно примыкать средства управления БД. Так, для обеспечения устойчивых к отказам данных необходимо владение средствами управления и синхронизации географически разнесенных теневых и резервных баз данных.
Проблема границ применимости двух основных методов проектированияВ ходе исследований и практического проектирования должны быть определены границы применимости двух концепций: проектирование БД как объекта, осознано отделенного от прикладных программ, и объектно-ориентированное проектирование, в котором объект инкапсулирует и данные, и методы их обработки.
ЗАКЛЮЧЕНИЕ
Создание корпоративных БД в условиях Нового Системного Проектирования - деятельность, использующая многие методы классического проектирования, но требующая иной организации и многих дополнительных методов, а также новых, которые заменили бы некоторые из тех, что были разработаны 10 и более лет назад.
Дисциплина проектирования БД в новых условиях еще отсутствует. Тем не менее, ее начала видны, ее элементы работают в реальных проектах.
В соответствии с принципом сохранения иммунитета к компьютерным революциям (см. [Зиндер95б]) классические методы проектирования БД должны продолжать использоваться, но только в тех в областях, где они действительно полезны. Методы проектирования, рассматриваемые в конкретных проектах корпоративных ИС и БД, и соответствующие инструменты должны проверяться на свои возможности обеспечивать функции в соответствии с требованиями Нового Системного Проектирования.
Свойство только для чтения во время выполнения программы.
| - Департамент Полиции Брюсселя, чем я могу помочь Вам? - Ох.. Да.. Я только что получил пирогом с кремом по лицу. - О'кэй, сэр. Обращались ли Вы в Департамент Полиции Брюсселя ранее? - Нет. - Хорошо, дайте мне немного информации о себе, для занесения в журнал. Ваше имя? - Билл Гейтс. - Страна? - США. - Родной язык? - Английский. - О'кей, сэр. Ваш идентификационный номер в Департаменте Полиции: БПЗ1415927. Пожалуйста, используйте этот номер при следующем обращении к нам. Итак, Вы говорите, что получили пирогом по лицу? - Да, я только что должен был встретится с премьер министром Бельгии. Один человек отвлек меня в то время, как другой ударил меня пирогом со сливочным кремом. - Мы имеем сообщения, что Вы получили по лицу пирогом с заварным кремом. Вы уверены, что это действительно был пирог со сливочным кремом? - Хорошо, все мое лицо покрыто белой массой и я не вижу никакого заварного крема, так что я действительно не думаю, что это был пирог с заварным кремом. - Встречались ли Вы с премьер министром раньше? - Да. - Тогда вы тоже получали пирогом по лицу? - Нет. - Хмм.. Посещали ли вы других премьер министров в последнее время? - Да. - Были ли какие-либо пироги при этом? - Нет. - О'кей, хорошо... Давайте попробуем следующее: выйдите из здания и зайдите опять. Я буду ждать. - Минутку.. - О'кей, я вернулся. - Получили еще одним пирогом по лицу? - Конечно нет. - Хорошо, сэр. Я не знаю, чем было вызвано появление первого пирога, но все выглядит так, что Ваши дела вновь наладились. Впрочем, мы возьмем эту проблему на заметку. Если это случится снова, пожалуйста, запомните точные детали происшествия и вызовите нас еще раз. Благодарим Вас за обращение в Департамент Полиции Брюсселя. |
Если значение свойства используется в процедуре, то сама процедура может содержать следующий код:
if csDesigning in ComponentState then begin // ... код, устанавливающий значение свойства ... end; |
Это позволяет в режиме выполнения приложения сделать свойство только для чтения.
Как запретить изменение размера моего компонента в design-time.
Поместите в конструктор компонента код, устанавливающий размеры по умолчанию. Переопределите метод SetBounds и проверяйте в нем "componentstate". Если компонент находится режиме "design-time" (csDesigning in ComponentState) просто передавайте значения ширины и высоты (width и heights) компонента по умолчанию (в нашем примере 50) методу класса-предка.
procedure TVu.SetBounds(ALeft: integer; ATop: integer;
AWidth: integer; AHeight: integer);
begin
if csdesigning in componentstate then
begin
AWidth := 50;
AHeight := 50;
inherited; //вызываем унаследованный от предка метод
end;
end;
|
Как установить цвет фона иконок на рабочем столе, либо сделать у них прозрачный фон.
Для этого нужно найти окно "SysListView32" (которое является списком, который содержит иконки рабочего стола). Сперва будем искать главное родительское окно "Progman", которое содержит дочернее окно "SHELLDLL_DefView" , которое в свою очередь имеет дочернее окно "SysListView32". Для этого можно воспользоваться API функцией FindWindow to. Когда Мы получим дескриптор окна "SysListView32", то можно будет воспользоваться макросами ListView_SetTextBkColor и ListView_SetTextColor для установки желаемого цвета.Ниже приведена процедура, которая делает всё вышеперечисленное. Если параметр Trans равен true, то будет установлен прозрачный фон, иначе цвет фона будет равен Background.
unit DeskIcons;
interface
uses Graphics; // Будет использоваться TColor
procedure SetDesktopIconColor(Forground, Background: TColor; Trans: Boolean);
procedure SetDefaultIconColors;
implementation
uses Windows, CommCtrl; // будут использоваться HWND и ListView_XXXXX
procedure SetDesktopIconColor(Forground, Background: TColor; Trans: Boolean);
var
Window: HWND;
begin
// Находим нужное окно в три этапа
Window := FindWindow('Progman', 'Program Manager');
// Используем FindWindowEx для нахождения дочернего окна
Window := FindWindowEx(Window, HWND(nil), 'SHELLDLL_DefView', '');
// SysListView32, это список с иконками на рабочем столе
Window := FindWindowEx(Window, HWND(nil), 'SysListView32', '');
// Используем макрос для очистки цвета фона
if Trans then
ListView_SetTextBkColor(Window, $FFFFFFFF) // фоновый цвет
else
ListView_SetTextBkColor(Window, Background); // фоновый цвет
ListView_SetTextColor(Window, Forground); // передний цвет
// теперь перерисовываем иконки
ListView_RedrawItems(Window, 0, ListView_GetItemCount(Window) - 1);
UpdateWindow(Window); // да¸м команду "немедленно перерисовать"
end;
procedure SetDefaultIconColors;
{ Эта процедура устанавливает цвета, которые заданы в
windows по умолчанию }
var
Kind: Integer;
Color: TColor;
begin
Kind := COLOR_DESKTOP;
Color := GetSysColor(COLOR_DESKTOP);
SetSysColors(1, Kind, Color);
end;
end.
|
Получить цвет пикселя на рабочем столе.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
function DesktopColor(const X, Y: Integer): TColor;
var
c: TCanvas;
begin
c := TCanvas.Create;
try
c.Handle := GetWindowDC(GetDesktopWindow);
Result := GetPixel(c.Handle, X, Y);
finally
c.Free;
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
Pos: TPoint;
begin
GetCursorPos(Pos);
Panel1.Color := DesktopColor(Pos.X, Pos.Y);
end;
Как определить размер рабочего стола без ТаsкВага.
Воспользуйтесь функцией SystemParametersInfo(), переслав ей в качестве параметров - SPI_GETWORKAREA и адрес структуры типа TRect, куда будут передан полученный результат:
procedure TForm1.Button1Click(Sender: TObject); var r: TRect; begin SystemParametersInfo(SPI_GETWORKAREA, 0, @r, 0); Memo1.Lines.Add(IntToStr(r.Top)); Memo1.Lines.Add(IntToStr(r.Left)); Memo1.Lines.Add(IntToStr(r.Bottom)); Memo1.Lines.Add(IntToStr(r.Right)); end; |
Снимок Desktop.
public
{ Public declarations }
procedure GrabScreen;
...
implementation
{$R *.DFM}
procedure TForm1.GrabScreen;
var
DeskTopDC: HDc;
DeskTopCanvas: TCanvas;
DeskTopRect: TRect;
begin
DeskTopDC := GetWindowDC(GetDeskTopWindow);
DeskTopCanvas := TCanvas.Create;
DeskTopCanvas.Handle := DeskTopDC;
DeskTopRect := Rect(0, 0, Screen.Width, Screen.Height);
Form1.Canvas.CopyRect(DeskTopRect, DeskTopCanvas, DeskTopRect);
ReleaseDC(GetDeskTopWindow, DeskTopDC);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
GrabScreen;
end;
|
Когда я добавляю обьект в список TStrings как мне его потом уничтожить.
procedure TForm1.FormCreate(Sender: TObject);
var
Icon: TIcon;
begin
Icon := TIcon.Create;
Icon.LoadFromFile('C:\Program Files\BorlandImages\CONSTRUC.ICO');
ListBox1.Items.AddObject('Item 0', Icon);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
ListBox1.Items.Objects[0].Free;
end;
|
Криптоалгоритм DES.
Оформил: DeeCo
Особенностью этой криптосистемы является использование операций циклического сдвига, зависящих от преобразуемых данных RC5 [12,13]. Это задает непредопределенность операций преобразования, выполняемых над преобразуемыми подблоками данных, что по замыслу разработчиков должно привести к до-стижению высокой криптостойкости. В этой криптосистеме пре-дусмотрена возможность задания пользователем числа раундов и размера входного блока данных. Входной блок разбивается на два подблока одинаковой длины. Обозначим длину подблока в битах через Ъ. Шифрование заключается в поочередном преоб-разовании подблоков с использованием операций поразрядно-го суммирования по модулю два, суммирования по модулю 26 и управляемых операций циклического сдвига. Варианты опера-ции циклического сдвига различаются величиной сдвига от 0 до Ь - 1 бит. Для обозначения операции циклического сдвига подблока (слова) W на х бит будем применять запись W <<< х (сдвиг влево) и W >>> х (сдвиг вправо). Для выбора конкретной модификации операции циклического сдвига используется log2 b младших разрядов управляющего блока. На каждом раунде преобразования используется отдельная пара подключей, поэтому при выборе различного числа раун-дов преобразования используется ключ шифрования различной длины. Для удобства пользователей в шифре RC5 используется алгоритм инициализации, выполняемый однократно при запуске криптосистемы. На этом этапе предвычислений по секретному ключу пользователя в зависимости от заданного числа раундов R формируется ключ шифрования в виде последовательности, содержащей 2(R+1) b-битовых подключей. Подключи используются по фиксированному расписанию, т. е. для всех входных блоков на данном шаге преобразования используется один и тот же подключ. Для того, чтобы указать значения конкретных па-раметров настройки алгоритма применяется запись RC5-b/R/l, где l -длина секретного ключа в байтах. Типичной является модификация RC5-32/12/16, ко-торая соответствует 64-битовому входному блоку данных, 128-битовому секретному ключу и 12 раундам шифрования. Эта модификация является стойкой к известным методам криптоанализа и может применяться для защиты данных в авто-матизированных системах обработки информации и управления. В случае применения 64-разрядных микропроцессоров целе-сообразно применение модификации RC5-64/12/16, которая обеспечит более высокую скорость преобразования. Кроме того, при преобразовании 128-битовых блоков исходного текста повышается стойкость шифрования за счет увеличения числа управляющих битов с 5 до 6. Алгоритм предвычислений формирует ключ шифрования необходимого размера, при этом используются процедуры, которые обеспечивают влияние каждого бита секретного ключа на каждый бит ключа шифрования. По значению клю-ча шифрования трудно восстановить секретный ключ. Мы не будем останавливаться на описании алгоритма предвы-числений, поскольку могут быть применены различные его варианты. Рассмотрим процедуры шифрования и дешифро-вания, которые составляют сущность криптосистемы RC5. Обозначим ключ шифрования как последовательность подключей Q0, Q1, ..., Q2R+1. Процедуры шифрования и дешифрования блока Т = A | В описываются следующими простыми алгоритмами: ВХОД: Исходное значение Ь-битовых подблоков A и В. 1. Установить счетчик i == 1 и число раундов R и выполнить преобразования:
А := (A + Qo) mod
2 b;
В := (B + Q1)mod2b.2. Преобразовать: A := {[(A ф В)
<<< В] +Q2i}mod 2 bB := [(B ф A) < < < A] + Q2i + 1} mod
2 b3.Если i? R, то прирастить i := i + 1 и перейти к шагу 2.4.
СТОП.
ВЫХОД: Преобразованное значение подблоков A и В.
Алгоритм дешифрования.
1. Установить счетчик i = R.
2. Преобразовать: B :=
{[(В - Q2i+i) mod 2b] >>> A} Ф AA := {[(A - Q2i ) mod2b]
>>> В} Ф В.3. Если i ? 1, то уменьшить на 1 значение счетчика
i := i - 1 и перейти к шагу 2.4. Преобразовать: B := (B - Q1)
mod2b;
A := (A - Q0) mod 2 b
Схема шифрующих преобразований одного раунда пред-ставлена на рис. Операция циклического сдвига относится к быстрым элементарным операциям современных процессоров, Кроме того, время выполнения операции циклического сдвига не зависит от величины сдвига. При программной реализации модификация RC5-32/12/16 обеспечивает скорость шифро-вания порядка нескольких Мбайт/с для микропроцессора Pentium. В приведенных алгоритмах не используются операция табличной подстановки, которые являются типичными нели-нейными операциями для многих блочных криптосистем, Нелинейными операциями рассмотренного шифра явля-ются операции циклического сдвига, зависящие от пре-образуемых данных. Как шифрование, так и дешифрова-ние начинается с выполнения операции над подблоками и подключами. Это обусловливает различие в операци-ях циклического сдвига на каждом раунде шифрования в дешифрования. Двухместные операции (Ф), выполняемые над двумя подблоками, усиливают эффект размножения ошибки.
Алгоритм шифрования DES.
Оформил: DeeCo
Алгоритм шифрования данных DES разработан для зашифрования и расшифрования данных разрядностью 64 бит на основе 64-битового ключа. Расшифрование выполняется по тому же ключу, что и зашифрование, но этот процесс является инверсным по отношению к процессу зашифрования данных. При описании алгоритма шифрования используются следующие обозначения. Если L и R - последовательности бит, то через LR будем обозначать конкатенацию последовательностей L и R, т.е. последовательность бит, размерность которой равна сумме размерностей L и R. В этой последовательности биты последовательности R следуют за битами последовательности L. Конкатенация битовых строк является ассоциативной, то есть запись ABCDE, означает, что за битами последовательности A, следуют, биты последовательности B, затем C и т.д. Символом + будем обозначать операцию побитового сложения по модулю 2.
Процесс шифрования.Процесс шифрования данных поясняется рисунком 1. Сначала 64 бита входной последовательности перестанавливаются в соответствии с таблицей 1. Таким образом, бит 58 входной последовательности становится битом 1, бит 50 – 2 и т.д.
Таблица 1. "Начальная перестановка". 58. 50. 42. 34. 26. 18. 10. 2. 60. 52. 44. 36. 28. 20. 12. 4. 62. 54. 46. 38. 30. 22. 14. 6. 64. 56. 48. 40. 32. 24. 16. 8. 57. 49. 41. 33. 25. 17. 9. 1. 59. 51. 43. 35. 27. 19. 11. 3. 61. 53. 45. 37. 29. 21. 13. 5. 63. 55. 47. 39. 31. 23. 15. 7. Полученная последовательность бит разделяется на две последовательности: L(0) (биты 58, 50, 42, ..., 8) и R(0) (биты 57, 49, 41, ..., 7), каждая из которых содержит 32 бита. Затем выполняется итеративный процесс шифрования, который описывается следующими формулами:L(i)=R(i-1), i=1,2,...,16.
R(i)=L(i-1) + F(R(i-1),K(i)), i=1,2,...,16.
Функция F называется функцией шифрования. Ее аргументами являются последовательность R, полученная на предыдущем шаге, и 48-битовый ключ K(i), который является результатом функции преобразования 64-битового ключа шифра. Подробно функция шифрования и алгоритм получения ключей K(i) описаны ниже.
На последнем шаге итерации будут получены последовательности L(16) и R(16), которые конкатенируются в 64-х битовую последовательность R(16)L(16). Видно, что в полученной последовательности 64 бита, перестанавливаются в соответствии с таблицей 2. Как легко видеть данная перестановка является обратной по отношению к начальной (см. таблицу 1).
Таблица 2. "Конечная перестановка". 40. 8. 48. 16. 56. 24. 64. 32. 39. 7. 47. 15. 55. 23. 63. 31. 38. 6. 46. 14. 54. 22. 62. 30. 37. 5. 45. 13. 53. 21. 61. 29. 36. 4. 44. 12. 52. 20. 60. 28. 35. 3. 43. 11. 51. 19. 59. 27. 34. 2. 42. 10. 50. 18. 58. 26. 33. 1. 41. 9. 49. 17. 57. 25. Полученная последовательность из 64 бит и будет являться зашифрованной последовательностью. Рисунок 1.
Процесс расшифрования данных является инверсным по отношению к процессу шифрования. Все действия должны быть выполнены в обратном порядке. Это означает, что расшифровываемые данные сначала переставляются в соответствии с таблицей 1, а затем над последовательностью бит R(16)L(16) выполняется те же действия, что и в процессе зашифрования, но в обратном порядке. Итеративный процесс расшифрования описан следующими формулами:
R(i-1)=L(i), i =16, 15, ..., 1.
L(i-1)=R(i)+F(L(i),K(i)), i=16, 15, ..., 1.
На последнем шаге итерации будут получены последовательности L(0) и R(0), которые конкетанируются в 64 битовую последовательность L(0)R(0). В полученной последовательности 64 бита перестанавливаются в соответствии с таблицей 2. Результат преобразования - исходная последовательность бит (расшифрованное 64-битовое значение).
Функция шифрования.Функция шифрования F(R,K) схематически показана на рисунке 2. Для вычисления значения функции F используется функция E (расширение 32 бит до 48), функции S(1), S(2),...,S(8) преобразование 6-битового числа в 4-битовое) и функция P (перестановка бит в 32-битовой последовательности). Приведем определения этих функций. Аргументами функции шифрования являются R (32 бита) и K (48 бит). Результат функции E(R) есть 48-битовое число, которое складывается по модулю 2 с числом K. Таким образом, получается 48-битовая последовательность, которая рассматривается, как конкатенация 8 строк длиной по 6 бит (т.е. B(1)B(2)B(3)B(4)B(5)B(6)B(7)B(8)). Результат функции S(i)B(i) - 4 битовая последовательность, которую будем обозначать L(i). В результате конкетанации всех 8 полученных последвательностей L(i) имеем 32-битовую последовательность L=L(1)L(2)L(3)L(4)L(5)L(6)L(7)L(8). Наконец, для получения результат функции шифрования надо переставить биты последовательности L. Для этого применяется функция перестановки P(L).
Рисунок 2.
Функция расширения Е, выполняющая расширение 32 бит до 48, определяется таблицей 3. В соответствии с этой таблицей первые три бита Е(R) - это биты 32,1 и 2, а последние - 31,32,1.
Таблица 3. "Функция расширения Е". 32. 1. 2. 3. 4. 5. 4. 5. 6. 7. 8. 9. 8. 9. 10. 11. 12. 13. 12. 13. 14. 15. 16. 17. 16. 17. 18. 19. 20. 21. 20. 21. 22. 23. 24. 25. 24. 25. 26. 27. 28. 29. 28. 29. 30. 31. 32. 1. Функция S(i), которая преобразует 6-битовые числа в 4-битовые, определяется в таблицей 4. Таблица 4. "Функции преобразования S(i)". S(1). 14. 4. 13. 1. 2. 15. 11. 8. 3. 10. 6. 12. 5. 9. 0. 7. 0. 15. 7. 4. 14. 2. 13. 1. 10. 6. 12. 11. 9. 5. 3. 8. 4. 1. 14. 8. 13. 6. 2. 11. 15. 12. 9. 7. 3. 10. 5. 0. 15. 12. 8. 2. 4. 9. 1. 7. 5. 11. 3. 14. 10. 0. 6. 13. S(2). 15. 1. 8. 14. 6. 11. 3. 4. 9. 7. 2. 13. 12. 0. 5. 10. 3. 13. 4. 7. 15. 2. 8. 14. 12. 0. 1. 10. 6. 9. 11. 5. 0. 14. 7. 11. 10. 4. 13. 1. 5. 8. 12. 6. 9. 3. 2. 15. 13. 8. 10. 1. 3. 15. 4. 2. 11. 6. 7. 12. 0. 5. 14. 9. S(3). 10. 0. 9. 14. 6. 3. 15. 5. 1. 13. 12. 7. 11. 4. 2. 8. 13. 7. 0. 9. 3. 4. 6. 10. 2. 8. 5. 14. 12. 11. 15. 1. 13. 6. 4. 9. 8. 15. 3. 0. 11. 1. 2. 12. 5. 10. 14. 7. 1. 10. 13. 0. 6. 9. 8. 7. 4. 15. 14. 3. 11. 5. 2. 12. S(4). 7. 13. 14. 3. 0. 6. 9. 10. 1. 2. 8. 5. 11. 12. 4. 15. 13. 8. 11. 5. 6. 15. 0. 3. 4. 7. 2. 12. 1. 10. 14. 9. 10. 6. 9. 0. 12. 11. 7. 13. 15. 1. 3. 14. 5. 2. 8. 4. 3. 15. 0. 6. 10. 1. 13. 8. 9. 4. 5. 11. 12. 7. 2. 14. S(5). 2. 12. 4. 1. 7. 10. 11. 6. 8. 5. 3. 15. 13. 0. 14. 9. 14. 11. 2. 12. 4. 7. 13. 1. 5. 0. 15. 10. 3. 9. 8. 6. 4. 2. 1. 11. 10. 13. 7. 8. 15. 9. 12. 5. 6. 3. 0. 14. 11. 8. 12. 7. 1. 14. 2. 13. 6. 15. 0. 9. 10. 4. 5. 3. S(6). 12. 1. 10. 15. 9. 2. 6. 8. 0. 13. 3. 4. 14. 7. 5. 11. 10. 15. 4. 2. 7. 12. 9. 5. 6. 1. 13. 14. 0. 11. 3. 8. 9. 14. 15. 5. 2. 8. 12. 3. 7. 0. 4. 10. 1. 13. 11. 6. 4. 3. 2. 12. 9. 5. 15. 10. 11. 14. 1. 7. 6. 0. 8. 13. S(7). 4. 11. 2. 14. 15. 0. 8. 13. 3. 12. 9. 7. 5. 10. 6. 1. 13. 0. 11. 7. 4. 9. 1. 10. 14. 3. 5. 12. 2. 15. 8. 6. 1. 4. 11. 13. 12. 3. 7. 14. 10. 15. 6. 8. 0. 5. 9. 2. 6. 11. 13. 8. 1. 4. 10. 7. 9. 5. 0. 15. 14. 2. 3. 12. S(8). 13. 2. 8. 4. 6. 15. 11. 1. 10. 9. 3. 14. 5. 0. 12. 7. 1. 15. 13. 8. 10. 3. 7. 4. 12. 5. 6. 11. 0. 14. 9. 2. 7. 11. 4. 1. 9. 12. 14. 2. 0. 6. 10. 13. 15. 3. 5. 8. 2. 1. 14. 7. 4. 10. 8. 13. 15. 12. 9. 0. 3. 5. 6. 11. К таблице 4 требуются дополнительные пояснения. Каждая из функций S(i)B(i) преобразет 6-битовый код в 4-битовый выход по следующему алгоритму: первый и последний биты входной последовательности B, определяют номер строки k. второй, третий, четвертый и пятый биты последовательности B задают номер колонки l результат преобразования выбирается из строки k и колонки l.Предположим, что B=011011. Тогда S(1)(B)=0101. Действительно, k=1, l=13. В колонке 13 строки 1 задано значение 5, которое и является значением функции S(1)(011011).
Функция перестановки бит P(L), также используемая для определения функции шифрования, задается значениями, приведенными в таблице 5. В последовательности L 32 перестанавливается так, чтобы бит 16 стал первым битом, бит 7 - вторым и т.д.
Таблица 5. "Функция перестановки P". 16. 7. 20. 21. 29. 12. 28. 17. 1. 15. 23. 26. 5. 18. 31. 10. 2. 8. 24. 14. 32. 27. 3. 9. 19. 13. 30. 6. 22. 11. 4. 25. Процесс получения ключей. Чтобы завершить описание алгоритма шифрования данных, осталось привести алгоритм получение ключей K(i), i=1,2,...,16, размерностью в 48 бит. Ключи K(i) определяются по 64-битовому ключу шифра как это показано на рисунке 3. Рисунок 3.
В начале над ключом шифра выполняется операция B, которая сводится к выбору определенных бит и их перестановке, как это показано в таблицей 6. Причем, первые четыре строки определяют, как выбираются биты последовательности C(0) (первым битом C(0) будет бит 57 бит ключа шифра, затем бит 49 и т.д., а последними битами биты 44 и 36 ключа шифра), а следующие четыре строки - как выбираются биты последовательности D(0) (т.е. последовательность D(0) будем состоять из битов 63,55,...,12, 4 ключа шифра).
Таблица 6. "Функция перестановки и выбора последовательности B". 57. 49. 41. 33. 25. 17. 9. 1. 58. 50. 42. 34. 26. 18. 10. 2. 59. 51. 43. 35. 27. 19. 11. 3. 60. 52. 44. 36. 63. 55. 47. 39. 31. 23. 15. 7. 62. 54. 46. 38. 30. 22. 14. 6. 61. 53. 45. 37. 29. 21. 13. 5. 28. 20. 12. 4. Как видно из таблицы 6, для генерации последовательностей C(0) и D(0) не используются биты 8,16,25,32,40,48,56 и 64 ключа шифра. Эти биты не влияют на шифрование и могут служить для других целей (например, для контроля по четности). Таким образом, в действительности ключ шифра является 56-битовым. После определения C(0) и D(0) рекурсивно определяются C(i) и D(i), i=1,2,...,16. Для этого применяются операции сдвига влево на один или два бита в зависимости от номера шага итерации, как это показано в таблицей 7. Операции сдвига выполняются для последовательностей C(i) и D(i) независимо. Например, последовательность C(3) получается, посредством сдвига влево на две позиции последовательности C(2), а последовательность D(3) - посредством сдвига влево на две позиции последовательности D(2). Следует иметь в виду, что выполняется циклический сдвиг влево. Например, единичный сдвиг влево последовательности C(i) приведет к тому, что первый бит C(i) станет последним и последовательность бит будет следующая: 2,3,..., 28,1. Таблица 7. "Функция сдвига Si". 1. 1. 2. 1. 3. 2. 4. 2. 5. 2. 6. 2. 7. 2. 8. 2. 9. 1. 10. 2. 11. 2. 12. 2. 13. 2. 14. 2. 15. 2. 16. 1. Ключ K(i), определяемый на каждом шаге итерации, есть результат выбора определенных бит из 56-битовой последовательности C(i)D(i) и их перестановки. Другими словами, K(i) = K(C(i)D(i)), где функция K определяется данными, приведенными в таблицей 8. Таблица 8. "Функция перестановки и выбора K".| 14. | 17. | 11. | 24. | 1. | 5. |
| 3. | 28. | 15. | 6. | 21. | 10. |
| 23. | 19. | 12. | 4. | 26. | 8. |
| 16. | 7. | 27. | 20. | 13. | 2. |
| 41. | 52. | 31. | 37. | 47. | 55. |
| 30. | 40. | 51. | 45. | 33. | 48. |
| 44. | 49. | 39. | 56. | 34. | 53. |
| 46. | 42. | 50. | 36. | 29. | 32. |
Как следует из таблицы 8 первый бит K(i) - это бит 14 последовательности C(i)D(i), второй - бит 17, последний - бит 32.
Определение кодовой страницы.
Автор: Alexander Trunov.
{
Work with codepages
(c) 1999 by Alexander Trunov, {2:5069/10}, {jnc@mail.ru}
}
unit Codepage;
interface
const
cpWin = 01;
cpAlt = 02;
cpKoi = 03;
function DetermineCodepage(const st: string): Byte;
function Alt2Win(const st: string): string;
function Win2Alt(const st: string): string;
function Alt2Koi(const st: string): string;
function Koi2Alt(const st: string): string;
function Win2Koi(const st: string): string;
function Koi2Win(const st: string): string;
function X2Y(const st: string; srcCp, dstCp: Byte): string;
implementation
const
AltSet = ['А'..'Я', 'а'..'п', 'р'..'я'];
KoiSet = ['Б'..'Р', 'Т'..'С'];
WinSet = ['а'..'п', 'р'..#255];
Win2AltTable: array[0..255] of Byte = (
$00, $01, $02, $03, $04, $05, $06, $07, $08, $20, $0A, $0B, $0C, $0D, $0E, $0F,
$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $1A, $1B, $1C, $1D, $1E, $1F,
$20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $2A, $2B, $2C, $2D, $2E, $2F,
$30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $3A, $3B, $3C, $3D, $3E, $3F,
$40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4A, $4B, $4C, $4D, $4E, $4F,
$50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $5A, $5B, $5C, $5D, $5E, $5F,
$60, $61, $62, $63, $64, $65, $66, $67, $68, $69, $6A, $6B, $6C, $6D, $6E, $6F,
$70, $71, $72, $73, $74, $75, $76, $77, $78, $79, $7A, $7B, $7C, $7D, $7E, $7F,
$80, $81, $82, $83, $84, $85, $86, $87, $88, $89, $8A, $8B, $8C, $8D, $8E, $8F,
$90, $91, $92, $93, $94, $95, $96, $97, $98, $99, $9A, $9B, $9C, $9D, $9E, $9F,
$A0, $A1, $A2, $A3, $A4, $A5, $A6, $A7, $A8, $A9, $AA, $22, $AC, $AD, $AE, $AF,
$B0, $B1, $B2, $B3, $B4, $B5, $B6, $B7, $B8, $FC, $BA, $22, $BC, $BD, $BE, $BF,
$80, $81, $82, $83, $84, $85, $86, $87, $88, $89, $8A, $8B, $8C, $8D, $8E, $8F,
$90, $91, $92, $93, $94, $95, $96, $97, $98, $99, $9A, $9B, $9C, $9D, $9E, $9F,
$A0, $A1, $A2, $A3, $A4, $A5, $A6, $A7, $A8, $A9, $AA, $AB, $AC, $AD, $AE, $AF,
$E0, $E1, $E2, $E3, $E4, $E5, $E6, $E7, $E8, $E9, $EA, $EB, $EC, $ED, $EE, $EF);
Alt2WinTable: array[0..255] of Byte = (
$00, $01, $02, $03, $04, $05, $06, $07, $08, $09, $0A, $0B, $0C, $0D, $0E, $0F,
$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $1A, $1B, $1C, $1D, $1E, $1F,
$20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $2A, $2B, $2C, $2D, $2E, $2F,
$30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $3A, $3B, $3C, $3D, $3E, $3F,
$40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4A, $4B, $4C, $4D, $4E, $4F,
$50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $5A, $5B, $5C, $5D, $5E, $5F,
$60, $61, $62, $63, $64, $65, $66, $67, $68, $69, $6A, $6B, $6C, $6D, $6E, $6F,
$70, $71, $72, $73, $74, $75, $76, $77, $78, $79, $7A, $7B, $7C, $7D, $7E, $7F,
$C0, $C1, $C2, $C3, $C4, $C5, $C6, $C7, $C8, $C9, $CA, $CB, $CC, $CD, $CE, $CF,
$D0, $D1, $D2, $D3, $D4, $D5, $D6, $D7, $D8, $D9, $DA, $DB, $DC, $DD, $DE, $DF,
$E0, $E1, $E2, $E3, $E4, $E5, $E6, $E7, $E8, $E9, $EA, $EB, $EC, $ED, $EE, $EF,
$20, $20, $20, $A6, $A6, $A6, $A6, $2B, $2B, $A6, $A6, $2B, $2B, $2B, $2B, $2B,
$2B, $2D, $2D, $2B, $2D, $2B, $A6, $A6, $2B, $2B, $2D, $2D, $A6, $2D, $2B, $2D,
$2D, $2D, $2D, $2B, $2B, $2B, $2B, $2B, $2B, $2B, $2B, $5F, $5F, $5F, $5F, $5F,
$F0, $F1, $F2, $F3, $F4, $F5, $F6, $F7, $F8, $F9, $FA, $FB, $FC, $FD, $FE, $FF,
$A8, $B8, $AA, $BA, $AF, $BF, $A1, $A2, $B0, $B7, $B7, $5F, $B9, $A4, $5F, $5F);
Koi2AltTable: array[0..255] of Byte = (
$00, $01, $02, $03, $04, $05, $06, $07, $08, $09, $0A, $0B, $0C, $0D, $0E, $0F,
$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $1A, $1B, $1C, $1D, $1E, $1F,
$20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $2A, $2B, $2C, $2D, $2E, $2F,
$30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $3A, $3B, $3C, $3D, $3E, $3F,
$40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4A, $4B, $4C, $4D, $4E, $4F,
$50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $5A, $5B, $5C, $5D, $5E, $5F,
$60, $61, $62, $63, $64, $65, $66, $67, $68, $69, $6A, $6B, $6C, $6D, $6E, $6F,
$70, $71, $72, $73, $74, $75, $76, $77, $78, $79, $7A, $7B, $7C, $7D, $7E, $7F,
$80, $81, $82, $83, $84, $85, $86, $87, $88, $89, $8A, $8B, $8C, $8D, $8E, $8F,
$90, $91, $92, $93, $94, $95, $96, $97, $98, $99, $9A, $9B, $9C, $9D, $9E, $9F,
$A0, $A1, $A2, $A5, $A4, $A5, $A6, $A7, $A8, $A9, $AA, $AB, $AC, $AD, $AE, $AF,
$B0, $B1, $B2, $B3, $B4, $B5, $B6, $B7, $B8, $B9, $BA, $BB, $BC, $BD, $BE, $BF,
$EE, $A0, $A1, $E6, $A4, $A5, $E4, $A3, $E5, $A8, $A9, $AA, $AB, $AC, $AD, $AE,
$AF, $EF, $E0, $E1, $E2, $E3, $A6, $A2, $EC, $EB, $A7, $E8, $ED, $E9, $E7, $EA,
$9E, $80, $81, $96, $84, $85, $94, $83, $95, $88, $89, $8A, $8B, $8C, $8D, $8E,
$8F, $9F, $90, $91, $92, $93, $86, $82, $9C, $9B, $87, $98, $9D, $99, $97, $FF);
Alt2KoiTable: array[0..255] of Byte = (
$00, $01, $02, $03, $04, $05, $06, $07, $08, $09, $0A, $0B, $0C, $0D, $0E, $0F,
$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $1A, $1B, $1C, $1D, $1E, $1F,
$20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $2A, $2B, $2C, $2D, $2E, $2F,
$30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $3A, $3B, $3C, $3D, $3E, $3F,
$40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4A, $4B, $4C, $4D, $4E, $4F,
$50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $5A, $5B, $5C, $5D, $5E, $5F,
$60, $61, $62, $63, $64, $65, $66, $67, $68, $69, $6A, $6B, $6C, $6D, $6E, $6F,
$70, $71, $72, $73, $74, $75, $76, $77, $78, $79, $7A, $7B, $7C, $7D, $7E, $7F,
$E1, $E2, $F7, $E7, $E4, $E5, $F6, $FA, $E9, $EA, $EB, $EC, $ED, $EE, $EF, $F0,
$F2, $F3, $F4, $F5, $E6, $E8, $E3, $FE, $FB, $FD, $9A, $F9, $F8, $FC, $E0, $F1,
$C1, $C2, $D7, $C7, $C4, $C5, $D6, $DA, $C9, $CA, $CB, $CC, $CD, $CE, $CF, $D0,
$B0, $B1, $B2, $B3, $B4, $B5, $B6, $B7, $B8, $B9, $BA, $BB, $BC, $BD, $BE, $BF,
$C0, $C1, $C2, $C3, $C4, $C5, $C6, $C7, $C8, $C9, $CA, $CB, $CC, $CD, $CE, $CF,
$D0, $D1, $D2, $D3, $D4, $D5, $D6, $D7, $D8, $D9, $DA, $DB, $DC, $DD, $DE, $DF,
$D2, $D3, $D4, $D5, $C6, $C8, $C3, $DE, $DB, $DD, $DF, $D9, $D8, $DC, $C0, $D1,
$85, $A3, $F2, $F3, $F4, $F5, $F6, $F7, $F8, $F9, $FA, $FB, $FC, $FD, $FE, $FF);
function X2Y(const st: string; srcCp, dstCp: Byte): string;
begin
case srcCp of
cpWin:
begin
case dstCp of
cpWin:
begin
Result := st;
end;
cpAlt:
begin
Result := Win2Alt(st);
end;
cpKoi:
begin
Result := Win2Koi(st);
end;
end;
end;
cpAlt:
begin
case dstCp of
cpWin:
begin
Result := Alt2Win(st);
end;
cpAlt:
begin
Result := st;
end;
cpKoi:
begin
Result := Alt2Koi(st);
end;
end;
end;
cpKoi:
begin
case dstCp of
cpWin:
begin
Result := Koi2Win(st);
end;
cpAlt:
begin
Result := Koi2Alt(st);
end;
cpKoi:
begin
Result := st;
end;
end;
end;
end;
end;
function Win2Koi(const st: string): string;
begin
Result := Alt2Koi(Win2Alt(st));
end;
function Koi2Win(const st: string): string;
begin
Result := Alt2Win(Koi2Alt(st));
end;
function Alt2Win(const st: string): string;
var
i: Integer;
begin
Alt2Win[0] := Char(Length(st));
for i := 1 to Length(st) do
begin
Alt2Win[i] := Char(Alt2WinTable[Byte(st[i])]);
end;
end;
function Win2Alt(const st: string): string;
var
i: Integer;
begin
Win2Alt[0] := Char(Length(st));
for i := 1 to Length(st) do
begin
Win2Alt[i] := Char(Win2AltTable[Byte(st[i])]);
end;
end;
function Alt2Koi(const st: string): string;
var
i: Integer;
begin
Alt2Koi[0] := Char(Length(st));
for i := 1 to Length(st) do
begin
Alt2Koi[i] := Char(Alt2KoiTable[Byte(st[i])]);
end;
end;
function Koi2Alt(const st: string): string;
var
i: Integer;
begin
Koi2Alt[0] := Char(Length(st));
for i := 1 to Length(st) do
begin
Koi2Alt[i] := Char(Koi2AltTable[Byte(st[i])]);
end;
end;
function DetermineCodepage(const st: string): Byte;
var
WinCount,
AltCount,
KoiCount,
i, rslt: Integer;
begin
DetermineCodepage := cpAlt;
WinCount := 0;
AltCount := 0;
KoiCount := 0;
for i := 1 to Length(st) do
begin
if st[i] in AltSet then Inc(AltCount);
if st[i] in WinSet then Inc(WinCount);
if st[i] in KoiSet then Inc(KoiCount);
end;
DetermineCodepage := cpAlt;
if KoiCount > AltCount then
begin
DetermineCodepage := cpKoi;
if WinCount > KoiCount then DetermineCodepage := cpWin;
end
else
begin
if WinCount > AltCount then DetermineCodepage := cpWin;
end;
end;
end.
|
Как определить видеокарту.

| Стоит программист перед светофором: - Никак не могу понять, что там за видеоадаптер: у Геркулеса - 2 цвета, у CGA - 4, у EGA - 16, у VGA -256, у XGA - 65535, а 3-х - ну ни у кого нет! |
procedure TForm1.button1click(Sender: TObject);
var
lpDisplayDevice: TDisplayDevice;
dwFlags: DWORD;
cc: DWORD;
begin
form2.memo1.Clear;
lpDisplayDevice.cb := sizeof(lpDisplayDevice);
dwFlags := 0;
cc := 0;
while EnumDisplayDevices(nil, cc, lpDisplayDevice, dwFlags) do
begin
Inc(cc);
form2.memo1.lines.add(lpDisplayDevice.DeviceString);
{Так же мы увидим дополнительную информацию в lpDisplayDevice}
form2.show;
end;
end;
|
Разработка серверных Web-приложений на Delphi.
| Приходит девушка к программисту в гости, а тот: - Чай, кофе, Интернет? |
Практически ежегодно появляется очередная переработанная или серьезно дополненная версия Delphi — в прошлом году была выпущена уже пятая. Количество книг по программированию в среде Delphi, вышедших у нас за последние три-четыре года, свидетельствует о том, что этот продукт наиболее популярен среди аналогичных средств разработки ПО. Его можно использовать для создания различных программ, в том числе и Web-приложений, процесс разработки которых благодаря применению Web-компонентов и Web-классов значительно упростился.
Кстати, для тех программистов, которые работают на Си и не хотят переходить на Паскаль, но желали бы пользоваться всеми возможностями и преимуществами Delphi, фирма Inprise выпустила инструментальный аналог этого пакета — C++ Builder. Он обладает всеми достоинствами Delphi и обновляется одновременно с ним. Однако пока пакет не вызывает столь же большого интереса, поскольку выбор соответствующего инструментария при работе на Си шире, чем при использовании Паскаля.
Delphi пятой версии существенно отличается от предыдущей. Так, в палитре VCL этого инструментального пакета появилась страница InternetExpress, на которой представлены TWebConnection и TmidasPageProducer. Они подходят для разработки динамических Internet-клиентов с помощью сценариев, написанных на JavaScript, HTML 4 и XML.
Delphi 5.0 поставляется в трех вариантах: Standard, Professional и Enterprise, различающихся назначением и составом дополнительных инструментальных средств. В Standard наличествует лишь базовый набор средств разработки, Professional можно дополнительно оснащать Internet-компонентами из специального пакета WebBroker, а Enterprise включает весь доступный Internet-инструментарий и поддерживает новую технологию Microsoft — ASP (Active Server Pages). Для этого в Enterprise также включен специализированный эксперт, с помощью которого можно создавать активные серверные страницы. Такие решения, поддерживаемые Web-сервером MS IIS (Microsoft Internet Information Server), позволяют создавать динамические Web-узлы. Кроме того, для разработки Web-приложений используются элементы, находящиеся на Internet-странице палитры компонентов. Эта страница в Delphi 5.0 существенно отличается от соответствующей в версии 4.0 и внешне (другие значки), и по наполнению.
На ней остались лишь те компоненты, которые нужны непосредственно для проектирования Web-приложений. Наряду с пятью невизуальными там имеются ClientSocket и ServerSocket (два первых), а также WebBrowser (последний).
Информацию, касающуюся комплектации и обновления Delphi, можно получить на узлах Inprise.ru (соm), interface.ru, demo.ru и др.
Web-приложения
В качестве клиентских Web-приложений могут быть использованы современные браузеры, среди которых лидируют MS Internet Explorer и Netscape Navigator. Альтернативой является разработка собственной программы с помощью Delphi. Этот путь может показаться более трудоемким и затратным, но в результате зачастую получается такой продукт, который максимально соответствует решаемой задаче.
Если в качестве клиентского приложения все же используется универсальный Web-браузер, то при создании интересных динамических Web-страниц на профессиональном уровне уже не обойтись без программирования хотя бы на элементарном уровне. Тогда придется разрабатывать продукты на уровне CGI- или API-приложений, и инструментарий Delphi 5.0 окажется как раз к месту.
Технология WebBroker
Для проектирования серверных Web-приложений в Delphi разработана специальная технология — WebBroker. С ее помощью можно создавать сложные программы, в том числе и работающие с базами данных как локальными, так и хранящимися на наиболее популярных серверах — InterBase, Oracle, Informix, Sybase, MS SQL. В последнем случае связь серверного приложения с источником данных обеспечивается с помощью одного из двух механизмов — BDE (Borland Data Engine) или ODBC.
Технология WebBroker реализована на основе Web-компонента — TWebModule. Для обеспечения комфортности работы и ускорения процесса проектирования предусмотрены два мастера — Web Server Application и Database Web Application Wizard. Использование TWebModule позволяет создавать программы, которые будут работать под управлением серверов, поддерживающих интерфейсы расширения — ISAPI (Internet Server API, разработанный корпорацией Microsoft), NSAPI (Netscape API, предложенный компанией Netscape), а также CGI и WinCGI.
Особенности каждого из указанных стандартов учитываются в технологии WebBroker. При этом предусматривается выполнение программы непосредственно на сервере. Но Delphi также позволяет создавать приложения типа ActiveX, запускаемых на компьютере клиента. Следует отметить, что каждое Web-приложение может иметь лишь один компонент TWebModule.
ISAPI- и NSAPI-приложения — это библиотеки DLL, которые Web-сервер загружает по запросу клиента, поступающему от браузера через Internet по протоколу TCP/IP. Клиентская информация передается из запроса в dll-файл в структурированном виде и обрабатывается компонентом TISAPIApplication. Каждый поток управляется отдельным запросом. Как только dll-файл загрузится, сервер может отвечать на клиентские запросы внутри основного процесса.
Поскольку обмен информацией между клиентом и Web-приложением происходит в оперативной памяти сервера, то программы на базе ISAPI/NSAPI работают гораздо быстрее, чем приложения на основе CGI/WinCGI. CGI-приложения являются консольными и не отличаются эффективностью, так как каждый раз при поступлении соответствующего запроса от клиента сначала происходит их запуск, а по окончании работы — полное завершение. Поэтому количество клиентов, подключающихся к Web-серверу с CGI-приложениями, ограничено объемом его оперативной памяти и быстродействием.
Однако подобные приложения обладают универсальностью, обусловленной совместимостью с различными платформами, поскольку стандарт CGI был разработан и широко использовался на Unix, а затем был реализован и для других ОС, в том числе для Windows. Данные от клиента, поступающие в командной строке запроса, который организован в этом стандарте, передаются на сервер после обработки компонентом TCGIApplication.
WinCGI — модификация стандарта CGI, рассчитанная на работу под управлением ОС Windows. Программы, разработанные на базе этих двух стандартов, будут работать и на Web-серверах — MS IIS 4.0 или Netscape, поддерживающих стандарты ISAPI и NSAPI соответственно. Несомненное достоинство технологии WebBroker — возможность представления конечного программного продукта в любом из указанных стандартов простой перекомпиляцией исходных текстов, что существенно облегчает работу.
Для разработки серверных Web-приложений в палитре компонентов Delphi 5 программисту предлагается Internet-страница, на которой расположено пять невизуальных Web-компонентов: TWebDispatcher, TPageProducer, TDataSetPageProducer, TDataSetTableProducer и TQueryTableProducer. Шестой невизуальный компонент — TwebModule — автоматически включается в проект при создании Web-приложения с помощью мастера Web Server Application. Он — основа любого серверного Web-приложения, а также служит репозитарием (своеобразным «контейнером») для остальных невизуальных компонентов, чем напоминает Data-модуль, применяемый для работы с БД при обычном проектировании клиентских приложений.
Чтобы выбрать Web Server Application, следует обратиться к меню File•New•New. После подключения этого мастера открывается подменю, где нужно указать стандарт (ISAPI, NSAPI, CGI или WinCGI), с которым будет работать создаваемое Web-приложение.
Это позволит разработать новый проект на основе автоматически определенного Web-модуля. В его единственном окне и будут размещаться Internet-компоненты. Они, как и сам модуль, имеют статус невизуальных, т. е. доступных лишь в режиме проектирования (design-time). Все видимые элементы и компоненты серверного Web-приложения станут отображаться в окне браузера клиента, обращающегося к серверу, а во время разработки, отладки и тестирования Web-программы сам Web-сервер и браузер могут находиться на одном компьютере, выполняющем одновременно функции как клиента, так и сервера.
Когда разрабатывается ISAPI/ NSAPI-приложение, в заголовок файла проекта добавляется директива library, а в список под директивой uses заносятся необходимые записи. Затем создается сам файл проекта (листинг 1).
Листинг 1.
Файл проекта ISAPI/NSAPI-приложения.
library IServer;
uses
WebBroker, HTTPApp, ISAPIApp,
main in ‘main.pas’ {CustomerInfoModule: TDataModule};
{$R *.RES}
exports
GetExtensionVersion,
HttpExtensionProc,
TerminateExtension;
begin
Application.Initialize;
Application.CreateForm(TCustomerInfoModule, CustomerInfoModule);
Application.Run;
end.
|
Кстати, в Delphi 5.0 при формировании Web-проекта появился новый модуль WebBroker. Поэтому при переходе на нее в готовом (или разрабатываемом) проекте серверного Web-приложения нужно перед его перекомпилированием приписать в ‘library’ название этого нового модуля в разделе ‘uses’. И наоборот, при возврате к версии 4.0 следует (с помощью комментария) убрать это имя.
Классы TWebRequest и TWebResponse.
TWebModule предоставляет серверному Web-приложению возможность сформировать ответы на клиентские HTTP-запросы. Обмен между серверным и клиентским приложениями происходит в объектной форме. Запросы представляются в виде объектов в соответствии с выбранными условиями в списке свойств action items.
В модуле HTTPAPP.pas как абстрактные базовые классы объявлены TWebRequest и TWebResponse. Они инкапсулируют протокол HTTP, по которому происходит обмен информацией между Web-сервером и клиентскими браузерами. Класс TWebRequest предоставляет доступ ко всей информации, поступающей от клиентов на Web-сервер через свои свойства и методы. Класс же TWebResponse, наоборот, с помощью своих свойств и методов позволяет переслать клиенту данные в форме ответа на отправленный ранее запрос по протоколу HTTP. Причем данные от Web-сервера можно переслать любым из возможных для протокола HTTP способов.
В действительности взаимодействие между Web-сервером и клиентами обеспечивают классы TISAPIResponse и TISAPIRequest, объявляемые в модуле ISAPIAPP.pas и учитывающие специфику интерфейсов ISAPI и NSAPI. Они являются прямыми потомками абстрактных классов TWebRequest и TWebResponse. Полиморфизм, свойственный Delphi, позволяет организовать их передачу в виде параметров TWebRequest и TWebResponse через обработчик события OnAction в компонент TwebModule (листинг 2).
Листинг 2.
Обработчик события OnAction.
procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin //обработчик события end; |
Свойства класса TISAPIRequest, содержащего информацию запроса от клиентского браузера, позволяют получить разнообразную и достаточно полную информацию о самом клиенте, хотя ряд параметров в передаваемом запросе может быть не определен. Этот класс имеет около 30 ненаследуемых свойств. Так, свойства RemoteHost и RemoteAddr включают Internet-адрес клиентского компьютера. Данные о браузере, установленном на клиентском месте, заключены в свойстве UserAgent. Из свойства Accept можно извлечь список типов графических файлов, с которыми может работать браузер клиента. Свойство Refere содержит URL Web-страницы, где можно получить ссылку на этот Web-сервер, а данные Cookie содержатся только в свойстве Cookie в виде строк. С помощью свойства CookieFields через массивы полей можно сразу же получить доступ к значениям нескольких клиентских рабочих мест.
Подробные данные клиентского URL-запроса можно извлечь из свойства Query. Например, если в URL-запросе ‘http://www.TSite.com/art/gallery.dll/ mammals?animal=dog&color=black’ часть ‘/gallery.dll’ представляет собой имя Web-приложения, то свойство Query будет включать animal=dog&color=black. В этом свойстве информация неотделима от URL. Причем Query может включать множественные поля, разделенные знаками логического «И» (&). При передаче параметров через URL они обычно следуют за знаком вопроса (?). Пробелы заменяются плюсом (+), а последний наряду с некоторыми другими передается в кодированном виде: %хх, где хх — шестнадцатеричное представление символа.
Чтобы ознакомиться с функциями, свойствами и методами класса TISAPIRequest, целесообразно провести тестирование. Для этого нужно сформировать HTML-страницу в обработчике события OnAction и отобразить на ней значения всех многообразных свойств класса или хотя бы некоторых наиболее важных (листинг 3).
Листинг 3.
Вывод информации о сервере и клиенте.
procedure TWebModule1.WebModule1WebActionItem2Action(Sender:
TObject; Request: TWebRequest; Response: TWebResponse;
var Handled: Boolean);
begin
Pg:= TstringList.Create;
Pg.Add('<HTML>');
Pg.Add('<HEAD>');
Pg.Add('<charset=1251>');
Pg.Add('<ТIТLЕ>Информация о сервере и клиенте'</ТIТLЕ>');
Pg.Add('</HEAD>');
Pg.Add('<BODY>');
Pg.Add('<HR>');
Pg.Add('<В>Динамическая страница, созданная в Delphi<B><P>');
Pg.Add('<В>как Web-расширение <B><P>');
Pg.Add('Method = ' + Request.Method + '<BR>');
with Pg do
begin
Add('ProtocolVersion = ' + Request.ProtocolVersion + '<BR>');
Add('URL = ' + Request.URL + '<BR>');
Add('Query = ' + Request.Query + '<BR>');
Add('PathInfo = ' + Request.PathInfo + '<BR>');
Add('PathTranslated = ' + Request.PathTranslated + '<BR>');
Add('Authorization = ' + Request.Authorization + '<BR>');
Add('CacheControl = ' + Request.CacheControl + '<BR>');
Add('Cookie = ' + Request.Cookie + '<BR>');
Add('Accept = ' + Request.Accept + '<BR>');
Add('From = ' + Request.From + '<BR>');
Add('Host = ' + Request.Host + '<BR>');
Add('Referer = ' + Request.Referer + '<BR>');
Add('UserAgent = ' + Request.UserAgent + '<BR>');
Add('ContentEncoding = ' + Request.ContentEncoding + '<BR>');
Add('ContentType = ' + Request.ContentType + '<BR>');
Add('ContentLength = ' + IntToStr(Request.ContentLength) + '<BR>');
Add('ContentVersion = ' + Request.ContentVersion + '<BR>');
Add('Content = ' + Request.Content + '<BR>');
Add('Connection = ' + Request.Connection + '<BR>');
Add('DerivedFrom = ' + Request.DerivedFrom + '<BR>');
Add('Title = ' + Request.Title + '<BR>');
Add('RemoteAddr = ' + Request.RemoteAddr + '<BR>');
Add('RemoteHost = ' + Request.RemoteHost + '<BR>');
Add('ScriptName = ' + Request.ScriptName + '<BR>');
Add('ServerPort = ' + IntToStr(Request.ServerPort) + '<BR>');
end;
Pg.Add('<HR>');
Pg.Add('</BODY>');
Pg.Add('</HTML>');
Response.Content := Pg.Text;
Pg.Free;
end;
|
Эта страница отсылается через Response.Content на клиентский браузер для тестирования системы.
Конечно, не нужно отправлять подобную информацию клиенту, пользующемуся услугами Web-сервера, ее целесообразно использовать для подготовки эффективного и даже «интеллектуального» ответа клиенту. В частности, информация о версии и функциональных возможностях браузера позволит выдать клиенту Web-страницу в требующемся виде, например без графических файлов, в том формате, который его браузер не воспринимает. А анализ запросов (например, по свойству Query) позволяет выявить «полезного» и даже «нужного» клиента. Если запомнить его URL-адрес, то при следующем обращении на Web-сервер можно персонально его поприветствовать, сразу открыть ему нужную страницу либо раздел, предоставить доступ к дополнительной информации. Этот же способ помогает регистрировать данные о посетителях на Web-сервере и, если необходимо, избавляться от нежелательных «визитов» и т. п.
Данные пересылаются клиенту с Web-сервера в форме ответа через свойство Content класса TISAPIResponse. Этот класс имеет 23 свойства, из них 19 — ненаследуемые. В Content должен содержаться HTML-код, отображаемый клиентским браузером в виде Web-страницы. В другом важном свойстве — Version — указывается версия HTTP-протокола. Значение Version берется из ProtocolVersion свойства HTTPRequest. Для получения дополнительной информации по спецификации HTTP-протокола необходимо ознакомиться с соответствующей документацией, имеющейся, например, по адресу http://www.w3.org.
Большое влияние на работу с классом TISAPIResponse оказывают методы. Например, вызвав после установки свойств объекта HTTP-ответа метод SendResponse, можно послать клиенту сообщение еще до окончания действия обработчика OnAction. Метод SendRedirect поможет перенаправить клиента на другой Web-сервер с соответствующим URL. Метод SendStream обеспечивает передачу данных любого типа. Однако перед его вызовом нужно затребовать SendResponse, который формирует заголовок сообщения HTTP-ответа, базирующийся на свойствах объекта TISAPIResponse. Вместо метода SendResponse перед вызовом SendStream нужно установить свойство ContentStream в значении AStream.
Наибольший интерес, пожалуй, представляет использование технологии WebBroker при построении динамических Web-страниц для работы с базами данных. Однако здесь не обойтись без языка HTML, ранее не применявшегося в Delphi. Специальные компоненты TPageProducer, TDataSetPageProducer, TDataSetTableProducer и TQueryTableProducer помогают программисту эффективно решать подобные задачи, которые далеко не всегда тривиальны, что существенно экономит его время и силы.
TPageProducer
Компонент TPageProducer позволяет создавать динамические Web-страницы и манипулировать ими с помощью шаблонов, т. е. клиенту предоставляется нужная HTML-страница в соответствии с определенными условиями, которые анализируются в обработчике события OnHTMLTag. Само это событие будет инициироваться в момент прочтения специального дескриптора, начинающегося со знака #, который включает имя и параметры с соответствующими значениями, например <#TagName Param1=Value1 Param2=Value2 ...>. Имя должно быть допустимым идентификатором языка Паскаль, а параметры с пробелами заключаются в двойные кавычки. В Delphi имеется семь предопределенных дескрипторов.
В TPageProducer предусмотрены свойства — HTMLDoc и HTMLFile. С помощью первого можно определить шаблон, который в дальнейшем будет использоваться для формирования страницы, возвращаемой клиенту при обращении к методу Content (в ответ на запрос). Установив TPageProducer в окне TWebModule и открыв редактор списка строк HTMLDoc, можно построчно набрать там шаблон HTML-страницы. Такой шаблон не будет отличаться от обычной страницы, написанной на стандартном HTML-языке. Метод Content преобразует код, находящийся в свойстве HTMLDoc (где могут присутствовать и ссылки в виде дескрипторов), в окончательную строку в формате HTML. При входе в каждый дескриптор вызывается событие OnHTMLTag, обработчиком которого и выполняется это преобразование (листинг 4).
Листинг 4.
Замена дескрипторов в обработчике события OnHTMLTag.
procedure TMyModule.ImageHTMLTag(Sender: TObject; Tag: TTag;
const TagString: string; TagParams: TStrings; var ReplaceText: string);
var
ImageID: integer;
begin
case Tag of
tgImage:
begin
ImageID := StrToInt(TagParams.Values[‘ImageID’])
if ImageTable.FindKey([ImageID]) then
ReplaceText := '<IMG SRC=' +
ImageTable.FieldByName('Filename').AsString +
' ALT=' + ImageTable.FieldByName(
'Description').AsString + ' >'
else
ReplaceText:= '(Изображение отсутствует)'
end;
tgTable:
begin
ReplaceText := DataSetTableProducer1.Content;
end;
end;
end;
|
Предопределенный дескриптор tgImage предназначен для описания изображения, а tgTable — для установки HTML-таблицы. Кроме того, в Delphi 5.0 есть предопределенные теги tgCustom, tgLink, tgImageMap, tgObject и tgEmbed. С помощью tgLink описывается гипертекстовая ссылка, tgObject используется для встраивания ActiveX в HTML-страницу, tgImageMap заменяется картой изображения (контекстно-сенсорными зонами), tgEmbed содержит ссылку на DLL-расширение, совместимое с Netscape, tgCustom присваивается свойству Tag тогда, когда в HTML-тексте встречается неопределенный дескриптор, заменяемый любым значением, которое задает пользователь. Например, если в свойстве HTMLDoc HTML-текста имеется также непредопределенный тег <#DATE>, то Tag будет присвоено значение tgCustom. В обработчике OnHTMLTag этот дескриптор может быть заменен командой, выводящей текущую дату на HTML-страницу.
Установка HTMLDoc не допускает использования альтернативного HTMLFile, которое предусматривает применение внешнего файла для хранения аналогичного шаблона на языке HTML. А такой способ позволяет еще более расширить функциональные возможности Web-приложения путем замены и манипулирования HTML-страницами в нескольких шаблонах без изменения текста основной программы.
TDataSetTableProducer и TQueryTableProducer
Компоненты, находящиеся на странице DataControl и предназначенные для управления данными и для их вывода, в том числе и в табличной форме, не могут быть использованы в Web-приложении. Вместо них можно применить TDataSetTableProducer и TQueryTableProducer, информация для которых берется из TTable и TQuery соответственно, включенные в компонент TWebModule. Причем источники данных и компоненты управления непосредственно связаны между собой, потому и не требуется промежуточного компонента типа TDataSource. Компонент TDataSetTableProducer, аналогичный TDBGrid, позволяет переслать на клиентский браузер отчет в табличной форме в формате HTML. Его можно настроить на отображение любых столбцов выбранного набора данных.
Свойства Header и Footer позволяют вставить текст на HTML и до таблицы, и после нее. С помощью свойств Columns, RowAttributes и TableAttributes можно форматировать строки, столбцы и отдельные ячейки таблицы. В свойстве Dispatcher должен быть указан компонент-диспетчер, предоставляющий доступ к объектам TWebRequest и TWebResponse для получения запросов и передачи ответов.
Аналогично функционирует и TQueryTableProducer, за исключением режима доступа к данным. Этот компонент помогает сформировать HTML-таблицу на основе параметров, указанных в HTTP-запросе. Если последний основан на методе GET, то параметры определяются свойством QueryFields объекта запроса TWebRequest, а если на методе POST — свойством ContentFields того же объекта. В приведенной демонстрационной программе Iserver.dpr представлен пример использования TPageProducer,
Отладка серверных Web-приложений
Если разработано достаточно сложное Web-приложение в стандарте ISAPI(NSAPI), его необходимо отладить. Этот процесс существенно отличается от общепринятой отладки обычных Windows-приложений в среде Delphi, так как созданное Web-приложение запускается лишь в ответ на запросы, получаемые от браузера клиента через Web-сервер. Запуск даже самого простого Web-приложения непосредственно из среды Delphi 5.0 невозможен, поскольку теряется его связь с Web-сервером. Тогда и приложение не может получать запросы от клиента и, следовательно, реагировать на них. Методы отладки определяются также типом Web-сервера.
На платформе NT в качестве Web-сервера наиболее целесообразно с точки зрения доступности использовать MS IIS версий 2—4, а в качестве браузера MS IE —версии не ниже четвертой. Операционная система Windows NT 4.0 поставляется в комплекте с MS IIS 2.0 (дистрибутив — в I386\InetSrv). Можно также установить бесплатно распространяемый пакет NTOptPak, в который входит MS IIS 4.0, более распространенная, чем версии 2.0 и 3.0, и лишенная их недостатков.
Настройка отладки Web-приложения вместе с MS IIS 4.0 в интегрированной среде Delphi достаточно сложна и трудоемка, главным образом, из-за внесения множества изменений в реестр. Поэтому имеет смысл сначала отладить такое приложение с MS IIS версии 2 или 3.
ISAPI- и NSAPI-приложения представляют собой динамические библиотеки DLL с предопределенными адресами входа. Web-сервер запускает Web-приложения передачей запросов на эти адреса. Для обеспечения их связи нужно установить определенные параметры. Причем перед тем, как начнется работа Web-приложения, Web-сервер должен быть остановлен.
При отладке Web-приложений с MS IIS в меню Run/Parameters вводится следующее: Host Application: c:\winnt\system32\inetsrv\inetinfo.exe (маршрут к файлу должен соответствовать фактическому) и Run/Parameters: -e w3svc. Это позволит запускать MS IIS 2.0 и 3.0 и производить отладку ISAPI DLL под управлением Web-сервера.
Если на сервере установлен MS IIS версии 4.0 или более поздней, то в конфигурацию Windows NT 4.0 придется внести целый ряд изменений.
С помощью утилиты-конфигуратора DCOMCnfg нужно изменить учетную запись (user account) в разделе identify сервиса MS IIS Admin Service на имя того пользователя, который будет выполнять отладку.
С использованием редактора реестра (REGEDIT) или аналогичной утилиты следует удалить имена LocalService из всех подключей, связанных с аналогичными структурами IISADMIN, которые входят в ветвь реестра HKEY_CLASSES_ROOT/AppID и HKEY_ CLASSES_ROOT/CLSID. Раздел LocalService присутствует в составе следующих подключей:
{61738644-F196-11D0-9953-00C04FD919C1} // IIS WAMREG admin Service; {9F0BD3A0-EC01-11D0-A6A0-00A0C922E752} // IIS Admin Crypto Extension; {A9E69610-B80D-11D0-B9B9-00A0C922E750} // IISADMIN Service.Кроме того, из двух первых подключей узлов AppID нужно удалить имя RunAs, а последний дополнить значением Interactive User.
Далее с применением REGEDIT необходимо добавить подключи LocalService32 ко всем структурам, связанным с IISADMIN и относящимся к узлу CLSID. Иными словами, каждый подключ узла CLSID, указанный в предыдущем пункте, или любой другой, под которым найдено имя LocalService keyword, следует дополнить подключом LocalService32. После чего нужно установить по умолчанию для этих новых подключей значение: c:\winnt\system32\inetsrv\inetinfo.exe -e w3svc (с учетом фактического расположения файла inetinfo.exe).
Следует установить в dword:3 значение Start для подключей:
HKEY_LOCAL_MACHINE\SYSTEM\ CurrentControlSet\Services\IISADMIN, HKEY_LOCAL_MACHINE\SYSTEM\ CurrentControlSet\Services\MSDTC, HKEY_LOCAL_MACHINE\SYSTEM\ CurrentControlSet\Services\W3SVC.Чтобы вернуть реестр в исходное положение по окончании процесса отладки, необходимо проделать обратные операции. Они достаточно трудоемки и требуют повышенного внимания. Но можно автоматизировать процесс перенастройки реестра, разработав собственную программу, что поможет быстро определить его состояние без поиска, с помощью стандартных средств просмотра и коррекции, и перейти от отладки к работе ОС в штатном режиме. Такая программа предназначена для многократного использования. В качестве основного класса при разработке можно выбрать Tregistry, а для анализа, чтения и записи значений, а также удаления подключей подойдут многочисленные методы этого компонента: ReadKey, WriteFloat, WriteString, CloseKey, DeleteKey и т. п.
Такая программа поможет также контролировать фактическое состояние реестра в нужных подключах. Работа на этом уровне весьма ответственна, поскольку любая ошибка может вызвать недопустимые изменения в важнейшем для ОС механизме и привести к переустановке всей системы. Поэтому целесообразно освоить основные методы чтения фрагментов реестра, а удаление, создание и запись выполнять сначала только на тестовой ветви системы. Если приводимый ниже текст (листинг 5) вставить, например, в подпрограмму, реагирующую на событие открытия формы OnActivate, то в окне Label1 при настройке реестра на режим отладки появится имя подключа LocalServer32, а в окне Edit1 — соответствующее значение по умолчанию: c:\winnt\system32\inetsrv\ inetinfo.exe -e w3svc.
Используя отлаженную программу, можно быстро и безошибочно изменять, контролировать и восстанавливать необходимые настройки реестра. Для удобства можно заменить числовую информацию пользовательскими терминами и т. п. Предварительно, конечно, придется разработать и отладить программу, вносящую нужные изменения в реестр.
При отладке Web-приложений с Personal Web Server под управлением Windows 95/98 в меню Run/Parameters вводятся следующие параметры: Host Application: c:\Program Files\websvc\system\inetsw95.exe; Run Parameters: -w3svc. Они позволят запускать Personal Web Server и отлаживать ISAPI DLL. Для настройки на отладку Web-приложений с сервером Netscape Server версии 2.0 в соответствии с рекомендациями фирмы Borland требуется дополнительно изменить конфигурацию. Сначала следует скопировать файл ISAPITER.DLL (из каталога Bin) в каталог C:\Netscape\Server\Nsapi\Examples (с учетом фактического размещения файлов). Затем необходимо произвести преобразования в серверных конфигурационных файлах, расположенных в каталоге C:\Netscape\Server\Httpd-\Config.
В файле OBJ.CONF file после строки Init fn=load-types mime-types=mime.types требуется добавить строку Init funcs="handle-isapi,check-isapi,log-isapi" fn="load-modules" shlib="c:/netscape/server/nsapi/examples/ ISAPIter.dll". В секции <Object name=default> файла OBJ.CONF перед строкой NameTrans fn=document-root root="C:/ Netscape/Server/docs" нужно вставить строку NameTrans from="/scripts" fn="pfx2dir" dir="C:/Netscape/Server/ docs/scripts" name="isapi". В конце файла OBJ.CONF необходимо добавить секцию <Object name="isapi"> PathCheck fn="check-isapi" ObjectType fn="force-type" type="magnus-internal/isapi" Service fn="handle-isapi" </Object>. В конце файла MIME.TYPES следует добавить строку type=magnus-internal/isapi exts=dll.Дополнения в первом и втором действиях вносятся одной непрерывной строкой. Затем для отладки Web-приложений под управлением сервера Netscape Fast Track требуется установить параметры Host Application: c:\Netscape\server\bin\httpd\httpd.exe и Run Parameters: c:\Netscape\server\httpd-<servername>\config. Они позволяют запустить сервер, а также указывают путь к конфигурационным файлам.
Создание DLL-приложений для работы под управлением Windows NT также имеет свою специфику. Необходимо расширить права пользователя так, чтобы стало возможным отладить DLL-программы. Делает это администратор Windows NT через программу User Manager, назначив Act As Part Of The Operating System (работа в режиме операционной системы) в меню Policies/User Rights (обязательно указав Show Advanced User Rights) тому пользователю-программисту, который будет осуществлять операцию. Подобная процедура неудобна потому, что по окончании отладки нужно восстановить исходные права пользователя.
Перед запуском приложения следует установить в соответствующих местах исходного текста программы точки прерывания таким образом, чтобы при передаче Web-сервером запроса в DLL-приложение прерывалось бы выполнение программы и можно было бы провести отладку.
После отладки DLL-приложения в формате ISAPI или NSAPI можно быстро перекомпилировать исходные тексты в exe-файл формата CGI/WinCGI, добавив отлаженный Web-модуль в репозитарий. При этом нужно присвоить название Web-модулю, дать описание, выбрать страницу репозитария (например, Data Modules) и иконку, указать фамилию автора. Созданный Web-модуль сохраняется в качестве шаблона. Затем через меню File/New следует вызвать мастер Web Server Application. В диалоге New Web Server Application выбрать CGI или WinCGI. Стереть автоматически сгенерированный Web-модуль и через меню File/New установить шаблон отлаженного приложения, который был ранее сохранен на соответствующей странице.
С помощью Delphi 5.0 можно создавать CGI/WinCGI-приложения, которые в виде exe-файлов должны напрямую запускаться Web-сервером. Однако методика их отладки несколько сложнее, чем DLL-приложений, поскольку нужно моделировать сервер путем создания конфигурационного файла с параметрами, включающими информацию из запроса. Затем нужно запустить WinCGI-приложение, указав полные адреса файла, содержащего информацию от клиента, и файла, в котором должен быть записан ответ. После этого можно, как обычно, отлаживать Web-приложение.
Механизм работы CGI/WinCGI-приложений проще, чем у ISAPI/NSAPI-приложений. При каждом обращении они запускаются в отдельном потоке, поэтому пересечения с многопотоковым механизмом не происходит. Кроме того, в CGI/WinCGI-приложениях не возникает проблем, имеющих место при кэшировании Web-модулей ISAPI/NSAPI-приложений.
Таким образом, можно облегчить настройку при отладке Web-приложений, используя упрощенную версию MS IIS 2.0. Полученные программы, как правило, могут работать и под управлением MS IIS 4.0. При этом следует учитывать некоторые незначительные на первый взгляд детали, которые могут существенно затруднить процесс отладки серверных приложений, как-то: проблемы с установкой состояния реестра, предварительная настройка доступа к определенным каталогам и файлам и последовательность выполнения операций.
Рассмотрим процесс отладки Web-серверного приложения iserver.dll, предлагаемого разработчиками Delphi 5.0. Его исходные тексты (iserver.dpr, main.pas, main.dfm) находятся в каталоге ...\Program Files\Borland\Delphi5\ Demos\Webserv\, а файловая база данных — в каталоге ...\Program Files\Common Files\Borland Shared\Data\. На этот же каталог должен указывать и DBDemos, настраиваемый с помощью программы-администратора bdeAdmin.exe, имеющейся в ...\Program Files\Common Files\Borland Shared\BDE\.
Само DLL-приложение должно быть скомпилировано в каталог, из которого Web-сервер может производить запуск exe-программ. Таковым может быть, например, создаваемый по умолчанию (при установке Web-сервера) виртуальный каталог Web-сервера SCRIPTS или аналогичный, созданный заново. После внесения рекомендованных изменений в реестр NT при отладке программы под управлением MS IIS 4.0 Web-сервер может запускаться только из среды Delphi 5.0. А в случае использования MS IIS 2.0 или 3.0 за работой Web-сервера приходится следить программисту, запуская и останавливая соответствующий сервис World Wide Web Publishing Service из окна Services панели управления.
Перед отладкой MS IIS должен быть остановлен, а начинать работу он будет запуском Web-приложения (DLL-программы) из среды Delphi 5.0. Сначала разумно проверить, работает ли уже отлаженное приложение, такое как демонстрационный пример Iserver.dll. Для этого нужно поместить исходный файл в раздел SCRIPTS. В окне Services следует проверить, подключен ли сервис Wide Web Publishing Service, если же нет — нужно нажать кнопку Start. Затем в окне браузера требуется набрать имя ПК или IP-адрес.
Иногда программа полностью не запускается, хотя все вроде бы выполнено правильно, т. е. MS IIS работает и есть доступ к запуску DLL-приложения. В этом случае могут появиться сообщения о «внутренней» ошибке сервера, например о неудачной инициализации BDE, невозможности работать с закрытой таблицей и т. п. Подобная информация свидетельствует об отсутствии права пользователя на доступ к каталогу ...\BDE\ и/или ...\DATA\, который, если необходимо, открывается или закрывается нажатием правой кнопки мыши на свойствах (properties/Security/permissions) соответствующего компонента (диска, каталога или файла).
Перед запуском MS IIS нужно установить протокол TCP/IP и выбрать какой-нибудь (фиктивный) IP-адрес, например 222.10.1.220, поскольку при разработке и отладке серверных приложений ПК следует отключить от Internet. Работу TCP/IP можно протестировать, набрав сначала консольную команду ping 127.0.0.1, а затем ping 222.10.1.220. В случае успешной установки протокола будет получен ответ типа ‘Reply from 127.0.0.1: bytes=32 time<10ms TTL=32’, а при неверной конфигурации — Pinging 222.10.1.220 with 32 bytes of data: Request timed out’.
Включившись в работу, завершите функционирование MS IIS из окна Services панели управления. Затем, запустив Delphi 5.0 и открыв указанный проект, расставьте точки останова в тексте модуля main, а после установки параметров загрузите отлаживаемый модуль через меню Run/Add Breakpoint/Module Load Breakpoint. Откройте окно Modules, нажав комбинацию клавиш <Alt>+<Ctrl>+m, и убедитесь, что там есть строка: iserver.dll not loaded. Потом, набрав комбинацию клавиш <Ctrl>+<F9> (или нажав кнопку Run), запустите Web-сервер. В окне Modules появится список модулей, запущенных под управлением MS IIS, но отлаживаемый модуль еще не будет загружен.
Функционирование модуля начнется после запуска браузера и набора строки типа http:// 222.10.1.220/scripts/iserver.dll/. При этом в окне Modules можно увидеть все входные точки и модули, связанные с отлаживаемым приложением. Затем начнется непосредственная отладка, и в iserver.dpr на строке Application.Initialize курсор остановится. Теперь нужно снова запустить приложение, нажав кнопку Run. Следующий останов будет в точке прерывания. Время соединения браузера с сервером ограничено определенными параметрами настройки браузера, а потому не следует медлить, иначе связь клиент—сервер прервется и процесс отладки прекратится. Тогда можно увеличить время соединения и продолжить отладку.
Перевести файл формы DFM в текстовый и наоборот.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
Since Delphi 5, DFM-Files are stored as text files. Unfortunately, D2-D4 can only read DFM-Files in a binary-format. Delphi provides a conversion utility (convert.exe) to convert between .txt and .dfm formats. Seit D5 werden die DFM-Dateien als Textdateien gespeichert. Um diese mit D2-D4 bearbeiten zu кцnnеn, mьssеn sie in eine ьinдге Form gewandelt werden. Um dies zu erreichen, ist im BIN-Verzeichnis von Delphi ein kleines Programm namens "Convert.exe" vorhanden. // Sample calls, Verwendung: // Converting from *.DFM to *.TXT Convert.exe FormMain.dfm FormMain.txt // Converting from *.TXT to *.DFM Convert.exe FormMain.txt FormMain.dfmВызов диалога подключения сетевого диска.

| Идёт себе по дороге программер, вдруг ему по башке кирпич... "Тетрис" - успел подумать программист. |
Попробуйте WNetConnectionDialog. Данная функция инкапсулирована в Windows.pas и специально предназначена для этого.
Заголовок диалогового окна.

| Окошко, окошко, повернись к ядру задом, а ко мне дружественным интерфейсом! |
Заголовок диалогового окна устанавливается в момент вызова CreateMessageDialog, чей код расположен в Dialogs.pas. При этом происходит вызов LoadStr, который получает Warningcaption, Cautioncaption и пр., так что у вас есть два пути: Или вы изменяете Dialogs.pas, или вы редактируете строки в .res-файле.
Вызывает диалог выбора иконки.
Автор: Babay_S WEB-сайт: http://delphibase.endimus.com
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Вызывает диалог выбора иконки (как при смене значка на десктопе)
Зависимости: uses activex;
Автор: Babay_S
Copyright: Мои раскопки
Дата: 27 сентября 2002 г.
***************************************************** }
uses activex;
function ChangeIconDialog(hOwner: LongInt; Filename: string; var IconIndex:
LongInt): LongInt;
{Вызывает диалог выбора иконки (как при смене значка на десктопе)
Параметры
hOwner - хэндл родителя.
FileName - полный путь файла с иконками (должен существовать).
Если в указанном файле нет иконок, вызываются иконки Shell32.dll.
IconIndex возвращает номер иконки в файле.
Результат выполнения функции возвращает 1 если иконка выбрана и 0 если отмена
Далее эту иконку можно вытащить с помощью вполне документированной функции
ExtractIconEx из той же библиотеки.
}
implementation
function SHChangeIconDialog(hOwner: LongInt; sFilename: LPWSTR; nBuf: LongInt;
var nIconIndex: LongInt): LongInt;
stdcall; external 'Shell32.dll' index 62;
function ChangeIconDialog(hOwner: LongInt; Filename: string; var IconIndex:
LongInt): LongInt;
var
nFileName: LPWSTR;
FNLen: Integer;
i: LongInt;
begin
FNLen := Length(FileName) + 1;
nFileName := CoTaskMemAlloc(FnLen * sizeof(WideChar));
StringToWideChar(FileName, nFileName, FnLen);
if FileName = '' then
nFileName := nil;
Result := SHChangeIconDialog(hOwner, nFileName, 0, IconIndex);
CoTaskMemFree(nFileName);
end;
Доработанная функция.
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Вызывает диалог выбора иконки. Доработанная
Функция вызова диалогового окно "Изменение иконки"
Зависимости: Windows, SysUtils
Автор: Alex Sal'nikov, alex-co@narod.ru, Москва
Copyright: Доработка библиотеки JVCL
Дата: 15 июля 2003 г.
***************************************************** }
uses
Windows, SysUtils;
function ChangeIconDialog(hOwner: tHandle; var FileName: string; var IconIndex:
Integer): Boolean;
// Функция вызова диалогового окно "Изменение иконки"
implementation
resourcestring
SNotSupported = 'This function is not supported by your version of Windows';
function ChangeIconDialog(hOwner: tHandle; var FileName: string; var IconIndex:
Integer): Boolean;
type
SHChangeIconProc = function(Wnd: HWND; szFileName: PChar; Reserved: Integer;
var lpIconIndex: Integer): DWORD; stdcall;
SHChangeIconProcW = function(Wnd: HWND; szFileName: PWideChar;
Reserved: Integer; var lpIconIndex: Integer): DWORD; stdcall;
const
Shell32 = 'shell32.dll';
var
ShellHandle: THandle;
SHChangeIcon: SHChangeIconProc;
SHChangeIconW: SHChangeIconProcW;
Buf: array[0..MAX_PATH] of Char;
BufW: array[0..MAX_PATH] of WideChar;
begin
Result := False;
SHChangeIcon := nil;
SHChangeIconW := nil;
ShellHandle := Windows.LoadLibrary(PChar(Shell32));
try
if ShellHandle <> 0 then
begin
if Win32Platform = VER_PLATFORM_WIN32_NT then
SHChangeIconW := GetProcAddress(ShellHandle, PChar(62))
else
SHChangeIcon := GetProcAddress(ShellHandle, PChar(62));
end;
if Assigned(SHChangeIconW) then
begin
StringToWideChar(FileName, BufW, SizeOf(BufW));
Result := SHChangeIconW(hOwner, BufW, SizeOf(BufW), IconIndex) = 1;
if Result then
FileName := BufW;
end
else if Assigned(SHChangeIcon) then
begin
StrPCopy(Buf, FileName);
Result := SHChangeIcon(hOwner, Buf, SizeOf(Buf), IconIndex) = 1;
if Result then
FileName := Buf;
end
else
raise Exception.Create(SNotSupported);
finally
if ShellHandle <> 0 then
FreeLibrary(ShellHandle);
end;
end;
Пример использования:
procedure TForm1.Button1Click(Sender: TObject); var file: string; index: Integer; begin ChangeIconDialog(Handle, file, index); Edit1.Text := file; Edit2.Text := IntToStr(index); end;
Диалог-компонент с кнопками Да, Нет, Отмена.
Автор: Святослав WEB-сайт: http://delphibase.endimus.com
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Диалог с кнопками Yes, No, Cancel (компонент).
Работает примерно так же как TDialog (для удобства).
TYesOrNoDialog.ShowNoButton - показывать ли кнопку No.
TYesOrNoDialog.ShowCancelButton - показывать ли кнопку Cancel.
TYesOrNoDialog.Caption - заголовок диалгоа.
TYesOrNoDialog.Text - текст диалога.
function TYesOrNoDialog.Execute:Integer - показывает
диалог и возвращает значение MessageBox'a
Зависимости: Windows, Messages, SysUtils, Classes, Dialogs, Forms;
Автор: Святослав, lisin@asicdesign.ru, ICQ:138752432, Saint Petersburg
Copyright: (C) NetBreaker666[AWD]<SP666>@Svjatoslav_Lisin - т.е. я сам
Дата: 11 августа 2002 г.
***************************************************** }
unit YesOrNoDialog;
interface
uses
Windows, Messages, SysUtils, Classes, Dialogs, Forms;
type
TYesOrNoDialog = class(TComponent)
private
{ Private declarations }
_OnEx: TNotifyEvent;
__SNO: Boolean;
__SC: Boolean;
_Y, _N, _C: Boolean;
_Caption, _Text: string;
procedure SetNo(S: Boolean);
procedure SetCancel(S: Boolean);
protected
{ Protected declarations }
public
{ Public declarations }
property Yes: Boolean read _Y;
property No: Boolean read _N;
property Cancel: Boolean read _C;
published
{ Published declarations }
property ShowNoButton: Boolean read __SNO write SetNo;
property ShowCancelButton: Boolean read __SC write SetCancel;
property Caption: string read _Caption write _Caption;
property Text: string read _Text write _Text;
property OnExecute: TNotifyEvent read _OnEx write _OnEx;
function Execute: Integer;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('NetBreakers', [TYesOrNoDialog]);
end;
//Voen secret.
// 2301900
function TYesOrNoDialog.Execute: Integer;
var
Hn: THandle;
begin
Hn := Application.Handle;
if __SNO and __SC then
begin
Result := MessageBox(Hn, PChar(text), PChar(Caption), MB_YESNOCANCEL);
_Y := (Result = 6) or (Result = 1);
_N := Result = 7;
_C := REsult = 2;
Exit;
end;
if __SNO then
begin
Result := MessageBox(Hn, PChar(text), PChar(Caption), MB_YESNO);
_Y := (Result = 6) or (Result = 1);
_N := Result = 7;
_C := REsult = 2;
Exit;
end;
if __SC then
begin
Result := MessageBox(Hn, PChar(text), PChar(Caption), MB_OKCANCEL);
_Y := (Result = 6) or (Result = 1);
_N := Result = 7;
_C := REsult = 2;
Exit;
end;
Result := MessageBox(Hn, PChar(text), PChar(Caption), MB_OK);
_Y := (Result = 6) or (Result = 1);
_N := Result = 7;
_C := Result = 2;
end;
procedure TYesOrNoDialog.SetNo(S: Boolean);
begin
__SNO := S;
end;
procedure TYesOrNoDialog.SetCancel(S: Boolean);
begin
__SC := S;
end;
end.
Вызов диалога отключения сетевого диска.
Автор: Dimka Maslov WEB-сайт: http://delphibase.endimus.com
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Открытие диалогового окна «Отключение сетевого диска»
Зависимости: Windows
Автор: Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright: Dimka Maslov
Дата: 21 мая 2002 г.
***************************************************** }
function DisconnectNetworkDrive(Wnd: HWND = 0): DWORD;
begin
if Wnd = 0 then
Wnd := FindWindow('Shell_TrayWnd', '');
Result := WNetDisconnectDialog(Wnd, RESOURCETYPE_DISK);
end;
// Пример использования:
DisconnectNetworkDrive(Application.Handle);
Вызывает диалог открытия файла.
Автор: Babay_S WEB-сайт: http://delphibase.endimus.com
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Вызывает диалог открытия файла
вызывет диалог открытия как в офисе
Зависимости: uses activeX
Автор: Babay_S
Copyright: Мои раскопки
Дата: 27 сентября 2002 г.
***************************************************** }
uses activeX;
function GetFileNameFromBrowse(hOwner: LongInt; var sFile: string;
sInitDir, sDefExt, sFilter, sTitle: string): Boolean;
{Вызывает диалог открытия файла
Пример
GetFileNameFromBrowse(handle,b1,'c:\Install','*.txt',
'Текстовые файлы'#0'*.txt'#0'Все файлы'#0'*.*'#0#0,'Название');
фильтр который будет использоваться должен стоять в перечне первым
}
implementation
function SHGetFileNameFromBrowse(hOwner: LongInt; sFile: LPWSTR; nMaxFile:
LongInt;
sInitDir: LPWSTR; sDefExt, sFilter, sTitle: LPWSTR): Boolean;
stdcall; external 'Shell32.dll' index 63;
//*************************** Код функций ****************************************
function GetFileNameFromBrowse(hOwner: LongInt; var sFile: string;
sInitDir, sDefExt, sFilter, sTitle: string): Boolean;
var
sFileW, sInitDirW, sDefExtW, sFilterW, sTitleW: PWideChar;
sFileL, sInitDirL, sDefExtL, sFilterL, sTitleL: Integer;
begin
sFileW := CoTaskMemAlloc(255 * sizeof(WideChar));
StringToWideChar(SFile, SFileW, 255);
SInitDirL := Length(sInitDir) + 1;
sInitDirW := CoTaskMemAlloc(SInitDirL * sizeof(WideChar));
StringToWideChar(SInitDir, SInitDirW, sInitDirL);
SDefExtL := Length(sDefExt) + 1;
sDefExtW := CoTaskMemAlloc(SDefExtL * sizeof(WideChar));
StringToWideChar(SDefExt, SDefExtW, sDefExtL);
SFilterL := Length(sFilter) + 1;
sFilterW := CoTaskMemAlloc(SFilterL * sizeof(WideChar));
StringToWideChar(SFilter, SFilterW, sFilterL);
STitleL := Length(sTitle) + 1;
sTitleW := CoTaskMemAlloc(STitleL * sizeof(WideChar));
StringToWideChar(STitle, STitleW, sTitleL);
Result := SHGetFileNameFromBrowse(hOwner, sFileW, Integer(sFileW), sInitDirW,
sDefExtW, sFilterW, sTitleW);
SFile := sFileW;
CoTaskMemFree(sFileW);
CoTaskMemFree(sInitDirW);
CoTaskMemFree(sDefExtW);
CoTaskMemFree(sFilterW);
CoTaskMemFree(sTitleW);
end;
Пример использования:
if GetFileNameFromBrowse(handle, b1, 'c:\Install', '*.txt', 'Текстовые файлы'#0'*.txt'#0'Все файлы'#0'*.*'#0#0 , 'Название') then begin end;
Функция вызывает стандартный диалог Свойства.
Автор: Babay_S WEB-сайт: http://delphibase.endimus.com
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Функция вызывает стандартный диалог "Свойства"
Зависимости: uses Activex;
Автор: Babay_S
Copyright: мои раскопки
Дата: 27 сентября 2002 г.
***************************************************** }
uses Activex;
function ObjectProperties(hOwner, uFlags: LongInt; sName, sParam: string):
LongInt;
{Функция вызывает диалог "Свойства"
Параметры
hOwner - хэндл родителя.
uFlags - может принимать два значения - 1 для свойств принтера, 2 - для файла
sName - имя принтера или файла. Если вместо имени файла указаны папка или диск,
вызывается соответствующее окно свойств. Если указано "" - появляются свойства Системы.
sParam - название вкладки на пропертях, которую надо показать. Если предать пустую
строку или несуществующую вкладку - будет показана первая вкладка.
}
implementation
function ObjectProperties(hOwner, uFlags: LongInt; sName, sParam: string):
LongInt;
var
sNameW, sParamW: PWideChar;
sNameL, sParamL: Integer;
begin
SNameL := Length(sName) + 1;
sNameW := CoTaskMemAlloc(SNameL * sizeof(WideChar));
StringToWideChar(SName, SNameW, sNameL);
SParamL := Length(sParam) + 1;
sParamW := CoTaskMemAlloc(SParamL * sizeof(WideChar));
StringToWideChar(SParam, SParamW, sParamL);
Result := SHObjectProperties(hOwner, uFlags, sNameW, sParamW);
CoTaskMemFree(sNameW);
CoTaskMemFree(sParamW);
end;
Z-порядок при каждом показе диалога.
// Там не листбокс, а EDIT, но суть точно та же. Чтобы получить текст, а не // записать его, надо просто использовать Get вместо Set. Судя по тому, что // программа уже 2 года работает без малейших проблем, Z-порядок при каждом // показе диалога один и тот же. program Project1; uses Windows, Messages; const Title1 = 'Установка связи'; Title2 = 'Удаленное соединение'; Login = '...'; Password = '...'; var Wnd: HWND; Control: array [0..127] of char; procedure TypeTextIntoNextEdit(AText:string); begin repeat // Ищем следующее в Z-порядке окно класса EDIT Wnd := GetWindow(Wnd, GW_HWNDNEXT); GetClassName(Wnd, Control, SizeOf(Control)) until Control = 'Edit'; SendMessage(Wnd, WM_SETTEXT, 0, Integer(PChar(AText))) // Вводим текст end; begin Wnd := FindWindow(nil, Title1); // Это окно самого диалога if Wnd = 0 then // Если не найдено, ищем другой диалог begin Wnd := FindWindow(nil, Title2); if Wnd = 0 then Exit; end; Wnd := GetWindow(Wnd, GW_CHILD); // Это верхний комбобокс TypeTextIntoNextEdit(Login); // Вводим логин TypeTextIntoNextEdit(Password) // Вводим пароль end. |
Размер диалогового окна.
Автор: Steve Schafer Давайте начнем с Microsoft Windows User Interface Guidelines (Руководящие Принципы Построения Интерфейса Пользователя Microsoft Windows) и допустим, что мы создаем диалоговое окно, содержащее компонент TMemo, занимающий большую часть площади формы и кнопки OK и Cancel, размещенные в ее нижней части. Несколько примечаний из "Принципов": Диалоговые окна должны быть основаны на базовых диалоговых модулях, dialog base units (DBU), которые создаются с учетом размера шрифта и разрешения экрана. Диалоговые окна должны быть созданы, по возможности, на основе одного из нескольких стандартных размеров. Для нашего окна мы используем размер 212x188 DBU. Все элементы управления должны распологаться как минимум на расстоянии 7 DBU от края окна. Все элементы управления должны иметь между друг другом зазор размером минимум 4 DBU. Кнопки должны иметь высоту 14 DBU. (Про ширину кнопок "принципы" умалчивают; в обычном случая я использую кнопки шириной 40 DBU.)Вот необходимая для создания формы и элементов управления информация, которую мы можем получить во время выполнения приложения:
procedure TMyForm.FormCreate(Sender: TObject);
var
BaseUnit, Margin, Spacing, BtnW, BtnH: Integer;
begin
BaseUnit := Canvas.TextHeight('0'); { 1 BaseUnit = 8 DBU определениям }
Width := (212 * BaseUnit) div 8;
Height := (188 * BaseUnit) div 8;
Margin := (7 * BaseUnit) div 8 - GetSystemMetrics(SM_CXFIXEDFRAME);
Spacing := (4 * BaseUnit) div 8;
BtnW := (40 * BaseUnit) div 8;
BtnH := (14 * BaseUnit) div 8;
Memo1.SetBounds(Margin, Margin, ClientWidth - 2 * Margin, ClientHeight -
2 * Margin - Spacing - BtnH);
OkButton.SetBounds(ClientWidth - Margin - Spacing - 2 * BtnW, ClientHeight -
Margin - BtnH, BtnW, BtnH);
CancelButton.SetBounds(ClientWidth - Margin - BtnW, ClientHeight - Margin -
BtnH, BtnW, BtnH);
end;
|
Данный код позволяет создать диалоговое окно с правильными размерами и пропорциями, независимо от разрешения экрана и шрифтов.
Безопастность Dial-up пользователям.
Оформил: DeeCo
Я пользуюсь динамическим IP адресом - стоит ли мне заботиться о безопасности?
Хотя и бытует мнение,что нет,на самом деле это все враки. Во-первых, если приблизительно известен временной диапазон, в котором компьютер может быть подключен к Internet,то произвести сканирование всего пространства динамических адресов провайдера - не так уж сложно. Во-вторых,многие атаки производятся "наугад" - без какой-либо конкретной цели. И в-третьих,динамический адрес можно успеть "засветить" на irc,www и т.п. - при пользовании практически любым сетевым сервисом.
А что,собственно,мне угрожает?
Да все,что угодно. Ваш копльютер могут "подвесить", причем иногда - даже для этого вообще не нужно,чтобы ваша машина принимала какие-либо соединения,могут украсть или подменить какую-либо ценную информацию.
Какие базовые предосторожности необходимы?
Для начала неплохо оценить,во что обойдутся возможные последствия - это поможет принять _адекватные_ меры. Если нужно подключить к Internet небольшую офисную сеть из десятка-полутора машин, да еще при том,что некоторые из них содержат конфиденциальную информацию,вполне возможно, что имеет смысл приобрести несложный firewall - или сделать его своими силами,если есть возможность. Для того,чтобы более-мнее безопасно выходить в Internet с домашнего компьютера, достаточно принять некоторые базовые предосторожности.
Во-первых,нужно отключить разделение ресурсов компьютера по TCP/IP. Это можно сделать как минимум двумя способами - первый, который рекомендует фирма Microsoft состоит в том,чтобы выключить разделение ресурсов вообще (Settings -> Control Panel -> Network -> File and Print Sharing) Разумеется, он пригоден в том случае,если вы не собираетесь активно использовать эту возможность.
Второй способ - отсоединить интерфейс Netbios от TCP/IP (Settings -> Control Panel -> Network -> TCP/IP ->Properties -> Bindings).
Во-вторых,полезно убедиться,что используемые версии Windows 95 или NT и сетевого ПО являются последними и что для них не выходило никаких апдейтов,сервис-паков и т.д. Ну а о том,что не нужно пользоваться сомнительным ПО, запускать любой файл,приходящий по email и вообще совершать подобные глупости,наверное, и упоминать не стоит. Напомню только,что в любом word-документе может оказаться macro-вирус ;-).
Какое ПО может представлять дополнительную опасность?
Почти любое ;-). Чуть подробнее: При использовании наиболее распространенных WWW brоwsеr'ов, таких как Netscape Navigator или MS Internet Explorer возможны проблемы и более серьезные,чем "падение" в результате внутренней ошибки - включая, например, чтение любого файла с вашего диска - по-настоящему, а не так,как это реализовано в дурацкой шутке,которой уже не первый год пугают лопухов.
Как защититься от этого?
Использовать последние версии brоwsеr'ов, в которых _известные_ "дырки" более-менее заткнуты,а если нужно в некоторой мере обезопасить себя от неизвестных - могу разве что посоветовать выключить поддержку ActiveX,Java и Javascript. В случае с еmаil'ом - опять же,если не делать глупостей и не запускать приходящие .exe файлы (и помнить о макро-вирусах!),вряд ли придется столкнуться с большей нериятностью,чем приходящие с завидной регулярностью идиотические письма с предложениями купить что-нибудь ненужное или поучаствовать в очередной дурацкой пирамиде с посыланием денег в конверте черт-те куда. Это называется spam - и бороться с этим можно, вычисляя по заголовкам письма интернет-провайдера,допускающего подобный бардак и начиная с ним ругаться. Другого способа,по всей видимости,нет - кроме автоматизации удаления подобных писем (Inbox Assistant как раз умеет это делать). Теоретически MIME,расширенный формат сообщений, часто (а то и чаще, чем нужно) используемый в Internet _может_ представлять проблемы с безопасностью,но на практике это случается нечасто. News с точки зрения безопасности клиента не сильно отличается от email. Если вы пользуетесь IRC,то,скорее всего,самое главное - это то, что ip-адрес вашего компьютера, версия irc-клиента и зачастую тип и версия операционной системы становятся известными ну действительно всем и каждому - что явно не есть хорошо. Любителей испробовать на вас свои (чаще - не свои ;-) технические достижения может оказаться больше, чем кажется на первый взгляд. Кроме того,скрипт или даже сам irc клиент может содержать закладки с практически любыми возможностями. Больше всего неприятностей может доставить протокол dcc - кстати,его можно просто выключить в большинстве irc-клиентов. Если вы не понимаете в деталях,как работает предлагаемый скрипт - ставить его ни в коем случае нельзя - разве что если знающий человек его проверит. ICQ еще мало изучен в данном вопросе - но известно про него две вещи: a) любой клиент можно "свалить" потоком бессмысленных данных на тот порт,где он отвечает - "И это -только начало" (c) AO MMM b) он не предоставляет никаких дополнительных возможностей по сравнению с irc. Я бы вообще не рекомендовал пользоваться ICQ.
Я использую старый абонентский комплект с MSIE 3.01. Насколько это опасно и что нужно сделать?
Версия 3.01 MS Internet Explorer может принести несколько довольно-таки неприятных проблем с безопасностью: эта версия может запускать программы на том компьютере,где она работает - без согласия пользователя. По этой причине лучше заменить ее на более новую версию 3.02. Версия, которая в настоящий момент входит в абонентский комплект (3.02) тоже не лишена аналогичной ошибки,но во-первых,она относится не к самому MSIE,а к его совместному использованию с РоwеrРоint'ом, а во-вторых _такая_ ошибка (пока) известна только одна. Да и fix к ней имеется. К сожалению,мы пока не имеем возможности заменить версию MSIE внутри абонентского комплекта (программа для его генерации в "лучших" традициях фирмы Microsoft довольно-таки закрытая) на _самый_ последний релиз 3.02 ("Last Updated June 13, 1997"),но эта разница уже не столь принципиальна. Что, вкратце, можно сказать про Netscape Communicator 4.0-release? Эта версия,также как и предыдущие версии Web-brоwsеr'ов фирмы Netscape, имеет баг в реализации javascript, позволяющий (при стандартных установках) читать любой файл с вашего компьютера. Можно либо заменить ее на версию 4.01, либо изменить установки Netscape так, чтобы эта опасность перестала быть актуальной (меню Security Advisor - включить подтверждения на отправку данных по сети).
Почему меры безопасности не принимает провайдер?
Дело в том, что, например, запретив обращения к портам,используемым Windows/Samba для разделения ресурсов, мы лишим наших клиентов возможности использовать это для вполне законных и иногда даже необходимых целей. Поэтому мы обеспечиваем безопасность на своем участке сети - и ваша задача обеспечить ее на своем. Я,конечно, могу помочь это сделать - но не обязан делать это бесплатно ;-). Мне пришло письмо с предупреждением о вирусе, распространяющемся через email.. Подобная бредятина распространяется по сети уже не первый год - это просто очередной вариант "святого письма". Тот факт,что рост Internet'a обеспечивает постоянный приток свежих идиотов,готовых рассылать всем такие вещи - поистине удручает. Самая большая опасность,которой подвергает свои жертвы этот вирус - возможность пополнить вышеупомянутые ряды.
Является ли UUCP подключение полностью безопасным?
Практически - да. Полностью - нет. Практически - потому,что что во-первых, случаи "взлома" uucp почти не известны - для этого нужна прямая связь с атакуемой машиной именно по протоколу uucp. Однако,по крайней мере в некоторых версиях самой популярной реализации uucp для *DOS - uupc/@ by Ache мной был обнаружен баг, позволяющий обойти встроенную защиту обработчика команды uucp и прочитать/записать любой файл в любом директории - всего лишь используя относительный путь вида ~/../../somewhere. А во-вторых, оффлайновый способ взаимодействия машины с сетью делает невозможным получение информации немедленно и облегчает обнаружение попыток взлома. Разумеется,сказанное про email в значительной степени относится в том числе и к uucp подключению.
Набрать телефонный номер.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
implementation
{$R *.DFM}
function tapiRequestMakeCall(DestAddress, AppName,
CalledParty, Comment: PChar): Longint; stdcall; external 'TAPI32.DLL';
procedure TForm1.Button1Click(Sender: TObject);
var
PhoneNumber, AppName: array[0..255] of Char;
begin
PhoneNumber := '1234567890';
StrPCopy(AppName, Application.Title);
tapiRequestMakeCall(PhoneNumber, AppName, '', '');
end;
Что нужно предусмотреть при разработке приложения, которое будет работать при различном разрешении дисплея.
| Два мужика разговаривают: - Почему твой сын так быстро растёт, за уши тянешь что-ли? - Не, я просто монитор каждый месяц на 5 см поднимаю! |
Сделать сложный фон окна.
Фон окна может представлять собой повторяющиеся картинки произвольного размера.
procedure TForm1.FormCreate(Sender: TObject);
begin
bm := TBitMap.Create;
bm.LoadFromFile('Example.bmp');
end;
procedure TForm1.FormPaint(Sender: TObject);
var
x, y: integer;
begin
for x := 0 to Form1.ClientWidth div bm.Width do
for y := 0 to Form1.ClientHeight div bm.Height do
Form1.Canvas.Draw(x * bm.Width, y * bm.Height, bm);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
bm.Destroy;
end;
|
Вы можете использовать для "подложки" формы любой стиль кисти. Таким образом, можно получить окно "в полосочку", "в сеточку" и т.д.
procedure TForm1.FormCreate(Sender: TObject); begin Form1.Brush.Style := bsDiagCross; end; |
Ту же задачу можно решить, используя свойство кисти Bitmap, позволяющее создавать свои стили. Размер картинки при этом всегда равен 8X8.
procedure TForm1.FormCreate(Sender: TObject);
begin
Form1.Brush.Bitmap := TBitMap.Create;
Form1.Brush.Bitmap.LoadFromFile('Phone.bmp');
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Form1.Brush.Bitmap.Destroy;
end;
|
У меня константы могут иметь значение, отличное от заданного.
Автор: Nomadic.
Const из другого unit'а дает неверное значение. Похоже, это действительно bug, причем ОСОБО ОПАСНЫЙ, т.к. может исказить результаты расчетов, не вызвав заметных нарушений работы программы. В общем так. Эксперимент показал, что любая вещественная константа, определенная в интерфейсе модуля, может быть неверно (и не обязательно очень неверно - например, вместо 0.7 может появиться 0.115) прочитана в другом модуле. Баг особенно опасен тем, что он неустойчив и может пропадать и возникать без видимых причин (например, возникнуть, если предыдущая компиляция была неудачной и исчезнуть после использования константы в модуле, где она определена).Лечится (вроде бы) указанием типа.
const Wko: double = 0.9; |
Правда, теперь это уже не совсем константа...
Как сделать, чтобы орган управления, например, сложная линия, хваталась только за некий контур, и пропускала мышь под себя в других местах.
Автор: Nomadic Надо обрабатывать сообщение CM_HITTEST (Это сообщение получают даже потомки от TGraphicsControl, не имеющего своего HWND).Например, так:
procedure TLine.CMHitTest(var Message: TWMNCHitTest);
begin
if PointInLineReg(Message.XPos, Message.YPos) then
begin
Message.Result := 1;
end
else
begin
Message.Result := 0;
end;
end;
|
Для органов управления Windows, если Вы не используете VCL, требуется обрабатывать сообщение WM_NCHITTEST.
Простой Взлом - AriSoft DirectFTP v1.0.
Оформил: DeeCo Автор: http://www.cracklab.narod.ru
Page: http://home.coqui.net/alexr/index.htm ========== Вступление: ========== Хороших ФТП клиентов мало, и такого чтобы меня удовлетворил от и до я ещё не видел. Не стала исключением и эта программа. Однако кракать было нечего и я решился. ========== Разведка местности: ========== Я запустил программу и увидел окно с просьбой ввести своё имя и серийный номер (далее - СН). Я ввел имя "vallkor" номер "123321" и нажал "Register" полсе чего выскочил MessageBox с надписью "Invalid serial number". ========== Активные действия: ========== Я загрузил DirectFTP.exe в W32DAsm v 8.93 (со всеми патчами :) и после дизассемблирования я зашёл в String Reference и начал искать ту самую строку "Invalid serial number". И нашел её вот тут:
* Possible StringData Ref from Data Obj ->"Invalid serial number"
|
:0041131F 68FA704B00 push 004B70FA
:00411324 8BC7 mov eax, edi
:00411326 E8D5540600 call 00476800
:0041132B 50 push eax
* Reference To: USER32.MessageBoxA, Ord:0000h
В таких ситуациях должно быть ещё одно сообщение типа "Good serial" или "Thank you for 10 dollars, asso", причем в программе располагается все так: call CheckSerialNumber(char name, char sn) cmp cl,0 je BadMessage GoodMessage: call MessageBoxA BadMessage: call MessageBoxAЭто, конечно, очень грубо говоря, но смысл понятен. Т.е. нам надо найти теперь строку хорошего сообщения. Для этого мы опять заходим в String Reference и ищем сообщение об удачной регистрации или ещё чего-нить типа этого и ...... находим строку:
* Possible StringData Ref from Data Obj ->"Thank you for registering "
|
:00410BD7 B843704B00 mov eax, 004B7043
:00410BDC E8EFD50900 call 004AE1D0
Теперь поищем чуть выше условные переходы и найдём вот что: :00410B61 E88AD20900 call 004ADDF0 :00410B66 59 pop ecx :00410B67 84C9 test cl, cl <--Если СН введён правильный :00410B69 0F84F2040000 je 00411061 <--то НЕ прыгаем :00410B6F 6A40 push 00000040Т.е. Заменив je 00411061 на je 00410B6F мы вводя все что угодно будем получать сообщение об удачной регистрации. Но наша цель получить настоящий СН для своего имени. Смотрим чуть выше и видим:
:00410B1F 8D856CFFFFFF lea eax, dword ptr [ebp+FFFFFF6C] :00410B25 8B00 mov eax, dword ptr [eax] :00410B27 E810960800 call 0049A13C :00410B2C E887630900 call 004A6EB8 :00410B31 3BD8 cmp ebx, eax <--В eax введенный нами СН, а в ebx - ПРАВИЛЬНЫЙТеперь достаточно находясь в SoftIce курсором на строке:
:00410B31 3BD8 cmp ebx, eaxпосмотреть значение регистра eax:
:? ebx 0018F33A 0001635130 "у:"Полученый десятичный вариант и будет правильным СН: 1635130 Вот, наверное, и все. Рабочий СН получен, а писать КейГен не буду, потому как там основной цикл на 3 экрана, а ещё внутри всякая фигня. Короче не стоит она того. Исследовал и наваял туториал: vallkor //PTDS E-mail: vallkor@chat.ru (vallkor@etel.dn.ua) Page: http://vallkor.chat.ru
Использование PASSTHROUGH для отправки данных прямо на принтер.
Оформил: DeeCo Автор: http://www.swissdelphicenter.ch
{
By using the Windows API Escape() function,
your application can pass data directly to the printer.
If the printer driver supports the PASSTHROUGH printer escape,
you can use the Escape() function and the PASSTHROUGH printer escape
to send native printer language codes to the printer driver.
If the printer driver does not support the PASSTHROUGH printer escape,
you must use the DeviceCapabilities() and ExtDevMode() functions instead.
Mit der Windows API Funktion Escape() kann man Daten direkt zum Drucker schicken.
Wenn der Drucker Treiber dies nicht unterstutzt, mussen die DeviceCapabilities()
und ExtDevMode() Funktionen verwendet werden.
}
// DOS like printing using Passthrough command
// you should use "printer.begindoc" and "printer.enddoc"
type
TPrnBuffRec = record
bufflength: Word;
Buff_1: array[0..255] of Char;
end;
function DirectToPrinter(S: string; NextLine: Boolean): Boolean;
var
Buff: TPrnBuffRec;
TestInt: Integer;
begin
TestInt := PassThrough;
if Escape(Printer.Handle, QUERYESCSUPPORT, SizeOf(TESTINT), @testint, nil) > 0 then
begin
if NextLine then S := S + #13 + #10;
StrPCopy(Buff.Buff_1, S);
Buff.bufflength := StrLen(Buff.Buff_1);
Escape(Printer.Canvas.Handle, Passthrough, 0, @buff, nil);
Result := True;
end
else
Result := False;
end;
// this code works if the printer supports escape commands
// you can get special esc codes from printer's manual
// example:
printer.BeginDoc;
try
directoprinter('This text ');
finally
printer.EndDoc;
end;
DirectX и Delphi.
Автор: Азиз (JINX) Специально для Королевства Delphi Перед тем как приступить я хотел бы сделать пару оговорок. Во-первых для использования DirectX в Delphi необходим файл с заголовками, где объявлены имена функций DirectX API либо какой-нибудь компонент, позволяющий обращаться к интерфейсу DirectX через свои методы. В момент написания сего опуса я использую компонент DelphiX (автор - Hiroyuki Hori), распространяемый бесплатно - http://www.yks.ne.jp/~hori/DelphiX-e.html. (если у вас есть что-нибудь получше и Вы поделитесь со мной - я буду очень признателен.) И еще один адрес, по которому можно скачать компонент DelphiX : http://www.torry.ru/vcl/packs/hhdelphix.zip По возможности я буду писать и названия методов DelphiX и названия соответствующих интерфейсов Directx API – чтоб вам легче было ориентироваться в DirectX SDK. Во-вторых при всем своем гипертрофированном самомнении я не могу назвать себя экспертом в области DirectX – так что не судите чересчур строго. Я надеюсь сие творение хоть как то сможет помочь делающим первые шаги в DirectX – если Вы круче меня – буду признателен за помощь и указание на ошибки (коих увы наверняка сделал немало (честное слово не нарочно :-) ) Оговорка без номера – я пишу эти строки в те времена когда последней версией DirectX является DirectX 6. Ну что ж приступим пожалуй. Как известно DirectX предназначен в основном для программирования игр под Windows 9x. Тем не менее можно придумать еще не мало ему применений (рано или поздно грядет таки эра повсеместного трехмерного пользовательского интерфейса). DirectX состоит из следующих компонентов: DirectDraw® - предназначен для программирования всевозможных анимаций за счет быстрого доступа к изображению на экране и к видеопамяти, а также за счет использования возможностей аппаратуры (видеоадаптера) по манипуляции с буферами. DirectSound® - как видно из названия позволяет выводить звук, используя все что можно выжать из Вашей звуковой карты (ну почти все) DirectMusic™ - музыка. В отличие от DirectSound работает не с оцифрованным звуком (WAV) а с музыкальными командами, посылаемыми звуковой карте. DirectPlay® - упрощает жизнь программиста, решившегося добавить в своей программе возможность совместной работы (игры) по сети и по модему. (это наверняка хорошо знакомо любому геймеру) Direct3D® (мой любимый J) – содержит высокоуровневый интерфейс Retained Mode позволяющий легко выводить 3-хмерные графические обьекты, и низкоуровневый интерфейс Immediate Mode предоставляющий полный конроль над рендерингом. (если кто-нибудь знает как будет рендеринг по-русски – мой адрес в конце статьи) DirectInput® - поддержка устройств ввода. Пока джойстик, мышь, клавиатура и т.д. – впрочем можете быть уверены – если появится еще что – за Microsoft не заржавеет. DirectSetup – предназначен для установки DirectX. AutoPlay – самый обычный AutoPlay – позволяет запускать какую-нибудь программу (инсталяшку или саму игру) при установке CD-диска в дисковод. Вообще-то описание AutoPlay относится к Win 32 SDK и просто повторяется в DirectX SDK (думаю Microsoft включила его в DirectX SDK просто чтоб оно было под рукой у разработчика) Кое что о Direct3DRM®. (Reatined Mode) Система координат В Direct3D она соответствует так называемому правилу "левой руки". Суть правила в том, что если Вы растопырите пальцы левой руки так, что указательный палец будет направлен к экрану, большой к потолку, а средний параллельно столу туда, где обычно лежит мышиный коврик, то большому пальцу будет соответствовать координата Y, среднему – X, указательному Z. Говоря короче координата Z направлена как бы вглубь экрана (я во всяком случае нахожусь по эту его сторону :-)), координата Y – вверх, координата X – вправо (все рисунки из SDK). Возможно Вам это покажется непривычным. А что Вы тогда скажите на это – в DirectX цвета задаются тремя составляющими R,G,B, каждая из которых – число с плавающей точкой в диапазоне [0-1]. Например белый цвет – (1,1,1), серенький (0.5,0.5,0.5), красный (1,0,0) ну и т.д.
Все трехмерные объекты задаются в виде набора (mesh) многоугольников (граней – faces). Каждый многоугольник должен быть выпуклым. Вообще-то лучше всего использовать треугольники – более сложные многоугольники все равно будут разбиты на треугольники (на это уйдет столь драгоценно процессорное время). Грани (faces) состоят из вершин (vertex).
Грань становится видимой если она повернута так, что образующие ее вершины идут по часовой стрелке с точки зрения наблюдателя. Отсюда вывод – если Ваша грань почему-то не видна – переставьте вершины так, чтоб они были по часовой стрелке. Кроме того имеются другие объекты – источники света (прямой свет - directional light и рассеянный свет – ambient light), т.н. камера, текстуры, которые могут быть "натянуты" на грани и прочая, прочая… Наборы объектов составляют т.н. frames (затрудняюсь дать этому русское название). В Вашей программе всегда будет хоть один главный frame, называемый сцена (scene), не имющий фрейма-родителя, остальные фреймы принадлежат ему или друг другу. Я не буду долго разговаривать о том, как инициализировать все это хозяйство, для Дельфи-программиста достаточно разместить на форме компонент TDXDraw из библиотеки DelphiX.
Перейдем однако к делу. Запустите-ка Дельфи и откройте мою (честно говоря не совсем мою – большую часть кода написал Hiroyuki Hori – однако не будем заострять на этом внимание :-)) учебную программку - Sample3D. Найдите методTMainForm.DXDrawInitializeSurface.Этот метод запускается при инициализации компонента TDXDraw. Обратите внимание, что DXDraw инкапсулирует D3D, D3D2, D3Ddevice, D3DDevice2, D3DRM, D3DRM2, D3DRMDevice, D3DRMDevice2, DDraw – ни что иное как соответствующие интерфейсы DirectX. (только в названиях интерфейсов Microsoft вместо первой буквы D слово IDirect). Инициализация компонента очень подходящее место, чтоб выбрать кое какие режимы (что и делается в программке). Обратите внимание на DXDraw.D3DRMDevice2.SetRenderMode(D3DRMRENDERMODE_BLENDEDTRANSPARENCY or D3DRMRENDERMODE_SORTEDTRANSPARENCY); - Эти два флага установлены вот для чего – если у нас два треугольника находятся один под другим и оба видны (т.е. вершины у них по часовой) нужно их сперва отсортировать по координате Z чтоб понять кто кого загораживает. Включает такую сортировку флаг, названный скромненько эдак, по Microsots-ки: D3DRMRENDERMODE_SORTEDTRANSPARENCY. Однако как говаривал К. Прутков – смотри в корень. Корнем же у нас является метод
TMainForm.DXDrawInitialize(Sender: TObject);Здесь сначала создаются два фрейма – Mesh и Light, для нашего видимого объектика и для лампочки, его освещающей.
MeshFrame.SetRotation(DXDraw.Scene, 0.0, 10.0, 0.0, 0.05);(первые три цифры – координаты вектора вращения, последний параметр – угол полворота) . Тонкое (не очень правда :-)) отличие между методами SetRotation и AddRotation в том, что AddRotation поворачивает объект только один раз, а SetRotation – заставляет его поворачиваться на указанный угол при каждом следующей итерации (with every render tick) Потом создается т.н. MeshBuilder – специальный объект, инкапсулирующий методы для добавления к нему граней. Этот обьект может быть загружен из файла (и естественно сохранен в файл). По традиции файлы имеют расширение X. (насколько мне извесно эта традиция возникла еще до появления сериала X-Files :-)) В самом же деле – в конце 20 века задавать координаты каждого треугольника вручную… Можно заставит сделать это кого то еще – а потом просто загрузить готовый файл :-). Ну а если серьезно в DirectX SDK входит специальная утилита - conv3ds. {conv3ds converts 3ds models produced by Autodesk 3D Studio and other modelling packages into X Files. } Однако создадим объект вручную – ну их эти Х-файлы. Наш объект будет состоять из 4-х граней (ни одного трехмерного тела с меньшим количеством граней я не смог придумать). Естественно каждая грань – треугольник, имеющий свой цвет.
MeshBuilder.Scale(3, 3, 3); - Увеличиваем в три раза по всем координатам.Наконец MeshFrame.AddVisual(MeshBuilder); - наш MeshBuilder готов, присоединяем его как визуальный объект к видимому объекту Mesh.
DXDraw.Scene.SetSceneBackgroundRGB(0,0.7,0.7); -Как понятно из названия метода цвет фона. (Видите – я не врал RGB-цвет действительно задается числами с плавающей точкой :-)) Интересные дела творятся в методе TMainForm.DXTimerTimer. (небольшая тонкость – это не обычный таймер, а DXTimer из библиотеки DelphiX)
DXDraw.Viewport.ForceUpdate(0, 0, DXDraw.SurfaceWidth, DXDraw.SurfaceHeight);указываем область, которую нужно обновить (не мудрствуя лукаво – весь DXDraw.Surface)
DXDraw.Scene.Move(1.0);- применяем все трехмерные преобразования, добавленные методами вроде AddRotation и SetRotation к нашей сцене. (вот где собака то порылась… :-) вычисления новых координат точек начнутся не сразу после метода AddRotation а только здесь) DXDraw.Render – Рендерим (ну как же это по русски то? :-)) DXDraw.Flip – выводим результат рендеринга на экран (аминь :-)); (в этом методе помещены также несколько строк, выводящих на экран число кадров в секунду и информацию о поддержке Direct3D аппаратурой или программно – пригодится при отладке) Метод FormKeyDown. Здесь проверяется код нажатой клавиши – если Alt+Enter – переходим из оконного в полноэкранный режим (клево, правда? :-)) и наоборот. Напоследок пара слов о DXDrawClick. Просто выводим FileOpenDialog – Вы можете поэкспериментировать с x-файлами. Пока все. Вот уж не думал, что это будет так трудно. Надеюсь хоть кто-то дочитал до этого места.
Пишите: aziz@telebot.net, error@softhome.net.
DirectX и Delphi - введение.
Автор: Азиз (JINX) Специально для Королевства Delphi Как обычно, начну с оговорок. Первое – для меня большая проблема перевести некоторые термины. Поэтому я думаю может и не стоит их переводить. :-) Вот список терминов, которые вызывают у меня трудности с переводом: blitting - blit сокращение от "bit block transfer" пересылка блоков данных из одной области видеопамяти в другую. flip – переключение между буферами видеопамяти Surface – "поверхность" – область видеопамяти Второе – разговор идет о использовании DirectDraw в Delphi. Для того, чтобы воспользоваться DirectX вообще и DirectDraw в частности, нужно, чтобы во-первых DirectX был установлен на компьютере (скачать его можно у Microsoft например, впрочем я не думаю, что для читателя будет проблемой его найти), во-вторых нужны файлы заголовков DirectX – их существует немало, я по-прежднему считаю компонент DelphiX от Hiroyuki Hori – превосходным , кроме того, существует официально поддерживаемые Воrlаnd'ом заголовки DirectX, составленные в рамках проекта "JEDI" – скачать их можно с (http://www.delphi-jedi.org/DelphiGraphics/). Третье – неплохо если Вы имеете некоторое общее представление о работе видеоадаптера (ну очень общее – тонкости не нужны) и еще более общее о COM-технологии (всего-то нужно знать что такое COM- Interface, впрочем и это не обязательно). DirectDraw – интерфейс DirectX, предназначенный, по существу, для управления видеопамятью.Прелесть однако заключается в том, что с DirectDraw доступ к видеопамяти становится не зависимым от типа используемой видеоплаты (ну или почти не зависимым). DirectDraw обращается к апаратуре посредством hardware abstraction layer (HAL) – (уровня абстагирования от аппаратуры). Кроме того с помощью hardware emulation layer (HEL) (уровня эмуляции аппаратуры) те возможности, которые не реализованы в данной видеокарте эмулируются программно (к сожалению тут есть пара исключений). Благодаря такому подходу жизнь программиста становится легче и веселее – если, например, видеокарта поддерживает hardware blitting – DirectDraw использует эту возможность через HAL – если нет – эмулирует через HEL (естественно эмуляция всегда медленнее). На рисунке из SDK показаны отношения между DirectDraw, GDI, HAL и HEL.

Выясним все поддерживаемые видеорежимы {используем DirectX headers от JEDI}.
function MyEnumFunction(const lpDDSurfaceDesc: TDDSurfaceDesc; lpContext:
Pointer): HResult; stdcall
var
SMode: string;
begin
SMode := IntToStr(lpDDSurfaceDesc.dwWidth) + ' X ';
SMode := SMode + IntToStr(lpDDSurfaceDesc.dwHeight) + ' X ';
SMode := SMode + IntToStr(lpDDSurfaceDesc.ddpfPixelFormat.dwRGBBitCount);
Form1.ListBox1.Items.Append(SMode);
end;
procedure TForm1.Button2Click(Sender: TObject);
var
DD: IDirectDraw;
hr: HRESULT;
begin
hr := DirectDrawCreate(nil, DD, nil);
if (hr = DD_OK) then
begin
ListBox1.Clear;
DD.EnumDisplayModes(0, nil, nil, MyEnumFunction);
end;
end;
|
{то же используя компонент DelphiX}.
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
SMode: string;
begin
ListBox1.Clear;
for i := 0 to DXDraw1.Display.Count - 1 do
begin
SMode := IntToStr(DXDraw1.Display.Modes[i].Width) + ' X ';
SMode := SMode + IntToStr(DXDraw1.Display.Modes[i].Height) + ' X ';
SMode := SMode + IntToStr(DXDraw1.Display.Modes[i].BitCount);
ListBox1.Items.Append(SMode);
end;
end;
|
Установим видеорежим 640x480x8 {используем DirectX headers от JEDI}.
procedure TForm1.Button1Click(Sender: TObject);
var
DD: IDirectDraw;
DD4: IDirectDraw4;
hr: HRESULT;
begin
hr := DirectDrawCreate(nil, DD, nil);
if (hr = DD_OK) then
begin
DD.QueryInterface(IID_IDirectDraw4, DD4);
DD4.SetCooperativeLevel(Self.Handle, DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN);
DD4.SetDisplayMode(640, 480, 8, 0, 0);
//DD4.RestoreDisplayMode;
end;
end;
|
{то же используя компонент DelphiX}.
procedure TForm1.Button1Click(Sender: TObject); begin DXDraw1.Display.Width := 640; DXDraw1.Display.Height := 480; DXDraw1.Display.BitCount := 8; DXDraw1.Options := DXDraw1.Options + [doFullScreen]; DXDraw1.Initialize; end; |
Видите – справа от нашего Front-bufera может быть какой-то кэш, если Вы вздумаете писать напрямую в видеопамять – писать туда (в кэш) строго не рекомендуется (за последствия никто не ручается). Кроме того Pitch, в отличие от ширины измеряется в байтах а не в пикселах.
Раз уж заговорили, про Front-bufеr'ы – скажем уж и про то, что один Surface, называемый PrimarySurface, является главным - это тот самый Surface, который был виден на экране в момент когда мы начали создавать эти самые Surfасе'ы.
Surfасе'ы могут быть обьединены в так называемые flip-цепочки. Когда происходит flip между Surfасе'ами – тот Surface, что сейчас на экране, заменяется следующим в цепочке, на следующем fliр'е – этот – следующим и т.д. – если дошли до последнего в цепочке – то он заменяется на первый. Ну в обычной жизни цепочка может состоять из всего двух Surface' ов – при каждом они просто fliр'е сменяют друг друга. Обратите внимание – при fliр'е смена Surfасе'ов происходит не в результате пересылки всего их содержимого, а просто в результате изменения указателей на области видеопамяти в видеоадаптере – поэтому flip выполняется очень быстро. (Исключение может быть только в случае если Вы создали столько Surfасе'ов, что они не поместились в видеопамяти – тогда за дело возьмется HEL – бедняге придется все это эмулировать и скорость будет – не ахти). C помощью flip можно создавать анимацию, выводим какую-то картинку, затем в BackBuffer'e – чуть-чуть меняем эту картинку, вызываем flip, чуть-чуть меняем картинку в BackBuffer'e, вызываем flip и т.д. в цикле.
Вот пример создания Flip-цепочки из двух Surfасе'ов, обьектов IDirectDrawSurface4.
(Ссылки на два созданных Surfасе'а сохраняются в переменных FPrimarySurface и FbackSurface) (этот пример взят из моей демо-программульки, которую Вы может скачать здесь 169K) {используются JEDI – заголовки DirectX}.
uses ... DDraw;
var
hr: HRESULT;
SurfaceDesc: TDDSurfaceDesc2;
DDSCaps: TDDSCAPS2;
DD: IDirectDraw;
begin
/// ...начнем, помолясь
hr := DirectDrawCreate(nil, DD, nil); ///создали DirectDraw
if (hr = DD_OK) then
begin
// Получим интерфейс IDirectDraw4
DD.QueryInterface(IID_IDirectDraw4, FDirectDraw);
// интерфейс DirecDraw1 нам больше не нужен
DD := nil;
// Установим эксклюзивный уровень кооперации и полноэкранный режим
hr := FDirectDraw.SetCooperativeLevel(Handle, DDSCL_EXCLUSIVE or
DDSCL_FULLSCREEN);
if (hr = DD_OK) then
begin
hr := FDirectDraw.SetDisplayMode(640, 480, 8, 0, 0);
///переключаем видеорежим на 640X480X8
if (hr = DD_OK) then
begin
// Создаем главный surface с одним back buffеr'ом
FillChar(SurfaceDesc, SizeOf(SurfaceDesc), 0);
SurfaceDesc.dwSize := SizeOf(SurfaceDesc);
///говорим что нам нужны back buffеr'ы
SurfaceDesc.dwFlags := DDSD_CAPS or DDSD_BACKBUFFERCOUNT;
////говорим что создаем первый Surface
SurfaceDesc.ddsCaps.dwCaps := DDSCAPS_PRIMARYSURFACE
or DDSCAPS_FLIP //// во Flip-цепочке
or DDSCAPS_COMPLEX; //// а вообще будут и дополнительные Surfасе'ы
//// число этих самых дополнительных - 1
SurfaceDesc.dwBackBufferCount := 1;
///все готово, создаем Surfасе'ы и запоминаем главный в FPrimarySurface
hr := FDirectDraw.CreateSurface(SurfaceDesc, FPrimarySurface, nil);
if (hr = DD_OK) then
begin
// А теперь получим указатель на back buffer (создали-то два Surface'a сразу)
ddscaps.dwCaps := DDSCAPS_BACKBUFFER;
///получили и запомнили в FBackSurface
hr := FPrimarySurface.GetAttachedSurface(ddscaps, FBackSurface);
if (hr = DD_OK) then
begin
{Все нормально - Surfасе'ы созданны - выходим}
exit;
end;
end;
end;
end;
end;
{где-то была ошибка - сообщаем об этом неприятном факте}
MessageBox(Self.Handle, PChar('Не удалось инициализировать DirectDraw! ' +
ErrorString(Hr)), 'ERROR', MB_OK);
Close();
end;
|
С фантазией у меня всегда было не очень – поэтому просто заполню всю область Surfасе'ов одним цветом, записав в видеопамять одно и тоже значение.
var
i, j: integer;
AdresVideo: PByteArray;
SurfaceDesc: TDDSURFACEDESC2;
HR: HResult;
begin
// Пишем прямо в видеопамять
FillChar(SurfaceDesc, SizeOf(SurfaceDesc), 0);
SurfaceDesc.dwSize := SizeOf(SurfaceDesc);
HR := FPrimarySurface.Lock(nil, SurfaceDesc, {DDLOCK_WAIT or}
DDLOCK_SURFACEMEMORYPTR, 0);
if (HR = DD_OK) then
begin
AdresVideo := SurfaceDesc.lpSurface;
for i := 0 to SurfaceDesc.dwHeight - 1 do
begin
for j := 0 to SurfaceDesc.dwWidth - 1 do
begin
AdresVideo[j + i * SurfaceDesc.lPitch] := $FF;
end;
end;
FPrimarySurface.Unlock(nil);
end;
end;
|
Обратите внимание - какой я аккуратный – перехожу между строчками, учитывая Pitch. Да кстати – я просто демонстрирую как обратится к каждому байту видеопамяти Surface'a на самом деле если нужно закрасить весь Surface одним цветом то заносить значения в каждый байт слишком медленно – для этого можно воспользоваться методом IDirectDrawSurface4.Blt, передав ему флаг DDBLT_COLORFILL. Кроме того можно выводить на Surface и привычными функциями GDI – ТехtОut'ом например:
var
DC: HDC;
begin
if (FPrimarySurface.GetDC(DC) = DD_OK) then
begin
{Выводим текст на 1-й surface, используя GDI-фуекцию TextOut}
SetBkColor(DC, RGB(255, 255, 255));
SetTextColor(DC, RGB(255, 0, 0));
TextOut(DC, 10, 20, ' Проба пера', Length(' Проба пера'));
FPrimarySurface.ReleaseDC(DC);
end;
end;
|
Теперь попробуем fliр'ануть наши Surfасе'ы. Переключимся на другой Surface.
hr := FPrimarySurface.Flip(nil, 0);DDERR_NOEXCLUSIVEMODE – этот код возврата означает, что наша программа потеряла эксклюзивный режим. Произойти такое может, если мы fliр'уем в цикле по таймеру, а пользователь зачем-то ушел из нашей программы, свернув ее или нажав Alt-TAB. В таком случае, чтобы зря не тратить процессорные циклы, лучше подождать его возвращения, вызывая функцию Sleep(0) или WaitMessage. DDERR_SURFACELOST – потеря Surfасе'ов пользователь уходил, но вернулся, Surfасе'ы нужно забрать назад, вызвав IDirectDraw4.RestoreAllSurfaces, содержимое их придется восстановить. Все вышесказанное касается классического стиля использования DirectDraw в стиле С от JEDI. Поклонники же Ноri'вского набора DelphiX могут поэкспериментировать c Surfасе'ами используя TDXDraw.DDraw.SurfaceCount, TDXDraw.DDraw.Surfaces, TDXDraw.Flip – вместе с набором компонент распространяются отличные примеры. Я очень рад, что Вы дочитали до этого места (если Вы просто пролистали в конец, не читая, сделайте вид, что это не так, порадуйте меня, старика) :-). На этом пока все. Если Вы заинтересовались – скачайте демо-программку и поэкспериментируйте.
Пишите мне – aziz@telebot.com или error@softhome.net – с удовольствием приму Ваши пожелания и предложения (особенно если предложите какую-нибудь работу) :-).
Содержание.
- Как определить изменение фокуса строки в TDBGrid.
- Определить номер строки в TDBGrid.
- DBGrid - сохранение конфигурации.
- DBGrid - выбранные строки.
- Выделить все строки в DBGrid.
- Как выделить окошко DBGrid другим цветом.
- Решение проблемы передачи фокуса TDBGrid.
- Сортировка колонок в DBGrid.
- Копирование информации из DBGrid-а в Clipboard.
- Как из DBGrid перенести данные в существующий Excel файл.
- Экспорт из TDBGrid в Excel без OLE.
- Экспортировать DBGrid в HTML.
- Перемещение по таблице с помощью вертикальной полосы прокрутки.
- Корректная работа при работе с колесиком мышки в TDBGrid.
- DBGrid компонент, который отображает разными цветами удалённые, обновлённые и добавленные записи.
- DBGrid c FixedCols.
- DBGrid с номером строки.
- Синтаксис функции DbiAddAlias.
- Пример DbiAddFilter.
- Как поместить картинку из базы данных, например MsSQL, в компонент TIMAGE.
- Значение DBLookupComboBox.
- Обновить DBLookUpComboBox при скролинге таблицы.
- Показываемое DBLookupComboBox значение.
- Поиск текста в DBMemo.
- Выводить текст с помощью DBMS_OUTPUT.PUT_LINE в режиме отладки.
- Выключение кнопок в DBNavigator.
- Свойства кнопок DBNavigator.
- Вызов кнопок DBNavigator.
- Настройки всплывающих подсказок в DBNavigator.
- DBNavigator без иконок.
- DBTreeView своими руками.
- Как можно открыть отчёт (в режиме Print Preview а также Print Direct) в MS Access.
- Как в Delphi сбросить кэш БД на диск.
- История изменений объектов.
- Создание компонентов для работы с базами данных.
- Использование DB Controls без базы данных.
- Как создать БД в кодировке CP1251.
- Курсоры в ADO.
- Часто задаваемые вопросы по базам данных.
- Запуск файлов хрянящихся в БД.
- Отобразить определенного формата файлы базы данных.
- Как узнать содержание активной записи в БД.
- Как активизировать компоненты доступа к данным связанные с определенным TDatabase.
- Быстрый доступ к нужной записи в таблице Paradox.
- Контекстное меню на основе базы данных.
- БД-дерево взаимоотношений.
- Сортировка связанного списка.
- Структура DCB.
- Использование компилятора Delphi (dcc32.exe) в прикладных программах.
- В чем разница между сокетами, DCOM и OLE Enterprise при использовании их в качестве транспорта.
- DCOM permissions.
- Как можно работать с DDE под Delphi, используя вызовы API.
- DDE - передача текста.
- DDE для захвата текущего URL из окна Internet Explorer или Netscape Navigator.
- DDE.
- Регистрация программ в меню Пуск Windows95.
- DDE для вызова диалога поиска файлов и папок.
- Пример DDE и WordPerfect.
- Работа с Netscape Navigator через DDE.
- Деактивация приложения.
- Набор данных для отладки.
- В режиме отладки приложения не разрешается открытие базы данных.
- Крах отладчика при передаче параметров.
- Определение и использование курсора.
- Определение присутствия функции в DLL.
- Декларация события OnPassword.
- Декомпиляция в Delphi.
- Декомпилляция звукового файла формата Wave и получение звуковых данных.
- Разукрасьте Ваше приложение (Skins).
- Убывающий индекс.
- Как преобразовать числовое значение в римскую интерпретацию.
- Определить SMTP сервер по умолчанию.
- Как узнать язык Windows по умолчанию.
- Демонстрация DefineProperties.
- Код определения свойств.
- Получить путь к браузеру по умолчанию.
- Библиотека DegisyDb.
- Процедура задержки выполнения.
- Реализация функции Delay.
- Реализация функции Delay 2.
- Реализация функции Delay 3.
- Реализация функции Delay 4.
- Реализация функции Delay 5.
- Как удалить BDE таблицу.
- Определение удаления записей в .DBF.
- Удаление Debug information.
- Удалить каталог со всем содержимым.
- Удалить каталог со всем содержимым 2.
- Удалить каталог со всем содержимым 3.
- Удалить каталог со всем содержимым 4.
- Удаление из файла элементов HTML.
- Как удалить переносы из строки.
- Ошибка отключения сетевого диска.
- Удалить ОЗУ.
- Стандартный запрос на удаление записи в таблице.
- Удаление ненужных подстрок из строки.
- Как удалить вертикальную полосу прокрутки (скроллбар) из DBGrid.
- Удаление большого количества записей.
- Удалить закладку в Word.
- DirectX для начинающих.
- Обработка событий клавиш в DelphiX.
- Проигрывание звука, передвижение картинок и проверка их столкновения в DelphiX.
- Delphi и 1C - экспорт и импорт.
- Что Delphi знает об ассемблере.
- Delphi и CorelDraw.
- Delphi и OLE Automation с Excel.
- Delphi и Flash. Совмещение несовместимого.
- Просто и ясно о MapInfo и Delphi - Вызов MapInfo и встраивание его в свою программу (основы интегрированной картографии).
- Просто и ясно о MapInfo и Delphi - Реализация CallBack вызовов MapInfo и перехват в собственной программе.
- Просто и ясно о MapInfo и Delphi - Настройка панелей и меню. Реализация собственных CallBack вызовов и обработка поступивших данных.
- Delphi и Oracle - вопросы и ответы.
- Delphi и системная информация о ресурсах компьютера.
- Delphi и WordBasic.
- Delphi и OLE Automation с Word.
- Как научить приложение Delphi разговаривать.
- Массив в Delphi.
- Программирование АССЕМБЛЕРОМ в Delphi.
- Delphi и COM.
- Как сделать окошко подсказки в редакторе как Delphi по CTRL-J.
- Простой шаблон для Delphi DLL.
- Delphi for DotNet – первый взгляд.
- Обработка почты с помощью Delphi.
- Исключения в Delphi.
- Delphi для качков.
- Фреймы в Delphi.
- Delphi и графика.
- Использование открытых интерфейсов среды программирования Delphi.
- Запуск Delphi с секретными параметрами.
- Сервисы в Delphi на чистом API.
- Программирование сокетов в Delphi.
- Добавление функций проверки орфографии в разрабатываемые приложения.
- SQL-запросы в Delphi.
- Получение данных из Delphi-приложения в документе Word.
- Как удалить файл после перезагрузки Windows.
- Как удалить файл после перезагрузки Windows 2.
- Как удалить все файлы из Recent Documents List.
- Удалить все выделенные элементы списка.
- Удаление файла в корзину.
- Обрезание имени файла по длине.
- Удаление непустого каталога вместе с подкаталогами.
- Удалить кнопки сворачивания и максимизации с формы.
- Как удалить одно значение из динамического массива.
- Удалить свою программу после ее завершения.
- Удалить из директории проекта лишние файлы.
- Удалить пункт системного меню.
- Производная TIntegerField.
- Производная TOutline.
- Проектирование баз данных.
- Свойство только для чтения во время выполнения программы.
- Как запретить изменение размера моего компонента в design-time.
- Как установить цвет фона иконок на рабочем столе, либо сделать у них прозрачный фон.
- Получить цвет пикселя на рабочем столе.
- Как определить размер рабочего стола без ТаsкВага.
- Снимок Desktop.
- Когда я добавляю обьект в список TStrings как мне его потом уничтожить.
- Криптоалгоритм DES.
- Алгоритм шифрования DES.
- Определение кодовой страницы.
- Как определить видеокарту.
- Разработка серверных Web-приложений на Delphi.
- Перевести файл формы DFM в текстовый и наоборот.
- Вызов диалога подключения сетевого диска.
- Заголовок диалогового окна.
- Вызывает диалог выбора иконки.
- Диалог-компонент с кнопками Да, Нет, Отмена.
- Вызов диалога отключения сетевого диска.
- Вызывает диалог открытия файла.
- Функция вызывает стандартный диалог Свойства.
- Z-порядок при каждом показе диалога.
- Размер диалогового окна.
- Безопастность Dial-up пользователям.
- Набрать телефонный номер.
- Что нужно предусмотреть при разработке приложения, которое будет работать при различном разрешении дисплея.
- Сделать сложный фон окна.
- У меня константы могут иметь значение, отличное от заданного.
- Как сделать, чтобы орган управления, например, сложная линия, хваталась только за некий контур, и пропускала мышь под себя в других местах.
- Простой Взлом - AriSoft DirectFTP v1.0.
- Использование PASSTHROUGH для отправки данных прямо на принтер.
- DirectX и Delphi.
- DirectX и Delphi - введение.