Visualizza il feed RSS

Fix's Blog

LINQ - Ordinamento ListView

Valuta questo inserimento
di
Fix
pubblicato il 01-09-2010 alle 21:07 (4992 Visite)
Per ordinare gli elementi di una ListView, in base alla colonna di appartenenza, microsoft ci spiega come realizzare una Classe che implementi l'interfaccia IComparer e come poterla utilizzare per lo scopo.

Il link di questa spiegazione lo trovate qui: Sorting ListView Items by Column Using Windows Forms

Provate, però, ad ordinare 10000(diecimila) elementi, i tempi di azione variano da 1 a 2 secondi per ogni ordinamento, a patto che associate la Classe ListViewItemComparer solamente al momento del bisogno, perchè se lo fate sin dall'inizio (prima di caricare i 10000 elementi) il primo caricamento durerà un'eternità.

Così mi sono chiesto: "e se usassi LINQ per ordinare gli elementi ? "


Per fare la prova ho utilizzato:
  • Le estensioni di LINQ con le Lambda Expressions
  • Un enumeratore per specificare il tipo di dati da ordinare
  • Una Sub (che ho trasformato in Extension per poterla richiamare direttamente dal Controllo)
Premessa:per poter utilizzare le Extensions di LINQ, ho dovuto prima convertire la ListViewItemCollection in IEnumerable (Of T), utilizzando l'Extension Cast di IEnumerable

Per evitare di dover commentare più volte le stesse righe (ripetute), inserirò prima il codice e poi lo analizzeremo

Codice:
Imports System.Runtime.CompilerServices

Module modListViewItemSorter

Enum DataType
[String]
[Integer]
[Date]
End Enum

<Extension()> _
Sub SortBy(ByVal lstView As ListView, ByVal Order As SortOrder, ByVal col As Integer, ByVal dType As DataType)

Dim SortedItems As ListViewItem() = Nothing


Select Case dType

Case DataType.String

If Order = SortOrder.Ascending Then
SortedItems = lstView.Items.Cast(Of ListViewItem).OrderBy(Function(item) item.SubItems(col).Text).ToArray
lstView.Items.Clear()
lstView.Items.AddRange(SortedItems)
Else
SortedItems = lstView.Items.Cast(Of ListViewItem).OrderByDescending(Function(item) item.SubItems(col).Text).ToArray
lstView.Items.Clear()
lstView.Items.AddRange(SortedItems)
End If


Case DataType.Integer

If Order = SortOrder.Ascending Then
SortedItems = lstView.Items.Cast(Of ListViewItem).OrderBy(Function(item) CInt(item.SubItems(col).Text)).ToArray
lstView.Items.Clear()
lstView.Items.AddRange(SortedItems)
Else
SortedItems = lstView.Items.Cast(Of ListViewItem).OrderByDescending(Function(item) CInt(item.SubItems(col).Text)).ToArray
lstView.Items.Clear()
lstView.Items.AddRange(SortedItems)
End If


Case DataType.Date

If Order = SortOrder.Ascending Then
SortedItems = lstView.Items.Cast(Of ListViewItem).OrderBy(Function(item) DateTime.Parse(item.SubItems(col).Text)).ToArray
lstView.Items.Clear()
lstView.Items.AddRange(SortedItems)
Else
SortedItems = lstView.Items.Cast(Of ListViewItem).OrderByDescending(Function(item) DateTime.Parse(item.SubItems(col).Text)).ToArray
lstView.Items.Clear()
lstView.Items.AddRange(SortedItems)
End If


End Select

End Sub

End Module

----------
Ho creato un modulo chiamato modListViewItemSorter
Module modListViewItemSorter
....
....
End Module
----------
Ho creato un enumeratore per specificare che tipo di dati devo ordinare quando richiamo SortBy
Enum DataType
[String]
[Integer]
[Date]
End Enum
----------
La firma della Sub SortBy prevede il passaggio di 3 valori: Direzione di ordinamento + indice colonna cliccata + tipo di dati
Sub SortBy(ByVal lstView As ListView, ByVal Order As SortOrder, ByVal col As Integer, ByVal dType As DataType)
----------
Ho creato una matrice provvisoria su cui copierò i riferimenti degli elementi, in modo da poterli reinserire dopo aver svuotato la ListView
Dim SortedItems As ListViewItem() = Nothing
----------
Il codice che segue prevede la verifica del tipo di dati da ordinare (Select Case dType) e per ogni tipologia c'è la verifica dell'ordinamento (If Order = ...), tale verifica mi serivrà per determinare se utilizzare OrderBy (crescente) oppure OrderByDescending (decrescente).

----------

Adesso analizziamo la Query base:
SortedItems = lstView.Items.Cast(Of ListViewItem).OrderBy/OrderByDescending(Function(elemento) Dati).ToArray
  • Cast = Convertiamo nell'oggetto IEnumerable (Of T) in modo da poter poi sfruttare le Extensions di LINQ
  • OrderBy/OrderByDescending = Funzione che ordinerà gli elementi in modo Crescente/Decrescente in base ad una chiave di selezione (KeySelector)
  • Function(elemento) Dati = è la lambda che useremo come chiave di selezione, dove "elemento" è ogni singolo Item della ListView, e "Dati" è il valore dell'elemento che verrà usato per la comparazione (dovrà quindi essere il valore di una delle colonne convertito in base al suo reale Tipo di appartenenza)
----------


Nei Form dove saranno presenti le ListView da ordinare, inseriremo 2 varibili ed il codice nell'evento Column_Clicked

Nel mio esempio ho creato una ListView con 3 colonne (String, Integer, Date)

Codice:

Dim sortColumn AsInteger = -1
Dim orderDirection As SortOrder

Private Sub ListView1_ColumnClick(ByVal sender As Object, ByVal e As System.Windows.Forms.ColumnClickEventArgs) Handles ListView1.ColumnClick
'-- se è la prima volta che viene cliccata una colonna
'-- ordiniamo direttamente in modo Crescente
If e.Column <> sortColumn Then
'-- teniamo traccia dell'ultima colonna cliccata
sortColumn = e.Column
'-- salviamo lo stato di ordinamento corrente
orderDirection = SortOrder.Ascending
Else
'-- verifichiamo l'ultimo ordinamento fatto
'-- ed eseguiamo l'opposto
If orderDirection = SortOrder.Ascending Then
orderDirection = SortOrder.Descending
Else
orderDirection = SortOrder.Ascending
End If
End If

Select Case e.Column

Case 0 '-- Colonna 1
'-- ordiniamo in base alla colonna 1 di tipo String
ListView1.SortBy(orderDirection, e.Column, DataType.String)
Case 1 '-- Colonna 2
'-- ordiniamo in base alla colonna 2 di tipo Integer
ListView1.SortBy(orderDirection, e.Column, DataType.Integer)
Case 2 '-- Colonna 3
'-- ordiniamo in base alla colonna 3 di tipo Date
ListView1.SortBy(orderDirection, e.Column, DataType.Date)
End Select

End Sub
----------
Gli stessi 10000 elementi sono stati ordinati in meno di mezzo secondo !!

Può essere inserito anche un secondo criterio di Ordinamento (supplementare al primo) per tutti quei valori che risulteranno "uguali", ovvero, ordinare gli elementi uguali in base al valore di un'altra colonna.

Basterà aggiungere alla query ThenBy
esempio di ordinamento in base al valore String della prima colonna con ordinamento secondario in base al valore Integer della seconda colonna:
SortedItems = lstView.Items.Cast(Of ListViewItem).OrderBy(Function(item) item.SubItems(col).Text).ThenBy(Function(Item) CInt(Item.SubItems(col + 1).Text)).ToArray
----------
Ovviamente ci sono i pro ed i contro, la gestione di una Classe che implementa IComparer è sicuramente più versatile e può gestire meglio le singole comparazioni, mentre l'utilizzo di queste Query prevede che i dati passati siano già stati controllati a monte.

Allo stesso tempo LINQ è risultato più veloce su ListView di grosse dimensioni.

NB: In presenza del SyntaxHighlighter (codice colorato con numeri di riga a lato) prima si deve eseguire un DoppioClick e successivamente copiarne il contenuto, altrimenti si avranno problemi di Formattazione

aggiornamento da 04-06-2011 a 11:04 di Fix

Categorie
Programmazione

Commenti