[リストへもどる]   [VBレスキュー(花ちゃん)]
一括表示

投稿時間:2006/10/02(Mon) 18:00
投稿者名:こう
Eメール:
URL :
タイトル:
画面呼び出しで実行時エラー「438」が発生
画面A、画面B、画面Cの3つの画面があります。

画面Aからボタン押下により、画面Bと画面Cを呼び出します。

画面Bは、呼び出し時に処理を実行し、処理結果により画面B
を開くか、開かないか判断しています。
画面Cは、ボタンが押されたら画面を開いています。

たぶん、私の作りが悪いと思うのですが、
画面Aからボタンを押下し、画面Bを開き、また、画面Aに戻った
場合、どうしても、画面AのFrom_Loadが動いてしまいます。
画面AのFrom_Loadが呼ばれないように「Unload」から「Hide」に
変更しました。
変更後は、上手くいったのですが、今後は、画面Cを開くと、以下の
エラーが表示され画面が終了してしまいます。

実行時エラー '438'
オブジェクトは、このプロパティまたは、メソッドをサポートしていません。
が表示されます。
「OK」を押すと終了する。

デバッガで動かしてみると問題なく動くのですが、何がいけないのでしょうか。
どのようにすれば正しく動くのでしょうか。

環境は、
Windows2000
VB6.0

-------------------------------------------------------------
画面A
Private Sub Command1_Click()
  Dim Result         As Integer

    Result = From_B.PANEL_INIT
    If Result <> NORMAL_RETURN Then
        Unload From_B
        Exit Sub
    End If
    From_B.Show
    From_B.Data_Delivery data1,data2,data3
    'Unload Me
    Hide
End Sub

Private Sub Command2_Click()
    'From_C.Show
    'Unload Me
    From_C.Show 1, From_A
End Sub

-------------------------------------------------------------
画面B
Friend Function PANEL_INIT() As Integer
   On Error Resume Next

    処理
    エラーが発生したら
        GoTo EXIT_ERR

    PANEL_INIT = NORMAL_RETURN

    Exit Function

'エラー処理
EXIT_ERR:
    PANEL_INIT = UNUSUAL_RETURN

   On Error GoTo 0

End Function

Private Sub Command1_Click()
    From_A.Show
    Unload Me
End Sub

-------------------------------------------------------------
画面C
Private Sub Command1_Click()
    'From_A.Show
    Unload Me
End Sub

Private Sub Command2_Click()
    処理A
End Sub

Private Sub Command3_Click()
    処理B
End Sub

投稿時間:2006/10/02(Mon) 19:21
投稿者名:魔界の仮面弁士
Eメール:
URL :
タイトル:
Re: 画面呼び出しで実行時エラー「438」が発生
現象を再現できる最小限のコードを示す事は可能ですか?
少なくとも当方では、提示されたコードから今回の件を再現することができませんでした。

> From_A.Show
# "フロム・エー(From_A)" → "フォーム・エー(Form_A)" かな…。


> 場合、どうしても、画面AのFrom_Loadが動いてしまいます。
Load イベントの先頭にブレークポイントを貼っておき、一時停止されたときに
[Ctrl] + [L] キーを押して、呼び出し履歴を表示してみてください。
どの行が原因でリロードされてしまっているのか、わかるかと思います。


> どうしても、画面AのFrom_Loadが動いてしまいます。

フォームの Unload 後は、そのフォーム上のコントロールや、あるいは、
フォームの実体を必要とするプロパティやメソッド(hWnd, Move, Caption等)には、
絶対にアクセスしてはいけません。不用意にそれらのメンバを操作しようとすると、
今回のような、暗黙のリロードが発生してしまうからです。

この件に関しては、ヘルプの『Visual Basic フォームの有効期間』というトピックに
詳しく書かれていますので、参照してみてください。



それと、それぞれの画面がお互いを操作しあうような設計は、フォーム間の結びつきが
強くなってしまう上に、誰が誰をロードするのかといった、フォームの有効期間の管理が
複雑になってしまいがちです。処理の流れを見直されることをおすすめします。

投稿時間:2006/10/02(Mon) 21:02
投稿者名:こう
Eメール:
URL :
タイトル:
Re^2: 画面呼び出しで実行時エラー「438」が発生
> > 場合、どうしても、画面AのFrom_Loadが動いてしまいます。
> Load イベントの先頭にブレークポイントを貼っておき、一時停止されたときに
> [Ctrl] + [L] キーを押して、呼び出し履歴を表示してみてください。
> どの行が原因でリロードされてしまっているのか、わかるかと思います。

[Ctrl] + [L] キーを押して、呼び出し履歴を表示してみてください。
で呼び出し履歴が確認できるとは知りませんでした。

画面BのCommand1を押下したら呼び出し画面(Form_A)に戻るとしてい
います。
ここで呼ばれていることがわかりました。

ソースにつては、必要最低限で載せられるか作ってみます。

投稿時間:2006/10/03(Tue) 00:18
投稿者名:こう
Eメール:
URL :
タイトル:
Re^2: 画面呼び出しで実行時エラー「438」が発生
「Hide」の使い方、使う位置は、間違ってないでしょうか。

最小限のコードです。
訂正版です。
----------------------------------------
Form1

Private Sub Command1_Click()
  Dim Result         As Integer

    Result = Form2.PANEL_INIT
    If Result <> 0 Then
        Unload Form2
        Exit Sub
    End If
    Form2.Show
    Form2.Data_Delivery "1", "2", "3"
    'Unload Me
    Hide
End Sub

Private Sub Command2_Click()
    Form3.Show 1, Form1
End Sub

Private Sub Form_Load()
    Text1.Text = ""
    Text2.Text = ""
    Text3.Text = ""
    Text4.Text = ""
End Sub

-------------------------------------
Form2

Friend Function PANEL_INIT() As Integer
   On Error Resume Next

    Set objSess = CreateObject("OracleInProcServer.XOraSession")
    Set objDb = objSess.OpenDatabase("dbname", "user/password", 0&)

    strSQL = "SELECT ・・・・"
    Set objDs = objDb.DbCreateDynaset(strSQL, 12&)
    Set objCol = objDs.Fields

    If objDb.lastservererr <> 0 Then
        MsgBox "ORACLEでエラーが発生しました。" & Chr(13) & Chr(10) & _
        "(Oracle Error:" & objDb.lastservererr & " Text:" & objDb.LastServerErrText & ")"
        GoTo EXIT_ERR
    End If

    処理1
    処理2
    処理3

    PANEL_INIT = 0

    Exit Function

'エラー処理
EXIT_ERR:
    PANEL_INIT = 1
  
   'エラートラップ終了
   On Error GoTo 0

End Function

Private Sub Command1_Click()
    Form1.Show
    Unload Me
End Sub

Public Sub Data_Delivery(ByVal varid1 As String, ByVal varid2 As String, ByVal varid3 As String)
   Text1.Text = varid1
   Text2.Text = varid2
   Text3.Text = varid3
End Sub

--------------------------------
Form3

Option Explicit
Private Sub Command1_Click()
    Unload Me
End Sub

Private Sub Form_Load()
Dim objSess         As Object
Dim objDb           As Object
Dim objDs           As Object
Dim objCol          As Object
Dim strSQL          As String
Dim ErrMsg          As String

    Set objSess = CreateObject("OracleInProcServer.XOraSession")
    Set objDb = objSess.OpenDatabase("dbname", "user/password", 0&)

    strSQL = "SELECT ・・・・"
    Set objDs = objDb.DbCreateDynaset(strSQL, 12&)
    Set objCol = objDs.Fields

    If objDb.lastservererr <> 0 Then
        MsgBox "ORACLEでエラーが発生しました。" & Chr(13) & Chr(10) & _
        "(Oracle Error:" & objDb.lastservererr & " Text:" & objDb.LastServerErrText & ")"
        Exit Sub
    ElseIf Err <> 0 Then
        Err.Eaise Err
        ErrMsg = Err.Description
        MsgBox ErrMsg, vbOKOnly + vbCritical, "VBエラーメッセージ"
        Exit Sub
    End If
    If Not objDs.EOF Then
        Exit Sub
    End If
    If Not IsNull(objDs(0).Value) Then
        Text1.Text = objDs(0).Value
    End If
    If Not IsNull(objDs(1).Value) Then
        Text2.Text = objDs(1).Value
    End If
    If Not IsNull(objDs(2).Value) Then
        Text2.Text = objDs(2).Value
    End If
End Sub

投稿時間:2006/10/03(Tue) 04:17
投稿者名:魔界の仮面弁士
Eメール:
URL :
タイトル:
Re^3: 画面呼び出しで実行時エラー「438」が発生
現時点では、Unload や Hide ばかりに目が行っているようですが、それと同様に、
いつフォームがロードされるのか、という点も重要だったりします。

まずは先に回答した、ヘルプの『Visual Basic フォームの有効期間』をよく確認してください。

[Visual Basic ドキュメント]
└[Visual Basic の使用方法]
 └[プログラミング ガイド]
  └[Visual Basic を使ってできること]
   └[オブジェクト]
    └[独自のクラスの作成]
     └[Visual Basic フォームの有効期間]★

その中でも特に重要なのが、このトピック中にある
 「Visual Basic のフォームは、その有効期間中、通常 4 つの状態を通過します。」
と書かれている部分です。この部分を良く読んでおいてください。


> 「Hide」の使い方、使う位置は、間違ってないでしょうか。

フォームの管理という視点からいえば、何らかの間違いを含んでいそうです。

たとえば現在のコードを見る限りでは、
 1. Form1 の Command1 ボタンを押す。
  → Form2 が表示され、Form1 は非表示状態になる。
 2. その後、Form2 右上の ×ボタンを押す。
  → Form2 が閉じ、非表示の Form1 だけがメモリ上に取り残される。
 3. これにより、アプリが終了せずに残ってしまう…。
という問題を抱え込んでいます。

この問題に関して、下記を参照しておいてください。
http://www.gj.il24.net/~nakasima/vb/tech/end/index.htm


> 最小限のコードです。

この中から、Form1 の Command1_Click を例に挙げて見てみましょう。

>     Result = Form2.PANEL_INIT
>     If Result <> 0 Then
>         Unload Form2
>         Exit Sub
>     End If
>     Form2.Show

このコードは、フォームの管理という点において、若干の問題を含んでいます。

まずはここで、Form2.PANEL_INIT にアクセスしていますが、
この実行直後の時点で、Form2 のオブジェクトの状態が
 [1] 作成されているが、ロードされていない。
 [2] ロードされているが、表示されていない。
 [3] 表示されている。
 [4] メモリ上に作成されていない。(解放状態)
の 4 種いずれの状態にあるかを考えてみる事にします。


まず、呼び出し側には、Form2 に対する「Load ステートメント」は記述されていません。
ですから、初回の呼び出し直前には、おそらく [4] の解放状態だったと想定されます。

そして、この解放状態のまま PANEL_INIT メソッドにアクセスしています。
この時、ヘルプで言うところの「隠されたグローバル変数」にあたる Form2 変数を
利用してアクセスしていますので、Form2 の実体(インスタンス)の生成は、
VB6 によって自動的に行われます。
つまり、この時点で、Form2 オブジェクトは [1] の生成済み状態に遷移した事になります。

しかしこの時点では、まだ Form2 が"生成"されたというだけである点に注意してください。
(Form2 はまだロードされていないので、[2] の状態にはなっていません)


次に、呼び出された Function PANEL_INIT() の方のコードに目を向けてみます。
そこには、「Load Me」といったコードは書かれていません。
しかも、自身のコントロール等にアクセスしている事も無く、そのまま 戻り値 0 を
返して終了しているだけです。ゆえに、このメソッド中での状態変化は発生しておらず、
この PANEL_INIT 実行後も、[1] の状態を維持しているという事になります。


すなわち、Form1 側の「Result = Form2.PANEL_INIT」の実行後も、Form2 オブジェクトは
[1] の状態(生成されたが、まだロードはされていない状態)にあるという事を意味します。


……にも関わらず、呼び出し側では、その後のコードで、「Unload Form2」という処理が
加えられています。

ロードされていないフォームを Unload しようとしたからといって、エラーになるわけでは
ありませんが、このコードを見る限りでは、『Visual Basic フォームの有効期間』が
あまり意識されていないかのように思えます。となると、その他の Hide などの操作も、
フォームの状態を意識せずに記述されている可能性があるのではないでしょうか。

投稿時間:2006/10/03(Tue) 11:50
投稿者名:こう
Eメール:
URL :
タイトル:
Re^4: 画面呼び出しで実行時エラー「438」が発生
>いろいろと解説、説明ありがとうございます。

Visual Basic フォームの有効期間』については、ほとんど
気にもとめていませんでした。
勉強になりました。

>たとえば現在のコードを見る限りでは、
> 1. Form1 の Command1 ボタンを押す。
>  → Form2 が表示され、Form1 は非表示状態になる。
> 2. その後、Form2 右上の ×ボタンを押す。
>  → Form2 が閉じ、非表示の Form1 だけがメモリ上に取り残される。
> 3. これにより、アプリが終了せずに残ってしまう…。
>という問題を抱え込んでいます。

Xボタンを押せないようにしてしまえばすむということになりますが、
それでは、本解決にも、自分が理解したかになりません。

また、最初に質問した件で、Form2からForm1に戻ったとき、Form1の
Form_Loadを呼ばれないようにするには、今までことを理解すれば
可能でしょうか。

投稿時間:2006/10/03(Tue) 13:23
投稿者名:こう
Eメール:
URL :
タイトル:
Re^4: 画面呼び出しで実行時エラー「438」が発生
画面呼び出しで実行エラーが発生していたのは、他にもソースコード
におかしいところがあり、それが原因でした。

実際は、Form2.PANEL_INIT内で、SQLを実行し、画面表示を
しているのですが、SQLで取得した後の処理が正しく行われ
いなかったため、おかしい動きをしていまし。

フォームにつては勉強になりました。

投稿時間:2006/10/03(Tue) 13:29
投稿者名:魔界の仮面弁士
Eメール:
URL :
タイトル:
Re^3: 画面呼び出しで実行時エラー「438」が発生
# No.7410 のサンプル、Form3 のコードが壊れているようなので、
# 投稿内容を [修正] しておいてください。>こうさん

> Xボタンを押せないようにしてしまえばすむということになりますが、
> それでは、本解決にも、自分が理解したかになりません。

フォーム管理において重要な事は、
  各フォームが、どのタイミングで生成され、ロードされ、表示され、アンロードされるのか
を把握することにあります。

たとえば今回のように、Hideメソッド(あるいはVisibleプロパティ)によってフォームを
非表示にしたのであれば、当然、それをどこかで再表示する or アンロードする必要が
あるはずですよね。その処理はどこで行うべきか、画面仕様に準えて考えてみてください。



> また、最初に質問した件で、Form2からForm1に戻ったとき、Form1の
> Form_Loadを呼ばれないようにするには、今までことを理解すれば
> 可能でしょうか。

もちろん可能です。
そしてそのためには、各フォームがどこでロードされるべきかを考える必要があるでしょう(A)し、
また、なぜ Form1 が再ロードされてしまうのかを把握すること(B)も必要になってきます。

-----
まずは前者。
各フォームがどこでロードされるべきか(A)を考慮する、すなわち、ヘルプでいうところの
「フォームの有効期間」を考察するために、先に提示されたNo.7410のコードをみてみましょう。

<Form1>
 * Form1 は、スタートアップフォームである(?)
 * Form1 は、Form2 の Command1 によっても Show される。
 * Form1 は、Form1 自身の Command1 にて、自身をUnload(あるいは Hide)する。
<Form2>
 * Form2 は、Form1 の Command1 によって Show される。
 * Form2 は、Form1 の Command1 によって Unload される可能性がある。
 * Form2 は、Form2 自身の Command1 によって 自身を Unload する。
<Form3>
 * Form3 は、Form1 の Command1 によって Show され、Form1 の子フォームとなる。
 * Form3 は、Form3 自身の Command1 によって 自身を Unload する。


これをみると、Form3 と Form1 は親子関係にあるので、Form3 の有効期間はわかりやすいのですが、
Form1 と Form2 は、互いが互いを Show / Unload しあっていて、聊か奇妙な関係に見えます。

アプリが終了するためには、すべてのフォームがアンロードされなければならないわけですが、
現在のコードでは、Form1 と Form2 の両方がアンロードされる条件というのが存在しないからです。

実際、Form1 が Unload される際には、代わりに Form2 が 表示されてしまいますし、
> Private Sub Command1_Click()
>  (中略)
>   Form2.Show
>   Form2.Data_Delivery "1", "2", "3"
>   'Unload Me
>   Hide
> End Sub
逆に、Form2 が Unload される際には、代わりに Form1 が 表示される仕様になっています。
> Private Sub Command1_Click()
>    Form1.Show
>    Unload Me
> End Sub

これでは、いつまで経ってもアプリケーションは終了できません。
まして、Unload を Hide に変えたともなれば、それは問題を悪化させるだけであると言えます。
まずは、この曖昧な画面関係を見直すようにしてみてください。

-----
次に後者です。

問題となっている、Form1 が再ロードされてしまう原因を把握する方法(B)についてですが
これにはまず、正規のロード処理のタイミングを整理しておかなければいけません。

すなわち、それには上記の前者(A)の設計が重要になってくるという事を意味します。

そして、画面関係の設計が見直された段階で、フォームがロードされるべき部分のコードに対して、
「Load ステートメント」を明示的に呼び出すように、コードを修正してみてください。
さらにその上で、それぞれの Load イベントにブレークポイントを貼っておき、フォームが
ロードされるたびに、先述の「呼び出し履歴」機能を用いるようにします。

そうすれば、Load ステートメント以外の理由によって自動ロードされていた場合、それが
予期せぬリロード処理である、という意味になります。

もちろん、予期せぬリロードというのは、『コーディングミス』をあらわしますので、
それらのコードを見直して、その原因を取り除いてやれば、問題も解決するでしょう。

投稿時間:2006/10/03(Tue) 18:55
投稿者名:こう
Eメール:
URL :
タイトル:
Re^3: 画面呼び出しで実行時エラー「438」が発生
>
> 「Hide」の使い方、使う位置は、間違ってないでしょうか。
>
> 最小限のコードです。
訂正版です。
----------------------------------------
Form1

Private Sub Command1_Click()
  Dim Result         As Integer

    Result = Form2.PANEL_INIT
    If Result <> 0 Then
        Unload Form2
        Exit Sub
    End If
    Form2.Show
    Form2.Data_Delivery "1", "2", "3"
    'Unload Me
    Hide
End Sub

Private Sub Command2_Click()
    Form3.Show 1, Form1
End Sub

Private Sub Form_Load()
    Text1.Text = ""
    Text2.Text = ""
    Text3.Text = ""
    Text4.Text = ""
End Sub

-------------------------------------
Form2

Friend Function PANEL_INIT() As Integer
   On Error Resume Next

    Set objSess = CreateObject("OracleInProcServer.XOraSession")
    Set objDb = objSess.OpenDatabase("dbname", "user/password", 0&)

    strSQL = "SELECT ・・・・"
    Set objDs = objDb.DbCreateDynaset(strSQL, 12&)
    Set objCol = objDs.Fields

    If objDb.lastservererr <> 0 Then
        MsgBox "ORACLEでエラーが発生しました。" & Chr(13) & Chr(10) & _
        "(Oracle Error:" & objDb.lastservererr & " Text:" & objDb.LastServerErrText & ")"
        GoTo EXIT_ERR
    End If

    処理1
    処理2
    処理3

    PANEL_INIT = 0

    Exit Function

'エラー処理
EXIT_ERR:
    PANEL_INIT = 1
  
   'エラートラップ終了
   On Error GoTo 0

End Function

Private Sub Command1_Click()
    Form1.Show
    Unload Me
End Sub

Public Sub Data_Delivery(ByVal varid1 As String, ByVal varid2 As String, ByVal varid3 As String)
   Text1.Text = varid1
   Text2.Text = varid2
   Text3.Text = varid3
End Sub

--------------------------------
Form3

Option Explicit
Private Sub Command1_Click()
    Unload Me
End Sub

Private Sub Form_Load()
Dim objSess         As Object
Dim objDb           As Object
Dim objDs           As Object
Dim objCol          As Object
Dim strSQL          As String
Dim ErrMsg          As String

    Set objSess = CreateObject("OracleInProcServer.XOraSession")
    Set objDb = objSess.OpenDatabase("dbname", "user/password", 0&)

    strSQL = "SELECT ・・・・"
    Set objDs = objDb.DbCreateDynaset(strSQL, 12&)
    Set objCol = objDs.Fields

    If objDb.lastservererr <> 0 Then
        MsgBox "ORACLEでエラーが発生しました。" & Chr(13) & Chr(10) & _
        "(Oracle Error:" & objDb.lastservererr & " Text:" & objDb.LastServerErrText & ")"
        Exit Sub
    ElseIf Err <> 0 Then
        Err.Eaise Err
        ErrMsg = Err.Description
        MsgBox ErrMsg, vbOKOnly + vbCritical, "VBエラーメッセージ"
        Exit Sub
    End If
    If Not objDs.EOF Then
        Exit Sub
    End If
    If Not IsNull(objDs(0).Value) Then
        Text1.Text = objDs(0).Value
    End If
    If Not IsNull(objDs(1).Value) Then
        Text2.Text = objDs(1).Value
    End If
    If Not IsNull(objDs(2).Value) Then
        Text2.Text = objDs(2).Value
    End If
End Sub