質問者
ListViewにおいてソート機能を組み込むと、ListView再作成時Compareメソッドが呼ばれてしまう。

質問
-
ListView コントロールでソート(カラムクリック)機能を組み込んだのですが、悩んでいます。
画面初期表示時は、正常にListViewコントロール内に明細データを表示し、
項目見出しをクリックすると、ソーティングが正常に行われます。
但し、再度ListVIewコントロールのアイテムをクリアし、再生成時にエラーとなってしまいます。
ソート機能は、当フォーラムやMSDN(http://msdn2.microsoft.com/ja-jp/library/system.windows.forms.listview.listviewitemsorter(VS.80).aspx)を流用し、
ソート機能を組み込みました。
【画面概要】
画面上にListViewを表示するボタンを作成。
このボタン押下により、ListViewに項目を追加し、表示するようにしています。
アプリケーションを起動し、初期表示には、特に問題なく表示し、ソートも機能する。
再度ボタン押下すると、ListView.Items.Add時にエラーやエラーが起きない場合でも、
正常にListViewにデータが表示されません(SubItems項目が歯抜け状態で表示。)。
エラーの場合は、ListView.Items.Add時に上記雛形Class ListViewItemComparer の
Compareメソッドに制御が移り、『'2'のInvalidArgument=Valueは'index'に対して有効ではありません。
パラメータ名 index』とエラーメッセージが表示されます。
画面上のListViewには、4つのColumがあり、上記エラーの場合は、2番目の項目をクリック(ソート表示)後に、
再度ボタンを押下し、ListViewを再表示した時に起こります。
ソート機能を実行せず、再度ボタン押下時は、正常に再表示されます。
1.どうして、Compareメソッドに制御が移るのでしょうか。
2.ソート機能が実行されないと正常に再編集されるのは、なぜでしょうか。
3.カラムクリック時以外は、通らないものかと思っていたのですが、それとも、
下名のListView編集ロジックがおかしいのでしょうか。
回避策やアドバイスを頂ける方は、申し訳ございませんが、ご教示願います。
下名 ListView編集 ソースコードは、以下の通りです。
'''画面上に作成してあるボタンを押下すると、以下のプロシージャが呼び出されます。
'''見出しについては、デザイン画面で"列の編集"で設定しています。
Shared Sub CsvParser(ByVal strCsvFile As String)
Using parser As New TextFieldParser(strCsvFile, _
System.Text.Encoding.GetEncoding("Shift_JIS"))parser.TextFieldType = FieldType.Delimited
parser.SetDelimiters(",") ' 区切り文字はコンマDim RecCnt As Integer = 0
Dim ReadCnt As Integer = 1
My.Forms.FormMain.ListView1.Items.Clear()While Not parser.EndOfData
Dim row As String() = parser.ReadFields() ' 1行読み込み
If ReadCnt <> 1 Then ' 1行目は、TitleFieldの為、読み飛ばし。
Dim i As Integer = 0
Dim str1recs(0) As StringFor Each strfield As String In row
ReDim Preserve str1recs(i)
str1recs(i) = strfield
i += 1
Next
'''My.Forms.FormMain.ListView1.BeginUpdate() '入れなくてもOK。
My.Forms.FormMain.ListView1.Items.Add(CStr(str1recs(0)), RecCnt)
''' ※エラーは、上記ステップからCompareメソッドに制御が移りエラーとなってしまいます。My.Forms.FormMain.ListView1.Items(RecCnt).SubItems.Add(CStr(str1recs(1)))
My.Forms.FormMain.ListView1.Items(RecCnt).SubItems.Add(CStr(str1recs(2)))
My.Forms.FormMain.ListView1.Items(RecCnt).SubItems.Add(CStr(str1recs(3)))
RecCnt += 1
End If
ReadCnt += 1
End While
End Using
End Sub
'''--------------念のため、ソートステップも添付します。
'''FormMain クラス内
Private Sub ListView1_ColumnClick(ByVal sender As Object, ByVal e As System.Windows.Forms.ColumnClickEventArgs) _
Handles ListView1.ColumnClick
Me.ListView1.ListViewItemSorter = New ListViewItemComparer(e.Column)
End Sub
'''--------------ソートクラス 雛形ソースコードを貼り付けました。
Public Class ListViewItemComparer
Implements IComparer
Private col As IntegerPublic Sub New()
col = 0
End SubPublic Sub New(ByVal column As Integer)
col = column
End Sub'''以下のメソッドに制御が移ってしまいます。
Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer _
Implements IComparer.Compare
Return [String].Compare(CType(x, ListViewItem).SubItems(col).Text, CType(y, ListViewItem).SubItems(col).Text)
End Function
End Class
すべての返信
-
まどか様
ありがとうございました。
以下のステップを直後に入れたところ、正常に動作いたしました。
Me.ListView1.ListViewItemSorter = Nothing
.Netを勉強し始めたばかりですので、これからもフォーラムへ投稿することは、多々あると思いますが、
その際は、ご支援の程、よろしくお願い致します。
ついでに質問したいのですが、今回ListViewを試験的に構築し、デバッグ時、『コネクションの最大許容範囲を超えました。』
というメッセージが表示される事があったのですが、どういったエラーなのでしょうか。ネット等で探したのですが、見当たりませんでした。
ListViewコントロールのプロパティ:Sorting にAccending を設定し、ListViewを数回再作成すると、事象は発生しました。
(ちなみに、読み込んだCSVファイルのレコード数は、5300件程です。)
上記メッセージは、環境等に関係しているのでしょうか。
ご存知でしたら、ご返事下さいます様、お願い致します。
-
まどか さんからの引用 niway さんからの引用 デバッグ時、『コネクションの最大許容範囲を超えました。』というメッセージが表示される事があったのですが、どういったエラーなのでしょうか。
ListViewコントロールのプロパティ:Sorting にAccending を設定し、ListViewを数回再作成すると、事象は発生しました。
ぱっと見、接続数がオーバーしたと受け取れますが、ListViewと関連性は無いと思われます。
そのプログラムでネットワークやデータベースを触っていませんか?
そのメッセージが出た箇所はどんなコードでしょうか?
申し訳ございません。『コレクションは、最大許容量に達しました。』というメッセージでした。
誤字にてお手数をお掛けしました。
ListViewコントロールのプロパティ:Sorting にAccending を設定し、実行すると上記の警告ダイアログが
表示されます。画面上は、1番目の列のみ、表示された状態となります。
デバッグをステップインにて行うには、データ量が、かなりございますので行えません。
>>そのメッセージが出た箇所はどんなコードでしょうか?
特定できないです。
出力ウィンドウには、以下のメッセージが出力されております。
'System.InvalidOperationException' の初回例外が System.Windows.Forms.dll で発生しました。
プロパティ:SortingをNoneにて実行すると上記事象は、発生いたしません。
-
Shared Sub CsvParserについていくつか思うところを。
1.アクセス修飾子(Public, Friend, Private・・・)は省略しないほうがよいでしょう。
Public Shared Sub CsvParser
2."FormMain", "FormMain.ListView1"に完全依存していますので、FormMainクラス内のPrivateメソッドにするか
FormMainにかかわる部分を分離して同じくFormMainクラス内に記述したほうがよいでしょう。
3.BeginUpdate() ’入れなくてもOK
描画(見た目の内容が変わる)が関わるときはEndUpdateとともに囲むことを癖にしておいたほうがよいでしょう。
※強制や”べき”ではなく、やっておかない手は無いという意味です。
4.Items.AddとItems(Index)
Addは末尾に追加するメソッドです。コードではその直後で"RecCnt番目"を意識しています。
間違いではありませんが、Items(Index)は「すでにある集合から」ひとつを特定するという使い方をしたほうがよいでしょう。
今回の場合は新しい一行に対して値を設定する、つまり「何番目」は意識する必要が無いので次のようにできます。
Dim newItem As New ListViewItem(str1Recs(0))
newItem.SubItems.Add(str1Recs(1))
My.Forms.FormMain.ListView1.Items.Add(newItem)
5.ReadCnt
Do WhileやDo~Untilのように考え方が2つあります。
「これからN回目の処理だぞ」と「処理がN回終わったぞ」です。
今回の場合はループの主体がファイルIOであることと「今読み込んだのが何件目か」ということから
・初期値=0
・Read+カウントアップ
・N件目の処理
が自然かと思われます。
recCount = 0
Do While Not Eof
Read
recCount +=1
If recCount > 1 Then
※局所的でもわかりやすいですよね?
前述の2つの意味が明確になるような、名前、初期値、加減算タイミングを意識してください。
-
まどか さんからの引用 Shared Sub CsvParserについていくつか思うところを。
1.アクセス修飾子(Public, Friend, Private・・・)は省略しないほうがよいでしょう。
Public Shared Sub CsvParser
2."FormMain", "FormMain.ListView1"に完全依存していますので、FormMainクラス内のPrivateメソッドにするか
FormMainにかかわる部分を分離して同じくFormMainクラス内に記述したほうがよいでしょう。
3.BeginUpdate() ’入れなくてもOK
描画(見た目の内容が変わる)が関わるときはEndUpdateとともに囲むことを癖にしておいたほうがよいでしょう。
※強制や”べき”ではなく、やっておかない手は無いという意味です。
4.Items.AddとItems(Index)
Addは末尾に追加するメソッドです。コードではその直後で"RecCnt番目"を意識しています。
間違いではありませんが、Items(Index)は「すでにある集合から」ひとつを特定するという使い方をしたほうがよいでしょう。
今回の場合は新しい一行に対して値を設定する、つまり「何番目」は意識する必要が無いので次のようにできます。
Dim newItem As New ListViewItem(str1Recs(0))
newItem.SubItems.Add(str1Recs(1))
My.Forms.FormMain.ListView1.Items.Add(newItem)
5.ReadCnt
Do WhileやDo~Untilのように考え方が2つあります。
・初期値=0
・Read+カウントアップ
・N件目の処理
が自然かと思われます。
※局所的でもわかりやすいですよね?
前述の2つの意味が明確になるような、名前、初期値、加減算タイミングを意識してください。
1.アクセス修飾子(Public, Friend, Private・・・)は省略しないほうがよいでしょう。
ご教示の通り修正致しました。
2.完全依存するとは、具体的にどの様なことなのでしょうか。
3.了解致しました。今後は、そうします。
4.上記の記述方法が、勉強不足でわかりませんでした。
上記の様なコーディングをいろいろ、試みたのですが、
うまくゆかず、そのため、indexを使用したステップになってしまいました。
5.アドバイスありがとうございます。
アドバイスを頂いた箇所を修正したところ、『コレクションの最大許容量に達しました。』は、
出なくなり、正常処理となりました。ただ、画面表示までに時間がかかるため、Sortingプロパティの
設定は、データ量が多い場合は、行わない方がいいですね。
一応、修正後のスクリプトも添付いたします。 (本当にありがとうございました。)
Sub CsvParser(ByVal strCsvFile As String)
Using parser As New TextFieldParser(strCsvFile, _
System.Text.Encoding.GetEncoding("Shift_JIS"))parser.TextFieldType = FieldType.Delimited
parser.SetDelimiters(",") ' 区切り文字はコンマ' parser.HasFieldsEnclosedInQuotes = False
' parser.TrimWhiteSpace = FalseDim RecCnt As Integer = 0
Dim ReadCnt As Integer = 0
ListView1.Items.Clear()
While Not parser.EndOfData
Dim row As String() = parser.ReadFields() ' 1行読み込み
ReadCnt += 1
If ReadCnt > 1 Then ' 1行目は、TitleFieldの為、読み飛ばし。
Dim i As Integer = 0
Dim str1recs(0) As StringFor Each strfield As String In row
ReDim Preserve str1recs(i)
str1recs(i) = strfield
i += 1
NextListView1.BeginUpdate()
Dim newItem As New ListViewItem(str1recs(0))
newItem.SubItems.Add(str1recs(1))
newItem.SubItems.Add(str1recs(2))
newItem.SubItems.Add(str1recs(3))
ListView1.Items.Add(newItem)
ListView1.EndUpdate()'''Old Step
'ListView1.Items.Add(CStr(str1recs(0)), RecCnt)
'ListView1.Items(RecCnt).SubItems.Add(CStr(str1recs(1)))
'ListView1.Items(RecCnt).SubItems.Add(CStr(str1recs(2)))
'ListView1.Items(RecCnt).SubItems.Add(CStr(str1recs(3)))
End If
End While
End Using
End Sub -
niway さんからの引用 2.完全依存するとは、具体的にどの様なことなのでしょうか。
依存とは前提があるということです。
Sharedということはその記述してある型の持ち物でありその型の中で(概念、仕様が)閉じられています。
自分で必要なインスタンスを作成破棄する
Public Sub Shared Sub Hoge()
Dim f As New Form
f.Close()
f.Dispose
End Sub
なら閉じられていますが
My.Forms.~を使うことはその型の外の情報を前提としているわけです。
まして暗黙のフォームインスタンスという機能により、
インスタンスが存在しない場合はコンストラクタだけが実行されたインスタンスを扱うことになります。
#Loadは表示して初めて実行される
これにより呼び出す条件やタイミングが限定されることにより、その意味ではSharedに反します。
せめてListViewを引数にして呼び出し側が「これに編集してくれ」とすべきでしょう。
-
niway さんからの引用 ListView1.BeginUpdate()
Dim newItem As New ListViewItem(str1recs(0))
newItem.SubItems.Add(str1recs(1))
newItem.SubItems.Add(str1recs(2))
newItem.SubItems.Add(str1recs(3))
ListView1.Items.Add(newItem)
ListView1.EndUpdate()今回の場合ループ処理ですので、ループの外側でBeginUpdate、EndUpdateしないと
ループごとに描画されてしまいます。
-
まどか さんからの引用 niway さんからの引用 2.完全依存するとは、具体的にどの様なことなのでしょうか。
依存とは前提があるということです。
Sharedということはその記述してある型の持ち物でありその型の中で(概念、仕様が)閉じられています。
自分で必要なインスタンスを作成破棄する
Public Sub Shared Sub Hoge()
Dim f As New Form
f.Close()
f.Dispose
End Sub
なら閉じられていますが
My.Forms.~を使うことはその型の外の情報を前提としているわけです。
まして暗黙のフォームインスタンスという機能により、
インスタンスが存在しない場合はコンストラクタだけが実行されたインスタンスを扱うことになります。
#Loadは表示して初めて実行される
これにより呼び出す条件やタイミングが限定されることにより、その意味ではSharedに反します。
せめてListViewを引数にして呼び出し側が「これに編集してくれ」とすべきでしょう。
ご返答ありがとうございます。
本当に何度も質問してしまい、申し訳ございません。
ちょっと自信がなくなりましたので、確認させて下さい。
他の機能において、別のクラスを定義し、その中のメンバーにてMy.Forms~と
FormMainの画面コントロールを参照や、プロパティの設定等を行っていますが、
(呼び元は、FarmMainから。)
そうしたつくりも、良くないのでしょうか。
例)
・FormMainにテキストボックスを定義。textbox1
Class FormMain.textbox1
dim InsA as new aaa
blnAns = InsA.bbb
End Class
別クラスを定義
Public Class aaa
Public Function bbb(Byval obj as Object) as Boolean
My.Forms.FormMain.textbox1.text を参照し、
処理を実施。
あるいは、処理の中で、FormMainのコントロールに対して
設定等を実施している。
End Function
End Class
本件、ご返答の次にもアドバイスを頂まいしてありがとうございました。
ループの外にBeginUpdate,EndUpdateを配置するように致します。
よろしくお願い致します。
-
最初にお断りしますが、
間違いではありませんし、提供されている機能を使っているだけだと言われたら反論できません。
あくまでオブジェクト指向から見た考え方の問題です。
#間違いじゃないところがつらいところ。。。
niway さんからの引用 他の機能において、別のクラスを定義し、その中のメンバーにてMy.Forms~と
FormMainの画面コントロールを参照や、プロパティの設定等を行っていますが、
(呼び元は、FarmMainから。)
そうしたつくりも、良くないのでしょうか。
例)
Public Class aaa
Public Function bbb(Byval obj as Object) as Boolean
My.Forms.FormMain.textbox1.text を参照し、
End Function
End Class
通常は「Dim frmMain As FormMain = New FormMain」としてクラスインスタンスを作成しなければ利用できません。
#「Dim frmMain2 As FormMain = New FormMain」とすると別物の実体が2つできることもここでは重要です。
VB.NET2005には「暗黙のフォームインスタンス」という特殊な機能があり
「FormMain.ListView1」といきなりフォームクラス名が出てきたら
裏で「Dim frmMain As FormMain = New FormMain」というようなことを自動でおこなってくれて、例外にはなりません。
しかしオブジェクトは利用者(管理者)が作成して、利用者が責任を持って破棄するべきです。
さて、例のMy.Forms.FormMainには何が入っているのでしょう?
誰が作ったどんな値を持つインスタンスなのでしょうか?
しかもbbbはaaaのインスタンスを作成しなければ呼び出せません。
クラスは閉じられた世界で外のことを知りません。
#どんな型があるかは知っていますが、どんなインスタンスがいるかは知りません。
#クラスそのものも定義でありインスタンスではありませんし。
つまり、aaa.bbb()は知るはずも無いインスタンスを扱っているのです。
前述のとおり例外にはなりませんが、少なくともaaa.bbb()が意図しているインスタンスである保障はありません。
niway さんからの引用 (呼び元は、FormMainから。)
考え方の観点では循環参照です。>と私なら言い切ります。
Public Class Hoge
Public Sub SetDefaultValue(ByVal targetTextBox As TextBox)
targetTextBox.BackColor = color.White
End Sub
End Class
Public Class FormMain
Private Sub InitTextBox()
Dim objHoge As New Hoge
objHoge.SetDefaultValue(Me.TextBox1)
End Sub
End Class
型にのみ依存してるのがお分かりかと思います。
書かれた例と比べてみてください。
-
まどか様
ご丁寧なアドバイスありがとうございます。
下名が作成したクラスもMy.Forms~のステップを全て取り除き、
上記のようなコーディングと致しました。
本題(トピックス)とは、内容が反れてしまいましたが、大変勉強になりました。
裏で「Dim frmMain As FormMain = New FormMain」というようなことを自動でおこなってくれて、例外にはなりません。
という事は、InitializeComponent()がMy.Form.を使用した箇所で毎回実施されているということでしょうか。
イニシャライズで設定される以外はの情報は、そのまま保持されるのでしょうか。
さて、例のMy.Forms.FormMainには何が入っているのでしょう?
誰が作ったどんな値を持つインスタンスなのでしょうか?
上記の問い掛けに下名は答える力がありません。
これからもご指導、ご支援よろしくお願いします。
-
niway さんからの引用 裏で「Dim frmMain As FormMain = New FormMain」というようなことを自動でおこなってくれて、例外にはなりません。
という事は、InitializeComponent()がMy.Form.を使用した箇所で毎回実施されているということでしょうか。
イニシャライズで設定される以外はの情報は、そのまま保持されるのでしょうか。
毎回ではなく初めてプロパティなどのメンバにアクセスした際におこなわれます。
以降は上記はおこなわれず作成されたインスタンスが利用されます。
niway さんからの引用 さて、例のMy.Forms.FormMainには何が入っているのでしょう?
誰が作ったどんな値を持つインスタンスなのでしょうか?
上記の問い掛けに下名は答える力がありません。