VB6.0 動的に増やしたグリッドへの表示方法

タグの編集
投稿者 B子  (社会人) 投稿日時 2009/11/12 01:20:42
環境:VB6.0/Oracle9i/oo4o

いつもお世話になっています。
プログラムの勉強のため、Object Browserのようなツールを作成したいと考えています。
(実行したSELECT文を動的にGridを増やしてデータを参照したい。)

#dbGridなど簡単に作業できるようにADOでの接続を考えていましたが、
#接続しようとするとORA-01004のエラーが出て接続が出来きず
#設定が変更できない事情もあってADOは諦めてoo4oで接続しました。


フォームのdbGridのDataSourceプロパティにORACDを設定してグリッドの表示はうまくいきましたが、
プログラムで、DataSourceを変更したら、エラーになってしまいました。

DataSourceが変更できないのかな…と、今度はORACDとdbGridをやめてMSFlexGridでLoopする事にしました。
表示は無事に出来ましたが、今度はデータ量の多いテーブルは表示限度を超えてエラーになってしまいました。

今度はMSHFlexGridを使用でLoopしたらエラーにはなりませんでしたが、ありえないくらい表示に時間がかかってしまいました。


ORACDは早かったのですが、動的に増やすとなるとDataSourceが変更できないと駄目でしょうし
(実行時に増やしたコントロールの初期のプロパティじゃ駄目なので)
FlexGridでLoopをしても表示限度を超えてしまうし
MSHFlexGridでLoopだと遅すぎるし
と、ちょっと八方ふさがりになってしまいました。

いまさらな質問で大変恐縮ですが
何かほかにいい案はありませんでしょうか。
それともやっぱり無理でしょうか。

#MSHFlexGridで我慢するしかないのですかね。。。

説明が下手で申し訳ありませんが
ご教授いただけたら幸いです。

よろしくお願いします。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2009/11/12 03:04:05
> 今度はMSHFlexGridを使用でLoopしたらエラーにはなりませんでしたが、

ループを用いず、Clip プロパティを使って複数セルを一度にセットすると
高速にセットできます。

もし、ループで処理したいのであれば、Redraw プロパティを一時的に
False にしておいた状態で、TextMatrix プロパティを使うと良いでしょう。

あるいは、データバインドするという手もあります。ADO 接続が使えないなら、
OraDynaset を、非接続型 Recordset に入れなおしてから渡すという選択肢もあるかと。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2009/11/12 04:52:36
> フォームのdbGridのDataSourceプロパティにORACDを設定して

> DataSourceが変更できないのかな…と、今度はORACDとdbGridをやめて

ここでいう「ORACD」というのは、もしかして、
「ORADC」(Oracle Data Control) の事でしょうか?
だとしたら、動的変更は可能なはずですが…。


Private Sub Command1_Click()
    Load ORADC1(1)
    With ORADC1(1)
        .Connect = "user/password"
        .DatabaseName = "database"
        .RecordSource = SQL
        .Refresh
    End With
    Load DBGrid1(1) 'DataGrid は不可 
    With DBGrid1(1)
        .Move 0, 0, 5760, 2880
        .Visible = True
        Set .DataSource = ORADC1(1).Object   '.Object プロパティを忘れずに 
    End With
End Sub


RecordSource プロパティに SQL を指定する代わりに、
Recordset プロパティに OraDynaset オブジェクトを Set する事もできます。
投稿者 B子  (社会人) 投稿日時 2009/11/13 18:54:17
魔界の仮面弁士様、返信ありがとうございます。


現在は、MSHFlexGridの最大行・最大項目をすべてセットした後で、
RedrawをFalseにしてOraDynasetを全データをLoopにてClipで
セットしている感じです。
(データ量もそれなりにあるためこれが脅威的に遅いのです。。。)

TextMatrixのが遅いかなと思ってたのですがもしかして早いのでしょうか。。
ちょっと試してみます。


>「ORADC」(Oracle Data Control) の事でしょうか?

あっ。。
すいません、逆になってました。
そうです、「ORADC」の事です。


>.Object プロパティを忘れずに

これをつけてませんでした。
なんというミス、、、

つけたら無事にできました!


色々と調べつつやってるのですが、またつまずくかもしれませんので
何かあったら質問します。
たくさん試してみて自分なりにいい方法を考えたて頑張りたいと思います。
本当にありがとうございました。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2009/11/13 21:06:02
>> .Object プロパティを忘れずに
> これをつけてませんでした。
> なんというミス、、、
ActiveX コントロールを VB のフォーム上に貼ると、[Name プロパティ]や[Index プロパティ]など、
VB で管理するために追加拡張が加えられ、素のコンポーネントとは別のオブジェクトとなります。

.Object プロパティを用いることで、VB で拡張される前の素のオブジェクトを取得できます。


> OraDynasetを全データをLoopにてClipで
> セットしている感じです。
> (データ量もそれなりにあるためこれが脅威的に遅いのです。。。)
Clip は、複数セルに対して一括してデータを転送できるので、
それ自体にかかる時間は、さほど多くないと思います。

もしかして、ループ中で
 S = S & Col1 & vbTab & Col2 & vbCr
のような文字列連結を行っていませんか? だとすると、これは御法度です。

この場合、
 1. [S & Col1]に相当するメモリ領域[1]を一時確保し、そこに S & Col1 を転記。
 2. [1 & vbTab]に相当するメモリ領域[2]を一時確保し、そこに [1] & vbTab を転記。
 3. 使い終わったメモリ作業領域[1]を破棄する。
 4. [2 & Col2]に相当するメモリ領域[3]を一時確保し、そこに [2] & Col2 を転記。
 5. 使い終わったメモリ作業領域[2]を破棄する。
 6. [3 & Cr]に相当するメモリ領域[4]を一時確保し、そこに [3] & vbCr を転記。
 7. 使い終わったメモリ作業領域[3]を破棄する。
 8. 変数 S 上に、[4]に相当するメモリ領域を確保し、そこに [4] の内容を転記。
 9. 使い終わったメモリ作業領域[4]を破棄する。
という、メモリの確保と解放が繰り返されます。

このため文字列が長くなるにつれ、メモリ操作にかかるコストが無視できなくなってしまいます。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2009/11/13 21:15:56
>> (データ量もそれなりにあるためこれが脅威的に遅いのです。。。)
> Clip は、複数セルに対して一括してデータを転送できるので、
> それ自体にかかる時間は、さほど多くないと思います。

15000行のデータを Clip プロパティに渡して実験してみました。
同じデータであっても、その作り方の違いで、どのくらいの速度差があるか比べてみてください。


一応、当方の測定結果も示しておきます。

Command1 - 平均2.745秒 - 単純連結法
Command2 - 平均0.720秒 - 部分連結法
Command3 - 平均0.015秒 - Midステートメント法


Option Explicit
Private Const Limit As Integer = 15000    '検証データの件数 

Private Sub Form_Load()
    Command1.Caption = "単純連結法"
    Command2.Caption = "部分連結法"
    Command3.Caption = "Midステートメント法"
    Command4.Caption = "グリッドクリア"
    MSHFlexGrid1.FixedCols = 0
    MSHFlexGrid1.FixedRows = 1
    MSHFlexGrid1.Cols = 1
    MSHFlexGrid1.Rows = Limit + 1
End Sub

Private Sub Command4_Click()
    MSHFlexGrid1.Clear
End Sub

Private Sub Command1_Click()
    Dim t As Single     '経過時間測定用 
    t = Timer

    Dim n As Integer    'ループカウンタ 
    Dim s As String     '連結結果 

    For n = 1 To Limit
        s = s & CStr(n) & "行目" & vbCrLf
    Next
    
    'Debug.Print s 

    SetData s
    Debug.Print FormatNumber(Timer - t, 4) & "秒 - "; Command1.Caption
    MsgBox FormatNumber(Timer - t, 4) & "秒", vbInformation, Command1.Caption
End Sub

Private Sub Command2_Click()
    Dim t As Single     '経過時間測定用 
    t = Timer
    
    Dim n As Integer    'ループカウンタ 
    Dim s As String     '連結結果 

    s = ""
    For n = 1 To Limit
        s = s & (CStr(n) & "行目" & vbCrLf)
    Next

    'Debug.Print s 
    
    SetData s
    Debug.Print FormatNumber(Timer - t, 4) & "秒 - " & Command2.Caption
    MsgBox FormatNumber(Timer - t, 4) & "秒", vbInformation, Command2.Caption
End Sub

Private Sub Command3_Click()
    Dim t As Single     '経過時間測定用 
    t = Timer

    Const BlockSize As Long = 100000   '一回あたりのメモリ確保サイズ 
    Dim n As Integer    'ループカウンタ 
    Dim s As String     '連結結果 
    Dim p As Long       '現在の文字数 
    Dim data As String  '連結させる文字列 
    Dim l As Long       '連結させる文字列の長さ 
    s = ""
    p = 1
    For n = 1 To Limit
        data = (CStr(n) & "行目" & vbCrLf)
        l = Len(data)
        Do Until p + l <= Len(s)
            s = s & Space(BlockSize)
        Loop
        Mid(s, p, l) = data
        p = p + l
    Next
    s = Left(s, p)

    SetData s
    Debug.Print FormatNumber(Timer - t, 4) & "秒 - "; Command3.Caption
    MsgBox FormatNumber(Timer - t, 4) & "秒", vbInformation, Command3.Caption
End Sub

Private Sub SetData(ByVal Text As String)
    'Debug.Print Text 
    MSHFlexGrid1.Col = 0
    MSHFlexGrid1.Row = 1
    MSHFlexGrid1.RowSel = Limit
    MSHFlexGrid1.Clip = Text
    MSHFlexGrid1.RowSel = 1
End Sub
投稿者 (削除されました)  () 投稿日時 2009/11/13 22:06:25
(削除されました)
投稿者 B子  (社会人) 投稿日時 2009/11/13 22:24:46
魔界の仮面弁士様、ご丁寧な回答ありがとうございます。

>もしかして、ループ中で
> S = S & Col1 & vbTab & Col2 & vbCr
>のような文字列連結を行っていませんか? だとすると、これは御法度です。

わわ。。
おっしゃる通りです。

項目数が複数あるので、
s = s & CStr(n) & "行目" & vbTab & "二列目" & vbCrLf
のようにしたら、
s = s & (CStr(n) & "行目" & vbTab & "二列目" & vbCrLf)
上記の()付きの方が、驚くくらい早かったです。

そして、それよりもTextMatrixのが早くてさらにびっくりです。

こちらでの結果としては

 単純連結法     …9.9531秒
 部分連結法     …0.8750秒
 TextMatrix   …0.0781秒

となりました。
( Midステートメント法は、試してないです。。すみません。。。)


ものすごく勉強になりました。
ありがとうございます。

これなら、MSHFlexGridで全然問題ない気がしてきました。。
どちらを使うかまだ決めかねていますが、色々実験してみたいと思います。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2009/11/14 00:23:50
先の投稿が部分的に間違っていたので、修正のため、再度投稿しなおしておきます。

----------- 再投稿 ここから -----------
> TextMatrixのが遅いかなと思ってたのですがもしかして早いのでしょうか。。
> ちょっと試してみます。

先ほどと同じ件数(15000行)で試してみて、平均で 0.039 秒ぐらいでした。

Dim t As Single     '経過時間測定用  
t = Timer

Dim n As Integer
MSHFlexGrid1.Redraw = False
For n = 1 To Limit
    'MSHFlexGrid1.TextMatrix(n, 0) = CStr(n) & "行目" & vbCrLf   '★間違い 
    MSHFlexGrid1.TextMatrix(n, 0) = CStr(n) & "行目"
Next
MSHFlexGrid1.Redraw = True

Debug.Print FormatNumber(Timer - t, 4) & "秒"
MsgBox FormatNumber(Timer - t, 4) & "秒", vbInformation

----------- 再投稿 ここまで -----------

なお、さらにその前に投稿した、Clip を使う方法についても、
> s = s & CStr(n) & "行目" & vbCrLf
のように、うっかり vbCrLf 区切りになってしまっていますので、
 s = s & CStr(n) & "行目" & vbCr
などの vbCr 区切りに読み替えておいてください。m(_ _;)m

=============================================================

> 上記の()付きの方が、驚くくらい早かったです。
「& 演算子」は、文字数が増えるほどに低速化していきます。
括弧を付けることで、短い文字列の連結を先に済ましてやると、
時間のかかる「長い文字列連結」の回数を減らすことができるというわけです。


> そして、それよりもTextMatrixのが早くてさらにびっくりです。
今回は、TextMatrix を使った方が望ましいでしょう。

Mid+Clip 法と、TextMatrix 法の処理速度は殆ど同じですし、
何よりも TextMatrix の方が分かり易く、融通も利きますからね。
(先の条件を 100万件にして比較したところ、どちらも 3秒弱で完了しました)


MSHFlexGrid.TextMatrix や DbGrid.DataSource を使うのであれば、
今回の Clip 法の出番は無いかと思いますが、「高速な文字列連結処理」というのは、
他の場所で使う機会があるかも知れないので、それぞれの実装法と
その速度の違いを覚えておくと、後々役に立つかと思います。


> ( Midステートメント法は、試してないです。。すみません。。。)

Mid ステートメントは、「文字列の一部を差し替える」ための物です。
S = "abcdefghij"
Mid(S, 4, 3) = "XYZ"
Debug.Print S   '「abcXYZghij」になる。 

これを文字列連結処理に応用したのが先のサンプルです。
Mid を使うと、最初に確保したメモリ領域が使いまわされる為、長い文字列でも処理速度が低下しません。


ただし、最初に充分な文字列サイズを確保しておかなければならないため、
データが固定長の場合には良いですが、今回のような可変長データだと、
(先のコードのような)やや分かりにくいコードになってしまうのが難点です。


なお、高速化が望める文字列連結法としては、他にも

 (案1) 外部ファイルを使う方法
   →ループ中で Print # でテキスト追記。最後に Get # で一括バイナリ読み込み。
 (案2) Dictionary + Join 関数を使う方法
   →ループ中で.Add メソッドで追記。最後に .Items メソッドを Join 関数で結合。
 (案3) ADODB.Stream のバイナリモードを使う。
   →.Write メソッドで追記。最後に先頭位置から .Read メソッドで一括読み込み。

などがあります。Mid ステートメント法ほどの速度は出ませんけれどね。
(1 や 3 で得たバイナリは、最後に StrConv 関数で文字列に復元させます)
投稿者 B子  (社会人) 投稿日時 2009/11/14 02:29:57
魔界の仮面弁士様、丁寧な回答ありがとうございます。


> 括弧を付けることで、短い文字列の連結を先に済ましてやると、
> 時間のかかる「長い文字列連結」の回数を減らすことができるというわけです。

なるほど、、
恥ずかしながら、今まで括弧について特に深く考えていませんでした。
こんなにも処理時間に差がでるとは、、、
これからは、気をつけて見てみたいと思います。


> 今回は、TextMatrix を使った方が望ましいでしょう。

今までTextMatrixをよく使用していたのですが、イメージ的にClip のが早いと感じていて、、
「百聞は一見にしかず」
やはり、実際動かしてみるのが一番でしたね。。。反省です。


> Mid を使うと、最初に確保したメモリ領域が使いまわされる為、長い文字列でも処理速度が低下しません。

最初に領域をとっておいて、どんどん書き換えていくイメージでいいのでしょうか。
うーん、、
確かに、可変長だと分かりにくくなってしまうかもしれないですね。


その他の案に関してもとても勉強になりました。
文字列連結法だけでも、こんなにあるのかと、、びっくりしてます。
(案3については、初めてみました。。。)

まだまだ、勉強することはたくさんなので、これからも精進していきたいと思います。

ありがとうございました。