[Оглавление]   [1]  [2]  [3]  [4]  [5]  [6[7]


Windows API & Delphi VCL FAQ

Часть 6


Q: Как правильно в Win32 отслеживать запуск второй копии программы?
A: FindWindow является неполным решением (если меняется заголовок окна или если есть другая программа с таким же заголовком или типом окна). Вторично: медленно.

Лениво пользовать семафоры, покажу на именованных мутексах (семафоры с двумя состояниями).

Unit OneInstance32;

interface

implementation

uses
 Forms;

var
 g_hAppMutex: THandle;

function OneInstance: boolean;
var
 g_hAppCritSecMutex: THandle;
 dw: Longint;
begin
g_hAppCritSecMutex := CreateMutex( nil, true, PChar(Application.Title +
'.OneInstance32.CriticalSection') );

// if GetLastError - лениво писать

g_hAppMutex := CreateMutex( nil, false, PChar(Application.Title +
'OneInstance32.Default') );

dw := WaitForSingleObject( g_hAppMutex, 0 );

Result :=  (dw <> WAIT_TIMEOUT);

ReleaseMutex( g_hAppCritSecMutex ); // необязательно вследствие последующего закрытия
CloseHandle( g_hAppCritSecMutex );

end;

initialization

g_hAppMutex := 0;

finalization

if LongBool( g_hAppMutex ) then
begin
 ReleaseMutex( g_hAppMutex); // необязательно
 CloseHandle( g_hAppMutex );
end;

end.


Akzhan Abdulin
(2:5040/55).

Q: Как из программы без особых усилий открыть некий URL или отправить кому-либо по электронной почте письмо?
A:
ShellExecute("mailto:writer@coolware.com");
ShellExecute("http://coolware.com");


Sergey Okhapkin
(2:5020/50).

Q: Как сделать, чтобы орган управления - сложная линия хваталась только за линию и пропускала мышь под себя в других местах?
A: Надо CM_HITTEST обpабатывать (Это сообщение получают даже потомки от TGraphicsControl, не имеющего своего HWND). Hапpимеp, так:
procedure TLine.CMHitTest(var Message: TWMNCHitTest);
begin
  if  PointInLineReg(Message.XPos, Message.YPos) then
     Message.Result:=1 else
     Message.Result:=0;
end;


Dmitry Medved
(2:464/58.7)

Q: Как исправить ошибку, возникающую при попытке печатать из RichEdit под Windows NT?
A: сходил на http://www.borland.com и -
unit PrtRichU;
interface
uses SysUtils, Windows, Classes, ComCtrls, RichEdit, Printers;
procedure PrintRichEdit(const Caption: string;
                        const RichEdt: TRichEdit);
implementation
procedure PrintRichEdit(const Caption: string;
                        const RichEdt: TRichEdit);
var
  Range: TFormatRange;
  LastChar, MaxLen, LogX, LogY, OldMap: Integer;
begin
  FillChar(Range, SizeOf(TFormatRange), 0);
  with Printer, Range do
  begin
    BeginDoc;
    hdc := Handle;
    hdcTarget := hdc;
    LogX := GetDeviceCaps(Handle, LOGPIXELSX);
    LogY := GetDeviceCaps(Handle, LOGPIXELSY);
    if IsRectEmpty(RichEdt.PageRect) then
    begin
      rc.right := PageWidth * 1440 div LogX;
      rc.bottom := PageHeight * 1440 div LogY;
    end
    else begin
      rc.left := RichEdt.PageRect.Left * 1440 div LogX;
      rc.top := RichEdt.PageRect.Top * 1440 div LogY;
      rc.right := RichEdt.PageRect.Right * 1440 div LogX;
      rc.bottom := RichEdt.PageRect.Bottom * 1440 div LogY;
    end;
    rcPage := rc;
    Title := Caption;
    LastChar := 0;
    MaxLen := RichEdt.GetTextLen;
    chrg.cpMax := -1;
    OldMap := SetMapMode(hdc, MM_TEXT);
    SendMessage(RichEdt.Handle, EM_FORMATRANGE, 0, 0);
    try repeat
      chrg.cpMin := LastChar;
      LastChar := SendMessage(RichEdt.Handle, EM_FORMATRANGE, 1,
Longint(@Range));
      if (LastChar < MaxLen) and (LastChar <> -1) then NewPage;
    until (LastChar >= MaxLen) or (LastChar = -1);
    EndDoc;
    finally
      SendMessage(RichEdt.Handle, EM_FORMATRANGE, 0, 0);
      SetMapMode(hdc, OldMap);
    end;
  end;
end;
end.
и главное печатает.

Igor Nechaev
igornet@imedia.ru.

Q: Как отследить изменение файловой системы и/или реестра ОС?
A: Отслеживание файловой системы через FindFirstFileNotification и прочие.
Отслеживание реестра ОС - RegNotifyChangeKeyValue (только для NT).

Alexey Mahotkin
(2:5020/433)


Dmitry V'yal
(2:450/110.11).

Q: Как быстро нарисовать тень в заданном регионе?
A:
procedure TForm2.DrawShadows(WDepth, HDepth : Integer);
var
  Dst, RgnBox  : TRect;
  hOldDC         : HDC;
  OffScreen      : TBitmap;
  Pattern          : TBitmap;
  Bits               : array[0..7] of WORD;
begin
  Bits[0]:=$0055;
  Bits[1]:=$00aa;
  Bits[2]:=$0055;
  Bits[3]:=$00aa;
  Bits[4]:=$0055;
  Bits[5]:=$00aa;
  Bits[6]:=$0055;
  Bits[7]:=$00aa;

  hOldDC:=Canvas.Handle;
  Canvas.Handle:=GetWindowDC(Form1.Handle);

  OffsetRgn(ShadeRgn, WDepth, HDepth);
  GetRgnBox(ShadeRgn, RgnBox);

  Pattern:=TBitmap.Create;
  Pattern.ReleaseHandle;
  Pattern.Handle:=CreateBitmap(8, 8, 1, 1, @(Bits[0]));
  Canvas.Brush.Bitmap:=Pattern;

  OffScreen:=TBitmap.Create;
  OffScreen.Width:=RgnBox.Right-RgnBox.Left;
  OffScreen.Height:=RgnBox.Bottom-RgnBox.Top;
  Dst:=Rect(0, 0, OffScreen.Width, OffScreen.Height);

  OffsetRgn(ShadeRgn, 0, -RgnBox.Top);
  FillRgn(OffScreen.Canvas.Handle, ShadeRgn, Canvas.Brush.Handle);
  OffsetRgn(ShadeRgn, 0, RgnBox.Top);

//  BitBlt работает быстрее CopyRect
  BitBlt(OffScreen.Canvas.Handle, 0, 0, OffScreen.Width, OffScreen.Height,
         Canvas.Handle, RgnBox.Left, RgnBox.Top, SRCAND);

  Canvas.Brush.Color:=clBlack;
  FillRgn(Canvas.Handle, ShadeRgn, Canvas.Brush.Handle);

  BitBlt(Canvas.Handle, RgnBox.Left, RgnBox.Top, OffScreen.Width,
   OffScreen.Height, OffScreen.Canvas.Handle, 0, 0, SRCPAINT);

  OffScreen.Free;
  Pattern.Free;
  OffsetRgn(ShadeRgn, -WDepth, -HDepth);

  ReleaseDC(Form1.Handle, Canvas.Handle);
  Canvas.Handle:=hOldDC;
end;
Комментарии :
Функция рисует тень сложной формы на форме Form2 (извиняюсь за стиль). Для определения формы тени используется регион ShadeRgn, который был создан где-то раньше (например в OnCreate). Относительно регионов см. Win32 API. Если что-то непонятно, пишите мне лично.

Титов Игорь Евгеньевич
infos@obninsk.ru.

Q: Как сделать MDI-приложение, в котором способны сливаться не только меню дочернего и главного окна, но и полосы инструментов?
A:

Jury Martynov
(2:5020/800.21).

Q: Чем отличается тип String в Delphi 2 и выше от аналогичного в Delphi 1?
A: B D2/D3 на самом деле используется тип LongString вместо String, а стаpый тип тепеpь обзывается ShortString (о чем, кстати, написано в help). Из того же help можно узнать, что указатель LongString указывает на nullterminated string и потому возможно обычное пpиведение типа LongString к PChar (о чем я и написал), котоpое сводится пpосто к смене вывески. Там же можно узнать, что длина стpоки хpанится в dword пеpед указателем. Есть также намек на то, что пpи пpисваивании дpугой стpоке инфоpмация не копиpуется, а увеличивается только счетчик ссылок. Более подpобную инфоpмацию можно почеpпнуть из system.pas:
type
    StrRec = record
        allocSiz:       Longint;
        refCnt: Longint;
        length: Longint;
    end;
От себя добавлю:
Сама пеpеменная LongString указывает на байт, непосpедственно следующий за этой пpоцедуpой, там же находится собственно значение стpоки. Значение '' (пустая стpока) пpедставляется как указатель nil, кстати, поэтому сpавнение str='' это быстpая опеpация.

Тепеpь подpобнее о счетчике ссылок. Я уже говоpил, что пpи пpисваивании копиpования не пpоисходит, а только увеличивается счетчик. Когда он уменьшается? Hу, очевидно, когда в pезультате опеpации значение стpоки меняется, то для стаpого значения счетчик уменьшается. Это понятно. Более непонятно, когда освобождаются значения, на котоpые ссылаются поля некого класса. Это пpоисходит в System.TObject.FreeInstance пpи вызове _FinalizeRecord, а инфоpмация беpется из vtInitTable (кстати, здесь же очищаются Variant). Еще более непонятно, когда освобождаются пеpеменые String, котоpые описаны как локальные в пpоцедуpах/функциях/методах. Здесь pаботает компилятоp, котоpые вставляет эти неявные опеpации в код этой функции.

Тепеpь о типе PString. Hа самом деле пеpеменные этого типа указывают на такие же значения, как и LongString, но для пеpеменных этого типа для всех опеpаций по созданию/копиpованию/удалению нужно помнить об этих самых счетчиках ссылок. Иногда без этого типа не обойтись. Вот опеpации для этого типа (sysutils.pas):

{ String handling routines }

{ NewStr allocates a string on the heap. NewStr is provided for backwards
compatibility only. }
function NewStr(const S: string): PString;

{ DisposeStr disposes a string pointer that was previously allocated using
  NewStr. DisposeStr is provided for backwards compatibility only. }
procedure DisposeStr(P: PString);

{ AssignStr assigns a new dynamically allocated string to the given string
  pointer. AssignStr is provided for backwards compatibility only. }
procedure AssignStr(var P: PString; const S: string);


Alex Konshin
2:5030/217.217

Можно отметить, что: явно задать использование long strings можно декларацией

var
 sMyLongString: AnsiString; // long dinamically allocated string
 sMyWideString: WideString; // wide string (UNICODE)
 sMyShortString1: ShortString; // old-style string
 sMyShortString2: String[255]; // old-style string, no more than 255 chars

Q: Вот всю жизнь в TVision в итераторах нужно было (параметром) передавать указатель на локальную процедуру, а тут задумал сделать свой итератор для обхода некоей древовидной структуры и на тебе - компилятор ругается. Да еще и в хелпе носом тыкают, что так мол в принципе нельзя делать... Гм. И как быть?
A: Конкретно по поводу локальных процедур - если нельзя, но очень хочется - то можно. Я недавно искал способ. Как водится, сначала придумал свой, а потом мне показали в исходниках VCL. Hо (как водится) мой красивее. Лови:

(c) Max Rusov. All rights reserved:

  function LocalAddr(Proc :Pointer) :TMethod; assembler;
  asm
    mov Result.Data, EBP
    mov Result.Code, Proc
  end;


  function TMyList.ForEach(Proc :TMethod) :Integer;
  type
    EnumProc = procedure(Index :Integer; Item :Pointer; var More :Boolean);
  var
    I    :Integer;
    More :Boolean;
    Tmp  :Pointer;
  begin
    Result := -1;
    More   := True;

    for I := 0 to Count - 1 do begin

      {Вызываем локальную процедуру...}
      Tmp := Proc.Data; asm push Tmp end;
      EnumProc(Proc.Code)(I, List^[I], More);
      asm pop ECX end;

      if not More then begin
        Result := I;
        Exit;
      end;
    end;

  end;
В принципе, здесь можно без Tmp - сразу Push Proc.Data. о иногда - в других enumertor'ах кодогенератор глючит. Так что, для надежности...

Использование:

  function Present(AList :TList; AItem :Pointer) :Boolean;

    procedure Compare(Index :Integer; Item :Pointer; var More :Boolean);
    begin
      More := Item <> AItem;
    end;

  begin
    Result := AList.ForEach(LocalAddr(@Compare)) <> -1;
  end;
(Для тех кто в танке: Это пример, IndexOf не предлагать!)

Max Rusov
(2:5030/456.1).

Q: Как получить имя папки pабочего стола (не чеpез registry). Пpосто очень хочется поpаботать с shell functions.
A: Вот как:!
procedure TForm1.Button1Click(Sender: TObject);
procedure madd(s:string);
begin
 memo1.lines.add(s);
end;
VAR
ppmalloc:imalloc;
id:ishellfolder;
pi:pitemidlist;
lpname:tstrret;
begin
if succeeded(shgetspecialfolderlocation(0,CSIDL_PROGRAMS,pi)) then  <<<<<<<
 begin
  madd('Succeeded programs location');
  if succeeded(shgetdesktopfolder(id)) then
   begin
    madd('Succeeded get desktop folder');
     if succeeded(id.getdisplaynameof(pi,0,lpname)) then
      begin
       madd('Succeeded get display name');
       if lpname.uType=2 then madd(lpname.cstr);
       end;
      end
      else
       madd('UnSucceeded get display name');
   end
  else
  madd('UnSucceeded get desktop folder');
 end
 else
   madd('UNSucceeded programs location');
end;


Denis Tanayev
denis@demo.ru.

Q: Как рисовать на органе управления, например, на TPanel?
A: У всех компонентов, порожденных от TCustomControl, имеется свойство Canvas типа TCanvas.

Грубо говоря, это аналог TDC из OWL. Те операции, которые нельзя выполнить с помощью методов TCanvas, можно выполнить с помощью WinAPI. Для этого у обьектов класса TCanvas имеется свойство Handle - это и есть Хэндл Дисплейного Контекста ОС Windows (HDC), который необходим графическим функциям WinAPI.

Если свойство Canvas недоступно, Вы можете достучаться до него созданием потомка и переносом этого свойства в раздел Public.

{ Example. We recommend You to create this component through Component Wizard.
In Delphi 1 it can be found as 'File|New Component...', and can be found
as 'Component|New Component...' in Delphi 2 or above. }
type
 TcPanel = class(TPanel)
  public
  property Canvas;
 end;


Akzhan Abdulin
(2:5040/55)

У меня есть маленькое замечание.

Если у объекта нет свойства Canvas (у TDBEdit, вpоде-бы нет), по кpайней меpе в D3 можно использовать класс TControlCanvas. Пpимеpное использование:

var cc: TControlCanvas;
...
cc := TControlCanvas.Create;
cc.Control := youControl;
...
и далее как обычно можно использовать методы Canvas.

Andrew Velikoredchanin
(2:5026/29.3).

Q: Как узнать текущее разрешение экрана?
A: Советуем ознакомиться с Help topic относительно глобального обьекта Screen типа TScreen. У этого обьекта есть свойства Width и Height.
{ Example }
begin
 iScreenWidth := Screen.Width;
end;
Заодно и другие, например, Fonts и Cursors.
Q: Как правильно создавать органы управления в runtime?
A: Примерно таким образом (Описываем метод-обработчик события OnClick формы):
{ Example }
procedure TForm1.OnClick(ASender: TObject);
var
 btnTemp: TButton;
begin
 { Creating }
 btnTemp := TButton.Create(Self);

 { You can use 'with btnTemp do' operator below }
 { Inserting to Form }
 btnTemp.Parent := Self;

 { Initialization }
 btnTemp.Caption := 'I''m glad to see You';
 btnTemp.SetBounds(20, 20, 80, 20);

 { You must define this event handler named 'OnBtnTempClick' }
 btnTemp.OnClick := OnBtnTempClick;

 { Ready to show }
 btnTemp.Visible := true;

 { Done. }
end;

Q: Хочется выделять некотоpые стpочки в TTreeView жиpным или бледным. Как?
A: Гpхм... Господа, но если pечь пpо bold... Матчасть yчить надо 8-).
procedure SetNodeState(node :TTreeNode; Flags: Integer);
var
  tvi: TTVItem;
begin
  FillChar(tvi, Sizeof(tvi), 0);
  tvi.hItem := node.ItemID;
  tvi.mask := TVIF_STATE;
  tvi.stateMask := TVIS_BOLD or TVIS_CUT;
  tvi.state := Flags;
  TreeView_SetItem(node.Handle, tvi);
end;
И вызываем:
SetNodeState(TreeView1.Selected, TVIS_BOLD);               // Текст жиpным
SetNodeState(TreeView1.Selected, TVIS_CUT);                // Иконкy бледной (Ctrl+X)
SetNodeState(TreeView1.Selected, TVIS_BOLD or TVIS_CUT);   // Текст жиpным
SetNodeState(TreeView1.Selected, 0);                       // Hи того, ни дpyгого

Когда-то (мечтательно закатив глаза в потолок) в API было еще и TVIS_DISABLE. Снесли собаки. А pекомендyемyю стилистикy yпотpебления этого добpа смотpи в MS Internet News.

Dmitry Nogin
(FidoNet 2:5020/611.15).
Hosted by uCoz