Forma w panelu - komponent
maxi
W niektórych aplikacjach używane są okna, w których część zawartości ulega zmianie, a część pozostaje stała. Przykładem takiego okna może być kreator instalatora lub menu ustawień, jak ustawienia projektu w Delphi, gdzie po lewej stronie znajduje się stałe drzewo kategorii, a po prawej opcje. Istnieje wiele sposobów na stworzenie działającego w ten sposób okna. Najprostszym byłoby utworzenie wielu paneli znajdujących się w docelowym miejscu formy, których widoczność byłaby zmieniana po wybraniu przez użytkownika opcji z drzewa. Jednak gdyby zaszła potrzeba dodania komponentu do panelu znajdującego się na spodzie, musiałbyś przebrnąć przez cały ich stos. Wygodniejszym rozwiązaniem byłoby umieszczenie na formie jednego panelu, do którego zawartość byłaby ładowana z przygotowanych wcześniej szablonów.
Najprostszym rodzajem łatwo dostępnego do edycji szablonu jest osobna forma. Naszym celem jest więc utworzenie możliwości skopiowania zawartości formy do panelu. Aby nie zaśmiecać kodu projektu, utworzymy nowy komponent TFormPanel.
Artykuł opisuje tworzenie komponentu w Delphi 2010. W innych wersjach tłumaczenia, skróty klawiaturowe i lokalizacja poszczególnych opcji w menu mogą się nieznacznie różnić.
Dla początkujących: tworzenie komponentu
Uruchom Delphi. Z menu File wybierz New -> Package - Delphi, a następnie projekt Package. Komponent znajdujący się w pakiecie będzie od razu gotowy do instalacji (począwszy od wersji 2005, Delphi instaluje komponenty tylko z pakietów). Kliknij prawym przyciskiem utworzony projekt i wybierz Add New... -> Other..., a następnie ze znajomo wyglądającego okna wybierz Component (będziesz musiał poszukać go w drzewie po lewej stronie). Otworzy się kreator tworzenia komponentu. Wybierz dziedziczenie z TCustomPanel, a następnie wskaż dowolny folder i wpisz nazwę pliku "FormPanel.pas". Od razu zapisz projekt i moduł (Ctrl+Shift+S) pod nazwami "FormPanel.pas" i "TFormPanel.dproj" (rozszerzenia mogą się różnić w zależności od wersji Delphi).
Dla bardziej zaawansowanych: TCustomPanel
Dlaczego wybrałem dziedziczenie z TCustomPanel, a nie TPanel? Ponieważ część właściwości standardowego panelu widocznych w Object Inspectorze jest w takich zastosowaniach zbędna i wprowadza bałagan. Dla przykładu, niepotrzebne będą Caption i ShowCaption. W komponencie TCustomPanel właściwości te są zadeklarowane jako chronione (protected), dzięki czemu pozostają ukryte. Wybrane spośród nich możemy później ponownie zadeklarować jako opublikowane (published), dzięki czemu pojawią się w Object Inspectorze.
Kod komponentu
Poniżej zamieszczam pełny, działający kod komponentu TFormPanel. Komentarze do bardziej skomplikowanych fragmentów znajdują się pod kodem.
unit FormPanel;
interface
uses
SysUtils, Classes, Controls, ExtCtrls, Forms;
type
TFormClass = class of TForm;
TFormOpenEvent = procedure(Sender: TObject; Form: TForm; FormClass: TFormClass) of object;
TFormPanel = class(TCustomPanel)
protected
DefaultCloseEvent: TCloseEvent;
DefaultHideEvent: TNotifyEvent;
DefaultShowEvent: TNotifyEvent;
LastCloseAction: TCloseAction;
FForm: TForm;
FFormClass: TFormClass;
FAutoHide: Boolean;
FFormClose: TNotifyEvent;
FFormOpen: TFormOpenEvent;
procedure FormClosed(Sender: TObject; var Action: TCloseAction);
procedure FormHidden(Sender: TObject);
procedure FormShown(Sender: TObject);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure FormLoad(FC: TFormClass);
function FormClose: TCloseAction;
property Form: TForm read FForm;
property FormClass: TFormClass read FFormClass;
published
property Align;
property AutoHide: Boolean read FAutoHide write FAutoHide;
property BevelInner;
property BevelOuter;
property BevelWidth;
property BorderWidth;
property BorderStyle;
property Color;
property Visible;
property OnClose: TNotifyEvent read FFormClose write FFormClose;
property OnOpen: TFormOpenEvent read FFormOpen write FFormOpen;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TFormPanel]);
end;
procedure TFormPanel.FormClosed(Sender: TObject; var Action: TCloseAction);
begin
if Assigned(DefaultCloseEvent) then
DefaultCloseEvent(Sender, Action);
LastCloseAction := Action;
if FAutoHide and (Action <> caNone) then
Visible := False;
if (Action <> caNone) and Assigned(FFormClose) then
FFormClose(Self);
end;
procedure TFormPanel.FormHidden(Sender: TObject);
begin
if Assigned(DefaultHideEvent) then
DefaultHideEvent(Sender);
if FAutoHide then
Visible := False;
if Assigned(FFormClose) then
FFormClose(Self);
end;
procedure TFormPanel.FormShown(Sender: TObject);
begin
if Assigned(DefaultShowEvent) then
DefaultShowEvent(Sender);
if FAutoHide then
Visible := True;
if Assigned(FFormOpen) then
FFormOpen(Self, FForm, FFormClass);
end;
constructor TFormPanel.Create(AOwner: TComponent);
begin
inherited;
FFormClass := TForm;
ShowCaption := False;
end;
destructor TFormPanel.Destroy;
begin
if Assigned(FForm) then
begin
FForm.Close;
FForm.Free;
end;
inherited;
end;
procedure TFormPanel.FormLoad(FC: TFormClass);
begin
if FC = TForm then
exit;
if Assigned(FForm) then
begin
FForm.Close;
FForm.Free;
end;
FFormClass := FC;
FForm := FFormClass.Create(nil);
case Align of
alTop, alBottom: begin
Height := FForm.ClientHeight;
FForm.Width := Width;
end;
alLeft, alRight: begin
Width := FForm.ClientWidth;
FForm.Height := Height;
end;
alClient: begin
FForm.Width := Width;
FForm.Height := Height;
end;
else begin
Width := FForm.ClientWidth;
Height := FForm.ClientHeight;
end;
end;
FForm.Parent := Self;
FForm.BorderStyle := bsNone;
FForm.BorderIcons := [];
FForm.AutoSize := True;
FForm.Align := alClient;
if Assigned(FForm.OnClose) then
DefaultCloseEvent := FForm.OnClose;
if Assigned(FForm.OnHide) then
DefaultHideEvent := FForm.OnHide;
if Assigned(FForm.OnShow) then
DefaultShowEvent := FForm.OnShow;
FForm.OnClose := FormClosed;
FForm.OnHide := FormHidden;
FForm.OnShow := FormShown;
FForm.Show;
if FAutoHide then
Visible := True;
if Assigned(FFormOpen) then
FFormOpen(Self, FForm, FFormClass);
end;
function TFormPanel.FormClose: TCloseAction;
begin
Result := caNone;
if not Assigned(FForm) then
exit;
FForm.Close;
Result := LastCloseAction;
end;
end.
Dla początkujących: instalowanie pakietu
Zapisz wszystkie pliki (Ctrl+Shift+S), a następnie zaznacz po prawej stronie pakiet TFormPanel.bpl, kliknij go prawym przyciskiem myszy i wybierz opcję Build. Po zakończeniu procesu ponownie wyświetl menu kontekstowe projektu i wybierz opcję Install. Po chwili pojawi się komunikat o dodaniu komponentu TFormPanel w kategorii Samples.
Objaśnienia
TFormClass = class of TForm;
W Delphi każda dodana forma ma swoją własną klasę, według której jest tworzona automatycznie przy uruchamianiu aplikacji. Dla form wyświetlanych w panelu najlepiej to automatycznie tworzenie wyłączyć (Ctrl+Shift+F11, Forms, ustaw tworzenie ręczne). Taka deklaracja typu oznacza, że zmienne typu TFormClass będą przechowywać klasę TForm lub dowolną inną klasę która z niej dziedziczy, a więc w praktyce dowolną formę.
DefaultCloseEvent: TCloseEvent;
DefaultHideEvent: TNotifyEvent;
DefaultShowEvent: TNotifyEvent;
// [...]
FAutoHide: Boolean;
// [...]
procedure FormClosed(Sender: TObject; var Action: TCloseAction);
procedure FormHidden(Sender: TObject);
procedure FormShown(Sender: TObject);
Zmienna FAutoHide jest później opublikowana jako właściwość AutoHide. Decyduje ona, czy komponent ma zostać automatycznie ukryty po zamknięciu zawartej w nim formy oraz automatycznie wyświetlony po załadowaniu nowej. Aby wykryć zamknięcie, ukrycie i wyświetlenie formy, możemy wykorzystać jej zdarzenia OnClose, OnHide oraz OnShow. Jednak nie chcemy nadpisać ich oryginalnej zawartości, więc zapamiętujemy ją wcześniej w zmiennych Default[Zdarzenie]Event o odpowiednich typach.
Przytrzymaj klawisz Ctrl i kliknij dowolną nazwę w kodzie, a zostaniesz przeniesiony do miejsca jej definicji lub deklaracji. Możesz wykorzystać ten sposób do identyfikowania typów zdarzeń.
FFormClose: TNotifyEvent;
FFormOpen: TFormOpenEvent;
Są to dwa zdarzenia, które pojawią się na zakładce Events Object Inspectora. Będą one wywoływane po zamknięciu formy w panelu oraz zaraz po jej otwarciu. Dzięki temu program może odpowiednio zareagować. Forma będzie tworzona w panelu z właściwością Align = alClient, to znaczy wypełni go w całości. Jednak nadal możemy dodać na panel inny komponent z właściwością Align = alTop i wykorzystać go do emulacji paska tytułu.
Użycie
Jeśli kompilator zwróci błąd braku pliku FormPanel.pas, będziesz musiał poszerzyć ścieżkę wyszukiwania. Aby to zrobić, otwórz okno opcji projektu (Ctrl+Shift+F11), kliknij przycisk wielokropka (...) przy Search path i dodaj ścieżkę folderu zawierającego plik FormPanel.pas.
// wczytanie formy
FormPanel1.FormLoad(TForm2);
// dostęp do formy (ogólnie)
FormPanel1.Form.Caption := 'Nie ma znaczenia, tytułu nie widać.';
// dostęp do formy (szczególnie)
(FormPanel1.Form as FormPanel1.FormClass).Caption := 'Dostęp szczególny';
// zamknięcie formy
FormPanel1.FormClose;
Ograniczenia
W pokazany sposób nie można ładować okien modalnie. Można co prawda zmodyfikować kod (FForm.Show na FForm.ShowModal), lecz efekt nie będzie taki, jak tego oczekiwaliśmy. Okna modalne mają to do siebie, że wstrzymują działanie okna nadrzędnego dopóki są otwarte. W ten sposób zablokujemy formę główną, a równocześnie nie będzie możliwości interakcji z formą w panelu (znajduje się na zablokowanej formie głównej).
Nie ma również możliwości zmiany zdarzeń OnClose, OnHide oraz OnShow formy w panelu. Spowodowałaby ona (częściowe) wyłączenie mechanizmu autoukrywania. Można jednak bez przeszkód przypisać je bezpośrednio w Object Inspectorze podczas edycji formy.