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

  • 質問

  •  

    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 String

                    For 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 Integer

     

        Public Sub New()
            col = 0
          End Sub

     

        Public 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

    2007年7月11日 7:32

すべての返信

  •  niway さんからの引用

            Me.ListView1.ListViewItemSorter = New ListViewItemComparer(e.Column)
     

    これが恒常的な設定だからでしょう。>アイテムの変化により並べ替えようとする

    直後でNothingを設定して解除してみてください。

    2007年7月11日 8:43
  • まどか様

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

    以下のステップを直後に入れたところ、正常に動作いたしました。

    Me.ListView1.ListViewItemSorter = Nothing

     

    .Netを勉強し始めたばかりですので、これからもフォーラムへ投稿することは、多々あると思いますが、

    その際は、ご支援の程、よろしくお願い致します。

     

    ついでに質問したいのですが、今回ListViewを試験的に構築し、デバッグ時、『コネクションの最大許容範囲を超えました。』

    というメッセージが表示される事があったのですが、どういったエラーなのでしょうか。ネット等で探したのですが、見当たりませんでした。

    ListViewコントロールのプロパティ:Sorting にAccending を設定し、ListViewを数回再作成すると、事象は発生しました。

    (ちなみに、読み込んだCSVファイルのレコード数は、5300件程です。)

    上記メッセージは、環境等に関係しているのでしょうか。

    ご存知でしたら、ご返事下さいます様、お願い致します。

     

     

    2007年7月11日 9:29
  •  niway さんからの引用

    デバッグ時、『コネクションの最大許容範囲を超えました。』というメッセージが表示される事があったのですが、どういったエラーなのでしょうか。

    ListViewコントロールのプロパティ:Sorting にAccending を設定し、ListViewを数回再作成すると、事象は発生しました。

    ぱっと見、接続数がオーバーしたと受け取れますが、ListViewと関連性は無いと思われます。

    そのプログラムでネットワークやデータベースを触っていませんか?

    そのメッセージが出た箇所はどんなコードでしょうか?

    2007年7月12日 0:39
  •  まどか さんからの引用
     niway さんからの引用

    デバッグ時、『コネクションの最大許容範囲を超えました。』というメッセージが表示される事があったのですが、どういったエラーなのでしょうか。

    ListViewコントロールのプロパティ:Sorting にAccending を設定し、ListViewを数回再作成すると、事象は発生しました。

    ぱっと見、接続数がオーバーしたと受け取れますが、ListViewと関連性は無いと思われます。

    そのプログラムでネットワークやデータベースを触っていませんか?

    そのメッセージが出た箇所はどんなコードでしょうか?

    申し訳ございません。『コレクションは、最大許容量に達しました。』というメッセージでした。

    誤字にてお手数をお掛けしました。

     

    ListViewコントロールのプロパティ:Sorting にAccending を設定し、実行すると上記の警告ダイアログが

    表示されます。画面上は、1番目の列のみ、表示された状態となります。

    デバッグをステップインにて行うには、データ量が、かなりございますので行えません。

     

    >>そのメッセージが出た箇所はどんなコードでしょうか?

    特定できないです。

     

    出力ウィンドウには、以下のメッセージが出力されております。

    'System.InvalidOperationException' の初回例外が System.Windows.Forms.dll で発生しました。

     

    プロパティ:SortingをNoneにて実行すると上記事象は、発生いたしません。

     

     

    2007年7月12日 1:07
  • 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つの意味が明確になるような、名前、初期値、加減算タイミングを意識してください。

    2007年7月12日 1:50
  •  niway さんからの引用

    プロパティ:SortingをNoneにて実行すると上記事象は、発生いたしません。

    たぶん最初の問題と同じで、アイテムが変化するたびに自動で並べ替えが発生するためと思われます。

    大量データの読み込みループ内で、1件のソート、2件のソート、・・・全件のソート、となりますから

    かなりの負荷がかかっているはずです。

    Sortingプロパティおよびカスタムソーターは必要なときのみ設定し、終わったら解除しておきましょう。

    2007年7月12日 2:05
  •  まどか さんからの引用

    Sortingプロパティおよびカスタムソーターは必要なときのみ設定し、終わったら解除しておきましょう。

    訂正。

    必ずそうしろと受け取れますが、そういう意味ではありません。

     

    並べ替えは専用の大量リソースを必要とします。

    リアルタイムである必要がるのか、5000件ものデータを表示する必要があるのか、という仕様について

    および、データをコレクションに入れて並べ替えた結果をリストビューへ表示という工夫

    などについて検討してみてください。

     

    2007年7月12日 2:19
  •  まどか さんからの引用

    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 = False     

          Dim 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 String

                  For Each strfield As String In row
                        ReDim Preserve str1recs(i)
                        str1recs(i) = strfield
                         i += 1
                    Next

                  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()

                    '''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

     

    2007年7月12日 2:58
  •  niway さんからの引用

    2.完全依存するとは、具体的にどの様なことなのでしょうか。

    依存とは前提があるということです。

    Sharedということはその記述してある型の持ち物でありその型の中で(概念、仕様が)閉じられています。

    自分で必要なインスタンスを作成破棄する

    Public Sub Shared Sub Hoge()

        Dim f As New Form

        f.Close()

        f.Dispose

    End Sub

    なら閉じられていますが

    My.Forms.~を使うことはその型の外の情報を前提としているわけです。

    まして暗黙のフォームインスタンスという機能により、

    インスタンスが存在しない場合はコンストラクタだけが実行されたインスタンスを扱うことになります。

    #Loadは表示して初めて実行される

    これにより呼び出す条件やタイミングが限定されることにより、その意味ではSharedに反します。

     

    せめてListViewを引数にして呼び出し側が「これに編集してくれ」とすべきでしょう。

    2007年7月12日 4:28
  •  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しないと

    ループごとに描画されてしまいます。

    2007年7月12日 4:32
  •  まどか さんからの引用
     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を配置するように致します。

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

     

     

     

    2007年7月12日 6:14
  • 最初にお断りしますが、

    間違いではありませんし、提供されている機能を使っているだけだと言われたら反論できません。

    あくまでオブジェクト指向から見た考え方の問題です。

    #間違いじゃないところがつらいところ。。。

     

     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

     

    型にのみ依存してるのがお分かりかと思います。

    書かれた例と比べてみてください。

     

    2007年7月12日 9:35
  •  

    まどか様

     ご丁寧なアドバイスありがとうございます。

    下名が作成したクラスもMy.Forms~のステップを全て取り除き、

    上記のようなコーディングと致しました。

     

    本題(トピックス)とは、内容が反れてしまいましたが、大変勉強になりました。

     

    裏で「Dim frmMain As FormMain = New FormMain」というようなことを自動でおこなってくれて、例外にはなりません。

    という事は、InitializeComponent()がMy.Form.を使用した箇所で毎回実施されているということでしょうか。

    イニシャライズで設定される以外はの情報は、そのまま保持されるのでしょうか。 

     

    さて、例のMy.Forms.FormMainには何が入っているのでしょう?

    誰が作ったどんな値を持つインスタンスなのでしょうか

    上記の問い掛けに下名は答える力がありません。

     

    これからもご指導、ご支援よろしくお願いします。

     

     

    2007年7月13日 0:59
  •  niway さんからの引用

     裏で「Dim frmMain As FormMain = New FormMain」というようなことを自動でおこなってくれて、例外にはなりません。

    という事は、InitializeComponent()がMy.Form.を使用した箇所で毎回実施されているということでしょうか。

    イニシャライズで設定される以外はの情報は、そのまま保持されるのでしょうか。 

    毎回ではなく初めてプロパティなどのメンバにアクセスした際におこなわれます。

    以降は上記はおこなわれず作成されたインスタンスが利用されます。

     

     niway さんからの引用

    さて、例のMy.Forms.FormMainには何が入っているのでしょう?

    誰が作ったどんな値を持つインスタンスなのでしょうか

    上記の問い掛けに下名は答える力がありません。

    クイズではなく、それが定まらないから問題だという意味です。
    2007年7月13日 2:53
  •  

    まどか様

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

     

    これからもよろしくお願い致します。

     

    2007年7月13日 5:53