WinForms
Adam Boduch
Windows Forms jest biblioteką komponentów zaprojektowaną na potrzeby .NET dostępną dla każdego języka wykorzystującego ową platformę. Mimo wielu podobieństw pomiędzy WinForms a VCL.NET istnieje równie dużo różnic, co sprawia, iż proces migracji z VCL.NET do WinForms jest o wiele trudniejszy.
1 Brak pliku <em>.dfm/</em>.nfm
1.1 Dynamiczne tworzenie komponentów w WinForms
1.2 Zdarzenia dla komponentów
2 VCL i WinForms
2.3 Przykład: wykorzystanie formularza VCL
Oto krótka lista różnic pomiędzy WinForms, a VCL.NET:
- Nazwy komponentów różnią się (nie ma
TButton
? jest po prostuButton
itp.); - Właściwość
Location
w miejsceLeft
,Top
; - Właściwość
Size
zamiastWidth
iHeight
; - Inne nazwy zdarzeń;
- Właściwość
Text
zamiastCaption
; - Brak niektórych komponentów;
- Inny sposób wyświetlania formularza;
- Inny sposób tworzenia obiektów na formularzu;
- Brak pliku formularza (.dfm/.nfm);
- Brak właściwości
ComponentCount
;
Aby skorzystać z biblioteki WinForms do listy Uses musisz dodać modułSystem.Windows.Forms
. Pamiętaj również o tym, aby w pliku *.dpr znalazła się odpowiednia deklaracja włączająca podzespółSystem.Windows.Forms.dll
:
{%DelphiDotNetAssemblyCompiler '$(SystemRoot)\microsoft.net\framework\v1.1.4322\System.Windows.Forms.dll'}
Brak pliku .dfm/.nfm
W przypadku WinForms informacje na temat komponentów nie są zawarte w żadnych plikach zewnętrznych. Wszystkie informacje na temat komponentów (właściwości, zdarzenia) są zawarte w kodzie źródłowym, w procedurze InitializeComponent
:
procedure TWinForm2.InitializeComponent;
begin
Self.Components := System.ComponentModel.Container.Create;
Self.Size := System.Drawing.Size.Create(300, 300);
Self.Text := 'WinForm2';
end;
Umieść teraz na formularzu komponent Panel; zwróć teraz uwagę na zawartość procedury InitializeComponent
:
procedure TWinForm3.InitializeComponent;
begin
Self.Panel1 := System.Windows.Forms.Panel.Create;
Self.SuspendLayout;
//
// Panel1
//
Self.Panel1.Location := System.Drawing.Point.Create(32, 80);
Self.Panel1.Name := 'Panel1';
Self.Panel1.TabIndex := 0;
//
// TWinForm3
//
Self.AutoScaleBaseSize := System.Drawing.Size.Create(5, 13);
Self.ClientSize := System.Drawing.Size.Create(292, 273);
Self.Controls.Add(Self.Panel1);
Self.Name := 'TWinForm3';
Self.Text := 'WinForm2';
Self.ResumeLayout(False);
end;
Nie należy zmieniać poszczególnych właściwości komponentów bezpośrednio w kodzie źródłowym ? najpewniejszym sposobem jest skorzystanie z Inspektora Obiektów.
Komponent Panel
(TPanel
w VCL.NET) jest charakterystycznym komponentem służącym do przechowywania innych obiektów i jego rola ograniczenia się praktycznie tylko do bycia rodzicem dla innych komponentów.
Procedura InitializeComponent
zawiera instrukcje potrzebne do tworzenia komponentów, a ona z kolei jest wywoływana przez konstruktor głównej klasy WinForms ? w naszym wypadku ? TWinForm3
.
Dynamiczne tworzenie komponentów w WinForms
Czasami istnieje konieczność tworzenia komponentów w sposób dynamiczny ? tj. podczas działania programu. Nie jest to trudne ? należy jedynie wywołać konstruktor odpowiedniego obiektu i określić jego parę podstawowych właściwości.
Umieść na formularzu komponent Button
; w naszym przykładzie zaprezentuje sposób stworzenia innego przycisku na komponencie Panel
. Oto fragment kodu:
procedure TWinForm3.Button1_Click(sender: System.Object; e: System.EventArgs);
var
MyButton : System.Windows.Forms.Button;
begin
MyButton := System.Windows.Forms.Button.Create;
MyButton.Location := System.Drawing.Point.Create(1, 1);
MyButton.Name := 'MyButton';
MyButton.Text := 'MyButton';
Panel1.Controls.Add(MyButton);
end;
Na samym początku należy zadeklarować zmienną wskazującą na odpowiednią klasę ? w tym przypadku System.Windows.Forms.Button
(w VCL.NET odpowiednikiem jest po prostu klasa TButton
).
W następnym kroku wywołujemy konstruktor klasy, a następnie określamy jego położenie (właściwość Location
), nazwę oraz tekst wyświetlany na obiekcie.
Ostatnim krokiem jest wywołanie metody Add
i podanie w parametrze nazwy obiektu, który ma zostać dodany do komponentu.
Odpowiednikiem powyższego kodu WinForms byłby następujący z VCL.NET:
var
MyButton : TButton;
begin
MyButton := TButton.Create(Panel1); // wywołanie konstruktora
MyButton.Parent := Panel1; // rodzic dla obiektu
MyButton.Left := 1; // położenie w poziomie
MyButton.Top := 1; // położenie w pionie
MyButton.Name := 'MyButton'; // nazwa
MyButton.Caption := 'MyButton'; // tekst na obiekcie
end;
Zdarzenia dla komponentów
W przypadku WinForms przypisanie zdarzenia dla komponentu odbywa się zupełnie inaczej niż w przypadku VCL.NET. Otóż służy do tego funkcja Include, którą stosuje się w następujący sposób:
Include(MyButton.Click, Button1_Click)
Pierwszy parametr określa nazwę zdarzenia ? w tym wypadku ? Click
. Odpowiednikiem zdarzenia Click
w VCL.NET jest OnClick
.
Drugi parametr to nazwa procedury zdarzeniowej która zostanie przypisana do określonego zdarzenia.
Poniżej znajduje się listing przykładowego programu. Naciśnięcie przycisku spowoduje dynamiczne stworzenie kolejnego. Do każdego nowo tworzonego obiektu przypisywana jest ta sama procedura zdarzeniowa. Rezultat działania programu przedstawiony został na rysunku.
unit WinForm;
interface
uses
System.Drawing, System.Collections, System.ComponentModel,
System.Windows.Forms, System.Data;
type
TWinForm = class(System.Windows.Forms.Form)
{$REGION 'Designer Managed Code'}
strict private
/// <summary>
/// Required designer variable.
/// </summary>
Components: System.ComponentModel.Container;
Button1: System.Windows.Forms.Button;
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
procedure InitializeComponent;
procedure Button1_Click(sender: System.Object; e: System.EventArgs);
{$ENDREGION}
strict protected
/// <summary>
/// Clean up any resources being used.
/// </summary>
procedure Dispose(Disposing: Boolean); override;
private
{ Private Declarations }
public
constructor Create;
end;
[assembly: RuntimeRequiredAttribute(TypeOf(TWinForm))]
implementation
{$REGION 'Windows Form Designer generated code'}
/// <summary>
/// Required method for Designer support -- do not modify
/// the contents of this method with the code editor.
/// </summary>
procedure TWinForm.InitializeComponent;
begin
Self.Button1 := System.Windows.Forms.Button.Create;
Self.SuspendLayout;
//
// Button1
//
Self.Button1.Location := System.Drawing.Point.Create(8, 416);
Self.Button1.Name := 'Button1';
Self.Button1.Size := System.Drawing.Size.Create(144, 23);
Self.Button1.TabIndex := 1;
Self.Button1.Text := 'Naciśnij mnie!';
Include(Self.Button1.Click, Self.Button1_Click);
//
// TWinForm
//
Self.AutoScaleBaseSize := System.Drawing.Size.Create(5, 13);
Self.ClientSize := System.Drawing.Size.Create(512, 453);
Self.Controls.Add(Self.Button1);
Self.Name := 'TWinForm';
Self.Text := 'Tworzenie obiektów';
Self.ResumeLayout(False);
end;
{$ENDREGION}
procedure TWinForm.Dispose(Disposing: Boolean);
begin
if Disposing then
begin
if Components <> nil then
Components.Dispose();
end;
inherited Dispose(Disposing);
end;
constructor TWinForm.Create;
begin
inherited Create;
//
// Required for Windows Form Designer support
//
InitializeComponent;
//
// TODO: Add any constructor code after InitializeComponent call
//
end;
procedure TWinForm.Button1_Click(sender: System.Object; e: System.EventArgs);
var
MyButton : System.Windows.Forms.Button;
begin
MyButton := System.Windows.Forms.Button.Create;
Randomize;
MyButton.Location := System.Drawing.Point.Create(Random(Size.Width), Random(Size.Height));
MyButton.Size := System.Drawing.Size.Create(144, 23);
MyButton.Text := 'Naciśnij mnie!';
Include(MyButton.Click, Button1_Click); // przypisz zdarzenie
Controls.Add(MyButton);
end;
end.
VCL i WinForms
Jako, że przekształcenie kodu VCL do WinForms jest nieco kłopotliwym zadaniem, dobrym rozwiązaniem może okazać się korzystanie z obu bibliotek jednocześnie. Z punktu widzenia kompilatora nie ma to najmniejszego znaczenia, a nam pozwoli oszczędzić czas. Jedyną wadą takiego rozwiązania jest większy rozmiar aplikacji wynikowej niżeli ma to miejsce przy zastosowaniu jedynie WinForms.
Z punktu widzenia kompilatora nie ma znaczenia, czy korzystamy z przestrzeni nazw stricte związanych z .NET, czy z modułów charakterystycznych dla Delphi ? np. Windows, SysUtils itd. Jednak użycie modułów charakterystycznych dla Delphi, wiąże się z tym, że nasza aplikacja nie będzie mogła działać na innych platformach, na których zostanie zaimplementowane .NET. Wszystko dlatego, że np. ? moduł Windows korzysta z bibliotek Win32, charakterystycznych jedynie dla systemu Windows.
Możemy tworzyć aplikacje wykorzystujące zarówno VCL.NET i WinForms, w jednym projekcie. Obowiązuje tu jednak pewna zasada: jeden moduł może zawierać albo kod formularza VCL.NET albo WinForms.
Przykład: wykorzystanie formularza VCL
Napisałem kiedyś bardzo prosty przykładowy program prezentujący działanie klasy TApplication
. Program wyświetlał po prostu na etykiecie (komponent TLabel
) ścieżkę do programu. Program został napisany w Delphi 7, a jego jedyną metodą była procedura zdarzeniowa OnCreate
:
procedure TMainForm.FormCreate(Sender: TObject);
begin
Label1.Caption := Application.ExeName;
end;
Zaprezentuje teraz jak wykorzystać ten formularz w Delphi 8.
#Utwórz nowy projekt Windows Forms;
#Zapisz projekt na dysku;
#Do katalogu z projektem skopiuj pliki formularza Delphi 7 (.pas oraz .dfm).
Kolejnym krokiem będzie w naszej aplikacji, w formularzu głównym WinForms dodanie modułu MainFrm
do listy Uses (pod warunkiem oczywiście, że tak jak w moim wypadku ? nazwa pliku z formularzem to MainFrm.pas).
uses
System.Drawing, System.Collections, System.ComponentModel,
System.Windows.Forms, System.Data, MainFrm; // <-- nasz moduł
Umieść na formularzu komponent Button
oraz wygeneruj jego zdarzenie Click
:
procedure TWinForm2.Button1_Click(sender: System.Object; e: System.EventArgs);
begin
TMainForm.Create(nil).ShowModal;
end;
Jedyna linia znajdująca się wewnątrz procedury zdarzeniowej powoduje utworzenie i wyświetlenie formularza TMainForm
. Sam więc widzisz, że z wykorzystaniem formularzy VCL nie ma w Delphi 8 żadnych problemów.
Naturalnie zaprezentowałem tutaj bardzo prosty przykład, który bezproblemowo podlegał kompilacji zarówno na Delphi 8 jak i starszych wersjach. W przypadku bardziej skomplikowanych projektów, należałoby zmodyfikować kod formularza (plik *.pas) według wskazówek przedstawionych wcześniej, w tym dokumencie.