QA@IT

DLLでフォームを作りたい・値の受け渡しをしたい(VB2008)

15768 PV

はじめまして。よろしくお願い致します。

これまではVB2008のWindowsフォームアプリケーションを選択し、フォームデザイナで作成したフォームを

Form_HOGE.Show

として使っていたのですが、使用するフォーム数が多くなり、他のコードを使い回すことも含めDLL化の
勉強中です。

アプリケーション起動時に使用するForm1のTextBox1.TextのNullチェックやNumericのチェックは
DLL側で行うことに成功したのですが、

【EXE起動時に開くフォーム】
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim V_VAL As String = Me.TextBox1.Text
        Dim CHK As New HOGE0001.CHKNULL

        '-- チェック対象の値をセット
        CHK.Add(V_VAL)

        '-- nullチェック
        If CHK.CHK_NULL = True Then
            Me.TextBox2.Text = "No Values" 'メッセージを表示するテキストボックス
            Me.TextBox1.Focus()            'チェック対象文字列
            Exit Sub
        End If

END Sub

END Class
【DLL】
    ' 値を受け取る
    Public Sub Add(ByVal Value As String)
        Values = Value
    End Sub

    ' NULLチェック
    Public Function CHK_NULL()
        Dim Ret As Boolean
        If Len(Values) = 0 Then
            Ret = True
        Else
            Ret = False
        End If
        Return Ret
    End Function

このような使い方で良いのでしょうか?

あと、FormをDLL化するにはどうすれば良いのでしょうか?
FormをEXE側で作る方法は各所で見受けられるのですが、DLL化のヒントを見つけることが出来ませんでした。

ご教示よろしくお願い致します。

回答

残念ながらその方法はダメです。

まずはClassについて学ばれるといいと思います。Classライブラリを作り、部品を作ります。
FormもClassですので、それと同じように作成できます。
ただし新規クラスライブラリから作成した場合は参照設定が足りませんので必要な参照設定を追加する必要があります。

単純に再利用だけ できればよい と割り切った場合、そのexeファイルを参照設定で指定すればForm1は Publicなので呼び出せます。

さらに Windows Formアプリケーションとして作成して、プロジェクトのプロパティからクラスライブラリに変更すればDLLにできるかもしれません(VBだとMain関数ないのでそれだけでいけるかも)。

いくつか指摘を上げていきます。

1.既定のインスタンスをつかっている

Form_HOGE.Show
として使っていたのですが

これはおそらくインスタンス変数を作らずにShowを呼び出していると思いますがこの書き方はダメです。
なぜか Microsoftはこの悪しき機能「既定のインスタンス」を復活させましたが使ってはいけません。

他のクラスから呼び出す場合は必ずインスタンス変数を使ってください。
(ちなみに個人的にはVB6でも既定のインスタンスは使うべきではないと思っています)。

Dim hogeForm = new Form_HOGE()
hogeForm.Show()

2.循環参照・または密結合となる

やりたいこととしては、NULLの場合はDLL側から直接Form1.TextBox2に"No Values"とセット出来ないか、

この発想はよくありません。現在のコードの方が良いです。
これを実現する案はいくつかありますが、

' これは循環参照になる
Public Function CHK_NULL(ByVal form as Form1) As Boolean
   ' 代入以外省略
   Form1.TextBox2.Text = "No Values"
End Function
または
' 動くがWindows.Formsを参照する必要がありよくない。
Public Function CHK_NULL(ByVal textbox as TextBox) As Boolean
   ' 代入以外省略
   textbox.Text = "No Values"
End Function

どちらもよくありません。
最初の例では クラス CHK_NULL は Form1を知らなくてはいけません。
なので CHK_NULLを含むプロジェクトは Form1を含むプロジェクトを参照仕様とします。
ところが Form1を含むプロジェクトはすでに CHK_NULLを含むプロジェクトを参照しています。お互いに参照しあっているため循環参照になります。
CHK_NULLは事前にForm1を知らなければいけない。 と Form1は事前にCHK_NULLを知らなければならない。 は同時には満たせません。
できたとしてもお互いに知り合っているのは密結合ですのでよくありません。

二番目の例ですが、こちらはWindows.Forms を参照設定に加えれば可能でしょう。
呼び出し(Form1)は

CHK_NULL(TextBox2)

と呼べば可能でしょうが、そもそもCHK_NULLが値をセットする必要があるでしょうか?

Form以外から呼び出したくなったときは?

別のフォームでは日本語の文字をセットしてほしくなったときは?

そしてそもそもTextBox2に値を設定する必要がなければWindows.Formsへの参照も必要ないのです。
文字を出したいのは画面(Form)の都合なので画面でよろしくやってくれ。と、CHK_NULLの立場で考えてください。
(値のチェックをするのは画面だけの都合ではないのでDLL化して再利用するのはいいと思います)

また、一般にメソッドに複数の機能を持たせるのはよくありません。

あとインターフェースを使う手もありますが、同じ理由でこのケースでは使わない方がいいでしょう。

3.変数名

名前はわかりやすいものにするといいです。慣習のようなものがありますので、機会があれば本を読まれるといいでしょう。リーダブルコードは最近出た本なので、悪く言えば過去の情報焼き直しですが読みやすくて薄いのでいいと思います。
あとはMicrosoftでも変数名の規則はネットで提示してます。こちらだけでも問題はありません(本だとお金かかりますしね)。

4.インスタンス変数の使い方

DLLの変数が Valuesとなっているのは引数 Valueとかぶるためと見受けましたが、これはMeを使えば同じ名前でも回避できます。

と、いうのを踏まえて私が書くと以下のようになります。


続きです。

コードの前に、

5.関数の戻りの型は必ず指定してください

( As Boolean などを省略しない )

ではコードです。

Public Class Form1

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim vVal = targetText.Text
        Dim chk = New NullValidator()

        chk.SetCheckTarget(vVal)

        If chk.IsTargetValueNull() = True Then
            warningText.Text = "No Values"
            targetText.Focus()

            ' SubでもReturnを使いましょう
            Return
        End If

        Dim frmHoge = New Form_HOGE()
        frmHoge.Show()

    End Sub
End Class
Public Class NullValidator
    ' または NullCheckerとか

    Private value As String

    ''' <summary>
    ''' 値を受け取る
    ''' </summary>
    ''' <param name="value"></param>
    ''' <remarks></remarks>
    Public Sub SetCheckTarget(ByVal value As String)
        Me.value = value
    End Sub

    ''' <summary>
    '''  NULLチェック
    ''' </summary>
    Public Function IsTargetValueNull()

        If Len(Me.value) = 0 Then
            ' これだと文字列表現が "" であるという意味になるので
            ' String.IsNullOrEmpty(Me.value) とするか 
            ' 厳格にNullか判断する(""は許すがNothingは許さない)ならば
            ' If Me.value Is Nothing Then

            Return True
        Else
            Return False
        End If

        ' 判定が終わったタイミングでReturnできるのでここでReturnするのはやめた。
        ' 共通の後処理がどうしても必要なら Finallyを使う。


    End Function

End Class

また、IF文で判定した論理値(Boolean)と IsTargetValueNull の返却値は一致するので

    Public Function IsTargetValueNull()

        Return (Len(Me.value) = 0)

    End Function

とできますね。

まだ直したい部分はいくつかありますが、(自分でつけたSetCheckTargetもSetって名前がちょっと引っかかるしそもそもこれはプロパティの方が…)
参考になれば幸いです。

Formのクラスライブラリの作り方は、新規クラスライブラリで、プロジェクトを右クリックして、「追加」>「Windowsフォーム」 がいればそれを指定してあげれば参照も勝手について楽かもしれません。
VS2012で見たのでVS2008にいなかったらすいません。


以下、回答を受けて追記します

1)

これらをそれぞれ独立したDLLにした方が良いという意味か、DLL内のそれぞれのメソッドには、
この場合ですとチェック以外の機能を実装しない方が良いという意味のどちらになるでしょうか?

まとめる単位としてはDLLではなくClassになります。Classとは階級の意味 ではなく まとまりの意味のクラスです。種類や集合と考えた方がよいでしょう。
.NETの場合は1つのDLLには複数のClassを含めることができます。それぞれどのような集合にするかはClassの名前やDLLの名前や名前空間(VB.NETだと何故か隠れて見えにくいですがプロジェクトのプロパティで指定できます)をどうするかが重要です。

先の例ではNullValidatorというクラスの名前にしました。この名前でNumericチェックを行ったら使う側はわかりませんよね?
ただしNullチェックはしてくれそうだとすぐにわかるという点は評価できます。
このクラスに他のチェックもしてほしいのであれば、もうすこし抽象的な名前にすべきでしょう。

NumericValidator、WideNarrowValidator…などチェック項目の度にクラスを増やすのは この場合では 冗長に思えます。
1つのクラスに1つの(チェック用)メソッドしかないってなんだかもったいないですよね。
同じValidatorならまとめちゃうのは今回は得策に見えます。

また DLLの名前がたとえば FliedOnion.Validator.DLL だったとして、そこに複雑な計算をするクラス(ComplexCalculator)があったとします。
使う側の身で考えてComplexCalculatorを使いたいときに FliedOnion.Validator.DLL を参照設定に追加することは自然でしょうか?

というような観点で考えてください。逆に

ではDLLの名前を FliedOnion.Commons.DLL としてみたらどちらも入れれそうですが、中に何が入っているかわかるでしょうか。

作ろうとするものの規模や設計方針、どこを中心だと考えるかで何が良いかは変わってくると私は考えています。

ですのでどれが良いか(より自然か)は作るものによって変わるでしょう。クラスの数が大して多くないのならぜんぶCommonsに突っ込んでもいい場合もあるでしょう。
ただ一つ言えるのはできるだけ名前をわかりやすく(そして可能ならシンプルに)すれば使いやすくなるということです。

もう一歩進んで考える場合はビルドする範囲の問題もかかわってきます。
先の例で、ValidatorとCalculatorが同じDLLだとCalculatorのバグを直してDLLのバージョンを上げると、Validatorも何も直してないにもかかわらずビルドしなおしてバージョンが上がったように見えます(同じDLLファイルなので)。これが良いかどうかも判断基準の一つになります。

2) また、フォームを閉じる際、呼び出し元でnewしたfrmHogeをdisposeさせにはどうすれば良いでしょうか。

基本的にDisposeは自動的になされます。ただし即時ではありません。
閉じるときにやってほしい処理はForm_ClosingやFormClosedに書きます。これがVB6のForm_Unloadにあたるものです。
もしDisposeで実装したい場合は .Dispose()を明示的に呼べば実行されます。
または usingブロックを使うといいですね。

呼び出し元で管理したいのであればShowではなくShowDialogで呼ぶのが一番手軽です。
ただし、親ウィンドウは子ウィンドウを閉じるまで固まりますので、frmHogeを親Formのインスタンス変数にするぐらいでしょうか。

ついでにもしご存じなければ And, Or と AndAlso, OrElse の違いもVB.NETでは大事なので調べてみてください。

Public Class Form1

    Private frmHoge As Form_HOGE = Nothing

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        If frmHoge Is Nothing OrElse frmHoge.IsDisposed Then
            frmHoge = New Form_HOGE()
        End If
        frmHoge.Show()
        frmHoge.Activate()

    End Sub
End Class
編集 履歴 (4)

flied_onion 様

度々大変ご丁寧なご指導を有難うございました。
(回答ガイドラインにコメントでお礼をとありましたが、字数オーバー?となったのでこちらでご容赦下さい)

Class(DLL)の件、難しく考えすぎていたようです。
携わるものの中で軸とすべきところ、柔軟性を持たすべきところをしっかり見極めながら決めてゆきたいと思います。

2つ目のFormを複数開けたくないという部分も、おかげさまで大変クリアになりました。アドバイスを戴き、早速And AndAlso、Or OrElseの違いを調べてみました。これまで先を急ぐあまり、エラーが出なければそれで良しとしてしまい、こういった大切な部分を随分と取りこぼしてきた気がします。また、ご例示下さったサンプルを拝見しますと、自分のコードには大切なものが抜け、不要・冗長な記述が多くあったと痛感しています。

余談となり恐縮ですが、私はこれまでにも各所で質問を投げ、多くの回答者様にお助け頂いてきましたが、これほど丁寧にご回答頂いたのは初めてでした。指針となるサンプルのご提示のみならず、なぜそれが良いのか・ダメなのかという部分まで踏み込んで頂けてとても感謝しております(独習者・初心者にとってはサンプルコードと同じくらい強く知りたい部分です)。

ご専門とはいえ、これだけの文書をお書き下さるには、かなりのお手間とお時間を割いて頂いたと推察致します。心からお礼申し上げます。

行き詰まった際にはまたこちらでお世話になりたいと思います。
拙い質問になろうかと存じますが、どうか今後ともご指導宜しくお願い致します。

編集 履歴 (0)
  • 私としては勝手気ままに回答しているだけですので、あまりお気になさらず。常に回答出来るわけではありませんのでその点はご了承下さい。 -

flied_onion様

ご教示有難うございました。
非常に丁寧にご説明頂き、大変感激しております。

これまでは一つのsubにどちゃ~っと上から下へひたすら書き続けるようなスタイルでした。
一応目的は達成できているのですが、もう一つ先の世界を知ってみたく今回の質問に至りました。

提示頂きましたサンプルのように私のコードも書き換えてみました。
フォームをDLL化するのも

さらに Windows Formアプリケーションとして作成して、プロジェクトのプロパティからクラスライブラリに変更すればDLLにできるかもしれません(VBだとMain関数ないのでそれだけでいけるかも)。

こちらで実現できました。本当に有難うございます!
あと2点ほど質問させて下さいませ。

1)

また、一般にメソッドに複数の機能を持たせるのはよくありません。

例えば、一つのDLLの中に複数のメソッドを書くことが出来ると思います。
私はNULLチェック、NUMERICチェック、半角文字チェック、日付チェック等を1つのDLLに内包しようとしていました。

これらをそれぞれ独立したDLLにした方が良いという意味か、DLL内のそれぞれのメソッドには、この場合ですとチェック以外の機能を実装しない方が良いという意味のどちらになるでしょうか?

2)

Dim frmHoge = New Form_HOGE()
frmHoge.Show()

フォーム上のあるボタンクリックイベントにこの様に記述しますと、ボタンをクリックした回数だけfrmHogeが表示されてしまいます。これを1スレッド内には1つだけのフォームを表示させるように制限するにはどうすれば良いでしょうか。また、フォームを閉じる際、呼び出し元でnewしたfrmHogeをdisposeさせにはどうすれば良いでしょうか。それとも、frmHogeが閉じられるタイミングでfrmHogeもdisposeされるのでしょうか。

素人むき出しでお恥ずかしい限りですが、何卒宜しくお願い致します。

編集 履歴 (0)
  • 回答に追記しました。 -

加筆@odanukiです。

このような使い方で良いのでしょうか?

の部分ですが、やりたいこととしては、NULLの場合はDLL側から直接Form1.TextBox2に
"No Values"とセット出来ないか、という意味です。

このNULLチェックは他のフォームからも使いまわしたいので、どのようにすれば
「呼び出し元のForm.TextBox2」へ"No Values"と値を渡せるかが知りたいです。

よろしくお願い致します。

編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。