Programowanie w języku Delphi » .NET

WinForms

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.



Oto krótka lista różnic pomiędzy WinForms, a VCL.NET:
  • Nazwy komponentów różnią się (nie ma TButton ? jest po prostu Button itp.);
  • Właściwość Location w miejsce Left, Top;
  • Właściwość Size zamiast Width i Height;
  • Inne nazwy zdarzeń;
  • Właściwość Text zamiast Caption;
  • 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.
  1. Utwórz nowy projekt Windows Forms;
  2. Zapisz projekt na dysku;
  3. 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.