[[20181108000225]] 『Userform内にUnLoad Meと書くのは作法としてアリax(中途B) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『Userform内にUnLoad Meと書くのは作法としてアリですか?』(中途B)

 いつも大変お世話になってます。
 お伺いしたいことは表題の通りなのですが、
 ちょっと経緯説明が長くなります。宜しければお付き合い願います。

 UserformをInputBox的な用途で使うのに、私は↓こんな書き方をしたりしてます。

 例えばUserform1に、あらかじめTextBoxを4つとCommandButtonをひとつ配置しておき、
 Userform1モジュールにこんなのを書いておきます。

 ●Userform1モジュール ------------------------------------------------------------

    Option Explicit

    Private Flg As Boolean

    Public Function GetText(Str1 As String, Lng1 As Long, Dbl1 As Double, Date1 As Date, Var1 As Variant) As Boolean
        TextBox1.Text = Str1
        TextBox2.Text = Lng1
        TextBox3.Text = Dbl1
        TextBox4.Text = Date1
        Me.Show vbModal
        If Flg Then
            Str1 = TextBox1.Text
            Lng1 = TextBox2.Text   'とりあえず型変換とか
            Dbl1 = TextBox3.Text   'エラーチェックは省略
            Date1 = TextBox4.Text  'してるので入力に注意
            Var1 = Dbl1
        End If
        GetText = Flg
    End Function
    Private Sub CommandButton1_Click()
        Flg = True
        Unload Me
    End Sub

 そして、標準モジュール側でこんな風に使います ----------------------------------------

    Sub test1()
        Dim T As String, L As Long, S As Double, D As Date, V As Variant
        Dim U As New UserForm1
        T = "abc"
        L = 100&
        S = 1.5
        D = Date
        Debug.Print "Boolean", "String", "Long", "Double", "Date", "Variant"
        Debug.Print U.GetText(T, L, S, D, V), T, L, S, D, V
    End Sub

 1.Userform1.GetTextに、あらかじめ初期値を入れた変数を引数として預け、各TextBoxに初期値を表示。
 2.ユーザーは必要に応じてTextBoxの内容を書き換えてCommandButtonを押す。(または[×]で閉じる)
 3.CommandButtonが押された場合は、引数にTextBoxの内容を入れ、戻値としてTrueを返す。

 というもので、実際ちゃんと動いてくれます。

 では、Userform内のTextBoxやCommandButtonをあらかじめ配置せず、
 Initialize内でオブジェクト変数として配置してみるとどうか。
 結論としては実行時エラーが発生します。

 ●Userform2モジュール ------------------------------------------------------------

    Option Explicit
    Private WithEvents CommandButton1 As MSForms.CommandButton
    Private TextBox1 As MSForms.TextBox
    Private TextBox2 As MSForms.TextBox
    Private TextBox3 As MSForms.TextBox
    Private TextBox4 As MSForms.TextBox

    Private Flg As Boolean

    Public Function GetText(Str1 As String, Lng1 As Long, Dbl1 As Double, Date1 As Date, Var1 As Variant) As Boolean
        TextBox1.Text = Str1
        TextBox2.Text = Lng1
        TextBox3.Text = Dbl1
        TextBox4.Text = Date1
        Me.Show vbModal
        If Flg Then
            Str1 = TextBox1.Text '実行時エラー91:オブジェクト変数またはWithブロック変数が設定されていません。
            Lng1 = TextBox2.Text
            Dbl1 = TextBox3.Text
            Date1 = TextBox4.Text
            Var1 = Lng1 + Dbl1
        End If
        GetText = Flg
    End Function
    Private Sub CommandButton1_Click()
        Flg = True
        Unload Me
    End Sub

    Private Sub UserForm_Initialize()
        Set TextBox1 = Me.Controls.Add("Forms.TextBox.1", "TextBox1")
        With TextBox1
            .Top = 2
        End With
        Set TextBox2 = Me.Controls.Add("Forms.TextBox.1", "TextBox2")
        With TextBox2
            .Top = 21
        End With
        Set TextBox3 = Me.Controls.Add("Forms.TextBox.1", "TextBox3")
        With TextBox3
            .Top = 40
        End With
        Set TextBox4 = Me.Controls.Add("Forms.TextBox.1", "TextBox4")
        With TextBox4
            .Top = 59
        End With
        Set CommandButton1 = Me.Controls.Add("Forms.CommandButton.1", "CommandButton1")
        With CommandButton1
            .Top = 78
        End With
    End Sub

 標準モジュール ------------------------------------------------------------

    Sub test2()
        Dim T As String, L As Long, S As Double, D As Date, V As Variant
        Dim U As New UserForm2
        T = "abc"
        L = 100&
        S = 1.5
        D = Date
        Debug.Print "Boolean", "String", "Long", "Double", "Date", "Variant"
        Debug.Print U.GetText(T, L, S, D, V), T, L, S, D, V
    End Sub

 Unload後にTextBoxにアクセスしようとしているので、素直に考えてみれば当たり前なんですよね。
 むしろUserform1の方が動きとしてはおかしい?と言えるのかも知れません。
 Unload後であっても、GetTextを終えるまではTextBoxにアクセでき、プロパティの値も残っています。
 ずっとこれがUnloadの正常な動きだと誤解してました。

 しかしUserform2のエラー発生個所を見ると、
 FlgというPrivate変数にはTrueという内容が残っていることが分かります。
 という事は、
 TextBoxの内容も、UnLoad前にをPrivate変数に覚えさせておけば、イケるかも知れない。

 ●Userform3モジュール ------------------------------------------------------------

    Option Explicit
    Private WithEvents CommandButton1 As MSForms.CommandButton
    Private TextBox1 As MSForms.TextBox
    Private TextBox2 As MSForms.TextBox
    Private TextBox3 As MSForms.TextBox
    Private TextBox4 As MSForms.TextBox
    Private Flg As Boolean, ResStr As String, ResLng As Long, ResDbl As Double, ResDate As Date, ResVar As Variant

    Public Function GetVariable(Str1 As String, Lng1 As Long, Dbl1 As Double, Date1 As Date, Var1 As Variant) As Boolean
        TextBox1.Text = Str1
        TextBox2.Text = Lng1
        TextBox3.Text = Dbl1
        TextBox4.Text = Date1
        Me.Show vbModal
        If Flg Then
            Str1 = ResStr
            Lng1 = ResLng
            Dbl1 = ResDbl
            Date1 = ResDate
            Var1 = ResVar
        End If
        GetVariable = Flg
    End Function
    Private Sub CommandButton1_Click()
        ResStr = TextBox1.Text
        ResLng = CLng(TextBox2.Text)
        ResDbl = CDbl(TextBox3.Text)
        ResDate = CDate(TextBox4.Text)
        ResVar = ResLng + ResDbl
        Flg = True
        Unload Me
    End Sub

    Private Sub UserForm_Initialize()
        Set TextBox1 = Me.Controls.Add("Forms.TextBox.1", "TextBox1")
        With TextBox1
            .Top = 2
        End With
        Set TextBox2 = Me.Controls.Add("Forms.TextBox.1", "TextBox2")
        With TextBox2
            .Top = 21
        End With
        Set TextBox3 = Me.Controls.Add("Forms.TextBox.1", "TextBox3")
        With TextBox3
            .Top = 40
        End With
        Set TextBox4 = Me.Controls.Add("Forms.TextBox.1", "TextBox4")
        With TextBox4
            .Top = 59
        End With
        Set CommandButton1 = Me.Controls.Add("Forms.CommandButton.1", "CommandButton1")
        With CommandButton1
            .Top = 78
        End With
    End Sub

 標準モジュール ------------------------------------------------------------

    Sub test3()
        Dim T As String, L As Long, S As Double, D As Date, V As Variant
        Dim U As New UserForm3
        T = "abc"
        L = 100&
        S = 1.5
        D = Date
        Debug.Print "Boolean", "String", "Long", "Double", "Date", "Variant"
        Debug.Print U.GetVariable(T, L, S, D, V), T, L, S, D, V
    End Sub

 エラーは発生しませんでした。
 しかし各引数の内容にはおかしいものがあります。
 引数の型でいうと、String型、Variant型については戻ってきた値が空っぽでした。

 ステップ実行で確認してみたところ、UnLoad直後にPrivate変数の中身が無くなっている様でした。
 Userform2でのオブジェクト変数と同様の振る舞いなのでしょう。

 ちなみにUserform3内のString型のPrivate変数をByte()に代えたら覚えててくれるか試してみましたが
 こちらもUnLoad直後に無くなりました。配列はダメなのかも知れません。

 ココまで来るとむしろ、Boolean、Long、Double、Dateはなぜ破棄されないのか?
 そっちの方が特殊なんじゃないのか? 実は毎回破棄されない保証など無いのでは?
 と思えてきました。

 結構あちこちに使ってる手法なのですが・・・
 で、表題の質問に繋がります。
 Userform内にUnLoad Meと書くのはアリですか? 修正しておくべきでしょうか?

< 使用 Excel:Excel2010、使用 OS:Windows7 >


 タイトルしか読んでないけど。
 >Userform内にUnLoad Meと書くのは作法としてアリですか?』
 普通にやります。
(BJ) 2018/11/08(木) 03:32

ざっと流し読みして率直な疑問ですが、
(1)なんでユーザー定義関数「GetText」を標準モジュールに記述しないんですか?
(2)同じく、グローバル変数を標準モジュールに置きたくない理由は何ですか?

↓もしかしたらこちらが参考になるかもしれません。
http://officetanaka.net/excel/vba/tips/tips113.htm

的を外していたらごめんなさい。
(もこな2) 2018/11/08(木) 06:14


 何となくコードディングの癖というか、組み立て方に親近感を覚えます。私とスタンス似てるかも^^;

 Userform内部の操作に関わるものは、なるべくUserform内に書いてしまい、
 Userformを利用する側には極力Userform内部の操作は書きたくない。
 利用する側と情報をやり取りする「窓口」は明確に表現し、出来れば1本化したい。

 ・・・という考え方で、こういう表現になっているのではないでしょうか?
 モジュールの役割分担をかなり意識しながら書かれているのかなと想像してます。
 あるいはクラスモジュールの方が慣れてらっしゃる方なのかな? (test内でもNewキーワード使ってるし)
 Propertyプロシージャとか好んで使ったりしてませんか?  (←否定的な意味ではなく私の願望 ^^;)

 とりあえず、UnLoadを書く場所についての私なりの見解なのですが、
 Userform内に書く事例はネット上でもよく見かけるので、普通にアリだと思います。
 但し、UnLoad後もUserform内部での処理を継続する様な使い方は、正直見たことがありません。
 おそらくそういう場合はHideを使うものなのではないでしょうか?

 例示されているコードにはShowとUnLoadが登場してますが、
 この2つは本来お互いが「対」の役割りを担うものではないと考えてます。
 Showの反対はHide。UnLoadの反対はLoadと捉える方が自然に思うんですよね。

 そう考えると、
 Loadを書く場所はどうしてもUserformを利用する側(今回の例では標準モジュール)になる訳ですから、
 その対となるUnLoadも利用する側に書かれている方が、表現としては素直ですよね? 
 (まぁ実際Loadってあんま使わないですけど^^;)

 同様にShowをUserform内に書くのであれば、その対としてUserform内に書くのはHideの方が自然かな、と。
 そういう考え方も出来ると思うのですが、如何でしょうか?

 しかしこれはなかなかおもしろいですね。
 UnLoad後の変数の有効期限なんて、即終了だと思ってました。
 こんな書き方も出来たんですね。

 配列ではないスカラ変数(という表現が正しいのか分かりませんが)については、
 UnLoad直後ではなく、UnLoad後にUserformからの参照が無くなってから破棄されるのかな?
 でも、String型、Variant型はUnLoad直後に消えてしまうのですね。
 ひょっとしたらメモリ上での変数のサイズが固定的であるか否かと関係があるかも知れません。

 いずれにせよ、変数の型によって消える消えないが分かれてしまう様な表現なのであれば、
 私ならやめておきますよ。

(白茶) 2018/11/08(木) 15:56


 BJさん、もこな2さん、白茶さん、お返事ありがとうございます。

 もこな2さんからのご質問事項については、概ね白茶さんの推察の通りです。
 これといった理由がある訳ではなく「こう書くものだ」というアタマがありました。

 言い訳ですが、私の主義と言うよりは、VBAの手解きをして頂いた職場の先輩のクセが、
 自分にも潜在的に根付いてしまっているのだと思います。 (従っておかないとグチグチ言われるし)

 >クラスモジュールの方が慣れてらっしゃる方
 残念ながらこちらは慣れてはいませんね。通算しても10個も作ってない。まだまだです。
 でも、
 >Propertyプロシージャとか好んで使ったり
 こちらは確かにUserform内では結構使う事あります。
 モジュール外と値の受渡しをする時とか、複数のコントロールで値を同期させる時とか。

 そう言えば上記の先輩が作った標準モジュールにも、
 「イベント付きのLongだよ」みたいなコメントを添えたPropertyプロシージャがあったりして
 参考にさせてもらった覚えがあります。やはりこれも先輩の影響ですね。

 ある意味「毒されている」とも言えるかもw
 何せ、もこな2さんに
 >グローバル変数を標準モジュールに置きたくない理由は何ですか? 
 と尋ねられて
 「 え? 標準モジュールにPublic変数? 」と戸惑ってしまったくらいです。
 もはや弊害レベルですよ

 >Showの反対はHide。UnLoadの反対はLoadと捉える方が自然
 これ、ものすっごくしっくりいきました。
 なるほどなぁ。
 確かにそう言われると潔く修正したくなります。

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

(中途B) 2018/11/08(木) 21:57


 弊害レベルとまで言われるとなんだか可哀想になりますね^^;

 一応先輩を擁護しますけど、割と一般的なセオリーとしてよく言われる事ですから
 そのスタンスを基本的に置いたままでも構わないと思いますよ。

 モジュールの汎用性(他所での使い回しが効く)は高いに越したことはないです。
 「あの時作ったUserform、こっちでも使えるなぁ」なんて事は、たいてい後になってから思いつくので、
 いざ移植する時に、そのモジュールだけ移植すれば済むくらいに固めておいた方が作業が楽だし、
 業務で使ってるのなら、後任者がメンテする時にもコードを読み取り易いです。

 >「イベント付きのLongだよ」
 なるほど、上手いこと言いますね^^;

(白茶) 2018/11/09(金) 11:20


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.