LCL Internals/ja

From Free Pascal wiki
Jump to: navigation, search

English (en) | español (es) | 日本語 (ja)

日本語版メニュー
メインページ - Lazarus Documentation日本語版 - 翻訳ノート - 日本語障害情報

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に入れてください。そうすることが、読みたいみなさんのレベルにあわせて、あまり過不足のないドキュメントを書く手助けになります。.