|
In questo articolo vado ad esporre una soluzione alternativa al classico
approccio per la creazione di ambienti MDI. Viene in particolar modo
esaminata la tecnica di spostamento e ridimensionamento di componenti grafici
posti su una Form (come quando si mette ad esempio un pannello su una
Form nell' IDE di Delphi). La finestra MDI che fa da contenitore per
le finestre MDI figlie (FormStyle = fsMDIForm) può essere emulata
da un componente TScrollBox, mentre le finestre MDI child (FormStyle
= fsMDIChild) possono essere emulate da un componente TPanel, il cui
parent è il componente TScrollBox, con un componente figlio di tipo
TPanel che va ad emulare la Caption della Form. Il pannello di
emulazione della Caption avrà le seguenti caratteristiche
1) Align := alTop;
2) BevelOuter := bvNone;
3) Height := 21;
4) Alignment := taLeftJustify;
5) Caption := 'ChildForm1';
6) Color := clActiveCaption;
7) Font.Color := clCaptionText;
Userò poi la seguente notazione:
Form Panel: il pannello che emula la Form figlia
Caption Panel: il pannello di emulazione della Caption della
Form figlia (il suo Parent è Form Panel)
Form Panel deve essere ridimensionabile proprio come qualsiasi Form
con BorderStyle bsSizeable (il valore di default della proprietà
BorderStyle). Per raggiungere quest' obiettivo dobbiamo intercettare il
messaggio di Windows wm_NcHitTest. Il messaggio WM_NCHITTEST viene
inviato ad una finestra quando si sposta il cursore su quella finestra oppure
quando un pulsante del mouse è premuto o rilasciato sulla finestra. Ha 2
parametri: XPos and YPos. Rappresentano le coordinate (x,y)
(relativamente all' angolo in alto a sinistra dello schermo) del cursore. In una
situazione normale (cioè quando non è stata definita una gestione personalizzata
di questo messaggio) quando questo messaggio viene inviato ad una Form,
viene processato dalla funzione DefWindowProc che restituisce i seguenti
valori che indicano la posizione del cursore:
HTBOTTOM: nel bordo orizzontale in basso della finestra
HTBOTTOMLEFT: nell' angolo in basso a sinistra della finestra
HTBOTTOMRIGHT: nell' angolo in basso a destra della finestra
HTTOP: nel bordo orizzontale in alto della finestra
HTTOPLEFT: nell' angolo in alto a sinistra della finestra
HTTOPRIGHT: nell' angolo in alto a destra della finestra
HTLEFT: nel bordo verticale a sinistra della finestra
HTRIGHT: nel bordo verticale a destra della finestra
...
...
(rimando al Platform SDK per altri valori).
Nel messaggio c' è anche un campo Result che va settato, all' interno
della procedura che rileva il messagio, per definire il comportamento.
Se il risultato è HTBOTTOM or HTTOP, la freccia del cursore sarà una
freccia verticale:
Se premo il pulsante sinistro del mouse e sposto il mouse, la Form sarà
ridimensionata in ampiezza.
Se il risultato è HTRIGHT or HTLEFT, la freccia del cursore sarà una
freccia orizzontale:
Se premo il pulsante sinistro del mouse e sposto il mouse, la Form sarà
ridimensionata in altezza.
Se il risultato è HTBOTTOMLEFT or HTBOTTOMRIGHT or HTTOPLEFT or HTTOPRIGHT,
la freccia del cursore sarà una freccia diagonale: se premo il pulsante sinistro
del mouse e sposto il mouse, la Form sarà ridimensionata sia in ampiezza
sia in altezza.
Se vogliamo raggiungere il medesimo risultato in un componente TPanel
bisogna fare l' overriding della sua Window Procedure ed impostare il
Result. Tutto ciò può essere raggiunto nel seguente modo:
type
TSzblPanel = class (TPanel)
public
procedure WmNcHitTest (var Msg: TWmNcHitTest);
message wm_NcHitTest;
end;
procedure TSzblPanel.WmNcHitTest(var Msg: TWmNcHitTest);
begin
with ScreenToClient(Point (Msg.XPos, Msg.YPos)) do
begin
if (x < 5) and (y < 5) then
Msg.Result := htTopLeft
else
if (x > Width - 5) and (y < 5) then
Msg.Result := htTopRight
else
if (x > Width - 5) and (y > Height - 5) then
Msg.Result := htBottomRight
else
if (x < 5) and (y > Height - 5) then
Msg.Result := htBottomLeft
else
if (x < 5) then
Msg.Result := htLeft
else
if (y < 5) then
Msg.Result := htTop
else
if (x > Width - 5) then
Msg.Result := htRight
else
if (y > Height - 5) then
Msg.Result := htBottom
else
inherited;
end;
end;
Ora occorre derivare il componente TSizePanel per dargli il Caption Panel. Se premo il pulsante sinistro del mouse sul Caption panel e sposto il mouse, il Form Panel verrà spostato conseguentemente. Un pannello può essere trascinato implementando il suo evento OnMouseDown:
procedure TForm1.Panel1MouseDown(Sender: TObject;
Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
const
kSC_DragMove = $F012 ;
var
rPnl : TPanel ;
begin
rPnl := TPanel(Sender);
ReleaseCapture();
// invia il messaggio al controllo su cui stiamo facendo click
rPnl.Perform( WM_SYSCOMMAND, kSC_DragMove, 0 );
end;
E' da notare che voglio trascinare il Form Panel all' interno del componente TScrollBox e non il Caption panel all' interno del Form Panel. Creiamoci quindi il componente derivato da TSzblPanel che ci consente di emulare in pieno una Child Form
type
TFormPanel = class(TSzblPanel)
private
FCaptionPanel: TPanel;
procedure SetCaptionTitle(Title: string);
function GetCaptionTitle(): string;
protected
procedure CaptionPanelMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
constructor Create(Aowner: TComponent); override;
public
{ Public declarations }
published
{ Published declarations }
property CaptionTitle: string read GetCaptionTitle write SetCaptionTitle;
end;
constructor TFormpanel.Create(AOwner: TComponent);
begin
inherited;
Borderwidth := 3;
FcaptionPanel := TPanel.Create(Self);
with FCaptionPanel do
begin
Parent := Self;
Align := alTop;
BevelOuter := bvNone;
Height := 21;
Alignment := taLeftJustify;
Caption := 'ChildForm1';
Color := clActiveCaption;
Font.Color := clCaptionText;
//assegna l' evento "OnMouseDown" per trascinare "Form Panel"
OnMouseDown := CaptionPanelMouseDown;
end;
end;
procedure TFormPanel.CaptionPanelMouseDown(Sender: TObject;
Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
const
kSC_DragMove = $F012 ;
var
rPnl : TPanel ;
begin
//vogliamo trascinare il parent del "Caption Panel"
//e non il "Caption Panel" stesso così sostituiamo
// rPnl := TPanel(Sender);
//con
// rPnl := TPanel(TPanel(Sender).Parent) ;
rPnl := TPanel(TPanel(Sender).Parent) ;
ReleaseCapture();
// invia il messaggio al parent del controllo su cui stiamo facendo click.
rPnl.Perform( WM_SYSCOMMAND, kSC_DragMove, 0 );
end;
procedure TFormPanel.SetCaptionTitle(Title: string);
begin
FCaptionPanel.Caption := Title;
end;
function TFormPanel.GetCaptionTitle(): string;
begin
result := FCaptionPanel.Caption;
end;
Di seguito il codice sorgente completo del componente che emula una Child Form
unit FormPanel;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;
type
TSzblPanel = class (TPanel)
public
procedure WmNcHitTest (var Msg: TWmNcHitTest);
message wm_NcHitTest;
end;
type
TFormPanel = class(TSzblPanel)
private
FCaptionPanel: TPanel;
procedure SetCaptionTitle(Title: string);
function GetCaptionTitle(): string;
protected
procedure CaptionPanelMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
constructor Create(Aowner: TComponent); override;
public
{ Public declarations }
published
{ Published declarations }
property CaptionTitle: string read GetCaptionTitle write SetCaptionTitle;
end;
procedure Register;
implementation
procedure TSzblPanel.WmNcHitTest(var Msg: TWmNcHitTest);
begin
with ScreenToClient(Point (Msg.XPos, Msg.YPos)) do
begin
if (x < 5) and (y < 5) then
Msg.Result := htTopLeft
else
if (x > Width - 5) and (y < 5) then
Msg.Result := htTopRight
else
if (x > Width - 5) and (y > Height - 5) then
Msg.Result := htBottomRight
else
if (x < 5) and (y > Height - 5) then
Msg.Result := htBottomLeft
else
if (x < 5) then
Msg.Result := htLeft
else
if (y < 5) then
Msg.Result := htTop
else
if (x > Width - 5) then
Msg.Result := htRight
else
if (y > Height - 5) then
Msg.Result := htBottom
else
inherited;
end;
end;
constructor TFormpanel.Create(AOwner: TComponent);
begin
inherited;
Borderwidth := 3;
FcaptionPanel := TPanel.Create(Self);
with FCaptionPanel do
begin
Parent := Self;
Align := alTop;
BevelOuter := bvNone;
Height := 21;
Alignment := taLeftJustify;
Caption := 'ChildForm1';
Color := clActiveCaption;
Font.Color := clCaptionText;
OnMouseDown := CaptionPanelMouseDown;
end;
end;
procedure TFormPanel.CaptionPanelMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
const
kSC_DragMove = $F012 ;
var
rPnl : TPanel ;
begin
rPnl := TPanel(TPanel(Sender).Parent) ;
ReleaseCapture();
rPnl.Perform( WM_SYSCOMMAND, kSC_DragMove, 0 );
end;
procedure TFormPanel.SetCaptionTitle(Title: string);
begin
FCaptionPanel.Caption := Title;
end;
function TFormPanel.GetCaptionTitle(): string;
begin
result := FCaptionPanel.Caption;
end;
procedure Register;
begin
RegisterComponents('Carlo Pasolini', [TFormPanel]);
end;
end.
|