-
フォーム上にある同じ種類のコントロールを配列に入れて処理する方法は以下の記事で紹介しましたが、 今回はそれをコレクションクラスの List で行ってみます。
⇒コントロールを配列で処理する方法 :[AddHandler,DirectCast]
以下の画像は、今回のフォームを実行した場合の表示で、Button3 を押下した時の様子です。
以下のソースは、フォームロード時に「コントロール配列」に TextBox1~5 及び Button1~5 を設定し 各ボタンのクリックイベントの処理関数を AddHandler で設定しています。
ボタンのクリックイベントではイベント発生が在ったオブジェクト sender がどのボタンに該当するのかの配列の指標を取得します。 その指標を使って、テキストボックスを List から取得しそのデータを表示しています。■コントロールを List で処理する方法
Public Class frmCtrlList 'TextBoxコントロールのList Private ListTextBox As List(Of TextBox) 'ButtonコントロールのList Private ListButton As List(Of Button) ' フォームロードイベント Private Sub frmCtrlList_Load(sender As Object, e As EventArgs) Handles Me.Load 'コントロールListに各コントロールを設定 ListTextBox = New List(Of TextBox) From {Me.TextBox1, Me.TextBox2, Me.TextBox3, Me.TextBox4, Me.TextBox5} ListButton = New List(Of Button) From {Me.Button1, Me.Button2, Me.Button3, Me.Button4, Me.Button5} 'ボタンコントロールのクリックイベント処理を定義 For Each btn As Button In ListButton AddHandler btn.Click, AddressOf ButtonClick Next End Sub ' ボタンクリックイベント Private Sub ButtonClick(ByVal sender As Object, ByVal e As System.EventArgs) 'ボタンのオブジェクト名から指標文字列を取得(Nameが[ButtonNN]の形式が前提) Dim strTitleIdx As String = DirectCast(sender, Button).Name.Replace("Button", "") '指標が数値?? If IsNumeric(strTitleIdx) = True Then 'テキストボックスをListから取得 Dim TextBox As TextBox = ListTextBox.Item(CInt(strTitleIdx) - 1) 'テキストボックスの入力値を取得 Dim strTextBox As String = TextBox.Text 'ボタンのCaptionを取得 Dim strTitle As String = DirectCast(sender, Button).Text '表示 MsgBox(strTextBox, MsgBoxStyle.OkOnly, strTitle) End If End Sub ' フォームClosedイベント Private Sub frmCtrlList_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed 'コントロールListの廃棄 ListButton.Clear() ListButton = Nothing ListTextBox.Clear() ListTextBox = Nothing End Sub End Class
更に、チェックボックスを List を使って制御する例を示します。
フォームロード時にチェックボックスを List を生成し、ボタンをクリックした時にチェックボックスのチェックを反転処理しています。 チェックボックスの繰り返し処理は For Each文 で行っています。■チェックボックスを List を使って制御
Public Class frmCtrlList2 ' CheckBox の Listの宣言 Private list As List(Of CheckBox) Private Sub frmCtrlList2_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' Listの生成(初期化) list = New List(Of CheckBox) From {Me.CheckBox1, Me.CheckBox2, Me.CheckBox3} End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click ' For Each文でのチェックボックス処理 For Each chk As CheckBox In list chk.Checked = Not chk.Checked 'チェック状態反転 Next End Sub End Class下図はチェックの反転の様子です。
尚 List の ForEach メソッドを使う方法は以下の通りです。■List の ForEach メソッドを使う方法
Public Class frmCtrlList2 ' CheckBox の Listの宣言 Private list As List(Of CheckBox) Private Sub frmCtrlList2_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' Listの生成(初期化) list = New List(Of CheckBox) From {Me.CheckBox1, Me.CheckBox2, Me.CheckBox3} End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click ' ForEachメソッドで匿名メソッドを宣言して処理する list.ForEach(Sub(chk) chk.Checked = Not chk.Checked Console.WriteLine("Name[{0}]...Checked:{1}", chk.Name, chk.Checked) End Sub) ' 1行の処理ならば以下の方法でもOK 'list.ForEach(Sub(chk) chk.Checked = Not chk.Checked) End Sub End Class上記の匿名メソッドの宣言方法では、メソッドの名前を宣言せずに、通常の Sub を宣言する様にします。 (複数行の処理を行う場合を示しています)
コメント行で記述した方法でもOKです。簡単に1行で済ます場合には有効ですが、パッと見た感じでは分かりにくいですが。 (匿名メソッドはJavascriptなどではよく出てきます)関連する記事
⇒コントロールを配列で処理する方法 :[AddHandler,DirectCast]
⇒コレクション「List」の使い方について
⇒コレクション「List」と配列の相互変換について
⇒コレクション「Dictionary」の使い方について
⇒コレクション「Dictionary」から配列及び List への変換について
PR -
コレクションクラスの List と配列は相互に変換ができます。 「コレクション「List」の使い方について」の記事では配列との変換について記述していませんでしたので、ここでまとめます。
■List から配列への変換(ToArray, CopyTo)
変換を行う為に List の ToArray 関数及び CopyTo メソッドを使いますが、 それぞれの定義は以下の様になります。
メソッド
or 関数書式 説明 ToArray ToArray() As T()
戻り値:List(Of T) の要素のコピーを格納する配列List(Of T) の要素を新しい配列にコピーします CopyTo CopyTo(array() As T)
array:List(Of T) から要素がコピーされる 1次元の System.Array
System.Array には、0 から始まるインデックス番号が必要ですList(Of T) 全体を互換性のある1次元の先頭から配列にコピーします CopyTo CopyTo(array() As T, index As Integer)
array:List(Of T) から要素がコピーされる 1次元の System.Array
index:コピーの開始位置で、array 内の 0 から始まるインデックス
例外:ArgumentOutOfRangeException
index が 0 未満
例外:ArgumentException
List(Of T) の要素数が index からコピー先の array の
末尾までに格納できる数を超えていますList(Of T) 全体を互換性のある1次元の指定したインデックスから配列にコピーします
以下のソースを見て下さい。ToArray, CopyTo の例を記します。Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click Dim i As Integer = 0 ' Listの生成(初期化) Dim list As New List(Of Integer) From {1, 10, 100, 1000, 10000} Dim list2 As New List(Of Integer) From {2, 20, 200} ' 配列へ変換 Dim arr() As Integer Console.WriteLine("=== ToArray ===") arr = list.ToArray For i = 0 To UBound(arr) Console.WriteLine("arr({0})の要素:{1}", i, arr(i)) Next ' 配列へのコピー Console.WriteLine("=== CopyTo ===") list2.CopyTo(arr) For i = 0 To UBound(arr) Console.WriteLine("arr({0})の要素:{1}", i, arr(i)) Next ' 最初に少ないListデータを配列へ変換 arr = list2.ToArray Try ' エラーの起きる配列へのコピー list.CopyTo(arr) Catch ex As Exception Console.WriteLine("CopyToエラー:{0}", ex.Message) End Try ' 再度listから変換 arr = list.ToArray ' インデックスを指定して配列へのコピー Console.WriteLine("=== CopyTo(index) ===") list2.CopyTo(arr, 2) For i = 0 To UBound(arr) Console.WriteLine("arr({0})の要素:{1}", i, arr(i)) Next End Sub実行結果がコンソールには以下様に表示されます。
10行目の ToArray 関数により「list」から「arr」に変換され、要素がコピーされていることが分かります。 17行目の CopyTo メソッドにより「list2」から「arr」の先頭から要素が上書きコピーされていることが分かります。 「arr」の要素(3),(4)はそのままになっています。
再度「list2」から「arr」に変換し、26行目で要素数の多い「list」を上書きコピーさせています。 「arr」は要素数が足りないので、エラーが発生します。
再度「list」から「arr」に変換し、35行目で2番目の要素から「list2」を上書きコピーさせています。=== ToArray === arr(0)の要素:1 arr(1)の要素:10 arr(2)の要素:100 arr(3)の要素:1000 arr(4)の要素:10000 === CopyTo === arr(0)の要素:2 arr(1)の要素:20 arr(2)の要素:200 arr(3)の要素:1000 arr(4)の要素:10000 CopyToエラー:ターゲット配列の長さが足りません。destIndex、長さ、および配列の最小値を確認してください。 === CopyTo(index) === arr(0)の要素:1 arr(1)の要素:10 arr(2)の要素:2 arr(3)の要素:20 arr(4)の要素:200
■List の生成(New)で配列指定
List を生成する時に配列を指定すれば、配列要素をコピーして生成されます。
New の引数として IEnumerator(Of T) が必要なのですが、配列(System.Array)はこれを持っているため、指定ができます。 尚、New の引数として List を用いても同様にできます。
AddRange でも同様に引数に配列を指定することができます。Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click Dim i As Integer = 0 ' 配列宣言・初期化 Dim arr() As Integer = {1, 20, 300} ' 配列を指定したListの生成 Console.WriteLine("=== New(配列) ===") Dim list As New List(Of Integer)(arr) i = 0 For Each nVal As Integer In list Console.WriteLine("{0}番目に取得したListの要素:{1}", i, nVal) i += 1 Next ' Listを指定したList2の生成 Console.WriteLine("=== New(List) ===") Dim list2 As New List(Of Integer)(list) i = 0 For Each nVal As Integer In list2 Console.WriteLine("{0}番目に取得したList2の要素:{1}", i, nVal) i += 1 Next ' 配列をListに追加 Console.WriteLine("=== AddRange ===") list.AddRange(arr) i = 0 For Each nVal As Integer In list Console.WriteLine("{0}番目に取得したListの要素:{1}", i, nVal) i += 1 Next End Sub結果は以下の通りです。
=== New(配列) === 0番目に取得したListの要素:1 1番目に取得したListの要素:20 2番目に取得したListの要素:300 === New(List) === 0番目に取得したList2の要素:1 1番目に取得したList2の要素:20 2番目に取得したList2の要素:300 === AddRange === 0番目に取得したListの要素:1 1番目に取得したListの要素:20 2番目に取得したListの要素:300 3番目に取得したListの要素:1 4番目に取得したListの要素:20 5番目に取得したListの要素:300
関連する記事
⇒コレクション「List」の使い方について
⇒配列の使い方について(Dim, Redim)
⇒配列の使い方の注意点について(コピー, Clone)
⇒配列の範囲指定によるコピー(Array.Copy, Skip, Take)
-
コレクションクラスの中に List がありますが、以前の記事の配列と似た処理が可能です。 配列では少し面倒だった領域を拡張などが無くなる分、使い勝手は良い様に思います。
それでは List の使い方について順を追って説明したいと思います。■簡単な List の宣言と使い方
以下のソースを見て下さい。
処理の前半は、以前の配列を利用した場合の合計処理を示しています。 配列の各要素に値を設定する部分は、ベタに記述していますが、 合計を求めるところで指標を使ったループ処理で行っています。
処理の後半で List を生成し Add メソッドで新しく要素を追加します。 合計を求める処理では配列の様に Item プロパティで要素を取得しています。
尚、 List の要素を指標で取得する場合は、指標は「0」番目から始まります。Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click '=== 配列宣言 === Dim arr(5) As Integer '要素に値設定 arr(1) = 1 arr(2) = 10 arr(3) = 100 arr(4) = 1000 arr(5) = 10000 '指標宣言 Dim i As Integer '合計計算 Dim nSum As Integer = 0 For i = 1 To 5 nSum = nSum + arr(i) Next Console.WriteLine("配列を利用した場合の合計処理:{0}", nSum) '=== Listの生成 === Dim list As New List(Of Integer) '要素に値を追加 list.Add(1) list.Add(10) list.Add(100) list.Add(1000) list.Add(10000) nSum = 0 '合計計算 For i = 0 To list.Count - 1 Console.WriteLine("{0}番目のListの要素:{1}", i, list.Item(i)) nSum = nSum + list.Item(i) Next Console.WriteLine("List を利用した場合の合計処理:{0}", nSum) End Sub実行結果がコンソールには以下様に表示されます。
配列を利用した場合の合計処理:11111 0番目のListの要素:1 1番目のListの要素:10 2番目のListの要素:100 3番目のListの要素:1000 4番目のListの要素:10000 List を利用した場合の合計処理:11111
■List の繰り返し処理は For Each で行う
上記のソースでは List の要素を Item プロパティで取得しましたが、 For Each 文を使えば List の要素を順次取得できます。 (こちらの方が通常よく使います。)
そこで上記のソースを以下の様に変更し実行してみます。Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click '=== Listの生成 === Dim list As New List(Of Integer) '要素に値を追加 list.Add(1) list.Add(10) list.Add(100) list.Add(1000) list.Add(10000) Dim nSum As Integer = 0 '合計計算 For Each nVal As Integer In list nSum = nSum + nVal Next Console.WriteLine("List を利用した場合の合計処理:{0}", nSum) End Sub結果は最初の場合と同様になります。
List を利用した場合の合計処理:11111■List の初期化
List の生成とで一緒に初期化ができます。 生成の宣言の後に、From で繋ぎ {}(中括弧) で要素を ,(カンマ) で区切って記述します。
Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click ' Listの生成(初期化) Dim list As New List(Of Integer) From {1, 10, 100, 1000, 10000} '指標宣言 Dim i As Integer = 0 '合計計算 Dim nSum As Integer = 0 '合計計算 For Each nVal As Integer In list Console.WriteLine("{0}番目に取得したListの要素:{1}", i, nVal) i += 1 '指標+1 nSum = nSum + nVal Next Console.WriteLine("List を利用した場合の合計処理:{0}", nSum) End Sub実行結果は以下の通りで、データの順番は記述した順となっています。
0番目に取得したListの要素:1 1番目に取得したListの要素:10 2番目に取得したListの要素:100 3番目に取得したListの要素:1000 4番目に取得したListの要素:10000 List を利用した場合の合計処理:11111
このソース合計計算の For ループで「5」を直値(リテラル)で記述しています。 この場合は「5」であることが明らかなので問題は無いのですが、 配列の最終の指標が何か分からない時には UBound 等を用います。 以下に項目を新しくして説明します。
■List の定義と各メソッド等について
List クラスは以下の様に定義されています。
Public Class List(Of T) 型パラメーター: T:リスト内の要素の型。 概要: インデックスを使用してアクセスできる、厳密に型指定されたオブジェクトのリストを表します。 リストの検索、並べ替え、および操作のためのメソッドを提供します。
「リスト内の要素の型」にはいろんなものが設定可能で、データ型、オブジェクト型、コントロールオブジェクト等が指定できます。 また List クラスには以下の様なメソッドがあり、要素に対する各種の処理が可能です。
メソッド or
プロパティ書式 説明 Add Add(item As T)
item:List(Of T) の末尾に追加するオブジェクトList(Of T) の末尾にオブジェクトを追加します AddRange AddRange(collection As IEnumerable(Of T))
collection:List(Of T) の末尾に要素が追加されるコレクション指定したコレクションの要素を List(Of T) の末尾に追加します Clear Clear() 指定List(Of T) からすべての要素を削除します Count Count As Integer (これはプロパティ) List(Of T) に実際に格納されている要素の数を取得します IndexOf IndexOf(item As T) As Integer (プロパティ)
item:List(Of T) 内で検索するオブジェクトList(Of T) 全体内で item が見つかった場合は、
最初に見つかった位置の 0 から始まるインデックス
それ以外の場合は –1IndexOf IndexOf(item As T, index As Integer) As Integer
(プロパティ)
item:List(Of T) 内で検索するオブジェクト
index:検索の開始位置で 0 から始まるインデックスList(Of T) 全体内で item が見つかった場合は、
最初に見つかった位置の 0 から始まるインデックス
それ以外の場合は –1Insert Insert(index As Integer, item As T)
index:挿入する位置の 0 から始まるインデックス
item:挿入するオブジェクトList(Of T) 内の指定したインデックスの位置に要素を挿入 Item Item(index As Integer) As T (プロパティ)
index:取得,設定する要素の 0 から始まるインデックス
例外:ArgumentOutOfRangeException
index が 0 未満,または List(Of T).Count 以上指定したインデックスにある要素 Remove Remove(item As T) As Boolean
item:List(Of T) から削除するオブジェクト
戻り値:
item が正常に削除された場合は true,それ以外は falseList(Of T) 内で最初に見つかった特定のオブジェクトを削除します RemoveAt RemoveAt(index As Integer)
index:削除する要素の、0 から始まるインデックス
例外:ArgumentOutOfRangeException
index が 0 未満,または List(Of T).Count 以上List(Of T) の指定したインデックスにある要素を削除します RemoveRange RemoveRange(index As Integer, count As Integer)
index:削除する要素の範囲の開始位置
( 0 から始まるインデックス)
count:削除する要素の数
例外:ArgumentOutOfRangeException
index が 0 未満 または count が 0 未満
例外:ArgumentException
index, count が List(Of T) 内の要素の有効範囲外List(Of T) から要素の範囲を削除します
上記のメソッド等を順を追って説明します。- AddRange コレクションの要素を末尾に追加
- Clear と Count について
- IndexOf コレクション要素のインデックスの検索
- Insert 指定したインデックスの位置に要素を挿入
- Remove RemoveAt RemoveRange 要素の削除
■AddRange コレクションの要素を末尾に追加
2個の List を生成し、最初の List に2番目の List を追加します。 この時 (Of T) のデータ型が同じで無い場合はエラーが発生します。
Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click '指標宣言 Dim i As Integer = 0 ' Listの生成(初期化) Dim list As New List(Of Integer) From {1, 10, 100, 1000, 10000} Dim listAdd As New List(Of Integer) From {2, 20, 200} ' AddRange Console.WriteLine("=== AddRange ===") list.AddRange(listAdd) For Each nVal As Integer In list Console.WriteLine("{0}番目に取得したListの要素:{1}", i, nVal) i += 1 '指標+1 Next ' 異なる型のList Dim listAdd2 As New List(Of Long) From {3, 30, 300} Try ' エラーが必ず発生 list.AddRange(listAdd2) Catch ex As Exception Console.WriteLine("AddRangeエラー:{0}", ex.Message) End Try End Sub実行結果は以下の通りです。最初の AddRange で List の末尾に追加されたことが分かります。 また、異なるデータ型の場合にはエラーが発生しています。
=== AddRange === 0番目に取得したListの要素:1 1番目に取得したListの要素:10 2番目に取得したListの要素:100 3番目に取得したListの要素:1000 4番目に取得したListの要素:10000 5番目に取得したListの要素:2 6番目に取得したListの要素:20 7番目に取得したListの要素:200 AddRangeエラー:型 'System.Collections.Generic.List`1[System.Int64]' のオブジェクトを型 'System.Collections.Generic.IEnumerable`1[System.Int32]' にキャストできません。■Clear と Count について
先ず List を生成しその一覧を Item プロパティで取得し、その後 Clear で List の要素を全て削除します。
Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click '指標宣言 Dim i As Integer = 0 ' Listの生成(初期化) Dim list As New List(Of Integer) From {1, 10, 100} ' Listの一覧 Console.WriteLine("=== Listの一覧 ===") For i = 0 To list.Count - 1 Console.WriteLine("{0}番目のListの要素:{1}", i, list.Item(i)) Next Console.WriteLine("=== Clear ===") ' Clear list.Clear() Console.WriteLine("Listの要素個数:{0}", list.Count) i = 0 For Each nVal As Integer In list ' ForEachでの一覧は処理されない Console.WriteLine("{0}番目に取得したListの要素:{1}", i, nVal) i += 1 '指標+1 Next End Sub実行結果は以下の通りです。Count で List の件数分の一覧処理が行われることが分かります。 また Clear 後は件数が「0」になり For Each での処理が行われません。
=== Listの一覧 === 0番目のListの要素:1 1番目のListの要素:10 2番目のListの要素:100 === Clear === Listの要素個数:0
■IndexOf コレクション要素のインデックスの検索
先ず List を生成しその要素の値指定でインデックスを IndexOf 関数で取得します。
その後、検索開始インデックスを指定する IndexOf での例を以下に示します。Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click Dim i As Integer = 0 ' Listの生成(初期化) 0 1 2 3 4 5 6 7 Dim list As New List(Of Integer) From {1, 10, 100, 1000, 10000, 2, 20, 200} ' IndexOf(item As T) Console.WriteLine("=== IndexOf(item As T) ===") i = list.IndexOf(1000) Console.WriteLine("itemが「{0}」のインデックス:{1}", 1000, i) i = list.IndexOf(1500) Console.WriteLine("itemが「{0}」のインデックス:{1}", 1500, i) ' IndexOf(item As T, index As Integer) Console.WriteLine("=== IndexOf(item As T, index As Integer) ===") i = list.IndexOf(20, 4) Console.WriteLine("itemが「{0}」をインデックス「{1}」から検索した結果のインデックス:{2}", 20, 4, i) i = list.IndexOf(1000, 4) Console.WriteLine("itemが「{0}」をインデックス「{1}」から検索した結果のインデックス:{2}", 1000, 4, i) End Sub実行結果は以下の通りです。
要素の値指定のみの IndexOf では先頭から検索する為「1000」の値の場合はインデックス「3」が返り、 「1500」の値の場合は List の要素に存在しないので「-1」が返ります。
検索開始インデックスを指定する IndexOf では開始インデックスを「4」とし、検索要素値「20」の場合は「6」が返り、 検索要素値「1000」の場合はインデックスを「4」以降には値が存在しないので「-1」が返ります。=== IndexOf(item As T) === itemが「1000」のインデックス:3 itemが「1500」のインデックス:-1 === IndexOf(item As T, index As Integer) === itemが「20」をインデックス「4」から検索した結果のインデックス:6 itemが「1000」をインデックス「4」から検索した結果のインデックス:-1
■Insert 指定したインデックスの位置に要素を挿入
先ず List を生成しインデックス「5」の位置に「300」を挿入します。
その後、存在しないインデックス「10」の位置に挿入処理を行いエラーが発生することを確認します。Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click Dim i As Integer = 0 ' Listの生成(初期化) Dim list As New List(Of Integer) From {1, 10, 100, 1000, 10000, 2, 20, 200} ' Insert(index As Integer, item As T) Console.WriteLine("=== Insert(index As Integer, item As T) ===") list.Insert(5, 300) 'インデックス「5」の位置に「300」を挿入 i = 0 For Each nVal As Integer In list Console.WriteLine("{0}番目に取得したListの要素:{1}", i, nVal) i += 1 '指標+1 Next Try ' エラーが必ず発生 list.Insert(10, 900) 'インデックス「10」の位置に「900」を挿入 Catch ex As Exception Console.WriteLine("Insertエラー:{0}", ex.Message) End Try End Sub実行結果は以下の通りです。
インデックス「5」の位置に「300」が挿入され、それ以降は順次繰り下がったことがわかります。
また、インデックス「10」の位置での挿入処理はエラーが返されています。=== Insert(index As Integer, item As T) === 0番目に取得したListの要素:1 1番目に取得したListの要素:10 2番目に取得したListの要素:100 3番目に取得したListの要素:1000 4番目に取得したListの要素:10000 5番目に取得したListの要素:300 6番目に取得したListの要素:2 7番目に取得したListの要素:20 8番目に取得したListの要素:200 Insertエラー:インデックスは一覧の範囲内になければなりません。 パラメーター名:index
■Remove RemoveAt RemoveRange 要素の削除
先ず List を生成し Remove で値が「300」の要素を削除します。2回行うことで異なる結果が返ってくるはずです。
その後 RemoveAt でインデックス「5」の位置を削除処理を行い一覧確認します。 さらに存在しないインデックス「10」の位置を削除処理し、エラー発生を確認します。
その後 RemoveRange でインデックス「3」の位置から「2」個の要素を削除処理を行い一覧確認します。 さらにインデックス「3」の位置から存在しない個数の「5」で削除処理し、エラー発生を確認します。Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click Dim i As Integer = 0 ' Listの生成(初期化) Dim list As New List(Of Integer) From {1, 10, 100, 1000, 10000, 300, 2, 20, 200} Dim blnRet As Boolean ' Remove(item As T) Console.WriteLine("=== Remove(item As T) ===") blnRet = list.Remove(300) Console.WriteLine("itemが「{0}」削除:{1}", 300, blnRet) blnRet = list.Remove(300) Console.WriteLine("itemが「{0}」削除:{1}", 300, blnRet) ' RemoveAt(index As Integer) Console.WriteLine("=== RemoveAt(index As Integer) ===") list.RemoveAt(5) Console.WriteLine("インデックス「{0}」の要素を削除", 5) i = 0 For Each nVal As Integer In list Console.WriteLine("{0}番目に取得したListの要素:{1}", i, nVal) i += 1 '指標+1 Next Try ' エラーが必ず発生 list.RemoveAt(10) 'インデックス「10」の位置の要素削除 Catch ex As Exception Console.WriteLine("RemoveAtエラー:{0}", ex.Message) End Try ' RemoveRange(index As Integer, count As Integer) Console.WriteLine("=== RemoveRange(index As Integer, count As Integer) ===") list.RemoveRange(3, 2) Console.WriteLine("インデックス「{0}」から「{1}」個の要素を削除", 3, 2) i = 0 For Each nVal As Integer In list Console.WriteLine("{0}番目に取得したListの要素:{1}", i, nVal) i += 1 '指標+1 Next Try ' エラーが必ず発生 list.RemoveRange(3, 5) Catch ex As Exception Console.WriteLine("RemoveRangeエラー:{0}", ex.Message) End Try End Sub実行結果は以下の通りです。
=== Remove(item As T) === itemが「300」削除:True itemが「300」削除:False === RemoveAt(index As Integer) === インデックス「5」の要素を削除 0番目に取得したListの要素:1 1番目に取得したListの要素:10 2番目に取得したListの要素:100 3番目に取得したListの要素:1000 4番目に取得したListの要素:10000 5番目に取得したListの要素:20 6番目に取得したListの要素:200 RemoveAtエラー:インデックスが範囲を超えています。負でない値で、コレクションのサイズよりも小さくなければなりません。 パラメーター名:index === RemoveRange(index As Integer, count As Integer) === インデックス「5」から「2」個の要素を削除 0番目に取得したListの要素:1 1番目に取得したListの要素:10 2番目に取得したListの要素:100 3番目に取得したListの要素:20 4番目に取得したListの要素:200 RemoveRangeエラー:配列のオフセットおよび長さが範囲を超えているか、カウンターがソース コレクションのインデックスから最後までの要素の数より大きい値です。
関連する記事
⇒コレクション「List」と配列の相互変換について
⇒コレクション「Dictionary」の使い方について
⇒コレクション「Dictionary」から配列及び List への変換について
⇒配列の使い方について(Dim, Redim)
⇒配列の使い方の注意点について(コピー, Clone)
⇒配列の範囲指定によるコピー(Array.Copy, Skip, Take)
-
一般的に VB.NET において現在実行しているイベント処理が終了するまでは、他のイベント処理は実行されない様になっています。
イベント処理は実行しているプログラムから見て、外部から引き起こされる要因により発生します。 例えば、マウスのクリックイベント、キー入力のキーダウンイベント、タイマコントロールからのチックイベントなどがあります。
それぞれのイベントが発生した順番にイベント処理は順次実行されていきます。 同時にキーやマウスが動作しても、内部的には必ず順番が振られて、わずかな時間差で順次の処理になってしまいます。 多くのイベントが発生した場合はそれぞれが持ち行列的に待たされて順次処理されます。
それでは簡単な例で順次示していきます。- フォームにタイマコントロールを1個設置しタイマイベント時刻表示
- タイマイベント内に時間の掛かる処理をさせる
- タイマイベント内に再入を引き起こす処理をさせてみる
- タイマコントロールを2個設置して1個のタイマイベント内に時間の掛かる処理をさせる
- ボタンコントロールを2個設置して1個のクリックイベント内に時間の掛かる処理をさせる
■フォームにタイマコントロールを1個設置しタイマイベント時刻表示
先ずはフォームにタイマコントロールを1個設置し、タイマイベントでデバッグ出力ウインドウへの時刻表示を行います。 ソースは以下の様になります。
Public Class frmMulEvent Private Sub frmMulEvent_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' 「Timer1」設定・開始 Me.Timer1.Interval = 2000 ' 2秒毎のイベント発生 Me.Timer1.Start() End Sub Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick ' 出力ウインドウに現在時刻表示 Debug.Print("Timer1 : " & Now.ToString("hh:mm:ss")) End Sub End Class実行結果が出力ウインドウには以下様に2秒毎に時刻が表示されます。
Start... Timer1 : 10:28:23 Timer1 : 10:28:25 Timer1 : 10:28:27 Timer1 : 10:28:29
■タイマイベント内に時間の掛かる処理をさせる
タイマイベントの中に時間のかかる処理を追加して、出力ウインドウへの表示を見てみます。
Public Class frmMulEvent Private Sub frmMulEvent_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' 「Timer1」設定・開始 Me.Timer1.Interval = 2000 ' 2秒毎のイベント発生 Me.Timer1.Start() End Sub Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick ' 出力ウインドウに現在時刻表示 Debug.Print("Timer1 : " & Now.ToString("hh:mm:ss")) ' 時間の掛かる処理を[Sleep]で行う System.Threading.Thread.Sleep(1000 * 5) '5秒待つ処理 End Sub End Classタイマコントロールのインターバルは2秒ですが、タイマイベント処理では5秒待つ処理を行っていますので、 出力ウインドウには以下様に5秒毎に時刻が表示されます。
Start... Timer1 : 10:55:43 Timer1 : 10:55:48 Timer1 : 10:55:53 Timer1 : 10:55:58
タイマイベント処理がインターバルよりも超えているためにイベントは発生せずに、5秒毎の時刻表示になります。 この5秒間に発生しているタイマイベントはキャンセルされている様に見えます。
本当のことを言えば、この様な処理は問題があります。実際にはイベント内の処理時間はインターバルの時間内に終わる様にするべきだと思います。
但し逆に言えば、タイマイベント処理自身が処理しているときは再度自分自身が呼ばれる(このことを再入と言います)ことは無いことになります。 つまり二重に処理されることはありませんので、これを逆手に取ってもいい場合があるかもしれません。
■タイマイベント内に再入を引き起こす処理をさせてみる
タイマイベントの処理で System.Threading.Thread.Sleep メソッドで時間待ちをしていた部分をメッセージボックスの表示にしてみます。 ソースは以下の通りです。
Public Class frmMulEvent Private Sub frmMulEvent_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' 「Timer1」設定・開始 Me.Timer1.Interval = 2000 ' 2秒毎のイベント発生 Me.Timer1.Start() End Sub Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick ' 出力ウインドウに現在時刻表示 Debug.Print("Timer1 : " & Now.ToString("hh:mm:ss")) ' メッセージBOXを表示させる MsgBox("Timer1 のイベント処理中") End Sub End Classこれを実行すると以下の様にメッセージボックスが順次表示されてしまいます。 このままにしておくと無限に繰り返されて、やがて限界が来たところでエラーで止まると思います。
この様な現象が起こるのは、メッセージボックスの処理の中でイベントを受け付ける様になっているためです。 メッセージボックス自身にボタンコントロールが在るため、このボタンのクリックイベントを受け付けるためで、その他のイベントも拾ってしまう為です。
■タイマコントロールを2個設置して1個のタイマイベント内に時間の掛かる処理をさせる
Public Class frmMulEvent Private Sub frmMulEvent_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' ラベルに現在時刻表示 Debug.Print("Start...") ' 「Timer1」設定・開始 Me.Timer1.Interval = 2000 ' 2秒毎のインターバル Me.Timer1.Start() ' 「Timer2」設定・開始 Me.Timer2.Interval = 4000 ' 4秒毎のインターバル Me.Timer2.Start() End Sub Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick ' 出力ウインドウに現在時刻表示 Debug.Print("Timer1 : " & Now.ToString("hh:mm:ss")) System.Threading.Thread.Sleep(1000 * 5) End Sub Private Sub Timer2_Tick(sender As Object, e As EventArgs) Handles Timer2.Tick ' 出力ウインドウに現在時刻表示 Debug.Print("...Timer2 : " & Now.ToString("hh:mm:ss")) End Sub End Classこのソースの実行結果は以下の通りです。Timer2 のタイマイベントは時々阻止されているのがわかると思います。
Start... Timer1 : 02:02:15 ...Timer2 : 02:02:20 Timer1 : 02:02:20 ...Timer2 : 02:02:25 Timer1 : 02:02:25 Timer1 : 02:02:30 ...Timer2 : 02:02:35 Timer1 : 02:02:35 Timer1 : 02:02:40 ...Timer2 : 02:02:45
■ボタンコントロールを2個設置して1個のクリックイベント内に時間の掛かる処理をさせる
以下の様にフォーム上にボタンコントロールを2個設置して Button1 のクリックイベント内に時間の掛かる処理をさせる様にします。
Public Class frmMulEventBtn Private Sub frmMulEventBtn_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' 開始メッセージ出力ウインドウに表示 Debug.Print("...Start") End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click ' 出力ウインドウに現在時刻表示 Debug.Print("Button1 : " & Now.ToString("hh:mm:ss")) System.Threading.Thread.Sleep(1000 * 5) End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click ' 出力ウインドウに現在時刻表示 Debug.Print("Button2 : " & Now.ToString("hh:mm:ss")) End Sub End Class...Start Button1 : 02:40:41 ⇒この時点で「Button2」を3回クリック Button2 : 02:40:46 Button2 : 02:40:46 Button2 : 02:40:46
Button1 をクリックした直後に Button2 を連続してクリックします。 Button2 をクリックしたすぐには時刻表示が無く Button1 のクリックイベント処理が終わった直後、 3個連続して時刻表示が行われます。
Button1 のイベント処理中には処理が他には移らないのですが、ボタンのクリックは覚えている様で連続して3回の時刻表示がされます。
これは結構問題で Button1 の処理が終わったと勘違いして Button2 の処理が行われない様にする必要があります。 解決方法としては、ボタンイベント処理中には全てのボタンをクリック不可の状態にするか、 もしくはフラグを導入してボタンクリック処理が連続して行わない様にするかではないでしょうか。
尚、キー入力などでも同様なことが起きるはずですので、この様な現象が起きない様に考える必要があります。関連する記事
⇒フォーム上のコントロールのイベント処理の一括関連付け:[AddHandler,DirectCast]
⇒フォーム上のコントロールのイベント処理の一括関連付けその2:[AddHandler,DirectCast]
⇒コントロールの同じイベント処理に複数の関連付けをテスト:[AddHandler,DirectCast]
⇒Timerコントロールの使い方 :[Timer,Interval]
⇒複数のTimerコントロールの使い方 :[Timer,Interval]
⇒コントロールを配列で処理する方法 :[AddHandler,DirectCast]
-
フォーム上にピクチャボックスのみ配置しても、フォームが画像よりもサイズが小さい場合、スクロールはできません。 そこで、フォーム上にパネルを貼付て、そのパネル上にピクチャボックスを全面表示で貼り付けます。
画像イメージは System.Drawing.Image.FromFile メソッドで設定します。 尚、画像ファイルは以下のものを使います。
ソースは以下の様になります。
フォームロード時にパネルを生成しスクロール許可を与えます。 更に、ピクチャボックスを生成し、各種の設定を行ってからパネルのコントロールに追加し、 そのパネルをフォームのコントロールに追加します。パネルとピクチャボックスによる画像表示
Public Class frmPanelPic ' パネルの静的変数宣言 Dim Panel1 As Panel ' ピクチャボックスの静的変数宣言 Dim Pic1 As PictureBox 'フォームロード時イベント処理 Private Sub frmPanelPic_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' パネルの生成 Panel1 = New Panel ' [Dock]:フォーム全体にFill Panel1.Dock = DockStyle.Fill ' スクロール許可 Panel1.AutoScroll = True ' ピクチャボックスの生成 Pic1 = New PictureBox ' [SizeMode]:ピクチャボックスの大きさに合わせる指定 Pic1.SizeMode = PictureBoxSizeMode.AutoSize ' 画像イメージ Pic1.Image = System.Drawing.Image.FromFile("Desert.jpg") ' ピクチャボックスをパネル上に追加表示 Panel1.Controls.Add(Pic1) ' パネルをフォーム上に追加 Me.Controls.Add(Panel1) End Sub End Classこれを実行すると以下の様な表示になります。 左側は初期表示で、右側はフォームのサイズを大きくした場合の状態です。
関連する記事
⇒標準コントロールのパネルの使い方について(Panel)
⇒標準コントロールのパネルの使い方について2(複数Panelの表示切替)