旧C++Builder FAQ - プログラミング・VCL(1)

Abstract: この FAQは、旧 www.borland.co.jpに掲載されていた記事を転載したものです。記事は掲載時点の情報をあるがままに掲載しており、新バージョンでのご利用においては、コンポーネントや APIの仕様変更等によりご利用いただけない場合がありますのでご留意ください。

    TQueryを使用して Refreshを呼び出しても、最新のデータに更新されません。(99/5/12)

該当するバージョン:C++Builder 4

Q:

TQueryを使用して Refreshを呼び出しても、最新のデータに更新されません。

A:

SQL文で order by節を使用されている場合、この項目はインデックスになっている必要があります。

descなどを使用した場合も、インデックスを作成する時にその順序で整列されている必要があります。

さらにその 2次インデックスは"保守する"を有効にしていなければ、Refreshでデータが反映されません。(Paradox, dBaseの場合)

また、2次インデックスを保守する場合は、主キーが必要になります。

もちろん TQueryの RequestLiveは trueに設定している必要があります。

    TDBGridから垂直スクロールバーを取り除く方法

Q:

TDBGridから垂直スクロールバーを取り除くには、どうしたら良いですか?

A:

TDBGridからスクロールバーを取り除くには、Paintメソッドをオーバーライドする必要があります。Paintメソッド内で、SetScrollRange API関数をコールして、スクロール値の最小値と最大値を 0に設定(これにより、スクロールバーを無効に)します。以下のコードを、保存して、コンポーネントのインストールを行なうと、NoVertScrollDBGridコンポーネントとして登録されます

//*** NewGrid.h ***//
//------------------------------------------------------------------
#ifndef UnitxH
#define UnitxH
//------------------------------------------------------------------
#include <vcl\SysUtils.hpp>
#include <vcl\Controls.hpp>
#include <vcl\Classes.hpp>
#include <vcl\Forms.hpp>
#include <vcl\DBGrids.hpp>
#include <vcl\Grids.hpp>
//------------------------------------------------------------------
class TNoVertScrollGrid : public TDBGrid
{
private:
protected:
        void __fastcall  Paint(void);
public:
        __fastcall TNewGrid(TComponent* Owner);
__published:
};
//------------------------------------------------------------------
#endif

//*** NewGrid.cpp ***//
#include <vcl\vcl.h>
#pragma hdrstop

#include "Unit1.h"
//-----------------------------------------------------------------
static inline TNoVertScrollGrid *ValidCtrCheck()
{
        return new TNoVertScrollGrid(NULL);
}
//-----------------------------------------------------------------
__fastcall TNoVertScrollGrid::TNoVertScrollGrid(TComponent* Owner)
        : TDBGrid(Owner)
{
}
// Paint メソッドのオーバーライド
void __fastcall TNoVertScrollGrid::Paint(void)
{
    TDBGrid::Paint();
        SetScrollRange(Handle, SB_VERT, 0, 0, false);
}
//-----------------------------------------------------------------
namespace Unitx
{
        void __fastcall Register()
        {
                TComponentClass classes[1] = {__classid(TNewGrid)};
                RegisterComponents("Samples", classes, 0);
        }
}
//-----------------------------------------------------------------

[備考] ObjectPascalとの相違

  • オーバーライドの書き方

    C++Builderでのクラス(delclspec)

C++Builderでは Delphiで培われた VCLの信頼性を活かすために、コンポーネントライブラリとして VCL(Visual Component Library)が採用されています。

このため Delphiライクなクラス構造と互換性を取るために、クラスを__declspec(delphiclass)で修飾します。つまり、VCLから派生したクラスは全て __declspec(delphiclass)で修飾されていることになります。このような拡張の結果 Delphiでは一般的な virtualなコンストラクタを C++Builderで使用できるようになります。

    CreateTableを使用してテーブルを作成する

ローカルデーターベースの Paradox テーブルを作成する手順を説明します。

テーブルの作成をするには各フィールド(項目)やインデックスを指定しなければなりません。各テーブルのフィールド(項目)やインデックスは、それぞれ FieldDefs及び IndexDefsプロパティに登録する事で実現します。

  Table1->FieldDefs->Clear();
  Table1->FieldDefs->Add( "Field1", ftInteger, 0, false );
  Table1->FieldDefs->Add( "Field2", ftString, 10, false );
  Table1->FieldDefs->Add( "Field3", ftDate, 0, false );

TFieldDefs::Clearは、存在するフィールド情報を初期化します。この結果一つのフィールドも持たない構造が作成されます。

TFieldDefs::Addはフィールドを追加します。一つ目の引数はフィールド名で AnsiString型です。二つ目の引数はフィールドの型を指定します。作成するテーブルでサポートされたフィールド型を指定します。三つ目の引数はフィールドのサイズで、ftString型等サイズを指定する必要のあるデータ型に対して指定します。最後の引数は、レコードを追加する際データが必ず存在しなければならないか(空のデータを許さないか)を指定します。

インデックスに対しても IndexDefsプロパティに同じように登録します。

  Table1->IndexDefs->Clear();
  Table1->IndexDefs->Add( "", "Field1", TIndexOptions()<<ixPrimary<<ixUnique );
  Table1->IndexDefs->Add( "Field1_2", "Field1;Field2", TIndexOptions() );
  Table1->IndexDefs->Add( "Field2", "Field2", TIndexOptions() );

TIndexDefs::Addはインデックスを追加します。一つ目の引数はインデックスの名前です(Paradox ではキー名、dBASE ではタグ名と呼びます)。二つ目の引数はフィールド名です(Paradox では連結インデックスを作成する事ができ ;を使ってフィールドを複数指定することが出来ます。dBASEでは、式インデックスを使用して実現するため指定方法が異なります。)最後の引数はインデックスの型を表し、TIndexOptions型です。TIndexOptions型は次のように定義されたテンプレートクラスです。

  // *** VCL\DBTABLES.HPP ***
  enum DBTables_1 { ixPrimary, ixUnique, ixDescending,
                    ixCaseInsensitive, ixExpression };
  typedef Set<DBTables_1, ixPrimary, ixExpression>  TIndexOptions;

ですからデフォルトインデックス(Paradox の場合、保守する、大文字小文字を区別する二次インデックス)を指定する場合にも TIndexOptionsを生成して渡します。

デフォルト以外のオプションを追加する場合には operator <<を使用して各インデックス型を登録します。

テーブルを実際に作成するには TTable::CreateTableを使用します。

完成したコードは次のようになります。

  try {
    Table1->Close();
    Table1->FieldDefs->Clear();
    Table1->FieldDefs->Add( "Field1", ftInteger, 0, false );
    Table1->FieldDefs->Add( "Field2", ftString, 10, false );
    Table1->FieldDefs->Add( "Field3", ftDate, 0, false );
    Table1->IndexDefs->Clear();
    Table1->IndexDefs->Add( "", "Field1",
                            TIndexOptions()<<ixPrimary<<ixUnique );
    Table1->IndexDefs->Add( "Field1_2", "Field1;Field2", TIndexOptions() );
    Table1->IndexDefs->Add( "Field2", "Field2", TIndexOptions() );
    Table1->CreateTable();
    Table1->Open();
  }
  catch( ... ) {
    ShowMessage( "cannot create table" );
  }

上記コードを Delphiで記述すると次のようになります。

  try
    with Table1 do begin
      Close;
      with FieldDefs do begin
        Clear;
        Add( 'Field1', ftInteger, 0, False );
        Add( 'Field2', ftString, 10, False );
        Add( 'Field3', ftDate, 0, False );
      end;
      with IndexDefs do begin
        Clear;
        Add( '', 'Field1', [ixPrimary, ixUnique] );
        Add( 'Field1_2', 'Field1;Field2', [] );
        Add( 'Field2', 'Field2', [] );
      end;
      CreteTable;
    end;
  except
    ShowMessage( 'cannot create table' );
  end:

    コンポーネントに新しいイベントを追加する方法

Q:

コンポーネントに新しいイベントを追加するには、どのようにすれば良いですか?

A:

OnCreateイベントを持つ Editコンポーネントを作成する手順を説明します。

統合環境のメニューの [コンポーネント(C)|新規作成(N)]を選び"コンポーネントウィザード" ダイアログを開きます。

ここで以下を記入します。

クラス名(C)

: 作成したいクラス名を記入(仮に CEdit)

上位クラス(A)

: TEditをここでは選択

パレットページ名(P)

: CEditを配置したいパレット名を記入(Samples)

OKボタンを押して確定します。

CEditのヘッダーファイルとソースに以下を記述します。

--- CEdit.h ---

 class CEdit : public TEdit
 {
   private:
     TNotifyEvent FOnCreate;     //追加
   protected:
     void __fastcall WMCreate( TWMCreate *Message );        //追加
     BEGIN_MESSAGE_MAP                                      //追加
       MESSAGE_HANDLER(WM_CREATE, TWMCreate*, WMCreate )    //追加
     END_MESSAGE_MAP( TEdit )                               //追加

   public:
    __fastcall CEdit(TComponent* Owner);
      __published:
        //追加
        __property TNotifyEvent OnCreate = {read=FOnCreate, write=FOnCreate};
  };

--- CEdit.cpp ---

   //追加
   void __fastcall CEdit::WMCreate( TWMCreate *Message )
   {
     if( FOnCreate != NULL )
       FOnCreate( this );
   }

適当な場所に上記を保存します。

統合環境のメニューの [コンポーネント(C)|インストール(I)] を選び"コンポーネントのインストール" ダイアログを開きます。

このダイアログの追加(A)ボタンを押し、モジュールの追加ダイアログを開きます。ここの モジュール名(M)に先ほど保存した CEdit.cppを指定して OKボタンを押して頂ければ、コンパイル後 Samplesの パレットに CEditが表示されます。

この CEditのイベントを見ると OnCreateが、追加されていることが確認できます。

    QuickReportコンポーネントを使用して、簡単な一覧表示を行なう方法

該当するバージョン:C++Builder 1

Q:

QuickReportコンポーネントを使用して簡単な一覧表示を行なうには、どうしたら良いですか?

A:

DBDEMODの CUSTOMER.DBを用いて一覧表示を行なう方法をご案内します。

  1. [ファイル(F)|新規作成(N)]より表示されるオブジェクトリポジトリから[フォーム]をクリックし QuickReportリストを選択します。
  2. フォームに、TTableコンポーネントと TDataSourceコンポーネントを配置します。オブジェクトインスペクタで、以下のプロパティを設定します。
  Table1->DatabaseName : DBDEMOS
  Table1->TableName : CUSTOMER.DB
  DataSource1->DataSet : Table1
  1. オブジェクトインスペクタで、QuickReportのプロパティを設定します。
  QuickReport->DataSource : DataSource1
  1. 「このバンドに置かれたコンポーネントがページヘッダーとして表示されます。」のようなコメントが記述されている、QRLabel1、QRLabel2、QRLabel3 を削除します。
  2. フォームの上から 3番めのバンド Detailに、QRDBTextコンポーネントを 5つ配置します。このコンポーネントには、DataSourceプロパティと DataFieldプロパティがあり、データセットの項目を表示することができます。
  3. 配置した QRDBTextのそれぞれのプロパティを、オブジェクトインスペクタで設定します。
  QRDBText1->DataSource : DataSource1
  QRDBText1->DataField : CustNo
  QQDBText1->AutoSize : True;

  QRDBText2->DataSource : DataSource1
  QRDBText2->DataField : Company
  QQDBText2->AutoSize : True

  QRDBText3->DataSource : DataSource1
  QRDBText3->DataField : Zip
  QQDBText3->AutoSize : True

  QRDBText4->DataSource : DataSource1
  QRDBText4->DataField : Addr1
  QQDBText4->AutoSize : True

  QRDBText5->DataSource : DataSource1
  QRDBText5->DataField : Phone
  QQDBText5->AutoSize : True
  1. Table1の Activeプロパティを Trueにして、QuickReportをダブルクリックすると、レポートプレビューが表示されます。

アプリケーションから、上記のレポートフォームを使用して、印刷したりプレビューするには、このフォームクラスに印刷やプレビューを行なう手続きを追加します。

  1. QRListFormクラスの public宣言に、以下の記述を追加します。
  class  TQRListForm : public  TForm
  {
      TQuickReport QuickReport;
      TQRBand      Title;
        :
        :
    private
    public
      void PreviewList( void );  //  追加
      void PrintList( void );    //  追加
  }
  1. 以下の手続きを記述します。
  void TQRListForm::PreviewList( void )
  {
    QuickReport->Preview();
  }

  void TQRListForm::PrintList( void );
  {
    QuickReport->Print();
  }
  1. この時点で、このフォームの印刷やプレビューを実行する手段が整いました。ここで、この手続きを実行するメインフォームを作成します。デスクトップには、レポートフォームの他に空のフォーム(Form1)があるはずです。このフォームをメインフォームとして使用します。
  2. メインフォームに、TButtonを 2つ配置します。オブジェクトインスペクタで、以下のプロパティを設定します。
  Button1->Caption : 印刷
  Button2->Caption : プレビュー
  1. Button1の OnClickイベントに以下のように記述します。
   void __fastcall TForm1::Button1Click( TObject *Sender );
   {
     QRListForm->PrintList();
   }
  1. Button2 の OnClick イベントに以下のように記述します。
   void __fastcall TForm1::Button2Click( TObject *Sender );
   {
     QRListForm->PreviewList();
   }
  1. このメインフォームは、QRListFormを使用しますので、[ファイル(F)|ユニットを使う(U)]から、QRListFormのユニット(デフォルトであれば Unit2)を選択します。

以上の手順を行なって、コンパイル、実行してください。

    QuickReportを使用していますが、用紙サイズの変更ができません

Q:

QuickReportを使用していますが、用紙サイズの変更ができません。

A:

以下のように記述していただければ用紙サイズの変更ができます。

__(省略)__
  char      p_name[256],
            p_drv[256],
            p_port[256];
  THandle   devm;
  TDevMode *dmw;

  //プリンターの設定を変更
  Printer()->GetPrinter( p_name, p_drv, p_port, devm );
  dmw = (TDevMode *)GlobalLock( ( HGLOBAL )devm );
  dmw->dmOrientation = DMORIENT_LANDSCAPE;
  dmw->dmPaperSize = DMPAPER_A3;             //用紙を A3 に変更
  GlobalUnlock( ( HGLOBAL )devm );
  Printer()->SetPrinter( p_name, p_drv, p_port, devm );

  QRMDForm->QuickReport->Print();  //印刷
__(省略)__

    「グリッドインデックスが範囲を超えています」というエラーについて

該当するバージョン:C++Builder 1

あるタイミングまで表示されない DBGridがあるプログラムで、この DBGrid に結びついている DataSetのレコードを移動させると、「グリッドインデックスが範囲を超えています」というエラーが発生することがあります。

DBGridは複数レコードを表示するために、DataSetコンポーネントに複数行のバッファを要求します。しかし、この要求は DBGridが表示されたときにはじめて行われるため、一度も表示されていない DBGridと結び付けられた DataSetコンポーネントには 1レコード分のバッファしか割り当てられていません。この場合、レコードポインタが移動すると、バッファの先頭が移動するため、DBGridの行と DataSetのバッファが一致しなくなります。そのために、グリッドの範囲外に移動する不正な命令が発行されてしまうことがあります。

  1. 初期状態で Visible=falseの DBGridがメインフォームにある場合(アクティブではない PageControlなどにある場合を除く)。Formの OnShowイベントに以下のようなコードを記述します。
 void __fastcall TForm1::FormShow(TObject *Sender)
 {
     DBGrid1->Visible = true;
     DBGrid1->Visible = false;
 }
  1. PageControlで、アクティブではない TabSheet上に DBGridがある場合 FormのOnShowイベントに以下のようなコードを記述します。
 void __fastcall TForm1::FormShow(TObject *Sender)
 {
     TTabSheet *OrgSheet;

     OrgSheet = PageControl1->ActivePage;
     PageControl1->ActivePage = TabSheet2; // DBGrid があるページ
     PageControl1->ActivePage = OrgSheet;
 }
  1. 自動作成の対象となっているサブフォームにDBGridがある場合、設計時にDBGridのDataSourceプロパティをブランクに設定し、サブフォームのOnShowイベントで、DBGrid.DataSourceプロパティを設定する。
  void __fastcall TForm1::FormShow(TObject *Sender)
  {
      DBGrid1->DataSource = DataModule2->DataSource1;
  }

設計時にブランクにせず、メインフォームのOnShowイベントで 一度切り離してもよい。

  void __fastcall TForm1::FormShow(TObject *Sender)
  {
     Form2->DBGrid1->DataSource = "";
  }

    FTPコンポーネントの DocOutputイベントの引数 DocOutputのプロパティを参照したい

該当するバージョン:C++Builder 1

Q:

FTPコンポーネントの DocOutputイベントの引数 DocOutputのプロパティを参照したい

A:

DocOutputの引数自体が Variant型ですので以下のように記述していただければ参照可能です。

 void __fastcall TMainForm::FTPDocOutput(TObject *Sender,
                                         const Variant &DocOutput)
 {
   // FTP コンポーネントは,COM ですので
   // 以下のように記述してプロパティを取得して下さい
   Variant vrnt1  = DocOutput.OlePropertyGet("BytesTotal");
   Variant vrnt2  = DocOutput.OlePropertyGet("BytesTransferred");
        
   Label1->Caption = "Total: " + IntToStr((int)vrnt1);

   try{
     // ゲージに転送状況を表示
     Gauge1->Progress = 100*((int)vrnt2/(int)vrnt1);
   }
   catch(...){
   }
 }

    Form 上に配置されているボタンのカーソルキーのイベントを取得したい

Q:

Form 上に配置されているボタンのカーソルキーのイベントを取得したい

A:

ボタンにメッセージを送ることによって実現可能です。

 //ヘッダーです ( Unit1.h ) 
 //-----------------------------------------------------------------
 #ifndef Unit1H
 #define Unit1H
 //-----------------------------------------------------------------
 #include <vcl\Classes.hpp>
 #include <vcl\Controls.hpp>
 #include <vcl\StdCtrls.hpp>
 #include <vcl\Forms.hpp>
 //-----------------------------------------------------------------
 class TForm1 : public TForm
 {
 __published:   // IDE 管理のコンポーネント
   TButton *Button1;

   void __fastcall Button1KeyDown(TObject *Sender, WORD &Key, TShiftState Shift);
 private:  // ユーザー宣言
 public:   // ユーザー宣言
    __fastcall TForm1(TComponent* Owner);
 protected:
    void __fastcall CMDialogKey( Messages::TWMKey &Message );

 BEGIN_MESSAGE_MAP
   MESSAGE_HANDLER( CM_DIALOGKEY, Messages::TWMKey, CMDialogKey )
 END_MESSAGE_MAP( TForm )
 };
 //-----------------------------------------------------------------
 extern TForm1 *Form1;
 //-----------------------------------------------------------------
 #endif
 //ソースです ( Unit1.cpp )
 //-----------------------------------------------------------------
 #include <vcl\vcl.h>
 #pragma hdrstop

 #include "Unit1.h"
 //-----------------------------------------------------------------
 #pragma resource "*.dfm"
 TForm1 *Form1;
 //-----------------------------------------------------------------
 __fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
 {
 }
 //-----------------------------------------------------------------
 void __fastcall TForm1::CMDialogKey( Messages::TWMKey &Message )
 {
   TForm::Dispatch( (void*)&Message );
   //カーソル上 VK_UP
   //        右 VK_RIGHT
   //        下 VK_DOWN
   if( Message.CharCode == VK_LEFT )
   {
     // Button1 に WM_KEYDOWN メッセージを送付
     Button1->Perform( WM_KEYDOWN, VK_LEFT, 0 );
   }
 }
 //-----------------------------------------------------------------
 void __fastcall TForm1::Button1KeyDown(TObject *Sender, WORD &Key,
    TShiftState Shift)
 {
   ShowMessage(IntToStr(Key));
 }
 //-----------------------------------------------------------------

    プログラムから Excelを操作したい

該当するバージョン:C++Builder 1,C++Builder 3

Q:

実行プログラムからMicrosoft Excelを操作したい

A:

Variantから OleObjectを生成していただければ実現可能です。

以下に実現方法を記述します。

  #include  <vcl\oleauto.hpp>

  __( 途中省略 )__

  //OleObject の生成
  Variant excel = CreateOleObject("Excel.Application");

  //Visible プロパティを true にすることにより
  //Excel が表示される
  excel.OlePropertySet( "Visible", true );
  Variant book = excel.OlePropertyGet( "Workbooks" );

  //ワークブックの Open
  book.OleFunction( "Open", "e:\\book1.xls" );
  Variant application = excel.OlePropertyGet( "Application" );

  //マクロの実行
  application.OleFunction( "Run", "macro1");

  //保存
  application.OleFunction( "Save" );

  //終了
  application.OleFunction( "Quit" );

    GUIアプリ上での標準出力を使用するにはどうすればよいのでしょうか

Q:

GUIアプリ上での標準出力を使用するにはどうすればよいのでしょうか

A:

標準出力のリダイレクトを行えば、ほかのファイルに出力内容を転送できます。

下記の例では freopen()と fclose()の間に行われた出力を c:\redirect.txtというファイルに出力しています。

  FILE *fp;
  fp = freopen("C:\\REDIRECT.TXT", "w", stdout);
    if (fp == NULL) return;
    printf("Hello, World!\n");
  fclose(fp);

ただし GUIアプリケーションでは coutによる標準出力割り当ては実現できてませんので、以下のように記述していただく必要があります。

  #include <fstream.h>

  ofstream ofile("c:\\redirect.txt");
  cout = (ostream&)ofile;
  cout << "C++Builder" << endl;

    Internetタブの TCPを使用すると 2度目の接続時に"Address in use"と表示され接続できない

該当するバージョン:C++Builder 1

Q:

C++Builderの Examples\Internet\tcpに含まれるサンプル chatで接続(Connect)を行った側から切断(Close)を行い、そのまま再度接続を行おうとすると"Address in use"が表示されて接続ができない。

A:

この現象は netstatコマンドで確認すると、切断時に WinSock上で Socketが残り stateが TIME_WAITになり Socketが開放されていないために起こります。

ActiveXである TTCPは stateに TIME_WAITが存在せず、サンプルでは sckClosedでなければ再度接続するようにコーディングされています。

TIME_WAITの Scketeは 4~5分程度で開放され、再度使用可能になりますが、すぐに最接続したい場合は以下のように変更してください。

FileConnectItemClickClick の内部を以下のように変更する。

  if(InputQuery("Computer to connect to", "Address (either IP or Name):", Server)) {
    TCP2->LocalPort = 0;
    if(Server.Length() > 0) TCP2->Connect(Server, 1024);
  }

LocalPortに 0を入れると未使用のポートを使用して新しく接続されます。

この時 TIME_WAITの Socketが残るので、過度に接続・切断をくり返すと Socketが作成できなくなる可能性がありますので注意が必要でしょう。

    "Table1 : 項目 'xxxxxx' がみつかりません."と表示されます(99/5/12)

Q:

"Table1 : 項目 'xxxxxx' がみつかりません."と表示されます

A:

この現象は、以前 Table1を使用して[項目の追加]で項目を追加した後、別の TableNameを設定して Activeプロパティを trueに設定したり Openメソッドを呼び出した場合に発生します。

[項目の追加]で以前の TableNameの時に追加した項目を全て削除して下さい。

    TPanelに TImageをのせて TSplitterで動かしています。Image の大きさが変わりません(99/5/12)

該当するバージョン:C++Builder 3,C++Builder 4

Q:

TPanelに TImageをのせて TSplitterを使用して領域を動かしています。

TImageの Alignを alClientにしていますが TImageの領域が変わりません。

A:

実際には TImageの領域は TSplitterに合わせて変化しています。

画像の大きさが変化しないのは Stretchが trueに設定されていないためです。

    C/S版にて、InterBaseの SQL Linkがメモりを開放しません(99/4/2)

Q:

C/S版を使用していますが、InterBaseの SQL Linkがメモりを開放していないようです

A:

ループの中で TQueryを使用して SQL文を変えながら発行している場合、メモりリークしているように見えます。

この現象は、メモリを取得している速度とメモリを開放している速度の差のために発生します。

次の例のように開放させる時間をドライバに与えてください。

  void __fastcall TForm::Button1Click( TObject *Sender )
  {
    flag = true;

    while(flag)
    {
      Query1->Close();
      Query1->SQL->Clear();
      Query1->SQL->Add( "select * from employee" );

      Sleep(1000);  //開放の時間を与える

      Query1->Open();

      Application->ProcessMessages(); 
    }
  }

    QuickReportでプログラム上でプリンターを変更したい(99/4/2)

Q:

QuickReportでプログラム上でプリンターを変更したい

A:

TPrinterの PrinterIndexでは、QuickReportのプリンターを変更することができません。

QuickReportでは、QuickReportの PrinterIndexを使用してプリンターを変更して下さい。

   //最初に登録してあるプリンターが選択される
   QuickRep1->PrinterSettings->PrinterIndex = 0;

    TNMUDPコンポーネントで一度に転送できるデータサイズは 2049ですか?(99/03/04)

該当するバージョン:C++Builder 3,C++Builder 4

Q:

TNMUDPコンポーネントを使用しています。

一度に転送できるデータサイズが 2049のようですが、もっと大量のデータを一度に転送したい

A:

TNMUDPのクラス定義が NMUDP.HPPに定義されています。

そのクラスの中で、データ転送バッファサイズが char IBuff[2049]と宣言されています。

このコンポーネントでは、このサイズが最大値になります。

また UDPは、もともと少量のデータ(メッセージなど)を、迅速に転送するために設計されているため、大量のデータを一度に送信するためには不向きです。

RFC 768にも記載されていますが、信頼性のあるデータを転送するには TCPを使用するのがベストでしょう。

    Paradoxテーブルにパスワードを付加して,テーブルを作成したい(99/03/04)

Q:

Paradoxのテーブルを作成する時、パスワードを付加して作成したい

A:

VCLのクラスのみの記述では不可能になります。

BDEの APIを使用すれば実現可能です。

次は簡単な例です。

Database1には、あらかじめエリアス等を設定しておく必要があります。

#include <bde.hpp>

__(途中省略)__

//-----------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
   Database1->Open();

   CHAR szTblType[] = szPARADOX;
   CHAR szTblName[] = "PX_Table";
   CRTblDesc   TblDesc;    // テーブルディスクリプタを作成する
   Word NumFields  = 2;
   FLDDesc fldDesc[] = {
            { // フィールド 1 - AUTOINCREMENT
               1, "MyNumber", fldPDXAUTOINC, fldUNKNOWN, 0, 0,
               0, 0, 0, fldvNOCHECKS, fldrREADWRITE
            },
            {  // フィールド 2 - 

               2, "MyString", fldPDXCHAR, fldUNKNOWN, 10, 0, 0,
               0, 0, fldvNOCHECKS, fldrREADWRITE
            }
   };

   memset((void *) &TblDesc, 0, sizeof(CRTblDesc));
   lstrcpy(TblDesc.szTblName, szTblName);
   lstrcpy(TblDesc.szTblType, szTblType);

   TblDesc.bProtected = true;               //暗号化したい場合 true
   lstrcpy(TblDesc.szPassword, "xxxx" );    //パスワード文字列を指定

   TblDesc.iFldCount = NumFields;
   TblDesc.pfldDesc = fldDesc;
   // インデックス、データ検証、セキュリティディスクリプタを追加する
   DbiCreateTable( Database1->Handle, true, TblDesc );

   Database1->Close();
}

    TServerSocketを使用してデータを受信すると OnClientReadイベントが多発します。(99/03/04)

該当するバージョン:C++Builder 3,C++Builder 4

Q:

TServerSocketを使用してデータを受信すると OnClientReadイベントが多発します。

一度でデータを受信する方法はありますか。

A:

データを 1度に受信することはソケットの性質上不可能です。

ただし OnClientReadイベントを使用しない方法はあります。

ブロッキング接続で通信を行う方法です。

通常の接続は NoneBlockingで行われているかと思いますがこの方法は Windowsのメッセージループを考慮にいれて通信を行いますのでメッセージキューに他のメッセージが割り込む可能性があります。

ブロッキング接続は Windowsのメッセージを考慮に入れませんので、その接続の処理に集中できます。

そのかわりデータ通信中は、そのアプリケーションが応答しなくなりますので、スレッドで処理を行います。

ブロッキング接続を行う方法を示します。

  1. ClientSocketの ClientTypeを clBlockingに、ServerSocketの ServerTypeを stThreadBlockingにしてください。
  2. データ転送に TWinSocketStreamを使用して行います。
  3. Server用のスレッドにユーザー定義のスレッドを使用します。

次はコードのサンプルを示します。(修正する部分のみを記述します)

アプリケーションがサーバー機能とクライアント機能の両方を持っていると仮定します。(TClientSocketと TServerSocketを両方保持)

//-----------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
  ClientSocket1->ClientType = clBlocking;
  ServerSocket1->ServerType = stThreadBlocking
}
//-----------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  //OpenDialog で返されるファイルを
  //Server へ転送する。
  if( OpenDialog1->Execute() )
  {

    TFileStream  *rFs =
       new TFileStream( OpenDialog1->FileName, fmShareDenyRead );

    TWinSocketStream *pStream =
      new TWinSocketStream( ClientSocket1->Socket, 6000 );

    pStream->CopyFrom( rFs, 0 );
    
    delete rFs;
  }
  
}
//-----------------------------------------------------------------
//ServerSocket のイベント OnGetThread
void __fastcall TForm1::ServerSocketGetThread(TObject *Sender,
      TServerClientWinSocket *ClientSocket,
      TServerClientThread *&SocketThread)
{
  //Server スレッドの作成
  SocketThread = new TServerThread( false, ClientSocket );
}
//-----------------------------------------------------------------

// TServerThred クラス
//-----------------------------------------------------------------
//Server スレッドのクラスを TServerClientThread から
//継承します。
class TServerThread : public  TServerClientThread
{
  public:
    __fastcall TServerThread( bool CreateSuspended,
                              TServerClientWinSocket* ASocket );
    void __fastcall ClientExecute( void );
};

//----------------------------------------------------------------
__fastcall TServerThread::TServerThread( bool CreateSuspended,
                                TServerClientWinSocket* ASocket )
  : TServerClientThread( CreateSuspended, ASocket )
{
}
//-----------------------------------------------------------------
//データ受信処理
//クライアントからデータが1分しても受信できない場合は
//ループを終了する
void __fastcall TServerThread::ClientExecute( void )
{
  const int bSize = 1000;
  //ファイルを開く
  TFileStream *fStream = new TFileStream( "temp.dat", fmCreate | fmOpenWrite );

  try{
    while( !Terminated && ClientSocket->Connected )
    {

      try{

        TWinSocketStream *pStream =
           new TWinSocketStream( ClientSocket, 60000 );
        try{

          if( pStream->WaitForData(60000))
          {
            char buff[ bSize ];
            int iReadSize;
            memset( buff, 0, sizeof(buff) );

            if(( iReadSize = pStream->Read( buff, bSize )) == 0 )
              ClientSocket->Close();

            //処理を記述
            fStream->Write( buff, iReadSize );

          }
          else
            ClientSocket->Close();
        }
        __finally{
          delete pStream;
        }
      }
      catch(...){
      }
    }

  }
  __finally{
    //ファイルを閉じる
    delete fStream;
  }
}
//-----------------------------------------------------------------

    16bitのメタファイル WMFを TImageに表示したい。(99/03/04)

Q:

アルダス形式でない WMFのメタファイルを使用しています。これを TImageに表示したい

A:

アルダス形式でない WMFのメタファイルは現在あまり使用されていません。

Paintツール等を使用されて EMFなどに変換されるのがよいでしょう。

また TImageに含まれる TMetafileは、純粋な 16Bitメタファイルに対応されていませんので、TImgageへアルダス形式でない WMFのメタファイルを表示したい場合は変換が必要になります。

変換の記述は次のようになります。

//-----------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  if( OpenDialog1->Execute() )
  {
    HMETAFILE  hWmf, hEmf;
    DWord      nSize=0;
    Byte      *pData=0;

    if( !Image1->Picture->Metafile->Empty )
      Image1->Picture->Metafile->Clear();

    //16bit メタファイルの取得
    hWmf = GetMetaFile( OpenDialog1->FileName.c_str() );

    //メタファイルをバッファへコピー
    nSize = GetMetaFileBitsEx( hWmf, nSize, NULL );
    pData = new Byte[nSize];
    GetMetaFileBitsEx( hWmf, nSize, pData );

    //Image のメタファイルをキャンバスに割り当て
    TMetafileCanvas *pCanvas =
       new TMetafileCanvas( Image1->Picture->Metafile, 0 );

    //32bit メタファイルへ変換
    hEmf = SetWinMetaFileBits( nSize, pData, pCanvas->Handle, NULL );

    //キャンバスへ描画 この描画により Image へ表示される
    RECT rct = Image1->ClientRect;
    PlayEnhMetaFile( pCanvas->Handle, hEmf, &rct );

    delete pCanvas;
    delete[] pData;
    DeleteMetaFile( hWmf );
    DeleteEnhMetaFile( hEmf );
  }
}
//-----------------------------------------------------------------

    TFormメソッドの Printを使用すると枠が印刷されてしまう(99/2/3)

該当するバージョン:C++Builder 3

Q:

TFormメソッドの Printを使用して印刷しています。

Version1.0の時は、枠は印刷されませんでしたが 3.0になって左上に枠が印刷され、印刷状態が気になります。

A:

原因は Formのイメージを取得している関数 GetFormImage内の FillRectにあります。

FillRectの仕様で Topと Leftの領域が取得されるために黒い枠が印刷されます。(C++Builder1.0Jの場合は、実装が異なっています)

枠を印刷しないようにするためには、枠をはずした Bitmapを生成して印刷するようにしてください。

以下は実装の例です。

ヘッダー部

class TForm1 : public TForm
{
__published:    // IDE 管理のコンポーネント
    TButton *Button1;
    void __fastcall Button1Click(TObject *Sender);
private:  // ユーザー宣言
    void __fastcall PrintBitmap( Graphics::TBitmap *Bitmap );
public:        // ユーザー宣言
    __fastcall TForm1(TComponent* Owner);

};

ソース部

//-----------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  Graphics::TBitmap *formbmp, *temp;
  formbmp = new Graphics::TBitmap();
  temp = new Graphics::TBitmap();

  temp = GetFormImage();
  formbmp->Width = ClientWidth;
  formbmp->Height = ClientHeight;

  TRect tmpRec;
  tmpRec.Top   = 1;
  tmpRec.Left  = 1;
  tmpRec.Right = ClientWidth;
  tmpRec.Bottom = ClientHeight;

  formbmp->Canvas->CopyRect( ClientRect, temp->Canvas, tmpRec );

  Printer()->BeginDoc();
  PrintBitmap( formbmp );
  Printer()->EndDoc();

  delete formbmp;
  delete temp;
}
//-----------------------------------------------------------------
void __fastcall TForm1::PrintBitmap( Graphics::TBitmap *Bitmap )
{
  int infoSize, imageSize;
  int DIBWidth, DIBHeight, PrintWidth, PrintHeight;

  ::GetDIBSizes( Bitmap->Handle, infoSize, imageSize );
  TBitmapInfo *info = (TBitmapInfo *)new Byte[infoSize];
  void *img = new Byte[imageSize];
  try {
    GetDIB( Bitmap->Handle, 0, info, img );

    DIBWidth = info->bmiHeader.biWidth;
    DIBHeight = info->bmiHeader.biHeight;

    switch(PrintScale)
    {
      case poProportional:
        PrintWidth  = MulDiv( DIBWidth, GetDeviceCaps( Printer()->Handle,
                              LOGPIXELSX), PixelsPerInch );
        PrintHeight = MulDiv( DIBHeight, GetDeviceCaps( Printer()->Handle,
                              LOGPIXELSY), PixelsPerInch );
        break;

      case poPrintToFit:
        PrintWidth  = MulDiv( DIBWidth, Printer()->PageHeight, DIBHeight );
        if( PrintWidth < Printer()->PageWidth )
          PrintHeight = Printer()->PageHeight;
        else
          PrintWidth = Printer()->PageWidth;
          PrintHeight = MulDiv( DIBHeight, Printer()->PageWidth, DIBWidth );
        break;

      default:
        PrintWidth = DIBWidth;
        PrintHeight = DIBHeight;
    }

    StretchDIBits( Printer()->Canvas->Handle,
      0, 0, PrintWidth, PrintHeight,
      0, 0, DIBWidth, DIBHeight,
      img, info, DIB_RGB_COLORS, SRCCOPY );
  }
  catch( ... ) {
  }

  delete img;
  delete info;
}
//-----------------------------------------------------------------

    TImageListへリソースファイルからビットマップをロードしたい(99/2/3)

Q:

TImageListへリソースファイルからビットマップをロードしたい

A:

新しくリソースファイルを作成して下さい。

リソースの作成は ImageEditor で可能です。

作成されたリソースファイル(*.res) をプロジェクトに追加して下さい。

その後に、そのリソースファイル中のリソース名を

  ImageList1->ResourceLoad( rtBitmap, "FOLDER_BMP", clNone );

でロードして下さい。

ResourceLoadの第 3引数は、マスクする色を指定します。

背景色を透過させたい色を指定して下さい。

例: 背景色が黒の場合

  ImageList1->ResourceLoad( rtBitmap, "FOLDER_BMP", clBlack );

    データベースのフィールドに NULL値を設定したい(99/01/08)

Q:

データベースのフィールドに NULL 値を設定したい

A:

TFieldオブジェクトのメソッド Clear()を使用します。

  Table1->FieldByName("Column1")->Clear();

    TBitBtnの Glyphを塗りつぶしたい(99/01/08)

Q:

TBitBtnの Glyphを塗りつぶしたい

A:

TBitBtnの Glyphを変更など行う場合には、内部の Bitmapを変更して下さい。

以下例

    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
       Graphics::TBitmap *bmp = new Graphics::TBitmap;

       bmp->Assign( BitBtn1->Glyph );
       bmp->Canvas->Brush->Color = clRed;
       bmp->Canvas->FillRect(Rect( 0, 0, 50, 50));
       BitBtn1->Glyph = bmp;

       delete bmp;
     }

    配列のデータをバイナリフィールドに書き込みたい(99/01/08)

Q:

配列のデータをバイナリフィールドに書き込みたい

A:

バイナリ型の項目にデータをセットする場合は TBlobStreamを使用して下さい。

Table1 のフィールド "Data1" がバイナリ型と仮定

インサートの例

    const int MaxData=256;
    Byte      bData[MaxData];

    for( int cnt=0; cnt < MaxData; cnt++ )
    {
      bData[cnt] = cnt;  //任意のデータを設定
    }

    Table1->Open();
    Table1->Insert();

    TBlobStream *BlbStrm = new TBlobStream(
             (TBlobField *)Table1->FieldByName("Data1"), bmWrite );

    BlbStrm->Write( bData, MaxData );

    delete BlbStrm;
    Table1->Post();

    Table1->Close();

参照の例

    Table1->Open();
    Table1->First();

    TBlobStream *BlbStrm = new TBlobStream(
                (TBlobField *)Table1->FieldByName("Data2"), bmRead );

    int MemSize = BlbStrm->Size;
    Byte *bData = new Byte[MemSize];
    BlbStrm->Read( bData, MemSize );

    delete BlbStrm;
    Table1->Close();

    //ファイルに書き込んでデータが正しいか
    //確認するための処理
    ofstream fout("ByteFile.dat");    // #include <fstream.h>
    for( int cnt = 0; cnt < MemSize; cnt++ )
    {
      fout << bData[cnt];
    }

    delete[] bData;

    TDateTimePickerを使用してカレンダーを表示した後、カレンダーを選択しないと落ちます(99/01/08)

該当するバージョン:C++Builder 3

Q:

TDateTimePickerを使用してカレンダーを表示した後、そのカレンダーを選択せずに、他のコントロール等にフォーカスを持って行くとアプリケーションが落ちます

A:

TDateTimePicker を初期化して下さい。

    __fastcall TForm1::TForm1( TComponent *Owner ) : TForm(Owner)
    {
      NMDATETIMECHANGE nmDtc;
      SYSTEMTIME       stNow;

      GetSystemTime( &stNow );
      nmDtc.nmhdr.hwndFrom = DateTimePicker1->Handle;
      nmDtc.nmhdr.idFrom = NULL;
      nmDtc.nmhdr.code = DTN_DATETIMECHANGE;
      nmDtc.dwFlags = GDT_VALID;
      nmDtc.st = stNow;

      DateTimePicker1->Perform( WM_NOTIFY, NULL, (int)&nmDtc );
    }

    Windows NTのタスクマネージャに作成したアプリケーションのアイコンが表示されない(98/12/2)

該当するバージョン:C++Builder 3

Q:

Windows NT のタスクマネージャに作成したアプリケーションのアイコンが表示されない

A:

ウィンドウ(アプリケーション)クラスにアイコンを関連付けます。

例 : メインフォームの OnCreate イベント

    void __fastcall TForm1::FormCreate(TObject *Sender)
    {
        SetClassLong( Application->Handle,
                      GCL_HICON,
                      (long)Application->Icon->Handle );
    }

    TColor型の値を文字列(AnsiString)に変換したい(98/12/2)

Q:

TColor型の値を文字列(AnsiString)に変換したい

A:

ColorToString()関数を使用します。

また AnsiStringから TColor型に変換するには StringToColor()関数を使用します。

    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
        ShowMessage( ColorToString(Color) );
        Color = StringToColor("clRed");
    }

    Oracleのパッケージのプロシージャを使用したい(98/12/2)

Q:

TStoredProcで Oracleのパッケージのプロシージャを使用したいのですが、StoredProcNameのプルダウンに、名前が表示されません。

A:

StoredProcNameにパッケージ名から指定していただくと利用可能です。

パッケージ名 : PK1

プロシージャ名 : PRC1

   StoredProc1->StoredProcName = "PK1.PRC1";

もしくは、オブジェクトインスペクタのプロパティにそのまま PK1.PRC1(大文字で指定)と記述してください。

    NetMasters社のコンポーネントを使用していますが、終了時に例外が生成されます(98/12/2)

該当するバージョン:C++Builder 3,C++Builder 4

Q:

NetMasters社のコンポーネントを使用していますが、終了時に例外が生成されます。

回避方法はありますか?

A:

NetMasters社のコンポーネントのオンラインヘルプにありますように、OnDisconnectで VCLコンポーネントを参照する場合は、NULLで無いことを確認して下さい。

以下は簡単な例です。

void __fastcall TForm1::NMEcho1Disconnect(TObject *Sender)
{
   if( StatusBar1 != NULL )
     StatusBar1->SimpleText = "接続を切断";
}

    ストアドプロシージャを実行時に設定したい(98/11/2)

該当するバージョン:C++Builder 3,C++Builder 4

Q:

TStoredProcコンポーネントを使用し、実行時にストアドプロシージャを設定していますが、パラメータに値を設定すると"パラメータが見つかりません"とエラーになります。

A:

Prepareメソッド(ストアドプロシージャの実行準備)を使用し、パラメータをバインドさせます。

例 : Oracleのストアドプロシージャ"TEST_PROC(P1 IN VARCHAR2, P2 OUT VARCHAR2)"を実行します。

    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
        // Procedureの設定
        StoredProc1->StoredProcName = "TEST_PROC";

        // ストアドプロシージャの実行準備
        StoredProc1->Prepare();

        // Inputパラメータに値を設定
        StoredProc1->ParamByName("P1")->AsString = "PROCEDURE Test";

        // Procedureの実行
        StoredProc1->ExecProc();

        // Outputパラメータの値を取得
        ShowMessage( StoredProc1->ParamByName( "P2" )->AsString );

        // リソースを解放
        StoredProc1->UnPrepare();
    }

    TNMUDPの SendBufferメソッドの例を使用するとコンパイルエラー(98/11/2)

該当するバージョン:C++Builder 3

Q:

TNMUDPコンポーネントの SendBufferメソッドの例を使用するとコンパイルエラーになります。

  void __fastcall TForm1::Button1Click(TObject *Sender)
  {
      int BuffLen = 3;
      char *Buff = "Hi";
      NMUDP1->RemoteHost = Edit2->Text;
      NMUDP1->RemotePort = StrToInt(Edit1->Text);
      NMUDP1->SendBuffer(Buff, BuffLen);
  }

A:

TNMUDPコンポーネントの SendBufferメソッドの宣言は以下のようになります。

宣言

void SendBuffer(const char * Buff, const int Buff_Size, int length);

説明

SendBufferメソッドは,Buffパラメータで渡されたバッファをリモートホストに送信します。

パラメータ: Buffパラメータは,リモートホストに送信するバッファを指定します。

Buff_sizeパラメータは,バッファ Buffのサイズを指定します。

lengthパラメータは,送信データのサイズを指定します。

  void __fastcall TForm1::Button1Click(TObject *Sender)
  {
      char Buff[10];

      strcpy( Buff, "Hi" );
      NMUDP1->RemoteHost = Edit2->Text;
      NMUDP1->RemotePort = StrToInt(Edit1->Text);
      NMUDP1->SendBuffer(Buff, sizeof(Buff), strlen(Buff)+1);
  }

    ダイアログを複数作成するとエラー(98/10/05)

該当するバージョン:C++Builder 3

Q:

[ファイル|新規作成|ダイアログ]からダイアログボックス("ヘルプボタン付きダイアログ"や"標準ダイアログ")を複数作成しています。

他のユニットにヘッダーファイルをインクルードしてもコンパイルエラー"Undefined symbol ...."になります。

A:

作成したヘッダーファイルが正常にインクルードされないためです。

以下のようにダイアログのヘッダーファイルを変更してください。

変更前:

//------------------------------------------------------------------
#ifndef OCBH
#define OCBH
// ( 0CBHは作成したダイアログにより異なります )
//------------------------------------------------------------------
#include <ExtCtrls.hpp>

変更後:

//------------------------------------------------------------------
#ifndef UNIT2H
#define UNIT2H
// ( UNIT2Hはファイル名に対応させます )
//------------------------------------------------------------------
#include <ExtCtrls.hpp>

ダイアログの新規作成時に反映させるためには以下のヘッダファイルを変更をします。

"C++Builder3インストール先\Objrepos"
   OKCNHLP1.H
   OKCNHLP2.H
   OKCANCL1.H
   OKCANCL2.H
   DCEXAMPL.H (C/S版のみ)

例 : OKCNHLP1.Hの #ifndef/#defineを 変更

    //-----------------------------------
    #ifndef OKCNHLP1H
    #define OKCNHLP1H
    //-----------------------------------

OKCNHLP1Hは、それぞれのファイル名('.'を除く)にします。