LazarusでのTOpenDialogのカスタマイズについて

解決


km  2020-05-18 20:18:44  No: 148765

https://www.petitmonte.com/bbs/answers?question_id=7034

いつも勉強させていただいております。一点質問させてください。

Delphiでtopendialogにコンボボックスを1〜2個追加したい場合、上記スレッドによるようなコーディングを行うことで、追加を行えます。
ですが、同じことをlazarusで行おうとした場合、GetStaticRectなどといった関数が使えず、エラーになります。
もし、このようなダイアログのカスタマイズをlazarusで実現しているコンポーネントの例をご存知の方がいらっしゃいましたらお教えいただけますでしょうか。

希望としては、ダイアログの下部にコンボボックスを二つ追加したいと思っています。

※家でもライセンスを気にせず気軽に学べる開発環境として、lazarusを使い始めました。最初はDelphiの下位互換的な存在かと思ったのですが、想像以上に良くできていて、少し本格的に使ってみたくなりました。
ドキュメントが少ないのが気になりますが、努力でカバーできるか、これを機に少し頑張ってみようと思っています。


km  2020-05-31 19:44:07  No: 148783

先日の件で、時間ができたので、以下のとおりサンプルを作ってみました。
Delphiだと、以下のとおりで動きましたが、Lazarusの場合、2点が引っ掛かりました。
大変恐縮なのですが、もし改善点等の情報をお持ちの方は、ご提供をお願いできますでしょうか。
検索してもなかなか情報がなく難儀しております。どうぞよろしくお願いいたします。

uses
  Windows;

type
  TOpenDialogEx = class(TOpenDialog)
  public
    ComboBox: TComboBox;
    procedure DoShow; override;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function Execute: Boolean; override;
  end;

// クラスの関数

procedure TForm1.Button1Click(Sender: TObject);
var
  OD: TOpenDialogEx;
begin
  OD := TOpenDialogEx.Create(Self);
  if OD.Execute then;
end;

constructor TOpenDialogEx.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ComboBox:= TComboBox.Create(Self);
end;

destructor TOpenDialogEx.Destroy;
begin
  ComboBox.Free;
end;

procedure TOpenDialogEx.DoShow;
var
  OptionRect: TRect;
begin
  GetClientRect(Handle, OptionRect);
  ComboBox.ParentWindow := Handle;
  // ↑①この行はDelphiでは成功するが、Lazarusでは必ず失敗。エラーメッセージは下記。
  ComboBox.Top := OptionRect.Height - 40;
  ComboBox.Visible := True;
end;

function TOpenDialogEx.Execute: Boolean;
begin
  Template := 'TEXTFILEDLG';
  // ↑②この行を入れるとLazarusでは未定義エラー。
  // Delphiでは削除すると、TOpenDialogExは開くものの、コンボボックスが追加されない。
  Result := inherited Execute;
end;

// 実行部

procedure TForm1.Button1Click(Sender: TObject);
var
  OD: TOpenDialogEx;
begin
  OD := TOpenDialogEx.Create(Self);
  if OD.Execute then;
end;

①のところで表示されるエラーメッセージ(理解できていません…)

プロジェクト project1 は、例外クラス'Exception'を発生させました。メッセージ:
Failed to create win32 control, error: 1400 : ?E?B???h?E ?n???h????????????B ←自分の環境ではエラーメッセージも文字化けします。
 該当ファイル 'win32wscontrols.pp' 該当行 220


km  2020-05-31 19:45:51  No: 148784

環境はDelphi Community 10.3と、Lazarus2.0.8です。
WIndowsは10home 64bitです。


おかぽん  2020-06-02 15:38:46  No: 148788

にわかですが、Delphi と Lazarus の実装が違っているようですね。

 TOpenDialogEx.DoShow 内で参照する Handle値 ですが、Delphiでは ダイアログの WindowHandle を取得できていますが、
Lazarusは、WindowHandleですらなく、OPENFILENAME 構造体へのポインタです(なんだそりゃ?)。
(struct.inc 参照)

ダイアログ作成時のコールバック関数で、ダイアログを画面中央とかに移動させています(Delphi,Lazarus共通)。
加えて、Delphiでは、WindowHandle を取得してHandleプロパティにセットしていますが、
Lazarusは、以下のコメントがあるだけでなんにもしてくれません。

    (Googleさんの翻訳)
    //ところで、ダイアログの幅と高さを設定しても、子コントロールは再配置されません:(
    //したがって、少なくともここで別の高さと幅を設定する方法はありません
(Win32WSDialogs.pp 参照)

さらなる深淵になにかあるのかもしれませんが、少なくともDelphi同様の記述でダイアログに
コントロールを乗せることはできないようです。

たぶんですが、LPOPENFILENAME(Handle)^.lpfnHook で、Lazarus 側で設定した標準の
コールバック関数(function OpenFileDialogCallBack(~~))のアドレスが取得できるので、
これを待避し、時前のコールバック関数と差し替えて、ちょこちょこしたら、動作するかもしれませんが、
そのへんの知識には自信がありません。


km  2020-06-03 23:03:44  No: 148796

ご多忙のところ、細かくみていただきましてありがとうございます。
Delphiで通用したソースコードが全く通用しない実装、とお聞きし、愕然としました。想定されている型が異なれば、確かに実行できなくて当然ですね。
ハンドルといえばいつも使っているウィンドウハンドルだろうと思い込んでいて、この部分のソースは全くみていませんでした。
解決まではまだ厳しそうですが、お時間をとっていただいてこのようなことをお教えいただき、全く異なる開発環境であったことを改めて認識する良い機会となりました。ひとまず感謝の気持ちをお伝えしたいと思います。どうもありがとうございます。


Mr.XRAY  2020-06-06 12:00:19  No: 148798

>  // Delphiでは削除すると、TOpenDialogExは開くものの、コンボボックスが追加されない。 

Delphi XE5 ですが,ソースコードの DoShow のところに以下の記述があります.,
( TCommonDialog.WMInitDialog メソッド内 )

Called only by non-explorer style dialogs

実際テストしてみたら,DoShow は実行されませんでした.
ランタイムテーマが無効ならば実行されます.
ランタイムテーマが無効の場合は Windows XP と同じスタイルになります.

TOpenDialog コンポーネントで表示するダイアログのハンドルは TOpenDialog.Handle ではありません.
クラス名が #32770 のウィンドウのハンドルです.
たとえ,DoShow メソッドが実行されても,その時点ではダイアログのハンドルは取得できないと思います.
このことは,OnShow イベントでも同じです.

ローカルフック等を使用すればダイアログのハンドルを取得できます.
以下を参考にしてください.

[ 22_ダイアログの表示位置とサイズの制御  -  ローカルフック ]
http://mrxray.on.coocan.jp/Delphi/Others/Dialogs.htm#22

更に,TComboBox 等の親にするのはダイアログそのものではなく,
一般的にはダイアログ上の子コントロールです.
VCL のフォームを親しても,そのフォームに他のコントロールがあるとそれに隠れてしまうのと同じです.

以上は Windows の場合です.
Lazarus は使用していませんので,あしからず.


Mr.XRAY  2020-06-06 18:00:28  No: 148799

>ローカルフック等を使用すればダイアログのハンドルを取得できます.
> 以下を参考にしてください.

やってみました.

[ TOpenDialog で表示したダイアログにコントロールを配置 ]
http://mrxray.on.coocan.jp/Delphi/Others/000-005.htm


km  2020-06-07 15:16:32  No: 148801

いつもお世話になります。
Mr.XRAY様、いつぞやはあなたのご助言で課題が非常に簡単に解決したことがあり、今も深く感謝しております。
このたびはわざわざホームページまで執筆くださり、本当にありがとうございます。
今は出先でありますので、Lazarusに移植できるか、自分で試行錯誤しつつ、挑戦してみたいと思います。
解決できましたらその旨で、解決できなかった場合は改めて少しご相談させていただくかも知れません。その折には何卒よろしくお願いいたします。

※handleはopendialogのハンドルだと完全に思い込んでいました。親に乗せて表示、というのはVCLでも全く同じでしたので。
フックはなんとなくでしかわかりませんので、頂いたコードのコマンドや識別子を調べ、何をやっているのか理解していこうと思います。


Mr.XRAY  2020-06-07 20:45:45  No: 148802

> ※handleはopendialogのハンドルだと完全に思い込んでいました。

ダイアログによって違います.
TPrinterSetupDialog.Handle は,プリンタ設定ダイアログのウインドウハンドルです.
TPrinterSetupDialog.OnShow イベント内で取得して操作できます.
TPrinterSetupDialog では,サブクラス化という方法も利用可能です.
TOpenDialog では,サブクラス化という方法は使用できません.

フックやサブクラス化は便利かも知れませんが,難しく面倒です.
そのような機能を使用しない範囲でコーディングできればいいのですが..


おかぽん  2020-06-10 17:19:39  No: 148803

> [ TOpenDialog で表示したダイアログにコントロールを配置 ]
http://mrxray.on.coocan.jp/Delphi/Others/000-005.htm

スレ主ではありませんが、↑のTOpenDialogExを変更し、以下の感じで
コントロールを乗せることができました。

var
  FDlgInst     :TOpenDialogEx;
  FMsgHook     :HHOOK;

function TOpenDialogEx.Execute: Boolean;
begin
  FDlgInst := Self;
  FMsgHook := SetWindowsHookEx(WH_CALLWNDPROC, @GetMsgProc, 0, GetCurrentThreadID);
  try
    Result := inherited Execute;
  finally
    FDlgInst := nil;
    UnHookWindowsHookEx(FMsgHook);
  end;
end;

GetMsgProcは、このように変更
    if (LClassName = '#32770') and (FDlgName = 'OpenDialog1') then begin
  ↓
    if (LClassName = '#32770') and (FDlgInst <> nil) then begin

なお、Lazarusでは、ランタイムテーマが有効でもDoShowが発生しました。
メモ帳の名前をつけて保存と同じようなダイアログが出たので、古いスタイルではない模様。


km  2020-06-14 18:33:31  No: 148812

 お世話になります。ご返事が遅れてしまい、大変申し訳ございません。

 Mr.XRAY様、追加のご助言をありがとうございます。
 おかぽん様、貴重な時間を割いて検証くださり、誠にありがとうござい
ます。少し個人的な事情で身動きが取れませんでしたが、今からいただい
たコードを元に、検証を行ってみたいと思います。
 まったく先行きが見えないなか、進展が望めそうで、大変うれしく、本
当にありがたく感じております。本当にありがとうございました。大変助
かりました。


km  2020-06-18 21:16:49  No: 148829

 返答が遅れ、申し訳ございません。
 まだ完全に理解できているわけではありませんが、いただいたソースを元に、
いじっておりまして、当方の環境でも実際にウインドウに乗ることを確認しました。
当方では、Mr.XRAY様のご提示くださった、

http://mrxray.on.coocan.jp/Delphi/Others/FileDialog_AddCtrl.htm#05

の、リスト8のソースを利用させていただきました。無事、乗りました。いままでは
表示すらされていなかったので、本当に感動しました。誠にありがとうございます。
深く感謝いたします。

※厳密には、以下の部分に@を追加しました。(Lazarusの記法のようです。)
 FMsgHook := SetWindowsHookEx(WH_CALLWNDPROC, @GetMsgProc, 0, GetCurrentThreadID);

あのあと、実際に適用して、しばらく使ってみたのですが、Tabキーをどんどん押し
ていくと、各種コントロールにFocusが移っていくところ、追加したコントロールには
Focusが移らない様子でした。(ComboBoxのTabStopをTrueに設定しても状況は同じで
した。)

今度は、この点を改善すべく試行錯誤をしつつ、コントロールをきれいに並べられ
るように挑戦してみたいと思います。
(なんとなくですが、OpenDialog上の各コントロールの位置情報を、何らかのAPIで
取得し、その位置に合わせて追加したいコントロールの位置を決めることになるの
ではないかと思いますが、まずはGoogleで検索しまくってきたいと思います。)


km  2020-06-18 21:22:36  No: 148830

改めまして、Mr.XRAY様、おかぽん様、この度は誠にありがとうございました。本当に助かりました。


※返信する前に利用規約をご確認ください。








  このエントリーをはてなブックマークに追加