前回、
☆TEditにインデント機能をつける。
で作成したインデント機能をDBGridのInplaceEditorに実装してみます。
当初、インデント処理をクラスにして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キーで半角進むことを利用しています。