Annexe
1
Nouveau
composant et messages
Plan
du chapitre 4 Annexe 1:
4.1. Installation de nouveau composant
4.2. Le cracking de message...
4.3. Un exemple avec Qt
4.1. Installation de nouveau composant
Un petit exemple de création de composant, un memo qui clignote
sur un click de souris. Pour intégrer ce nouveau composant, l'unit.pas
a été placée dans usr/local/kylix/source/vcl. Puis
on sélectionne dans le menu Component, Install Component, comme
dans Delphi. On peut également choisir de l'installer
dans un nouveau paquet. En fait la démarche est la même,
et la procédure register component permet d'installer le composant
dans l'onglet désiré, de la même façon.
MemoClignote
4.2. Le cracking de message...
L'interception des messages sous Delphi se fait grâce aux API
de Windows. Les appels au système sous linux sont bien sûr
différents, et sous Kylix un gestionnaire d'évènement
du type if .. assigned ne peut pas être utilisé. La syntaxe
utilisant les noms des messages du type WM_ ou CM_qqchose n'existent pas.
Voici deux exemples simples, reprenant la syntaxe de base, qui permet
de s'en rendre compte:
Exemple1: Mesage01
Exemple2:RepartMessage
La gestion des messages sous Delphi schématisée ci-dessous
a été comparée à Kylix.
Le départ du message vient de TApplication sous Delphi. Dans
Kylix, la classe TApplication est déclarée dans QForms.
En utilisant le raccourci ctrl+click les procédures impliquées
dans la propagation du message en Delphi on été recherchées
dans les uses de Kylix.
Les messages eux mêmes sont répertoriés dans l'uses
Message de Delphi et empaquetés dans des packed record. Première
différence déjà avec Kylix: il n'y a pas d'uses Message.
Les procédures qui ont été retrouvées sont
TApplication.HandleMessage (QForms), WndProc (QForms), DefaultHandler (QForms)
et Dispatch (TObject.dispatch dans System).
Dans Delphi la méthode WndProc sélectionne les messages
avant de les transmettre à la méthode Dispatch qui,
à son tour, détermine la méthode qui gère
le message. En surchargeant WndProc, le composant a la possibilité
de filtrer certains messages avant qu'ils ne soient transmis.
Le tableau suivant compare les deux méthodes trouvées sous
Kylix et Delphi:
Kylix:
function WndProc(Message: PMsg): Integer; stdcall;
begin
Result := 0;
with Message^ do
begin
case message of
WM_ACTIVATEAPP:
begin
if wParam = 0 then
Application.DoDeactivate
else
Application.DoActivate;
end;
WM_SYSCOMMAND:
begin
if Assigned(Application.FMainForm) then
begin
if hwnd = QWidget_winId(Application.FMainForm.Handle) then
case WParam and $FFF0 of
SC_MINIMIZE: Application.Minimize;
SC_RESTORE: Application.Restore;
end;
end
end;
end;
end;
if Assigned(OldWndProc) and (@WndProc <> @OldWndProc) then
Result := OldWndProc(Message) or Result;
end;
|
Delphi:
procedure TApplication.WndProc(var Message: TMessage);
type
TInitTestLibrary = function(Size: DWord; PAutoClassInfo: Pointer):
Boolean; stdcall;
var
I: Integer;
SaveFocus, TopWindow: HWnd;
InitTestLibrary: TInitTestLibrary;
procedure Default;
begin
with Message do
Result := DefWindowProc(FHandle, Msg, WParam,
LParam);
end;
procedure DrawAppIcon;
var
DC: HDC;
PS: TPaintStruct;
begin
with Message do
begin
DC := BeginPaint(FHandle, PS);
DrawIcon(DC, 0, 0, GetIconHandle);
&sbsp; EndPaint(FHandle, PS);
end;
end;
begin
try
Message.Result := 0;
for I := 0 to FWindowHooks.Count - 1 do
if TWindowHook(FWindowHooks[I]^)(Message)
then Exit;
CheckIniChange(Message);
with Message do
case Msg of
WM_SYSCOMMAND:
case WParam and $FFF0
of
SC_MINIMIZE:
Minimize;
SC_RESTORE:
Restore;
else
Default;
end;
WM_CLOSE:
if MainForm <>
nil then MainForm.Close;
{ WM_SYSCOLORCHANGE:
if (Ctl3DHandle <>
0) and (Ctl3DHandle <> INVALID_HANDLE_VALUE) and
(@Ctl3DColorChange
<> nil) then
Ctl3DColorChange;
} WM_PAINT:
if IsIconic(FHandle)
then DrawAppIcon else Default;
WM_ERASEBKGND:
begin
Message.Msg
:= WM_ICONERASEBKGND;
Default;
end;
WM_QUERYDRAGICON:
Result := GetIconHandle;
WM_SETFOCUS:
begin
PostMessage(FHandle,
CM_ENTER, 0, 0);
Default;
end;
WM_ACTIVATEAPP:
begin
Default;
FActive
:= TWMActivateApp(Message).Active;
if TWMActivateApp(Message).Active
then
begin
RestoreTopMosts;
PostMessage(FHandle, CM_ACTIVATE, 0, 0)
end
else
begin
NormalizeTopMosts;
PostMessage(FHandle, CM_DEACTIVATE, 0, 0);
end;
end;
WM_ENABLE:
if TWMEnable(Message).Enabled
then
begin
RestoreTopMosts;
if FWindowList
<> nil then
begin
EnableTaskWindows(FWindowList);
FWindowList := nil;
end;
Default;
end else
begin
Default;
if FWindowList
= nil then
FWindowList := DisableTaskWindows(Handle);
NormalizeAllTopMosts;
end;
WM_CTLCOLORMSGBOX..WM_CTLCOLORSTATIC:
Result := SendMessage(LParam,
CN_BASE + Msg, WParam, LParam);
WM_ENDSESSION: if TWMEndSession(Message).EndSession
then FTerminate := True;
WM_COPYDATA:
if (PCopyDataStruct(Message.lParam)^.dwData
= DWORD($DE534454)) and
(FAllowTesting)
then
if FTestLib
= 0 then
begin
FTestLib := SafeLoadLibrary('vcltest3.dll');
if FTestLib <> 0 then
begin
Result := 0;
@InitTestLibrary := GetProcAddress(FTestLib, 'RegisterAutomation');
if @InitTestLibrary <> nil then
InitTestLibrary(PCopyDataStruct(Message.lParam)^.cbData,
PCopyDataStruct(Message.lParam)^.lpData);
end
else
begin
Result := GetLastError;
FTestLib := 0;
end;
end
else
Result := 0;
CM_ACTIONEXECUTE, CM_ACTIONUPDATE:
Message.Result :=
Ord(DispatchAction(Message.Msg, TBasicAction(Message.LParam)));
CM_APPKEYDOWN:
if IsShortCut(TWMKey(Message))
then Result := 1;
CM_APPSYSCOMMAND:
if MainForm <>
nil then
with MainForm
do
if (Handle <> 0) and IsWindowEnabled(Handle) and
IsWindowVisible(Handle) then
begin
FocusMessages := False;
SaveFocus := GetFocus;
Windows.SetFocus(Handle);
Perform(WM_SYSCOMMAND, WParam, LParam);
Windows.SetFocus(SaveFocus);
FocusMessages := True;
Result := 1;
end;
CM_ACTIVATE:
if Assigned(FOnActivate)
then FOnActivate(Self);
CM_DEACTIVATE:
if Assigned(FOnDeactivate)
then FOnDeactivate(Self);
CM_ENTER:
if not IsIconic(FHandle)
and (GetFocus = FHandle) then
begin
TopWindow
:= FindTopMostWindow(0);
if TopWindow
<> 0 then Windows.SetFocus(TopWindow);
end;
CM_INVOKEHELP: InvokeHelp(WParam,
LParam);
CM_WINDOWHOOK:
if wParam = 0 then
HookMainWindow(TWindowHook(Pointer(LParam)^))
else
UnhookMainWindow(TWindowHook(Pointer(LParam)^));
CM_DIALOGHANDLE:
if wParam = 1 then
Result
:= FDialogHandle
else
FDialogHandle
:= lParam;
WM_SETTINGCHANGE:
begin
Mouse.SettingChanged(wParam);
Default;
end;
WM_FONTCHANGE:
begin
Screen.ResetFonts;
Default;
end;
else
Default;
end;
except
HandleException(Self);
end;
end; |
Dans Delphi la procédure Dispatch détermine si un message
se trouve dans la liste des gestionnaires de message déclarés
pour cet objet. Si l'objet ne gère pas ce message, Dispatch examine
les gestionnaires de message de la classe de son ancêtre, et continue
ainsi à vérifier les ancêtres jusqu'à trouver
un gestionnaire spécifique ou épuiser tous les ancêtres.
Dans ce dernier cas, la méthode DefaultHandler est appelée.
Dans Kylix on la retouve également, dans l'unité System,
déclarée avec la même syntaxe, de type assembleur
(asm).
Finalement, en remontant la boucle des messages de la même façon
qu' en Delphi, des similitudes ont été rencontrées
en ce qui concerne le nom des procédures mais pas leur fonctionnement.
C'était déjà le cas lorqu'on comparait la procédure
TApplication.ProcessMessages.
4.3. Un exemple avec Qt
Le dernier niveau atteint appelle une unité nommée Qt,
dans laquelle toutes les routines et propriétés essentielles
semblent avoir été déclarées. Sur le site developpez.com
puis doc.trolltech.com des informations
précieuses ont été trouvées. Qt est une librairie
de classes en langage C++, et qui a servi de point de départ à
la librairie CLX de Kylix. Malheureusement, les informations sur la façon
dont elle a été reprise ne sont pas accessibles.
Pour ce qui concerne Qt, on dispose de classes telles que QButton, Qevent,
QLabel, QGrid, QWidget, etc...
Ces classes sont renommées dans Kylix en ajoutant H à
la fin (QApplicationH, QButttonH...)
La classe QWidget semble agir comme un conteneur de composant, ou encore
comme la fiche principale. L' exemple proposé sur internet (par
Bruno Sonnino) suivant a été testé:
program SimpleQt;
uses
Qt,
QForms,
SysUtils;
type
TDummyClass = class
procedure
ButtonClicked; cdecl;
end;
var
App : QApplicationH;
// QApplication handle
MainWnd : QWidgetH;
// Main window handle
Button : QWidgetH;
// Button handle
MainForm : TForm;
// Main application form
Caption : WideString;
DummyClass : TDummyClass;
ButtonHook : QButton_hookH;
BtnClicked : TMethod;
procedure TDummyClass.ButtonClicked;
var
Caption, Text, YesText,
NoText : AnsiString;
begin
Caption := 'Close Application';
Text := 'Are you sure you
want to quit?';
YesText := 'Yes';
NoText := 'No';
// creates messagebox
if QMessageBox_query(@Caption,
@Text, @YesText, @NoText, MainWnd, PChar('mb')) then QApplication_quit(App);
end;
begin
DummyClass := TDummyClass.Create;
//
initializes application and stores handle in App
Application.Initialize;
App := Application.Handle;
//
creates main window
MainForm := TForm.Create(nil);
Caption := 'Minimum CLX
Application';
MainWnd := MainForm.Handle;
// set window caption and size
QWidget_setCaption(MainWnd,@Caption);
QWidget_setGeometry(MainWnd,100,100,200,100);
//
creates the button
Caption := 'Close App';
Button := QPushButton_create(@Caption,
MainWnd, PChar('Button'));
// sets button size
QWidget_setGeometry(Button,20,20,160,160);
//
creates the button hook
ButtonHook := QButton_hook_create(Button);
//
convert the method to a TMethod
QButton_clicked_Event(BtnClicked)
:= DummyClass.ButtonClicked;
//
link the method to the clicked event of the button
QButton_hook_hook_clicked(ButtonHook,
BtnClicked);
//
sets application main widget
QApplication_setMainWidget(App,MainWnd);
//
shows widget and enters application loop
QWidget_show(MainWnd);
QApplication_exec(App);
DummyClass.Free;
end.
Plus de renseignements sur l'exemple
http://community.borland.com/article/0,1410,27231,00.html
|
|
|
Les captions de la fiche(Caption := 'Minimum CLX Application';) et du
bouton (Caption := 'Close App';) n'ont pas été prises en
considération, mais, à priori l'exemple marche.
Ce programme fixe les formats et les emplacements des composants et
intercepte le message de click sur bouton par le biais d'une déclaration
de classe muette "DummyClass". Celle-ci ne comporte que la méthode
"ButtonClicked" qui fait apparaître le boîte de dialogue "close
application" après un click.
L'utilisation de cette classe se fait avec notamment la conversion
de la méthode ButtonClicked gérant l'évènement
click en la TMethode BtnClicked.