tagCANDY CGI VBレスキュー(花ちゃん)の Visual Basic 6.0用 掲示板 [ツリー表示へ]   [Home]
一括表示(VB6.0)
タイトルExcel処理の繰り返しでNG
記事No14461
投稿日: 2010/02/11(Thu) 16:35
投稿者ぽよ〜ん
いつも参考にさせて頂いております。
VB6からExcelファイルにアクセスして、行を選択した後、繰り返しコピーを
行ないたいのですが、1回目は正常?に動いているように見えますが、
プログラムを起動したまま再度繰り返すと目的の処理がうまく行えません。
チョッとした設定ミスがあるかも知れませんが相当悩んでおります。
解決方法をご教授頂けたらと思います。宜しくお願い致します。

Private Sub Command1_Click()
Dim i As Integer
Dim xlApp As Excel.Application
Dim xlBook As Excel.Workbook
Dim xlSheet As Excel.Worksheet
Set xlApp = CreateObject("Excel.Application")
Set xlBook = xlApp.Workbooks.Open(App.Path & "\xlTestFile.xls")
Set xlSheet = xlBook.Worksheets("Sheet1")
'xlTestFile.xls は予め用意をしておく
'B5:E10 まで格子状に罫線を引いただけのファイルで保存しておく
    On Error Resume Next
    ' Excel画面を非表示にして実行
    xlApp.Visible = True
    
    With xlSheet
        .Activate
        For i = 1 To 5
            .Rows("10:10").Select   '行を選択
            With Selection          'コピーして下へ
                .Copy
                .Insert Shift:=xlDown
            End With
        Next
        ' 下線を二重線で引く
        .Range("B15:E15").Select
        With Selection.Borders(xlEdgeBottom)
            .LineStyle = xlDouble
            .Weight = xlThick
            .ColorIndex = xlAutomatic
        End With
    End With

    '保存時の問合せを非表示に設定
    xlApp.DisplayAlerts = False
    ' オブジェクトを解放します。
    Set xlSheet = Nothing
    xlBook.Close            'Book を閉じる
    Set xlBook = Nothing
    ' Quit メソッドを使って Excel を終了します。
    xlApp.Quit
    Set xlApp = Nothing
    
End Sub

Private Sub Command2_Click()
    End
End Sub

[ツリー表示へ]
タイトルRe: Excel処理の繰り返しでNG
記事No14462
投稿日: 2010/02/11(Thu) 18:38
投稿者魔界の仮面弁士
> Dim xlApp As Excel.Application
> Set xlApp = CreateObject("Excel.Application")
参照設定している場合は、
 Set xlApp = New Excel.Application
と書けますよ。

参照設定していない場合は、
   Dim xlApp As Object
   Set xlApp = CreateObject("Excel.Application")
のように、CreateObject が必要ですけれどね。

> xlApp.Visible = True
> With xlSheet
ここまでは良いですが…

>   .Rows("10:10").Select   '行を選択
>   With Selection          'コピーして下へ
ここが NG。
ここで「Selection」と書いていますが、対象オブジェクトが指定されていません。
どのオブジェクトの Selection プロパティなのか、親オブジェクトを明記しましょう。

たとえば仮に、このアプリから Excel を同時に 2 つ起動していたとしたらどうでしょう。
その場合、「xlSheet.Rows("10:10")」であれば、変数 xlSheet の方の Excel を
操作しているのだと分かりますが、これが「Rows("10:10")」や「Selection」のように
親オブジェクト無しで指定されると、どちらを操作しようとしているかが、
曖昧になってしまいますよね。

親オブジェクトが指定されなかった場合、暗黙の Excel インスタンスが
グローバル オブジェクトとして参照される事になります。Excel VBA であれば、
「自分自身」を操作するだけなので問題はありませんが、外部(VB6 等)から
操作する場合には、親オブジェクトを必ず指定する必要があります。

親オブジェクトの指定漏れは、今回のような 2 回目以降の処理が行われないという
問題だけではなく、Excel が終了しない(Excel が非表示のまま残ってしまう)という、
解放漏れの障害をも引き起こしますので、十分に注意してください。


なお、参照設定を使わない場合には、このような親オブジェクトの指定漏れの箇所が
「オブジェクトが必要です」のコンパイルエラーになるため(Options Explicit の場合)、
問題箇所を容易に発見できます。


> .Range("B15:E15").Select
> With Selection.Borders(xlEdgeBottom)
このあたりも同様ですね。


また、そもそも Selection というのは多用すべきコードではありません。
普段、VB6 を操作する際にも、
  Form2.Text1.Text = "New Text"
というコードを、わざわざ
  Form2.Activate
  ActiveForm.Text1.SetFocus
  ActiveForm.ActiveControl.Text = "New Text"
などと書いたりはしませんよね? それと同じ事です。

[ツリー表示へ]
タイトルRe^2: Excel処理の繰り返しでNG
記事No14464
投稿日: 2010/02/12(Fri) 10:36
投稿者ぽよ〜ん
魔界の仮面弁士様、素早い回答有り難う御座いました。

> > Set xlApp = CreateObject("Excel.Application")
> 参照設定している場合は、
>  Set xlApp = New Excel.Application
> と書けますよ。
参考になります。元ソースは変更致しました。

>   .Rows("10:10").Select   '行を選択
> >   With Selection          'コピーして下へ
> ここが NG。
> ここで「Selection」と書いていますが、対象オブジェクトが指定されていません。
> どのオブジェクトの Selection プロパティなのか、親オブジェクトを明記しましょう。
> 親オブジェクトが指定されなかった場合、暗黙の Excel インスタンスが
> グローバル オブジェクトとして参照される事になります。Excel VBA であれば、
> 「自分自身」を操作するだけなので問題はありませんが、外部(VB6 等)から
> 操作する場合には、親オブジェクトを必ず指定する必要があります。
Excelから取得したマクロの記録ソースを元に作っていったのですが、言われる通りです。
とても分かりやすく説明して頂き、感謝致します。
今までのソースでデバッグを繰り返していると、システムが不安定になっていたのも
頷けます。元ソースを次のように変更してみました。

    With xlSheet
        .Activate
        For i = 1 To 5
            With .Range("10:10")
                .Select   '行を選択
                .Copy
                .Insert Shift:=xlDown
            End With
        Next
        ' 下線を二重線で引く
        .Range("B15:E15").Select
        With .Range("B15:E15").Borders(xlEdgeBottom)
            .LineStyle = xlDouble
            .Weight = xlThick
            .ColorIndex = xlAutomatic
        End With
    End With

何回かプログラムをループさせながら動作してみましたが、目的の処理は
行われているようです。書き方が正しいかどうかは不安ですが。

有り難う御座いました。今後も宜しくお願い致します。

[ツリー表示へ]
タイトルRe^3: Excel処理の繰り返しでNG
記事No14465
投稿日: 2010/02/12(Fri) 12:48
投稿者魔界の仮面弁士
> 元ソースを次のように変更してみました。

さらに修正案。ループを使わないコードに書きなおしてみました。

xlSheet.Rows(10).Copy
xlSheet.Range("10:14").Insert xlShiftDown
xlApp.CutCopyMode = False
xlSheet.Range("B15:E15").Borders(xlEdgeBottom).LineStyle = xlDouble

[ツリー表示へ]
タイトルRe^4: Excel処理の繰り返しでNG
記事No14466
投稿日: 2010/02/14(Sun) 08:14
投稿者ぽよ〜ん
魔界の仮面弁士様、アドバイス有難う御座います。
挿入の部分は、その都度変化するので以前のままでもと思いますが、
ラインを引く部分は、自動のマクロから取ったままなので冗長だったかも知れませんね。
1行で目的の処理が出来るならこの方が宜しいようですので、参考にさせて頂きます。
> xlSheet.Range("B15:E15").Borders(xlEdgeBottom).LineStyle = xlDouble

で、またまた挿入で思った処理が出来ずに悩んでしまいました。
Private Sub Command1_Click()
Dim i As Integer
Dim xlApp As Excel.Application
Dim xlBook As Excel.Workbook
Dim xlSheet As Excel.Worksheet
Set xlApp = New Excel.Application
Set xlBook = xlApp.Workbooks.Open(App.Path & "\xlTestFile.xls")
Set xlSheet = xlBook.Worksheets("Sheet1")
'xlTestFile.xls は予め用意をしておく
'B5:E10 まで格子状に罫線を引いただけのファイルで保存しておく
    On Error Resume Next
    ' Excel画面を非表示にして実行
    xlApp.Visible = True
    
    With xlSheet
        .Activate
        With .Range("10:10") ’このような書き方のほうがスピードアップする?
        For i = 1 To 5
            .Select
            .Copy
            .Insert Shift:=xlDown
        Next
        End With
    End With

    '前回と同じく、1回目はOKなのですが、2回目以降が旨く処理されない
'    xlSheet.Copy After:=Sheets(1)  '別の書き方でも試したが結果は同じ
    With xlBook.Sheets("Sheet1")
        .Select
        .Copy After:=Sheets(1)
    End With
    'シートをコピーしてそのシート内で色々な処理を行なう
    'オブジェクトの宣言と開放がやはり必要なのかな?
    'Set NewxlSheet = xlBook.Worksheets("Sheet1(1)") とか・・・?
    '旨く出来なかったけど・・・根本的なところが理解してないのかな〜
    'これも1回目は目的の処理をしてくれますが、2回目以降はコピーされない
    '元のSheet1内にデーターが書かれてしまう
    With xlBook.Sheets.Application
        .ActiveSheet.Activate
        .Range("B5").Value = 1234
    End With

    '保存時の問合せを非表示に設定
    xlApp.DisplayAlerts = False
    ' オブジェクトを解放します。
    Set xlSheet = Nothing
    xlBook.Close            'Book を閉じる
    Set xlBook = Nothing
    ' Quit メソッドを使って Excel を終了します。
    xlApp.Quit
    Set xlApp = Nothing
    
End Sub

Private Sub Command2_Click()
    End
End Sub

また長くて済みません。
要は、そのシートをコピーしてその中で処理をしたいだけなのですが、色々と書き方を
換えながら動きを見ているのですが、思った処理が出来ていません。
アドバイスなど頂けたら幸いです。皆様、宜しくお願い致します。

[ツリー表示へ]
タイトルRe^5: Excel処理の繰り返しでNG
記事No14467
投稿日: 2010/02/14(Sun) 10:35
投稿者魔界の仮面弁士
>     '前回と同じく、1回目はOKなのですが、2回目以降が旨く処理されない
同じ間違いを繰り返していますよ。

> '    xlSheet.Copy After:=Sheets(1)  '別の書き方でも試したが結果は同じ
この書き方にしても、
>         .Copy After:=Sheets(1)
この書き方にしても、
「Sheets」の親オブジェクトが指定されていません。

http://hanatyan.sakura.ne.jp/vbhlp/caution.htm
http://hanatyan.sakura.ne.jp/vbhlp/ExcelErr.htm

>     With xlBook.Sheets.Application
Application プロパティを使っているのは、何のためですか?
そこから返されるオブジェクトは、既に変数 xlApp に保持されていますよね。

>         .ActiveSheet.Activate
ActiveSheet を Activate する事に、何の意味があるのでしょうか?
また、そもそも ActiveSheet を使うのではなく、どのシートかを
明示的に指定すべきかと思います。

>     End
原則として、End ステートメントは使うべきではありません。
替わりに、すべてのフォームを Unload する事で終了させましょう。

End は強制終了の文として、かつての BASIC 言語の互換ステートメントとして
用意されてはいますが、正規の終了処理(QueryUnload イベント等)が呼び出されないため、
時として、意図せぬ動作を引き起こすことが知られています。

http://support.microsoft.com/kb/165824/ja
http://support.microsoft.com/kb/81469/en-us


> 挿入の部分は、その都度変化するので以前のままでもと思いますが、
ループの方が便利な事も多々あるので、どちらが良いとは一概には言えませんが、
ループ回数が多い場合には、無視できない程の速度差になる事もありますので、
一応、両方の方法を習得しておいた方が良いと思います。

たとえば今回のように、同一箇所に複数行を挿入する場合、
 ・「1 行挿入」を X 回繰り返す。
よりも
 ・「X 行挿入」を1回行う。
方が、Excel との通信回数が減る分、処理効率が良くなります。

特に VB6 から操作するなどの、「外部から」Excel を操作する際には、環境によっては
プロパティ/メソッドの通信回数の増加が、ハングアップの要因となる事もあります。
http://support.microsoft.com/kb/414107/ja

[ツリー表示へ]
タイトルRe^6: Excel処理の繰り返しでNG
記事No14470
投稿日: 2010/02/14(Sun) 14:32
投稿者ぽよ〜ん
魔界の仮面弁士様、ありがとうございます。
> 同じ間違いを繰り返していますよ。
やっぱりよく分かってないようです。
> 「Sheets」の親オブジェクトが指定されていません。
あれこれ調べて xlSheet.Copy After:=xlBook.Sheets(1) として記述したら
目的の処理をしてくれました。複数回でも同様です。

> また、そもそも ActiveSheet を使うのではなく、どのシートかを
> 明示的に指定すべきかと思います。
    With xlBook.Sheets(2)
        .Activate
        .Range("B5").Value = 1234
    End With
このように記述して、動作させました。

> 原則として、End ステートメントは使うべきではありません。
> 替わりに、すべてのフォームを Unload する事で終了させましょう。
・・・・・
> たとえば今回のように、同一箇所に複数行を挿入する場合、
>  ・「1 行挿入」を X 回繰り返す。
> よりも
>  ・「X 行挿入」を1回行う。
> 方が、Excel との通信回数が減る分、処理効率が良くなります。
>
> 特に VB6 から操作するなどの、「外部から」Excel を操作する際には、環境によっては
> プロパティ/メソッドの通信回数の増加が、ハングアップの要因となる事もあります。
> http://support.microsoft.com/kb/414107/ja
ありがとうございます。勉強になります。
言われてみればそうですよね。元のソースは改定いたしました。

タスクマネージャーから確認してもEXCELの残骸も無さそうだし、これでOKなのかな〜
本当に勉強になりました。上の書き方で問題なければこのまま進めて見ます。
有難う御座いました。

[ツリー表示へ]
タイトルRe^7: Excel処理の繰り返しでNG
記事No14471
投稿日: 2010/02/14(Sun) 16:27
投稿者魔界の仮面弁士
>     With xlBook.Sheets(2)
>         .Activate
>         .Range("B5").Value = 1234
>     End With
> このように記述して、動作させました。
『xlBook.Sheets(2).Range("B5").Value = 1234』では都合が悪いのでしょうか?

Activate させなければならない理由があるのならば良いですが、
そうでないなら冗長なだけですし、切り替えの分のタイムロスにもなってしまうかと。


また、ワークシートをシート名で指定する場合と
> Set xlSheet = xlBook.Worksheets("Sheet1")
番号で指定する場合とを
> With xlBook.Sheets(2)
混在させている理由は何でしょうか?

[ツリー表示へ]
タイトルRe^8: Excel処理の繰り返しでNG
記事No14475
投稿日: 2010/02/16(Tue) 17:28
投稿者ぽよ〜ん
魔界の仮面弁士様、ご指導有り難う御座います。

> Activate させなければならない理由があるのならば良いですが、
> そうでないなら冗長なだけですし、切り替えの分のタイムロスにもなってしまうかと。
    Set xlSheet = xlBook.Worksheets(1)
これを記述した段階でアクティブになってしまうのかな?
そのシートを扱うときには入れるものと覚えてしまったので、無意識に入れてます。
コメントにして動かしても目的の動作はしますね。

> また、ワークシートをシート名で指定する場合と
> > Set xlSheet = xlBook.Worksheets("Sheet1")
> 番号で指定する場合とを
> > With xlBook.Sheets(2)
> 混在させている理由は何でしょうか?
シート名を明確にして宣言したものが上。やはり仕様が違うのでしょうか?
実は、"Sheet1"をコピーする前に、シートの名前を変更しております。
    xlBook.Sheets(1).Name = "Test1-"
    xlSheet.Copy After:=xlBook.Sheets(1)
上のように記述して動かしております。
途中で名前を変えてしまう様な時には、Set xlSheet = xlBook.Worksheets(1)
こちらの方が宜しいのでしょうか?
申し訳御座いません。又質問になってしまいました。

[ツリー表示へ]
タイトルRe^9: Excel処理の繰り返しでNG
記事No14479
投稿日: 2010/02/20(Sat) 18:26
投稿者魔界の仮面弁士
>     Set xlSheet = xlBook.Worksheets(1)
> これを記述した段階でアクティブになってしまうのかな?
変数に Set しただけでは、アクティブなシートは切り替わりません。

> そのシートを扱うときには入れるものと覚えてしまったので、無意識に入れてます。
多くの場合、シートを操作する際にシートをアクティブにする必要は無いはずです。
無闇に利用するのではなく、「選択状態にしないと操作できない場合」などの
明確な目的がある場合にのみ使う事をおすすめします。

Activate/Select すると言うことは、そこでフォーカスの切り替えが発生するため、
その分処理効率も落ちますから。(ループ中などで複数回アクセスする場合は特に顕著)


> > > Set xlSheet = xlBook.Worksheets("Sheet1")
> > > With xlBook.Sheets(2)
前者 [Worksheets] は「ワークシートのみ」が対象で、
後者 [Sheets] は「ワークシート以外のシート」も対象になります。
(マクロシートや、グラフシートなど)

> シート名を明確にして宣言したものが上。やはり仕様が違うのでしょうか?
上記のように、Worksheets と Sheets の仕様は異なります。ただし、
名前指定と番号指定の事を指しているのだとしたら、それらは機能的には同一です。

番号指定が良いか名前指定が良いかはケースバイケースなので、
適宜、使い分けていくことになるかと思います。

たとえば、新たなシートが挿入されたり、あるいは既存の不要なシートを
削除したりすると、それ以降のシート番号がずれますよね。
または、ユーザーがシートの順番を変えることもあるかも知れません。
そのような場合でも、.Worksheets(3) ではなく .Worksheets("Sheet3") とすれば
目的のシートを正しく操作できます。

一方、シートを .Copy した時などは、コピー後の新シートを得るために、
番号(Index)が必要になってくるかと思います。


> 実は、"Sheet1"をコピーする前に、シートの名前を変更しております。
>     xlBook.Sheets(1).Name = "Test1-"
>     xlSheet.Copy After:=xlBook.Sheets(1)
上記の「xlSheet」変数の中身は何ですか?
Worksheets でしょうか。それとも Worksheet でしょうか?
シートだとしたら、それは Sheets(1) でしょうか? Sheets(1) 以外でしょうか?

変数の中身が分からないので、何をどこにコピーしようとしているのか
読み切れませんでしたが、プログラムの途中で番号や名前が変化する場合には、
 Set xlSheet1 = xlBook.Worksheets("Sheet1")
 Set xlSheet2 = xlBook.Worksheets("Sheet2")
 xlSheet2.Name = "Test1"
 xlSheet1.Copy After:=xlSheet2
のように、あらかじめ変数に Set しておき、それを使った方が混乱が少ないかと。


> 途中で名前を変えてしまう様な時には、Set xlSheet = xlBook.Worksheets(1)
> こちらの方が宜しいのでしょうか?
どちらでも良いとは思います。ただ、名前を変更する場合に限らず、そのシートを
繰り返し呼ぶ場合には、変数を用意した方が便利だとは思います。

たとえば xlBook.Worksheets(1) というコードは、
 ・変数 xlBook は、Workbook オブジェクトである。
 ・Workbook オブジェクトの Worksheets プロパティから、Sheets オブジェクトを取得。
 ・Sheets オブジェクトの 既定のプロパティから、Worksheet オブジェクトを取得。
という段階を経て、Worksheet オブジェクトを得ますが、xlSheet に格納しておけば、
このプロパティ呼び出しを省略できるため、ループ回数が多い場合には有効です。
(もっとも、数回程度の呼び出しなら、その都度 .Worksheets から得ても大差ありません)

この他、変数に入れると IntelliSense (入力候補)を活用できるメリットもあります。
たとえば、「xlBook.Worksheets(1).」と打った場合には、入力ヒントが出ませんが、
「xlSheet.」と打った場合には、入力ヒントが利用できるというメリットがあります。

[ツリー表示へ]