☆DBGridにインデント機能を付ける。
前回、☆TEditにインデント機能をつける。
で作成したインデント機能をDBGridのInplaceEditorに実装してみます。
当初、インデント処理をクラスにしてInplaceEditorをプロパティで与えて処理させようと したのですが、そこはやはりDBGrid・・・データベースに対しての処理が必要になり、 結局カスタムコンポーネントとしました。
今回一番悩んだのは、加工した文字列をInplaceEditorに設定する部分です。
SelectedFieldに設定してPostしてしまえば、簡単に実現できたのですが、 それではDBGridの動作とかけ離れてしまいます。
処理のポイントとしては、
インデント処理によるデータ落ちを防ぐため、フィールド長さが超えるものは処理させません。 ですから、テストするときにフィールド長さが短いものやデータの文字列自身が長い場合には 動作していないように見えますのでご注意して下さい。 又、要素間の移動は、前回より拡張して、要素の前後に移動するようにしました。
コンポーネントとして登録するのは面倒なので、下記のように実行時に生成して試しています。
[20071009訂正]
空白セルに対して動作しなかったため、次の部分を訂正しました。
SpaceAdd手続きを追加しました。
NextIndent手続きを差し替えました。
PriorIndent手続きを差し替えました。
結局、力技での解決になってしまいました。
DBGridでCTRL+SPACEキーで半角進むことを利用しています。
当初、インデント処理をクラスにしてInplaceEditorをプロパティで与えて処理させようと したのですが、そこはやはりDBGrid・・・データベースに対しての処理が必要になり、 結局カスタムコンポーネントとしました。
今回一番悩んだのは、加工した文字列をInplaceEditorに設定する部分です。
SelectedFieldに設定してPostしてしまえば、簡単に実現できたのですが、 それではDBGridの動作とかけ離れてしまいます。
処理のポイントとしては、
1.インデント処理以前に Key := #0 でキーを無効にしておく。 2.SelectedFieldのDataSetを編集モードにしておく。 3.InplaceEditor.TextではなくSelectedFieldに加工した文字列を設定する。 4.次の編集処理のため、Postしない。です。
インデント処理によるデータ落ちを防ぐため、フィールド長さが超えるものは処理させません。 ですから、テストするときにフィールド長さが短いものやデータの文字列自身が長い場合には 動作していないように見えますのでご注意して下さい。 又、要素間の移動は、前回より拡張して、要素の前後に移動するようにしました。
unit HRIndentDBGrid; interface uses Windows, Messages, SysUtils, Classes, DB, Grids, DBGrids, AdjustedEdit; type TIndentPosList = array of Integer; THRIndentDBGrid = class(TDBGrid) private FIndentPosList: TIndentPosList; function GetIndentPos: String; procedure SetIndentPos(Value: String); procedure MakeIndentPosList(Indent: String); procedure GetElementPosList(S: String; var List: TIndentPosList); procedure PriorIndent; procedure NextIndent; procedure PriorElement; procedure NextElement; function IndentKeyPress(Key: Char): Boolean; protected procedure KeyPress(var Key: Char); override; public constructor Create(AOwner: TComponent); override; published property IndentPosition: String read GetIndentPos write SetIndentPos; end; procedure Register; implementation procedure Register; begin RegisterComponents('Hiderin', [THRIndentDBGrid]); end; { THRIndentDBGrid } constructor THRIndentDBGrid.Create(AOwner: TComponent); begin inherited Create(AOwner); IndentPosition := '1'; end; procedure THRIndentDBGrid.KeyPress(var Key: Char); begin // TStringFieldの場合のみインデント処理を可能します。 if (InplaceEditor <> nil) and (SelectedField.DataType = ftString) and IndentKeyPress(Key) then begin key := #0; // 2度設定していることになるが、 // これを入れないと要素間移動時にポンと音が鳴ります。 Exit; end; inherited KeyPress(Key); end; function THRIndentDBGrid.IndentKeyPress(Key: Char): Boolean; procedure DefaultResult; begin Result := True; Key := #0; end; begin Result := False; // Ctrl + Iで指定位置にエレメントを移動させます。 if (GetKeyState(VK_SHIFT) and $80 = 0) and (GetKeyState(VK_CONTROL) and $80 > 0) and (Key = #9) then begin DefaultResult; NextIndent; end; // Ctrl + Shift + Iで指定位置にエレメントを移動させます。 if (GetKeyState(VK_SHIFT) and $80 > 0) and (GetKeyState(VK_CONTROL) and $80 > 0) and (Key = #9) then begin DefaultResult; PriorIndent; end; // 要素開始位置へキャレットを移動させます。CTRL+U if (GetKeyState(VK_SHIFT) and $80 = 0) and (GetKeyState(VK_CONTROL) and $80 > 0) and (Key = #21) then begin DefaultResult; NextElement; end; // 要素開始位置へキャレットを移動させます。CTRL+SHIFT+U if (GetKeyState(VK_SHIFT) and $80 > 0) and (GetKeyState(VK_CONTROL) and $80 > 0) and (Key = #21) then begin DefaultResult; PriorElement; end; end; function THRIndentDBGrid.GetIndentPos: String; var I: Integer; begin Result := IntToStr(FIndentPosList[Low(FIndentPosList)]); for I := Low(FIndentPosList) +1 to High(FIndentPosList) do Result := Result + ',' + IntToStr(FIndentPosList[I]); end; procedure THRIndentDBGrid.SetIndentPos(Value: String); begin MakeIndentPosList(Value); end; procedure SpaceAdd(Count: Integer); var I: Integer; begin keybd_event(VK_CONTROL, 0, 0, 0); for I := 0 to Count- 1 do begin keybd_event(VK_SPACE, 0, 0, 0); keybd_event(VK_SPACE, 0, KEYEVENTF_KEYUP, 0); end; // VK_CONTROLのKeyUpは不要です。 // ユーザーがCTRL+○と操作しているからです。 end; procedure THRIndentDBGrid.NextIndent; var P1, P2: Integer; I,J: Integer; S1, S2, S3: String; begin SendMessage(InplaceEditor.Handle, WM_SETREDRAW, Ord(False), 0); try P1 := GetSelStart(InplaceEditor); P2 := 0; for I := 0 to High(FIndentPosList) do if FIndentPosList[I]-1 > P1 then begin P2 := FIndentPosList[I]-1; Break; end; if P2 = 0 then begin P2 := P1; Exit; end; S1 := Copy(InplaceEditor.Text, 1, P1); S2 := Copy(InplaceEditor.Text, P1+1, Length(InplaceEditor.Text)-P1); J := P2 - P1; S3 := S1 + StringOfChar(' ', J) + S2; if Length(S3) < SelectedField.Size then begin // カーソルより先が空白の場合 if (J > 0) and (Trim(S2) = '') then begin SpaceAdd(J); Exit; end; if not (DataLink.DataSet.State in [dsEdit, dsInsert]) then SelectedField.DataSet.Edit; SelectedField.AsString := S3; end else P2 := P1; finally SendMessage(InplaceEditor.Handle, WM_SETREDRAW, Ord(True), 0); SetSelStart(InplaceEditor, P2); end; end; procedure THRIndentDBGrid.PriorIndent; var P1, P2, P3: Integer; I,J: Integer; S1, S2, S3: String; begin SendMessage(InplaceEditor.Handle, WM_SETREDRAW, Ord(False), 0); try P1 := GetSelStart(InplaceEditor); P2 := 0; for I := High(FIndentPosList) downto 0 do if FIndentPosList[I]-1 < P1 then begin P2 := FIndentPosList[I]-1; Break; end; if P2 < 0 then begin P2 := P1; Exit; end; S1 := TrimRight(Copy(InplaceEditor.Text, 1, P1)); P3 := Length(S1); S2 := Copy(InplaceEditor.Text, P1+1, Length(InplaceEditor.Text)-P1); if P3 >= P2 then begin S3 := S1 + S2; P2 := P3; end else begin J := P2 - Length(S1); S3 := S1 + StringOfChar(' ', J) + S2; end; if Length(S3) < SelectedField.Size then begin // カーソルより先が空白の場合 if (J > 0) and (Trim(S2) = '') then begin SpaceAdd(J); Exit; end; if not (DataLink.DataSet.State in [dsEdit, dsInsert]) then SelectedField.DataSet.Edit; SelectedField.AsString := S3; end else P2 := P1; finally SendMessage(InplaceEditor.Handle, WM_SETREDRAW, Ord(True), 0); SetSelStart(InplaceEditor, P2); end; end; procedure THRIndentDBGrid.NextElement; var I: Integer; P1: Integer; List: TIndentPosList; begin GetElementPosList(InplaceEditor.Text, List); P1 := GetSelStart(InplaceEditor); for I := 0 to High(List) do if P1 < List[I] then begin P1 := List[I]; Break; end; SetSelStart(InplaceEditor, P1); end; procedure THRIndentDBGrid.PriorElement; var I: Integer; P1: Integer; List: TIndentPosList; begin GetElementPosList(InplaceEditor.Text, List); P1 := GetSelStart(InplaceEditor); for I := High(List) downto 0 do if P1 > List[I] then begin P1 := List[I]; Break; end; SetSelStart(InplaceEditor, P1); end; // インデント位置リストを作成します。 procedure THRIndentDBGrid.MakeIndentPosList(Indent: String); var I: Integer; SL: TStringList; begin SL := TStringList.Create; try SL.CommaText := Indent; SetLength(FIndentPosList, SL.Count); for I := 0 to SL.Count - 1 do FIndentPosList[I] := StrToIntDef(SL[I],0); finally SL.Free; end; end; // 要素の位置を取得します。 procedure THRIndentDBGrid.GetElementPosList(S: String; var List: TIndentPosList); procedure Increment(var P: PChar; var I: Integer; Value: Integer); begin Inc(P, Value); Inc(I, Value); end; var P: PChar; I, J: Integer; w: Word; F: Boolean; begin SetLength(List, 100); // 100は適当 P := PChar(S + #0); I := 0; J := -1; F := True; while (P^ <> #0) do begin if IsDBCSLeadByte(Byte(P^)) then begin w := (Byte(P^) shl 8) or Byte((P+1)^); if F and (w <> $8140) then begin Inc(J); List[J] := I; Increment(P, I, 2); F := False; end else if (not F) and (w = $8140) then begin Inc(J); List[J] := I; Increment(P, I, 2); F := True; end else if F and (w = $8140) then begin Increment(P, I, 2); end else Increment(P, I, 2); end else begin if F and (P^ <> ' ') then begin Inc(J); List[J] := I; Increment(P, I, 1); F := False; end else if (not F) and (P^ = ' ') then begin Inc(J); List[J] := I; Increment(P, I, 1); F := True; end else if (P^ = ' ') then begin Increment(P, I, 1); F := True; end else Increment(P, I, 1); end; end; // 文字列の最後を付加しておきます。 Inc(J); List[J] := length(S); SetLength(List, J+1); end; end.
コンポーネントとして登録するのは面倒なので、下記のように実行時に生成して試しています。
type TForm1 = class(TForm) Table1: TTable; DataSource1: TDataSource; procedure FormCreate(Sender: TObject); private { Private declarations } public HRIndentDBGrid: THRIndentDBGrid; end;
procedure TForm1.FormCreate(Sender: TObject); begin HRIndentDBGrid := THRIndentDBGrid.Create(Self); HRIndentDBGrid.Parent := Self; HRIndentDBGrid.Align := alClient; HRIndentDBGrid.DataSource := DataSource1; HRIndentDBGrid.IndentPosition := '1,3,5,7,9,11'; end;Undoができないのは、相変わらずなんですが、キーでインデント位置を前後に移動できるため Undoの仕様としては、インデントに対して無効、キー入力部分に対して有効となり、 それはそれでいいんじゃないかと思っています。
[20071009訂正]
空白セルに対して動作しなかったため、次の部分を訂正しました。
SpaceAdd手続きを追加しました。
NextIndent手続きを差し替えました。
PriorIndent手続きを差し替えました。
結局、力技での解決になってしまいました。
DBGridでCTRL+SPACEキーで半角進むことを利用しています。
| 固定リンク