Swanman's Horizon

性欲をもてあましつつなんらかの話をするよ。

DelphiからCocoaを使う その1。

その1とか書いてるけど、次回があるかどうかは知らないです^q^

FireMonkey?ぼっこぼこにしてやんよ

最初に断っておきますが、数々のバグは別にしてFireMonkeyのコンセプト自体は良いと思います。やはりひとつのソースから複数プラットフォームの実行ファイルが生成できるというのは素晴らしいです。
だけどやっぱりプラットフォーム固有の機能を使った方が、そのプラットフォームの流儀に合ってたり、見た目的にも綺麗だったりします。バグもずっと少ないし。
そこでMacプラットフォームでCocoaを直接使う方法を考えてみます。

えーマジShowMessage?ShowMessageが許されるのはWindowsまでだよね

とりあえず楽なところから攻めるとして、まずはダイアログから。
Mac上でShowMessageを使うと、こんなダイアログが表示されます。

一方、Cocoaのダイアログはこんな感じ。

このダイアログはNSAlertというクラスによって生成されています。

NSAlertさんチーッス

本来Objective-Cからしか呼べないCocoaのクラスを使うために、RTTIを駆使しまくった動的なインターフェースの生成、Objective-Cのメッセージ式をインラインアセンブラDelphiのメソッドに変換する等々、なかなか凄まじい技術が内部では繰り広げられてたりしますが、NSAlertを使うにはTNSAlertというクラスが用意されているので、難しいことは考えずにこれを使います。

Objective-Cではクラスのインスタンスを生成する際、Delphiのコンストラク*1にあたるallocというクラスメソッドを呼んでメモリを割り当て、init系のインスタンスメソッドを呼んでインスタンスを初期化します。また、クラスメソッドに用意されているヘルパーメソッドのようなものを使って、ある程度初期値を指定した状態で生成することもクラスによっては可能です。

NSAlertでも、普通に生成する以外にクラスメソッドによる生成が選択できます。

NSAlert *alert = [NSAlert alertWithMessageText:@"message" defaultButton:@"defaultButton"
  alternateButton:nil otherButton:nil informativeTextWithFormat:@"informativeTextWithFormat"];
[alert runModal];

これをDelphiで書く場合はこうなります。

uses
  ..., Macapi.Foundation, Macapi.AppKit;
var
  alert: NSAlert;
begin
  alert := TNSAlert.Wrap(TNSAlert.OCClass.alertWithMessageText(NSSTR('message'), NSSTR('defaultButton'), 
    nil, nil, NSSTR('informativeTextWithFormat')));
  alert.runModal;
end;

だいぶややこしいと言えばややこしいですが、ShowMessageだって内部ではフォーム作ってラベルとボタン置いて…という処理が走ってるわけで、それに比べたらむしろシンプルなくらいです。
ちなみに、文字列はDelphiのstringが直接渡せないため、Macapi.FoundationユニットのNSSTR関数でNSStringに変換しています。

せっかくだから、俺はこの固有機能も使うぜ

NSAlertにはダイアログではなくシートとしてウィンドウ内に表示する機能もあります。
突如としてFmxHandleToObjCだとかILocalObjectが出現してますが、とりあえず気にしない。

uses
  ..., Macapi.Foundation, Macapi.AppKit, FMX.Platform.Mac, Macapi.ObjectiveC;
var
  alert: NSAlert;
  win: NSWindow;
begin
  alert := ...; // 上記と同じ
  win := TNSWindow.Wrap((FmxHandleToObjC(Self.Handle) as ILocalObject).GetObjectID); // SelfはTForm
  alert.beginSheetModalForWindow(win, nil, nil, nil);
end;


本日のまとめ

ShowMessage風の手続きにしてみたものがこちら。

// Formを省略すると普通のダイアログ、指定するとシートとして表示
procedure CocoaShowMessage(const Msg: string; Form: TForm = nil);
var
  alert: NSAlert;
  win: NSWindow;
begin
  alert := TNSAlert.Wrap(TNSAlert.OCClass.alertWithMessageText(NSSTR(''), NSSTR('OK'), nil, nil, NSSTR(Msg)));
  if Form = nil then
    alert.runModal
  else begin
    win := TNSWindow.Wrap((FmxHandleToObjC(Form.Handle) as ILocalObject).GetObjectID);
    alert.beginSheetModalForWindow(win, nil, nil, nil);
  end;
end;

*1:というよりはNewInstance