思ってたんと違う

delphiの~.Controlsで大ハマりしたので備忘録という名の供養。
私なりにちゃんと調べたんですがんばった。

結論。
結局〜.Controlsから→〜.Pagesに変えたら望んだ通りにできました。
こんなのに3、4日潰して愚か、実に愚か…。

以降、長々と頑張った!と言うのを書いてるだけなのでご注意ください。
解決策だけ知りたい人は見る必要ないです。

やりたかったのはこんな感じ。
フォルダを選択し、直下のファイル一覧をStringGridに表示。
表示した分から任意のファイルをダブルクリックでPageControlにTabSheetを追加してRichEditに内容展開。
この時に重複しないよう展開済みかどうかチェックする。
展開済みなら対象のタブを表示。

MemoでなくRichEditを使ってるのは文字単位で色を変えたいからなんですが、今回は関係ないので置いといて。

イメージ付かない人は、一覧にあるファイル名をダブルクリックしたら新しくタブを作ってファイルの中身を表示する、とご想像ください。
同じファイルを何個も開けちゃうのは困るし、開き済みならタブ表示した方が優しいよねーと言うお話。

大ハマりしたのは「開き済みならタブ表示」の部分。

以降、文章ではStringGridはグリッド、PageControlはページ、TabSheetはタブ、RichEditはエディットと記載します。

まず、親子関係はこう。
()内はNameプロパティ。(プログラム内で使う名前)
TFormMain
┗TStringGrid (sg)
┗TPageControl (pc)
 ┗TTabSheet
  ┗TRichEdit

で、組んでたのはこう。

//--------------------------------------
// グリッドダブルクリック
//    sg -> StringGrid
//    pc -> PageControl
//--------------------------------------
procedure Tfrm.sgDblClick(Sender: TObject);
var
    i: Integer;
begin
    try
// ~前略~
        //選択ファイルがタブ展開済みなら抜ける
        for i := 0 to pc.PageCount - 1 do
        begin
            if sg.Cells[0, sg.Row] = (pc.Controls[i] as TTabSheet).Caption then
            begin
                pc.ActivePageIndex := i;
                Exit;
            end;
        end;
// ~後略~
  finally
  end;
end;

タブはグリッドダブルクリック時(~後略~内)に都度作成してるので、プロパティ名がsheet0、sheet1みたいな連番になっています。
その下にいるエディットも親のタブと同じ番号(rich0、rich1)で作成。
グリッド表示してる順でタブが並ぶわけでもないし、複数展開した後に一部を閉じるといった操作も実装予定なので表示タブを1つずつ確認します。
上で書いた通り数が不定なので全部書き出してチェックする訳にもいかず、「ページにぶら下がってるタブ」と指定してチェック。
(TComponentで配列作った方がいいかと思ったものの、SetLengthを都度するか5個ずつぐらいにしようかとか考えてそのまま)

これで色々動かしてたら重複はしないものの頓珍漢な動きをするので調べました。
(3つのテキストファイルをa、b、cの順で展開、aタブを開いた状態で再度aを展開しようとするとcタブが表示、みたいな)
どうもpc.Controls[i]の中身がころころ変わっている模様。

箇所にログを吐くよう追記(赤文字)して実行。
出力メソッドは別ユニットに用意してます。
参考元を丸パクリ(ちょっと改変)したので、参考部分に載せておきます。

//--------------------------------------
// グリッドダブルクリック
//    sg -> StringGrid
//    pc -> PageControl
//--------------------------------------
rocedure Tfrm.sgDblClick(Sender: TObject);
var
    i, j: Integer;
begin
    try
// ~前略~
        unit.WriteLog('★start★', Chk.Checked);
        unit.WriteLog('  opened ' + pc.Pages[ActivePageIndex].Name + ' / '
                               + pc.Pages[ActivePageIndex].Caption, Chk.Checked);
        unit.WriteLog('  reopen ' + sg.Cells[0, sg.Row], Chk.Checked);
// ~中略~
        //選択ファイルがタブ展開済みなら抜ける
        for i := 0 to pc.PageCount - 1 do
        begin
            unit.WriteLog('  i=' + IntToStr(i) + ' (.Name / .Caption)', Chk.Checked);
            for j := 0 to pc.PageCount - 1 do
            begin
                unit.WriteLog('  opened ' + pc.Pages[j].Name + ' / ' +
                                       pc.Pages[j].Caption, Chk.Checked);
            end;
            if sg.Cells[0, sg.Row] = (pc.Controls[i] as TTabSheet).Caption then
            begin
                pc.ActivePageIndex := i;
                Exit;
            end;
        end;
// ~後略~
    finally
        unit.WriteLog('  end.', Chk.Checked);
        for j := 0 to pc.PageCount - 1 do
        begin
              unit.WriteLog('  opened ' + pc.Pages[j].Name + ' / ' + 
                                     pc.Pages[j].Caption, Chk.Checked);
        end;
        unit.WriteLog('★end★', Chk.Checked);
    end;
end;

検証方法はこう。
確認用のテキストファイルを4つ(01.txt、02.log、03.txt、04.log)用意する。
全て展開。(タブが4つある状態)
確認項目は4つ。
 ①どのタブを開いた状態で
 ②どの展開済みファイルを再展開しようとすると
 ③どのタブが表示されるか
 ④pc.Controls[i]の中身(.Nameと.Caption)

ログの結果をまとめたらこうなりました。
空のところはExitで抜けた後なのでループ内のログ出力を踏んでません。

見辛いでしょうが申し訳。
配置とかがうまくまとまらず、ようやっと形にできたのがこれなんで大目に見てもらえると嬉しいです。

見る感じ表示中のタブがpc.Controls[3](末番)へ→後続のタブが繰り上がって格納されるような動きをしていますね。
opened:sheet0 / 01.txt、reopen:01.txtのパターンで説明するとこうです。

こちらが想定していた動きはこうですが

実際の動きはこう。

htmlで無理やりサンプルを作ってるのでゴミが付いてるのは許して。
で、ループ中のpc.Controls[i](.Name / .Caption)の中身がこう。
(赤文字が表示中)

  1. sheet1 / 02.log
  2. sheet2 / 03.txt
  3. sheet3 / 04.log
  4. sheet0 / 01.txt

再展開しようとしたファイル名と展開済みのタブ名が一致するので、対象のタブに切り替えるためActivePageIndexにiを代入。
.Nameに01.txtが入っているのは上記リストの3番目なので、iの中身も3になります。

ActivePageIndexは表示するタブを番号で指定するプロパティ。
表示タブが切り替わったことで、連動してpc.Controls[i]の中身も変わります。
表中で言うendの行です。

  1. sheet1 / 02.log
  2. sheet2 / 03.txt
  3. sheet0 / 01.txt
  4. sheet3 / 04.log

何がどうなって04.logタブが出てくるのか、ですが、前述の通りpc.Controls[i]の中身は表示タブによって変わります。
しかし、画面上のタブ順は変わりません。
と言うことは、実際のタブ順とpc.Controls[i]のデータ順が一致しないのです。

タブ順は01.txtが0、02.logが1、03.txtが2、04.logが3。
pc.Controls[i]はというと、全部展開してから01.txtを表示した時点で下図のような順番(赤文字)になっています。

さて、ループでのチェックでActivePageIndexに3を入れました。
これにより、画面上のインデックスが3にあたる04.logタブが表示、pc.Controls[i]も末番にsheet3 / 04.logが入っていた、ということで。

これ理解するのにめちゃくちゃ時間かかってしまった。
解決自体は冒頭の通り3、4日ですが、記事にしようとして解決以上の日が経ってます。
わかった!から理解した!までが長すぎる。

まぁそれはそれとして、以下赤文字のように書き換えて想定通りに動きました。
いや時間食ったなぁ。

//--------------------------------------
// グリッドダブルクリック
//    sg -> StringGrid
//    pc -> PageControl
//--------------------------------------
procedure Tfrm.sgDblClick(Sender: TObject);
var
    i: Integer;
begin
    try
// ~前略~
        //選択ファイルがタブ展開済みなら抜ける
        for i := 0 to pc.PageCount - 1 do
        begin
            if sg.Cells[0, sg.Row] = pc.Pages[i].Caption then
            begin
                pc.ActivePageIndex := i;
                Exit;
            end;
        end;
// ~後略~
    finally
    end;
end;

参考

MIGARO 株式会社ミガロ.
Delphi/400 フォーム上のコンポーネントを一括処理
Delphi/400 TextFileを使った簡単テキストログ出力

山本隆の開発日誌
フォームに配置したコンポーネントを名前で検索するには

embarcadero
Vcl.ComCtrls.TTabSheet.PageIndex
Vcl.ComCtrls.TPageControl.ActivePageIndex

投稿者: motalenemy

切り絵とゲームを気まぐれで作る仮面ライダー好きな馬骨

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。