EnumChildWindowsでTLabelが取得できません。
http://mrxray.on.coocan.jp/Halbow/VCL06.html#VChap6-5
のList19でやっている「子ウィンドウの探索」がまさにやりたいことなのですが、
TLabelが列挙されません。
冒頭で
>TLabel や TImage 、TPaintBox 、 TSpeedButton などの TGraphicControl の派生クラスは、
>ウィンドウの特定のクライアント領域の描画機能をカプセル化したものであり、
>ウィンドウではないのでOSからは見えない。
と記載されており、
TLabelはEnumChildWindowsで取得できないのかもしれませんが、
どうにか取得する方法がありませんか?
よろしくお願いします。
//以下サンプル
//フォームにはTButtonとTListBoxとTLabelを最低1個置き、そのほかは適当に配置している状態
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ComCtrls, ExtCtrls, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Label1: TLabel;
Button2: TButton;
ComboBox1: TComboBox;
Panel1: TPanel;
Label2: TLabel;
Button3: TButton;
ComboBox2: TComboBox;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
type
PHWndArray = ^THWndArray;
THWndArray = array[0..500] of HWND;
function EnumChildWndProc(hWindow:HWND;lData:LPARAM):BOOL;stdcall;
var
pWA:PHWndArray;
begin
pWA := PHWndArray(lData);
pWA^[0] := pWA^[0]+1;
pWA^[pWA^[0]] := hWindow;
result := true;
end;
function AEnumChildWindows(var WA:THWndArray; hParent:HWND):integer;
begin
WA[0] := 0;
EnumChildWindows(hParent,@EnumChildWndProc,LPARAM(@WA));
result := WA[0];
end;
function GetClassNameStr(hWindow:HWND):string;
var
p:PChar;
ret:integer;
begin
GetMem(p,100);
ret := GetClassName(hWindow,p,100);
SetString(result,p,ret);
FreeMem(p);
end;
function GetWindowTextStr(hWindow:HWND):string;
var
p:PChar;
ret:integer;
begin
ret := GetWindowTextLength(hWindow);
GetMem(p,ret+1);
ret := GetWindowText(hWindow,p,ret+1);
SetString(result,p,ret);
FreeMem(p);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
SL:TStringList;
WA:THWndArray;
i,Num:integer;
begin
ListBox1.clear;
SL := TStringList.Create;
try
Num := AEnumChildWindows(WA,Handle);
Label1.Caption := IntToStr(Num);
if Num > 0 then
for i := 1 to Num do begin
ListBox1.Items.Add(GetClassNameStr(WA[i])+'/'+GetWindowTextStr(WA[i]));
end;
finally
SL.Free;
end;
end;
end.
こういうことかな?
他のアプリの情報は取れないけど。
procedure TForm1.Button1Click(Sender: TObject);
var
i, Num: integer;
begin
Num := Self.ComponentCount;
Label1.Caption := IntToStr(Num);
for i := 0 to Num-1 do
begin
if Self.Components[i] is TLabel then
begin
ListBox1.Items.Add(Self.Components[i].Name + '/' + (Self.Components[i] as Label).Caption);
end;
end;
end;
Selfの部分を他のフォーム(例えばForm2)にすれば、そのフォームの情報が取れます。
Questさん、asキャストがLabelになってます…。
> ListBox1.Items.Add(Self.Components[i].Name + '/' + (Self.Components[i] as Label).Caption);
ここの as Label を as TLabelにする必要があります。 >フクロウさん
------------------------------------------------------------------------------------
TLabelはVCL内で作られた”絵”なので、「ウインドウハンドルを取得」ということ自体が成り立ちません。
・ 自分自身(Delphi製アプリ)のTLabel → 自分の中ですからどうにでも出来ます。
・ 自作のDelphi製アプリのラベルを他のアプリから → TStaticTextにすれば出来るはずです。
・ 他のDelphi製アプリのTLabel → 無理です。
・ 他のDelphi製でないアプリのラベル → スタティックコントロールであれば出来るはずです。
最終的に何をしたいのでしょう?
Harryさん、フォローどうもm(__)m
うーん、実際動かしたヤツをコピペしたんだけどなぁ。
あっ、そうか。コピペした時に途中で改行されたんで
それを削除するときに消しすぎたのか。
返事遅れてすいません。
>>Questさん
サンプルありがとうございます。
自EXE内でしたら取得できるのですが、他EXEでラベルを取得したいのです。
私の出したサンプルが悪かったですね。申し訳ないです・・・。
>>Harryさん
やりたいことは、マニュアルの雛形作成です。
delphiでコンパイル済のEXEの指定フォームから
ラベルのCaptionとHintの一覧を取得するイメージです。
(※できればTop,Left順に)
ウィンドウの取得は今のところ、
以下のようにTimerとGetForeGroundWindowを利用して取得する予定です。
--------------------------------------------
ThisWin : HWND;
TgtWin : HWND;
procedure TForm1.Button4Click(Sender: TObject);
begin
ThisWin := GetForeGroundWindow;
Timer1.Enabled :=True;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
SL:TStringList;
WA:THWndArray;
i,Num,Res:integer;
begin
TgtWin := GetForeGroundWindow;
if TgtWin <> ThisWin then begin
Timer1.Enabled:=False;
Res := MessageBox(0,
PChar('['+ GetWindowTextStr(TgtWin) + '] この画面でいいですか?'),
'確認',
MB_YESNOCANCEL or MB_TOPMOST);
if Res=IDYES then begin
(TgtWinのラベル一覧を表示)
end;
end;
end;
--------------------------------------------
Harryさんの選択肢で言うと、
「自作のDelphi製アプリのラベルを他のアプリから 」
「他のDelphi製アプリのTLabel」
のどちらかになるかと思います。
ちょっとこの選択肢の違いがわからないのですが、
TStaticTextを利用すれば実現可能でしょうか?
すいません。
マニュアルとは操作マニュアルという意味のマニュアルです。
直接の回答ではないですが、ヒントになれば。
実行中のEXEからLabelの情報を取り出すのは無理そうですが
XN Resourece EditorというフリーのツールでDelphi製EXEを開くと
リソースとしてRC Dataという項目に各フォームの情報(*.dfmの内容)が
保存されているようです。
たぶん、フォームをテキスト形式で保存しないとダメだとは思いますが
各パラメータなどは記載されているので、これを解析すれば位置や大きさ
キャプションなどはわかると思います。
Delphi2007で作ったEXEで確認しました。
あ、キャプションやヒントが実行時に動的に設定される場合は
当然取得できないです(^^ゞ
フクロウさん
もう少し細かく書きますとこういうことです。
(1) フクロウさん自作するDelphi製アプリのラベルを他のアプリから → TLabelをTStaticTextに変更すれば出来るはずです。
(2) フクロウさんが制作に関与できない、どこかのDelphi製アプリのTLabel → 無理です。TLabelはウインドウではありませんので。
今回は(1)のパターンですよね?
ならばTStaticTextを試してみてはいかがでしょうか。変更できるなら、ですが。
しかし自作のアプリから取得するなら、TLabelのままにして「マニュアル作成モード」でも仕込む方が楽なように思えます。
リリースでは除去してもいいですし。つまり↓この状態にするのです。
>・ 自分自身(Delphi製アプリ)のTLabel → 自分の中ですからどうにでも出来ます。
------------------------------------------------------------------------------------
Questさん
それ自分も思いました(笑)
ただし Resource Hackerですが。ちなみにこれもDelphi製。
でもこの用途だとチョー面倒に思えてしまいます。だって、自作アプリなら手元に*.dfmありますから…。
>>Questさん
ほー。このようなツールがあるのですね。
ただ、dfmがバイナリ形式で正常に読込できませんでした。
>>Harryさん
それで言うなら、(1)ですね。
ただ、EXEが300個以上あり、担当者も複数に渡ります。
ですので、TStaticTextへの変更や、マニュアルモードの仕込みは
結構難しいです。
Resource Hackerはバイナリ形式のdfmでも確認できましたが、
しかし、delphiで見れる、dfmがツリービュー形式で見れるというだけで、
TLabelのリストはちょっと難しいかなと思っています。。。
ちょっと、ツール化は難しいそうなので、コピペで泥臭くがんばろうかなと今は思っています。
>ただ、EXEが300個以上あり、担当者も複数に渡ります。
>ですので、TStaticTextへの変更や、マニュアルモードの仕込みは
>結構難しいです。
>ちょっと、ツール化は難しいそうなので、コピペで泥臭くがんばろうかなと今は思っています。
目的のEXEのソースコードにフクロウさんがアクセスできるのならばツール化は可能と思います。
(書き込みできなくても読み込みのみできればOK)
・まずアプリ中のフォームのLabelを列挙するツールを作ります。
・そのツールのプロジェクトに目的のEXEのフォームを追加します。
・「プロジェクト」の「オプション」で追加したフォームを「自動作成の対象」にします。
・ツールを実行。
場合によっては必要なdcuがないと怒られることもあるのでその場合はそのdcuの元になっているソースコードをプロジェクトに追加します。
↓こんな感じかなと。
procedure TForm1.Button1Click(Sender: TObject);
procedure LabelGet(AForm: TForm);
var
i : Integer;
l_Label : TLabel;
begin
for i := 0 to AForm.ComponentCount -1 do
begin
if (AForm.Components[i] is TLabel) then
begin
l_Label := TLabel(AForm.Components[i]);
//Top,Leftの順に並べ替えるため4桁の幅の数値にしてしまう
ListBox1.Items.Add(Format('%s %.4d %.4d %s "%s"/%s', [AForm.Name, l_Label.Top, l_Label.Left, l_Label.Name, l_Label.Caption, l_Label.Hint]));
end;
end;
end;
var
i : Integer;
l_Form : TForm;
begin
ListBox1.Sorted := True;
ListBox1.Items.Clear;
for i := 0 to Screen.FormCount -1 do
begin
l_Form := Screen.Forms[i];
if (l_Form <> Self) then
begin
LabelGet(l_Form);
end;
end;
end;
仕込みすればいいのに…と思ってましたが、
>ただ、EXEが300個以上あり、担当者も複数に渡ります。
(^_^;;;;;;;;;;;;;;;;
それでは仕込みは厳しいですねー。
>ちょっと、ツール化は難しいそうなので、コピペで泥臭くがんばろうかなと今は思っています。
フクロウさん、あきらめ早過ぎっすよ!
可能性がありそうな方法を分類すると、以下のようになると思います。
(a) 仕込み型 … 当該のプロジェクトにTLabelの情報を取得するコードを仕込んでコンパイル。(Questさん、私)
(b) フォーム拝借型 … 当該のプロジェクトからフォームだけ取り出し、TLabelの情報を取得するコードと合わせてコンパイル。(Dさん)
(c) ソース解析型 … 当該のプロジェクトからフォームの*.dfmを抜き出し、解析してTLabelの情報を取得。(一番現実的か?)
(d) EXEから取得型 … 当該のEXEのリソースからフォーム情報を取り出し、解析してTLabelの情報を取得。(かなり面倒?)
(a)と(b)が既出ですが、(c)のソース解析型が一番真っ当な気がします。
で、(d)の方法も出来そうな気がしてきたので、ちょっと研究してみます。うまくいったらお伝えします。
あれ、開発環境はどうなってますか? dfmがバイナリどうたらということはかなり古い?
------------------------------------------------------------------------------------
あと、自分も(a)の仕込み型のサンプルを作ってたので、せっかくですからここに置いておきます。
TLabelだけじゃ面白くないので、TControlなら取得するようにしてみました。(しかしかなり適当)
仕込むプロジェクトでフォームを新規作成、フォームをダブルクリック、フォームのNameプロパティをGetTCtrlFormに変更、
TMemoを配置、下記のコードをコピー&貼り付けすればOkです。
type
TGetTCtrlForm = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private 宣言 }
procedure ScreenActiveFormChange(Sender: TObject);
procedure GetTControlData(AForm: TForm; AStrings: TStrings);
public
{ Public 宣言 }
end;
var
GetTCtrlForm: TGetTCtrlForm;
implementation
{$R *.dfm}
uses
ClipBrd;
type
TControlEx = class(TControl); // protectedなプロパティを取り出すための仕掛け
procedure TGetTCtrlForm.FormCreate(Sender: TObject);
begin
Memo1.Font.Name:='MS ゴシック';
Self.FormStyle:=fsStayOnTop;
Self.Show;
Screen.OnActiveFormChange:=Self.ScreenActiveFormChange;
end;
procedure TGetTCtrlForm.FormDestroy(Sender: TObject);
begin
Screen.OnActiveFormChange:=nil;
end;
procedure TGetTCtrlForm.ScreenActiveFormChange(Sender: TObject);
var
SL: TStringList;
begin
SL:=TStringList.Create;
try
Self.GetTControlData(Screen.ActiveForm, SL);
SL.Sort; // ソートすれば Top、Left順 になる
SL.CommaText:=StringReplace(SL.Text, sLineBreak, ',', [rfReplaceAll]); // ゴニョゴニョして整形
Memo1.Lines.Assign(SL); // TMemoに出力
ClipBoard.AsText:=SL.Text; // クリップボードにも出力
finally
SL.Free;
end;
end;
procedure TGetTCtrlForm.GetTControlData(AForm: TForm; AStrings: TStrings);
var
TempSL: TStringList;
I: Integer;
AControl: TControl;
begin
AStrings.Add('------------------------------------------');
TempSL:=TStringList.Create;
try
for I:=0 to AForm.ComponentCount-1 do begin
if not (AForm.Components[I] is TControl) then Continue; // TLabelのみにしても良い
AControl:=AForm.Components[I] as TControl;
TempSL.Clear;
TempSL.Add(Format('(%4d,%4d) %d/%d', [AControl.Top, AControl.Left, I+1, AForm.ComponentCount]));
TempSL.Add(AControl.Name);
if not (AControl is TCustomMemo) then begin
TempSL.Add(TControlEx(AControl).Caption); // Captionは無理やりほじくり出す
end else begin
TempSL.Add('* Caption N/A *'); // TMemoとかのCaptionにはText全部が入ってるからナシで
end;
TempSL.Add(AControl.Hint);
TempSL.Add('------------------------------------------');
AStrings.Add(TempSL.CommaText);
end;
finally
TempSL.Free;
end;
end;
では、私は「(c)ソース解析型」の取っ掛かりの部分だけ。
フォームにメモ、ボタン、オープンダイアログを置いて
ボタンクリックイベントに
procedure TForm1.Button1Click(Sender: TObject);
var
dfmName: string;
fs: TFileStream;
ms: TMemoryStream;
begin
if OpenDialog1.Execute then
begin
dfmName := OpenDialog1.FileName;
fs := TFileStream.Create(dfmName, fmOpenRead);
ms := TMemoryStream.Create;
try
try
ObjectResourceToText(fs, ms);
ms.Position := 0;
Memo1.Lines.LoadFromStream(ms);
except
Memo1.Lines.LoadFromStream(fs);
end;
finally
ms.Free;
fs.Free;
end;
end;
end;
を記述。
核の部分のtry〜exceptの部分はいい加減で
バイナリでないdfmを読むとエラーになるので
エラーになったらテキストだろうと限定しています。
なので、他のエラーの場合は考えていません。
あと問題は、Delphi6(多分?)以降は全角文字が
"#12345"のような表記になっているのでこれの変換が必要です。
例)
Font.Name = #65325#65331' '#26126#26397
これは「MS 明朝」です。
IDE自身がフォームを読み込むんだから、内部にそれらしい変換関数が
あっても良さそうですけど。
この辺はDEKOさんが詳しいかも。
あとは(一番面倒な?)語句解析を何とかすれば
全フォームを一気に解析も夢ではないかと(^^ゞ
Label限定ならそれほど難しくはないかも。
さらに、LabelのHintとCaptionだけ羅列するようにしてみました。
procedure TForm1.Button1Click(Sender: TObject);
var
dfmName: string;
fs: TFileStream;
ms: TMemoryStream;
begin
if OpenDialog1.Execute then
begin
dfmName := OpenDialog1.FileName;
fs := TFileStream.Create(dfmName, fmOpenRead);
ms := TMemoryStream.Create;
try
try
ObjectResourceToText(fs, ms);
Memo1.Lines.Text := ExtractLabelParam(ms);
except
Memo1.Lines.Text := ExtractLabelParam(fs);
end;
finally
ms.Free;
fs.Free;
end;
end;
end;
function TForm1.ExtractLabelParam(st: TStream): string;
var
s: string;
sLine: TStringList;
LabelFlag: boolean;
begin
Result := '';
LabelFlag := False;
sLine := TStringList.Create;
try
st.Position := 0;
sLine.LoadFromStream(st);
for s in sLine do
begin
if (Pos('object', s) > 0) and (Pos(': TLabel', s) > 0) then
begin
Result := Result + 'Name : '+Copy(s, Pos('object', s)+7, Pos(': TLabel', s)-(Pos('object', s)+7)) + #13;
LabelFlag := True;
end;
if LabelFlag then
begin
if Pos('Caption =', s) > 0 then
Result := Result + ' ' + Trim(s) + #13;
if Pos('Hint =', s) > 0 then
Result := Result + ' ' + Trim(s) + #13;
if Trim(s) = 'end' then
begin
Result := Result + #13;
LabelFlag := False;
end;
end;
end;
finally
sLine.Free;
end;
end;
あとは"#12345"の全角文字をどうにかするだけ(^_^;)
あ、これはDelphi2007でテストしてます。
一応、力技でコード変換してみました。
Delphi2009以降では動かない(コンパイルできない)と思いますが(^_^;)
function TForm1.ExtractLabelParam(st: TStream): string;
var
s: string;
sLine: TStringList;
LabelFlag: boolean;
function CodeToString(const Str: string): string;
var
ix: integer;
c: char;
cnt: integer;
buf: string;
wc: WideChar;
begin
Result := '';
ix := 1;
while Length(Str) >= ix do
begin
c := Str[ix];
if c <> '''' then
begin
if c = '#' then
begin
cnt := 0;
buf := '';
Inc(ix);
c := Str[ix];
while c in ['0'..'9'] do
begin
buf := buf + c;
Inc(cnt);
Inc(ix);
c := Str[ix];
end;
Dec(ix);
if cnt = 5 then
begin
wc := WideChar(StrToInt(buf));
Result := Result + wc;
end
else
Result := Result + '#' + buf;
end
else
Result := Result + c;
end;
Inc(ix);
end;
end;
begin
Result := '';
LabelFlag := False;
sLine := TStringList.Create;
try
st.Position := 0;
sLine.LoadFromStream(st);
for s in sLine do
begin
if (Pos('object', s) > 0) and (Pos(': TLabel', s) > 0) then
begin
Result := Result + 'Name : '+Copy(s, Pos('object', s)+7, Pos(': TLabel', s)-(Pos('object', s)+7)) + #13;
LabelFlag := True;
end;
if LabelFlag then
begin
if Pos('Caption =', s) > 0 then
Result := Result + ' Caption = ''' + CodeToString(Copy(s, Pos('Caption =', s)+10, Length(s))) + ''''#13;
if Pos(' Hint =', s) > 0 then
Result := Result + ' Hint = ''' + CodeToString(Copy(s, Pos('Hint =', s)+7, Length(s))) + ''''#13;
if Trim(s) = 'end' then
begin
Result := Result + #13;
LabelFlag := False;
end;
end;
end;
finally
sLine.Free;
end;
end;
Questさんすごいです。
CodeToString関数だけ抜き出して試しましたが、完璧ですね!
…で、力技のところ誠に恐縮なんですが、、、
>IDE自身がフォームを読み込むんだから、内部にそれらしい変換関数があっても良さそう
それ、ClassesにTParserがありました \(^o^)/
// dfm内部形式の文字列をWideStringに変換
// 例1:#65325#65331' '#65328#12468#12471#12483#12463 → MS Pゴシック
// 例2:'Label1' → Label1
// 例3:'Button2'#12384#12424 → Button2だよ
// 例4:Delphi → Delphi
function TokenToWideString(Token: String): WideString;
var
Stream: TStringStream;
Parser: TParser;
begin
Stream:=TStringStream.Create(Token);
try
Parser:=TParser.Create(Stream);
try
Result:=Parser.TokenWideString;
if Result='' then Result:=Parser.TokenString;
finally
Parser.Free;
end;
finally
Stream.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
const
aStr: array[0..3] of String =(
'#65325#65331'' ''#65328#12468#12471#12483#12463',
'''Label1''',
'''Button2''#12384#12424',
'Delphi'
);
var
I: Integer;
begin
for I:=Low(aStr) to High(aStr) do begin
Memo1.Lines.add(CodeToString(aStr[I]));
Memo1.Lines.add(TokenToWideString(aStr[I]));
end;
end;
それと、EXEのリソースをObjectBinaryToTextで変換し、それを使ってdfmの解析も出来ました。
もう少しパーツを作ってつなぎ合わせれば(d)が作れそうな感じです。
Harryさん、こんにちは。
>それ、ClassesにTParserがありました \(^o^)/
ぬぅ〜、やっぱりありましたか(笑)
古い人間なんで、無ければ作る(しかもベタに)タチなんで(^^ゞ
スレ主さんそっちのけで、なんかすんませんm(__)m
>>レスをいただいたみなさま
諦めかけてスレを見ていなかった間に
こんなにもレスを。
ありがとうございます。
>>Dさん
ソースへの読み書きは可能ですが、
ソース量はもちろん、ソースが画一的でないため、
手動での書き込みは難しいです。
dcuの前に、すべてのEXEが複雑に継承構成をとっているため、
継承元がないと怒られることが多数あり、
>>・そのツールのプロジェクトに目的のEXEのフォームを追加します。
が現実的に難しかったです。
>>Harryさん
>>フクロウさん、あきらめ早過ぎっすよ!
す、すいません!!
できるところまでがんばります!
開発環境はDelphi3です。
(c)案は当初より考えてはいましたが、は個人的に難しいのでは?と思っています。
というのも継承構成が複雑で、継承先のdfmには継承元のdfmの情報は直接持っていないため、
継承構成を懸案しながら、dfmの解析が必要だからです。
ですので、(d)案で動いてTlabelの問題にぶつかったという経緯です。
>>Questさん
バイナリでも読込はできましたよ。
「それらしい変換関数」とやらいるんでしょうかね。
色々ソースを検討いただき本当にありがとうございます。
ただ、(c)案の方向でソースを考えて頂いているように感じています。
Questさんの
>>それと、EXEのリソースをObjectBinaryToTextで変換し、それを使ってdfmの解析も出来ました。
>>もう少しパーツを作ってつなぎ合わせれば(d)が作れそうな感じです。
これはどのような、方法で解析されたのでしょうか?
> Questさんの
> >>それと、EXEのリソースをObjectBinaryToTextで変換し、それを使ってdfmの解析も出来ました。
> >>もう少しパーツを作ってつなぎ合わせれば(d)が作れそうな感じです。
> これはどのような、方法で解析されたのでしょうか?
これはHarryさんのレスですね。
ただ、XN Resourece EditorがEXEからリソースを抜き出せているので
「なんらかの方法」でリソースを抽出できれば、あとはその中の
"RC Data"をdfmファイルから読むように解析すればいいんじゃないでしょうか。
肝心の「なんらかの方法」はHarryさんにパス(^^ゞ
※下記は前のレスのセルフフォローに過ぎません。なぜならば、これですべての問題が解決するわけではないので。
-----------------------------------------------------------------------------------------------
dfmの簡易的解析は十分達成されたと思ってたのですが、基本的な所で落とし穴がありました。
どうもdfmファイル内で一行を255文字以内に納めるため、適当な所でちょん切って改行する仕様みたいです。
模擬的な例を示しますと、以下のような形です。
#12487+#12523 + ' '+#9734 +
#12501+ #12449 +#12452
定数として表現するとこのようになります。
'#12487+#12523 + '' ''+#9734 +'#$0D#$0A'#12501+ #12449 +#12452'
これをデコードすると、「デル ☆ファイ」になります。
これに対応するには、先に提示したTokenToWideString関数の一番内側のブロックを以下に差し替えればOkです。
if Parser.Token in [toString, toWString] then begin
Result:=Parser.TokenWideString;
while Parser.NextToken='+' do begin
Parser.NextToken;
if not (Parser.Token in [toString, toWString]) then Parser.CheckToken(toString);
Result:=Result+Parser.TokenWideString;
end;
end else begin
Result:=Parser.TokenString;
end;
ちなみに、(OpenCLXの)ClassesにあるObjectTextToBinaryの関数内関数内関数をパク…もとい、参考にしました。
-----------------------------------------------------------------------------------------------
これで万事Ok…ではないです。この関数に渡す前に複数トークンの切り出しと連結を行わないといけませんし、
そもそも「1行=1プロパティ」となってない以上、簡易的解析では無理だと悟りました。(後でもっと無理と判明。)
で、Classesをいろいろ眺めていたら、TReaderを使えば出来そうだと気付きました。
TParserがトークン(最小単位)の切り出ししか行えないのに対し、TReaderならdfmの構造をチェックしつつ、意味の
あるデータとして取り出してくれます。
自力であーだこーだしてみたんですが無理っぽいので、使用例を検索。ぜんぜん無い…かと思ったらありました!
TXMLDocument - Delphi@WCIMH(いつも心に工事中!) - YTさんのサイト
http://hp.vector.co.jp/authors/VA028375/delphi/sample_xml.html
↑こちらはXMLに変換するというサンプルですが、出力を変えればどうにでも出来ます。
これで目途が立った…と思ったんですが、そうは甘くなかったですね。
ということで次に続きます。
フクロウさん
>開発環境はDelphi3です。
え、、、それは未知領域です。。。。 少なくとも動的配列は禁止ですね。
>(d)案で動いてTlabelの問題にぶつかったという経緯です。
念のため。いいえ、(d)案はウインドウを取得するのではなく、EXEのリソースからdfmのデータを取り出します。
>(c)案は当初より考えてはいましたが、は個人的に難しいのでは?と思っています。
>というのも継承構成が複雑で、継承先のdfmには継承元のdfmの情報は直接持っていないため、
>継承構成を懸案しながら、dfmの解析が必要だからです。
その通りです!!
これは(d)案にもまったく同じことが言えます。ただし、(c)・(d)案ともに継承元(dfmファイル/リソース)は取得できます
ので、やる気次第ですね。(理屈は簡単ですが、プログラミング的に難しい。)
ちなみに、以下はサンプルのTAboutBoxフォームを継承した場合のTAboutBox2のdfmです。
TLabelの位置だけを変更してみたので、Left = 136 が追加されています。
inherited AboutBox2: TAboutBox2
Caption = 'AboutBox2'
PixelsPerInch = 96
TextHeight = 12
inherited Panel1: TPanel
inherited ProductName: TLabel
Left = 136
end
end
end
1つのフォーム中では名前の衝突は無いはずだからツリー的管理は必要なく、先に継承元を読み込んで単純に
オーバーライドしていけば可能…と思ってます。(言うは易し。)
継承のinheritedの他に、inlineてのもある模様…これはフレームか!
名前の衝突が無いという前提がいきなり崩れました…。どうすれば良いのか…。
>これはどのような、方法で解析されたのでしょうか?
Questさんとは違ったアプローチですが、Name、Top、Left、Caption、Hintだけ取得する簡易的なものです。
上からプロパティを読んでいき、objectかendがあったら取捨選択して記録、再帰。ソートして出力…みたいな。
今となっては徒労であったことが判明したので、具体的なコードは割愛します。
Questさん
>「なんらかの方法」でリソースを抽出できれば
ここはとても簡単です。普通にリソースを読むだけなので。↓こんな感じで。
// EXEのリソースからdfmのテキストを得る関数、EXENameには'TFORM1'とかを指定する
//
// ヘルプのObjectBinaryToTextの例より作成
// 検索順序:ReadComponentResFile→ObjectTextToResource→ObjectResourceToText→ObjectBinaryToText
function CompoResourceToString(const EXEName, ResName: String): String;
var
AInstance: HMODULE;
ResStream: TResourceStream;
StrStream: TStringStream;
begin
AInstance:=LoadLibrary(PChar(EXEName));
if AInstance=0 then Exit;
try
ResStream:=TResourceStream.Create(AInstance, ResName, RT_RCDATA);
try
StrStream:=TStringStream.Create('');
try
ObjectBinaryToText(ResStream, StrStream);
Result:=StrStream.DataString;
finally
StrStream.Free;
end;
finally
ResStream.Free
end;
finally
FreeLibrary(AInstance);
end;
end;
↑これはObjectBinaryToTextでテキスト化してますが、TReaderだとバイナリのストリームのまま放り込むことになります。
以下は余談。
TReaderで扱えるのはリソースヘッダ無しのバイナリdfmデータしか扱えませんが、変換すればテキストも大丈夫。
dfmデータは以下の3種類あると思います。若干ややこしいです。。。
・ テキストの*.dfm … ObjectTextToBinaryで変換すればOk。
・ バイナリの*.dfm … リソースヘッダが付いてるので、TStream.ReadResHeaderでリソースヘッダを読み飛ばせばOk。
・ EXEリソース内のバイナリdfmデータ … リソースヘッダは付いてないので、そのままでOk。
ということで、TReaderを使ったアプローチもまだ考え中です。
フクロウさんにお聞きしておきたいことがいくつかあります。
・ 制作に使用されたバージョンとは別に、現在使用できるDelphiでもっと新しいのはないのでしょうか?
・ 動的に生成、または変化するCaptionとか無いですよね? あったら前提が崩れます。
・ 出力はTop、Left順の一覧テキストで良いのでしょうか。
↑いま見直したら、関数の冒頭の肝心のコメントが間違ってました。正解はこちら↓
// EXEのリソースからdfmのテキストを得る関数、ResNameには'TFORM1'とかを指定する
関数名もイマイチでした…。↓こうしましょう。
function EXEResourceToString(const EXEName, ResName: String): String;
ツイート | ![]() |