Własne okna dialogowe

furious programming

Wstęp

Nie raz pewnie podczas pisania programu chcemy, by wygląd całego GUI aplikacji był unikalny. Tworzymy różnego rodzaju kontrolki, wyposażamy je w ręczne rysowanie np. item'ów (jak w komponentach z klasy TListBox, TComboBox, TMainMenu itd.), dodajemy własną grafikę. Interefejs programu zaczyna wyglądać efektownie, lecz pojawia się problem - standardowe okna dialogowe nie pasują wyglądem do naszego programu lub chcemy, by wyposażone zostały we więcej komponentów, żeby miały większe możliwości; Co wtedy? Trzeba stworzyć własne okno dialogowe i nadać mu wygląd, jakiego nie ma możliwości uzyskać wykorzystując wbudowane. Ale jak to zrobić...?

Ten artykuł ma na celu przedstawienie sposobu tworzenia własnych okien dialogowych, które pod każdym względem będziemy mogli dostosować do naszej aplikacji. Nie będą to jedynie proste okienka zwracające jedynie wartość modalną okna (jak mrYes, mrNo, mrYesToAll, mrCancel itd.) ale także takie, które pozwalają zwrócić np. całą strukturę danych.

Artykuł ten kierowany jest do początkujących programistów borykających się z tym problemem. Zachęcam serdecznie wszystkich do przeczytania poniższej treści.

1 Wstęp
2 Proste okno dialogowe - InfoBox
     2.1 Formularz
     2.2 Komponenty
     2.3 Zestaw ikon informacyjnych
     2.4 Zwalnianie okna z pamięci
     2.5 Funkcja dialogu
3 Okna zwracające wartość niemodalną
4 Własny FontDialog
     4.6 Formularz i kontrolki
     4.7 Ikona czcionki i rozmiaru
     4.8 Tworzenie formularza
     4.9 Usuwanie formularza z pamięci
     4.10 Własny wygląd kontrolek
     4.11 Modyfikowanie tekstu podglądu czcionki
     4.12 Klasa dialogu
          4.12.1 Konstruktor klasy
          4.12.2 Własna procedura zamknięcia formularza
          4.12.3 Metoda Execute
     4.13 Wykorzystanie klasy dialogu
     4.14 Funkcja wykorzystująca klasę dialogu
5 Najważniejsze informacje
6 Zakończenie

Proste okno dialogowe - InfoBox

Formularz

W pierwszej kolejności należy zaprojektować formularz. Projektowanie rozpoczynamy od ustawienia właściwości samego formularza. Ustalamy właściwości okna po kolei:

Właściwość Wartość
BorderIcons [biSystemMenu]
BorderStyle bsSingle
Caption ''
Cursor crArrow
Height 163
Icon wybieramy ikonę swojego programu o wymiarach 16x16 pikseli (w przykładzie - koło zębate)
KeyPreview True
Name InfoBoxForm
Position poOwnerFormCenter
Width 457
Jeśli chodzi o formularz - mamy wszystko przygotowane.

Komponenty

Nasze okienko wyposażymy w kilka kontrolek. Będą potrzebne:

Identyfikator Klasa komponentu Opis
imgIcon TIcon ikona typu wiadomości (pytanie, informacja, ostrzeżenie, błąd)
lblText TLabel przedstawiona będzie w nim treść wiadomości
btnYes TButton przycisk Tak
btnNo TButton przycisk Nie
btnOk TButton przycisk Ok
Komponenty ustawiamy według własnego uznania, ja proponuję tak, jak jest to pokazane na poniższym rysunku:

InfoBoxForm.png

Na rysunku nie widać przycisku btnOk ponieważ jest on schowany pod btnNo. Do dyspozycji będziemy mieli dwie możliwości ustawień przycisków: Tak i Nie lub Ok, stąd ten ostatni będzie wyświetlany w miejscu przycisku btnNo.


Zestaw ikon informacyjnych

Na formularzu mamy komponent o identyfikatorze imgIcon, który będzie służyć do wyświetlania obrazu odpowiedniego dla prezentowanej informacji. Musimy przygotować sobie zestaw ikon, potrzebne będą: zapytania (pytajnik), informacji (żarówka), ostrzeżenia (wykrzyknik) oraz błędu (krzyżyk). Poniżej przykładowy zestaw ikonek:

InfoBoxIcons.png

Ikony trzeba przygotować do odpowiedniego rozmiaru. W naszym okienku komponent imgIcon ma rozmiary 48x48 px, stąd każda ikona powinna mieć dokładnie taką samą wielkość. Teraz musimy utworzyć sobie plik zasobu, w którym umieścimy wszystkie ikonki. Ustalamy je w kolejności podanej wyżej:

ID Opis ikony
INFO_BOX_ICON_0 ikona zapytania
INFO_BOX_ICON_1 ikona informacji
INFO_BOX_ICON_2 ikona ostrzeżenia
INFO_BOX_ICON_3 ikona błędu
Po utworzeniu pliku zasobu InfoBoxIcons.res dodajemy go do programu wpisując odpowiednią linijkę w kodzie modułu:
implementation

{$R *.dfm}
{$R InfoBoxIcons.res}

{...}

Zwalnianie okna z pamięci

Jedyne co musimy oprogramować to zwalnianie okna z pamięci podczas jego zamknięcia. Uzupełniamy więc zdarzenie OnClose formularza w poniższe dwie linijki:

procedure TInfoBoxForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
  InfoBoxForm := nil;
end;

Dzięki temu okno po każdorazowym użyciu będzie zwalniane z pamięci i unikniemy późniejszych niepotrzebnych błędów.


Funkcja dialogu

Samo okienko do niczego się jeszcze nie nadaje - można je wyświelić, kliknąć w jakiś przycisk. Wartość zostanie zwrócona, ale nic jej jeszcze nie przechwytuje. Potrzebujemy więc funkcję, która wykorzysta zaprojektowane wcześniej okienko i przechwyci zwróconą przez nie wartość na podstawie wciśniętego przycisku.

W pierwszej kolejności musimy stworzyć sobie kilka stałych oraz typów, które będą nam służyć do rozróżniania pewnych trybów okienka, jak np. zbiór przycisków, typ ikony informacyjnej, aktywny przycisk:

{ TYP IKONY INFORMACYJNEJ }
const
  itQuestion    = 'INFO_BOX_ICON_0';
  itInformation = 'INFO_BOX_ICON_1';
  itWarning     = 'INFO_BOX_ICON_2';
  itStop        = 'INFO_BOX_ICON_3';
type
  TInfoIconType = type String;

{ ZBIÓR PRZYCISKÓW }
const
  bsOk    = 0;
  bsYesNo = 1;
type
  TButtonsSet = bsOk .. bsYesNo;

{ AKTYWNY PRZYCISK }
const
  abOk  = 'btnOk';
  abYes = 'btnYes';
  abNo  = 'btnNo';
type
  TActiveButton = type String;

Mamy już przygotowane wszystko, co będzie potrzebne do napisania funkcji. Musimy wykonać kilka kroków uzupełniających nasze okienko w odpowiednie informacje:

  • utworzyć formularz,
  • ustalić jego tytuł (Caption),
  • ustalić treść wiadomości (lblText.Caption),
  • załadować obrazek (imgIcon.Picture),
  • pokazać odpowiednie przyciski,
  • ustalić aktywny przycisk,
  • pokazać okienko,
  • przechwycić wartość modalną.
    Przykładowa funkcja realizująca to zadanie:
function InfoBox(Owner: TComponent; const Title, Text: String; IconType: TInfoIconType;
                 ButtonsSet: TButtonsSet; ActiveButton: TActiveButton): TModalResult;
var
  InfoBoxForm: TInfoBoxForm;
  rsIcon: TResourceStream;
begin
  InfoBoxForm := TInfoBoxForm.Create(Owner);

  try
    with InfoBoxForm do
    begin
      { TYTUŁ OKNA }
      Caption := Title;
      { TREŚĆ WIADOMOŚCI }
      lblText.Caption := Text;

      { IKONA }
      rsIcon := TResourceStream.Create(hInstance, IconType, 'RT_ICON');

      try
        imgIcon.Picture.Icon.LoadFromStream(rsIcon);
      finally
        rsIcon.Free();
      end;

      { ZBIÓR PRZYCISKÓW }
      case ButtonsSet of
        bsOk:    btnOk.Show();
        bsYesNo: begin
                   btnYes.Show();
                   btnNo.Show();
                 end;
      end;

      { AKTYWNY PRZYCISK }
      ActiveControl := TButton(FindComponent(ActiveButton));
      { WYWOŁANIE OKIENKA I PRZECHWYCENIE WARTOŚCI MODALNEJ }
      Result := ShowModal();
    end;
  finally
    InfoBoxForm.Free();
  end;
end;

Przykładowe wywołanie okienka ostrzegającego użytkownika przed utratą danych:

procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := InfoBox(Self, 'Wyjśćie z programu',
                      'Wyjście z programu grozi bezpowrotną utratą danych.'#10#10 +
                      'Czy na pewno chcesz zakończyć działanie aplikacji bez zapisu?',
                      itWarning, bsYesNo, abYes) = mrYes;
end;

czego rezultatem jest pokazanie następującego okienka:

InfoBoxLostData.png

Inny przykład - wyświetlenie informacji o pomyślnym zapisie ustawień programu do pliku:

InfoBox(Self, 'Zapis ustawień', 'Ustawienia zostały pomyślnie zapisane do pliku "' + FileName + '".',
        itInformation, bsOk, abOk);

po wykonaniu powyższej linii zostanie wyświetlone okienko:

InfoBoxSaveSettings.png


Okna zwracające wartość niemodalną

Pierwsza część artykułu przedstawiała sposób na utworzenie okna dialogowego, które zwraca wartość modalną. Przy tworzeniu takiego dialogu nie musimy wiele pracować, ponieważ jego obsługa ogranicza się jedynie do przechwycenia wartości zwracanej przez przyciski.

W kolejnej części zaprezentuję sposób tworzenia okna dialogowego zwracającego dane dowolnego typu. Nie będzie to jednak tak proste jak w poprzednim przykładzie - musimy zaprojektować okno, oprogramować jego poszczególne elementy po czym stworzyć klasę, która będzie wykorzystywać nasz formularz i pobierać informacje z różnych komponentów umieszczonych w okienku.


Własny FontDialog

Formularz i kontrolki

W pierwszej kolejności musimy zaprojektować formularz. Kładziemy na nim kilka kontrolek określających różne właściwości czcionki. Przykładowe okienko zaprezentowane jest na poniższym rysunku:

FontForm.png

Opis kontrolek umieszczonych na formularzu:

Nazwa Klasa komponentu Identyfikator kontrolki Opis
Nazwa czcionki TComboBox cbFontName lista wszystkich destępnych czcionek w systemie
Rozmiar TComboBox cbFontSize lista standardowych rozmiarów czcionki
pogrubienie TCheckBox cbBold czcionka pogrubiona
kursywa TCheckBox cbItalic czcionka pochylona
podkreślenie TCheckBox cbUnderline czcionka podkreślona
przekreślenie TCheckBox cbStrikeOut czcionka przekreślona
Podgląd TPanel pnlPreview podgląd ustawień czcionki
Zapisz ustawienia TButton btnSave przycisk do zapisu ustawień

Ikona czcionki i rozmiaru

Do przedstawienia w bardziej ciekawy sposób kroju czcionki oraz rozmiaru potrzebować będziemy dwóch ikonek. Ja dla przykładu wybrałem poniższe:

FontIcons.png

Ikona nazwy czcionki ma rozmiary 24x24 piksele, a rozmiaru 16x16 pikseli. Musimy teraz utworzyć plik zasobów, w którym umieszczone zostaną powyższe ikony:

ID Opis
FONT_ICON_0 ikona nazwy czcionki
FONT_ICON_1 ikona rozmiaru czcionki

Przygotowany plik zasobów FontIcons.res dodajemy do listy zasobów modułu:

implementation

{$R *.dfm}
{$R FontIcons.res}

Tworzenie formularza

W pierwszej kolejności musimy załadować ikony z zasobów aplikacji. Tworzymy prostą macierz o statycznym rozmiarze:

type
  TFontForm = class(TForm)
    {...}
  private
    aFontIcons: array [0 .. 1] of TIcon;
  end;

i ładujemy do niej ikonki w zdarzeniu OnCreate formularza:

procedure TFontForm.FormCreate(Sender: TObject);
var
  rsIcon: TResourceStream;
  I: Byte;
begin
  for I := 0 to 1 do
  begin
    rsIcon := TResourceStream.Create(hInstance, 'FONT_ICON_' + IntToStr(I), 'RT_ICON');

    aFontIcons[I] := TIcon.Create();
    aFontIcons[I].LoadFromStream(rsIcon);

    rsIcon.Free();
  end;

  {...}

Macierz mamy uzupełnioną w ikonki, teraz trzeba załadować listę czcionek dostępnych w systemie do komponentu cbFontName:

  {...}
  cbFontName.Items.Assign(Screen.Fonts);
end;

Usuwanie formularza z pamięci

Podczas usuwania formularza musimy zwolnić pamięć zajmującą przez wczytane z zasobów ikonki do macierzy:

procedure TFontForm.FormDestroy(Sender: TObject);
var
  I: Byte;
begin
  for I := 0 to 1 do
    aFontIcons[I].Free();
end;

Własny wygląd kontrolek

Oprogramowaliśmy już wczytywanie ikon do prywatnej macierzy formularza, teraz czas na napisanie własnych procedur OnDrawItem dla komponentów cbFontName oraz cbFontSize. Procedury nie będą skomplikowane - wystarczy narysowanie tła, ikony oraz tekstu. W przypadku listy czcionek każda z nich będzie rysowana w swoim kroju.

Przykładowy kod zdarzenia OnDrawItem kontrolki cbFontName:

procedure TFontForm.cbFontNameDrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
var
  List: TComboBox;
  iTextHeight: Word;
begin
  List := TComboBox(Control);

  with List.Canvas do
  begin
    { USTALENIE KOLORÓW WEDŁUG ZAZNACZENIA }
    case odSelected in State of
      True:  begin
               Pen.Color := $009C5327;
               Brush.Color := $00C56A31;
               Font.Color := clWhite;
             end;
      False: begin
               Pen.Color := clWhite;
               Brush.Color := clWhite;
               Font.Color := clBlack;
             end;
    end;

    { TŁO }
    Rectangle(Rect);
    { IKONA }
    Draw(Rect.Left + 2, Rect.Top + 2, aFontIcons[0]);

    { TREŚĆ }
    Font.Name := List.Items[Index];
    Font.Size := 10;
    iTextHeight := TextHeight(List.Items[Index]);

    TextOut(Rect.Left + 30, Rect.Top + 5 + ((28 - iTextHeight) mod 2), List.Items[Index]);

    { USUNIĘCIE STANDARDOWEJ RAMKI PODŚWIETLAJĄCEJ }
    if odFocused in State then
    begin
      Pen.Color := Pen.Color xor $FFFFFF;
      DrawFocusRect(Rect);
    end;
  end;
end;

Najpierw rozpoznajemy czy dany item jest podświetlony (wartość odSelected w zbiorze State) i ustalamy kolory dla ramki, tła oraz tekstu od razu. Następnie rysujemy tło motedą Rectangle(), po czym rysujemy ikonę pobraną z tablicy aFontIcons. Kolejnym krokiem będzie narysowanie tekstu (nazwy czcionki) w swoim kroju oraz usunięcie standardowej ramki. Teraz czas na oprogramowanie zdarzenia OnDrawItem kontrolki cbFontSize:

procedure TFontForm.cbFontSizeDrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
var
  List: TComboBox;
begin
  List := TComboBox(Control);

  with List.Canvas do
  begin
    { USTALENIE KOLORÓW }
    case odSelected in State of
      True:  begin
               Pen.Color := $009C5327;
               Brush.Color := $00C56A31;
               Font.Color := clWhite;
             end;
      False: begin
               Pen.Color := clWhite;
               Brush.Color := clWhite;
               Font.Color := clBlack;
             end;
    end;

    { TŁO }
    Rectangle(Rect);
    { IKONA }
    Draw(Rect.Left + 2, Rect.Top + 2, aFontIcons[1]);
    { TEKST }
    TextOut(Rect.Left + 28, Rect.Top + 4, List.Items[Index]);

    { USUNIĘCIE STANDARDOWEJ RAMKI PODŚWIETLAJĄCEJ }
    if odFocused in State then
    begin
      Pen.Color := Pen.Color xor $FFFFFF;
      DrawFocusRect(Rect);
    end;
  end;
end;

Rysowanie mamy już za sobą. Powyższe algorytmy oczywiście można połączyć w jeden, który rozróżnia czy będzie rysowana czcionka czy jej rozmiar. Zaprezentowany sposób jednak jest prostrzy w zrozumieniu dla początkujących.

Rezultat zastosowania kodów zdarzeń OnDrawItem komponentów przedstawia poniższy rysunek:

FontOwnCombos.png

Oczywiście to tylko przykładowy wygląd - można go ustalić dowolnie, według własnego uznania i potrzeb dla projektu.


Modyfikowanie tekstu podglądu czcionki

Skoro mamy już okienko podglądu z przykładowym tekstem - musimy podczas zmiany stanu kontrolek odpowiednio modyfikować tekst kontrolki pnlPreview. Do tego celu napiszemy sobie dwie procedury - jedna będzie ustalać krój czcionki i jej rozmiar, a druga atrybuty. Oprogramujemy jedynie dwa zdarzenia (odpowiednio dla kontrolek z klasy TComboBox oraz TCheckBox) i podepniemy je pod resztę kontrolek z tej samej klasy.

Najpierw zajmijmy się listami - zdarzenie OnChange kontrolki cbFontName:

procedure TFontForm.cbFontNameChange(Sender: TObject);
begin
  with pnlPreview.Font do
  begin
    Name := cbFontName.Text;
    Size := StrToInt(cbFontSize.Text);
  end;
end;

To zdarzenie podpinamy pod OnChange kontrolki cbFontSize. Następnie trzeba oprogramować zdarzenie OnClick komponentu cbBold:

procedure TFontForm.cbBoldClick(Sender: TObject);
begin
  with pnlPreview.Font do
  begin
    { POGRUBIENIE }
    case cbBold.Checked of
      True:  Style := Style + [fsBold];
      False: Style := Style - [fsBold];
    end;
    { KURSYWA }
    case cbItalic.Checked of
      True:  Style := Style + [fsItalic];
      False: Style := Style - [fsItalic];
    end;
    { PODKREŚLENIE }
    case cbUnderline.Checked of
      True:  Style := Style + [fsUnderline];
      False: Style := Style - [fsUnderline];
    end;
    { PRZEKREŚLENIE }
    case cbStrikeOut.Checked of
      True:  Style := Style + [fsStrikeOut];
      False: Style := Style - [fsStrikeOut];
    end;
  end;
end;

i podpinamy je pod wszystkie komponenty z klasy TCheckBox formularza.


Klasa dialogu

Formularz mamy już w całości oprogramowany - teraz trzeba napisać klasę, która będzie go wykorzystywać i pobierać z niego informacje do swoich pól. PIerwszym krokiem będzie ustalenie typu danych, jakie będzie przechowywał dialog - w tym przykładzie zadeklarujemy sobie rekord przechowyjący podstawowe informacje o czcionce:

type
  TFontInfoRec = record
    { NAZWA CZCIONKI }
    Name: TFontName;
    { ROZMIAR }
    Size: Integer;
    { ATRYBUTY }
    Style: TFontStyles;
  end;

Następnie musimy napisać wspomnianą klasę. Przykładowa deklaracja takiej klasy:

type
  TFontDialog = class(TObject)
  private
    FFontDialogForm: TFontForm;
    FFontInfo: TFontInfoRec;
    procedure DialogClose(Sender: TObject; var Action: TCloseAction);
  public
    constructor Create();
    function Execute(Owner: TComponent; FontName: TFontName; FontSize: Integer;
                     FontStyle: TFontStyles): Boolean;
  public
    property Font: TFontInfoRec read FFontInfo;
  end;

Opis wykorzystanych elementów w klasie:

Identyfikator Opis
FFontDialogForm prywatne pole - zaprojektowany wcześniej formularz
FFontInfo rekord do przechowania informacji o czcionce
DialogClose własna procedura zamknięcia okna - w niej odbywać się będzie przechwytywanie informacji
Create konstruktor klasy
Execute funkcja ta będzie tworzyć i pokazywać formularz
Font informacje o czcionce pobrane z okna

Konstruktor klasy

Teraz trzeba oprogramować po kolei każdą metodę - rozpoczynamy od konstruktora klasy, w którym musimy utworzyć obiekt dialogu oraz zainicjować formularz:

constructor TFontDialog.Create();
begin
  inherited Create();
  FFontDialogForm := nil;
end;

Własna procedura zamknięcia formularza

Następnie kodujemy metodę CloseDialog - najpierw przepisujemy właściwości komponentu pnlPreview do pola FFontInfo, po czym zamykamy i nil'ujemy formularz:

procedure TFontDialog.DialogClose(Sender: TObject; var Action: TCloseAction);
begin
  with FFontDialogForm do
  begin
    { NAZWA CZCIONKI }
    FFontInfo.Name := pnlPreview.Font.Name;
    { ROZMIAR }
    FFontInfo.Size := pnlPreview.Font.Size;
    { ATRYBUTY }
    FFontInfo.Style := pnlPreview.Font.Style;
  end;

  Action := caFree;
  FFontDialogForm := nil;
end;

W tej właśnie procedurze przed zamknięciem formularza przechwytujemy interesujące nas informacje i wpisujemy je do prywatnych pól klasy dialogu. Jeśli tego nie zrobimy - formularz zostanie zamknięty a dane zostaną utracone bezpowrotnie.


Metoda Execute

Kolejnym krokiem jest oprogramowanie metody Execute. W pierwszej kolejności musimy sprawdzić czy formularz jest już utworzony i jeśli nie - utworzyć go. Następnie przypisujemy mu nową procedurę zamknięcia - DialogClose, po czym na podstawie podanych parametrów funkcji uzupełniamy pola rekordu FFontInfo - będą to wartości domyślne. W następnej kolejności musimy ustawić właściwości komponentów na formularzu także w oparciu o wartości argumentów funkcji. Ostatnią czynnością jest pokazanie okna i oczekiwanie na zwróconą wartość modalną, która wpisana zostanie do rezultatu funkcji.

Poniżej przykładowy kod funkcji Execute:

function TFontDialog.Execute(Owner: TComponent; FontName: TFontName; FontSize: Integer;
                             FontStyle: TFontStyles): Boolean;
var
  iNameIndex, iSizeIndex: Integer;
begin
  { TOWRZENIE FORMULARZA }
  if not Assigned(FFontDialogForm) then
    FFontDialogForm := TFontForm.Create(Owner);

  with FFontDialogForm do
  begin
    { PRZYPISANIE WŁASNEJ PROCEDURY ZAMKNIĘCIA }
    OnClose := DialogClose;

    { WYPEŁNIENIE PÓL REKORDU KLASY DIALOGU }
    FFontInfo.Name := FontName;
    FFontInfo.Size := FontSize;
    FFontInfo.Style := FontStyle;

    { USTAWIENIE WŁAŚCIWOŚCI KOMPONENTÓW }
    { NAZWA CZCIONKI }
    iNameIndex := cbFontName.Items.IndexOf(FontName);

    if iNameIndex <> -1 then
      cbFontName.ItemIndex := iNameIndex
    else
      cbFontName.ItemIndex := cbFontName.Items.IndexOf('Tahoma');

    { ROZMIAR CZCIONKI }
    iSizeIndex := cbFontSize.Items.IndexOf(IntToStr(FontSize));

    if iSizeIndex <> -1 then
      cbFontSize.ItemIndex := iSizeIndex
    else
      cbFontSize.ItemIndex := cbFontSize.Items.IndexOf('8');

    { ODŚWIEŻENIE PANELU PODGLĄDU }
    cbFontNameChange(cbFontName);

    { STYL CZCIONKI }
    cbBold.Checked := fsBold in FontStyle;
    cbItalic.Checked := fsItalic in FontStyle;
    cbUnderline.Checked := fsUnderline in FontStyle;
    cbStrikeOut.Checked := fsStrikeOut in FontStyle;

    { WYWOŁANIE OKNA DIALOGU I PRZECHWYCENIE WARTOŚCI MODALNEJ }
    Result := ShowModal() = mrOk;
  end;
end;

Teraz wystarczy tylko wybrać odpowiednie ustawienia czcionki i kliknąć w przycisk Zapisz ustawienia.


Wykorzystanie klasy dialogu

Aby wykorzsytać nasz dialog należy zadeklarować zmienną typu TFontDialog. Po utworzeniu wywołać metodę Execute z podanymi parametrami i w razie poprawnego zamnięcia dialogu (funkcja Execute zwróci True) wykorzystać właściwość Font dialogu.

Przykład wykorzystania dialogu:

procedure TMainForm.btnFontSettingsClick(Sender: TObject);
var
  dlgFont: TFontDialog;
begin
  dlgFont := TFontDialog.Create();

  try
    if dlgFont.Execute(Self, 'Tahoma', 10, [fsItalic, fsUnderline]) then
    begin
      { WYKORZYSTANIE WŁAŚCIWOŚCI dlgFont.Font }
    end;
  finally
    dlgFont.Free();
  end;
end;

Powyższe kod spowoduje otworzenie okna dialogowego z ustawioną nazwą czcionki na Tahoma, rozmiar 10 oraz stylem pochylonym i podkreślonym. Efekt wykorzystania kodu zaprezentowany jest na poniższym rysunku:

FontDialog.png


Funkcja wykorzystująca klasę dialogu

Ręczne tworzenie obiektu dialogu jest trochę niewygodne. Stworzymy więc funkcję, która będzie wykorzystywać klasę dialogu i zwracać jako rezultat wartość logiczną, a przez referencję nowe właściwości czcionki.

Deklaracja funkcji wykorzystującej okno dialogowe:

function GetFontProps(Owner: TComponent; var FontInfo: TFontInfoRec; FontName: TFontName;
                      FontSize: Integer; FontStyle: TFontStyles): Boolean;

Funkcja jest dość prosta - posiada kilka parametrów:

Parametr Typ Opis
Owner TComponent właściciel modalnie wywoływanego okna dialogowego
FontInfo TFontInfoRec struktura danych czcionki, do której zostaną wpisane pobrane z okna dialogowego właściwości czcionki
FontName TFontName domyślna nazwa czcionki
FontSize Integer domyślny rozmiar czcionki
FontStyle TFontStyles atrybuty czcionki
W ciele klasy musimy przede wszystkim utworzuć obiekt dialogu, wywołać metodę Execute i jeśli poprawnie zamknięto okno (wciśnięto przycisk Zapisz ustawienia) przepisać informacje z właściwości Font dialogu to parametru. Definicja funkcji wygląda następująco:
function GetFontProps(Owner: TComponent; var FontInfo: TFontInfoRec; FontName: TFontName;
                      FontSize: Integer; FontStyle: TFontStyles): Boolean;
var
  dlgFont: TFontDialog;
begin
  Result := False;

  { UTWORZENIE OBIEKTU DIALOGU }
  dlgFont := TFontDialog.Create();

  try
    { WYWOŁANIE OKNA DIALOGOWEGO }
    if dlgFont.Execute(Owner, FontName, FontSize, FontStyle) then
    begin
      Result := True;

      { UZUPEŁNIENIE PARAMETRU FontInfo }
      FontInfo.Name := dlgFont.Font.Name;
      FontInfo.Size := dlgFont.Font.Size;
      FontInfo.Style := dlgFont.Font.Style;
    end;
  finally
    dlgFont.Free();
  end;
end;

Przykładowe wykorzystanie gotowej funkcji do ustalania czcionki komponentu memNote z klasy TMemo:

procedure TMainForm.btnFontSettingsClick(Sender: TObject);
var
  firNew: TFontInfoRec;
begin
  { WYWOŁANIE OKNA DIALOGOWEGO }
  if GetFontProps(Self, firNew, memNote.Font.Name, memNote.Font.Size, memNote.Font.Style) then
  begin
    { WYKORZYSTANIE POBRANYCH INFORMACJI }
    memNote.Font.Name := firNew.Name;
    memNote.Font.Size := firNew.Size;
    memNote.Font.Style := firNew.Style;
  end;
end;

Użycie pojedynczej funkcji jest znacznie wygodniejsze niż każdorazowe ręcznie tworzenie i obsługiwanie obiektu. Poza tym jeśli w kilku miejscach programu chcemy wykorzystać nasz dialog - tworząc funkcję zaoszczędzamy na ilości linii kodu programu.


Najważniejsze informacje

Tworząc własne okno dialogowe należy pamiętać przede wszystkim o:

  • wewnątrz konstruktora klasy tworzymy obiekt dialogu i nil'ujemy formularz,
  • w ciele procedury DialogClose dokonujemy kopiowania danych z formularza do prywatnego pola klasy, po czym zamykamy i zwalniamy formularz z pamięci,
  • metoda Execute służy do tworzenia formularza, ustawienia właściwości komponentów, pokazania okna na ekranie oraz przechwycenia wartości modalnej zwróconej przez okno,
  • w zdarzeniu OnClick odpowiedniego przycisku ustalamy szereg warunków, które zablokują możliwość wybrania przez użytkownika złych danych lub nie wybrania ich wcale, a po sprawdzeniu stanu kontrolek przypisaniu formularzowi odpowiedniej wartości modalnej (tutaj zastosowałem wartość mrOk po kliknięciu w przycisk btnSaveSettings, lecz nie było wymagane spradzanie poprawności wprowadzonych danych);

Zakończenie

W wymieniony wyżej sposób mozemy tworzyć dowolne okna, ustalać im kontrolki oraz ich zachowania według własnych potrzeb oraz zwracać dane dowolnego typu, czy to będą typy proste jak String, Boolean, Byte czy złożone jak struktury danych, macierze itd.. Mam nadzieję, że z artykułu będą korzystać przede wszystkim początkujący programiści, którzy nie znali jeszcze możliwości tworzenia w ten sposób własnych okien dialogowych.

Jeżeli ma ktoś jakieś pytania bądź sugestie bardzo proszę o kontakt. Dziękuję za zainteresowanie tematem.

2 komentarzy

Muzeum Programowania ;)

Początkującym na pewno się przyda i włożyłeś w to dużo pracy. Z grubsza czytając, bardzo dobrze napisany - prostym językiem :D
Pozdrawiam :)