« 2007年9月 | トップページ | 2007年11月 »

☆BDE Table.Locateで・・・。

ずいぶん昔に作ったBDEを使ったプログラムをDelphi2007で再コンパイルして仕事をしていたところ、 「この操作は行えません」というエラーメッセージが頻繁に表示されるようになりました。

そこでシンプルなサンプルを作って、原因を探したところ、Locateメソッドの検索文字がフィールドサイズより長い場合にエラーになっていました。(そんな仕様だっけ?)

又、この問題とは別に、TEditのMaxLengthがランタイムテーマの影響を受けることが確認できました。 検索文字入力用Editには、元々MaxLengthをフィールド長さで設定してあり、今まで検索文字がフィールドサイズを超えることがなかったのですが、ランタイムテーマの影響で、バイト数から文字数扱いになってしまい、エラーが起こったようです。

今回は、文字長さをバイト数で計算して、フィールド長さ以下であることを確認の上、Locateメソッドに渡すことにより、この問題を解決しました。

※テスト用プログラムです。データベースは、DBDEMOSのemployee.dbを使っています。
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Grids, DBGrids, DB, DBTables;

type
  TForm1 = class(TForm)
    Table1: TTable;
    DataSource1: TDataSource;
    DBGrid1: TDBGrid;
    Button1: TButton;
    Edit1: TEdit;
    Edit2: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  S1, S2: String;
begin
  S1 := Edit1.Text;
  S2 := Edit2.Text;
  Table1.Locate('LastName;FirstName',VarArrayOf([S1, S2]), [loPartialKey]);
end;

end.

Locate

|

☆『コンピュータ・アルゴリズム事典』サポートページ

この『コンピュータ・アルゴリズム事典』とは、奥村晴彦 先生の本です。
アルゴリズムの本は、ほとんどがC言語なのですが、この本はPascalで書かれています。 初版が1987年ということもあり (私のは初版第8刷で1994年です) 残念ながら既に入手できないようですが、 サポートページなるものを発見しました。
なんと本で紹介されているソース一式が、ダウンロードできるようになっています。

おすすめのアルゴリズムは、CHAP12.PRGの中にある「式の評価」ですね。
テキストで書かれた数式の値を計算することができます。


奥村晴彦 氏
『コンピュータ・アルゴリズム事典』サポートページ
http://oku.edu.mie-u.ac.jp/~okumura/algo/algo_pas.html

|

■TMS Advanced ToolBars & Menusアップデート

TMS Advanced ToolBars & Menus が v3.0.1.1 から v3.0.5.0 にアップデートされました。

メーカーホームページからの引用です。

in v3.0.5.0
- New : tab handling to TAdvPreviewMenu & TAdvShapeButton
- New : wizard creates Quick Access Toolbar (QAT)
- Fixed : issue with deleting styler at design time
- Fixed : issues with actions on TAdvGlowButton
- Improved : various smaller painting improvements in TAdvGlowButton
- Improved : TAdvPreviewMenu design time editor
- Improved : tab key handling in previewmenu


tmssoftware.com

|

☆ListBoxのlbOwnerDrawVariableの不思議?

ListBoxの各アイテム毎の高さを変えて表示するには、StyleプロパティをlbOwnerDrawVariableに設定し、OnDrawItem、OnMeasureItemイベントの処理を書きます。 しかしそれだけでは OnMeasureItem が発生せず、なぜかMultiSelectをTrueにする必要があるようです。 高さが可変なのと複数行選択には、関連がなさそうなのですけど仕様なのでしょうか。 いつもMultiSelectがTrueの状態で使っていたので、今まで気付きませんでした。

又、アイテムの高さには制限があるようです。これについて少し調べてみました。 下記のようなプログラムを作成して、どこまできちんと描画されているかを確認しました。

ボタンを押すとフォントのサイズが1つ大きくなり、再描画するようにしています。

Lb1


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Memo1: TMemo;
    Panel1: TPanel;
    Button1: TButton;
    Edit1: TEdit;
    procedure ListBox1MeasureItem(Control: TWinControl; Index: Integer;
      var Height: Integer);
    procedure ListBox1DrawItem(Control: TWinControl; Index: Integer;
      Rect: TRect; State: TOwnerDrawState);
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}


procedure SetSample;
var
  I :Integer;
begin
  with Form1 do
  begin
    ListBox1.Font.Size := ListBox1.Font.Size + 1;
    Edit1.Text := IntToStr(ListBox1.Font.Size);

    Memo1.Lines.Clear;
    ListBox1.Items.Clear;
    for I := 0 to 80 do
      ListBox1.Items.Add(IntToStr(I));
    ListBox1.ItemIndex := 0;
  end;
end;

function GetRowCount(Index: Integer): Integer;
begin
  Result := Index mod 30;
end;

function GetFontHeight: Integer;
begin
  with Form1 do
  Result := Abs(Trunc((-ListBox1.Font.Size *
    ListBox1.Font.PixelsPerInch) / 72)) + 4;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ListBox1.Font.Size := 7;
  SetSample;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  SetSample;
end;

procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  I, J, Row: Integer;
begin
  // 行がわかりやすいように色を変えます
  if Index mod 2 = 0 then
  begin
    ListBox1.Canvas.Brush.Color := clwhite;
    ListBox1.Canvas.Font.Color := clBlue;
  end
  else
  begin
    ListBox1.Canvas.Brush.Color := $00D3F8FF;
    ListBox1.Canvas.Font.Color := clBlack;
  end;

  ListBox1.Canvas.FillRect(Rect);
  Row := GetRowCount(Index+1);
  for I := 0 to Row - 1 do
  begin
    J := Rect.Top + GetFontHeight * I;
    ListBox1.Canvas.TextOut(Rect.Left, J, Format('Item%d',[Index+1]));
  end;
end;

procedure TForm1.ListBox1MeasureItem(Control: TWinControl; Index: Integer;
  var Height: Integer);
begin
  Height := GetFontHeight * GetRowCount(Index+1) ;
  Memo1.LInes.Add(Format('%d---%d',[Index+1, Height]) );
end;

end.

[Font.Size := 8 の場合] 252の高さまで、正常に表示されています。

Lb2


[Font.Size := 9 の場合] 256の高さで、アイテムは表示すらされていません。

Lb3


[Font.Size := 10 の場合] 255の高さまで、正常に表示されています。

Lb4


他のサイズも同じように調べ、その結果からわかったことは、OnMeasureItemのHeightは256未満でないと、 この処理では描画できないということです。 (256の場合には、表示すらされていません。)
でもlbOwnerDrawFixedの場合は、ItemHeightに256を入れても問題なく動作しているんですよね。
可変高さで、全ての項目を表示したい場合には、ListBoxをあきらめてStringGridを使うか、最終行に「省略されています」というような表示が必要に なりますね。

|

■Delphi Hour in Tokyo にネットから参加しました。

リアルとネットを融合したものに参加するのは初めてなので、 「会場の映像を受けながら、時折スクリーンのプレゼン画面に切り替わって」というスタイルを 想像していたのですが、会場の様子の写真以外、ほとんど同じ画面でした。 たぶんNick Hodges 氏のPC画面が送られていたんですね。
(テレビじゃなんだから、これが王道なのかな??)

内容については、次期Delphiへの要望等が積極的に話されていました。
私が聞きたかったVCLのUnicode化の際、既存のStringとの関係はどうなるのかというのも、 他の方から質問がありました。 (正式な回答ではないということなので、ここで書くのは控えておきますけど)

VCLのUnicode化は期待と不安がいっぱいです。
がんばっていい製品に仕上げて下さいね。

|

☆幅を指定したDocking

Docking1

タイトルだけでは、よくわからないと思いますが、実現したい処理としては上のようなフォームにおいて、起動時にForm2とForm3の幅を指定して表示させたいというだけのことです。ただそれだけのことなのに、ここ数日ずっと悩んでいました。例のごとく力技ですけど、なんとか動くようになりましたので載せておきますね。

ポイントとしては、Form2,Form3を指定サイズに設定後、それらの間に表示されるSplitter?をメッセージで所定の位置に移動させているところです。でもこんな単純なこと、もっと簡単に設定する方法があるかも知れませんね。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Tabs, DockTabSet;

type
  Taa = class(TDockTree);
  TForm1 = class(TForm)
    Memo1: TMemo;
    DockTabSet1: TDockTabSet;
    Panel1: TPanel;
    procedure FormShow(Sender: TObject);
    procedure DockTabSet1DockDrop(Sender: TObject; Source: TDragDockObject; X,
      Y: Integer);
    procedure DockTabSet1TabRemoved(Sender: TObject);
    procedure Panel1DockOver(Sender: TObject; Source: TDragDockObject; X,
      Y: Integer; State: TDragState; var Accept: Boolean);
    procedure Panel1DockDrop(Sender: TObject; Source: TDragDockObject; X,
      Y: Integer);
    procedure Panel1UnDock(Sender: TObject; Client: TControl;
      NewTarget: TWinControl; var Allow: Boolean);
  private
  public
  end;

var
  Form1: TForm1;

implementation

uses Unit2, Unit3, Unit4, Unit5;

{$R *.dfm}

const
  DockPanelHeight = 100;

procedure TForm1.DockTabSet1DockDrop(Sender: TObject; Source: TDragDockObject;
  X, Y: Integer);
begin
  DockTabSet1.Visible := True;
  DockTabSet1.Top := Height - (DockTabSet1.Height) +1;
end;

procedure TForm1.DockTabSet1TabRemoved(Sender: TObject);
begin
  DockTabSet1.Visible := DockTabSet1.Tabs.Count > 0;
end;

procedure TForm1.FormShow(Sender: TObject);
var
  I: Integer;
begin
  { ドッキング設定 }

  // フォームの高さ---必須処理ではありません。
  Form2.Height := DockPanelHeight;
  Form3.Height := DockPanelHeight;
  Form4.Height := DockPanelHeight;
  Form5.Height := DockPanelHeight;

  // フォームが一瞬表示されてしまうので、画面の外に表示させます。
  Form2.Left := -2000;
  Form3.Left := -2000;
  Form4.Left := -2000;
  Form5.Left := -2000;

  // フォームの表示
  Form2.Show;
  Form3.Show;
  Form4.Show;
  Form5.Show;

  // ドッキング処理---Panel1
  Form2.ManualDock(Panel1,Panel1,alLeft);
  Form3.ManualDock(Panel1,Panel1,alClient);

  I := Panel1.Width div 3;

  Panel1.DockClients[0].Left := 0;
  Panel1.DockClients[0].Width := I;

  // ドッキングによって表示されるSplitter?を力技で移動させます。
  Panel1.Perform(WM_LBUTTONDOWN,0,MakeLParam(Panel1.Width div 2,1));
  Panel1.Perform(WM_MOUSEMOVE,0,MakeLParam(I,1));
  Panel1.Perform(WM_LBUTTONUP,0,MakeLParam(I,1));
  Panel1.DockManager.ResetBounds(False);
                                        
  // ドッキング処理--DockTabSet1
  Form4.ManualDock(DockTabSet1);
  Form5.ManualDock(DockTabSet1);

  // 表示位置の修正
  DockTabSet1.Top := Height - DockTabSet1.Height;  
end;


procedure TForm1.Panel1DockDrop(Sender: TObject; Source: TDragDockObject; X,
  Y: Integer);
begin
  if Panel1.Height = 0 then
    Panel1.Height := DockPanelHeight;
  DockTabSet1.Top := Height+1;
end;

procedure TForm1.Panel1DockOver(Sender: TObject; Source: TDragDockObject; X,
  Y: Integer; State: TDragState; var Accept: Boolean);
var
  Rect: TRect;
begin
  Accept := (Source.Control = Form2) or (Source.Control = Form3) or
            (Source.Control = Form4) or (Source.Control = Form5);
  if Accept then
  begin
    Rect.TopLeft := Panel1.ClientToScreen(Point(0, 0));

   if Panel1.DockClientCount = 0 then
      // Panel1に何もドッキングされていない状態だと枠がフォームの下に
      // 表示されてしまうため、この処理をしています。
      Rect.BottomRight := Panel1.ClientToScreen(Point(Panel1.Width, -DockPanelHeight))
    else
      Rect.BottomRight := Panel1.ClientToScreen(Point(Panel1.Width, DockPanelHeight));

    Source.DockRect := Rect;
  end;
end;

procedure TForm1.Panel1UnDock(Sender: TObject; Client: TControl;
  NewTarget: TWinControl; var Allow: Boolean);
begin
  if Panel1.DockClientCount = 1 then
    Panel1.Height := 0;
end;

end.

|

☆動的配列のメモリ確保で・・・。

動的配列のメモリ確保を別処理にした場合に、 SetLengthのところでエラーになりました。

procedure Shori(var AOR: array of String);
begin
  SetLength(AOR, 10);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  AOR: array of String;
begin
  Shori(AOR);
end;

エラーメッセージの内容です。

[DCC エラー] Unit1.pas(30): E2008 互換性の無い型です



次のように array of StringTypes 名前空間で定義されている TStringDynArray に置き換えることにより解決しました。
uses Types;

procedure Shori(var AOR: TStringDynArray);
begin
  SetLength(AOR, 10);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  AOR: TStringDynArray;
begin
  Shori(AOR);
end;


[20071023追記]
この件は、動的配列の問題ではなく、正確にはオープン配列パラメータの仕様です。
タイトルが動的配列となっており、ややこしくてすみません。
詳しくはヘルプの オープン配列パラメータ をご覧下さい。 「オープン配列パラメータは SetLength には渡せません。」ときちんと載っています。

|

☆TreeViewのアイテム高さを設定する。

TreeViewでは、使うアイコンによってノードの間隔がとても狭くなることがありますが、 そのような場合に使える処理です。

uses
  CommCtrl;

procedure SetTreeNodeHeight(TreeView: TTreeView; Height: Integer);
var
  H: Integer;
begin
  H := TreeView.Perform(TVM_GETITEMHEIGHT, 0, 0);
  if H <> Height then
    TreeView.Perform(TVM_SETITEMHEIGHT, Height, 0);
end;

※関連付けられたImageListのサイズによっても、ノードの高さは変わります。

|

☆ListViewのヘッダー高さを取得する。

ViewStyleがvsReportの場合のヘッダー高さを取得します。

マクロを使った処理
uses
  CommCtrl, Types;

function GetListViewHeaderHeight(ListView: TListView): Integer;
var
  Header_Handle: THandle;
  Rect: TRect;
begin
  Result := 0;
  Header_Handle := LISTVIEW_GETHEADER(ListView.Handle);
  if (ListView.Columns.Count > 0) then
  begin
    Header_GetItemRect(Header_Handle, 0, @Rect);
    Result := Rect.Bottom - Rect.Top;
  end;
end;


GetWindowPlacementを使った処理
変数名を変えたり、マクロで処理させていますが、基本的に下記サイトにある処理のままです(^^;
uses
  CommCtrl;

// ヘッダーの高さを取得します。
function GetListViewHeaderHeight(ListView: TListView): Integer;
var
  Header_Handle: HWND;
  WindowPlacement: TWindowPlacement;
begin
  Header_Handle := ListView_GetHeader(ListView.Handle);
  FillChar(WindowPlacement, SizeOf(WindowPlacement), 0);
  WindowPlacement.Length := SizeOf(WindowPlacement);
  GetWindowPlacement(Header_Handle, @WindowPlacement);
  Result  := WindowPlacement.rcNormalPosition.Bottom -
    WindowPlacement.rcNormalPosition.Top;
end;



[参考にしたサイト]

delphiDabbler.com
How to use the TListView OnCustomDrawXXX events
http://www.delphidabbler.com/articles?article=16

|

■旧バージョンのプロジェクトで・・・。

現在、私のPCにはBorland Developer Studio 2006(BDS2006)とDelphi2007がインストールされています。 この環境で、BDS2006で作ったものを別のフォルダにコピーして、Delphi2007で手直ししていた時の話です。 コンパイルをすると自家製コンポーネントに対して警告が出たので、とりあえず訂正してもう一度コンパイル・・・ しかし、何度訂正しても警告が消えません。

原因は、プロジェクトオプションの検索パスに BDS2006のコンポーネントへ検索パスが残っていたからでした。何度も訂正していたのは、Delphi2007用に設定したコンポーネント、そしてコンパイルに使われていたのはBDS2006用ということですね。 単純ミスなのですが、なかなか気付きませんでした。
(オプションでBDS2006へのパスが含まれていないかどうかまでは確認したんですけどね)

|

■USBテンキーからの入力で・・・。

ELECOM製USBテンキーを買ってきました。
その説明書にNumLock問題として、「USBテンキーから数字を入力するには、NumLockキーを オンにしなければならないが、そのことによりノートパソコン上のキーボードも 数字入力に切り替わってしまう。」というような内容が書いてありました。 そして、この製品はハードウェアレベルでこの問題を解決しているとのことでした。

実際に使ってみるとOnKeyPressでは問題ありませんが、OnKeyDownで処理するときには 注意が必要です。 というのも、テンキーを押すとそのコードの前後にVK_NUMLOCKが送られてくるからです。

1を押すと
VK_NUMLOCK(144)
VK_NUMPAD1(97)
VK_NUMLOCK(144)
と送られてきます。

長い時間押すとキーリピート機能が働き
VK_NUMLOCK(144)
VK_NUMPAD1(97)
VK_NUMPAD1(97)
  ・
  ・ (押した分だけ)
  ・
VK_NUMLOCK(144)
と送られてきます。

00という特殊なキーは、長い時間押してもキーリピート機能が働かず、
VK_NUMLOCK(144)
VK_NUMPAD0(96)
VK_NUMPAD0(96)
VK_NUMLOCK(144)
と送られてきます。

まあ、基本的なことですが条件分岐をきちんと書かなければいけないということですね。

|

☆TRichEditでハイパーリンク

URLやメールアドレスを含んだ文書を扱うとき、私はすぐにHTMLとWebBrowserの組み合わせを考えてしまうのですが、WebBrowserを使うと表示が遅いため、Wordのように文章中のリンクを操作できないかなと思いました。

ちょうど先日、マウス下の単語や文節を力技で取得するコードを書いたので、それとTRichEditを組み合わせればできるかなと思い、作業を開始しました。しかし、TRichEditでリンク文字を表現する方法を探していたところ、既にエレガントに、その機能を実装したサンプルを見つけてしまいました(笑)
ということで、そのサイトを紹介しておきますね。


About.com Delphi Programming
TRichEditURL - hyperlink aware RichEdit
http://delphi.about.com/od/vclwriteenhance/l/aa051804a.htm

|

☆AnimateWindowを使う。

よくあるタスクトレイの上から「にゅーっ」と現れて、「にゅーっ」と消えていくフォームのサンプルです。
未だVISTAではなくXPがメインの私は、UIのデザインセンスもきっと古いんでしょうね(笑)

メインフォーム
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses Unit2;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Form2.Show;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Form2.Close;
end;

end.



サブフォーム(このフォームが現れたり、消えたりします。)
unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Buttons;

type
  TForm2 = class(TForm)
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormShow(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    procedure WMNCHitTest(var message: TWMNCHitTest); message WM_NCHitTest;
  public
    { Public 宣言 }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

uses
  ShellAPI;

procedure TForm2.WMNCHitTest(var message: TWMNCHitTest);
begin
  inherited;
  // キャプションバー上のマウス操作を無効にします。
  // フォームを移動させないため
  if message.Result = HTCaption then
    message.Result := 0;
end;


procedure TForm2.FormCreate(Sender: TObject);
begin
  BorderStyle := bsToolWindow;
end;

procedure TForm2.FormShow(Sender: TObject);

  function GetTaskBarPos: TRect;
  var
    apbData : TAPPBARDATA;
  begin
    apbData.cbSize := SizeOf(TAPPBARDATA);
    SHAppBarMessage(ABM_GETTASKBARPOS, apbData);
    with Result do
    begin
      Left   := apbData.rc.Left;
      Right  := apbData.rc.Right;
      Top    := apbData.rc.Top;
      Bottom := apbData.rc.Bottom;
    end;
  end;

var
  Rect: TRect;
  I: Integer;
begin
  Rect := GetTaskBarPos;

  // タスクバーの高さ
  if (Rect.Top <> 0) then
    I := Rect.Bottom-Rect.Top
  else
    I := 0;

  // フォームの表示位置を設定します。
  Left := Screen.Width - Width;
  Top  := Screen.Height - Height -I;

  // ぼわーんと表示する場合
  //AnimateWindow(WindowHandle, 1000, AW_ACTIVATE + AW_BLEND);

  // 下からにゅーと表示する場合
  AnimateWindow(Handle,1000, AW_SLIDE + AW_VER_NEGATIVE);
end;

procedure TForm2.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  // ぼわーんと終了させる場合
  //AnimateWindow(WindowHandle,1000, AW_HIDE + AW_BLEND);

  // 下からにゅーと終了させる場合
  AnimateWindow(Handle,1000, AW_HIDE + AW_SLIDE + AW_VER_POSITIVE);
end;

end.




MSDN
AnimateWindow
http://msdn.microsoft.com/library/ja/default.asp?url=/library/ja/jpwinui/html/_win32_animatewindow.asp

|

☆DBGridのDrawCell

カスタムコンポーネントでのDrawCellの一例です。
今回は次のような描画をしてみます。
1.Excelのように選択セルの行(インジゲーター)と列(タイトル)部分に色を付ける。
2.InplaceEditorの背景に色を付ける。

Drawcell

※フォームやツールバーは、Delphi標準のコンポーネントではなく、TMS Software製TMS ToolBar Application Wizardから新規に作成したままの状態です。

type
  TDummyEditor = class(TCustomEdit);

  TMyDBGrid = class(TDBGrid)
  private
    procedure WMCommand(var Message: TWMCommand); message WM_COMMAND;
  protected
    procedure DrawCell(ACol, ARow: Longint; ARect: TRect;
      AState: TGridDrawState); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
  end;


uses
  GraphUtil;

{ TMyDBGrid }

const
  ActiveColor = $009FEBFD;
  ActiveColorTo = $0056B4FE;
  BackColor = $00FDEADA;
  BackColorTo =  $00E4AE88;
  EditColor = $00D3F8FF;

var
  Old_SelectedField: Integer;

procedure DrawBackground(ACanvas: TCanvas; ARect: TRect;
  Focused: Boolean);
begin
  if Focused then
    GradientFillCanvas(ACanvas, ActiveColor, ActiveColorTo,
      ARect, gdVertical)
  else
    GradientFillCanvas(ACanvas, BackColor, BackColorTo,
      ARect, gdVertical);
end;

procedure TMyDBGrid.DrawCell(ACol, ARow: Integer; ARect: TRect;
  AState: TGridDrawState);
const
  AlignFlags : array [TAlignment] of Integer =
    (DT_LEFT, DT_RIGHT, DT_CENTER);
var
  Text: String;
  Flags: LongInt;
  Check: Boolean;
begin
  inherited DrawCell(ACol,ARow,ARect,AState);

  InflateRect(ARect, 1, 1);
  if (ACol = 0) then
  begin
    Check := (ARow >= 0) and (ARow-1 = Datalink.ActiveRecord);
    DrawBackground(Canvas, ARect, Check);
  end
  else if (ARow = 0) then
  begin
    // 背景の描画
    SetBkMode(Canvas.Handle, TRANSPARENT);
    Check := (SelectedIndex = ACol- 1);
    DrawBackground(Canvas, ARect, Check);

    // カラム間の線
    Canvas.Pen.Color := clSilver;
    Canvas.MoveTo(ARect.Left, ARect.Top+1);
    Canvas.LineTo(ARect.Left, ARect.Bottom-1);
    // タイトルの描画
    Canvas.Font.Assign(TitleFont);
    Text := Columns[ACol-1].Title.Caption;
    Flags := DT_SINGLELINE or DT_VCENTER or
      AlignFlags[Columns[ACol-1].Title.Alignment];
    InflateRect(ARect, -2, -2);
    DrawText(Canvas.Handle, PChar(Text), Length(Text), ARect, Flags);
  end;
end;

procedure TMyDBGrid.KeyDown(var Key: Word; Shift: TShiftState);
begin
  inherited KeyDown(Key, Shift);
  // 選択フィールドのカラムに色をつける処理
  if SelectedField.Index <> Old_SelectedField then
  begin
    Old_SelectedField := SelectedField.Index;
    Invalidate;
  end;
end;

procedure TMyDBGrid.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
begin
  inherited MouseDown(Button, Shift, X, Y);
  // 選択フィールドのカラムに色をつける処理
  if SelectedField.Index <> Old_SelectedField then
  begin
    Old_SelectedField := SelectedField.Index;
    Invalidate;
  end;
end;

// InplaceEditorの背景色を設定する。
procedure TMyDBGrid.WMCommand(var Message: TWMCommand);
begin
  with Message do
  begin
    if (InplaceEditor <> nil) and (Ctl = InplaceEditor.Handle) then
    begin
      if TDummyEditor(InplaceEditor).Color <> EditColor then
        TDummyEditor(InplaceEditor).Color := EditColor;
    end;
  end;
  inherited;
end;

|

☆DBGridのスクロールバーを非表示にする。

カスタムコンポーネントでの処理です。 縦スクロールバーは、簡単に非表示にできましたが、横スクロールバーは、なかなか思うよう にできなかったです。

TDBGridからの派生コンポーネントで、UpdateScrollBarをoverrideしておきます。
  THRCustomDBGrid = class(TDBGrid)
  protected
    procedure UpdateScrollBar; override;


縦スクロールバーを非表示にする処理です。(inheritedはしない)
procedure THRCustomDBGrid.UpdateScrollBar;
var
  SI: TScrollInfo;
begin
  SI.cbSize := sizeof(SI);
  SI.fMask := SIF_RANGE;
  SI.nMin := 0;
  SI.nMax := 0;
  SetScrollInfo(Handle, SB_VERT, SI, False);
end;

横スクロールバーを消す処理です。
(SetScrollInfoをSB_HORZとしても非表示にはできませんでした。)
constructor THRCustomDBGrid.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ScrollBars := ssVertical; // この処理
  (略)
end;  

上記の両方を設定すると、無事、両方のスクロールバーが非表示になりました。
(ScrollBars := ssNoneにしてもなぜか縦スクロールが表示されました。)

以上のように、なんかScrollBarsやSetScrollInfo APIだけで簡単に設定できそうなのに、なかなか手強かったです。まだ、実際に使っていない処理なので、不具合があるかも知れないです。

[追記]
Delphi Q & A 掲示板のMr.XRAYさんの方法が、簡単でいいですね。
(property ScrollBars;を追記するだけ)

Delphi Q & A 掲示板
■ DBGridの横スクロールバーの非表示について

|

☆DBGridのカラム移動が変?

まず、次のように3つのDBGridに同じDataSourceを関連付けたものを3つ用意します。

Column_move1



そして実行します。

Column_move2



それから最上段のDBGridのCapitalというカラムを先頭に移動させます。 2段目、3段目が一緒に移動しています。しかも微妙にカラム幅が変わっています。

Column_move3_2



カラム移動は、WMTimerで制御しているようなので、その影響なのでしょうか? 理由はよくわからないですけど、中途半端に連動して動くという点が使い物にならないですよね。 対応策ですが、DBGridのカラムを設定するとこの問題は発生しませんでした。

|

☆DBGridに複数行選択のオペレーション機能を追加する。

Excelのようにインジゲーター部分をスライドさせて複数行選択できる機能を実装してみます。 DBGridの複数行選択機能としては、CTRL+左クリックとShift+上下キーの 2種類がありますが、今回は、MouseMove時にShift+上下キーをシミュレートします。 スライド選択でのポイントは、端部にスライドした場合の処理です。 当初WMTimerで処理していたのですが、タイトルをクリックしたときにも メッセージが流れてきてたので、Timerコントロールを使いました。

こんなイメージです。

Slide


unit HRSlideSelectDBGrid;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, DB, Grids, DBGrids, ExtCtrls;

type
  THRSlideSelectDBGrid = class(TDBGrid)
  private
    FEditMode: Boolean;
    FTrackingDirection :Integer;
    FTracking: Boolean;
    FIndicatorWidth: Integer;
    FScrollTimer: TTimer;
    FScrollOperation: Word;
    procedure ShiftPlusUp;
    procedure ShiftPlusDn;
    procedure SetIndicatorWidth(Value: Integer);
    procedure ScrollTimer(Sender: TObject);
  protected
    procedure SetColumnAttributes; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property IndicatorWidth: Integer read  FIndicatorWidth
      write SetIndicatorWidth default 30;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Hiderin', [THRSlideSelectDBGrid]);
end;

constructor THRSlideSelectDBGrid.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  // スライドし易いように、Indicator幅を広くする。
  FIndicatorWidth := 30;
  SetColumnAttributes;

  // スクロールタイマー
  FScrollTimer := TTimer.Create(Self);
  with FScrollTimer do
  begin
    Interval := 50;
    OnTimer  := ScrollTimer;
    Enabled := False;
  end;

  // 複数選択できるようにしておく。
  Options :=  Options + [dgMultiSelect];
end;

destructor THRSlideSelectDBGrid.Destroy;
begin
  FScrollTimer.Free;
  inherited Destroy;
end;

procedure THRSlideSelectDBGrid.MouseDown(Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  Cell: TGridCoord;
begin
  // セル位置の取得
  Cell := MouseCoord(X, Y);

  // スライド選択開始のための設定
  if (Cell.X = 0) and (Cell.Y > 0) and (not FTracking) and
     (not (ssCtrl in Shift)) and (not (ssShift in Shift)) then
  begin
    FTracking := True;
    FTrackingDirection := Cell.Y;
    // dgEditingがOptionsに含まれていると、スライド選択時に最終行を追加
    // 処理してしまうため、一時無効にしておく。
    if ([dgEditing] * Options = [dgEditing]) then
    begin
      FEditMode := True;
      Options := Options - [dgEditing];
    end;
  end;

  inherited MouseDown(Button, Shift, X, Y);;
end;

procedure THRSlideSelectDBGrid.MouseUp(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
  // スライド選択の終了処理
  FScrollTimer.Enabled := False;
  FTracking := False;
  // 必要ならば無効にしたdgEditingを有効に戻しておく。
  if FEditMode then
  begin
    FEditMode := False;
    Options := Options + [dgEditing];
  end;
  inherited MouseUp(Button, Shift, X, Y);
end;

procedure THRSlideSelectDBGrid.MouseMove(Shift: TShiftState; X, Y: Integer);

  procedure RowSelectByMouseMove;
  const
    Offset = 20; //適当
  var
    Cell: TGridCoord;
  begin
    // セル位置の取得
    Cell := MouseCoord(X, Y);

    // 表示されていない部分をスクロールさせます。
    if (Y < Offset) or (Y >= ClientHeight - Offset) then
      begin
        if (Y < Offset) then
          FScrollOperation := SB_LINEUP
        else
          FScrollOperation := SB_LINEDOWN;
        FScrollTimer.Enabled := True;
      end
    else
      FScrollTimer.Enabled := False;

    // マウスの位置の行を選択させます。
    if (FTrackingDirection <> Cell.Y) then
    begin
      if (FTrackingDirection < Cell.Y) then
        ShiftPlusDn
      else
        ShiftPlusUp;
      FTrackingDirection := Cell.Y;
    end;
  end;

begin
  // スライド選択が開始されていない場合には処理しません。
  if FTracking then
  begin
    RowSelectByMouseMove;
    Exit;
  end;
  inherited MouseMove(Shift, X, Y);
end;


procedure THRSlideSelectDBGrid.ScrollTimer(Sender: TObject);
var
  KeyBoardState: TKeyBoardState;
  DefaultKeyState: Byte;
begin
  if (FScrollOperation = SB_LINEDOWN) then
  begin
    GetKeyBoardState(KeyBoardState);
    DefaultKeyState := KeyBoardState[VK_SHIFT];
    KeyBoardState[VK_SHIFT] := $81;
    SetKeyBoardState(KeyBoardState);
    Perform(WM_KEYDOWN, VK_DOWN, 1);
    Perform(WM_KEYUP, VK_DOWN, 1);
    KeyBoardState[VK_SHIFT] := DefaultKeyState;
    SetKeyBoardState(KeyBoardState);
  end
  else
  begin
    GetKeyBoardState(KeyBoardState);
    DefaultKeyState := KeyBoardState[VK_SHIFT];
    KeyBoardState[VK_SHIFT] := $81;
    SetKeyBoardState(KeyBoardState);
    Perform(WM_KEYDOWN, VK_UP, 1);
    Perform(WM_KEYUP  , VK_UP, 1);
    KeyBoardState[VK_SHIFT] := DefaultKeyState;
    SetKeyBoardState(KeyBoardState);
  end;
end;

procedure THRSlideSelectDBGrid.ShiftPlusDn;
var
  KeyBoardState: TKeyBoardState;
  DefaultKeyState: Byte;
begin
  GetKeyBoardState(KeyBoardState);
  DefaultKeyState := KeyBoardState[VK_SHIFT];
  KeyBoardState[VK_SHIFT] := $81;
  SetKeyBoardState(KeyBoardState);
  Perform(WM_KEYDOWN, VK_DOWN, 1);
  Perform(WM_KEYUP, VK_DOWN, 1);
  KeyBoardState[VK_SHIFT] := DefaultKeyState;
  SetKeyBoardState(KeyBoardState);
end;

procedure THRSlideSelectDBGrid.ShiftPlusUp;
var
  KeyBoardState: TKeyBoardState;
  DefaultKeyState: Byte;
begin
  GetKeyBoardState(KeyBoardState);
  DefaultKeyState := KeyBoardState[VK_SHIFT];
  KeyBoardState[VK_SHIFT] := $81;
  SetKeyBoardState(KeyBoardState);
  Perform(WM_KEYDOWN, VK_UP, 1);
  Perform(WM_KEYUP  , VK_UP, 1);
  KeyBoardState[VK_SHIFT] := DefaultKeyState;
  SetKeyBoardState(KeyBoardState);
end;

procedure THRSlideSelectDBGrid.SetIndicatorWidth(Value: Integer);
begin
  if Value <> FIndicatorWidth then
  begin
    FIndicatorWidth := Value;
    SetColumnAttributes;
  end;
end;

procedure THRSlideSelectDBGrid.SetColumnAttributes;
begin
  inherited;
  if (dgIndicator in Options) then
    ColWidths[0] := IndicatorWidth;
end;

end.

|

☆DBGridにインデント機能を付ける。

前回、☆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キーで半角進むことを利用しています。

|

■TEAD for CodeGear RAD Studio

ニュースグループ borland.public.delphi.japanese で DEKO 氏から CodeGear RAD Studio 2007(D2007/CB2007含む)ユーザー向けに、 高機能なテキストエディタ"TEAD"を無償公開するというアナウンスがありました。 詳細は、ニュースグループをご覧下さい。

DEKO氏のホームページ
DEKOのアヤシいお部屋。
http://homepage1.nifty.com/ht_deko/

[情報元]
borland.public.delphi.japanese
TEAD for CodeGear RAD Studio

|

■リソースエディタ

先日からDBGridのIndicator部分の変更をしています。 Indicatorのアイコンは、dbgrids.resに含まれているようなので、 アイコンを入れ替えるためにリソースファイルを作ろうと思ったら、 リソースエディタが見つかりませんでした。 昔は、BINフォルダにあったような気がするんだけど。

仕方がないのでネットで調べて XN Resource Editor というのをダウンロードしました。 なんとDelphi製で、ソースコードも公開されておられます。 プロジェクトファイルの日付が2005年12月17日となっていますので、 もう十分完成されたものなんですね。 皆さんご存知だと思われる有名ソフトウェアの紹介でした。

Colin Wilson's Web Site.
Colin Wilson's Delphi 2006 Website
http://www.wilsonc.demon.co.uk/delphi.htm

|

☆ネストしたクラス

Nick Hodges氏のブログで、「ネストしたクラスが使えるって知ってた?」という記事がありますね。 これのメリットって、クラス間の関係がはっきりするってことぐらいなんでしょうか。

確かに、細項目(TItem)、いくつかの細項目が集まった項目(TItems)とそれを管理するリスト(ITtemList)を 作ったときにきちんと注釈を入れておかないと、あとでどのような関係になっているのかわからなくなりますけどね。

ネストしないクラスの場合
type

  TItem1 = class(TObject);
  TItem2 = class(TObject);
  TItem3 = class(TObject);

  TItems = class
  private
    FItem1: TItem1;
    FItem2: TItem2;
    FItem3: TItem3;
  public
    constructor Create;
    destructor Destroy; override;
  end;

  TItemList = class
  private
    FList: TList;
  public
    constructor Create;
    destructor Destroy; override;
  end;

ネストしたクラスの場合
type

  TItemList = class
  private
    type
      TItems = class
      private
        type
          TItem1 = class(TObject);
          TItem2 = class(TObject);
          TItem3 = class(TObject);
      private
        FItem1: TItem1;
        FItem2: TItem2;
        FItem3: TItem3;
      public
        constructor Create;
        destructor Destroy; override;
      end;
    private
      FList: TList;
  public
    constructor Create;
    destructor Destroy; override;
  end;
関係はわかりやすいけど、見た目ややこしくない?
今までのクラスを書き直すほど、魅力はないかな。


Nick Hodges
Did you know….
http://blogs.codegear.com/nickhodges/2007/10/03/38952

[参考]
CodeGear
Delphi 7以降の言語およびコンパイラの新機能
http://dn.codegear.com/jp/article/34409

|

☆ドッキングされたフォーム上のコンポーネントにフォーカスを当てる。

☆TDockTabSetを使ってみる。 で、作ったサンプルで、Form3にパネルとボタンを貼り付け、次のような処理を行います。
procedure TForm3.Button1Click(Sender: TObject);
var
  I: Integer;
begin
  I := ListBox1.Items.Add('NewItem');
  ListBox1.ItemIndex := I;
  ActiveControl := ListBox1;
end;

実行するとこんな感じです。(ドッキング時)

Focusonlistbox


(フローティング時)

Focusonlistbox2

コードを見ていただくとわかって頂けるかと思いますが、追加したアイテムにフォーカスを当てる処理です。 この処理は、ドッキングしていない状態の時は、動作しますが、ドッキングしているときは 次のようなメッセージが表示されます。

Err

Err2


この問題を解決するために、ドッキングしているかどうかを判断させて次のような処理を行いました。

Form1への追加処理
type
  TForm1 = class(TForm)
   (略)
  public
    procedure FocusOnListBox;
  end;

    (略)

procedure TForm1.FocusOnListBox;
begin
  ActiveControl := Form3.ListBox1;
end;

Button1Clickを次のように変更します。 フォームがドッキングしているかどうかは、Parentがnilかどうかで判断させます。
procedure TForm3.Button1Click(Sender: TObject);
var
  I: Integer;
begin
  I := ListBox1.Items.Add('NewItem');
  ListBox1.ItemIndex := I;
if Self.Parent = nil then
    ActiveControl := ListBox1
  else
    Form1.FocusOnListBox;
end;
これで問題なく動作しました。

|

☆TDrawGridを使ってみる。

今更、「使ってみると言われても~」って、感じでしょうか(笑)
以前コテコテに描画していたものをシンプルなものに作り変えてみました。
工程表などを作るときのベースには使えるんじゃないかと思います。


実行したところです。

Dg2


ZOOMもできます。

Dg3


まずは、カレンダーのクラスです。わざわざ作らなくてもよさそうな気もしますけど。
unit JobClass;

interface

uses
  Windows, Messages, SysUtils,  Classes, DateUtils;

type
  {* TDay *}

  TDay = class(TObject)
  private
    FDay : TDateTime;              // 日時
    function GetDay: Integer;      // 日取得
    function GetMonth: Integer;    // 月取得
    function GetYear: Integer;     // 年取得
    function GetWeek: String;      // 曜日取得
    function GetIsToday: Boolean;  // 今日かどうか
  public
    constructor Create(Day: TDateTime);
  published
    property DT    : TDateTime read FDay;       // 日時
    property Day   : Integer   read GetDay;     // 日
    property Month : Integer   read GetMonth;   // 月
    property Year  : Integer   read GetYear;    // 年
    property Week  : String    read GetWeek;    // 曜日
    property IsToday: Boolean  read GetIsToday; // 今日かどうか
  end;

  {* TCalendar *}

  TCalendar = class(TObject)
  private
    FList: TList;           { リスト }
    function GetBaseDate: TDateTime;                 // カレンダーの開始日
    function GetEndDate: TDateTime;                  // カレンダーの終了日
    function GetData(Index: Integer): TDay;          // データの読み込み
    procedure SetData(Index: Integer; Day: TDay);    // データの書き込み
    function GetCount: Integer;                      // データ数
  protected
    procedure Error;                                 // Errorの表示
  public
    constructor Create;                              // 生成
    destructor Destroy; override;                    // 破棄
    procedure Clear;                                 // 消去
    function Add(Day: TDay): Integer;                // 追加
    procedure Insert(Index: Integer; Day: TDay);     // 挿入
    procedure Delete(Index: Integer);                // 削除
    procedure MakeCalendar(FromDay, UntilDay: TDateTime); // カレンダーを作成
    property Items[Index: Integer]: TDay read GetData write SetData; default;
  published
    property BaseDate: TDateTime read GetBaseDate;   // カレンダーの開始日
    property EndDate: TDateTime read GetEndDate;     // カレンダーの終了日
    property Count: Integer read GetCount;           // データ数の取得
  end;

implementation


{ TDay }

constructor TDay.Create(Day: TDateTime);
begin
  inherited Create;
  FDay := Day;
end;

function TDay.GetDay: Integer;
begin
  Result := DayOf(FDay);
end;

function TDay.GetMonth: Integer;
begin
  Result := MonthOf (FDay);
end;

function TDay.GetYear: Integer;
begin
  Result := YearOf(FDay);
end;

function TDay.GetWeek: String;
var
  I: Integer;
begin
  I := DayOfTheWeek(FDay);
  case I of
    1 : Result := '月';
    2 : Result := '火';
    3 : Result := '水';
    4 : Result := '木';
    5 : Result := '金';
    6 : Result := '土';
    7 : Result := '日';
  else
    Result := '';
  end;
end;

function TDay.GetIsToday: Boolean;
begin
  Result := (FDay = TODAY); // TODAYがNOWではダメ
end;

{ TCalendar }

constructor TCalendar.Create;
begin
  inherited;
  FList := TList.Create;
end;

destructor TCalendar.Destroy;
begin
  Clear;
  FList.Free;
  inherited;
end;

procedure TCalendar.Clear;
var
  I: Integer;
begin
  for I := 0 to FList.Count -1 do
    TDay(FList[I]).Free;
  FList.Clear;
end;

function TCalendar.Add(Day: TDay): Integer;
begin
  Result := FList.Add(Day);
end;

procedure TCalendar.Insert(Index: Integer; Day: TDay);
begin
  FList.Insert(Index, Day);
end;

procedure TCalendar.Delete(Index: Integer);
begin
  TDay(FList[Index]).Free;
  FList.Delete(Index);
end;

procedure TCalendar.Error;
begin
  raise Exception.Create('インデックスがリストの範囲を超えています');
end;

function TCalendar.GetData(Index: Integer): TDay;
begin
  if (Index < 0) or (Index >= FList.Count) then Error;
  Result := TDay(FList[Index]);
end;

procedure TCalendar.SetData(Index: Integer; Day: TDay);
begin
  if (Index < 0) or (Index >= FList.Count) then Error;
  FList[Index] := Day;
end;

function TCalendar.GetCount: Integer;
begin
  Result := FList.Count;
end;

function TCalendar.GetBaseDate: TDateTime;
begin
  if FList.Count = 0 then
    Result := -1
  else
    Result := TDay(FList[0]).DT;
end;

function TCalendar.GetEndDate: TDateTime;
begin
  if FList.Count = 0 then
    Result := -1
  else
    Result := TDay(FList[FList.Count-1]).DT;
end;

procedure TCalendar.MakeCalendar(FromDay, UntilDay: TDateTime);
var
  DT: TDateTime;
  Day: TDay;
begin
  Self.Clear;
  DT := FromDay;
  while (DT <= UntilDay) do
  begin
    Day := TDay.Create(DT);
    Self.Add(Day);
    DT := DT + 1;
  end;
end;

end.


デザイン時です。

Dg1_2


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, ExtCtrls, Grids, StdCtrls, DateUtils, JobClass;

const
  BeforeDay = 14;  // 本日より何日前からGridに表示するのか。
  HowDays   = 730; // 本日より何日後までをGridに表示するのか。
  DayStart  = 1;   // Gridでのカレンダーの開始列
  RowStart  = 3;   // Gridでの作業データの開始行
  JobWidth  = 120; // 作業名表示

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Edit1: TEdit;
    StatusBar1: TStatusBar;
    UpDown1: TUpDown;
    Label1: TLabel;
    DateTimePicker1: TDateTimePicker;
    DrawGrid1: TDrawGrid;
    procedure DrawGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
      Rect: TRect; State: TGridDrawState);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure DrawGrid1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure UpDown1Click(Sender: TObject; Button: TUDBtnType);
    procedure DrawGrid1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure DateTimePicker1Change(Sender: TObject);
    procedure DrawGrid1MouseWheelDown(Sender: TObject; Shift: TShiftState;
      MousePos: TPoint; var Handled: Boolean);
    procedure DrawGrid1MouseWheelUp(Sender: TObject; Shift: TShiftState;
      MousePos: TPoint; var Handled: Boolean);
  private
    { Private 宣言 }
    BarOffset1, BarOffset2: Integer;    // オフセット
    JobList: TStringList;               // 作業リスト
    Calendar: TCalendar;                // カレンダーリスト
    function GetDateCell(ACol, ARow: Integer): String; // マウス位置の日付
    function GetStartDateCol(ARow: Integer): Integer;  // 開始日のセル列
    function GetEndDateCol(ARow: Integer): Integer;    // 終了日のセル列
    procedure Zoom(Value: Integer);
  public
  end;

var
  Form1: TForm1;

implementation


{$R *.dfm}

// カンマ区切り文字列からIndexで指定された文字列を返します。
function GetItem(Index: Integer; S: String): String;
var
  SL: TStringList;
begin
  Result := '';
  SL := TStringList.Create;
  try
    SL.CommaText := S;
    if Index < SL.Count then
      Result := SL[Index];
  finally
    SL.Free;
  end;
end;

// 初期設定
procedure TForm1.FormCreate(Sender: TObject);
var
  StartDay: TDateTime;
begin
  // JobList
  JobList := TStringList.Create;

  // サンプルデータの作成
  JobList.Add('現地調査,2007/09/20,2007/09/30,True');
  JobList.Add('基本設計,2007/09/25,2007/10/15,False');
  JobList.Add('実施設計,2007/10/10,2007/11/15,False');

  // DrawGrid1の設定
  DrawGrid1.DefaultDrawing := False;
  DrawGrid1.Options := DrawGrid1.Options -
    [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect];
  DrawGrid1.ColCount  := HowDays;                  // 列数の設定
  DrawGrid1.RowCount  := JobList.Count + RowStart; // 行数の設定
  DrawGrid1.FixedCols := DayStart;                 // 固定列の設定
  DrawGrid1.FixedRows := 2;
  // StatusBar1の設定
  StatusBar1.SimplePanel := True;

  // 固定行の設定
  if (JobList.Count = 0) then
    DrawGrid1.FixedRows := RowStart -1
  else
    DrawGrid1.FixedRows := RowStart;

  // 表示倍率の設定
  UpDown1.Min := 25;
  UpDown1.Max := 150;
  UpDown1.Increment := 25;
  UpDown1.Position := 100;
  Zoom(100);

  // TCalendar - カレンダーリストの作成
  Calendar := TCalendar.Create;
  DateTimePicker1.DateTime := Now;
  StartDay := Date-BeforeDay;
  Calendar.MakeCalendar(StartDay, StartDay+HowDays);
end;

// 終了処理
procedure TForm1.FormDestroy(Sender: TObject);
begin
  Calendar.Free;
  JobList.Free;
end;

// 開始日のセルの列番号を返します。
function TForm1.GetStartDateCol(ARow: Integer): Integer;
var
  NowRow: Integer;
  StartDate: TDateTime;
begin
  NowRow := ARow - RowStart;
  StartDate := StrToDateTime(GetItem(1, JobList[NowRow]));
  if StartDate <= Calendar.BaseDate then
    Result := DayStart
  else
    Result := DaysBetween(StartDate, Calendar.BaseDate) + DayStart;
end;

// 終了日のセルの列番号を返します。
function TForm1.GetEndDateCol(ARow: Integer): Integer;
var
  NowRow: Integer;
  EndDate: TDateTime;
begin
  NowRow := ARow - RowStart;
  EndDate := StrToDateTime(GetItem(2, JobList[NowRow]));
  if EndDate >= Calendar.EndDate then
    Result := DaysBetween(Calendar.EndDate, Calendar.BaseDate) + DayStart
  else
    Result := DaysBetween(EndDate, Calendar.BaseDate) + DayStart;
end;

// Zoom
procedure TForm1.Zoom(Value: Integer);
begin
  with DrawGrid1 do
  begin
    Canvas.Font.Size := Round(  8 * (Value/100)); // フォントサイズ
    DefaultColWidth  := Round( 16 * (Value/100)); // 列のデフォルトサイズ
    DefaultRowHeight := Round( 18 * (Value/100)); // 行のデフォルトサイズ
    RowHeights[0]    := Round( 14 * (Value/100)); // 年月表示行の高さ
    RowHeights[1]    := Round( 14 * (Value/100)); // 日表示行の高さ
    ColWidths[0]     := Round(JobWidth * (Value/100)); // 作業名の幅
  end;
  BarOffset1  := Round( 3 * (Value/100)); // 工程バーと枠とのオフセット
  BarOffset2  := Round( 9 * (Value/100)); // 工程バーと枠とのオフセット
  DrawGrid1.Refresh;
end;

procedure TForm1.DrawGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);

  // 年月の描画
  procedure MonthDraw;
  var
    S: String;
    I: Integer;
    Y,M: Integer;
  begin
    I := Calendar[ACol-DayStart].Day;    // 日
    Y := Calendar[ACol-DayStart].Year;   // 年
    M := Calendar[ACol-DayStart].Month;  // 月

    // 枠の描画
    DrawGrid1.Canvas.Pen.Color := clSilver;
    DrawGrid1.Canvas.Pen.Width := 1;
    DrawGrid1.Canvas.MoveTo(Rect.Left , Rect.Bottom-1);
    DrawGrid1.Canvas.LineTo(Rect.Right, Rect.Bottom-1);

    // 文字の描画
    DrawGrid1.Canvas.Font.Color := clWhite;
    S := IntToStr(M);

    // 3日分のセルに年月日を描画させます。
    if (I > 3) then Exit;

    case I of
      1 : begin
            // 西暦の上2桁を描画します。2000年を示す20を描画します。
            if (Y > 2000) then
              S := '20'
            else
              S := '19';
            DrawText(DrawGrid1.Canvas.Handle, PChar(S), -1, Rect,
              DT_SINGLELINE or DT_RIGHT);
            // 枠の描画 - 1日のみ左側に罫線を描画します。
            DrawGrid1.Canvas.Pen.Color := clSilver;
            DrawGrid1.Canvas.Pen.Width := 1;
            DrawGrid1.Canvas.MoveTo(Rect.Left, Rect.Top);
            DrawGrid1.Canvas.LineTo(Rect.Left, Rect.Bottom);
          end;
      2 : begin
            // 西暦の下2桁+/を描画します。
            S := Copy(IntToStr(Y),3,2)+'/';
            DrawText(DrawGrid1.Canvas.Handle, PChar(S), -1, Rect,
              DT_SINGLELINE or DT_LEFT);
          end;
      3 : begin
            // 月を描画します。
            S := IntToStr(M);
            DrawText(DrawGrid1.Canvas.Handle, PChar(S), -1, Rect,
              DT_SINGLELINE or DT_LEFT);
          end;
    end;
  end;

  // 日の描画
  procedure DayDraw;
  var
    S: String;
    I: Integer;
  begin
    I := Calendar[ACol-DayStart].Day;
    S := IntToStr(I);
    DrawGrid1.Canvas.Font.Color := clBlack;
    DrawText(DrawGrid1.Canvas.Handle, PChar(S), -1, Rect,
      DT_CENTER or DT_SINGLELINE or DT_VCENTER);

    // 1日のみ左側に罫線を描画します。
    if (I = 1) then
    begin
      DrawGrid1.Canvas.Pen.Color := clSilver;
      DrawGrid1.Canvas.Pen.Width := 1;
      DrawGrid1.Canvas.MoveTo(Rect.Left, Rect.Top);
      DrawGrid1.Canvas.LineTo(Rect.Left, Rect.Bottom);
    end;
  end;

  // 曜日の描画
  procedure WeekDraw;
  var
    I,D: Integer;
  begin
    D := Calendar[ACol-DayStart].Day;
    I := DayOfTheWeek(Calendar[ACol-1].DT);
    case I of
      6 : DrawGrid1.Canvas.Font.Color := clBlue; // 土
      7 : DrawGrid1.Canvas.Font.Color := clRed;  // 日
    else
      DrawGrid1.Canvas.Font.Color := clBlack;    // その他
    end;

    // 曜日を描画します。
    DrawText(DrawGrid1.Canvas.Handle, PChar(Calendar[ACol-DayStart].Week),-1,Rect,
      DT_CENTER or DT_SINGLELINE or DT_VCENTER);

    // 枠を描画します。
    DrawGrid1.Canvas.Pen.Color := clSilver;
    DrawGrid1.Canvas.Pen.Width := 1;
    DrawGrid1.Canvas.MoveTo(Rect.Left, Rect.Bottom-1);
    DrawGrid1.Canvas.LineTo(Rect.Right, Rect.Bottom-1);

    // 1日のみ左側に罫線を描画します。
    if (D = 1) then
    begin
      DrawGrid1.Canvas.MoveTo(Rect.Left, Rect.Top);
      DrawGrid1.Canvas.LineTo(Rect.Left, Rect.Bottom-1);
      DrawGrid1.Canvas.LineTo(Rect.Right, Rect.Bottom-1);
    end;
  end;

var
  S: String;
  NowRow: Integer;
  SI,EI: Integer;
  ARect: TRect;
  EndDate: TDateTime;
  Finished: Boolean;
begin
  // タイトルの描画
  if (ACol = 0) and (ARow < 3) then
  begin
    //位置の設定
    ARect := Rect;
    ARect.Top := 0;
    ARect.Left := ARect.Left;
    ARect.Bottom := (DrawGrid1.RowHeights[0]+2) * 3{行};
    ARect.Right := ARect.Right + 1;
    //色の設定
    DrawGrid1.Canvas.Brush.Color := clGray;// clBtnFace;
    DrawGrid1.Canvas.FillRect(ARect);
    DrawGrid1.Canvas.Font.Color := clWhite;
    // 文字の描画
    S := '作業管理';
    DrawText(DrawGrid1.Canvas.Handle, PChar(S), -1, ARect,
      DT_CENTER or DT_SINGLELINE or DT_VCENTER);
    Exit;
  end;

  // カレンダーの表示
  if (ACol <> 0) and (ACol >= DayStart) and (ARow < RowStart) then
  begin
    //位置の設定
    //色の設定
    DrawGrid1.Canvas.Brush.Color := RGB(0,221,111);
    DrawGrid1.Canvas.FillRect(Rect);
    //描画
    case ARow of
      0: MonthDraw; // 月
      1: DayDraw;   // 日付
      2: WeekDraw;  // 曜日
    end;
    Exit;
  end;

  { 作業名の描画 }
  if (ACol = 0) and (ARow >= RowStart) then
  begin
    // セルの行番号
    NowRow := ARow - RowStart;

    // 背景の描画
    DrawGrid1.Canvas.Brush.Color := clBtnFace;
    DrawGrid1.Canvas.FillRect(Rect);

    // 線の描画
    DrawGrid1.Canvas.Pen.Color := clSilver;
    DrawGrid1.Canvas.Pen.Width := 2;
    DrawGrid1.Canvas.MoveTo(Rect.Left-1, Rect.Bottom);
    DrawGrid1.Canvas.LineTo(Rect.Right,Rect.Bottom);

    // 文字の描画
    Rect.Left := Rect.Left + 10;
    DrawGrid1.Canvas.Font.Color := clBlack;
    DrawGrid1.Canvas.Font.Style := [];
    S := GetItem(0,JobList[NowRow]);
    DrawText(DrawGrid1.Canvas.Handle, PChar(S), Length(S), Rect,
      DT_LEFT or DT_SINGLELINE or DT_VCENTER);
    Exit;
  end;

  { バーの描画 }
  if (ACol >= DayStart) and (ARow >= RowStart) then
  begin
    // セルの行番号
    NowRow := ARow-RowStart;

    // 背景の描画
    if Calendar[ACol-1].IsToday then
      DrawGrid1.Canvas.Brush.Color := clAqua  // 今日
    else
      DrawGrid1.Canvas.Brush.Color := RGB(255,244,250);
    DrawGrid1.Canvas.FillRect(Rect);

    // Grid枠線の描画
    DrawGrid1.Canvas.Pen.Color := clSilver;
    DrawGrid1.Canvas.Pen.Width := 1;
    DrawGrid1.Canvas.MoveTo(Rect.Left, Rect.Top);
    DrawGrid1.Canvas.LineTo(Rect.Left, Rect.Bottom-1);
    DrawGrid1.Canvas.LineTo(Rect.Right, Rect.Bottom-1);

    // 描画すべきデータかどうかをチェックします。
    // 終了日が開始日以前のデータは処理させません。
    EndDate := StrToDateTime(GetItem(2,JobList[NowRow]));
    if EndDate < Calendar.BaseDate then Exit;

    // 開始日のセル位置を計算します。
    SI := GetStartDateCol(ARow);

    // 終了日のセル位置を計算します。
    EI := GetEndDateCol(ARow);

    if (ACol >= SI) and (ACol <= EI) then
    begin
      // バー用にRectサイズを調整する。
      ARect := Rect;
      ARect.Top    := Rect.Top    + BarOffset1 -2;  //-2は微調整
      ARect.Bottom := Rect.Bottom - BarOffset1;
      Finished := StrToBool(GetItem(3, JobList[NowRow]));
      if Finished then
        DrawGrid1.Canvas.Brush.Color := clSilver
      else
        DrawGrid1.Canvas.Brush.Color := clBlue;

      DrawGrid1.Canvas.FillRect(ARect);
      DrawGrid1.Canvas.Brush.Style := bsSolid;
    end;
  end;
end;

procedure TForm1.DrawGrid1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  ACol, ARow: Integer;
  S: String;
begin
  // StatusBar1に日付を表示させます。
  DrawGrid1.MouseToCell(X, Y, ACol, ARow);
  S := GetDateCell(ACol, ARow);
  StatusBar1.SimpleText := S;
end;

// マウス位置の日付データを取得します。
function TForm1.GetDateCell(ACol, ARow: Integer): String;
var
  Y,M,D: Integer;
  W: String;
begin
  if (ACol >= DayStart) and (ARow >= RowStart) then
    begin
      Y := Calendar[ACol-DayStart].Year;   // 年
      M := Calendar[ACol-DayStart].Month;  // 月
      D := Calendar[ACol-DayStart].Day;    // 日
      W := Calendar[ACol-DayStart].Week;   // 曜日
      Result := Format(' %4d年%02d月%2d日 %S曜日',[Y,M,D,W]);
    end
  else
    Result := '';
end;

procedure TForm1.UpDown1Click(Sender: TObject; Button: TUDBtnType);
begin
  Zoom(UpDown1.Position);
end;

procedure TForm1.DrawGrid1KeyDown(Sender: TObject;
  var Key: Word; Shift: TShiftState);
var
  StartDay: TDateTime;
  Value: Integer;
begin
  { ←→キーで日付を移動させます。 }
  if (Shift = []) and ((Key = VK_LEFT) or (Key = VK_RIGHT)) then
  begin
    if (Key = VK_LEFT) then
      Value := -7
    else
      Value :=  7;

    { 表示開始日を設定します。 }
    StartDay := IncDay(Calendar.BaseDate, Value);

    { カレンダーデータを作成します。 }
    Calendar.MakeCalendar(StartDay, StartDay + HowDays);

    { 表示を更新します。 }
    DrawGrid1.Refresh;
  end;
end;

procedure TForm1.DrawGrid1MouseWheelDown(Sender: TObject;
  Shift: TShiftState; MousePos: TPoint; var Handled: Boolean);
var
  Key: Word;
begin
  key := VK_RIGHT;
  DrawGrid1KeyDown(Sender, key, []);
end;

procedure TForm1.DrawGrid1MouseWheelUp(Sender: TObject;
  Shift: TShiftState; MousePos: TPoint; var Handled: Boolean);
var
  Key: Word;
begin
  key := VK_LEFT;
  DrawGrid1KeyDown(Sender, key, []);
end;

procedure TForm1.DateTimePicker1Change(Sender: TObject);
var
  StartDay: TDateTime;
begin
  //カレンダー作成
  StartDay := DateTimePicker1.DateTime;
  Calendar.MakeCalendar(StartDay-BeforeDay, StartDay+HowDays);
  DrawGrid1.Refresh;
end;

end.

|

☆TEditにインデント機能をつける。

私の仕事で使う見積アイテムは、
 バルコニー手摺  アルミ製(ステンカラー)   H=1100
 床       300角磁器質タイル貼
 土間      防水モルタル金鏝押エ    t=30
といったように、見た目を合わせて書くものが多いです。(ここではフォントの関係でずれてますけど)
データベースから作成したものは、自動処理させていますが、手入力する部分は 結構面倒なので、TEditにインデント機能をつけてみようと思います。

今回作成する機能一覧
[ CTRL+I ]
  次のインデント位置に要素を移動させます。
[ CTRL+SHIFT+I ]
  前のインデント位置に要素を移動させます。
[ CTRL+U ]
  次の要素開始位置へキャレットを移動させます。  
[ CTRL+SHIFT+U ]
  前の要素開始位置へキャレットを移動させます。 

いまいち使い方が伝わらないかも知れませんが、編集エディットで上記のキーを試してみて下さい。

Indent


ランタイムテーマにも対応させるため、☆カーソル下のテキストを選択する。で 作成した AdjustedEdit 名前空間を使います。
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, AdjustedEdit;

type
  TPositionList = array of Integer;

  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Label1: TLabel;
    Memo1: TMemo;
    Label2: TLabel;
    Label3: TLabel;
    procedure Edit1KeyPress(Sender: TObject; var Key: Char);
  private
    procedure GetIndentPosList(S: String; var List: TPositionList);
    procedure GetElementPosList(S: String; var List: TPositionList);
  public
    procedure PriorIndent;
    procedure NextIndent;
    procedure PriorElement;
    procedure NextElement;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  // Ctrl + Iで指定位置にエレメントを移動させます。
  if (GetKeyState(VK_SHIFT) and $80 = 0) and
     (GetKeyState(VK_CONTROL) and $80 > 0) and
     (Key = #9) then
  begin
    NextIndent;
    Key := #0;
    Exit;
  end;

  // Ctrl + Shift + Iで指定位置にエレメントを移動させます。
  if (GetKeyState(VK_SHIFT) and $80 > 0) and
     (GetKeyState(VK_CONTROL) and $80 > 0) and
     (Key = #9) then
  begin
    PriorIndent;
    Key := #0;
    Exit;
  end;

  // 要素開始位置へキャレットを移動させます。CTRL+U
  if (GetKeyState(VK_SHIFT) and $80 = 0) and
     (GetKeyState(VK_CONTROL) and $80 > 0) and
     (Key = #21) then
  begin
    Key := #0;
    NextElement;
    Exit;
  end;

  // 要素開始位置へキャレットを移動させます。CTRL+SHIFT+U
  if (GetKeyState(VK_SHIFT) and $80 > 0) and
     (GetKeyState(VK_CONTROL) and $80 > 0) and
     (Key = #21) then
  begin
    Key := #0;
    PriorElement;
    Exit;
  end;
end;


procedure TForm1.NextIndent;
var
  P1, P2: Integer;
  I,J: Integer;
  S1, S2, S3: String;
  List: TPositionList;
begin
  SendMessage(Edit1.Handle, WM_SETREDRAW, Ord(False), 0);
  try
    P1 := GetSelStart(Edit1);
    P2 := 0;

    GetIndentPosList(Edit2.Text, List);
    for I := 0 to High(List) do
      if List[I]-1 > P1 then
      begin
        P2 := List[I]-1;
        Break;
      end;

    if P2 = 0 then Exit;

    S1 := Copy(Edit1.Text, 1, P1);
    S2 := Copy(Edit1.Text, P1+1, Length(Edit1.Text)-P1);

    J := P2 - P1;
    S3 := S1 +StringOfChar(' ', J) + S2;
    Edit1.Text := S3;
  finally
    SendMessage(Edit1.Handle, WM_SETREDRAW, Ord(True), 0);
  end;
  SetSelStart(Edit1, P2);
end;

procedure TForm1.PriorIndent;
var
  P1, P2, P3: Integer;
  I,J: Integer;
  S1, S2, S3: String;
  List: TPositionList;
begin
  SendMessage(Edit1.Handle, WM_SETREDRAW, Ord(False), 0);
  try
    P1 := GetSelStart(Edit1);
    P2 := 0;

    GetIndentPosList(Edit2.Text, List);
    for I := High(List) downto 0 do
      if List[I]-1 < P1 then
      begin
        P2 := List[I]-1;
        Break;
      end;

    if P2 < 0 then Exit;

    S1 := TrimRight(Copy(Edit1.Text, 1, P1));
    P3 := Length(S1);

    S2 := Copy(Edit1.Text, P1+1, Length(Edit1.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;
    Edit1.Text := S3;
  finally
    SendMessage(Edit1.Handle, WM_SETREDRAW, Ord(True), 0);
  end;
  SetSelStart(Edit1, P2);
end;

procedure TForm1.NextElement;
var
  I: Integer;
  P1: Integer;
  List: TPositionList;
begin
  GetElementPosList(Edit1.Text, List);
  P1 := GetSelStart(Edit1);
  for I := 0 to High(List) do
    if P1 < List[I] then
    begin
      P1 := List[I];
      Break;
    end;
  SetSelStart(Edit1, P1);
end;

procedure TForm1.PriorElement;
var
  I: Integer;
  P1: Integer;
  List: TPositionList;
begin
  GetElementPosList(Edit1.Text, List);
  P1 := GetSelStart(Edit1);
  for I := High(List) downto 0 do
    if P1 > List[I] then
    begin
      P1 := List[I];
      Break;
    end;
  SetSelStart(Edit1, P1);
end;

// 要素の位置を取得します。
procedure TForm1.GetElementPosList(S: String; var List: TPositionList);

  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 (w = $8140) then
      begin
        Increment(P, I, 2);
        F := True;
      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 (P^ = ' ') then
      begin
        Increment(P, I, 1);
        F := True;
      end
      else
       Increment(P, I, 1);
    end;
  end;
  SetLength(List, J+1);
end;

// インデントの位置を取得します。
procedure TForm1.GetIndentPosList(S: String; var List: TPositionList);
var
  I: Integer;
  SL: TStringList;
begin
  SL := TStringList.Create;
  try
    SL.CommaText := S;
    SetLength(List, SL.Count);
    for I := 0 to SL.Count - 1 do
      List[I] := StrToIntDef(SL[I],0);
  finally
    SL.Free;
  end;
end;

end.

問題点としては、Undoできないことです。よくわかりませんが、文字列を置き換える前に、keybd_event等でクリップボードのカットをさせたりするとできるのかな?

今後、これをTDBGridのTInplaceEditに実装したいと思っています。

|

■CodeGear Outside

『CodeGear製品に関する情報を掲載しているユーザーWebサイト、ブログなどをご紹介します。』ということで CodeGear Outsideというページができました。これからたくさん増えていくといいですね。
※誠に僭越ながら、DEKO 氏からの紹介により、ここも載せて頂いてます。

CodeGear Outside
http://blogs.codegear.com/teamj/2007/10/02/codegear-outside/

|

☆グラデーションパネルを作る。

他にも方法があるかも知れませんが、次の3種類について検討してみます。
※色は、clWhiteとclGradientInactiveCaptionを設定しています。

[A] カスタムコンポーネントを作る。
今回、TPanelを拡張したコンポーネントを作ってみました。(最後に、コードを載せています)
一度作ると利用は簡単ですが、いろんなコンポーネントを管理していくのは結構面倒です。

[B] TToolBarの上にTPanelを配置して使う。
左端にラインが表示されますが、とても簡単です。

[C] TPanel+TImage
見た目は問題ないのですが、リサイズ時に少しちらつきます。
リサイズ時の処理だけ書けばよいので、パネルの数が少ない場合には簡単です。
// Image1のグラデーション描画用
uses
  GraphUtil;

procedure TForm1.FormResize(Sender: TObject);
var
  Rect: TRect;
begin
  Image1.Picture := nil;
  Rect := Image1.ClientRect;
  GradientFillCanvas(Image1.Canvas, clWhite, clGradientInactiveCaption,
    Rect, gdVertical);
end;


デザイン時です。

Design



実行するとこんな感じです。

Run



見た目はそう変わらないですね。どれを採用するかはケースバイケースかな? 一度カスタムコンポーネントにしてしまうとそれを使うことが多いんですけど、 きちんと管理していかないと古いバージョンのものを訂正しようとするときに、 ○○コンポーネントがない!って言われて、訂正する気力を失うので、要注意です(笑)

今回作ったグラデーションパネルです。 グラデーションを描くためだけに作っているので、かなり適当です。 というか、TToolBarから必要なものを取ってきてるだけという疑惑もあります(笑)
unit HRGadientPanel;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
  ExtCtrls, GraphUtil, ComCtrls;

type
  THRGadientPanel = class(TPanel)
  private
    FDrawingStyle: TTBDrawingStyle;
    FGradientDirection: TGradientDirection;
    FGradientEndColor: TColor;
    FGradientStartColor: TColor;
    function IsGradientEndColorStored: Boolean;
    procedure SetGradientDirection(Value: TGradientDirection);
    procedure SetGradientEndColor(Value: TColor);
    procedure SetGradientStartColor(Value: TColor);
    procedure SetDrawingStyle(Value: TTBDrawingStyle);
  protected
    procedure Paint; override;
    procedure Resize; override;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property GradientDirection: TGradientDirection read FGradientDirection
      write SetGradientDirection default gdVertical;
    property GradientEndColor: TColor read FGradientEndColor
      write SetGradientEndColor stored IsGradientEndColorStored;
    property GradientStartColor: TColor read FGradientStartColor
      write SetGradientStartColor default clWindow;
    property DrawingStyle: TTBDrawingStyle read FDrawingStyle
      write SetDrawingStyle default dsNormal;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Hiderin', [THRGadientPanel]);
end;

{ THRGadientPanel }

constructor THRGadientPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  GradientStartColor := clWindow;
  GradientEndColor := GetShadowColor(clBtnFace, -25);
  GradientDirection := gdVertical;
end;

procedure THRGadientPanel.Paint;
const
  Alignments: array[TAlignment] of Longint = 
    (DT_LEFT, DT_RIGHT, DT_CENTER);
  VerticalAlignments: array[TVerticalAlignment] of Longint =
    (DT_TOP, DT_BOTTOM, DT_VCENTER);
var
  Rect: TRect;
  Flags: Longint;
begin
  if FDrawingStyle = dsGradient then
  begin
    Rect := GetClientRect;
    GradientFillCanvas(Canvas, FGradientStartColor, FGradientEndColor,
      Rect, GradientDirection);
    with Canvas do
    begin
      Brush.Style := bsClear;
      Font := Self.Font;
      Flags := DT_EXPANDTABS or DT_SINGLELINE or
        VerticalAlignments[VerticalAlignment] or Alignments[Alignment];
      Flags := DrawTextBiDiModeFlags(Flags);
      DrawText(Handle, PChar(Caption), -1, Rect, Flags);
    end;
  end
  else
    inherited;
end;

procedure THRGadientPanel.SetGradientEndColor(Value: TColor);
begin
  if Value <> FGradientEndColor then
  begin
    FGradientEndColor := Value;
    if HandleAllocated then
      Repaint;
  end;
end;

procedure THRGadientPanel.SetGradientStartColor(Value: TColor);
begin
  if Value <> FGradientStartColor then
  begin
    FGradientStartColor := Value;
    if HandleAllocated then
      Repaint;
  end;
end;

function THRGadientPanel.IsGradientEndColorStored: Boolean;
begin
  Result := FGradientEndColor <> GetShadowColor(clBtnFace, -25);
end;

procedure THRGadientPanel.SetGradientDirection(Value: TGradientDirection);
begin
  if FGradientDirection <> Value then
  begin
    FGradientDirection := Value;
    if HandleAllocated then
      Repaint;
  end;
end;

procedure THRGadientPanel.SetDrawingStyle(Value: TTBDrawingStyle);
begin
  if Value <> FDrawingStyle then
  begin
    FDrawingStyle := Value;
    if HandleAllocated then
      Repaint;
  end;
end;

procedure THRGadientPanel.Resize;
begin
  inherited Resize;
   if (FDrawingStyle = dsGradient) and HandleAllocated then
    Repaint;
end;

end.

|

■こんなに簡単!Delphiでデータベースアプリケーション開発

下記サイトで、CodeGearの米澤 千賀子 氏が『こんなに簡単!Delphiでデータベースアプリケーション開発 』という記事(2007年9月7日付)を書いておられます。

InterBase+DBExpressでのマスター/詳細のサンプルになっています。

[Think IT]
こんなに簡単!Delphiでデータベースアプリケーション開発
http://www.thinkit.co.jp/category/dev.html

|

■Team Japan ブログ開設!

スタッフ個人毎のブログではなく、Team Japanという一つのブログに複数の日本人スタッフが投稿するというスタイル らしいです。

『CodeGearの日本人スタッフによるブログ』というサブタイトルがありますが、今時、『日本人』という限定は いかがなものでしょうか。 社内に日本人以外、日本語ができず、結果として日本人スタッフになったとしても 『日本人』ではなく『日本語によるブログ』でいいんじゃないかと私は思います。
と、いきなり批判めいたことを書いてしまいましたが、記事の方、楽しみにしています。


CodeGear

Team Japan
CodeGearの日本人スタッフによるブログ
http://blogs.codegear.com/teamj/

|

☆USBの取付(取り外し)時にイベントを発生させる。

下記サイトにUSBの取付(取り外し)時にイベントを発生させ、DeviceDriverの名前等を 表示させるサンプルがあります。処理としては、WMDeviceChangeというメッセージを監視してイベントを発生させ、各情報はレジストリから取得するようですね。


Delphi3000.com
Detect USB Insert-Remove with Device and Friendly Names Returned
http://www.delphi3000.com/article.asp?id=4841

|

☆カーソル下のテキストを選択する。

☆メモ上でマウス下のテキスト取得で使った GetWordAndPhrase名前空間を使い、エディットコントロールでカーソル下のテキストを選択させます。

実行させるとこんな感じです。

Undermouse


ここで問題になるのが、TEdit、TMemoは「ランタイムテーマの影響を受けてしまう」ことです。 TRichEditは、ランタイムテーマの影響を受けず、常にバイト数で扱いますが、TEdit、TMemoは ランタイムテーマが有効時-文字数、無効時-バイト数となります。 今回、ランタイムテーマが有効、無効にかかわらず、バイト数で扱う処理を考えてみます。
uses
 GetWordAndPhrase, AdjustedEdit;

procedure TForm1.Edit1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  CursolPos, sp, ep: Integer;
  S: String;
begin
  Exit;
  CursolPos := LoWord(Edit1.Perform(EM_CHARFROMPOS, 0, MakeLParam(X, Y)));
  CursolPos := GetSelStart_Byte(Edit1.Text, CursolPos);
  S := GetWordAndPhraseUnderMouse(CursolPos, Edit1.Text, sp, ep);
  if S <> ''then
  begin
    if GetSelStart(Edit1) <> sp then
    begin
      SetSelStart(Edit1, sp);
      SetSelLength(Edit1, ep-sp+1);
    end;
  end;
  if ActiveControl <> Edit1 then
    ActiveControl := Edit1;
end;

procedure TForm1.Memo1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  CursolPos, sp, ep: Integer;
  S: String;
begin
  CursolPos := LoWord(Memo1.Perform(EM_CHARFROMPOS, 0, MakeLParam(X, Y)));
  CursolPos := GetSelStart_Byte(Memo1.Text, CursolPos);
  S := GetWordAndPhraseUnderMouse(CursolPos, Memo1.Text, sp, ep);
  if S <> ''then
  begin
    if GetSelStart(Memo1) <> sp then
    begin
      SetSelStart(Memo1, sp);
      SetSelLength(Memo1, ep-sp+1);
    end;
  end;
  if ActiveControl <> Memo1 then
    ActiveControl := Memo1;
end;

procedure TForm1.RichEdit1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  CursolPos, sp, ep: Integer;
  S: String;
  Pt:TPoint;
begin
  Pt := Point(X,Y);
  CursolPos := RichEdit1.Perform(EM_CHARFROMPOS, 0, Integer(@Pt));
  S := GetWordAndPhraseUnderMouse(CursolPos, RichEdit1.Text, sp, ep);
  if S <> ''then
  begin
    if RichEdit1.SelStart <> sp then
    begin
      RichEdit1.SelStart := sp;
      RichEdit1.SelLength := ep-sp+1;
    end;
  end;
  if ActiveControl <> RichEdit1 then
    ActiveControl := RichEdit1;
end;


ランタイムテーマの設定にかかわらず、バイト単位で扱うための処理を集めた名前空間 AdjustedEdit (名前が変なのは無視して~)
unit AdjustedEdit;

{ ランタイムテーマの設定にかかわらず、バイト単位で扱います。 }

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  StdCtrls, Buttons, ExtCtrls, Themes;

  // SelTextを取得します。
  function GetSelText(CustomEdit: TCustomEdit): String;

  // SelStartをバイト数で設定します。
  procedure SetSelStart(Edit: TCustomEdit; Position_Byte: Integer);

  // SelStartをバイト数で取得します。
  function GetSelStart(Edit: TCustomEdit): Integer;

  // SelLengthをバイト数で設定します。
  procedure SetSelLength(Edit: TCustomEdit; Length_Byte: Integer);

  // SelLengthをバイト数で取得します。
  function GetSelLength(Edit: TCustomEdit): Integer;

  // 文字数で指定された位置のバイト数を返します。
  function GetSelStart_Byte(S: WideString; Position_Moji: Integer): Integer;

implementation

// 指定位置のバイト数分の文字数を返します。
function GetMoji(S: String; Position: Integer): Integer;
var
  P: PChar;
  I: Integer;
  Check: String;
begin
  if not ThemeServices.ThemesEnabled then
  begin
    Result := Position;
    Exit;
  end;

  Check := Copy(S, 1, Position);
  P := PChar(Check+#0);
  I := 0;
  while (P^ <> #0) do
  begin
    if IsDBCSLeadByte(Byte(P^)) then
      Inc(P,2)
    else
      Inc(P);
    Inc(I);
  end;
  Result := I;
end;

// SelTextを取得します。
function GetSelText(CustomEdit: TCustomEdit): String;
var
  S, Str: WideString;
  I, Start, Len: Integer;
begin
  if not ThemeServices.ThemesEnabled then
  begin
    Result := CustomEdit.SelText;
    Exit;
  end;

  S := WideString(CustomEdit.Text);
  Start := CustomEdit.SelStart + 1;
  Len := Start + CustomEdit.SelLength -1;

  Str := '';
  for I := Start to Len do
    Str := Str + S[I];
  Result := Str;
end;

// SelStartをバイト数で設定します。
procedure SetSelStart(Edit: TCustomEdit; Position_Byte: Integer);
begin
  Edit.SelStart := GetMoji(Edit.Text, Position_Byte);
end;

// SelStartをバイト数で取得します。
function GetSelStart(Edit: TCustomEdit): Integer;
begin
  Result := GetSelStart_Byte(Edit.Text, Edit.SelStart);
end;

// SelLengthをバイト数で設定します。
procedure SetSelLength(Edit: TCustomEdit; Length_Byte: Integer);
var
  I,J: Integer;
begin
  I := GetSelStart(Edit);
  if I = 0 then I := 1;
  J := GetMoji(Edit.Text, I + Length_Byte);
  Edit.SelLength := J-GetMoji(Edit.Text,I);
end;

// SelLengthをバイト数で取得します。
function GetSelLength(Edit: TCustomEdit): Integer;
begin
  Result := Length(GetSelText(Edit));
end;

// 文字数で指定された位置のバイト数を返します。
function GetSelStart_Byte(S: WideString; Position_Moji: Integer): Integer;
var
  Check: String;
begin
  if not ThemeServices.ThemesEnabled then
    Result := Position_Moji
  else
  begin
    Check := Copy(S, 1, Position_Moji);
    Result := Length(Check);
  end;
end;

end.

上記、名前空間にSetSelTextという処理がないのは、次の処理がランタイムの設定に関係なく正常に動作するからです。

  Edit1.Seltext := 'ややこしい!';

SelTextを表示させたら、でたらめなのに、この処理が正常に動作する理由がわかりません。

こんなややこしい処理で悩むのなら、TMemoだけでなく、TEditもTRichEditを次の設定にして代用した方が簡単なのかも知れませんね。

  RichEdit1.WantReturns := False;
  RichEdit1.WordWrap := False;

|

« 2007年9月 | トップページ | 2007年11月 »