TDateTimePickerのFormatプロパティで日時の書式を設定する

Delphi XE2/C++Builder XE2からTDateTimePickerにFormatプロパティが追加されたようです。

Formatプロパティで日時の書式を設定することができます。

DateTimePicker1->Format = L"yyyy年MM月dd日";

TDateTimePicker

C++Builder XE以前ではDateTime_SetFormat関数を使います。

DateTime_SetFormat(DateTimePicker1->Handle, L"yyyy年MM月dd日");

MemoコンポーネントとRichEditコンポーネントのカーソル関連の処理

先日、某掲示板の質問に回答するためにMemoコンポーネントとRichEditコンポーネント
のカーソル関連の処理を調べたので、簡単にまとめます。

選択している文字

SelStartプロパティは選択されている文字の位置を示します。

SelLengthプロパティは選択している文字の数を示します。

SelTextプロパティは選択している文字を示します。

//選択状態を設定する
Memo1->SelStart = 2;
Memo1->SelLength = 4;

//選択している文字の情報を取得する
ShowMessage(IntToStr(Memo1->SelStart) + "文字目から" + IntToStr(Memo1->SelLength) + "文字選択しています");
ShowMessage("選択している文字は「" + Memo1->SelText + "」です");

現在のカーソル位置の行番号を取得する

カーソルがある行の行番号を取得するにはEM_LINEFROMCHARメッセージを使用します。
行番号は0から始まります。

int currentRowNumber = Memo1->Perform(EM_LINEFROMCHAR, -1, 0);
ShowMessage(IntToStr(currentRowNumber));

可視領域の一番上の行を取得する

可視領域の一番上の行を取得するにはEM_GETFIRSTVISIBLELINEメッセージを使用します。
行番号は0から始まります。

int row = RichEdit1->Perform(EM_GETFIRSTVISIBLELINE, 0, 0);
ShowMessage("可視領域の先頭行は「" + IntToStr(row) + "」行目です");

スクロールする

一行下にスクロールする

Memo1->Perform(EM_SCROLL, SB_LINEDOWN, 0);

一行上にスクロールする

Memo1->Perform(EM_SCROLL, SB_LINEUP, 0);

一ページ下にスクロールする

Memo1->Perform(EM_SCROLL, SB_PAGEDOWN, 0);

一ページ上にスクロールする

Memo1->Perform(EM_SCROLL, SB_PAGEUP, 0);

指定した行数だけスクロールする

int row = 移動する行数(負数なら上に、正数なら下にスクロール);
Memo1->Perform(EM_LINESCROLL, 0, row);

C++Builder XE3でSQLiteを使ってみた。

RAD Studio XE3 Hotfix 3によってProfessional editionでSQLiteが使えるようになったので、試してみました。

Pythonでサンプルのデータベースファイルを作成します。
作成したデータベースファイルは「C:\test\test.db」に配置しました。

#!python2.7
# -*- coding: utf-8 -*-
import sqlite3

con = sqlite3.connect(u"test.db", isolation_level=None)
con.execute(u"create table example (id integer, name varchar(20))")
con.executemany(u"insert into example values (?, ?)",
        [(1, u"Delphi XE3"), (2, u"C++Builder XE3"), (3,u"HTML5 Builder"),
         (4, u"デルファイ"), (5, u"シープラスプラス")])

c = con.cursor()
c.execute(u"select* from example")
for row in c:
    print row[0], row[1]
con.close()

チュートリアル:SQLite データベースに接続する(Delphi)の手順にしたがって操作します。

SQLConnection1のDriverプロパティを編集するときに「Driver/Connection レジストリ
ファイル ‘C:\~\dbxconnections.ini’ が見つかりません。」というエラーメッセー
ジが表示される場合は、指定された場所に空のdbxconnections.iniを作成するといいようです。

実行時に「sqlite3.dllが見つかりません。」というエラーメッセージが表示される場
合は、SQLiteのページからsqlite3.dllをダウンロードして、パスの通った場所に配置します。

実行時の画面

C++Builder XE3で記述したソースコード

void __fastcall TForm1::connectButtonClick(TObject *Sender)
{
  SQLConnection1->ConnectionName = "C:\\test\\test.db";
  SQLConnection1->Params->Add("Database=C:\\test\\test.db");
  try {
    SQLConnection1->Connected = true;
    executeButton->Enabled = true;
    outputMemo->Text = "Connection established!";
  } catch (EDatabaseError& E) {
    ShowMessage("Exception raised with message: " + E.Message);
  }
}
void __fastcall TForm1::executeButtonClick(TObject *Sender)
{
  outputMemo->Clear();
  UnicodeString query = "SELECT * FROM example;";
  TDataSet* results;
  try {
    SQLConnection1->Execute(query, NULL, results);
  } catch (Exception& E) {
    outputMemo->Text = "Exception raised with message: " + E.Message;
  }
  ShowSelectResults(results);
}
void TForm1::ShowSelectResults(TDataSet* results)
{
  if (!results->IsEmpty()) {
    TStringList* names = new TStringList();
    results->GetFieldNames(names);
    for (results->First(); !results->Eof; results->Next()) {
      UnicodeString currentLine;
      for (int i = 0; i < names->Count; ++i) {
        TField* currentField = results->FieldByName(names->Strings[i]);
        currentLine = currentLine + " " + currentField->AsString;
      }
      outputMemo->Lines->Add(currentLine);
    }
    delete names;
  }
}

Delphiで数値の形式を国際化に対応する

Windowsでは地域の設定によって小数点や桁区切りの記号が異なります。
また利用者が自由に記号を変更することも可能です。

アプリケーションを国際化に対応する場合は、数値の形式を意識する必要があります。

先日、私が対応したときの方法を紹介します。
ポイントはFormat関数やStrToFloat関数などを使用するとき、引数にTFormatSettingsを与えることです。

アプリケーションでは、フォームに表示する時はコントロールパネルの「地域の設定」で設定されている小数点記号と桁区切り記号を使用し、ファイルに保存する時には小数点記号「.」桁区切り記号「,」とすることにしました。

type
  TForm1 = class(TForm)
  private
    /// <summary>コントロールパネルの「地域と言語の設定のオプション」の設定</summary>
    /// <remarks>フォームへの入出力に使用する</remarks>
    FLocalSettings: SysUtils.TFormatSettings;

    /// <summary>小数点記号「.」桁区切り記号「,」の設定</summary>
    /// <remarks>ファイルへの入出力に使用する</remarks>
    FGlobalSettings: SysUtils.TFormatSettings;
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  //コントロールパネルの「地域と言語の設定のオプション」の設定
  FLocalSettings := TFormatSettings.Create;

  //小数点記号「.」桁区切り記号「,」の設定
  FGlobalSettings := TFormatSettings.Create;
  FGlobalSettings.DecimalSeparator := '.';  //小数点記号
  FGlobalSettings.ThousandSeparator := ','; //桁区切り記号
end;

フォームに入力された数字を数値に変換する時には、StrToFloat関数を使用します。
このとき第二引数を指定し、コントロールパネルの「地域の設定」の書式で変換します。

var
  Value: double;
begin
   //コントロールパネルの「地域の設定」の書式で変換する。
  Value := StrToFloat(Edit1.Text, FLocalSettings);

数値を画面に表示する時はFormat関数を使用します。
このときに、コントロールパネルの「地域の設定」の書式で変換します。

Label1.Caption := Format('%n', [Value], FLocalSettings);

ファイルに保存する時には、小数点記号「.」桁区切り記号「,」で保存します。

S := Format('%n', [Value], FGlobalSettings);

ファイルから読み込む時も同じ要領になります。

次のサンプルプログラムでは、入力された値をEdit1に入力された値を、Label1にはコントロールパネルの「地域」設定で、Label2には小数点記号「.」桁区切り記号「,」で出力しています。
なお、コントロールパネルの「地域」の設定では、小数点の記号を「#」、桁区切り記号を「%」に変更しています。

procedure TForm1.Edit1Exit(Sender: TObject);
var
  Value: double;
begin
  Value := StrToFloat(Edit1.Text, FLocalSettings);

  Label1.Caption := Format('%n', [Value], FLocalSettings);
  Label2.Caption := Format('%n', [Value], FGlobalSettings);
end;

Delphi XEの「リソース DLL の動的な切り替え」のコードを実行するには

Delphi XEのヘルプ「リソース DLL の動的な切り替え」には次のようなコードが紹介されている。

const
  FRENCH = (SUBLANG_FRENCH shl 10) or LANG_FRENCH;
if LoadNewResourceModule(FRENCH) <> 0 then
  ReinitializeForms;

このコードをコピーしただけでは動作しない。
このコードを実行するにはユニットをusesに追加する必要がある。

uses reinit

これだけでは動作しない。
reinitユニットが見つからないためだ。

このreinitはDemosディレクトリ下のRichEditサンプルに含まれている。
RichEditサンプルに含まれているreinit.pasをプロジェクトに登録すると動作するようになる。

└RAD Studio
 └8.0
  └Samples
   └Delphi
    └VCL
     └RichEdit
      └reinit.pas

このユニットについての説明は、Delphi 2010まではヘルプに掲載されていたようだ。
(Delphi 2010のヘルプリソース DLL の動的な切り替え)

DelphiやC++Builderで、TPageControl上にTPageControlを配置した時の背景色の不思議な動作

TPageControl上にTPageControlを配置すると、TPageControl上のTPageControlのタブの右側の色が背景色にならない。

TPageControl上にTPanelを配置し、その上にTPageControlを配置すると、背景色で表示される。

腑に落ちない挙動であるが、問題は回避できた。

DelphiでRAII その2

Delphiのインターフェースを使うと、参照カウントが0になったオブジェクトは自動で破棄されます。
この機能を使って、変数がスコープを離れた時にリソースを返却するRAIIを実現できます。

今回はスコープを離れたら自動的に削除される一時ファイルを作成します。

使用例

procedure TForm1.Button1Click(Sender: TObject);
var
  TempFile: ITempFile;  //一時ファイルオブジェクト
  FileName: String; //一時ファイルのファイル名
begin
  TempFile := CreateTempFile; //一時ファイルを作成する
  FileName := TempFile.GetFileName; //一時ファイルのファイル名を取得する
  DoSomething(FileName); //一時ファイルを使用する
end;  //スコープを離れたら一時ファイルは削除される

実装方法は次のようになります。

type
  /// <summary>一時ファイルを管理するRAIIのインターフェース</summary>
  ITempFile = interface
    /// <summary>一時ファイルのファイル名を取得する</summary>
    function GetFileName: String;
  end;

  /// <summary>RAIIのオブジェクトを取得する</summary>
  /// <returns>RAIIのオブジェクト。実体はTTempFileクラス</returns>
  function CreateTempFile():ITempFile;

implementation

type
  /// <summary>ITempFileの実装。このクラスは公開しない。</summary>
  TTempFile = class(TInterfacedObject, ITempFile)
  private
    FFileName: String; //一時ファイルのファイル名
  public
    constructor Create();
    destructor Destroy; override;
    /// <summary>一時ファイルのファイル名を取得する</summary>
    function GetFileName: String;
  end;

function CreateTempFile():ITempFile;
begin
  Result := TTempFile.Create();
end;

constructor TTempFile.Create;
begin
  //一時ファイルを作成してファイル名を取得する
  FFileName := IOUtils.TPath.GetTempFileName;
end;

destructor TTempFile.Destroy;
begin
  try
    IOUtils.TFile.Delete(FFileName)
  except on E: Exception do
    //削除に失敗した時は例外を無視する
  end;
  inherited;
end;

function TTempFile.GetFileName: String;
begin
  Result := FFileName;
end;

なかなか便利ではないかと思いますが、いかがでしょうか。

DelphiでRAII

Delphiのインターフェースを使うと、参照カウントが0になったオブジェクトは自動で破棄されます。
この機能を使って、変数がスコープを離れた時にリソースを返却するRAIIを実現できます。

次のサンプルプログラムは、ボタンを押した時に時間のかかる処理を行います。
処理中はカーソルを砂時計にして、処理が終わったらカーソルを元に戻します。

procedure TForm1.Button1Click(Sender: TObject);
var
  Save_Cursor: TCursor;
begin
  Save_Cursor := Screen.Cursor; //現在のカール祖を保存
  Screen.Cursor := crHourGlass; //カーソルを砂時計に変更
  try
    Sleep(5000);  //時間のかかる処理
  finally
    Screen.Cursor := Save_Cursor; //カーソルを元に戻す
  end;
end;

RAIIを使うと、次のようなコードを書くことができます。
コードがずいぶんと見やすくなります。

procedure TForm1.Button2Click(Sender: TObject);
var
  Cursor: IRAIICursor;
begin
  Cursor := CreateRAIICursor(crHourGlass); //カーソルを砂時計に変更
  Sleep(5000);  //時間のかかる処理
end;  //スコープを離れるとカーソルを元に戻す

実装方法は次のようになります。

type
  /// <summary>カーソルを管理するRAIIのインターフェース</summary>
  IRAIICursor = interface
  end;

  /// <summary>RAIIのオブジェクトを取得する</summary>
  /// <param name="Cursor">変更するカーソル</param>
  /// <returns>RAIIのオブジェクト。実体はTRAIICursorクラス</returns>
  function CreateRAIICursor(Cursor: TCursor):IRAIICursor;

implementation

type
  /// <summary>IRAIICursorの実装。このクラスは公開しない。</summary>
  TRAIICursor = class(TInterfacedObject, IRAIICursor)
  private
    FCursor: TCursor; //変更前のカーソル
  public
    constructor Create(Cursor: TCursor);
    destructor Destroy; override;
  end;

constructor TRAIICursor.Create(Cursor: TCursor);
begin
  FCursor := Screen.Cursor;
  Screen.Cursor := Cursor;
end;

destructor TRAIICursor.Destroy;
begin
  Screen.Cursor := FCursor; //カーソルを元に戻す
end;

function CreateRAIICursor(Cursor: TCursor):IRAIICursor;
begin
  Result := TRAIICursor.Create(Cursor);
end;

なかなか便利ではないかと思いますが、いかがでしょうか。

MDBファイルを最適化する

MDAC 2.1からJROでMDBを最適化できるようになっていたようだ。

Delphi XEを使って、次のコードでMDBファイルを最適化できた。

uses ComObj;

/// <summary>MDBファイルを最適化する</summary>
/// <param name="SourceMdbPath">最適化するMDBファイルのバス</param>
/// <param name="SourceMdbPassword">最適化するMDBファイルのパスワード</param>
/// <param name="TargetMdbPath">最適化したMDBファイルのバス</param>
/// <param name="TargetMdbPassword">最適化したMDBファイルのパスワード</param>
procedure CompactMdb(
  SourceMdbPath, SourceMdbPassword,
  TargetMdbPath, TargetMdbPassword: String);
const
  SOURCE_PARAM = 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=%s;Jet OLEDB:Database Password=%s';
  TARGET_PARAM = 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=%s;Jet OLEDB:Engine Type=%d;Jet OLEDB:Database Password=%s';
  ENGINE_TYPE = 5; //Jet OLEDB:Engineの種類 
var
  Engine: OleVariant;
  SourceStr, TargetStr: String;
begin
  SourceStr := Format(SOURCE_PARAM, [SourceMdbPath, SourceMdbPassword]);
  TargetStr := Format(TARGET_PARAM, [TargetMdbPath, ENGINE_TYPE, TargetMdbPassword]);
  Engine := ComObj.CreateOleObject('JRO.JetEngine');
  Engine.CompactDatabase(SourceStr, TargetStr);
end;

//C:\mdb\old.mdbを最適化したファイルをC:\mdb\new.mdbに作成する
//C:\mdb\old.mdbのパスワードは'pass1'
//C:\mdb\new.mdbのパスワードは'pass2'
CompactMdb('C:\mdb\old.mdb', 'pass1','C:\mdb\new.mdb', 'pass2');

参考

Delphi/C++Builderでアプリケーションのデフォルトフォントを設定する

Delphi 2009 handbook―Delphi最新プログラミングエッセンス』を読み返していたら、アプリケーションのデフォルトフォントを設定する方法を発見しました。

Delphi 2009からApplication.DefaultFontでアプリケーションのデフォルトフォントを設定することができます。

Application.DefaultFontの値を変更すると、ParentFontプロパティがtrueに設定されてているフォームと、そのフォームのコンポーネントのフォントがDefaultFontで設定したフォントに変更されます。

サンプルプログラムで動作を確認してみました。

フォームのParentFontプロパティの値をtrueに変更します。

ボタンを押すとフォント選択ダイアログを表示します。
選択されたフォントをデフォルトフォントに設定します。

procedure TForm1.Button1Click(Sender: TObject);
begin
  FontDialog1.Font := Application.DefaultFont;
  if FontDialog1.Execute() then
    Application.DefaultFont := FontDialog1.Font;
end;

実行します。

フォントを変更します。

そうすると、フォーム上のコンポーネントのフォントが変更されました。
TMainMenuコンポーネントにはParentFontプロパティがないので、フォントが変更されないようです。

メインフォームだけではなく、すべてのフォームのフォントが変更されます。