DrawGrid/StringGridで拡張クラスを使用してFixedCellをクリックできるようにする方法
|
16 |
DrawGridStringGridDrawCell-4 |
動作確認 |
Delphi2007 |
更新日 |
2008/01/07(月) |
『DrawGrid/StringGridでFixedCellをクリックできるようにする方法 』の続きになります。
前回のテクニックではGridのOnMouseDown/MouseUp/MouseLeave/DrawCell
という各イベント部分に実装コードをたくさん書く必要があったので
やや使い勝手が悪い感じがします。
Unit1.pasにFixedCellをクリックするためのソースコードが多くなり
可読性が落ちている感じがしました。
そこで、FixedCellクリック機能を分離して別unitで記載してみましょう。
このような場合、Gridを継承して実装するのも手ですが
DrawGrid/StringGrid、2つのコンポーネント
それぞれについて継承して実装するのも変ですし
たとえば私はTStringGridではなく
スクロールバーつまみサイズが変化してくれるTStrGridを使っていますが
そのTStrGridに対しても
TFixedCellClickableStrGrid、というものを実装するのも
エレガントではありませんので
ここで、拡張クラスをつかってGridを拡張するという手法をとってみます。
コンストラクタで、Gridを指定して生成した場合に
固定セルをクリックする機能が拡張されるように
TFixedGridClickExtensionというクラスを実装してみます。
ソースコードは長いですが、中身を詳細に知る必要はありません。
前回のテクニックではMouseLeaveを使っていますが
若干のバグ対応もしたので今は使っていません。
────────────────────
unit FixedGridClickUnit;
interface
uses
Grids, Controls, Classes, Types, Graphics, Windows,
SysUtils,
SystemUnit, //このあたり以下は自作だから
MathUnit, //DelFusaLibraryからさがしてください
RectPointUnit,
end_uses;
type
TFixedGridClickExtension = class(TObject)
private
FGrid: TDrawGrid;
FMouseDownGridFixedCellPoint: TPoint;
FOriginalMouseDownEvent: TMouseEvent;
FOriginalMouseUpEvent: TMouseEvent;
FOriginalMouseMoveEvent: TMouseMoveEvent;
FOriginalDrawCellEvent: TDrawCellEvent;
procedure GridMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure GridMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure GridMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure GridDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect;
State: TGridDrawState);
procedure GridMousePointUpdate(Shift: TShiftState; X, Y: Integer);
protected
public
constructor Create(Grid: TDrawGrid);
destructor Destroy; override;
published
end;
function CheckFixedCell(Grid: TDrawGrid; ACol, ARow: Longint): Boolean;
function CheckNormalCell(Grid: TDrawGrid; ACol, ARow: Longint): Boolean;
implementation
{ TFixedGridClickExtension }
constructor TFixedGridClickExtension.Create(Grid: TDrawGrid);
begin
FGrid := Grid;
FGrid.DefaultDrawing := False;
{↓フラグ初期化}
FMouseDownGridFixedCellPoint := Point(-1, -1);
if Assigned(Grid.OnMouseDown) then
begin
FOriginalMouseDownEvent := Grid.OnMouseDown;
end else
begin
FOriginalMouseDownEvent := nil;
end;
Grid.OnMouseDown := Self.GridMouseDown;
if Assigned(Grid.OnMouseUp) then
begin
FOriginalMouseUpEvent := Grid.OnMouseUp;
end else
begin
FOriginalMouseUpEvent := nil;
end;
Grid.OnMouseUp := Self.GridMouseUp;
if Assigned(Grid.OnMouseMove) then
begin
FOriginalMouseMoveEvent := Grid.OnMouseMove;
end else
begin
FOriginalMouseMoveEvent := nil;
end;
Grid.OnMouseMove := Self.GridMouseMove;
if Assigned(Grid.OnDrawCell) then
begin
FOriginalDrawCellEvent := Grid.OnDrawCell;
end else
begin
FOriginalDrawCellEvent := nil;
end;
Grid.OnDrawCell := Self.GridDrawCell;
end;
destructor TFixedGridClickExtension.Destroy;
begin
FGrid.OnMouseDown := FOriginalMouseDownEvent;
FGrid.OnMouseMove := FOriginalMouseMoveEvent;
FGrid.OnMouseUp := FOriginalMouseUpEvent;
FGrid.OnDrawCell := FOriginalDrawCellEvent;
inherited;
end;
//Col/Rowで指定した位置が固定セルかどうかを調べる関数
function CheckFixedCell(Grid: TDrawGrid; ACol, ARow: Longint): Boolean;
begin
Result := False;
if CheckRange(0, ACol, Grid.FixedCols-1)
and CheckRange(0, ARow, Grid.RowCount-1) then
begin
Result := True;
Exit;
end;
if CheckRange(0, ARow, Grid.FixedRows-1)
and CheckRange(0, ACol, Grid.ColCount-1) then
begin
Result := True;
Exit;
end;
end;
//Col/Rowで指定した位置が固定セルかどうかを調べる関数
function CheckNormalCell(Grid: TDrawGrid; ACol, ARow: Longint): Boolean;
begin
Result := False;
if CheckRange(Grid.FixedCols, ACol, Grid.ColCount-1)
and CheckRange(Grid.FixedRows, ARow, Grid.RowCount-1) then
begin
Result := True;
Exit;
end;
end;
procedure TFixedGridClickExtension.GridMousePointUpdate(Shift: TShiftState; X, Y: Integer);
var
ACol, ARow: Longint;
begin
if ssLeft in Shift then
begin
FGrid.MouseToCell(X, Y, ACol, ARow);
FMouseDownGridFixedCellPoint := Point(ACol, ARow);
{↓選択した場所がGridの内部ではないなら、フラグをキャンセル}
if not PointInRect(Point(X, Y), FGrid.ClientRect) then
FMouseDownGridFixedCellPoint := Point(-1, -1);
{↓選択した場所が固定セルではないなら、フラグをキャンセル}
if not CheckFixedCell(FGrid, ACol, ARow) then
FMouseDownGridFixedCellPoint := Point(-1, -1);
// DebugPrintNotepad(IntToStr(ACol));
FGrid.Repaint;
end;
end;
//マウスDown時にフラグON
procedure TFixedGridClickExtension.GridMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
GridMousePointUpdate(Shift, X, Y);
if Assigned(FOriginalMouseDownEvent) then
begin
FOriginalMouseDownEvent(Sender, Button, Shift, X, Y);
end;
end;
procedure TFixedGridClickExtension.GridMouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
begin
GridMousePointUpdate(Shift, X, Y);
if Assigned(FOriginalMouseMoveEvent) then
begin
FOriginalMouseMoveEvent(Sender, Shift, X, Y);
end;
end;
//マウスUpでフラグOFF
procedure TFixedGridClickExtension.GridMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
ACol, ARow: Longint;
begin
// FGrid.MouseToCell(X, Y, ACol, ARow);
FMouseDownGridFixedCellPoint := Point(-1, -1);
FGrid.Repaint;
if Assigned(FOriginalMouseUpEvent) then
begin
FOriginalMouseUpEvent(Sender, Button, Shift, X, Y);
end;
end;
procedure TFixedGridClickExtension.GridDrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var
PenBuffer: TPen;
BrushBuffer: TBrush;
FontBuffer: TFont;
DC: HDC;
R: TRect;
{↓FixedCellの枠を描画する}
procedure DrawFixedFrame(ARect: TRect; Click: Boolean=False);
begin
if (Sender as TDrawGrid).Ctl3D then
with (Sender as TDrawGrid) do
begin
Canvas.Pen.Style := psSolid;
Canvas.Pen.Mode := pmCopy;
with ARect do begin
if Click then
Canvas.Pen.Color := clBtnShadow
else
Canvas.Pen.Color := clBtnHighlight;
Canvas.MoveTo( Right - 1, Top );
Canvas.LineTo( Left, Top);
Canvas.LineTo( Left, Bottom - 1 );
if Click then
Canvas.Pen.Color := clBtnHighlight
else
Canvas.Pen.Color := clBtnShadow;
Canvas.LineTo( Right - 1, Bottom - 1 );
Canvas.LineTo( Right - 1, Top );
end;
end;
end;
{↓パラメータを一時保存}
procedure BufferingOn;
begin
with Sender as TDrawGrid do
begin
PenBuffer.Assign(Canvas.Pen);
BrushBuffer.Assign(Canvas.Brush);
FontBuffer.Assign(Canvas.Font);
end;
end;
{↓パラメータを復帰}
procedure BufferingOff;
begin
with Sender as TDrawGrid do
begin
Canvas.Pen.Assign(PenBuffer);
Canvas.Brush.Assign(BrushBuffer);
Canvas.Font.Assign(FontBuffer);
end;
end;
begin
PenBuffer := TPen.Create;
BrushBuffer := TBrush.Create;
FontBuffer := TFont.Create;
BufferingOn;
with FGrid do begin
{↓固定セル}
if gdFixed in State then
begin
if (FMouseDownGridFixedCellPoint.X=ACol)
and (FMouseDownGridFixedCellPoint.Y=ARow) then
begin
Canvas.Brush.Color := FixedColor;
Canvas.FillRect(Rect);
{↑セル内を色で塗りつぶす}
{↓文字描画}
if FGrid is TStringGrid then
begin
Canvas.TextRect(Rect, Rect.Left+2, Rect.Top+2,
TStringGrid(FGrid).Cells[ACol, ARow]);
end;
DrawFixedFrame(Rect, True);
{↑クリック時の枠を描画}
end else
begin
Canvas.Brush.Color := FixedColor;
Canvas.FillRect(Rect);
{↑セル内を色で塗りつぶす}
{↓文字描画}
if FGrid is TStringGrid then
begin
Canvas.TextRect(Rect, Rect.Left+2, Rect.Top+2,
TStringGrid(FGrid).Cells[ACol, ARow]);
end;
DrawFixedFrame(Rect);
{↑枠を描く}
end;
end else
{↓セレクトセル}
if gdSelected in State then
begin
{↓DrawFocusSelectedの場合はFocusedセルの色が違う}
if (goDrawFocusSelected in Options)
and (ACol = Col) and (ARow = Row) then
begin
{↓選択色}
Canvas.Brush.Color := clHighlight;
Canvas.FillRect(Rect);
if FGrid is TStringGrid then
begin
Canvas.TextRect(Rect, Rect.Left+2, Rect.Top+2,
TStringGrid(FGrid).Cells[ACol, ARow]);
end;
Canvas.DrawFocusRect(Rect);
{↑枠を描く}
end else
if (ACol = Col) and (ARow = Row) then
begin
{↓通常セル色}
Canvas.Brush.Color := clWindow;
Canvas.FillRect(Rect);
if FGrid is TStringGrid then
begin
Canvas.TextRect(Rect, Rect.Left+2, Rect.Top+2,
TStringGrid(FGrid).Cells[ACol, ARow]);
end;
Canvas.DrawFocusRect(Rect);
{↑枠を描く}
end else
begin
Canvas.Brush.Color := clHighlight;
Canvas.FillRect(Rect);
{↓文字描画}
if FGrid is TStringGrid then
begin
Canvas.TextRect(Rect, Rect.Left+2, Rect.Top+2,
TStringGrid(FGrid).Cells[ACol, ARow]);
end;
{枠描画処理は無し}
end;
end else
{↓普通のセル}
begin
Canvas.Brush.Color := clWindow;
Canvas.FillRect(Rect);
{↓文字描画}
if FGrid is TStringGrid then
begin
Canvas.TextRect(Rect, Rect.Left+2, Rect.Top+2,
TStringGrid(FGrid).Cells[ACol, ARow]);
end;
end;
end;
BufferingOff;
PenBuffer.Free;
BrushBuffer.Free;
FontBuffer.Free;
if Assigned(FOriginalDrawCellEvent) then
begin
FOriginalDrawCellEvent(Sender, ACol, ARow, Rect, State);
end;
end;
end.
────────────────────
使い方は、上記ソースをFixedGridClickUnit.pasとして保存
プロジェクトに追加しておき
Formに
FFixedGridClicker: TFixedGridClickExtension
を定義しておいて
FormCreateなどで
FFixedGridClicker := TFixedGridClickExtension.Create(StringGrid1);
こうすることだけで、StringGrid1がクリック可能になります。
※FormDestroyあたりで破棄はしておいてください。
クリックイベントを感知して固定セルがクリックされたときに
Gridの列選択や行選択を行いたい場合は
次のようにイベントを実装しておきます。下記はDrawGridの例になります。
────────────────────
procedure TForm1.DrawGrid1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
ACol, ARow: Longint;
SelectionSetRect: TGridRect;
begin
with TDrawGrid(Sender) do begin
MouseToCell(X, Y, ACol, ARow);
if (ARow <= FixedRows - 1)
and (ACol <= FixedCols - 1) then
begin
{↑もしFixedRow/Colより小さい領域=左上ボタンを押したなら
↓全選択}
SelectionSetRect.Left := FixedCols;
SelectionSetRect.Top := FixedRows;
SelectionSetRect.Right := ColCount-1;
SelectionSetRect.Bottom := RowCount-1;
Selection := SelectionSetRect;
end else
if (ARow <= FixedRows - 1) then
begin
{↑もしFixedRowより小さい領域=上ボタンを押したなら
↓列選択}
SelectionSetRect.Left := ACol;
SelectionSetRect.Top := FixedRows;
SelectionSetRect.Right := ACol;
SelectionSetRect.Bottom := RowCount-1;
Selection := SelectionSetRect;
end else
if (ACol <= FixedCols - 1) then
begin
{↑もしFixedColより小さい領域=左ボタンを押したなら
↓行選択}
SelectionSetRect.Left := FixedCols;
SelectionSetRect.Top := ARow;
SelectionSetRect.Right := ColCount-1;
SelectionSetRect.Bottom := ARow;
Selection := SelectionSetRect;
end;
end;
end;
────────────────────
このTFixedGridClickExtensionクラスの実装で
あるコントロールの機能UPをおこなう拡張クラスの実装として参考になるのは
クラスを生成した時にCreateで指定しているGridに対して
OnMouseUpなどのイベントを乗っ取り、
TFixedGridClickExtensionクラスのMouseUpの実装コードが
呼び出されるようにしている部分です。
特にその準備はTFixedGridClickExtension.Create を中心におこなっています。
元のGridのOnMouseUp処理も正しくおこなわれます。
どんなコンポーネントに対してもこのテクニックは使えます。
定型的なイベントハンドラを実装する必要がある時には
拡張クラスを作って別ユニットにしてソースを隠蔽すると
よりスッキリとしたコードで機能を実現できるでしょう。
|