ウィキのコピーミラー

これは pascal.chat のウィキのミラーです。編集、履歴、ログインはそちらで行います。

LCL Internals/ja

English · Español · Русский · 日本語

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を使いました。そして、とても基本的なプログラムをしました。これがそのプログラムです。

pascal
program qttest;
pascal
uses qt4;
pascal
var
  App: QApplicationH;
  MainWindow: QMainWindowH;
begin
  App := QApplication_Create(@argc,argv);
pascal
MainWindow := QMainWindow_Create;
pascal
QWidget_show(MainWindow);
pascal
QApplication_Exec;
pascal
end.

上のプログラムはqt4のプログラムです。これで、Qtプログラムが出来てしまうなんて、素敵ですね。 でも、ちょっとまって下さい。これが本当につくりたかったものではないんです。 私がやりたいのは、すでに存在するアプリケーションを再コンパイルして、Qtとリンクするだけのものですよ ! 私は上のアプリケーションを次のようにします。

pascal
program qttest;
pascal
{$mode objfpc}{$H+}
pascal
uses
  Interfaces, Classes, Forms,
  { Add your units here }
  qtform;
pascal
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クラスのためのコードがあります。 空っぽの骨組みでは、この関数が実装されるべきいろんな関数を持っていることに気が付くでしょう。

pascal
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;
pascal
function  InitHintFont(HintFont: TObject): Boolean; override;
pascal
// create and destroy
function CreateComponent(Sender : TObject): THandle; override; // deprecated
function CreateTimer(Interval: integer; TimerFunc: TFNTimerProc): integer; override;
function DestroyTimer(TimerHandle: integer): boolean; override;
pascal
end;

新しいウインドウコンポーネントを実装する方法

ウインドウコンポーネントは、TWinControlからの派生です。 これらのコントロールはHandleなどといったものを持っていて、Widgetsetから作られるべきです。新しいウインドウコンポーネントをwidgetsetに追加するのは簡単です。

それでは、Qt WidgetsetにTQtWSCustomEditを追加してみましょう。

TCustomEditからはじめるために、StdCtrlsユニットのTWinControlの派生にすることです。

QtWSStrCtrlsユニットのTQtWSCustomEditの宣言を見てみましょう。

pascal
TQtWSCustomEdit = class(TWSCustomEdit)
private
protected
public
end;

TWSCustomEditで宣言されている静的メソッドを追加し、オーバーライドします。コードは次のようになるでしょう。

pascal
TQtWSCustomEdit = class(TWSCustomEdit)
private
protected
public
  class function CreateHandle(const AWinControl: TWinControl;
        const AParams: TCreateParams): HWND; override;
  class procedure DestroyHandle(const AWinControl: TWinControl); override;
pascal
{    class function  GetSelStart(const ACustomEdit: TCustomEdit): integer; override;
    class function  GetSelLength(const ACustomEdit: TCustomEdit): integer; override;
pascal
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;
pascal
class procedure GetPreferredSize(const AWinControl: TWinControl;
                    var PreferredWidth, PreferredHeight: integer); override;}
pascal
end;

コードのコメントされた部分はTCustomEditにとって完全に機能するように実装する必要のある関数です。CreateHandleとDestroyHandleだけは、フォームに表示して編集できるようにするために、必要充分なことをします。これで、この記事でやることを充分満たしています。

CTRL+SHIFT+Cを押して、コード補完をします。そして、CreateHandleとDestroyHandleを実装します。Qt4の場合は次のようになります。

pascal
{ TQtWSCustomEdit }
pascal
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));
pascal
// Sets it's initial properties
QWidget_setGeometry(Widget, AWinControl.Left, AWinControl.Top,
 AWinControl.Width, AWinControl.Height);
pascal
QWidget_show(Widget);
pascal
Result := THandle(Widget);
pascal
end;
pascal
class procedure TQtWSCustomEdit.DestroyHandle(const AWinControl: TWinControl);
begin
  QTextEdit_destroy(QTextEditH(AWinControl.Handle));
end;

そして、ユニットの下のほうの"RegisterWSComponent(TCustomEdit, TQtWSCustomEdit);"といった部分のコメントを外します。

これで、TCustomEditをフォームの下のほうへドロップすることができ、動くことが期待できます。

^)

TBitmapを実装する

TBitmapや他のグラフィカルなオブジェクトを実装することは、コメントがされていないqtwinapi.pasファイルの特別な関数を使っているので、骨が折れる作業かもしれません。

しかし、下のコードをコンパイルしてみてください。

pascal
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から派生されています。

pascal
unit TrayIcon;
pascal
interface
pascal
type
  TTrayIcon = class(TLCLComponent)
  public
    procedure DoTray;
  end;
pascal
implementation
pascal
uses wstrayicon;
pascal
procedure TTrayIcon.DoTray;
begin
  // Call wstrayicon
end;
pascal
end.

trayintfの内部で、trayintfファイルであるgtkwstrayiconか、win32trayiconを使います。 wstrayiconの内部で、次のようにクラスを作ります。

pascal
unit WSTrayIcon;
pascal
uses WSLCLClasses, Controls, TrayIcon; // and other things as well
pascal
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;
...
pascal
implementation
pascal
procedure TWSTrayIcon.EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon);
begin
 //do nothing
end;
pascal
procedure TWSTrayIcon.RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon);
begin
 //do nothing
end;

gtkwstrayicon.pasでは、このようにしています。

pascal
uses WSTrayIcon, WSLCLClasses, Controls, TrayIcon, gtk, gdk;
pascal
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;
...
pascal
implementation
pascal
procedure TGtkWSTrayIcon.CreateHandle(const AWinControl: TWinControl;
const AParams: TCreateParams): HWND;
var
WidgetInfo: PWidgetInfo;
begin
pascal
Result := gtk_plug_new;
WidgetInfo := CreateWidgetInfo(AWinControl, Result); // it's something
pascal
like this anyway
 TGtkWSWincontrolClass(WidgetSetClass).SetCallbacks(AWinControl);
 // and more stuff
end;
pascal
function TGtkWSTrayIcon.FindSystemTray(const ATrayIcon:
TCustomTrayIcon): TWindow;
begin
 // do something
end;
pascal
procedure TGtkWSTrayIcon.EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon);
var
SystemTray: TWindow;
begin
 SystemTray := FindSystemTray(ATrayIcon);
 //do something
end;
pascal
procedure TGtkWSTrayIcon.RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon);
begin
 //do something
end;
pascal
......
pascal
initialization
pascal
RegisterWSComponent(TCustomTrayIcon, TGtkWSTrayIcon); //this is very
important!!!
pascal
end.

そうすると、trayicon.pasは最終的には次のようになります。

pascal
uses WSTrayIcon; //etc. you DON'T include GtkWSTrayIcon here!
pascal
TCustomTrayIcon = class(TWinControl)
public
 procedure EmbedControl;
....
end;
pascal
...
procedure TTrayIcon.EmbedControl;
begin
 TWSTrayIconClass(WidgetSetClass).EmbedControl(Self);
pascal
end;

このドキュメントは作業途中です。このドキュメントの章を書く手助けをしていただいてかまいません。 もし、このドキュメントの中で何か情報を探していて見つからなかった場合には、質問をdiscussion pageに入れてください。そうすることが、読みたいみなさんのレベルにあわせて、あまり過不足のないドキュメントを書く手助けになります。.


From the Free Pascal wiki, page "LCL Internals/ja", as it stood on 2012-02-03 after a change by Chronos, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

第1版、最終更新:WikiArchive 。