locked
Update Listview item content when visible

    Question

  • Hello Everyone,

    I have a bit problem using Listview in WPF & vb.net '08. I'm trying to create a media library kinda thing and wanna add multiple items (mp3's) at one time reading their artist, album, title etc data on the fly. Adding these items makes my app slow down as ____ or even stop responding. Actually the resolution could be: ID3 Tags will be read only when the item is visible in the listview. So my question is how can i determine if the item in the listview is visible or not and how to update it's header text.

    Thanks a lot
    Attila Fogel
    Sunday, May 31, 2009 1:49 PM

Answers

  • Attila,

    What you tell is exactly why in past is choosen to create a treeview beside the listview.

    And in this way windows explorer (not IE) was created.

    Why don't you do the same?

    There is a WPF treeview as well.

    Cor
    • Proposed as answer by Cor LigthertMVP Sunday, May 31, 2009 3:03 PM
    • Marked as answer by Xingwei Hu Thursday, June 04, 2009 8:21 AM
    Sunday, May 31, 2009 3:03 PM

All replies

  • Attila,

    What you tell is exactly why in past is choosen to create a treeview beside the listview.

    And in this way windows explorer (not IE) was created.

    Why don't you do the same?

    There is a WPF treeview as well.

    Cor
    • Proposed as answer by Cor LigthertMVP Sunday, May 31, 2009 3:03 PM
    • Marked as answer by Xingwei Hu Thursday, June 04, 2009 8:21 AM
    Sunday, May 31, 2009 3:03 PM
  • Hi Cor,

    I want all the tags to be read for each and every item that is visible on the screen. If you could provide me a short sample on how to implement the list in the treeview control, that would be very helpful.

    Thanks in advance;
    Attila
    Attila Fogel
    Tuesday, June 16, 2009 7:22 PM
  • I tried to inherit listview: it is not possible to watch standart lisview scrolling - no way. But I have one idea: you can use BackgroundWorker to process all files, so that it would filled some front with processed files, and on WorkerProgress event, which could occure after every X files, you could refresh items by data from front, and also you could check, which item is on first position, and if changed, you could restart BackgroundWorker starting with actually visible set of items.

    Process could then look like this:


    Private lvFiles As Windows.Forms.ListView 'model listview
    
    Private files As Generic.List(Of MediaFile)
    Private folder As String, allDone As Boolean
    Private WithEvents worker As TagProcessor
    Const CountOfFilesVisible% = 32
    
    Sub LoadFiles() 'is called when list is filled with new files
      Me.files = New Generic.List(Of MediaFile)
      For Each file In IO.Directory.GetFiles(Me.folder, "*.mp3")
        Me.files.Add(New MediaFile(file))
        Me.lvFiles.Items.Add(Me.files.Last.toListViewItem)
      Next
      Me.allDone = False
      Me.worker = New TagProcessor(GetNextSet)
      Me.worker.Process() 'starts worker with 32 files to work in background
    End Sub
    
    Function GetNextSet() As MediaFile() 'returns next set of unprocessed files, according to shown items
      Dim firstItem As ListViewItem = Me.lvFiles.GetItemAt(8, 8) 'or such location
      Dim ret As New Generic.List(Of MediaFile), fileInTag As MediaFile
      'search from top item shown to last item in collection for files still not read 
      For i As Integer = Me.lvFiles.Items.IndexOf(firstItem) To Me.lvFiles.Items.Count - 1
        fileInTag = DirectCast(Me.lvFiles.Items(i).Tag, MediaFile)
        If Not fileInTag.MetaData.Ready Then
          ret.Add(fileInTag)
          If ret.Count > CountOfFilesVisible Then 'we collected full set of files to read
            Return ret.ToArray
          End If
        End If
      Next
      'maybe there is something unread from first item
      If ret.Count < CountOfFilesVisible Then
        For i As Integer = 0 To Me.lvFiles.Items.IndexOf(firstItem) - 1
          fileInTag = DirectCast(Me.lvFiles.Items(i).Tag, MediaFile)
          If Not fileInTag.MetaData.Ready Then
            ret.Add(fileInTag)
            If ret.Count > CountOfFilesVisible Then
              Return ret.ToArray
            End If
          End If
        Next
      End If
      If ret.Count = 0 Then 'all listviewitems vere refreshed by tag data
        Me.allDone = True
        Return Nothing
      ElseIf ret.Count < CountOfFilesVisible Then 'same as previous
        Me.allDone = True
        Return ret.ToArray
      Else 'there is maybe some rest of unread from start to first item shown
        Return ret.ToArray
      End If
    End Function
    
    Private Sub worker_Done(ByVal sender As TagProcessor) Handles worker.Done 'refreshes processed items
      Dim index%                                              'with tag data and starts new work
      For Each item As Windows.Forms.ListViewItem In Me.lvFiles.Items
        If sender.Return.Count > 0 Then
          index = sender.Return.IndexOf(DirectCast(item.Tag, MediaFile))
          If index > -1 Then
            sender.Return(index).refreshListViewItem(item)
            sender.Return.RemoveAt(index)
          End If
        End If
      Next
      If Not Me.allDone Then 'if latest GetNextSet checked all files being read, process is finished
        Me.worker = New TagProcessor(GetNextSet)
        Me.worker.Process()
      End If
    End Sub



    And classes used like this:


    #Region "MetaData"
      Class MetaData
        Public Artist, Album, Title As String
        Private ready_ As Boolean
        Public ReadOnly Property Ready() As Boolean
          Get
            Return Me.ready_
          End Get
        End Property
        Public Sub ReadFromFile()
          'some extraction of file tags
          Me.ready_ = True
        End Sub
        Public Sub New(ByVal path$)
          Me.ready_ = False
          Me.Title = IO.Path.GetFileName(path)
        End Sub
      End Class
    #End Region
    #Region "MediaFile"
      Class MediaFile
        Private Path_$
        Public ReadOnly Property Path$()
          Get
            Return Me.Path_
          End Get
        End Property
        Private MetaData_ As MetaData
        Public ReadOnly Property MetaData() As MetaData
          Get
            Return Me.MetaData_
          End Get
        End Property
        Public Function toListViewItem() As Windows.Forms.ListViewItem
          Dim ret As ListViewItem  =  New ListViewItem
          Me.RefreshListViewItem(ret)
          ret.Tag = Me
          Return ret
        End Function
        Public Sub refreshListViewItem(ByVal item As Windows.Forms.ListViewItem)
          item.SubItems.Clear()
          item.SubItems.Add(Me.MetaData.Artist)
          item.SubItems.Add(Me.MetaData.Album)
          item.SubItems.Add(Me.MetaData.Title)
        End Sub
        Sub New(ByVal filePath$)
          Me.Path_ = filePath
          Me.MetaData_ = New MetaData(filePath)
        End Sub
      End Class
    #End Region
    #Region "Batch background working tag processor"
      Class TagProcessor
        Public [Return] As New Generic.List(Of MediaFile)
        Private files As New Generic.Queue(Of MediaFile)
        Private WithEvents worker As New System.ComponentModel.BackgroundWorker
        Public Event Done(ByVal sender As TagProcessor)
        Private Sub worker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles worker.DoWork
          While Me.files.Count > 0
            Call Me.files.Peek.MetaData.ReadFromFile()
            Me.Return.Add(Me.files.Dequeue)
          End While
        End Sub
        Private Sub worker_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles worker.RunWorkerCompleted
          RaiseEvent Done(Me)
        End Sub
        Public Sub New(ByVal files_() As MediaFile)
          Me.worker.WorkerReportsProgress = False
          Me.worker.WorkerSupportsCancellation = True
          For Each file In files_
            Me.files.Enqueue(file)
          Next
          If Me.files.Count = 0 Then RaiseEvent Done(Me)
        End Sub
        Public Sub Process()
          Me.worker.RunWorkerAsync()
        End Sub
      End Class
    #End Region

    Best wishes, Matt
    Posting you abusive right now, and all of your posts!
    Tuesday, June 16, 2009 10:13 PM
  • thanks for the sample code... though i'm using wpf... i should implement something similar using the dispatcher class...
    Attila Fogel
    Wednesday, June 17, 2009 12:47 PM