自作コンポーネントのパレットの Bitmapを変更したい(98/10/05)
Q:
自作コンポーネントのパレットの Bitmapを変更したい
A:
デフォルトのビットマップではなく独自のビットマップを表示するには、コンポーネントリソースファイル(*.dcr)を作成します。
その際、リソースファイル名はユニット名に、リソースファイル内のビットマップの名前はコンポーネント(クラス)名にそれぞれ合わせます。
以下は、MyTool.cppユニットで TMyLabelコンポーネントを作成した場合の例です。
イメージエディタから[ファイル|新規作成]で Delphiコンポーネントリソースファイル(.dcr)を作成します。
リソースに 24x24ピクセルの「ビットマップ」を追加し、名前を TMYLABELとします。(ビットマップの名前は大文字にします。)
+-Contents
+-Bitmap
+-TMYLABEL
編集が終わったら、リソースファイルを MyTool.DCRという名前でユニットファイルがある場所と同じディレクトリに保存します。
コンポーネントをインストールすると、コンポーネントパレットにリソースファイルで指定したビットマップが表示されます。
TNMSMTP コンポーネントでメッセージヘッダーを変更する方法(98/10/05)
該当するバージョン:C++Builder 3
Q:
NMSMTPの FinalHeaderプロパティで"Content-Type"を設定していますが、"text/plain; charset=us-ascii"で送信されます。
A:
FinalHeaderプロパティは、OnSendStartイベントで変更する必要があります。
void __fastcall TForm1::NMSMTP1SendStart(TObject *Sender)
{
NMSMTP1->FinalHeader->Values["Content-Type"] =
"text/plain; charset=\"ISO-2022-JP\"";
}
VCLオブジェクトを動的に生成したが、フォームに表示されません(98/08/03)
Q:
VCLオブジェクトを動的に生成したが、フォームに表示されません。
A:
親コントロール内に表示されるように"Parent"プロパティを設定する必要があります。
例
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TButton *ButtonX = new TButton(this);
ButtonX->Caption = "new botton";
ButtonX->Parent = this;
}
ステータスバーのパネル(StatusBar->Panels)に文字列を設定したい(98/08/03)
Q:
ステータスバーのパネル(StatusBar->Panels)に文字を設定したい
A:
StatusBar->Panels->Itemsプロパティを使用します。
例
void __fastcall TForm1::Button1Click(TObject *Sender)
{
StatusBar1->Panels->Items[0]->Text = "Panel1";
StatusBar1->Panels->Items[1]->Text = "Panel2";
StatusBar1->Panels->Items[2]->Text = "Panel3";
StatusBar1->Panels->Items[3]->Text = "Panel4";
StatusBar1->Panels->Items[4]->Text = "Panel5";
}
ファイルのタイムスタンプを取得したい(98/08/03)
Q:
ファイルのタイムスタンプを取得したい
A:
FileGetDate()を使用し、指定されたファイルのタイムスタンプを取得します。
例
void __fastcall TForm1::Button1Click(TObject *Sender)
{
int FHandle;
AnsiString s;
FHandle = FileOpen( "c:\\autoexec.bat", fmOpenRead );
s = DateTimeToStr( FileDateToDateTime(FileGetDate(FHandle)) );
FileClose( FHandle );
ShowMessage( s );
}
dBASE7形式のファイルが DataBaseDeskTopで作成できない(98/08/03)
該当するバージョン:C++Builder 3,C++Builder 4
Q:
dBASE7形式のファイルが DataBaseDeskTopで作成できません。
A:
DataBaseDeskTopは、Paradox7相当の製品ですので dBASE7のファイル形式を理解できず、作成することはできません。
ただし、以下のように ixExpressinを付けてキーを作成していただくと作成可能です。
Table1->Active = false; // テーブルコンポーネントを非アクティブにする
// テーブルの型と名前を定義する
Table1->DatabaseName = "BCDEMOS";
Table1->TableType = ttDBase;
Table1->TableName = "CustInfo";
// テーブル内にフィールドを記述する
Table1->FieldDefs->Clear();
Table1->FieldDefs->Add("Field1", ftInteger, 0, True);
Table1->FieldDefs->Add("Field2", ftString, 30, False);
// インデックスを記述する
Table1->IndexDefs->Clear();
Table1->IndexDefs->Add("Tes","Field1+Field2",
TIndexOptions() <<ixPrimary << ixUnique << ixExpression );
// 必要な内容を指定したので、テーブルを作成する
Table1->CreateTable();
作成したコンポーネントが登録されません(98/08/03)
該当するバージョン:C++Builder 3
Q:
作成したコンポーネントが登録されません。
A:
インラインアセンブラをコンポーネントを作成時に使用しますと、コンポーネントパレットに表示できません。
動的に、そのコンポーネントを生成するとインスタンスは生成されますので、ライブラリーにはマージされているようです。
また、別の関数(C言語ライク)を定義して、その中でインラインアセンブラを使用し、その関数をコンポーネントから使用するようにすると間接的に使用可能です。
TBitmapの PixelFormatを pf24bitを使用してマスクをかけると縞模様になる。(98/08/03)
該当するバージョン:C++Builder 3
Q:
TBitmapの PixelFormatを pf24bitを使用してマスクをかけると縞模様になる。
A:
黒の縞模様が出てしまう原因は、24Bitの領域に 32Bitずつアクセスしてしまうため 8Bit分縞模様になってしまいます。
24Bitずつアクセスしていただければ問題ありません。
例
// Cardinal は、unsigned int と同じ
// Cardinal は 32Bit の型ですのでビットフィールドを使用して
// 24Bit の型を作成します。
// pragma pack(1) は、アライメントを 1 に変えます。
// デフォルトのアライメントは 4 で 32Bit 確保されますので
// 1 に変えることによって 8Bit ずつ並べられます。
#pragma pack(1)
struct Bit24
{
Cardinal ptr:24;
};
#pragma pack()
//-----------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//-----------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Graphics::TBitmap *bmp = new Graphics::TBitmap();
bmp->LoadFromFile("chemical.bmp");
bmp->PixelFormat = pf24bit;
for (int y = 0; y < bmp->Height; y++)
{
// 先程定義した Bit24 を使用する
Bit24 *ptr = (Bit24 *)bmp->ScanLine[y];
for (int x = 0; x < bmp->Width; x++)
{
//赤でマスクします。
ptr[x].ptr = ptr[x].ptr & 0xff0000;
}
}
Image1->Picture->Assign( bmp );
delete bmp;
}
//-----------------------------------------------------------------
Internetコンポーネントパレットの NM関連のコンポーネントを使用できません。(98/08/03)
該当するバージョン:C++Builder 3,C++Builder 4
Q:
Internetコンポーネントパレットの NM関連のコンポーネントを使用できません。
A:
NMコンポーネントを使用して Unknown Error No. 10093.のエラーメッセージが表示されてしまうのは Winsock関連のエラーです。
Winsockを使用するには TCP/IPの設定をご使用のマシンに設定しておく必要があります。
[コントロールパネル|ネットワーク] で TCP/IPの設定を行って下さい。
Internet関連のコンポーネントを使用するには TCP/IPの設定が必要です。
ActiveXの関数に AnsiString型を渡すことができません。(98/08/03)
該当するバージョン:C++Builder 3
Q:
ActiveXの関数に AnsiString型を渡すことができません。
A:
ActiveXに文字列で使用されている文字列は WideStringです。
Delphiでは、暗黙的に変換してくれますが、C++Builderでは変換してくれませんので、キャストが必要です。
例えば TComboBoxで ActiveXコンポーネント TComboBoxXProxyを作成したとします。
使用する時に以下の様にして下さい。
例
ComboBoxXProxy1->Items->Add( Variant("Item"), 1 );
ActiveXコンポーネントに渡す文字列は WideString型でなければいけませんので、文字列を WideStringか Variantでキャストして下さい。
AnsiString型が iostreamで使用できません。(98/08/03)
該当するバージョン:C++Builder 3,C++Builder 4
Q:
AnsiString型が iostreamで使用できません。
A:
C++Builder3.0の iostreamライブラリは Rogue Wave Software社の提供のものを使用しているため、VCLとの互換性が薄くなりました。
Readme.hlpに記述しているように VCL_IOSTREAMを記述しても Undefind structure 'IDispatch'エラーが表示されます。
AnsiStringクラスの文字列をストリームに出力するには、以下のように char * にして出力するようにして下さい。
ソースの変更が必要になります。
ofstream fout( "c.txt" );
fout << Edit1->Text.c_str();
追加情報
AnsiStringクラスを直接、ストリームに出力する方法は VCL_IOSTREAMを定義せず、iostream.hを vcl.hよりも上に記述していただきますとそのまま AnsiStringクラスを出力できます。どうしても AnsiStringクラスをストリームに使用したい場合には有効です。
プログラムから ComboBoxのドロップダウンリストを表示したい(98/11/2)
Q:
プログラムから ComboBoxのドロップダウンリストを表示したい。
A:
TComboBoxのプロパティ DroppedDownを使用します。
void __fastcall TForm1::Button1Click(TObject *Sender)
{
// ドロップダウンリストを表示
ComboBox1->DroppedDown = true;
}
日付のフォーマット形式を変更したい(98/08/03)
Q:
日付のフォーマット形式を変更したい
A:
ShortDateFormatで、日付のフォーマットを変更することができます。
使用例
// "1998/04/01" のフォーマットの場合
ShortDateFormat = "yyyy/mm/dd";
// "98-4-1" のフォーマットの場合
ShortDateFormat = "yy-m-d";
また、VCLには ShortDateFormatの他に、以下のような型付き定数が用意されています。
LongDateFormat
TimeSeparator
TimeAMString
TimePMString
ShortTimeFormat
LongTimeFormat
SQLで Not(文字列項目 Like '%AB%')で selectをかけた場合 NULLの項目が表示されません(98/08/03)
Q:
SQLで Not(文字列項目 Like '%AB%')で selectをかけた場合 NULLの項目が表示されません
A:
SQL上では NULLの扱いは特別になっています。
NULLの定義として、NULL値とは全てが 0であるとか、全てがブランクであるという意味ではなく、値が未知であるということになっています。
そこで Not(文字列項目 Like '%AB%')では、まず始めに括弧の中が解釈されます。
括弧の中では ABの文字列を含む文字列項目を対象としています。
そこに Notを付けると ABを含まない文字列項目を対象としています。
NULLは文字列ではないため、この条件には合致しないため検索対象にはなりません。
また Not(文字列項目 Like '%AB%')の条件で NULL値も検索させるときは以下の様にして下さい。
where Not(文字列項目 Like '%AB%') or 文字列項目 is null
heapcheck()関数を使用するとリンクエラーが発生します。(98/08/03)
Q:
heapcheck()関数を使用するとリンクエラーが発生します。
A:
C++Builderでは、VCLとの親和性を図るため、従来の Borland C++のライブラリとは違ったメモリ管理を行なっています。
例えば、同じ malloc()関数を呼び出しても実装は違うことになります。
そのために heapcheck(), heapwalk()や heapset()などが使用できなくなりました。
"プリンターを初期化できません"のエラーが発生(98/08/03)
Q:
"プリンターを初期化できません"のエラーが発生してしまいます。
A:
通常はこのようなエラーは発生しませんが、強制的にプリンターを初期化すると回避可能です。
#include <printers.hpp>
__(途中省略)__
char p_name[256],
p_drv[256],
p_port[256];
THandle devm;
TDevMode *dmw;
//プリンターの設定を変更
//GetPrinter は2回呼び出して下さい。
//2回呼び出さないとプリンタ情報を取得できません。
Printer()->GetPrinter( p_name, p_drv, p_port, devm );
Printer()->GetPrinter( p_name, p_drv, p_port, devm );
Printer()->SetPrinter( p_name, p_drv, p_port, devm );
__(以下省略)__
WM_KEYDOWNの lParamを使用したいのですが OnKeyDownでは取得できません。(98/08/03)
Q:
WM_KEYDOWNの lParamを使用したいのですが OnKeyDownでは取得できません。
A:
Applicationの OnMessageを使用すれば取得可能です。
OnMessageには、このアプリケーションで発生する全てのメッセージが取得可能です。
以下は例になります。
// ヘッダー側
//-----------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE 管理のコンポーネント
private: // ユーザー宣言
// この関数を新たに宣言します。
void __fastcall OnMessage( tagMSG &Msg, bool &Handled );
public: // ユーザー宣言
__fastcall TForm1(TComponent* Owner);
};
// ソース側
//-----------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
//Form1 の OnMessage を Application に割り当てます。
Application->OnMessage = OnMessage;
}
//-----------------------------------------------------------------
void __fastcall TForm1::OnMessage( tagMSG &Msg, bool &Handled )
{
if (Msg.message == WM_KEYDOWN )
{
ShowMessage("DOWN");
/***
if( Msg.lParam == (long)"XXX" ) //LPARAM を取得可能
{
}
***/
}
}
タイトルバーをクリックせずにフォームをドラッグしたい(98/08/03)
Q:
フォームのタイトルバーをクリックせずに、クライアント領域をクリックしてフォームをドラッグしたい。
A:
簡単な方法は、フォームのタイトルバーがクリックされたと Windowsに思わせることです。これは WM_NCHITTESTメッセージを処理して実現できます。
フローティングツールバーのようなタイトルもボーダーも無いウィンドウの場合は、フォームのタイトルを空の文字列に設定し、すべての BorderIconsを Falseに設定し、BorderStyleを bsNoneに設定します。
//ヘッダー側( Unit1.hpp )
//-----------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE 管理のコンポーネント
TButton *Button1;
void __fastcall Button1Click(TObject *Sender);
private: // ユーザー宣言
public: // ユーザー宣言
__fastcall TForm1(TComponent* Owner);
//追加するハンドラー
void __fastcall WMNCHitTest( TWMNCHitTest &Mes );
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER( WM_NCHITTEST, TWMNCHitTest, WMNCHitTest )
END_MESSAGE_MAP( TForm )
};
//ソース側( Unit1.cpp )
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TForm1::WMNCHitTest( TWMNCHitTest &Mes )
{
Mes.Result = HTCAPTION;
}
標準以外のカーソルを使用したい(98/07/01)
Q:
標準以外のカーソルを使用したい
A:
カーソルのハンドルを取得し、TScreenコンポーネントに割り当てます。
例
#define crMyCursor 1
void __fastcall TForm1::Button1Click(TObject *Sender)
{
HCURSOR hand;
hand = LoadCursorFromFile("c:\\windows\\cursors\\Hourglas.ani");
if (hand == NULL)
ShowMessage("Cursor not loaded.");
else
{
Screen->Cursors[crMyCursor] = hand;
Button1->Cursor = (Controls::TCursor)crMyCursor;
}
}
タスクバーからアプリケーションを非表示にするには(98/07/01)
Q:
タスクバーからアプリケーションを非表示にするには。
A:
SW_HIDEを与えます。以下の例をご参照ください。
void __fastcall TForm1::Button1Click(TObject *Sender)
{
ShowWindow(Application->Handle, SW_HIDE);
}
HTMLコンポーネントに、ローカルファイルをロードしたい(98/07/01)
Q:
HTMLコンポーネントにローカルファイルをロードしたい。
A:
RequestDocを使用します。以下の例をご参照ください。
void __fastcall TForm1::Button1Click(TObject *Sender)
{
HTML1->RequestDoc("file:///D:/sample.htm");
}
ラジオグループ(TRadioGroup)の特定の項目を使用不可したい(98/07/01)
Q:
ラジオグループ(TRadioGroup)の特定の項目を使用不可したい
A:
プロパティ"Controls"を使用します。
void __fastcall TForm1::Button1Click(TObject *Sender)
{
RadioGroup1->Controls[0]->Enabled = false;
}
指定した DBGridの項目の内容を取得したい
Q:
指定した DBGridの項目の内容を取得したい
A:
SelectedFieldプロパティ又は、SelectedIndexプロパティ等をご使用ください。以下に例を示します。
例: マウスでダブルクリックした DBGridの項目の内容を Edit1に表示する。
void __fastcall TForm1::DBGrid1DblClick(TObject *Sender)
{
Edit1->Text = DBGrid1->SelectedField->AsString;
}
Paradoxのインデックス名に関する仕様
Paradox テーブルでは、単一のインデックスの場合にはフィールド名と一致させなければなりません。また一次インデックスはインデックス名を持ちません。
したがって、連結インデックスや、デフォルト以外のインデックスに対してのみユニークなインデックス名を指定できます。
AppendIndex( "Index1", "Field1", TIndexOptions() );
(Delphi では AppendIndex( 'Index1', 'Field1', [] );)
のような記述は許されません。
テーブルのインデックスデータを取得したい
Q:
テーブルのインデックスデータを取得したい
A:
TTableの GetIndexNamesメソッドが使用できます。
GetIndexNamesメソッドは、Tableで使えるすべてのインデックスの名前を Listパラメータに追加します。
IndexDefsプロパティを使用すると、インデックス名、インデックスを構成する項目名、インデックス式、インデックスオプションなどのインンデックスについての情報を取得できます。
void __fastcall TForm1::Button1Click(TObject *Sender)
{
ListBox1->Clear();
Table1->GetIndexNames(ListBox1->Items);
}
IndexDefsオブジェクトの Countプロパティを基にしてループを行い、Table1に関するインデックス名を取得します。
void __fastcall TForm1::Button2Click(TObject *Sender)
{
TIndexDefs *p = Table1->IndexDefs;
ListBox2->Items->Clear();
p->Update();
if (p->Count > 0 )
for (int i=0;i<p->Count;i++)
ListBox2->Items->Add(p->Items[i]->Name);
}
以下の例では、インデックス名、インデックスを構成する項目名、インデックス式、インデックスオプションなどのインンデックスについての情報を TStringGridに表示します。
void __fastcall TForm1::FormShow(TObject *Sender)
{
TIndexDefs *p = Table1->IndexDefs;
AnsiString S;
Table1->Open();
// Refresh IndexDefs Object
p->Update();
if ( p->Count > 0 ) {
//Set up Colums and rows in grid to match IndexDefs items
StringGrid1->ColCount = 4;
StringGrid1->RowCount = p->Count + 1;
// Set grid column labels to TIndexDef property names
StringGrid1->Cells[0][0] = "インデックス名";
StringGrid1->ColWidths[0] = 150;
StringGrid1->Cells[1][0] = "項目名";
StringGrid1->ColWidths[1] = 150;
StringGrid1->Cells[2][0] = "式";
StringGrid1->ColWidths[2] = 150;
StringGrid1->Cells[3][0] = "オプション";
StringGrid1->ColWidths[3] = 200;
// Loop through IndexDefs.Items
for ( int i=0; i< p->Count; i++ ) {
// Fill grid cells for current row
StringGrid1->Cells[0][i+1] = p->Items[i]->Name;
StringGrid1->Cells[1][i+1] = p->Items[i]->Fields;
StringGrid1->Cells[2][i+1] = p->Items[i]->Expression;
if (p->Items[i]->Options * (TIndexOptions()<<ixPrimary)
!= TIndexOptions())
S = "ixPrimary, ";
if (p->Items[i]->Options * (TIndexOptions()<<ixUnique)
!= TIndexOptions())
S += "ixUnique, ";
if (p->Items[i]->Options * (TIndexOptions()<<ixDescending)
!= TIndexOptions())
S += "ixDescending, ";
if (p->Items[i]->Options * (TIndexOptions()<<ixCaseInsensitive)
!= TIndexOptions())
S += "ixCaseInsensitive, ";
if (p->Items[i]->Options * (TIndexOptions()<<ixExpression)
!= TIndexOptions())
S += "ixExpression, ";
if ( S > ' ' ) {
// Get rid of trailing ", "
S.Delete(S.Length() - 1, 2);
StringGrid1->Cells[3][i+1] = S;
}
}
}
}
[備考] ObjectPascalとの相違
- StringGridの Cellsプロパティの指定方法
- TIndexOptionsの集合型の中の要素のチェック方法
- Systemユニットの文字列に関する手続きと関数の実装方法
プロパティのデフォルトの値
Q:
__propertyの指定で defaultを使ってデフォルト値を設定したのですが、反映されていないようです。
A:
プロパティの設定をする際に使用する defaultは、統合環境がフォームファイルに保存するかどうかを判定するために使用します。(デフォルトの値から変更されていない場合には保存されません)実際に値をセットするのはコンストラクタで明示的に行なわなければなりません。
class TMyClass: public TComponent {
:
private:
bool FData;
:
__published:
__property bool Data = {read=FData, write=FData, stored=true, default=1};
:
};
TMyClass::TMyClass(TComponent *AOwner): TComponent( AOwner )
{
FData = true;
}
C++Builderでのクラス(コンポーネント)
C++ではクラス内のメンバのスコープを指定するのに private, protected, public があります。private部ではそのクラスのメンバからしか参照できないシンボルを定義します。(Delphiではそのモジュール内であればクラスの外でも参照出来ます。)
protected部では、そのクラス及び、そのクラスを派生したクラスからのみの参照を許します。publicは外部からの参照も許します。Delphiでは統合環境からデザイン時にプロパティーを設定できるようにするために publishedという予約語を導入しています。C++Builderでは __publishedを導入して対応しています。__published部に定義されたプロパティは、統合環境でデザイン時にオブジェクトインスペクタで編集が出来るようになります。VCLにはプロパティの概念が実装されているために C++Builderでもこれに代わる __propertyが導入されています。__propertyの記述は次のとおりです。
__property型 変数名 [={[read=xxx,][write=xxx,][stored=xxx,][default=xxx]]}];
変数名には配列を指定する事もできます。read, 及び writeはプロパティに対する読み込みか書き込みに対して参照される関数又は変数を指定します。storedには false, trueまたは論理型を返す関数を指定し、コンポーネントとして保存するかどうかを表します。defaultにはデフォルトの値を指定します。default=の代わりに nodefaultを指定することも出来ます。
注意
default指定は、統合環境がフォームファイルにコンポーネントの内容を保存するかどうかを認識するために用意されています。ですから実際にデフォルトの値を指定するのはそのクラスのコンストラクタで行なわなければなりません。
また、OLEオートメーションのために __automatedが導入されています。このセクションに登録されたメンバにはオートメーションに関する情報が生成されます。
次の例は func関数をディスパッチ IDを 1000として関連付けます。
__automated:
void __fastcall func(void) __dispid(1000);
Delphiの stringと C++Builderの AnsiString
C++Builderでは Delphiで使用されている string型を AnsiStringクラスとして実装しています。
AnsiStringクラスの Delphiで stringに対して使用できる代入や比較、連結といったオペレーションは C++の operatorで、また Delete や Insert等はメンバ関数として実装されています。
更に C stringで用意されている c_str()関数も char*を返すメンバ関数として実装されています。
よって
AnsiString str1( "string" );
AnsiString str2( 2 );
AnsiString str3( "string2" );
char* str
if( str3==(str1+str2) ) {
str3[7] = 0x33;
MessageBox( 0, str3.c_str(), "Test", MB_OK );
}
のような記述も出来ます。
Delphiの集合型の C++Builderでの実装
Delphiでは set ofを使用して集合型を定義できますが、C++Builderでは enum, template, operatorを使用して実現しています。
set ofはクラステンプレート Setを用意し、要素の演算を operatorオーバーロードを使用して実装しています。
各要素は単純に enumを使用しているだけです。
TFontStylesの実装を簡単に説明すると、ObjectPascalでは次のように定義されています。
TFontStyle = (fsBold, fsItalic, fsUnderline, fsStrikeOut);
TFontStyles = set of TFontStyle;
これは C++では SYSDEFS.Hで定義されているテンプレートクラス Setを使用して次のように定義されています。
enum TFontStyle { fsBold, fsItalic, fsUnderline, fsStrikeOut };
typedef Set<TFontStyle, fsBold, fsStrikeOut> TFontStyles;
Setには要素の型 TFontStyleと、要素の最小値及び最大値が渡されています。
また、Setには各要素を格納する領域として unsigned char型の Data変数が用意されています。このデータのサイズは、渡された要素の最小値及び最大値から割り出されます。各要素はビットで表され、unsigned charは 8ビットなので 32までの要素を格納することが出来ます。
各要素をセットするには operator <<を使用します。Set型のデータに対して <<を使用すると、次の関数が呼び出されます。
Set& __fastcall operator <<(const T el);
例えば TFontStyles fs;と定義している場合、fs << fsBold;とすることで呼び出されます。関数の戻り値は Set&なので、次のように複数の要素を続けて追加することもできます。
fs << fsBold << fsItalic;
また、Pascalで帰属関係を表す演算子 inは、指定した値が含まれているかどうかを論理型で返し、次のように使用します。
if ixPrimary in Table1.IndexDefs[0].Options then .....
C++Builderでは、集合型はテンプレート Setで実装されているので、Pascalの inに相当する演算子は有りません。そこで次のように記述します。
if( (Table1->IndexDefs->Items[0]->Options * TIndexOptions()<<ixPrimary)!= TIndexOptions() ) .....
上記の *演算子は Setクラスで定義されていて、ビットアンド &に相当するのでこのコードは次のようなビット演算をしているのに似ています。
if( (Table1->IndexDefs->Items[0]->Options & 0x0010)!=0 ) ....
上記の記述は次のように書き直すこともできます。
TIndexDefs prIndex;
prIndex << ixPrimary;
if( (Table1->IndexDefs->Items[0]->Options * prIndex)==prIndex ) ...
Tableへの Recordの追加
TTableクラスにはレコードの追加する関数として、レコードをまとめて追加する AppendRecord, 及び個々のフィールドをセットして追加する Postがあります。
TTable::AppendRecordは TVarRec*とそのサイズ(インデックス)を引数に取る関数で、一度にレコードを追加することができます。
TVarRec recs[3];
recs[0] = 10;
recs[1] = "Test";
recs[2] = 12345.0;
Table1->AppendRecord( recs, 2 );
二つ目の引数は 0から始まり、3つフィールドが有る場合には 2を指定します。
フィールドが一つの場合には C++Builderでも次のように一行で記述できます。
Table1->AppendRecord( &TVarRec(StrToInt(Edit1->Text))), 0 );
各フィールドにデータをセットする方法も取れます。追加するためのレコードバッファを作成してデータをセットします。レコードを現在の位置に挿入するためにレコードバッファを用意するには Insertを使用し、追加するために用意する場合には Appendを使用します。どちらの関数もデータを登録する場合には Post関数を使用します。各フィールドへのアクセスは Fields, FieldValues, 及び各フィールドのプロパティ、及び FieldByName関数が使用できます。上の例をこの方式を使用して書換えると次のようになります。
try {
Table1->Append(); // Insert(); であれば挿入 (インデックスが無い場合)
Table1->Fields[0]->AsInteger = StrToInt( Edit1->Text );
Table1->FieldValues["Field2"] = Edit2->Text;
Table1->FieldByName("Field3")->AsDateTime = StrToDateTime( Edit3->Text );
Table1->Post();
}
catch( ... ) {
Table1->Cancel();
}
TTable::Fieldsプロパティは各フィールドをインデックスで返します。インデックス番号は 0からはじまり、戻り値は TField型です。TFieldコンポーネントには Asxxxxプロパティがあり、それぞれのデータ型でアクセスができるように設計されています。TTable::FieldValuesはインデックスとして AnsiStringを使い、各フィールドの内容を表します。TTable::FieldByName関数は FieldValuesとは結果を TField型で返す点が異なります。TTable::Post関数を使用してデータを登録する場合には、インデックスの重複などでエラー(例外)が発生することを考慮しなければなりません。TTable::Cancelはデータの追加のために、初期化したレコードバッファを解放し、データーベースの編集モードから復帰します。
Printer出力
C++Builderでは Printerへの出力も VCLを使用して行なうことが出来ます。
VCLでは TPrinterクラスが用意されており、Delphiでも使われた Printer関数を使用してオブジェクトを生成、使用します。Printer関数は Printers.hppを includeして使用します。
TPrinter *prn = Printer();
TCanvas *cvs = prn->Canvas;
prn->BeginDoc();
cvs->Font->Assign( Font );
cvs->TextOut( 10, 10, "normal text" );
cvs->Font->Size = 24;
cvs->TextOut( 100, 100, "big text" );
prn->EndDoc();
なお、TPrinterの各メンバ関数は Printers.hppで宣言されている FPrinter変数を使用しているので、次のように TPrinterを直接生成して使用することはできません。
TPrinter *prn = new TPrinter;
:
:
delete prn;
インデックスを使用した Tableのレコード検索
TTableには、レコードのインデックスを使用して検索する関数として FindKey, GotoKey, FindNearest, GotoNearestの四つが用意されています。
TTable::FindKeyは検索を行ない、見つかった場合にのみカーソルハンドルを移動します(ローカルデーターベースの場合には、インデックスを使用して検索が行なわれるので、検索するキーはインデックスの物でなければなりません。またインデックスは IndexNameプロパティで指定します)。この関数は AppendRecord関数と同様に TVarRec*とそのサイズ(インデックス)を表す引数を取り、戻り値は boolです。
テーブルの一次インデックスに対して 3が定義されているレコードの検索を行なうには、次のように記述します。
if( Table1->FindKey( &TVarRec( 3 ), 0 )==true ) {
...
}
同じように、連結二次インデックスで検索する場合には、次のように IndexNameプロパティを指定した後 TVarRecの配列を用意してそれぞれのキーをセットしてから呼び出します。
Table1->IndexName = "Field1_2";
TVarRec recs[2];
rec[0] = 10;
rec[1] = "Test";
if( Table1->FindKey( recs, 1 )==true ) {
...
}
また、連結二次インデックスに登録されている全てのキーで検索するのでは無く、一部の(検索する際には先頭から連続していなければなりません)キーで検索する場合には、検索に使用しないキーに NULLをセットし、検索するサイズを指定して FindKeyを呼び出します。例えば上の例で、連結二次インデックスのうち初めのキーが 10で有るものを検索する場合には次のようになります。
Table1->IndexName = "Field1_2";
TVarRec recs[2];
rec[0] = 10;
rec[1] = NULL;
if( Table1->FindKey( recs, 0 )==true ) {
...
}
TTable::FindNearestは、指定したキーを含め大きい値のデータのうち最も近いレコードを検索し、カーソルハンドルを移動します(ローカルデーターベースでは FindKey同様インデックスを使用します)。FindKeyで異なるのは、完全に一致しない場合にもカーソルハンドルが移動する点です。この関数は void型ですので戻り値をチェックすることは出来ません。呼びだし方は FindKeyと同じです。
Table1->FindNearest( &TVarRec( 3 ), 0 );
TTable::GotoKey及び TTable::GotoNearestは、それぞれ FindKey, FindNearest関数に対応するもので、各キーの設定を行なうことができます(FindKeyは GotoKeyを呼び出し、FindNearestは GotoNearestを呼び出しています)。キーの設定は SetKey又は EditKeyを使用して行ないます。SetKeyは現在のキー設定を有効にします。
EditKeyはキーは、その範囲を変更するために使用します。
Table1->SetKey();
Table1->Fields[0]->AsInteger = 3;
if( GotoKey() == true ) {
...
}
連結二次インデックスで検索を行なう際にも同様に SetKeyと GotoKeyを使います。
Table1->IndexName = "Field1_2";
Table1->SetKey();
Table1->Fields[0]->AsInteger = 10;
Table1->Fields[1]->AsInteger = "Test";
if( GotoKey() == true ) {
...
}
連結二次インデックスの一部のキーで検索する場合には EditKeyを使用し、インデックス中のキーの数を指定する KeyFieldCountプロパティをセットしてから GotoKeyを呼び出します。
Table1->IndexName = "Field1_2";
Table1->EditKey();
Table1->KeyFieldCount = 1;
Table1->Fields[0]->AsInteger = 10;
if( GotoKey() == true ) {
...
}
GotoKeyの代わりに GotoNearestを使用することで FindNearestと同様にカーソルハンドルの位置を更新できます。
インデックスを使用しない Tableの検索
TDataSetクラスには、インデックスが無いフィールドでも検索ができるようにする関数として Locate, Lookup, Findxxx の三つのタイプの関数が用意されています。
TDateSet::Locateは、検索するキーと、そのオプションを指定して検索を行ないます。
一つ目の引数は AnsiString型で、キーになるフィールド名を指定します。複数のフィールドで検索を行なう場合には ;で区切ってフィールド名を指定します。二つ目の引数は Variantの参照型で、各キーの値を指定します。(Locateはインデックスが使用できる場合にはインデックスを使用して検索を行ないます)。
次の例はフィールド Field1で値 3に完全に一致するレコードを検索します。
if( Table1->Locate( "Field1", 3, TLocateOptions() ) == true ) {
...
}
Locateは、検索オプションを指定することができるので、大文字小文字の区別をせずに検索したり(loCaseInsensitive)、部分一致で検索したり(loPartialKey)することができます(ここでいう部分一致とは Paradoxのクエリでいうところの xxx..に相当します。つまり先頭からの部分一致を示します)。次の例は部分一致で検索を行ないます。
if( Table1->Locate( "Field2", "tes", TLocateOptions()<<loPartialKey )
== true ) {
...
}
また、二つ以上のフィールドで検索を行なう場合には、次のように OPENARRAYを使用し、PutElementメソッドでデータを登録します。Locate に指定するフィールドは ;を使用して分けます。
Variant recs( OPENARRAY( int, (0, 1) ), varOleStr );
recs.PutElement( 10, 0 );
recs.PutElement( "Test", 1 );
if( Table1->Locate( "Field1;Field2", recs, TLocateOptions() ) == true ) {
...
}
OPENARRAYは、Delphiに用意されているオープン配列に代わるもので、配列を管理するために使用するデータ型、配列の個数(初めのインデックス、終りのインデックス)を指定する引数を持ちます。検索対象のフィールドが三つの場合には
OPENARRAY( int, (0, 2) )
となります。
TDataSet::Lookupは、Locateと異なり、カーソルポインタを移動しません。代わりに指定したフィールドの値を戻り値として返します。この値は、Locate 関数の二番目の引数として渡すことができる Valiant配列です。次の例は Field1でキー値 3を検索し、Field1, Field2の値を取得します。
Variant recs = Table1->Lookup( "Field1", 3, "Field1;Field2" );
レコードが見つかったかどうかは Variantクラスのメンバ関数 IsEmptyを使用することで確認できます。IsEmptyは Variantクラスに値が収められていれば falseを返します。また Variantの各データにアクセスするには次のように GetElement関数を使用します。
if( recs.IsEmpty() == false ) {
int i = recs.GetElement( 0 );
char *s = ((System::AnsiString)(recs.GetElement( 1 )).c_str();
...
}
また、Lookupの戻り値 recsは Locateに渡すことで、カーソルポインタを移動することができます。
if( Table1->Locate( "Field1;Field2", recs, TLocateOptions() ) == true ) {
...
}
Lookupも Locateと同様に複数のフィールドで検索を行なう際には、OPENARRAYを使用してオープン配列を作成する必要があります。(Locateの説明を参照してください)
Findxxxx関数は Filterと一緒に使用して検索を行ないます。Filter は各フィールドに条件を設定することで取得できるレコードを絞り込みます。Filterプロパティには複数の条件を ANDや ORを使用して指定することができます。また条件を表す演算子として <, >, <=, >=, =, <> が使用できます。(Filter を有効にするには Finteredプロパティーを trueに設定します)。Findxxxx 関数には次の四つが用意されています。どの関数もフィルタリングされたレコードのうち次の条件を満たすレコードを検索し、カーソルポインタを移動します。
FindFirst .... 一番初めのレコード
FindLast ..... 一番最後のレコード
FindNext ..... 次に一致するレコード
FindPrior .... 一致する一つ前のレコード
ファイルとパスが含まれた文字列から、ファイル名とパス名を取り出す方法
Q:
ファイルのパスが含まれた文字列から、ファイル名とパスを別々に取り出すことができますか
A:
ファイル管理ルーチンに ExtractFileNameと ExtractFilePathがあります。
これらの関数を使用してパスとファイル名を取り出す事ができます。
Edit1->Text = ExtractFileName("C:\\Program Files\\Borland\\Delphi 2.0\\
Bin\\Delphi32.exe");
Edit2->Text = ExtractFilePath("C:\\Program Files\\Borland\\Delphi 2.0\\
Bin\\Delphi32.exe");
Memoの中で、特定の文字列を置換する
Q:
Memoの中で、特定の文字列を別の文字列で置換する事はできますか
A:
以下のコードを参考にしてください。Memoコンポーネントの Linesを検索して、「フォーム」という文字列があれば「Form」に変換します。
void __fastcall TForm1::Button1Click(TObject *Sender)
{
int Place;
AnsiString s1;
// "フォーム" を "Form" に置換します
AnsiString SearchStr("フォーム");
AnsiString ReplaceStr ("Form");
for (int i=0; i<Memo1->Lines->Count; i++)
{
s1 = Memo1->Lines->Strings[i];
do {
Place = s1.Pos(SearchStr);
if ( Place > 0 ) {
s1.Delete(Place, SearchStr.Length());
s1.Insert(ReplaceStr, Place);
Memo1->Lines->Strings[i] = s1;
}
} while ( Place != 0 );
}
}
文字列を斜めに表示する方法
コンポーネントや Canvasなどに使われている Fontプロパティでは、文字列の描画方向を指定することはできません。このため CreateFontIndirect()という Windows APIを使って方向を指定する必要があります。次のコードはフォーム上に PaintBoxコンポーネントを配置し、その OnPaintイベントハンドラで処理を行ったサンプルです
void __fastcall TForm1::PaintBox1Paint(TObject *Sender)
{
const TColor ColorTable[12] = {clBlack, clGreen, clOlive, clNavy,
clPurple, clTeal, clRed, clLime,
clYellow, clBlue, clFuchsia, clAqua};
int Angle = 0;
int Index = 0;
TLogFont *LogFont = new TLogFont();
while ( Angle < 3600 ) {
LogFont->lfHeight = 16;
LogFont->lfWidth = 0;
LogFont->lfEscapement = Angle;
LogFont->lfOrientation = 0;
LogFont->lfWeight = FW_BOLD;
LogFont->lfItalic = 1;
LogFont->lfUnderline = 0;
LogFont->lfStrikeOut = 0;
LogFont->lfCharSet = DEFAULT_CHARSET;
LogFont->lfOutPrecision = OUT_DEFAULT_PRECIS;
LogFont->lfClipPrecision = CLIP_DEFAULT_PRECIS;
LogFont->lfQuality = DEFAULT_QUALITY;
LogFont->lfPitchAndFamily = DEFAULT_PITCH;
StrPCopy(LogFont->lfFaceName, "CourierNew");
PaintBox1->Canvas->Font->Handle = CreateFontIndirect(LogFont);
PaintBox1->Canvas->Font->Color = ColorTable[Index];
PaintBox1->Canvas->Brush->Style = bsClear;
PaintBox1->Canvas->TextOut(Width/2, Height/2, " C++Builder!");
Angle +=300;
Index++;
}
delete LogFont;
}
ウィンドウのコントロールを実行時に移動する方法
マウスでウィンドウのコントロールを実行時に移動する方法としては、OnMouseDownでマウスの位置を保存し、OnMouseMoveで SetBounds を呼び出してコンポーネントの位置を再設定する方法がありますが、Windowsのコントロールの場合には、コントロールがドラッグされる際に送られるメッセージを利用して移動することもできます。
void __fastcall TForm1::Panel1MouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
ReleaseCapture();
Panel1->Perform( WM_SYSCOMMAND, 0xF012, 0 );
}
StringGridの各セルの表示を変更する
StringGridの個々のセルの表示をカスタマイズする場合には、OnDrawCellイベントで描画を行います。次の例は、各セルに二つの色を使用して C++Builderを表示します。
void __fastcall TForm1::StringGrid1DrawCell(TObject *Sender,long Col,
long Row, TRect &Rect, TGridDrawState State)
{
TCanvas *cvs = StringGrid1->Canvas;
cvs->Font->Color = clMaroon;
cvs->TextOut( Rect.Left+3, Rect.Top+3, "C++" );
cvs->Font->Color = clNavy;
cvs->TextOut( Rect.Left+3+cvs->TextWidth("C++"), Rect.Top+3, "Builder" );
}
プロパティのデフォルト値について
__property宣言の default指定は、そのプロパティのデフォルト値を宣言するために用います。実際の初期化は、コンストラクタで明示的に行なわなければなりません。
デフォルト値を宣言する意味は、プロパティの値の格納方法にあります。
オブジェクトインスペクタで設定したプロパティの値は、DFMファイルに格納されますが、全てのプロパティ値を格納しているとファイルサイズは膨大になってしまいます。そのため、格納するプロパティ値は、デフォルト値と異なる値の場合に限定しています。
__property宣言で、defaultを再定義する意味は、プロパティ値の格納の制御を変更することにあります。新しいデフォルト値が設定されればプロパティがその値を持つ限り、DFMファイルには値が格納されません。
次の例は、TMyLabelという TLabelを継承したコンポーネントの宣言の一部です。
__property Height = { default = 32 };
__property Width = { default = 128 };
コンストラクタでは、次のように初期化を行ないます。
Height = 32;
Width = 128;
このコンポーネントをフォームに配置すると、DFMファイルには、次のように格納されます。
object MyLabel1: TMyLabel
Left = 96
Top = 80
Caption = 'MyLabel1'
end
しかし、__property宣言を省略し、デフォルト値を設定しないと、コンストラクタで初期化を行なっていても、DFMファイルに格納されるのは次の値になります。
object MyLabel1: TMyLabel
Left = 96
Top = 80
Width = 128
Height = 32
Caption = 'MyLabel1'
end
ExecSQLを行っている最中のカーソルを普通の砂時計にしたい
Q:
ExecSQLを行っている最中のカーソルは、砂時計に SQLの文字が下に付きますが,普通の砂時計にできますか
A:
ExecSQLの実行中のカーソルは、サーバーのコールバックルーチンで crSQLWaitにセットされれます。カーソルの定数を変更することはできないのでカーソルの配列の内容を一時的に変更することで可能になります。
HCURSOR hSQLWaitCursor;
hSQLWaitCursor = Screen->Cursors[crSQLWait];
Screen->Cursors[crSQLWait] = Screen->Cursors[crHourGlass];
Query1->ExecSQL();
Screen->Cursors[crSQLWait] = hSQLWaitCursor;
マウスイベントでカーソル形状を変更する場合の注意
Q:
マウスイベントでカーソル形状を変更する場合に注意することはありますか
A:
単純に、OnMouseDown, OnMouseUpイベントで、Cursor := crxxxxとしても、左マウスの場合は、カーソルが変更されません。(ドラッグ用に予約されているため)
正しく動作させるためには、次のようにコードを書く必要があります。
void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
if ((Shift * TShiftState()<<ssLeft) != TShiftState())
ReleaseCapture();
Cursor = crHourGlass;
SetCapture( Handle );
}
void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
ReleaseCapture();
Cursor = crDefault;
}
Visual Basicの DoEvents 関数/ステートメントと同じ処理をしたい
Q:
Visual Basicの DoEvents関数/ステートメントと同じ処理を C++Builderで行うには
A:
現在開いているフォームの数を取得するには TScreen::FormCountプロパティを Windowsに制御を移す際には、TApplication::ProcessMessagesメソッドをご使用ください。
Visual Basicの AutoRedrawと同じ機能はありますか
Q:
Visual Basicにある AutoRedrawプロパティに相当するものはありますか
A:
Formではなく、Imageの Canvasに描画することで、同様の結果が得られます。
Formの Canvasと Paintboxの Canvasへの描画について
Q:
Formの Canvasと Paintboxの Canvasへの描画について
A:
Formに PaintBoxを貼り、Brushの Colorプロパティを設定せずに Formと PaintBoxのキャンバスに対して描画すると、それぞれ別の色で描画されます。
Ctl3Dプロパティが Trueの場合には Canvasの Brushの Colorプロパティを明示的に指定する必要があります。Canvasに対して描画する場合は、その前に Brushの Colorプロパティを設定してください。
void __fastcall TForm1::FormPaint(TObject *Sender)
{
Canvas->Brush->Color = clBtnFace;
Form1->Canvas->Ellipse(200,200,40,40);
}
プログラムの実行時にメインフォームを決定する方法
Q:
プログラムの実行時にメインフォームを決定したい
A:
どのフォームがメインフォームとなるかは プロジェクトソースの中で CreateFormで作成される順番により決まります。WinMainで最初に CreateFormで作成されたフォームがメインフォームとなります。以下のコードはプログラムの実行時引数の値によってメインフォームを変更したサンプルです
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try {
Application->Initialize();
if ( ParamStr(0) == 'A') {
Application->CreateForm(__classid(TForm1), &Form1);
Application->CreateForm(__classid(TForm2), &Form2);
} else {
Application->CreateForm(__classid(TForm2), &Form2);
Application->CreateForm(__classid(TForm1), &Form1);
}
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
MDIの親フォームで F1キーを押して指定するヘルプを開けない
Q:
MDIで親フォームがアクティブになっているとき、F1キーを押しても、オプション(O)|プロジェクト(P)|Application|ヘルプファイル(F):で指定したヘルプファイルを開くことができません。
A:
MDIの親フォームでは、その Messageがクライアントウインドウに渡されてしまうためです。これを回避するには、クライアントに渡される前に TApplicationの OnMessageで処理を行うようにします。
void __fastcall TMainForm::FormCreate(TObject *Sender)
{
Application->OnMessage = HandleAppMessage; // 追加
Application->OnHint = ShowHint;
Screen->OnActiveFormChange = UpdateMenuItems;
}
// 次のメンバー関数を追加
void __fastcall TMainForm::HandleAppMessage(TMsg &Msg, bool &Handled)
{
if ((Msg.hwnd == ClientHandle) && (Msg.message == WM_KEYDOWN) &&
(Msg.wParam == VK_F1))
{
Application->HelpContext(1);
Handled = false;
}
}
MDIの親フォームで OnKeyDownイベントが使えない
Q:
MDIの親フォームの OnKeyDownイベントに定義した内容が実行されません。
何かキーを押しても OnKeyDownイベントが呼ばれていないようです。
A:
MDIの親フォームでは、キーが押されたなどのイベントはクライアントウインドウに渡されてしまうので、フォームの OnKeyDownなどでは処理できません。
このような場合には、TApplicationの OnMessageで処理を行います。
void __fastcall TMainForm::FormCreate(TObject *Sender)
{
Application->OnMessage = HandleAppMessage; // 追加
Application->OnHint = ShowHint;
Screen->OnActiveFormChange = UpdateMenuItems;
}
// 次のメンバー関数を追加
void __fastcall TMainForm::HandleAppMessage(TMsg &Msg, bool &Handled)
{
if ((Msg.hwnd == ClientHandle) && (Msg.message == WM_KEYDOWN) &&
(Msg.wParam == VK_RETURN))
{
ShowMessage("Return Key!");
Handled = false;
}
}
グリッドの選択されているセルの文字色を変えたい
Q:
StringGridや DrawGridで、現在選択されているセルの文字の色を任意の色に変更できますか
A:
Gridの OnDrawCellイベントで以下のような処理を行うことで実現できます。
void __fastcall TForm1::StringGrid1DrawCell(TObject *Sender, long Col,
long Row, TRect &Rect, TGridDrawState State)
{
TCanvas *sc = StringGrid1->Canvas;
if (State * (TGridDrawState()<<gdFocused) != TGridDrawState())
{
sc->Font->Color = clRed;
sc->TextRect(Rect, Rect.Left, Rect.Top, "TEST");
} else if ((Col > 0) && (Row > 0)) {
sc->Font->Color = clBlue;
sc->TextRect(Rect, Rect.Left, Rect.Top, "TEST");
}
}
グリッドの選択されているセルの色を変えたい
Q:
StringGridや DrawGridで、現在選択されているセルの色を任意の色に変更できますか。
A:
Gridの OnDrawCellイベントで以下のような処理を行うことで実現できます。
void __fastcall TForm1::StringGrid1DrawCell(TObject *Sender, long Col,
long Row, TRect &Rect, TGridDrawState State)
{
TCanvas *sc = StringGrid1->Canvas;
if (State * (TGridDrawState()<<gdSelected) != TGridDrawState())
{
sc->Brush->Color = clLime;
sc->FillRect(Rect);
sc->TextRect(Rect, Rect.Left, Rect.Top, "TEST");
}
}