LCL Internals/ja
│
English (en) │
español (es) │
日本語 (ja) │
русский (ru) │
LCLの内部
LCLとその「インタフェース」があります。 LCLはプラットホーム独立を担う部分です。そして、それはlazarus/lcl/ディレクトリにあります。 このディレクトリは主要なクラス定義を含んでいます。 多くのさまざまなコントロールが、様々な.incファイルの中のlazarus/lcl/include/ディレクトリに実装されています。これは、より速く特定のコントロールの実装を見つけるためです。TCustomMemoを例にとると、それは、custommemo.incにあります。 どの.incファイルも、{%MainUnit...}ではじまり、どこでそれがインクルードされているかを定義しています。
そして、lazarus/lcl/interfaces/内のサブディレクトリに、「インターフェース」があります。 gtkインターフェースは、gtk/以下に、Win32インターフェースは win32/以下に、というふうに。 それらすべては、LCLで使われ、メインのインターフェースオブジェクトで生成されるインターフェースユニットを持っています。 たいてい、メインのインターフェースオブジェクトは、XXint.pp(win32int.ppなど)で定義されており、いろんなincファイルで実装されています。 XXobject.incは、特別なメソッドのインターフェースです。 XXwinapi.incは、winapiの実装メソッドです。 XXlistsl.incは、TComboBox,TListBoxなどのコントロールで使われるStringListの実装です。 XXcallback.incはウイジェットイベントのハンドリングと、適切にLCLへ通知をおこなうものです。
すべてのコントロールは、WidgetSetClassプロパティをもっています。これは、インターフェースディレクトリのミラーのクラスです。たとえば、TCustomEditのミラーは、TWSCustomEditです。 このメソッドは、win32wsstdctrlsの内部のTWin32WSCustomEditで実装されています。 これは、LCLがインターフェースと対話する方法で、インターフェースが何か仕事をする方法です。
インターフェースがLCLへコミュニケーションの返答をするのは、ほとんどメッセージ(たいてい、'DeliverMessage')を送ることによってなされます。これは、TControl.Perform(<message_id>, wparam, lparam)で、wparamとlparamに、いろんな情報を入れて送ります。
新しいWidgetsetを生成する方法
これは、新しいwidgetsetを開発する初歩的なチュートリアルです。 この方法は、新しいqt4インターフェースを作るときの私の経験によるものです。
どうして新しいWidgetsetを追加したくなるのでしょうか。もっと多くのプラットフォームのサポートを追加するために!なぜでしょう。 基本的には、再コンパイルさえすれば、他のいろんなプラットフォームで動作するという、Lazarusのもっとも有利な点を改善するためです。 だから、新しいwidgetsetを追加したいんだ、と言ってみましょう。 まず最初に、widgetのpascalバインディングを取得し、どのようにそれを使うか、知る必要があります。 これは数時間ネット上の基本的なチュートリアルをやってもらえば、それほど難しいことではありません。 それらのチュートリアルをpascalに翻訳すれば、それが導入には充分です。
Qt4を使うとき、私はパスカル向けのDen Jean qt4 bindingsを使いました。そして、とても基本的なプログラムをしました。これがそのプログラムです。
program qttest; uses qt4; var App: QApplicationH; MainWindow: QMainWindowH; begin App := QApplication_Create(@argc,argv); MainWindow := QMainWindow_Create; QWidget_show(MainWindow); QApplication_Exec; end.
上のプログラムはqt4のプログラムです。これで、Qtプログラムが出来てしまうなんて、素敵ですね。 でも、ちょっとまって下さい。これが本当につくりたかったものではないんです。 私がやりたいのは、すでに存在するアプリケーションを再コンパイルして、Qtとリンクするだけのものですよ ! 私は上のアプリケーションを次のようにします。
program qttest;
{$mode objfpc}{$H+}
uses
Interfaces, Classes, Forms,
{ Add your units here }
qtform;
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
こうすることで、フォームがLazarus IDEでメンテナンスできて、ビジュアルな設計が可能になります。 それでは、Qt用のLCLのWidgetsetをつくってみます。
最初に新しいwidgetsetでやることは、空っぽの骨組みをつくることです。 初期のqtやcarbonのようなwidgetsetsの開発では、骨組みからはじめます。
多くのwidgetsのファイルを注意深くみると、lclのInterfaces.pasからコールされる最初にファイルを見つけることができます。 このファイルは他のQtInt.pasやそういったファイルをコールするだけです。 QtInt.pasには私達が実装すべきTWidgetSetクラスのためのコードがあります。 空っぽの骨組みでは、この関数が実装されるべきいろんな関数を持っていることに気が付くでしょう。
TQtWidgetSet = Class(TWidgetSet)
private
App: QApplicationH;
public
{$I qtwinapih.inc}
{$I qtlclintfh.inc}
public
// Application
procedure AppInit(var ScreenInfo: TScreenInfo); override;
procedure AppRun(const ALoop: TApplicationMainLoop); override;
procedure AppWaitMessage; override;
procedure AppProcessMessages; override;
procedure AppTerminate; override;
procedure AppMinimize; override;
procedure AppBringToFront; override;
public
constructor Create;
destructor Destroy; override;
function DCGetPixel(CanvasHandle: HDC; X, Y: integer): TGraphicsColor; override;
procedure DCSetPixel(CanvasHandle: HDC; X, Y: integer; AColor: TGraphicsColor); override;
procedure DCRedraw(CanvasHandle: HDC); override;
procedure SetDesigning(AComponent: TComponent); override;
function InitHintFont(HintFont: TObject): Boolean; override;
// create and destroy
function CreateComponent(Sender : TObject): THandle; override; // deprecated
function CreateTimer(Interval: integer; TimerFunc: TFNTimerProc): integer; override;
function DestroyTimer(TimerHandle: integer): boolean; override;
end;
新しいウインドウコンポーネントを実装する方法
ウインドウコンポーネントは、TWinControlからの派生です。 これらのコントロールはHandleなどといったものを持っていて、Widgetsetから作られるべきです。新しいウインドウコンポーネントをwidgetsetに追加するのは簡単です。
それでは、Qt WidgetsetにTQtWSCustomEditを追加してみましょう。
TCustomEditからはじめるために、StdCtrlsユニットのTWinControlの派生にすることです。
QtWSStrCtrlsユニットのTQtWSCustomEditの宣言を見てみましょう。
TQtWSCustomEdit = class(TWSCustomEdit) private protected public end;
TWSCustomEditで宣言されている静的メソッドを追加し、オーバーライドします。コードは次のようになるでしょう。
TQtWSCustomEdit = class(TWSCustomEdit)
private
protected
public
class function CreateHandle(const AWinControl: TWinControl;
const AParams: TCreateParams): HWND; override;
class procedure DestroyHandle(const AWinControl: TWinControl); override;
{ class function GetSelStart(const ACustomEdit: TCustomEdit): integer; override;
class function GetSelLength(const ACustomEdit: TCustomEdit): integer; override;
class procedure SetCharCase(const ACustomEdit: TCustomEdit; NewCase: TEditCharCase); override;
class procedure SetEchoMode(const ACustomEdit: TCustomEdit; NewMode: TEchoMode); override;
class procedure SetMaxLength(const ACustomEdit: TCustomEdit; NewLength: integer); override;
class procedure SetPasswordChar(const ACustomEdit: TCustomEdit; NewChar: char); override;
class procedure SetReadOnly(const ACustomEdit: TCustomEdit; NewReadOnly: boolean); override;
class procedure SetSelStart(const ACustomEdit: TCustomEdit; NewStart: integer); override;
class procedure SetSelLength(const ACustomEdit: TCustomEdit; NewLength: integer); override;
class procedure GetPreferredSize(const AWinControl: TWinControl;
var PreferredWidth, PreferredHeight: integer); override;}
end;
コードのコメントされた部分はTCustomEditにとって完全に機能するように実装する必要のある関数です。CreateHandleとDestroyHandleだけは、フォームに表示して編集できるようにするために、必要充分なことをします。これで、この記事でやることを充分満たしています。
CTRL+SHIFT+Cを押して、コード補完をします。そして、CreateHandleとDestroyHandleを実装します。Qt4の場合は次のようになります。
{ TQtWSCustomEdit }
class function TQtWSCustomEdit.CreateHandle(const AWinControl: TWinControl;
const AParams: TCreateParams): HWND;
var
Widget: QWidgetH;
Str: WideString;
begin
// Creates the widget
WriteLn('Calling QTextDocument_create');
Str := WideString((AWinControl as TCustomMemo).Lines.Text);
Widget := QTextEdit_create(@Str, QWidgetH(AWinControl.Parent.Handle));
// Sets it's initial properties
QWidget_setGeometry(Widget, AWinControl.Left, AWinControl.Top,
AWinControl.Width, AWinControl.Height);
QWidget_show(Widget);
Result := THandle(Widget);
end;
class procedure TQtWSCustomEdit.DestroyHandle(const AWinControl: TWinControl);
begin
QTextEdit_destroy(QTextEditH(AWinControl.Handle));
end;
そして、ユニットの下のほうの"RegisterWSComponent(TCustomEdit, TQtWSCustomEdit);"といった部分のコメントを外します。
これで、TCustomEditをフォームの下のほうへドロップすることができ、動くことが期待できます。
- ^)
TBitmapを実装する
TBitmapや他のグラフィカルなオブジェクトを実装することは、コメントがされていないqtwinapi.pasファイルの特別な関数を使っているので、骨が折れる作業かもしれません。
しかし、下のコードをコンパイルしてみてください。
var
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.LoadFromFile('myfile.bmp');
Canvas.Draw(Bitmap, 0, 0);
finally
Bitmap.Free;
end;
end;
次のものは、実行時、上のコードでwidgetsetインターフェースがコールされるときに、関数が呼ばれる順序です。
1 - GetDC(0);
デバイスコンテキストを生成するだけです。
2 - GetDeviceRawImageDescription
Qtで内部のピクセルフォーマットを記述するユーティリティです。
3 - CreateBitmapFromRawImage
ここでネイティブなイメージオブジェクトを生成する必要があり、RawDataからロードします。 イメージデータに関する情報は、2でおこなったピクセルフォーマットに基づいて格納されています。
インターフェースがどのように機能するか示す例
下記は単純な例です。あるボタンコンポーネントを思い浮かべてください。 どうやってLCLでは別々のプラットフォームに対して実装されているのでしょうか。
下記のファイルがあると思います。
\trayicon.pas
\wstrayicon.pas
\gtk\gtkwstrayicon.pas
\gtk\trayintf.pas
\win32\win32wstrayicon.pas
\win32\trayintf.pas
この方法で、ifdefをなくすようにできます。
正しいWSTray classを初期化するためのtrayinf.pasを加えるために、ユニットパス に$(LCLWidgetType)を加える必要があります。
trayicon.pasの中に、wstrayiconをインクルードします。LCLクラスから派生してメインクラスをつくって、wstrayiconをimplementation部でusesしてください。widgetsetと通信できるすべてのLCLクラスは、LCLClasses ユニットで宣言されているTLCLComponentから派生されています。
unit TrayIcon;
interface
type
TTrayIcon = class(TLCLComponent)
public
procedure DoTray;
end;
implementation
uses wstrayicon;
procedure TTrayIcon.DoTray;
begin
// Call wstrayicon
end;
end.
trayintfの内部で、trayintfファイルであるgtkwstrayiconか、win32trayiconを使います。 wstrayiconの内部で、次のようにクラスを作ります。
unit WSTrayIcon; uses WSLCLClasses, Controls, TrayIcon; // and other things as well TWSTrayIcon = class of TWSTrayIcon; TWSTrayIcon = class(TWSWinControl); public class procedure EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon); virtual; // these must all be virtual and class procedures!! class procedure RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon); virtual; .... end; ... implementation procedure TWSTrayIcon.EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon); begin //do nothing end; procedure TWSTrayIcon.RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon); begin //do nothing end;
gtkwstrayicon.pasでは、このようにしています。
uses WSTrayIcon, WSLCLClasses, Controls, TrayIcon, gtk, gdk; TGtkWSTrayIcon = class(TWSTrayIcon); private class function FindSystemTray(const ATrayIcon: TCustomTrayIcon): TWindow; virtual; public class procedure EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon); override; class procedure RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon); override; class function CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): HWND; override; .... end; ... implementation procedure TGtkWSTrayIcon.CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): HWND; var WidgetInfo: PWidgetInfo; begin Result := gtk_plug_new; WidgetInfo := CreateWidgetInfo(AWinControl, Result); // it's something like this anyway TGtkWSWincontrolClass(WidgetSetClass).SetCallbacks(AWinControl); // and more stuff end; function TGtkWSTrayIcon.FindSystemTray(const ATrayIcon: TCustomTrayIcon): TWindow; begin // do something end; procedure TGtkWSTrayIcon.EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon); var SystemTray: TWindow; begin SystemTray := FindSystemTray(ATrayIcon); //do something end; procedure TGtkWSTrayIcon.RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon); begin //do something end; ...... initialization RegisterWSComponent(TCustomTrayIcon, TGtkWSTrayIcon); //this is very important!!! end.
そうすると、trayicon.pasは最終的には次のようになります。
uses WSTrayIcon; //etc. you DON'T include GtkWSTrayIcon here! TCustomTrayIcon = class(TWinControl) public procedure EmbedControl; .... end; ... procedure TTrayIcon.EmbedControl; begin TWSTrayIconClass(WidgetSetClass).EmbedControl(Self); end;
このドキュメントは作業途中です。このドキュメントの章を書く手助けをしていただいてかまいません。 もし、このドキュメントの中で何か情報を探していて見つからなかった場合には、質問をdiscussion pageに入れてください。そうすることが、読みたいみなさんのレベルにあわせて、あまり過不足のないドキュメントを書く手助けになります。.