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.

0 komentarzy