|
Delphi Programming / Object Pascal
[掲載 2015年07月20日] [更新 2020年02月10日] Delphi サンプルプログラム集 |
070_TStringGrid のスクロールバー制御 |
動作確認等 |
Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32 |
|
070_TStringGrid_ScrollBar.zip [7,432 KB] 2020年02月10日版 (EXE 同梱) |
TStrinGrid のスクロールバーの設定関係のサンプルです.
TStringGrid のスクロールバーの表示は,ScrollBars プロパティで設定できますが,スクロールバーを表示する設定にしても,TStringGrid のクライアント領域内に全てのデータが表示されている場合は表示しません.これを強制的に表示するテストをしてみました.また,クライアント領域に全てのデータが表示されてる場合は,スクロールバーを無効化の状態にするテストもしています.
本ページのサンプルでは,継承クラスをコンポーネントとして登録 (インストール) しないで,[派生元のコンポーネントと交換する方法] で説明しているユニットを使用しています.
|
01_TStringGrid のスクロールバーの基本動作確認用テスト |
本ページのサンプル作成のために,最初に作成したテストプログラムです.
スクロール長が MaxShortInt の値である 127 であることが分かります.水平スクロールバー (横スクロールバー) も同じです.この値は,行数や列数を変更しても常に 127 となります.
また,TScrollInfo.nPage の値,つまり,スクロールのページサイズの値が 0 になっています.このページサイズの値は,スクロールボックス (つまみ) の値です.
|
|
図1
マウスで下端までスクロールする
- Range の値は 常に 0
- スクロール長は 127 (MaxShortInt の値)
|
|
図2
クライアントの高さを高くして下端までスクロール
- 下端のスクロール位置は 127
- この値は行数を変えても同じ
|
リスト1
TStringGrid のスクロールバーの基本動作確認用テスト |
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.StdCtrls,
Vcl.ExtCtrls;
type
TplStringGrid = class(TStringGrid)
protected
procedure WMHScroll(var Message: TWMHScroll); message WM_HSCROLL;
procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
end;
TForm1 = class(TForm)
Panel1: TPanel;
Button1: TButton;
StringGrid1: TStringGrid;
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
plStringGrid1 : TplStringGrid;
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses
plChgCompo;
{$R *.dfm}
//=============================================================================
// フォームのOnCreateイベント処理
// すぐにテストできるように,列数と列幅等を設定し,データをセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
i : Integer;
begin
//フォーム作成時にコンポーネントの入れ替え
plStringGrid1 := TplStringGrid(ChangeComponent(StringGrid1, TplStringGrid));
plStringGrid1.Options := plStringGrid1.Options + [goDrawFocusSelected];
plStringGrid1.Options := plStringGrid1.Options + [goThumbTracking];
plStringGrid1.Options := plStringGrid1.Options + [goRowSizing];
plStringGrid1.Options := plStringGrid1.Options + [goColSizing];
plStringGrid1.ScrollBars := ssBoth;
plStringGrid1.ColCount := 10;
plStringGrid1.FixedCols := 1;
plStringGrid1.FixedRows := 1;
plStringGrid1.DefaultColWidth := 70;
plStringGrid1.RowCount := 20 + plStringGrid1.FixedRows;
//左端の列幅設定
plStringGrid1.ColWidths[0] := 30;
//スクロールした状態を把握するために,左端の列に番号を表示しておく
for i := 1 to plStringGrid1.RowCount - 1 do begin
plStringGrid1.Cells[0, i] := IntToStr(i);
end;
//固定行に文字列を描画
for i := StringGrid1.FixedRows to StringGrid1.ColCount - 1 do begin
StringGrid1.Cols[i].Text := 'COL-' + IntToStr(i);
end;
end;
//=============================================================================
// 行数と列数を変えてテストしてみる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
i : Integer;
begin
StringGrid1.RowCount := 31;
for i := 1 to plStringGrid1.RowCount - 1 do begin
plStringGrid1.Cells[0, i] := IntToStr(i);
end;
StringGrid1.ColCount := 5;
end;
{ TplStringGrid }
//=============================================================================
// WM_VSCROLLメッセージ処理
//=============================================================================
procedure TplStringGrid.WMVScroll(var Message: TWMVScroll);
var
LScrollInfo : TScrollInfo;
begin
inherited;
LScrollInfo.cbSize := SizeOf(LScrollInfo);
FillChar(LScrollInfo, SizeOf(LScrollInfo), #0);
LScrollInfo.cbSize := SizeOf(LScrollInfo);
LScrollInfo.fMask := SIF_ALL;
GetScrollInfo(Handle, SB_VERT, LScrollInfo);
Form1.Memo1.Lines.BeginUpdate;
Form1.Memo1.Lines.Clear;
Form1.Memo1.Lines.Add(' nMin : ' + IntToStr(LScrollInfo.nMin));
Form1.Memo1.Lines.Add(' nMax : ' + IntToStr(LScrollInfo.nMax));
Form1.Memo1.Lines.Add(' nPage : ' + IntToStr(LScrollInfo.nPage));
Form1.Memo1.Lines.Add(' nTrackPos : ' + IntToStr(LScrollInfo.nTrackPos));
Form1.Memo1.Lines.Add(' ScrollPos : ' + IntToStr(Message.Pos));
Form1.Memo1.Lines.EndUpdate;
end;
//=============================================================================
// WM_HSCROLLメッセージ処理
//=============================================================================
procedure TplStringGrid.WMHScroll(var Message: TWMHScroll);
var
LScrollInfo : TScrollInfo;
begin
inherited;
LScrollInfo.cbSize := SizeOf(LScrollInfo);
FillChar(LScrollInfo, SizeOf(LScrollInfo), #0);
LScrollInfo.cbSize := SizeOf(LScrollInfo);
LScrollInfo.fMask := SIF_ALL;
GetScrollInfo(Handle, SB_HORZ, LScrollInfo);
Form1.Memo1.Lines.BeginUpdate;
Form1.Memo1.Lines.Clear;
Form1.Memo1.Lines.Add(' nMin : ' + IntToStr(LScrollInfo.nMin));
Form1.Memo1.Lines.Add(' nMax : ' + IntToStr(LScrollInfo.nMax));
Form1.Memo1.Lines.Add(' nPage : ' + IntToStr(LScrollInfo.nPage));
Form1.Memo1.Lines.Add(' nTrackPos : ' + IntToStr(LScrollInfo.nTrackPos));
Form1.Memo1.Lines.Add(' ScrollPos : ' + IntToStr(Message.Pos));
Form1.Memo1.Lines.EndUpdate;
end;
end.
02_TStringGrid のスクロールバーを常に表示して無効化機能を追加 |
TStringGrid の継承クラス (コンポーネント) を作成して,WM_NCCALCSIZE メッセージ処理内で強制的に TStringGrid のスタイルに WS_HSCROLL と WS_VSCROLL を追加して,スクロールバーを常に表示するようにしたサンプルです.
WM_NCCALCSIZE メッセージは,ウィンドウが生成された場合や TWinControl.RecreateWnd で再生成された場合に発生します.このことを利用しています.
この変更をすると,ScrollBars プロパティの値を ssNone にしても,垂直スクロールバーを非表示にすることはできません.
本サンプルでは,データが TStringGrid のクライアント領域に全て表示されている場合は,スクロールバーの表示を無効化状態にするコードを追加しています.また,行または列の追加と削除,行の高さの変更と列幅の変更にも対応してみました. |
|
図3
設計時の画面 |
|
図4
実行開始画面 |
|
図5
フォームをリサイズ
- TStringGrid コンポーネントの高さがデータ領域よりも大きい場合
- 垂直スクロールバーが見えているが,スクロール操作はできない無効化の状態になる
|
|
図6
Button2 をクリックして列を削除
- TStringGrid コンポーネントの幅がデータ領域よりも広い場合
- 水平スクロールバーが見えているが,スクロール操作はできない無効化の状態になる
|
リスト2
TStringGrid スクロールバーを常に表示して,無効化の機能を追加 |
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.StdCtrls,
Vcl.ExtCtrls;
type
TplStringGrid = class(Vcl.Grids.TStringGrid)
protected
procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
procedure Resize; override;
procedure ColWidthsChanged; override;
procedure RowHeightsChanged; override;
procedure SizeChanged(OldColCount: Integer; OldRowCount: Integer); override;
public
procedure Available_Disable_ScrollBars;
end;
TForm1 = class(TForm)
Panel1: TPanel;
Button1: TButton;
StringGrid1: TStringGrid;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private 宣言 }
plStringGrid1 : TplStringGrid;
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses
plChgCompo;
{$R *.dfm}
//=============================================================================
// フォームのOnCreateイベント処理
// すぐにテストできるように,列数と列幅等を設定し,データをセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
i : Integer;
begin
//フォーム作成時にコンポーネントの入れ替え
plStringGrid1 := TplStringGrid(ChangeComponent(StringGrid1, TplStringGrid));
plStringGrid1.Options := plStringGrid1.Options + [goDrawFocusSelected];
plStringGrid1.Options := plStringGrid1.Options + [goThumbTracking];
plStringGrid1.Options := plStringGrid1.Options + [goRowSizing];
plStringGrid1.Options := plStringGrid1.Options + [goColSizing];
plStringGrid1.ColCount := 10;
plStringGrid1.FixedCols := 1;
plStringGrid1.FixedRows := 1;
plStringGrid1.DefaultColWidth := 70;
plStringGrid1.RowCount := 20 + plStringGrid1.FixedRows;
//左端の列幅設定
plStringGrid1.ColWidths[0] := 30;
//スクロールした状態を把握するために,左端の列に番号を表示しておく
for i := 1 to plStringGrid1.RowCount - 1 do begin
plStringGrid1.Cells[0, i] := IntToStr(i);
end;
//固定行に文字列を描画
for i := StringGrid1.FixedRows to StringGrid1.ColCount - 1 do begin
StringGrid1.Cols[i].Text := 'COL-' + IntToStr(i);
end;
end;
//=============================================================================
// TStringGridの行数を変更した時の動作確認用
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
i : Integer;
iRowNo : Integer;
begin
plStringGrid1.RowCount := 10 + plStringGrid1.FixedRows;
//スクロールした状態を把握するために,左端の列に番号を表示しておく
for i := plStringGrid1.FixedRows to plStringGrid1.RowCount - 1 do begin
iRowNo := i - plStringGrid1.FixedRows + 1;
plStringGrid1.Cells[0, i] := IntToStr(iRowNo);
end;
plStringGrid1.td-date := plStringGrid1.FixedRows;
//行または列の追加のときはこのメソッドを実行する
plStringGrid1.Available_Disable_ScrollBars;
end;
//=============================================================================
// 列の削除(列の数を少なくして動作を確認するため)
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
if plStringGrid1.ColCount >= 4 then begin
plStringGrid1.DeleteColumn(plStringGrid1.ColCount);
end;
end;
{ TplStringGrid }
//-----------------------------------------------------------------------------
// 垂直スクロールバーと水平スクロールバーの無効化を設定するメソッド
// 行または列を追加したらこのメソッドを外部から実行する
//-----------------------------------------------------------------------------
procedure TplStringGrid.Available_Disable_ScrollBars;
var
LRealVisbleRows : Integer;
LRealVisbleCols : Integer;
begin
//垂直スクロールバーの無効化の設定
LRealVisbleRows := VisibleRowCount + FixedRows;
if LRealVisbleRows = RowCount then begin
EnableScrollBar(Handle, SB_VERT, ESB_DISABLE_BOTH);
end else begin
EnableScrollBar(Handle, SB_VERT, ESB_ENABLE_BOTH);
end;
//水平スクロールバーの無効化の設定
LRealVisbleCols := VisibleColCount + FixedCols;
if LRealVisbleCols = ColCount then begin
EnableScrollBar(Handle, SB_HORZ, ESB_DISABLE_BOTH);
end else begin
EnableScrollBar(Handle, SB_HORZ, ESB_ENABLE_BOTH);
end;
end;
//=============================================================================
// Resizeメソッド処理
// リサイズされると自動的に実行されるメソッド
//=============================================================================
procedure TplStringGrid.Resize;
begin
inherited;
Available_Disable_ScrollBars;
end;
//=============================================================================
// 行の高さを変更すると発生するメソッドの処理
//=============================================================================
procedure TplStringGrid.RowHeightsChanged;
begin
inherited;
Available_Disable_ScrollBars;
end;
//=============================================================================
// カラム(列)の幅を変更すると発生するメソッドの処理
//=============================================================================
procedure TplStringGrid.ColWidthsChanged;
begin
inherited;
Available_Disable_ScrollBars;
end;
//=============================================================================
// TplStringGrid の行または列の数が変更された時の処理
//=============================================================================
procedure TplStringGrid.SizeChanged(OldColCount, OldRowCount: Integer);
begin
inherited;
Available_Disable_ScrollBars;
end;
//=============================================================================
// TStringGirdのWM_NCCALCSIZEメッセージ処理
// スクロールバー表示のスタイルを強制的に変更
// 以下のコードを実行すると,ScrollBarsによる設定は無効になる
//=============================================================================
procedure TplStringGrid.WMNCCalcSize(var Message: TWMNCCalcSize);
var
LStyle : Integer;
begin
LStyle := GetWindowLong(Handle, GWL_STYLE);
SetWindowLong(Handle, GWL_STYLE, LStyle or WS_HSCROLL or WS_VSCROLL);
inherited;
end;
end.
03_TStringGrid のスクロールボックス ( つまみ ) のサイズを設定 |
上のサンプルでは,スクロールボックス (つまみ) のサイズは設定していません.
このスクロールボックスのサイズを変更するサンプルです.ただし,垂直スクロールバーだけの制御となっています.
スクロールボックスのサイズは,一般的にはページサイズの値です.この値を
SetScrollInfo 関数を使用して設定しています.
TStringGrid のスクロールバーのスクロール長さは MaxShortInt (= 127) の固定です.スクロール長を変更しても TStringGrid が内部で MaxShortInt の値に応じた処理をするようになっています.
したがって,スクロールボックスのサイズは,これに見合った値に設定する必要があります.
実行してみると,下図のように,スクロールバーを下端までドラッグしても,最終行が見えません.実用的には問題がありそうです.
TStringGrid のスクロールバーのスクロール長は,常に MaxShortInt の値です.それを強引にスクロールボックスの値を変更しているわけですから,スクロール位置は,最大値の MaxShortInt の値にはなりません.当然と言えば当然なのですが,この現象を回避するには,スクロール長を変更できない限り難しいように思われます.
スクロール長を変更するテストもしてみましたが,MaxShortInt の値に戻されてしまいます.
TStringGrid の内部処理を変更するか,スクロールメッセージ等の処理が必要と思われますが,ここではこれ以上追及しないことにします.
興味のある方は,Grids ユニット内の MaxShortInt を追いかけてみてください.
|
|
図7
設計時の画面
実行してスクロールボックスを下端まで移動 |
|
図8
実行してスクロールボックスを下端まで移動
- データの行は 20 行あるが,最後までスクロールできていない
|
リスト3
TStringGrid のスクロールボックス (つまみ) のサイズを設定 |
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.StdCtrls,
Vcl.ExtCtrls;
type
TplStringGrid = class(Vcl.Grids.TStringGrid)
protected
procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
procedure Resize; override;
public
procedure Available_Disable_ScrollBars;
end;
TForm1 = class(TForm)
Panel1: TPanel;
Button1: TButton;
StringGrid1: TStringGrid;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
plStringGrid1 : TplStringGrid;
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses
plChgCompo;
{$R *.dfm}
//=============================================================================
// フォームのOnCreateイベント処理
// すぐにテストできるように,列数と列幅等を設定し,データをセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
i : Integer;
begin
//フォーム作成時にコンポーネントの入れ替え
plStringGrid1 := TplStringGrid(ChangeComponent(StringGrid1, TplStringGrid));
plStringGrid1.Options := plStringGrid1.Options + [goDrawFocusSelected];
plStringGrid1.Options := plStringGrid1.Options + [goThumbTracking];
plStringGrid1.Options := plStringGrid1.Options + [goRowSizing];
plStringGrid1.Options := plStringGrid1.Options + [goColSizing];
plStringGrid1.ColCount := 10;
plStringGrid1.FixedCols := 1;
plStringGrid1.FixedRows := 1;
plStringGrid1.DefaultColWidth := 70;
plStringGrid1.RowCount := 20 + plStringGrid1.FixedRows;
//左端の列幅設定
plStringGrid1.ColWidths[0] := 30;
//スクロールした状態を把握するために,左端の列に番号を表示しておく
for i := 1 to plStringGrid1.RowCount - 1 do begin
plStringGrid1.Cells[0, i] := IntToStr(i);
end;
//固定行に文字列を描画
for i := StringGrid1.FixedRows to StringGrid1.ColCount - 1 do begin
StringGrid1.Cols[i].Text := 'COL-' + IntToStr(i);
end;
end;
//=============================================================================
// TStringGridの行数を変更した時の動作確認用
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
i : Integer;
iRowNo : Integer;
begin
plStringGrid1.RowCount := 10 + plStringGrid1.FixedRows;
//スクロールした状態を把握するために,左端の列に番号を表示しておく
for i := plStringGrid1.FixedRows to plStringGrid1.RowCount - 1 do begin
iRowNo := i - plStringGrid1.FixedRows + 1;
plStringGrid1.Cells[0, i] := IntToStr(iRowNo);
end;
plStringGrid1.td-date := plStringGrid1.FixedRows;
//行または列の追加のときはこのメソッドを実行する
plStringGrid1.Available_Disable_ScrollBars;
end;
{ TplStringGrid }
//-----------------------------------------------------------------------------
// 垂直スクロールバーのスクロールボックスサイズと無効化の設定
//-----------------------------------------------------------------------------
procedure TplStringGrid.Available_Disable_ScrollBars;
var
LRealVisbleRows : Integer;
LScrollInfo : TScrollInfo;
begin
LRealVisbleRows := VisibleRowCount + FixedRows;
if LRealVisbleRows = RowCount then begin
EnableScrollBar(Handle, SB_VERT, ESB_DISABLE_BOTH);
end else begin
EnableScrollBar(Handle, SB_VERT, ESB_ENABLE_BOTH);
FillChar(LScrollInfo, SizeOf(TScrollInfo), #0);
LScrollInfo.cbSize := SizeOf(TSCrollInfo);
GetScrollInfo(Handle, SB_VERT, LScrollInfo);
LScrollInfo.fMask := SIF_PAGE;
LScrollInfo.nPage := MaxShortInt * LRealVisbleRows div RowCount;
SetScrollInfo(Handle, SB_VERT, LScrollInfo, True);
end;
end;
//=============================================================================
// Resizeメソッド処理
// リサイズされると自動的に実行されるメソッド
//=============================================================================
procedure TplStringGrid.Resize;
begin
inherited;
Available_Disable_ScrollBars;
end;
//=============================================================================
// TStringGirdのWM_NCCALCSIZEメッセージ処理
// スクロールバー表示のスタイルを強制的に変更
//=============================================================================
procedure TplStringGrid.WMNCCalcSize(var Message: TWMNCCalcSize);
var
LStyle : Integer;
begin
LStyle := GetWindowLong(Handle, GWL_STYLE);
SetWindowLong(Handle, GWL_STYLE, LStyle or WS_HSCROLL or WS_VSCROLL);
inherited;
end;
end.
04_スクロール制御用に TScrollBar を実装した TStringGrid |
前項の現象を回避する方法として,スクロールの制御を TScrollBar を配置して行う方法です.
フォーム上に TScrollBar を配置したのではコードが複雑になりますが,TStringGrid の継承クラス (コンポーネント) を作成して, protected 部のメソッド類を使用すれは効率的な処理が可能となります.例えば,行や列の追加や削除,あるいは行の高さの変更や列幅の変更を自動的にに検出して処理できます.
以下は,TScrollBar を実装したコンポーネントの例です.
このコンポーネントは TStringGrid の ScrollBars プロパティの仕様を変更して,ssNone 以外のときは常にスクロールバーを表示するようになっています.例えば ssVertical にすると縦スクロールバーだけを常に表示します.
ScrollBars プロパティの設定は実行時に反映されます.オブジェクト・インスペクタで変更しても表示は変わりません..
|
[備考]
垂直スクロールバーのページサイズ,つまり,スクロールバーのボックス (つまみ) のサイズと位置は,行の高さが全て同じと仮定して計算しています.水平スクロールバーは,列の幅が全て同じと仮定した時のクライアント領域の表示幅に相当する列数をボックスのサイズとしています.この設定によって,全列幅のどの程度の範囲が表示されているかを確認できます.
|
|
図9
実行時にコンポーネントを生成してテスト |
このコンポーネントは,スクロールバーの背景色のプロパティ Color を実装しています.
|
plStringGrid1.VScrollBar.Color := clLime;
plStringGrid1.HScrollBar.Color := $000984FF;
|
図10
スクロールバーの背景色を変更 |
リスト4
スクロールバーの制御を TScrollBar コンポーネントで行う TStringGrid コンポーネント |
unit plStrGrid;
interface
uses
Winapi.Windows, Winapi.Messages, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls,
Vcl.Grids, System.UITypes, Vcl.Graphics, Vcl.Dialogs, System.SysUtils;
type
TplStringGrid = class;
TplStringGridBar = class(TScrollBar)
private
FStrGrid : TplStringGrid;
protected
procedure Scroll(ScrollCode: TScrollCode; var ScrollPos: Integer); override;
procedure CNCtlcolorscrollbar(var Message: TMessage);
message CN_CTLCOLORSCROLLBAR;
procedure Change; override;
public
constructor Create(AOwner: TComponent); override;
published
property Color;
end;
TplStringGrid = class(TStringGrid)
private
FVPageSize : Integer;
FHPageSize : Integer;
FVScrollMax : Integer;
FHScrollMax : Integer;
FVScrollBar : TplStringGridBar;
FHScrollBar : TplStringGridBar;
FBottomMargin : Integer;
FRightMargin : Integer;
FScrollBars : System.UITypes.TScrollStyle;
procedure SetScrollBars(Value: TScrollStyle);
procedure ScrollBarsSetSizePos(ALeft, ATop, AWidth, AHeight: Integer);
function CalcHorzPageSize: Integer;
protected
procedure SetParent(AParent: TWinControl); override;
procedure Resize; override;
procedure SizeChanged(OldColCount: Integer; OldRowCount: Integer); override;
procedure ColWidthsChanged; override;
procedure RowHeightsChanged; override;
procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
procedure CMVisiblechanged(var Message: TMessage);
message CM_VISIBLECHANGED;
procedure CMEnabledchanged(var Message: TMessage);
message CM_ENABLEDCHANGED;
procedure WMNCPaint(var Message: TWMNCPaint); message WM_NCPAINT;
function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
MousePos: TPoint): Boolean; override;
procedure TopLeftChanged; override;
public
property VScrollBar : TplStringGridBar read FVScrollBar;
property HScrollBar : TplStringGridBar read FHScrollBar;
procedure SetBounds(ALeft: Integer; ATop: Integer; AWidth: Integer;
AHeight: Integer); override;
constructor Create(AOwner: TComponent); override;
published
property ScrollBars : TScrollStyle read FScrollBars write SetScrollBars;
end;
procedure Register;
implementation
{ TplStringGrid }
//=============================================================================
// コンポーネントのコンストラクタ (生成時) 処理
//=============================================================================
constructor TplStringGrid.Create(AOwner: TComponent);
begin
inherited;
ScrollBars := ssNone;
FScrollBars := ssBoth;
end;
//=============================================================================
// SetParent メソッドの処理
// TScrollBar の継承コンポーネントを生成
//
// VScrollBar : 縦スクロールバー
// HScrollBar : 横スクロールバー
//=============================================================================
procedure TplStringGrid.SetParent(AParent: TWinControl);
begin
inherited;
if not Assigned(FVScrollBar) then begin
FVScrollBar := TplStringGridBar.Create(self);
FVScrollBar.Parent := Parent;
FVScrollBar.Kind := sbVertical;
FHScrollBar := TplStringGridBar.Create(Self);
FHScrollBar.Parent := Parent;
FHScrollBar.Kind := sbHorizontal;
FRightMargin := FVScrollBar.Width;
FBottomMargin := FHScrollBar.Height;
ScrollBarsSetSizePos(Left, Top, Width, Height);
end;
end;
//-----------------------------------------------------------------------------
// ScrollBars プロパティの独自実装
// ssNone 以外は常に表示となる
// ex. ssVertical にすると縦スクロールバーだけが常に表示となる
//-----------------------------------------------------------------------------
procedure TplStringGrid.SetScrollBars(Value: TScrollStyle);
begin
if not Assigned(FVScrollBar) then Exit;
if Value <> FScrollBars then begin
FScrollBars := Value;
FVScrollBar.Visible := (Value = ssBoth) or (Value = ssVertical);
FHScrollBar.Visible := (Value = ssBoth) or (Value = ssHorizontal);
RecreateWnd;
ScrollBarsSetSizePos(Left, Top, Width, Height);
end;
end;
//-----------------------------------------------------------------------------
// スクロールバーの位置を設定
//-----------------------------------------------------------------------------
procedure TplStringGrid.ScrollBarsSetSizePOs(ALeft, ATop, AWidth, AHeight: Integer);
begin
if Assigned(FVScrollBar) then begin
FVScrollBar.Top := ATop;
FVScrollBar.Left := ALeft + AWidth - FRightMargin;
if FHScrollBar.Visible then begin
FVScrollBar.Height := AHeight - FBottomMargin + 2;
end else begin
FVScrollBar.Height := AHeight;
end;
FHScrollBar.Top := ATop + AHeight - FBottomMargin;
FHScrollBar.Left := ALeft;
if FVScrollBar.Visible then begin
FHScrollBar.Width := AWidth - FRightMargin + 2;
end else begin
FHScrollBar.Width := AWidth;
end;
end;
end;
//=============================================================================
// このコンポの位置やサイズが変更された時の処理
// スクロールバーの位置とサイズを設定し直す必要がある
//
// SetBounds はコントロールの位置やサイズが変更された時に自動的に実行される
// この後に自動的に Resize メソッドが実行される
//=============================================================================
procedure TplStringGrid.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
inherited;
ScrollBarsSetSizePos(ALeft, ATop, AWidth, AHeight);
end;
//-----------------------------------------------------------------------------
// 列方向のスクロールのページサイズの計算
// 全ての列幅が同じと仮定した時に(ClientWidth - 固定列の合計ピクセル数)
// に表示可能な列数をページサイズとする
//-----------------------------------------------------------------------------
function TplStringGrid.CalcHorzPageSize: Integer;
var
LColWidth : Integer;
LFixedW : Integer;
LDblWidth : Double;
LWidth : Integer;
LIndex : Integer;
begin
LColWidth := 0;
for LIndex := FixedCols to ColCount - 1 do begin
if ColWidths[LIndex] > 0 then begin
LColWidth := LColWidth + ColWidths[LIndex];
end;
end;
LFixedW := 0;
for LIndex := 0 to FixedCols - 1 do begin
if ColWidths[LIndex] > 0 then begin
LFixedW := LFixedW + ColWidths[LIndex];
end;
end;
if ClientWidth > (LColWidth + LFixedW) then begin
LWidth := LColWidth;
end else begin
LWidth := ClientWidth - LFixedW;
end;
LDblWidth := (ColCount - FixedCols) * LWidth / LColWidth;
Result := Trunc(LDblWidth);
end;
//=============================================================================
// TplStringGrid のサイズが変更された時の処理
// スクロールバーのスクロールボックス(つまみ)のサイズとその位置を設定し直す
// スクロールバーのサイズの設定は SetBounds メソッドで自動実行する
// コンポのサイズ変更時は最初に SetBounds メソッドが実行される
//=============================================================================
procedure TplStringGrid.Resize;
var
LOldVPos : Integer;
LOldHPos : Integer;
begin
inherited;
if not Assigned(FVScrollBar) then Exit;
LOldVPos := FVScrollBar.Position;
LOldHPos := FHScrollBar.Position;
//-----------------------------------
// 垂直スクロールバー関係の設定
//-----------------------------------
FVScrollMax := RowCount - FixedRows;
if (VisibleRowCount = FVScrollMax) or (FVScrollMax <= 1) then begin
// 列数が少ない場合
FVScrollBar.Enabled := False;
FVScrollBar.Position := 0;
end else begin
FVScrollBar.Enabled := True;
// PageSizeを先に設定する
FVPageSize := VisibleRowCount;
// 0 ベースとしての設定
FVScrollBar.PageSize := FVPageSize;
if (FVScrollMax < 3) or (FVPageSize < 2) then begin
FVScrollBar.LargeChange := 1;
end else begin
FVScrollBar.LargeChange := FVPageSize - 1;
end;
FVScrollBar.SmallChange := 1;
FVScrollBar.Min := 0;
FVScrollBar.Max := FVScrollMax - 1;
FVScrollBar.Position := LOldVPos;
end;
//-----------------------------------
// 水平スクロールバー関係の設定
//-----------------------------------
FHScrollMax := ColCount - FixedCols;
if (VisibleColCount = FHScrollMax) or (FHScrollMax <= 1) then begin
// 列数が少ない場合
FHScrollBar.Enabled := False;
FHScrollBar.Position := 0;
end else begin
FHScrollBar.Enabled := True;
// PageSizeを先に設定する
FHPageSize := CalcHorzPageSize;
// 0 ベースとしての設定
FHScrollBar.PageSize := FHPageSize;
if (FHScrollMax < 3) or (FHPageSize < 2) then begin
FHScrollBar.LargeChange := 1;
end else begin
FHScrollBar.LargeChange := FHPageSize - 1;
end;
FHScrollBar.SmallChange := 1;
FHScrollBar.Min := 0;
FHScrollBar.Max := FHScrollMax - 1;
FHScrollBar.Position := LOldHPos;
end;
end;
//=============================================================================
// 行または列の数が変更された時の処理
// スクロールバーのスクロールボックス(つまみ)のサイズとその位置を設定し直す
//=============================================================================
procedure TplStringGrid.SizeChanged(OldColCount, OldRowCount: Integer);
begin
// 継承元のメソッドには何の処理もない
inherited;
Resize;
end;
//=============================================================================
// 行の高さが変更された時の処理
// スクロールバーのスクロールボックス(つまみ)のサイズとその位置を設定し直す
//=============================================================================
procedure TplStringGrid.RowHeightsChanged;
begin
inherited;
Resize;
end;
//=============================================================================
// 列の幅が変更された時の処理
// スクロールバーのスクロールボックス(つまみ)のサイズとその位置を設定し直す
//=============================================================================
procedure TplStringGrid.ColWidthsChanged;
begin
inherited;
Resize;
end;
//=============================================================================
// Visible プロパティが変更された時の処理
//=============================================================================
procedure TplStringGrid.CMVisiblechanged(var Message: TMessage);
begin
inherited;
if Assigned(FVScrollBar) then begin
FVScrollBar.Visible := Visible;
FHScrollBar.Visible := Visible;
end;
end;
//=============================================================================
// Enabled プロパティが変更された時の処理
//=============================================================================
procedure TplStringGrid.CMEnabledchanged(var Message: TMessage);
begin
inherited;
if Assigned(FVScrollBar) then begin
FVScrollBar.Enabled := Enabled;
FHScrollBar.Enabled := Enabled;
end;
end;
//=============================================================================
// td-date または LeftCol が変更された時の処理
//=============================================================================
procedure TplStringGrid.TopLeftChanged;
begin
inherited;
FVScrollBar.Position := td-date - FixedRows;
FHScrollBar.Position := LeftCol - FixedCols;
end;
//=============================================================================
// DoMouseWheel メソッド (OnMouseWheel イベント相当) の処理
//
// 仕様を変更している
// 元の TStringGrid ではキー操作によるスクロールと同じ
// クライアント領域内ではスクロールしない
// 本コンポでは直ちにスクロールする
// スクロール量はシステムの設定値を使用
//=============================================================================
function TplStringGrid.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
MousePos: TPoint): Boolean;
var
LDelta : Integer;
LIndex : Integer;
begin
SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, @LDelta, 0);
if WheelDelta < 0 then begin
for LIndex := 0 to LDelta - 1 do begin
Perform(WM_VSCROLL, MakeWord(SB_LINEDOWN, 0), 0);
end;
Result := True;
end else begin
for LIndex := 0 to LDelta - 1 do begin
Perform(WM_VSCROLL, MakeWord(SB_LINEUP, 0), 0);
end;
Result := True;
end;
end;
//-----------------------------------------------------------------------------
// WM_NCCALCSIZE メッセージ処理
// スクロールバー表示のスタイルを強制的に変更
// 以下のコードを実行すると元の ScrollBarsによる設定は無効になる
// 本コンポでは ScrollBars プロパティを独自仕様にしている
//-----------------------------------------------------------------------------
procedure TplStringGrid.WMNCCalcSize(var Message: TWMNCCalcSize);
var
LStyle : Integer;
LBMargin : Integer;
LRMargin : Integer;
LRect : TRect;
LResRect : TRect;
begin
if Visible then begin
LStyle := GetWindowLong(Handle, GWL_STYLE);
LStyle := LStyle and not WS_VSCROLL and not WS_HSCROLL;
SetWindowLong(Handle, GWL_STYLE, LStyle);
end;
inherited;
// クライアント領域の設定
// スクロールバーを配置する非クライアント領域の設定
// スクロールバーの位置とサイズは ScrollBars プロパティ設定メソッド内で実行
if Assigned(FVScrollBar) then begin
if Message.CalcValidRects then begin
LRMargin := FRightMargin;
LBMargin := FBottomMargin;
if not FVScrollBar.Visible then LRMargin := 0;
if not FHScrollBar.Visible then LBMargin := 0;
// rgrc[0] : 移動またはサイズ変更後のウィンドウ値
// rgrc[1] : 移動またはサイズ変更前のウィンドウ値
// rgrc[2] : 移動またはサイズ変更前のクライアント領域の値
// と MS Windows Dev Center にはあるのだが...
LRect := message.CalcSize_Params.rgrc[0];
LResRect := Rect(Left, Top, LRect.Right - LRMargin, LRect.Bottom - LBMargin);
message.CalcSize_Params.rgrc[0] := LResRect;
Message.Result := 0
end;
end;
end;
//-----------------------------------------------------------------------------
// WM_NCPAINT メッセージ処理
// 非クライアント領域の再描画が必要になった時に発生するメッセージ
//-----------------------------------------------------------------------------
procedure TplStringGrid.WMNCPaint(var Message: TWMNCPaint);
var
LhHDC : HDC;
LCanvas : TCanvas;
begin
inherited;
LCanvas := TCanvas.Create;
LhHDC := GetWindowDC(Handle);
try
LCanvas.Handle := LhHDC;
// ClipRect でを塗り潰してクライアント領域を再描画
LCanvas.FillRect(LCanvas.ClipRect);
Invalidate;
finally
ReleaseDC(Handle, LhHDC);
FreeAndNil(LCanvas);
end;
Message.Result := 0;
end;
{ TplStringGridBar }
//=============================================================================
// コンポーネントのコンストラクタ (生成時) 処理
// スクロールバー (TScrollBar の継承クラス)
//=============================================================================
constructor TplStringGridBar.Create(AOwner: TComponent);
begin
inherited;
FStrGrid := TplStringGrid(AOwner);
end;
//-----------------------------------------------------------------------------
// CN_CTLCOLORSCROLLBAR メッセージ処理
// Color プロパティの実装用
//-----------------------------------------------------------------------------
procedure TplStringGridBar.CNCtlcolorscrollbar(var Message: TMessage);
begin
DefaultHandler(Message)
end;
//=============================================================================
// Change メソッドの処理
// 外部から Postion プロパティの値を設定した場合の処理
// td-date, LeftCol が変わるので TStringGrid で自動的に処理してくれる
//=============================================================================
procedure TplStringGridBar.Change;
begin
inherited;
if Self.Kind = sbVertical then begin
FStrGrid. td-date := Position + FStrGrid.FixedRows;
end else
if Self.Kind = sbHorizontal then begin
FStrGrid. LeftCol := Position + FStrGrid.FixedCols;
end;
end;
//=============================================================================
// スクロール時の処理
// このメソッド(イベント) では TScrollBar 操作によるスクロールだけを処理
// Position プロパティによる設定は処理しない
// トラッキングが ON と同じスクロールなので大量のデータで難があるかも知れない
//
// TScrollBar は下端または右端まで移動して操を続けると Position の値が増加する
// Position の値の増加が不要になったら引数の ScrollPos に戻す値を渡す
//=============================================================================
procedure TplStringGridBar.Scroll(ScrollCode: TScrollCode;
var ScrollPos: Integer);
var
LScrollPos : Integer;
LCellPos : Integer;
LRowPos : Integer;
LLastCol : Integer;
LLastRow : Integer;
begin
inherited;
if ScrollCode = scPosition then Exit;
LScrollPos := ScrollPos;
if Self.Kind = sbVertical then begin
LRowPos := LScrollPos + FStrGrid.FixedRows;
LLastRow := LRowPos + FStrGrid.VisibleRowCount - FStrGrid.FixedRows;
if LLastRow > (FStrGrid.RowCount - FStrGrid.FixedRows) then begin
LRowPos := FStrGrid.RowCount - FStrGrid.VisibleRowCount - FStrGrid.FixedRows;
Position := LRowPos;
// 変更した値を引数に返す
ScrollPos := Position;
end else begin
if LRowPos <= FStrGrid.FixedRows then begin
FStrGrid.td-date := FStrGrid.FixedRows;
end else begin
FStrGrid.td-date := LRowPos;
end;
end;
end;
if Self.Kind = sbHorizontal then begin
LCellPos := LScrollPos + FStrGrid.FixedCols;
LLastCol := LCellPos + FStrGrid.VisibleColCount - FStrGrid.FixedCols;
if LLastCol > (FStrGrid.ColCount - FStrGrid.FixedCols) then begin
LCellPos := FStrGrid.ColCount - FStrGrid.VisibleColCount - FStrGrid.FixedCols;
Position := LCellPos;
// 変更した値を引数に返す
ScrollPos := Position;
end else begin
if LCellPos <= FStrGrid.FixedCols then begin
FStrGrid.LeftCol := FStrGrid.FixedCols;
end else begin
FStrGrid.LeftCol := LCellPos;
end;
end;
end;
end;
//=============================================================================
// コンポーネント登録情報
// コンポーネントパレットの[plXRAY]というタブに登録
// 指定したタブがない場合は,自動的に作成される
//=============================================================================
procedure Register;
begin
RegisterComponents('plXRAY', [TplStringGrid]);
end;
end.
05_TStringGrid のスクロールバーを常に非表示 |
これまでのサンプルとは逆に,スクロールバーを非表示にするには,TStringGrid.ScrollBars プロパティの値で指定します.ssNone にすると垂直と水平スクロールバー共に表示しません.
ssHorizontal は水平スクロールバーだけを表示する設定です.ssVertical は垂直スクロールバーだけを表示する設定です.
|
StringGrid1.ScrollBars := ssNone;
StringGrid1.ScrollBars := ssVertical;
StringGrid1.ScrollBars := ssHorizontal;
StringGrid1.ScrollBars := ssBoth;
本ページの最初のサンプルと同じ方法も使えます.
WM_NCCALCSIZE メッセージ処理内で,LStyle or WS_VSCROLL の部分を LStyle and not WS_VSCROLL とすると,垂直スクロールバーを常に表示しないようにできます.この設定をすると,ScrollBars プロパティの値を ssBoth あるいは ssVertical にしても垂直スクロールバーは表示できません.
|
|
図11
スクロールバーが非表示の状態 |
リスト5
TStringGrid の垂直スクロールバーを表示しないようにするには,and not 演算にする |
procedure TplStringGrid.WMNCCalcSize(var Message: TWMNCCalcSize);
var
LStyle : Integer;
begin
LStyle := GetWindowLong(Handle, GWL_STYLE);
SetWindowLong(Handle, GWL_STYLE, LStyle and not WS_VSCROLL);
inherited;
end;
06_TStringGrid のスクロールのトラッキング |
スクロールのトラッキング (追従) 機能の動作確認用サンプルです.
Button1 クリックでトラッキングの ON, OFF を切り換えます.
スクロールバーのスクロールボックスをドラッグすると,スクロールボタンを放すまで,グリッドはスクロールしません.これが TStringGrid のデフォルトの設定です.
Options プロパティに goThumbTracking を含めると,スクロールボックスのドラッグと同時にグリッドもスクロールするようになります.
このスクロールのトラッキング (追従) を「滑らかなスクロール」と言うことがあるようです.
|
|
図12
Options に goThumbTracking がない場合
- スクロールボックスを放すまでグリッドはスクロールしない
|
|
図13
Options に goThumbTracking がある場合
- スクロールボックスのドラッグと同時にグリッドもスクロールする
|
リスト6
TStringGrid のスクロールのトラッキングの動作確認用サンプル |
//=============================================================================
// TStringGrid.Optionsの設定
// OptionsプロパティにgoThumbTracking要素を追加または削除する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LGridOptions : TGridOptions;
begin
LGridOptions := StringGrid1.Options;
//goThumbTrackingが含まれていれば削除
//goThumbTrackingが含まれいなければ追加
if goThumbTracking in LGridOptions then begin
System.Exclude(LGridOptions, goThumbTracking);
end else begin
System.Include(LGridOptions, goThumbTracking);
end;
StringGrid1.Options := LGridOptions;
end;
07_Options プロパティ ( 集合型プロパティ ) の集合値の取得 |
上のサンプルで,TStringGrid の Options プロパティの設定をしています.この Options プロパティにはどんな要素があるかを,実行時に確認してみます.
TGridOption という列挙型があります.この末尾に複数形を意味する s を付けて,列挙型の要素を複数選択形式とした TGridOptions という集合型が Options プロパティの型です.Options プロパティは,オブジェクトインスペクタでも確認できる,列挙型の要素の組み合わせです.
|
TGridOption 列挙型と TGridOptions 集合型 |
TGridOption = (goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine,
goRangeSelect, goDrawFocusSelected, goRowSizing, goColSizing, goRowMoving,
goColMoving, goEditing, goTabs, goRowSelect, goAlwaysShowEditor,
goThumbTracking, goFixedColClick, goFixedRowClick, goFixedHotTrack);
TGridOptions = set of TGridOption;
TypInfo ユニットあるルーチンや型を使用すると,列挙型の要素の列挙や,集合型の要素を調べることができます.
Button1 クリックでは,TGridOption 列挙型の全ての要素名を列挙します.
Button2 クリックでは,この TGridOption のセット,つまり,設定されている Options プロパティの要素名のリストを取得して表示します.
|
実行時に,集合型のプロパティの集合に値を追加したり,集合に値を削除する方法は,以下の記事で確認してください.
|
|
図14
Options プロパティで設定可能な値のリスト
|
|
図15
現在有効になっている Options プロパティの値 |
リスト7
Options プロパティ (集合型プロパティ) の集合値の取得 |
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.StdCtrls,
Vcl.ExtCtrls;
type
TForm1 = class(TForm)
Panel1: TPanel;
Button1: TButton;
Button2: TButton;
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses
TypInfo;
{$R *.dfm}
//=============================================================================
// フォームのOnCreateイベント処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
//フォントの設定
Memo1.Font.Charset := ANSI_CHARSET;
Memo1.Font.Name := 'Courier New';
Memo1.Font.Size := 9;
end;
//=============================================================================
// TStringGrid.Optionsで設定可能な値を取得
// TStringGrid.Optionsの要素はTGridOption列挙型として定義されている
//
// usesにTypInfoが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
pInfo : PTypeInfo;
pData : PTypeData;
i : Integer;
begin
Memo1.Lines.Clear;
pInfo := TypeInfo(TGridOption);
//列挙型であれば調査
if pInfo^.Kind = tkEnumeration then begin
pData := GetTypeData(pInfo);
for i := pData^.MinValue to pData^.MaxValue do begin
Memo1.Lines.Add(GetEnumName(pInfo, i));
end;
end;
end;
//=============================================================================
// TStringGrid.Optionsで設定した要素のリストを取得
// 新規にTStringGridを生成しているのでデフォルトの設定値ということになる
//
// usesにTypInfoが必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LGrid : TStringGrid;
LPropName : String;
LSL : TStringList;
i : Integer;
begin
Memo1.Lines.Clear;
LGrid := TStringGrid.Create(Self);
try
LPropName := 'Options';
LSL := TStringList.Create;
try
LSL.CommaText := SetToString(GetPropInfo(LGrid.ClassInfo, LPropName),
GetOrdProp(LGrid, LPropName), False);
for i := 0 to LSL.Count - 1 do begin
Memo1.Lines.Add(LSL[i]);
end;
finally
FreeAndNil(LSL);
end;
finally
FreeAndNIl(LGrid);
end;
end;
end.
08_TStringGrid のスクロールバーのコンテキストメニュー |
TStringGrid のスクロールバー上で,マウスの右ボタンをクリックすると,下図のようなコンテキストメニューが現れます.このコンテキストメニューを表示しないようにする方法です.
コンテキストメニューが表示する際,WM_CONTEXTMENU メッセージが発生します.このメッセージ処理内で何もしなければコンテキストメニューは現れません.何もしないと,TStringGrid で TPopupMenue コンポーネント等を使用していると,そのポップアップメニューも表示しなくなってしまいます.
そこで,マウスクリックの位置が,TStringGrid のクライアント領域の時は,WM_CONTEXTMENU メッセージ本来の処理を実行するようにしています.また,このコードでは,固定行ではメニューの表示をしないようにしていますが,実際には,TPopupMenu を表示する際に制御できます.
|
|
図16
スクロールバーのコンテキストメニュー
|
リスト8
TStringGrid のスクロールバーのコンテキストメニューを表示しないようにする |
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.StdCtrls,
Vcl.ExtCtrls;
type
TplStringGrid = class(TStringGrid)
protected
procedure WMContextMenu(var Message: TWMContextMenu);
message WM_CONTEXTMENU;
end;
TForm1 = class(TForm)
Panel1: TPanel;
StringGrid1: TStringGrid;
procedure FormCreate(Sender: TObject);
private
{ Private 宣言 }
plStringGrid1 : TplStringGrid;
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses
plChgCompo;
{$R *.dfm}
//=============================================================================
// フォームのOnCreateイベント処理
// すぐにテストできるように,列数と列幅等を設定し,データをセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
i : Integer;
begin
//フォーム作成時にコンポーネントの入れ替え
plStringGrid1 := TplStringGrid(ChangeComponent(StringGrid1, TplStringGrid));
plStringGrid1.Options := plStringGrid1.Options + [goDrawFocusSelected];
plStringGrid1.Options := plStringGrid1.Options + [goThumbTracking];
plStringGrid1.ScrollBars := ssVertical;
plStringGrid1.ColCount := 10;
plStringGrid1.FixedCols := 1;
plStringGrid1.FixedRows := 1;
plStringGrid1.DefaultColWidth := 70;
plStringGrid1.RowCount := 20 + plStringGrid1.FixedRows;
//左端の列幅設定
plStringGrid1.ColWidths[0] := 30;
//スクロールした状態を把握するために,左端の列に番号を表示しておく
for i := 1 to plStringGrid1.RowCount - 1 do begin
plStringGrid1.Cells[0, i] := IntToStr(i);
end;
end;
{ TplStringGrid }
//=============================================================================
// WM_CONTEXTMENUメッセージ処理
// スクロールバーのコンテキストメニューを表示しないようにする
//=============================================================================
procedure TplStringGrid.WMContextMenu(var Message: TWMContextMenu);
var
i : Integer;
LCellH : Integer;
LPos : TPoint;
LRect : TRect;
begin
LCellH := 0;
for i := 0 to FixedRows - 1 do begin
LCellH := LCellH + RowHeights[i] + GridLineWidth;
end;
LPos := ScreenToClient(Point(Message.XPos, Message.YPos));
LRect := ClientRect;
LRect.Top := LRect.Top + LCellH;
if PtInRect(LRect, LPos) then begin
inherited;
end else begin
Message.Result := 0;
end;
end;
end.
09_ShowScrollBar 関数と SetScrollInfo 関数 |
ShowScrollBar 関数を使用すると,スクロールバーを表示したり非表示にすることがてぎます.
|
//=============================================================================
// 横 (水平) スクロールバーと縦 (垂直) スクロールバーを非表示にする
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
begin
ShowScrollBar(StringGrid1.Handle, SB_HORZ , False);
ShowScrollBar(StringGrid1.Handle, SB_VERT , False);
end;
//=============================================================================
// 横 (水平) スクロールバーと縦 (垂直) スクロールバーを表示する
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
begin
ShowScrollBar(StringGrid1.Handle, SB_HORZ , True);
ShowScrollBar(StringGrid1.Handle, SB_VERT , True);
end;
SetScrollInfo 関数でも,レコード型の nMin と nMax メンバの値を共に 0 にすると,スクロールバーを非表示することができます.
ただし,以下のコードを,スクロールバーを持つコントロールを利用する側から実行しても非表示にはできません.レコード型のメンバ名から推測できるように,SetScrollInfo 関数はスクロールバーの情報を設定するための関数です.継承クラスを作成して,スクロールバー関係のメッセージ処理内で以下のコードを実行します.
例えば TDBGrid には
UpdateScrollBar
というメソッドがあります.このメソッド内で以下のコードを実行すると,縦 (垂直)) スクロールバーを非表示にできます.
|
//=============================================================================
// 縦 (垂直) スクロールバーを非表示にする設定
// このコードを実行しても非表示にはならない
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
LScrollInfo : TScrollInfo;
begin
LScrollInfo.cbSize := sizeof(LScrollInfo);
LScrollInfo.fMask := SIF_RANGE;
LScrollInfo.nMin := 0;
LScrollInfo.nMax := 0;
SetScrollInfo(StringGrid1.Handle, SB_VERT, LScrollInfo, True);
end;
ShowScrollBar 関数と SetScrollInfo 関数のどちらも,コントロールのスクロールバーが本来非表示になる状態の時はスクロールバーを表示することはできません.
TStringGrid では,クライアント領域にデータが全て表示されているときはスクロールバーを表示しません.この状態の時は,ShowScrollBar 関数でも SetScrollInfo 関数でもスクロールバーを表示することはできません. |
10_ウィンドウスタイルの WS_VSCROLL と WS_HSCROLL |
コントロールの生成時に,スタイルにスクロールバーを含めなければスクロールバーは表示できないことになります.
CreateParams メソッドでは生成するウィンドウのスタイルや拡張スタイルを変更することができるはずですが,TStringGrid, TDBGrid, TListBox 等のコントロールは,以下のコードを実行しても非表示とはなりません.スクロールバー関係のスタイル変更は自動で行われているようです.
|
//=============================================================================
// ウィントウのスタイルから横 (水平) と縦 (垂直) スクロールバーを除外する
// このコードを実行しても スクロールバーは非表示にならない
//=============================================================================
procedure TplStringGrid.CreateParams(var Params: TCreateParams);
var
LStyle : DWORD;
begin
inherited CreateParams(Params);
LStyle := Params.Style and not WS_VSCROLL and not WS_HSCROLL;
Params.Style := LStyle;
end;
そこで,本ページのサンプルでは,実行時にウィンドウ (コントロール) のスタイルを強制的に変更しています.それにより,スクロールバーの常に表示,常に非表示の機能を実現しています.スタイルの変更は,WM_NCCALCSIZE メッセージ内で処理しています.
WM_NCCALCSIZE メッセージは,ウィンドウの生成,再生成,あるいは非クライアント領域のサイズ変更の際に発生するメッセージです.スクロールバーは非クライアント領域に描画されていますから,確実な方法と思われます.
WM_NCCALCSIZE
メッセージ内でのウィンドウのスタイルの変更であれば,VCL スタイルの適用があってもスクロールバーの表示の制御が可能です.
次ページ以降の,TDBGrid, TListBox 等のコントロールのスクロールバーの表示制御も,この考え方でサンプルを作成しています.
|
[備考]
ウィンドウスタイルに WS_VSCROLL と WS_HSCROLL がないと,WM_VSCROLL, WM_HSCROLL メッセージは検出できません.これらのメッセージはコントロールの標準スクロールバーが発行しています.
マウスホイール操作によるスクロールの検出は可能です.
|
11_TScrollBar の背景色 - 継承クラスと WndProc とサブクラス化 |
TScrollBar を使用しているサンプルでは背景色のプロパティ Color を実装しています.
Color プロパティは TScrollBar の継承元の protected 部には実装されています (TControl.Color).背景色の処理を行うのは CN_CTLCOLORSCROLLBAR メッセージですが,このメッセージは,デフォルトで自分自身の WndProc を実行するだけてす.これを DefaultHandler にすれば Color プロパティの設定が有効になります.
本項のサンプルは ZIP 書庫に添付していません.
|
[備考 1]
TStringGrid, TDBGrid, TListBox 等,スクロールバー付きのコントロールのスクロールバーは,背景色を変更することはできません.
|
|
図17
TScrollBar の Color プロパティを有効にする
|
|
図18
TScrollBar の Color プロパティを無効にする
- DefaultColor の値を True にする
- TScrollBar の継承クラスを実行時に生成
|
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;
type
TplScrollBar = class(TScrollBar)
private
FDefaultColor : Boolean;
protected
procedure CNCtlcolorscrollbar(var Message: TMessage);
message CN_CTLCOLORSCROLLBAR;
public
constructor Create(AOwner: TComponent); override;
property Color;
property DefaultColor : Boolean read FDefaultColor write FDefaultColor;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
plScrollBar1 : TplScrollBar;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
if not Assigned(plScrollBar1) then begin
plScrollBar1 := TplScrollBar.Create(Self);
plScrollBar1.Parent := Self;
plScrollBar1.Left := 20;
plScrollBar1.Top := 50;
plScrollBar1.Width := 250;
end;
plScrollBar1.Color := clGreen;
plScrollBar1.DefaultColor := False;
end;
{ TplScrollBar }
constructor TplScrollBar.Create(AOwner: TComponent);
begin
inherited;
FDefaultColor := True;
end;
procedure TplScrollBar.CNCtlcolorscrollbar(var Message: TMessage);
begin
if FDefaultColor then begin
with Message do
CallWindowProc(DefWndProc, Handle, Msg, WParam, LParam);
end else begin
DefaultHandler(Message);
end;
end;
end.
CN_CTLCOLORSCROLLBAR メッセージの戻り値を HBRUSH にすると,そのブラシで背景色を描画します.
HBRUSH はブラシのハンドルです.Delphi では TBrush.Handle の値です.単色のブラシであれば,以下のコードのように CreateSolidBrush 関数で作成できます.
|
|
図19
ブラシで背景色を設定
|
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;
type
TplScrollBar = class(TScrollBar)
private
FBrush : HBRUSH;
protected
procedure CNCtlcolorscrollbar(var Message: TMessage);
message CN_CTLCOLORSCROLLBAR;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
plScrollBar1 : TplScrollBar;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
if not Assigned(plScrollBar1) then begin
plScrollBar1 := TplScrollBar.Create(Self);
plScrollBar1.Parent := Self;
plScrollBar1.Left := 20;
plScrollBar1.Top := 50;
plScrollBar1.Width := 250;
end;
end;
{ TplScrollBar }
constructor TplScrollBar.Create(AOwner: TComponent);
begin
inherited;
FBrush := CreateSolidBrush(clRed);
end;
destructor TplScrollBar.Destroy;
begin
DeleteObject(FBrush);
inherited;
end;
procedure TplScrollBar.CNCtlcolorscrollbar(var Message: TMessage);
begin
Message.Result := FBrush;
end;
end.
Delphi の TBrush ではビットマップが扱えます.以下は 16 x 16 のビットの 256 色のマップ画像ををブラシとして使用しています.
|
|
図20
TBrush.Bitmap を使用した TScrollBar の背景例
|
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;
type
TplScrollBar = class(TScrollBar)
private
FBitmap : TBitmap;
protected
procedure CNCtlcolorscrollbar(var Message: TMessage);
message CN_CTLCOLORSCROLLBAR;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
plScrollBar1 : TplScrollBar;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
if not Assigned(plScrollBar1) then begin
plScrollBar1 := TplScrollBar.Create(Self);
plScrollBar1.Parent := Self;
plScrollBar1.Left := 20;
plScrollBar1.Top := 50;
plScrollBar1.Width := 250;
end;
end;
{ TplScrollBar }
constructor TplScrollBar.Create(AOwner: TComponent);
begin
inherited;
FBitmap := TBitmap.Create;
FBitmap.LoadFromFile('EMOTION_SMILE.bmp');
end;
destructor TplScrollBar.Destroy;
begin
FreeAndNil(FBitmap);
inherited;
end;
procedure TplScrollBar.CNCtlcolorscrollbar(var Message: TMessage);
begin
if not FBitmap.Empty then begin
FBitmap.Canvas.Brush.Bitmap := FBitmap;
Message.Result := FBitmap.Canvas.Brush.Handle;
end;
end;
end.
CreatePatternBrush 関数を使用すると,ビットマップからブラシを作成することができます.
|
constructor TplScrollBar.Create(AOwner: TComponent);
begin
inherited;
FBitmap := TBitmap.Create;
FBitmap.LoadFromFile('EMOTION_SMILE.bmp');
FBrush := CreatePatternBrush(FBitmap.Handle);
end;
destructor TplScrollBar.Destroy;
begin
DeleteObject(FBrush);
FreeAndNil(FBitmap);
inherited;
end;
procedure TplScrollBar.CNCtlcolorscrollbar(var Message: TMessage);
begin
if not FBitmap.Empty then begin
Message.Result := FBrush;
end;
end;
メッセージは,コントロール自身の WndProc メソッドでも処理できます. |
|
図21
TScrollBar の WndProc によるメッセージ処理
|
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;
type
TplScrollBar = class(TScrollBar)
private
protected
procedure WndProc(var Message: TMessage); override;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
plScrollBar1 : TplScrollBar;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
if not Assigned(plScrollBar1) then begin
plScrollBar1 := TplScrollBar.Create(Self);
plScrollBar1.Parent := Self;
plScrollBar1.Left := 20;
plScrollBar1.Top := 50;
plScrollBar1.Width := 250;
end;
end;
{ TplScrollBar }
procedure TplScrollBar.WndProc(var Message: TMessage);
begin
if Message.Msg = CN_CTLCOLORSCROLLBAR then begin
Color := clBlue;
DefaultHandler(Message);
end;
// 元のWndPorcのメッセージ処理を行う
// これがないと正常に動作しない
inherited WndProc(Message);
end;
end.
ここまでは TScrollBar の継承クラスを作成して処理しています.以下は TScrollBar の継承クラスを作成しない方法です.
TScrollBar の WndProc を別のメソッドで置き換えます.これをサブクラス化と言います.書き換えたメソッド内でメッセージを処理します.元の WndProc を退避して最後に元に戻します.
サブクラス化すると,対象のコントロールのメッセージの処理が可能になります.protected 部のメソッドやプロパティが使用可能になるわけではありません.
|
|
図22
TScrollBar の サブクラス化メソッド WndProc によるメッセージ処理
|
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;
type
TForm1 = class(TForm)
ScrollBar1: TScrollBar;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private 宣言 }
LOriginSCrollBarProc : TWndMethod;
procedure ScrollBarSubClassProc(var message: TMessage);
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
// 元の WndProc を退避して別のメソッドを定義 (サブクラス化)
LOriginSCrollBarProc := ScrollBar1.WindowProc;
ScrollBar1.WindowProc := ScrollBarSubClassProc;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
// WndProc を元に戻す
ScrollBar1.WindowProc := LOriginSCrollBarProc;
end;
procedure TForm1.ScrollBarSubClassProc(var message: TMessage);
begin
if Message.Msg = CN_CTLCOLORSCROLLBAR then begin
// Color プロパティは使用不可
// TScrollBar.Color は protected のメンバ
// ここで Color を設定するとフォームの背景色になる
message.Result := CreateSolidBrush(clSkyBlue);
end;
// 元の WndProc を実行
// 元の WndProc は CN_CTLCOLORSCROLLBAR を使用しないので
// 最初に実行しても結果は同じ
LOriginSCrollBarProc(message);
end;
end.
[備考 2]
CN_CTLCOLORSCROLLBAR は,TApplicationEvents コンポーネントの OnMessage イベントでは捕捉できません.CN_CTLCOLORSCROLLBAR に相当する Windows メッセージは WM_CTLCOLORSCROLLBAR ですが,これも捕捉できません.
|
|