[2018/06/07] プログラム実行中のEXEファイルのファイル名変更ができる! (No.50)
[2018/05/24] 関数の戻り値がクラス(オブジェクト)の場合について (No.47)
[2018/05/22] 列挙体(Enum)の宣言と、メンバの値と名前の取得について(GetValues, GetName) (No.46)
[2018/05/21] 各種オブジェクトのコピーができるディープコピー(BinaryFormatter,MemoryStream,Serialize,Deserialize) (No.45)
-
データ入力を行うフォームを作成する時に、システム標準のTextBox等でできればいいのですが、 より細かな入力制限などを行いたい場合に市販されているコントロールを利用するのも手だと思います。
GrapeCity かたはいろんなコントロールが販売されていて、フォーム上の入力を楽にするものの一つとして InputMan があります。 InputMan には日付、文字列、数値、コンボボックスなどの拡張入力コントロールが揃っています。 一からコントロールを作成するよりは買った方が早いのでこれを使用しています。 (私は特に GrapeCity の回し者でもありませんが)
InputManのコントロールですが、一つ気をつけないといけない点があります。
例として数値入力のコントロールである GcNumber を取り上げてみます。 GcNumber は数値入力ですので、値を設定・取得する場合のプロパティとして Value が利用します。 この Value のデータ型がNULLを許すNullable型のDecimalとして定義されています。
この例を示すために、 フォーム上に GcNumber を1個と、ボタンを2個貼り付けます。GcNumber の Value について
Public Class frmDataNullable 'フォームロード時イベント Private Sub frmDataNullable_Load(sender As Object, e As EventArgs) Handles MyBase.Load 'GcNumber1に数値を削除した時にNULLを許可 Me.GcNumber1.AllowDeleteToNull = True End Sub 'Nullableな結果を通常の変数で受ける Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Try 'GcNumber1の値を受ける変数 Dim decNrm As Decimal '通常のDecimal型 decNrm = Me.GcNumber1.Value Dim strMsg As String = String.Format("GcNumber1.Value = {0}", decNrm.ToString) MsgBox(strMsg, MsgBoxStyle.OkOnly, "通常のDecimal型") Catch ex As Exception MsgBox(ex.Message) End Try End Sub 'Nullableな結果をNullableな変数で受ける Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click Try 'GcNumber1の値を受ける変数 Dim decNul As Decimal? 'NullableなDecimal型 decNul = Me.GcNumber1.Value Dim strMsg As String '値を表示 strMsg = String.Format("GcNumber1.Value = {0}", decNul.ToString) MsgBox(strMsg, MsgBoxStyle.OkOnly, "NullableなDecimal型") '値の有効状態を表示 strMsg = String.Format("GcNumber1.HasValue = {0}" & vbCrLf & "GcNumber1.Valus IsNothing = {1}", decNul.HasValue, Not decNul Is Nothing) MsgBox(strMsg, MsgBoxStyle.OkOnly, "NullableなDecimal型") Catch ex As Exception MsgBox(ex.Message) End Try End Sub End Class
結果これを実行し、「通常の変数」(Button1)を押下すると以下の様になります。
この時の処理は、通常のDecimal型の変数を用意し、その変数に GcNumber の Value を設定しています。 コントロールに数値が入っているときは問題無く動作します。
GcNumber の内容を全て削除して空白にしてから、「通常の変数」(Button1)を押下すると以下の様になります。
GcNumber の Value がNullになる様で、変数の代入時にエラーが発生し、Catch でエラーメッセージが表示されます。
このエラーを回避するには当然なのですが、GcNumber の Value と同じデータ型の変数に代入することです。 「Nullableの変数」(Button2)を押下すると以下の様になります。
Nullableの変数を宣言する時にはデータ型の最後に ? (クエスチョンマーク)を付加します。
この変数は、状態として数値が入っている場合と、NULLの場合がありますので、 この変数をそのまま計算式に使う場合には注意が必要です。 少し面倒ですが、変数がNULLかどうかで計算を分けてやる必要があります。
ただ、データベースのテーブルでは数値項目でもNULLが許可されている場合には、 Nullableの変数は使えると思います。テーブルのカラムに値を設定するSQL文を作成する時には 変数がNULLかどうかで、値にNULLとするのか、それとも値そのものを設定するのかの処理を分けることが 必要かと思います。
今回は GcNumber を例にとりましたが、日付入力コントロールの GcDate も同様に Value プロパティを持っていて、このデータ型が Date? となっていますので 変数で受ける場合はそれに合わせる必要があります。
PR -
プログラム実行中のEXEファイルを削除しようとすると以下の様にシステムから、 「BlogTestによってファイルは開かれているため、操作を完了できません。」というエラーが返されます。
EXEファイルの例として 「SerialPortコントロールの使い方その2」 の実行を行いました。
プログラム実行中はそのEXEファイルが、システムによってロックが掛けられていて削除できないと思います。
しかし、削除は出来ないのですが、ファイル名の変更は可能なのです。 プログラム実行中にできるとは驚きなのですが、この機能を使うことでプログラム実行中にファイルの更新ができるのです。
プログラムの更新手順は以下の様に行えると思います。- 実行中のEXEファイルを別の名前に変更する。
- 実行ファイルの更新版をコピーする。
- 別の名前に変更されたファイルを削除する。(もしくは別のフォルダにコピーする。)
このEXEファイルのファイル名変更ですが、DLLファイルでも可能な様です。
Windowsのシステムアップデートなどもこの機能が無いとできない気がします...
-
関数の戻り値として、関数内でクラスのインスタンスを New で生成しその参照を戻す場合があります。 この場合、戻されたその参照の実体はどこにあるのでしょうか。これなのですがVB.NETシステムのヒープ領域のメモリ上に在る様です。 このメモリ上の参照しているのが New で生成された結果の値を保持する変数になります。
以下のソースにその例を示します。
frmRetval と名付けたフォーム上に1個のボタンを張り付けてあります。 このフォームクラス内に、戻り値テスト用のクラスとして clsRetTest を宣言しています。 このクラスは2個の内部変数とその内容を文字列で返すメソッド、及びコンストラクタの簡単な処理から構成します。
更にクラス clsRetTest を戻す関数として GetRetVal 関数を定義しています。戻り値テスト用
Public Class frmRetval '戻り値テスト用のクラス Private Class clsRetTest Private _strTest As String Private _intTest As Integer 'コンストラクタ Sub New(ByVal strTest As String, ByVal intTest As Integer) Me._strTest = strTest Me._intTest = intTest End Sub '内部データの表示用の文字列取得 Function GetDataString() As String Return "strTest=" & Me._strTest & vbCrLf & "intTest=" & Me._intTest.ToString End Function End Class 'クラスを戻す関数をテストする処理 Private Sub RetrunTest_Click(sender As Object, e As EventArgs) Handles RetrunTest.Click 'クラスを生成する関数コール Dim clsTest As clsRetTest = GetRetVal() '内部データの表示 MsgBox(clsTest.GetDataString) End Sub 'クラス(clsRetTest)を戻す関数 Private Function GetRetVal() As clsRetTest 'クラスの生成 Dim clsRet As New clsRetTest("123", 100) 'クラスを戻す Return clsRet End Function End Classクラスを戻す関数をテストするのは、ボタンクリックイベントで行っています。 このイベントでは、クラスを生成する関数(GetRetVal)をコールし結果を clsTest 変数に入れています。 GetRetVal の中ではクラス clsRetTest がインスタンスがヒープ上に生成されその参照を戻り値として戻します。 イベント処理内ではその参照を使い、そのメソッドをコールすることができます。
このイベント処理内の変数 clsTest はこの処理を抜けると使用できなくなります。 (自動変数はそれ自身を含むブロックを超えては参照が出来ません。)
clsTest は使用できなくなるのは分かるのですが、先ほどのヒープ上の clsRetTest クラスの領域はどうなるのでしょうか。 少し心配になりますが、実はシステム側である時点が来た時にきれいに掃除をしてくれます。 (このことをガーベージコレクションと言うらしい。)
ボタンクリックが何度も押されると RetrunTest_Click が何度も実行されることになります。 この時はその度に、ヒープ上に別のインスタンスが生成され、そのインスタンスの参照が返されます。 そうすると使わないインスタンスがどんどん溜まっていくのですが、これもそのうちガーベージコレクションが行われて無くなる様です。 (ガーベージコレクションがいつ行われるかは神のみぞ知る的な感じな様ですが)
イベント処理内の clsTest がイベント処理が終わった後でも参照したい場合には、 その変数を以下のソースの様に、イベント処理内からフォームクラスの静的変数として関数の外で宣言すればできます。
クラス参照を静的変数で宣言
Public Class frmRetval '戻り値テスト用のクラス Private Class clsRetTest Private _strTest As String Private _intTest As Integer 'コンストラクタ Sub New(ByVal strTest As String, ByVal intTest As Integer) Me._strTest = strTest Me._intTest = intTest End Sub '内部データの表示用の文字列取得 Function GetDataString() As String Return "strTest=" & Me._strTest & vbCrLf & "intTest=" & Me._intTest.ToString End Function End Class 'クラス参照宣言 Dim clsTest As clsRetTest 'クラスを戻す関数をテストする処理 Private Sub RetrunTest_Click(sender As Object, e As EventArgs) Handles RetrunTest.Click 'クラスを生成する関数コール clsTest = GetRetVal() '内部データの表示 MsgBox(clsTest.GetDataString) End Sub 'クラス(clsRetTest)を戻す関数 Private Function GetRetVal() As clsRetTest 'クラスの生成 Dim clsRet As New clsRetTest("123", 100) 'クラスを戻す Return clsRet End Function 'クラスのメソッドのみをコールするイベント Private Sub DisplayClass_Click(sender As Object, e As EventArgs) Handles DisplayClass.Click '内部データの表示 MsgBox(clsTest.GetDataString) End Sub End ClassclsTest をフォームクラスの静的変数として宣言すれば、そこに設定されたヒープへの参照が残りますので、 それを使ってクラスの処理ができます。今回の例では、DisplayClass_Click がそれに当たります。
但し、このソースには問題が有ります。 RetrunTest_Click を行う前に DisplayClass_Click を行うと例外が発生してしまいます。 これは、clsTest が設定される前(値としては Nothing)にクラスのメソッドを使用するからです。 この問題を解消するには DisplayClass_Click 内で clsTest が Nothing では無いことを確認し 使用します。
尚、この clsTest ですが何時廃棄されるのかと言いますと、フォームクラスが廃棄される時点で行われ、 その後ガーベージコレクションが行われると思います。関連する記事
⇒関数の引数がクラスオブジェクトの場合の注意点について
-
区分データ等のソース上の既読性をアップする場合や、間違いを無くする場合に列挙体(Enum)を用います。
列挙体(Enum)の宣言は以下の様になります。 Enum 宣言の中のメンバーは値を指定しなくても、0からの値が割り振られます。 また、メンバーの値を"="イコールで定義すればその値となりますが、それ以降の値の定義されていないものは1加算された値が定義されます。
以下のソースでは、メンバー値を明示的に定義しない場合と、定義した場合の例を示します。列挙体(Enum)の宣言
'メンバ(定数)の値を定義しない場合のEnum Enum TestEnum1 Japan USA China Korea Other End Enum 'メンバ(定数)の値を定義する場合のEnum Enum TestEnum2 Japan = 100 USA China Korea = 1000 Other End EnumTestEnum1 では「Japan」が「0」の値で、以下「USA」「China」「Korea」「Other」は順に「1」「2」「3」「4」の値となります。 また、TestEnum2 では「Japan」が「100」の値で、順に「101」「102」「1000」「1001」の値となります。
実際にプログラムで列挙体(Enum)のメンバー名とその値を列挙する方法を以下に示します。
以下のソースでは、 Enum.GetValues メソッドを用いることで、指定した列挙体に含まれている定数の値の配列が 返されます。 Enum.GetValues メソッドでは指定した値を持つ指定した列挙体にある定数の名前を取得します。
Enumのメンバ名と値を表示
'Enumのメンバ名と値を表示するテスト Private Sub EnumTest_Click(sender As Object, e As EventArgs) Handles EnumTest.Click 'TestEnum1のメンバの値を列挙 Dim Enum1Val As TestEnum1 Dim strMsg As String = "" For Each Enum1Val In [Enum].GetValues(GetType(TestEnum1)) 'メンバ名を取得する Dim strName As String = [Enum].GetName(GetType(TestEnum1), Enum1Val) 'メンバの値と名前を表示する strMsg &= String.Format("{0} = {1}", strName, CInt(Enum1Val)) & vbCrLf Next MsgBox("TestEnum1:" & vbCrLf & strMsg) 'TestEnum2のメンバの値を列挙 Dim Enum2Val As TestEnum1 strMsg = "" For Each Enum2Val In [Enum].GetValues(GetType(TestEnum2)) 'メンバ名を取得する Dim strName As String = [Enum].GetName(GetType(TestEnum2), Enum2Val) 'メンバの値と名前を表示する strMsg &= String.Format("{0} = {1}", strName, CInt(Enum2Val)) & vbCrLf Next MsgBox("TestEnum2:" & vbCrLf & strMsg) Enum2Val = TestEnum2.Other End Sub尚、Enumには同じ値のものを別のメンバー名で定義することが可能ですが、 同じ値のメンバーがある場合は、注意が必要だと思います。 (プログラム上、必要になることもありますので、制限は必要無いとは思います)
列挙体(Enum)の宣言その2
'メンバ(定数)の値を定義する場合のEnum:2 Enum TestEnum2 Japan = 100 USA China Korea = 1000 Other = 1000 '同じ値で「Other」を宣言する End Enum
-
クラスや構造体などのオブジェクトのクローンコピーを作りたい場合があります。 各種のオブジェクトのデータを退避する時に、一括でクローンが作れれば便利なのですが、BinaryFormatter クラスを 使って、シリアライズとデシリアライズを行うことでできます。
以下のソースにその関数を記します。 BinaryFormatter の Serialize メソッドによりコピー元のオブジェクトをメモリストリームに展開します。 その後メモリストリームの内容を別のオブジェクトにデシリアライズを行います。ディープコピー関数
''' ----------------------------------------------------------------------- '''''' オブジェクトのコピー ''' '''Type ''' <param name="objSrc">コピー元のオブジェクト</param> '''クローンオブジェクト ''' ----------------------------------------------------------------------- Function DeepCopy(Of T)(ByVal objSrc As T) As T 'クローンオブジェクト宣言 Dim objRet As T 'BinaryFormatterクラス Dim b As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter 'バイト処理を行うメモリストリーム Dim ms As New System.IO.MemoryStream() Try 'シリアライズ(MemoryStreamにシリアライズ化して展開) b.Serialize(ms, objSrc) 'メモリ位置を初期化 ms.Position = 0 'デシリアライズ(MemoryStreamからデシリアライズ化してオブジェクトに戻す) objRet = CType(b.Deserialize(ms), T) Catch ex As Exception ' Finally ms.Close() End Try 'クローンを戻す Return objRet End Functionこの関数を使う例を以下に記します。
以下のソースでは、 DeepCopy 用のテストクラスとして clsCopyTest と、 テスト構造体として structCopyTest を宣言しています。
それぞれのクラス、構造体ともに <Serializable()> を指定しています。 これが無いとシリアライズ処理ができないので注意が必要です。
ディープコピー関数の使用例
'''
''' DeepCopy 用のテストクラス ''' <Serializable()> _ Private Class clsCopyTest Public _strTest As String Public _intTest As Integer 'コンストラクタ Sub New(ByVal strTest As String, ByVal intTest As Integer) Me._strTest = strTest Me._intTest = intTest End Sub '内部データの表示用の文字列取得 Function GetDataString() As String Return "strTest=" & Me._strTest & vbCrLf & "intTest=" & Me._intTest.ToString End Function End Class '''''' DeepCopy 用のテスト構造体 ''' <Serializable()> _ Private Class structCopyTest Private _strTest As String Private _intTest As Integer 'コンストラクタ Sub New(ByVal strTest As String, ByVal intTest As Integer) Me._strTest = strTest Me._intTest = intTest End Sub '内部データの表示用の文字列取得 Function GetDataString() As String Return "strTest=" & Me._strTest & vbCrLf & "intTest=" & Me._intTest.ToString End Function End Class Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click 'クラスでのテスト Dim clsT As New clsCopyTest("ABCD", 100) 'クラス生成 Dim clsCloneT As clsCopyTest = DeepCopy(clsT) 'クラスクローンコピー clsCloneT._strTest &= "..." 'クローンのデータを更新 clsCloneT._intTest *= 100 'クラスのメソッドで内容表示 MsgBox("Src :" & vbCrLf & clsT.GetDataString & vbCrLf & "Clone:" & vbCrLf & clsCloneT.GetDataString) '構造体でのテスト Dim strucT As New structCopyTest("EFGHI", 200) '構造体生成 Dim strucCloneT As structCopyTest = DeepCopy(strucT) '構造体クローンコピー MsgBox(strucCloneT.GetDataString) '構造体のメソッド End Subボタン処理内でクラスのテストと、構造体のテストを行っています。 クラスの方は、クローンコピーの値を少し変化を加えています。 その後、それぞれのクラス内の値を見てみると、両方の値が異なることが分かります。
関連する記事
⇒クラスのコピーについて(Object.MemberwiseClone)