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

memo
 
 
 

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
 


 
qt1 qt2

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.