VisualStyle なボタンの描画

1月 25th, 2008

OnPaint などでボタンの背景などを自前で描画するために System.Windows.Forms 名前空間の ControlPaint.DrawButton というメソッドが用意されています。が、これでボタンを描画するとWindows 2000 以前で標準だった 四角いVisualStyleがOFFなボタンしか描画できません。

Windows XP で採用された、角が丸いボタンを描画するには、 System.Windows.Forms の ButtonRenderer を使えばよいようです。他にも CheckBoxRenderer とか ComboBoxRenderer とか用意されているので、大抵のコントロールは ????Renderer で描画できます。

ちなみに、これらRenderer クラスは、System.Windows.Forms.VisualStyles 名前空間の VisualStyleRenderer のラッパーになっているようです。複雑なコントロールを描画するときには、VisualStyleRendererを駆使すれば大丈夫だと思われます。

Windows DNSキャッシュのクリア

11月 29th, 2007
ipconfig /flushdns

でDNSキャッシュクリア。

他にも

ipconfig /displaydns

でDNSキャッシュの内容を確認できます。

WMIでの日時データの扱い(SWbemDateTimeとCIM_DATETIME)

5月 30th, 2007

WMIで日時データを扱う場合は、SWbemDateTimeを利用する方法が簡単です

MSDN: WbemDateTime

Delphiだと、WbemScripting_TLB を uses して、こんな感じで使えます。


procedure Sample;
var
  I:Integer;
  line:string;
  Locator:ISWbemLocator;
  Services:ISWbemServices;
  ResultSet:ISWbemObjectSet;
  Enum:IEnumVariant;
  RowObj:OleVariant;
  Value:LongWord;
  wbemDateTime:ISWbemDateTime;
begin
  Locator:=CoSWbemLocator.Create as ISWbemLocator;
  Services:=Locator.ConnectServer(
    '.', '', '', '', '', '', 0, nil);
  ResultSet:=Services.ExecQuery(
    'SELECT * From Win32_NTEventLog', 'WQL',
    wbemFlagReturnImmediately,nil);
  Enum:=ResultSet._NewEnum as IEnumVariant;

  // ISWbemDateTimeオブジェクトを生成
  wbemDateTime:=CosWbemDateTime.Create as
    ISWbemDateTIme;

  for I:=1 to ResultSet.Count do
  begin
    Enum.Next(1, RowObj, Value);
    if Value <= 0 then Break;
    if VarIsNull(RowObj) then Continue;
    // WBemDateTimeに値を代入
  wbemDateTime.Value:=RowObj.TimeWritten;
  // フォーマット
    line:=Format('%.4d/%.2d/%.2d %.2d:%.2d:%.2d.%.3d',
      [wbemDateTime.Year, wbemDateTime.Month,
       wbemDateTime.Day, wbemDateTime.Hours,
       wbemDateTime.Minutes, wbemDateTime.Seconds,
       wbemDateTime.MicroSeconds*1000]);
    ShowMessage(line);
  end;
end;

ISWbemDateTimeオブジェクトを作って、ValueプロパティにWQLの検索結果の日時データを設定するだけです。逆に、TDateTime型の値をISWbemDateTimeに設定/取得する場合は、GetVarDate, SetVarDateを利用します。

しかし、SWbemDateTimeクラスはWindows XP以降にしかないので、Win 2000 などでも動かそうとすると日時データを自前で処理する必要があります。
ソフト作ってるときにWinXPで動いていたのにWin2000 に持ってくと 「クラスが登録されていません」 と手がかりなしのエラーを吐かれて、SWbemDateTimeが使えないことに気づくまでかなりはまりました。

WMIで利用される日時データは、CIM_DATETIME という形式のものです。

MSDN: CIM_DATETIME

上記、MSDNのページを見ればCIM_DATETIMEが


yyyymmddHHMMSS.mmmmmmsUUU

と定義されていることが分かります。 mmmmmm はマイクロ秒、sUUUはタイムゾーンを表す数値でUTCからの差を分で表します。日本だと +540 になります。

Delphi上では、CIM_DATETIMEはwideStringとして処理されるため、SWbemDateTimeを使わなくてもCIM_DATETIMEに変換できます。


function SystemTimeToWbemDateTime(
  const localTime:TSystemTime):wideString;
var
  utcTime:TSystemTime;
  fileTime,localFileTime: TFileTime;
begin
  SystemTimeToFileTime(localTime, localFileTime);
  LocalFileTimeToFileTime(localFileTime, fileTime);
  FileTimeToSystemTime(fileTime, utcTime);

  Result:=Format(
    '%4.4u%2.2u%2.2u%2.2u%2.2u%2.2u.%6.6u+000',
    [utcTime.wYear, utcTime.wMonth, utcTime.wDay,
     utcTime.wHour, utcTime.wMinute, utcTime.wSecond,
     utcTime.wMilliSeconds*1000]);
end;

TDateTimeだとミリ秒以下が扱えないので、ここではTSystemTimeからCIM_DATETIMEに変換してます。タイムゾーン部分を現在のタイムゾーンのUTCからの差を算出するのが面倒だったので、TFileTimeを経由してUTC時刻に変換してあります。これだと時差が +000 ですみます。

参考: How To Convert from GMT(UTC) Time to Local Time

 

逆に、CIM_DATETIMEからTSystemTimeに変換するには時差の扱いを考えないといけません。手順としては、

  1. CIM_DATETIMEの日時(年月日時分秒マイクロ秒)情報からTFileTimeオブジェクトを生成
  2. CIM_DATETIMEの時差情報から現在のタイムゾーンとCIM_DATETIMEのタイムゾーンとの時差(分差)を算出
  3. 分差に基づいてTFileTimeの値を修正
  4. TFileTimeからTSystemTimeに変換

といった感じでしょうか。
今回作ったソフトは時刻を表示すればよいだけだったので、CIM_DATETIMEを単純に文字列として扱ってフォーマットの変更をしました。


function FormatWbemDateTime(
  const wbemDateTime:wideString):string;
begin
  Result:=wbemDateTime;   // yyyymmddHHMMSS.mmmmmmsUUU
  Delete(Result, 19, 3);  // yyyymmddHHMMSS.mmmsUUU
  Insert(':', Result,13); // yyyymmddHHMM:SS.mmmsUUU
  Insert(':', Result,11); // yyyymmddHH:MM:SS.mmmsUUU
  Insert(' ', Result,9);  // yyyymmdd HH:MM:SS.mmmsUUU
  Insert('/', Result,7);  // yyyymm/dd HH:MM:SS.mmmsUUU
  Insert('/', Result,5);  // yyyy/mm/dd HH:MM:SS.mmmsUUU
end;

まぁ、こんな逃げ方もあるということで。

Delphi で WMI

5月 22nd, 2007

最近はDelphiを使うことが多くなりました。VC6 でアプリを作っていたのと比べると、はるかに楽です。

少し前にDelphiでWMIを使うことがあったので、そのサンプルを乗せておきます。
WMIは、WMI(Windows Management Instrumentation)の略で、Windows OS上のさまざまな情報を取得、更新するためのものです。
OSやハードウェアの情報などを取得するのにWin32のAPIを直接操作してもいいんですが、それだと各情報ごとにAPIを調べて処理方法を変更しなければ行けません。WMIだとWQLというSQLのサブセットみたいなクエリを使って、いろんな情報を同じ方法で処理できます。

 

例えば、以下のような情報が取れます。

BIOS情報を取得する
Select * From Win32_BIOS
結果は、
Select * From Win32_BIOS
------------------------------------
BiosCharacteristics=[4,7,8,9,11,12,15,16,24,26,27,
28,29,30,32,33,34,39,40,41,48,49,50,51,52,53,54,
55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,
71,72,73,74,75,76,77,78,79]
BIOSVersion=[DELL - 27d6040b,Phoenix ROM BIOS PLUS Version 1.10 A02]
BuildNumber=
Caption=Phoenix ROM BIOS PLUS Version 1.10 A02
CodeSet=
CurrentLanguage=en|US|iso8859-1
Description=Phoenix ROM BIOS PLUS Version 1.10 A02
IdentificationCode=
InstallableLanguages=1
InstallDate=
LanguageEdition=
ListOfLanguages=[en|US|iso8859-1]
Manufacturer=Dell Inc.
Name=Phoenix ROM BIOS PLUS Version 1.10 A02
OtherTargetOS=
PrimaryBIOS=-1
ReleaseDate=20060411000000.000000+000
SerialNumber=6VX74BX
SMBIOSBIOSVersion=A02
SMBIOSMajorVersion=2
SMBIOSMinorVersion=4
SMBIOSPresent=-1
SoftwareElementID=Phoenix ROM BIOS PLUS Version 1.10 A02
SoftwareElementState=3
Status=OK
TargetOperatingSystem=0
Version=DELL - 27d6040b
------------------------------------

メモリの情報を取得する
Select * From Win32_PhysicalMemory
結果は、
Select * From Win32_PhysicalMemory
------------------------------------
BankLabel=
Capacity=536870912
Caption=物理メモリ
CreationClassName=Win32_PhysicalMemory
DataWidth=64
Description=物理メモリ
DeviceLocator=DIMM_A
FormFactor=8
HotSwappable=
InstallDate=
InterleaveDataDepth=0
InterleavePosition=0
Manufacturer=
MemoryType=0
Model=
Name=物理メモリ
OtherIdentifyingInfo=
PartNumber=
PositionInRow=1
PoweredOn=
Removable=
Replaceable=
SerialNumber=
SKU=
Speed=533
Status=
Tag=Physical Memory 0
TotalWidth=64
TypeDetail=128
Version=

BankLabel=
Capacity=536870912
Caption=物理メモリ
CreationClassName=Win32_PhysicalMemory
DataWidth=64
Description=物理メモリ
DeviceLocator=DIMM_B
FormFactor=8
HotSwappable=
InstallDate=
InterleaveDataDepth=0
InterleavePosition=0
Manufacturer=
MemoryType=0
Model=
Name=物理メモリ
OtherIdentifyingInfo=
PartNumber=
PositionInRow=1
PoweredOn=
Removable=
Replaceable=
SerialNumber=
SKU=
Speed=533
Status=
Tag=Physical Memory 1
TotalWidth=64
TypeDetail=128
Version=
------------------------------------

メモリは2枚ささっているので、2枚分出ていることが分かります。

WQLのFrom句の部分を変えるだけで、他にもCPU, OS, 論理ディスク, 物理ディスク, サウンドボード, インストール済みソフトウェア, ….. とコントロールパネルから手に入りそうな情報は一通り入手できます。取得できる情報の一覧は、MSDNの以下のページからたどれます。
MSDN: Win32 Classes

 
 

で、このWMIをDelphiから使うのもとても簡単です。
1. タイプライブラリの取り込み
WMI用のタイプライブラリをDelphi環境に取り込みます。
Delphiを起動して、上部メニューのプロジェクト ->タイプライブラリの取り込みをクリック
Microsoft WMI Scripting V1.2 Library (Version 1.2) を選択して、下のインストールボタンをクリック
WMI タイプライブラリ取り込み
インストール先は、新規に適当な名前のパッケージを作ればOK

2.以下のコードを適当な場所にコピー&ペースト


//  uses
//     OleServer, WbemScripting_TLB, ComObj, ActiveX
// が必要

// varArray も扱えるようにした VarToStr
function VarToString(const oleVar:oleVariant):string;
var
  lowBound, highBound, I:Integer;
begin
  // 配列の場合
  if VarIsArray(oleVar) then
  begin
    Result:='[';
    LowBound:=VarArrayLowBound(oleVar,1);
    HighBound:=VarArrayHighBound(oleVar,1);
    for I:=lowBound to highBound do
    begin
      if I=highBound then
        Result:=Result+VarToString(oleVar[I])
      else
        Result:=Result+VarToString(oleVar[I])+',';
    end;
    Result:=Result+']';
  end else
  begin
    Result:=VarToStr(oleVar);
  end;
end;

// WMI経由で情報を取得する
function SelectWMI(wql:string):string;
var
  I,J:Integer;
  line:string;
  Locator:ISWbemLocator;
  Services:ISWbemServices;
  ResultSet:ISWbemObjectSet;
  Enum, Enum2:IEnumVariant;
  RowObj, ColObj:OleVariant;
  Value:LongWord;
begin
  Locator:=CoSWbemLocator.Create as ISWbemLocator;
  Services:=Locator.ConnectServer('.', '', '', '', '', '', 0, nil);
  ResultSet:=Services.ExecQuery(wql, 'WQL',
    wbemFlagReturnImmediately,nil);
  Enum:=ResultSet._NewEnum as IEnumVariant;

  line:=wql+#13#10#13#10;
  for I:=1 to ResultSet.Count do
  begin
    Enum.Next(1, RowObj, Value);
    if Value <= 0 then Break;
    if VarIsNull(RowObj) then Continue;
    Enum2 := IUnknown(RowObj.Properties_._NewEnum)
      as IEnumVariant;

    for J:=1 To RowObj.Properties_.Count do
    begin
      Enum2.Next(1, ColObj, Value);
      if Value <= 0 then Break;
      if VarIsNull(ColObj) then Continue;
      line:=line+Format('%s=%s'+#13#10,
        [VarToString(ColObj.Name), VarToString(ColObj.Value)]);
    end;
    line:=line+#13#10;
  end;
  Result:=line;
end;

3. 適当なWQLを作って、SelectWMIを呼び出す。
以上で、完成。

 

関連リンク

NetBeansのRuby対応がすごい

3月 15th, 2007

デモ
確かにすごい

隠し共有、管理共有

3月 13th, 2007

昨日、初めて知ったんですが、Windowsには隠し共有という仕組みがあるそうです。

隠し共有
ネットワーク上のほかのコンピュータへフォルダの中身を公開することができるのフォルダの共有機能ですが、この共有名の末尾に$をつけて共有を行うとそのフォルダは隠し共有フォルダになります。

隠し共有フォルダはマイネットワークや、ネットワーク上の共有リソース一覧を表示する”NET VIEW” コマンドでは表示されません。だから、他マシンのユーザはそんな共有があることすら分かりません。ただし、同じマシンのユーザは”NET SHARE”コマンドもしくは[管理ツール->コンピュータの管理->共有フォルダ->共有]でそのマシンの共有一覧(隠し共有も含む)を取得することができます。

管理共有
さらに、Windows NT, Windows 2000, Windows XP(Home Editionを除く)にはデフォルト管理共有という隠し共有があります。

上記OSでは、インストールが完了した時点で、各ドライブ(共有名: C$, D$ など)と、%SystemRoot%(共有名: Admin$)という隠し共有が作られた状態になっていて、Administrator権限でログインが可能!です。コマンドプロンプトから”NET SHARE”を行うと、

デフォルト管理共有

↑のように、C$、ADMIN$、IPC$という隠し共有があることが分かります。(IPC$というのは、管理通信用の名前付きパイプのようです。)
これらは本来管理者がリモートからマシンの管理を行えるように作られているものなんですが、デフォルトで共有されちゃうのでAdministratorのパスワードを知っていればマシンの中身が全部見えるという便利なような危ないような状態になっています。

まとめ

  • WinNT, 2000, XP ではドライブ全部がデフォルトで隠し共有されていて、Administratorでログインできる
  • 複数台PC使っているような状況ではわざわざ特定のフォルダを共有しなくても、\\マシン名\C$ とかで、そのまま各ドライブにそのままアクセスできて便利!
  • Administratorsのパスワードを知られちゃうと全部見られちゃうので、パスワードは複雑に

関連リンク
MS サポートオンライン: 最後の「$」文字での共有名は、非表示になります
@IT Windows Tips: デフォルト共有(管理共有)を停止させる方法
winxp.FAQ 管理共有 (C$ など) を使用禁止するには?

アメリカ・カナダ夏時間変更

3月 7th, 2007

今年から、アメリカとカナダの夏時間の期間が変更になるそうです。

これまでの夏時間期間:4月の第1日曜日 - 10月の最終日曜日
これからの夏時間期間:3月の第2日曜日 - 11月の第1日曜日

夏時間の期間が4週間も長くなります。
今年の夏時間の開始は、2007年3月11日から。会社では今頃(夏時間開始4日前)になってこの事を知り、北米向け出荷装置への対応で大変です。

絶対に便器!

2月 28th, 2007

お昼休みに会社で、ボーっとWEB見てたら吹き出しそうになりました。。。。
CodeZine: Firefoxのキャッシュを一覧表示できるエクステンション「CacheViewer」

記事の内容はどうでもいいんですが、最後のところにわざわざ背景色まで変えて書いてありました。
「絶対に便器!」

一応スナップショット取っときましたので、修正されてたら↓を参照。
CodeZine Typo

びっくりマークまでつけてあるって事は、何か主張したいんだと思いますが、いったい何が絶対に便器なのかがさっぱりわかりません。普通に生きたら、たぶん一生この言葉は口にしないと思います。
せっかくなので、無い頭を絞ってそんな状況を考えてみました。

田中: おれ、もうウンコ漏れそうだよ。 だめだ、我慢できね。ここでしていい?
竹花: だめ!絶対に便器!

いやはや、非常に奥が深い言葉です。

プレースバー?

2月 8th, 2007

前のエントリを書いた後、色々探していたらTweakUI を思い出しました。
まだインストールしたことなかったなと思い、ためしに入れてみました。
ダウンロードはしたのページの、右カラム真ん中あたりのTweakUI.exe。
Microsoft PowerToys for Windows XP

中をさらっと見てみると、Place Bar という設定がありました。調べてみるとplace barとは、ファイルを開いたり保存したりするときに出てくるダイアログの左側にあるバーのことのようです。「デスクトップ」「マイドキュメント」「マイピクチャ」「マイミュージック」などがおっきなアイコンで表示されているあれです。(下下図参照)

TweakUI を使うと、このPlaceBarに表示されるフォルダを変更することができます。プレースバーにはログインユーザごとに最大5個までフォルダを登録できるようです。
生まれてこのかたこの「マイピクチャ」や「マイミュージック」ボタンを押したことなんてありませんし、この先も押すことはないでしょう。ということで、こいつらをよく使うフォルダに設定してみました。

設定の仕方はこんな感じ。
TweakUI PlaceBar Setting
下三角を押すと候補のセレクトボックスが表示されますが、直接フォルダパスを入力すれば、好きなフォルダを登録できます。

設定した結果はこんな。
PlaceBar

TweakUIでなくても、プレースバーで検索すると変更できるソフトがいくつか出てきます。
以下、参考リンク
プレースバーを非表示にする
TweakUI の日本語化

デスクトップとマイドキュメントの移動方法

2月 8th, 2007

そもそも、デスクトップやマイドキュメントのフォルダ位置を移動できることを知らなかった。。。

マイドキュメントの移動方法
デスクトップの場所を変更する方法

デスクトップのフルパスが短くなって便利。
そもそもC:\Documents and Settings\myaccount フォルダを丸ごと他に移動できないかと思って探したけれど、簡単には(レジストリいじったりしないと)できないみたい。

MSFlexGridなどのActiveXコンポーネントを他のPCで使う

2月 2nd, 2007

Visual C++ に含まれているMSFlexGridを使ってアプリを作成したんですが、他のPCで動かしてみると “Can’t Create MSFlexGrid” とメッセージボックスが表示され、アプリは起動するんですがフレックスグリッドは空のままという現象が発生しました。開発環境は(VC6 SP6)

で、これについて色々調べると、どうも一部のActiveXコンポーネントを他のPC(VC環境が入っていないPC?)で使うには、プログラム上でコンポーネントをCreateするときにライセンスキーというものを渡してやる必要があるそうです。この一部のActiveXコンポーネントとは、

Visual C++ 6.0 で提供される以下の再配布可能なコントロールはライセンスの対象となっており、Visual C++ がインストールされていないコンピュータで動的に作成するには、ライセンス キーが必要です。

  • anibtn32.ocx アニメーション ボタン コントロール
  • keysta32.ocx キー ステータス コントロール
  • mci32.ocx マルチメディア MCI コントロール
  • mscomm32.ocx コミュニケーション コントロール
  • msflxgrd.ocx フレキシブル グリッド コントロール
  • msmask32.ocx マスク エディット コントロール
  • grid32.ocx グリッド コントロール
  • picclp32.ocx ピクチャ クリップ コントロール
  • tabctl32.ocx タブ コントロール

だそうです。
MS サポート: Visual C++ で提供されている再配布可能な ActiveX コントロールのインスタンスを動的に作成することができない

この問題はActieveXコンポーネントを Createするときに、ライセンスキーを引数で渡してやれば解決します。手順は以下のとおり。

  1. 該当のライセンスキーを調べるためのツール(Licreqst.exe)をMSのサイトからダウンロード
    ↓のページの最初の方にあるLicreqst.exe というツールをダウンロードします。ページでは、ライセンス キーを要求する方法を示す”サンプル”と書いてありますが普通に動きます。
    http://support.microsoft.com/kb/151771/ja
    licreqst.exe ダウンロードページ
  2. ダウンロードしたexeを実行して、適当なフォルダに解凍
  3. 解凍されたフォルダの中のLicreqst.exeを実行し、使おうとしているActiveXコンポーネントを選択。Copy Data To Clipboardボタンをクリック
    ↓はMSFlexGridを選択したところ。
    Licreqst.exe 実行画面
  4. コンポーネントのCreate時にライセンスキーを渡すようにコードを変更。
    もともと↓こうだったのを、

    
    // CMSFlexGrid flexGrid;
    if (flexGrid.Create(NULL, WS_VISIBLE|WS_CHILD,
            rect, this, IDC_MSFLEXGRID) == 0) {
        AfxMessageBox("Can't create MSFlexGrid");
    }
    

    ↓こんな風に変更

    
    /*
    2c49f800-c2dd-11cf-9ad6-0080c7e7b78d
    */
    
    WCHAR pwchLicenseKey[] =
    {
    	0x0032,	0x0063,	0x0034,	0x0039,	0x0066,	0x0038,
    	0x0030,	0x0030,	0x002D,	0x0063,	0x0032,	0x0064,
    	0x0064,	0x002D,	0x0031,	0x0031,	0x0063,	0x0066,
    	0x002D,	0x0039,	0x0061,	0x0064,	0x0036,	0x002D,
    	0x0030,	0x0030,	0x0038,	0x0030,	0x0063,	0x0037,
    	0x0065,	0x0037,	0x0062,	0x0037,	0x0038,	0x0064
    };
    BSTR bstrLicense = ::SysAllocStringLen(
        pwchLicenseKey, sizeof(pwchLicenseKey)/sizeof(WCHAR));
    if (flexGrid.Create(NULL, WS_VISIBLE|WS_CHILD, rect, this,
            IDC_MSFLEXGRID, NULL, FALSE, bstrLicense) == 0) {
        AfxMessageBox("Can't create MSFlexGrid");
    }
    ::SysFreeString(bstrLicense);
    

    ちなみに、上のライセンスキーでは動きません。

通常のCWnd::Createはライセンスキーを引数には受け取りません。ActiveXコンポーネントを追加した際に生成されるクラスの中で、Createがオーバーライドされているためです。ちなみに、このオーバーライドされたCreateの中では、CWnd::CreateControlを呼んでいます。
MSHFlexGrid コンポーネントを追加したときに生成されるクラス CMSHFlexGrid のCreateメソッド。


virtual BOOL Create(LPCTSTR lpszClassName,
    LPCTSTR lpszWindowName, DWORD dwStyle,
    const RECT& rect,
    CWnd* pParentWnd, UINT nID,
    CCreateContext* pContext = NULL)
{ return CreateControl(GetClsid(), lpszWindowName,
    dwStyle, rect, pParentWnd, nID); }

BOOL Create(LPCTSTR lpszWindowName, DWORD dwStyle,
    const RECT& rect, CWnd* pParentWnd, UINT nID,
    CFile* pPersist = NULL, BOOL bStorage = FALSE,
    BSTR bstrLicKey = NULL)
{ return CreateControl(GetClsid(), lpszWindowName,
    dwStyle, rect, pParentWnd, nID,
    pPersist, bStorage, bstrLicKey); }

 
 

ライセンスキーとは別の問題として、ActeiveX コンポーネントを他のPCで動作させるには、対応するOCXファイルをレジストリに登録しておく必要があります。
正しい方法は、vector とかからVB6用のランタイムライブラリをインストールするか、もしくはインストーラーを作成してその中でレジストリに登録する方法のようです。
ただし、ちょっと使いたいっていうだけだと該当するOCXファイルを動かしたいマシンにコピーした上で、コマンドプロンプトからregsvr32.exe を実行してレジストリに登録することもできます


regsvr32 PATH_TO_OCX_FILE
# msflexgridだと
regsvr32 MSFLXGRD.OCX

ただし、この方法は再配布のライセンスに違反するような気がするので(しっかり調べたわけではない。。)お試し程度に使ってください。
regsvr32.exe を実行した際に更新されるレジストリについては、↓を参照
http://support.microsoft.com/kb/183771/ja

関連:
MSFlexGridの追加の仕方
MSFlexGridの使い方