プログラミング

むしろ、チラシの裏。


32ビット形式のビットマップ

2016/12/21
GIMP でつくった32ビット形式のビットマップが Vcl.Graphics.TBitmap に読めたり読めなかったりするので調べた結果。

GIMP2.6 がつくるビットマップは BitmapVer,3 規格を拡張した独自のファイル。

もともと、Ver,3 ではアルファチャンネルをサポートしておらず、扱えるようになったのは Ver,4 から。誰が始めたのかはわからないけど、先走っちゃったらしい。あるいは、マイクロソフトが泥縄したのかも。
Delphi 2009 以降の TBitmap がサポートしているのはこの形式。

GIMP2.8 がつくるビットマップは BitmapVer,5 規格に基づいた正式なファイル。

なんだけど、32ビット形式のビットマップが読めるソフトは独自規格が対象で、正式なファイルは表示がおかしくなる。と、言うか、Ver,3 以降のビットマップを編集できるソフトが見つからなかった。GIMP 自体、2.6 と 2.8 で互換性が無いし。
TBitmap もこちらには未対応。

つまり、GIMP2.8 が作る32ビット形式のビットマップは、マイクロソフトの規格に従っているため、ほかのソフトは対応していない。

とりあえずは、TBitmap の対応待ち。まあ、みんなして様子見してるから、正式なフォーマットが復旧しないのかもしれないけど。


固定セルのリサイズ

2016/12/21
Vcl.Grids.TCustomGrid 継承コンポーネントの固定セルの幅と高さを実行時に使用者が変更する方法。

CalcSizingState プロテクトメソッドをオーバーライドする。

たとえば、先頭の固定セルの幅を変えるなら、


procedure TStringGrid.CalcSizingState(X, Y: Integer; var State: TGridState;
  var Index: Longint; var SizingPos, SizingOfs: Integer;
  var FixedInfo: TGridDrawInfo);
begin
  inherited;

  if (ColWidths[0] - 2 <= X) and (X <= ColWidths[0] + GridLineWidth + 2) and (Y <= RowHeights[0]) then
  begin
    State := gsColSizing;
    Index := 0;
    SizingPos := ColWidths[0];
    SizingOfs := ColWidths[0] - X;
  end;
end;
State はリサイズする方向。Index はセルのインデックス。SizingPos はセルの境界位置。SizingOfs はセル境界とカーソル位置の差。
Index, SizingPos, SizingOfs は、幅を変えるときは列、高さを変えるときは行を基準にした値を設定する。
これで、固定セルの境界上でカーソルの形状が変わり、ドラッグでサイズが変更できるようになる。

ただし、これだけだと ColWidth[0] の値がおかしくなるので、CalcFixedInfo もオーバーライドしてセルの幅を調整するのだけど……。
実際に幅の計算をしているらしい DrawInfo.Horz.GetExtent 引数の内容がどうなってるのかわからなかった。
仕方が無いので、MouseDown で ColWidth[0] の幅を記録し、MouseUp で書き戻してごまかす。

うん。いまいち。


タブの独自描画

2017/05/02
Vcl.ComCtrls.TTabSheet のタブの外観を変える方法。

結局は Vcl.ComCtrls.TPageControl をオーナードローするのだけど、OnDrawTab イベントではタブの見た目を変えられない(外枠を描画してしまう)ので、CN_DRAWITEM メッセージを処理する。
このとき、タブの範囲全体を描画できるよう、クリッピングリージョンを解除してから描画して、描き終わったらタブの枠を描けないようにリージョンのサイズをゼロにするのがコツ。


procedure Form1.SubWndProc(var Message: TMessage);
var
  TabCanvas: TCanvas;
  Region: HRGN;
begin
  FOlgWndProc(Message);

  case Message.Msg of
    CN_DRAWITEM:
    begin
      TabCanvas := TCanvas.Create;
      try
        TabCanvas.Handle := TWMDrawItem(Message).DrawItemStruct.hDC;
        SelectClipRgn(TabCanvas.Handle, 0); //クリッピングリージョンを解除。

    { 好きな形状で描画。
      タブの位置、インデックス、マルチラインを考慮して描き分ける。
      めちゃくちゃ長くなるので省略 }

        Region := CreateRectRgn(0, 0, 0, 0); //クリッピングリージョンのサイズをゼロに。
        try
          SelectClipRgn(TabCanvas.Handle, Region);
        finally
          DeleteObject(Region);
        end;
      finally
        TabCanvas.Free;
      end;
      Message.Result := 1;
    end;
  end;
end;
TPageControl のウィンドウプロシージャを差し替える形なら上のようになるけど、かなり手間。
ほかにも、スタイルフックを変更する方法もあるけど、手間なのは変わらない。
まずは、書き換えが必要かどうか吟味しないと余計な時間がかかる。と、いうか、かかった。


アクションツールバーにドロップダウンメニュー付きのボタンをつける

2019/12/09
Vcl.ActnMan.TActionClientItem を tbsDropDown 風に。

Vcl.ActnMenus.TActionMainMenuBar なら、アクションマネージャエディタから項目をドラッグアンドドロップで下位項目も作成できるけど、Vcl.ActnCtrls.TActionToolBar だとトップレベルの項目しか作成できないので、各項目の Items プロパティに下位項目を追加する。

具体的には、
1.フォームデザイナか構造ビューからドロップダウンメニューをつけたいボタン(TActionClientItem)を選択。
2.オブジェクトインスペクタで Items プロパティをダブルクリック。
3.ポップアップした編集ダイアログで下位項目を新規追加。
4.追加した項目の Action プロパティにアクションを割り当てる。
5.追加直後はボタンに反映されないので、保存して開きなおせば出来上がり。
ツリーノードに子ノードを追加する要領と同じなんだけど、専用のダイアログとかがないのでちょっと手間。
作成した下位項目は、TCustomDropDownButton を継承した TStandardDropDownButton, TThemedDropDownButton, TXPStyleDropDownBtn のいずれか(TActionManager.Style 次第)に結び付けられている。
なので、コードでメニューをドロップダウンするには、これの DropDownClick メソッドを使用する。


procedure TForm1.Action1Execute(Sender: TObject);
var
  Pt: TPoint;
  Item: TControl;
begin
  Pt := ActionToolBar1.ScreenToClient(Mouse.CursorPos);
  Item := ActionToolBar1.ControlAtPos(Pt, True);
  if Item is TCustomActionControl then
  begin
    Pt.X := Item.Left + Item.Width - 4; //目見当。
    Pt.Y := Item.Top + Item.Height div 2;
    Item := ActionToolBar1.ControlAtPos(Pt, True);
    if Item is TCustomDropDownButton then
      TCustomDropDownButton(Item).DropDownClick;
  end;
end;
大体こんな感じだけど、ドロップダウンボタンが右側にある前提なので、環境によってはうまくいかないかも。


リストビューとツリービューの更新タイミング

2019/04/30
テーマを変えるとフックが外れちゃうと思ったら、これが原因だったよ。

Vcl.ComCtrls.TListView.Items と TTreeView.Items の更新は、描写処理が行われるまで保留される。
このため、 Visible = False だと表示されるまで更新されず、この間は HandleAllocated = False, Items.Count = 0 で、画面オブジェクトハンドルが解放されたままになる。

フォームを非表示にしている間にデスクトップテーマが変わった場合、フォームを表示するまでリストビューとツリービューからアイテムが読み出せない。読み方によっては、アクセス違反を起こすかも。
また、アイテムが読めないので Item.Data にもアクセスできない。何らかの値を設定していた場合、この状態で終了するとメモリリークを起こす。
フォームを非表示で動作するタスクトレイ常駐型のプログラムで、動作設定にリストビューを使っていると、誤動作のもとになる。

表示されていない間は描写しないのはいいとしても、アイテムを読めないのは不具合のような……。Vcl.StdCtrls.TCheckBox とかは平気なんだし。

ともあれ、Items を読み出す前に HandleNeeded を呼んで更新しておけば、誤動作は防げる。
手っ取り早いところで、


interface

type
  TListView = class(Vcl.ComCtrls.TListView)
  private
    function GetItems: TListItems;
    procedure SetItems(Value: TListItems);
  published
    property Items: TListItems read GetItems write SetItems;
  end;

implementation

function TListView.GetItems: TListItems;
begin
  HandleNeeded;
  Result := inherited Items;
end;

procedure TListView.SetItems(Value: TListItems);
begin
  HandleNeeded;
  inherited Items := Value;
end;
というのをフォームの前に追加するのが楽かも。コピペで使いまわせるし。


戻る