Salvare e ripristinare la configurazione delle colonne della DataGrid
Nel precedente articolo abbiamo aggiunto alla nostra DataGrid standard la funzionalità che permette di sapere il contenuto della cella corrente al click sulla cella stessa. In questo articolo iniziamo a predisporre il necessario per parametrizzare la DataGrid e salvare la configurazione che le diamo per poterla ripristinare al prossimo avvio dell’applicazione.
Nella terza parte di questa serie abbiamo visto come, automaticamente, la DataGrid ci fornisce la funzione di Ordinamento sulle colonne al click del mouse, ci permette di spostare le colonne riordinandole a nostro piacimento, ci permette anche di cambiarne la larghezza. Però queste modifiche vengono perse alla chiusura della window.
Salvataggio e ripristino configurazione posizione, ampiezza, visibilità delle colonne di una grid
Il penultimo argomento nella lista delle funzionalità che volevamo applicare alla nostra DataGrid, per poter creare questa funzione per prima cosa dobbiamo ricavare le informazioni che vogliamo salvare che sono le seguenti:
- Posizione della colonna all’interno della DataGrid
- Larghezza della colonna
- Visibilità
Aggiungiamo a queste tre cose anche il Titolo della colonna che, come abbiamo fatto per visualizzare il contenuto delle colonne sulla TextBox, ci servirà per riconoscere la colonna a cui i dati appartengono.
Se ricordate quanto scritto nella MainWindow.xaml per configurare la DataGrid, abbiamo usato un oggetto che si chiama DataGrid.Columns per aggiungere i dati di configurazione di ciascuna colonna della DataGrid. Pertanto è questo l’oggetto dove troveremo le informazioni che vogliamo salvare.
Per salvare le informazioni prima di tutto dobbiamo memorizzarle in un oggetto che poi scriveremo su database o, come in questo articolo, su disco. In modo da poter recuperare le informazioni salvate alla partenza del programma.
Andiamo quindi a creare la classe che ci serve per questa prima operazione.
La classe ColumnDataItem.cs
Per creare questa classe ci spostiamo sulla cartella Entities e andiamo a generare una classe vuota:
Una volta aggiunta la classe andiamo a generare il necessario per farla funzionare come ci servirà:
using System; using System.Collections.Generic; using System.Text; using System.Linq; using System.ComponentModel; namespace WpfGridColors.Entities { public class ColumnDataItem { } }
Qui sopra la definizione della classe, adesso inseriamo al suo interno le property che rappresentano i dati che vogliamo salvare.
public const string FLD_DisplayOrder = "DisplayOrder"; private int mDisplayOrder; public int DisplayOrder { get { return mDisplayOrder; } set { mDisplayOrder = value; } } public const string FLD_Width = "Width"; private double mWidth; public double Width { get { return mWidth; } set { mWidth = value; } } public const string FLD_IsVisible = "IsVisible"; private bool mIsVisible; public bool IsVisible { get { return mIsVisible; } set { mIsVisible = value; } } public const string FLD_Header = "Header"; private string mHeader; public string Header { get { return mHeader; } set { mHeader = value; } }
Qui sopra le quattro property che definiscono i dati da memorizzare, aggiungerò anche la funzione ToString sempre utile per osservare il contenuto della classe in Debug.
public override string ToString() { StringBuilder sb = new StringBuilder(); sb.AppendFormat("{0}: Width {1} Order {2} Vis {3}", Header, Width,DisplayOrder,IsVisible); sb.AppendLine(); return (sb.ToString()); }
Ora abbiamo un oggetto per memorizzare i dati di configurazione di una colonna, ma ovviamente ci servirà un posto per memorizzarne molti. Pertanto l’oggetto più adatto è una collection.
Però, invece di usare una collection Generica, come la List<T> o la ObservableCollection<T> ho deciso di creare una classe derivata da una di queste perché, voglio incapsularvi anche la logica necessaria a salvare la lista su disco e ripristinare i valori leggendoli da un disco.
La Classe ColumnDataCollection.cs
Per andare a generare questa collection, prima generiamo una cartella posizionandoci sul nodo di progetto e usando New… Folder… creiamo la cartella e chiamiamola Collections.
Aggiunta la cartella Collections, posizioniamoci su di essa ed aggiungiamo la Classe collection che ci serve.
using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Xml.Serialization; using WpfGridColors.Entities; using WpfGridColors.Helpers; namespace WpfGridColors.Collections { ///<summary> /// Collection per la gestione del salvataggio della configurazione della grid ///</summary> public class ColumnDataCollection : List<ColumnDataItem> { } }
La definizione della nostra collection, che è derivata dalla classe Generic List (List<T>) andiamo ora a definire le funzionalità che ci permetteranno di salvare e rileggere la classe su una stringa che poi salveremo su disco.
[XmlIgnore] public ColumnDataItem this[string header] { get { return (this.FirstOrDefault(item => item.Header == header)); } } public string WriteXml() { return XmlHelper.SerializeToString(this); } public static ColumnDataCollection ReadXml(string data) { return XmlHelper.DeserializeFromString<ColumnDataCollection>(data); }
Possiamo vedere come abbiamo inserito nella nostra collection parametrizzata tre metodi. Il primo è un metodo Indexer, ovvero un metodo che mi permette di chiedere un elemento della collection con un “indice” sulla colonna Header.
myCollection[“Header”];
E’ un istruzione valida perché il nostro metodo Indexer è in grado di andare a prendere l’elemento con il titolo da noi passato.
I due metodi WriteXml e ReadXml sono spiegati dal loro nome e ci permettono di convertire il contenuto della collezione in una stringa XML e vice versa di impostare il contenuto di una classe collection leggendolo da una stringa XML che potrebbe arrivare da un file su disco.
Sono certa abbiate notato come i 2 metodi di serializzazione e deserializzazione, sono posti su una classe che non abbiamo discusso ma che è uno dei Bonus della serie.
La classe si chiama XmlHelper e l’ho aggiunta sulla cartella Helpers che ho aggiunto al progetto.
Non la discuterò ma ve la lascio ovviamente inclusa nel codice, perché una delle molteplici discussioni sulla serializzazione la trovate in questo articolo ed il successivo. Ve ne sono comunque accenni in tutti gli articoli che trattano in qualche modo dati XML. Se ritenete sia opportuno che spenda un po’ di tempo a rispiegarlo, fatemelo sapere usando i messaggi da questo sito.
Ora che abbiamo costruito gli “attrezzi” necessari a salvare la configurazione della Grid, vediamo dove metterli.
Modifiche alla classe MainWindow.cs per salvare la configurazione colonne della DataGrid
Il luogo più opportuno ove inserire il codice per salvare la configurazione delle colonne è l’evento Closed della Window, così che quando chiudiamo la Window i dati delle colonne vengono salvati.
protected override void OnClosed(EventArgs e) { ColumnDataCollection columnsData = GetColumnsData(); string filename = GetColumnSettingFileName(); File.WriteAllText(filename, columnsData.WriteXml()); base.OnClosed(e); }
Per salvare i dati ho generato un paio di metodi ovvero GetColumnsData, che elabora i dati sulle colonne della DataGrid e li restituisce come una istanza della Collection da me generata poco più in alto. GetColumnSettingFileName, invece genera un nome univoco per l’export dei dati rilevati.
Infine i dati sono memorizzati su un file su disco.
private ColumnDataCollection GetColumnsData() { ColumnDataCollection columnsData = new ColumnDataCollection(); foreach (var col in dgStat.Columns) { ColumnDataItem coldata = new ColumnDataItem(); coldata.DisplayOrder = col.DisplayIndex; coldata.Width = col.ActualWidth; coldata.IsVisible = col.Visibility == Visibility.Visible; coldata.Header = col.Header.ToString(); columnsData.Add(coldata); } return columnsData; }
Il metodo di raccolta dati, scorre la collection Columns della nostra DataGrid e riempie gli elementi da noi definiti per salvare la configurazione di una colonna.
private static string GetColumnSettingFileName() { string filename = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "WpfGridColors", "GridSettings.xml"); if (!Directory.Exists(Path.GetDirectoryName(filename))) { Directory.CreateDirectory(Path.GetDirectoryName(filename)); } return filename; }
Il nome del file viene generato sulla cartella WpfGridColors che è posta nei documenti dell’utente windows corrente. Ma potete anche salvarla sulla cartellla AppData o su qualsiasi altra cartella ove l’utente abbia diritti di lettura e scrittura.
Il nome della grid, in caso di grid multiple può essere preso dal nome oggetto e dal nome della classe.
Fatto tutto questo, dobbiamo decidere ove rileggere i dati. Nel mio caso, ho deciso di usare l’evento ContentRendered della finestra MainWindow ma può stare dove preferite, posto che sia accessibile in lettura e scrittura.
private void MainWindow_ContentRendered(object sender, EventArgs e) { LoadData(); string filename = GetColumnSettingFileName(); if (File.Exists(filename)) { string data = File.ReadAllText(filename); ColumnDataCollection columnsData = ColumnDataCollection.ReadXml(data); if (columnsData != null && columnsData.Count > 0) { foreach (var col in dgStat.Columns) { ColumnDataItem coldata = columnsData[col.Header.ToString()]; if (coldata != null) { col.DisplayIndex = coldata.DisplayOrder; col.Width = coldata.Width; col.Visibility = coldata.IsVisible ? Visibility.Visible : Visibility.Collapsed; } } } } }
Ecco come ho modificato il metodo nell’event handler:
- genero il nome del file da leggere
- verifico che il file esista
- se ho trovato i dati di colonna
- faccio un ciclo su tutte le colonne esistenti e se trovo la colonna salvata corrispondente (sempre via Header) e aggiorno il valore della colonna con quello trovato.
- aggiorno gli oggetti column corrispondenti.
fatto questo, le colonne avranno subito tutte le modifiche rilevate.
Chiudendo e riaprendo la Window, vedremo che il cambio di posizione funziona e viene mantenuta anche la dimensione.
Provate a spostare come ho fatto io le colonne percentuale e vedere se chiudendo la window restano nel posto ove sono state inserite.
Mi fermo qui, nella prossima lezione vedremo come acquisire e salvare la visibilità delle colonne della DataGrid.