Top Banner
Windows Forms Databinding Raffaele Rialdi MVP C# Raffaele Rialdi MVP C# [email protected] [email protected] http://mvp.support.microsoft.com http://mvp.support.microsoft.com
38
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Windows Forms DatabindingRaffaele Rialdi MVP C#Raffaele Rialdi MVP C#

[email protected]@vevy.com

http://mvp.support.microsoft.comhttp://mvp.support.microsoft.com

Page 2: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Cos'è il binding?

• Letteralmente significa legame e permette di stabilire automaticamente lo scambio di valori tra controllo e una sorgente dati.– Il databinding evita codice noioso e ripetitivo nel quale

assegnamo i dati al controllo e successivamente li riassegnamo alla sorgente dati.

• Il binding con DataSource 'poveri' implica l'uso di reflection e quindi impoverisce le performance.Se invece il DataSource implementa le interfacce giuste il discorso è molto diverso.

Page 3: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Big picture ...

• In ADO.NET le sorgenti dati disconnesse non hanno più il concetto di record corrente.

• Il binding di dotnet è gestito da un intermediario tra controllo e sorgente dati

• La presenza di un unico intermediario per bindare più controlli garantisce il sync tra questi

BindingManagerBaseBindingManagerBase DataSync

Page 4: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Sincronizzazione tra controlli

• E se non volessimo i due controlli sincronizzati?• E se volessimo scorrere i dati indipendentemente sui

due controlli?• Risposta: bisogna avere due intermediari

DataSync

BindingManagerBaseBindingManagerBase

BindingManagerBaseBindingManagerBase

Page 5: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

BindingContext

• BindingContext è semplicemente una lista di BindingManagerBase mantenuta tramite Hashtable

• Poiché il nome del datasource è parte della chiave della Hashtable, non più di un BindingManagerBase con lo stesso datasource può esistere in un BindingContext

• Perció per non avere sync tra due controlli, i due BindingManagerBase devono appartenere a due BindingContext diversi

DataSync

BindingManagerBaseBindingManagerBase

BindingManagerBaseBindingManagerBase

BindingContext(Hashtable)

BindingContext(Hashtable)

BindingContext(Hashtable)

BindingContext(Hashtable)

Page 6: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Esempio SimpleBindingText, Font, BindingContext

Page 7: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Binding Manager

• Il binding manager è l'intermediario • Per ogni datasource esiste un solo binding manager• Per ogni binding manager ci sono uno o più controlli• Questo serve per avere più controlli sincronizzati durante

la navigazione dei dati.

• La classe base del binding manager è BindingManagerBase (astratta) e ha due classi derivate:– PropertyManager gestisce il binding con singoli elementi– CurrencyManager gestisce il binding con liste di elementi

Page 8: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Simple binding

• Associa un qualsiasi tipo ad un controllo in modo da semplificare la presentazione di un valore e poterlo aggiornare

• Il binding con un singolo elemento implica l'uso di PropertyManager– La proprietà Position sarà sempre 0

• Il binding con una lista di elementi implica l'uso di CurrencyManager che ha il concetto di 'record corrente'.– Si usa Position per navigare le righe mostrate– Non si usa Position per leggere la posizione perchè la lista potrebbe

contenere elementi che non vengono mostrati (es. filtro sulla dataview)– Si usa Current per leggere l'elemento nella lista sottostante (datasource)

int i=5;myLabel.DataBindings.Add("Text", i, null);

int i=5;myLabel.DataBindings.Add("Text", i, null);

Proprietàcontrollo DataSource DataMember

se è null viene usatoToString()

Page 9: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Tip

• FAQ!Ricavare l'elemento del datasource data la riga selezionata nella datagrid.

• In caso di DataSet/DataTable ritorna una DataRowView

DataRowView drv = GetCurrentBindedObject(DataGrid1) as DataRowView;

private object GetCurrentBindedObject(DataGrid dg) {

if(dg == null || dg.DataSource == null) return null;BindingManagerBase bmb = dg.BindingContext[dg.DataSource];if(bmb == null || bmb.Count == 0) return null;return bmb.Current;

}

DataRowView drv = GetCurrentBindedObject(DataGrid1) as DataRowView;

private object GetCurrentBindedObject(DataGrid dg) {

if(dg == null || dg.DataSource == null) return null;BindingManagerBase bmb = dg.BindingContext[dg.DataSource];if(bmb == null || bmb.Count == 0) return null;return bmb.Current;

}

Page 10: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Sincronia dei dati

• Le variazioni del controllo vengono messe nel datasource quando:– Il datasource è un oggetto che espone proprietà– Il datamember è specificato esplicitamente nel binding

• Le variazioni del datasource vengono messe nel controllo quando:– Sono vere le condizioni di prima– Esiste un evento [NomeProprietà]Changed (ValChanged) che

segnala le modifiche al datasourceoppure

– La classe supporta IBindingList (lo vedremo più avanti)

Page 11: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Simple e Complex Binding

Data

CurrencyManageroppure

PropertyManager

BindingManagerBaseBindingManagerBase

BindingBinding

BindingManagerBaseBindingManagerBase

Bindings

BindingsCollectionoppure

ControlBindingsCollection

DataSource

BindingBinding

BindingBinding

DataMember

Control

PropertyName

DataBindingManagerBaseBindingManagerBase

BindingBinding

DataSource

DataMember

ControlPropertyName

IBindingList, ITypedList, ...

Simple Binding

Simple Binding

Complex BindingBindingContextBindingContext

BindingContextBindingContext

Page 12: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Esempio SingleElementSimple bindingsul singolo elemento

Page 13: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Simple Binding: format e parse

• La classe binding (disponibile solo nel simple binding) offre due eventi importanti:– format. Intercetta il dato proveniente dal DataSource prima che

venga impostato nel controllo.– parse. Intercetta il dato che dal controllo sta per essere trasferito

al DataSource

• È un ottimo metodo per migliorare la qualità della visualizzazione dei dati

• Non è utile alla validazione che deve essere effettuata dal controllo

Page 14: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Esempio FormatParseeventi format e parse

Page 15: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Complex bindingusando gli oggetti ado.net• IListSource permette di 'scoprire' qual'è la collection (IList) che contiene i veri

elementi con cui effettuare il binding.• La collection è la DataView• Gli elementi sono le DataRowView

DataTableDataTable

DataSetDataSet

DataRowDataRow

DataViewDataView

DataRowViewDataRowView

DataRowDataRow DataRowViewDataRowView

DataRowDataRow DataRowViewDataRowView

IList IListSource.GetList(){

return this.DefaultView;}

IList IListSource.GetList(){

return this.DefaultView;}

DataSource

DataMember

Page 16: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Tips

• Cosa specificare in DataSource e DataMember?– DataSource = myDataSet

– DataMember = myTable.myColumn

Non specificare mai nel DataSource myDataSet.myTable perché:

– il nome del DataSource funge da Key in BindingContext

– il designer usa la convenzione in alto

• Quando si usa il CurrencyManager, può essere necessario usare il suo metodo Refresh affinché il controllo venga aggiornato.La Listbox ha bisogno di questo refresh mentre altri controlli no.

• Affinché il DataSource sia sicuramente aggiornato è necessario chiamare EndCurrentEdit

Page 17: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Perché rinunciare al dataset

• Non sempre abbiamo bisogno di tutto ciò che il dataset offre

• Il dataset è un contenitore generico e come tale è costretto a gestire in modo meno efficente i dati al suo interno

• Anche quando viene tipizzato l'uso a design time è farraginoso perchè ci sono troppe proprietà/metodi

• Ma soprattutto perchè non rappresenta esattamente i nostri dati ma siamo costretti ad adeguare i dati al dataset

Page 18: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Custom entities

1. Creare una collection dei nostri dati

2. Possibilmente tipizzarla

3. Implementare almeno:• IEnumerable per il mondo web• IList per il mondo WinForms

4. Molte altre interfacce per renderla ricca di funzionalità al runtime e al design time (... le vedremo ...)

CustomerCollectionCustomerCollection

CustomerCustomer

CustomerCustomer

CustomerCustomer

CustomerCustomer

Page 19: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Collection

• Strada più veloce: derivare da ArrayList e implementare gli overload tipizzati per rendere più piacevole l'uso della collection

• Strada più elegante: implementare IList, ICollection e IEnumerable usando per composition un ArrayList privato

• Derivando CollectionBase non otteniamo una collection ben tipizzata e ci 'brucia' la possibilità di derivare la collection da un'altra classe base visto che in dotnet non c'è multiple inheritance

Page 20: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Collection

• Implementare IList (e quindi anche ICollection e IEnumerable)

• IList: metodi base come Add, Remove, Clear e Contains• IList è l'unica interfaccia indispensabile per essere un

datasource valido nelle Winform• ICollection: proprietà Count, SyncRoot• IEnumerable: GetEnumerator indispensabile per poter

usare foreach• IEnumerable è l'unica interfaccia indispensabile per

essere un datasource valido nelle Webform

Page 21: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Duplicabilità (opzionale)

• ICloneable. Unico metodo è Clone• MSDN dice che è facoltà del programmatore scegliere

se si vuole implementare Clone come:– Deep Copy: copia della collection e di tutti gli elementi

referenziati [Implementazione complessa]– Shallow Copy: copia della sola collection che condividerà gli

elementi con la collection di partenza [Implementazione semplice]

Page 22: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Usabile dal designer

• IComponent oppure MarshalByValueComponent• IComponent unito agli attributi permette l'uso nel

designer. Si aggiunge la collection alla toolbox e si trascina sulla superficie del form– [DesignerCategory("")] oppure [DesignerCategory("component")]– [ToolboxItem(true)]– [DesignTimeVisible(true)]

• MarshalByValueComponent ci risparmia la fatica di implementare IComponent ma ci "brucia" la possibilità di derivare da un'altra classe

Page 23: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

IListSource: gerarchia a design time• Dato un DataSource vedo in DataMember l'elenco delle collection

figlie

• Una possibilità è quella di usare un oggetto contenitore e segnare il getter delle collection con l'attributo:DesignerSerializationVisibility

• Se invece si vuole la gerarchia analoga a quella del DataSet devo costruire un oggetto analogo al DataViewManager:– il count riporta sempre 1 (così a design time c'è una sola riga)

– ITypedList riporta le informazioni sulle inner-collection

– costruire un PropertyDescriptor personalizzato

Implementazione omessa nell'esempio per brevità

Page 24: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Serializzabile, usabile da remoting• [Serializable] oppure ISerializable• La serializzazione permette di attraversare i remoting

boundary

Page 25: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Abilitazione alle notifiche, alle ricerche, editabile e ordinabile

• IBindingList fornisce tutte quelle caratteristiche che troviamo nella DataView. È l'interfaccia più noiosa da implementare

• Proprietà Allowxxx, Supportxxx, Sortxxx• Metodo AddNew che permette di inserire• Metodo ApplySort che permette di ordinare• Evento ListChanged per segnalare al controllo che i dati

nella lista sono cambiati– Questo implica che l'oggetto child (collezionato) deve segnalare

alla lista se e quando è cambiato

Page 26: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Abilitazione alle notifiche, alle ricerche, editabile e ordinabile

• E se non si implmenta IBindingList?– Per aggiornare i dati è necessario dare uno 'scrollone' al binding:

– Per notificare le modifiche è necessario creare un evento che abbia il nome della proprietà + "Changed"Esattamente quanto già visto in una slide iniziale e nell'esempio "SingleElement"

private void RefreshGrid(){

BindingManagerBase bmb = dataGrid1.BindingContext[dataGrid1.DataSource];

bmb.SuspendBinding();bmb.ResumeBinding();

}

private void RefreshGrid(){

BindingManagerBase bmb = dataGrid1.BindingContext[dataGrid1.DataSource];

bmb.SuspendBinding();bmb.ResumeBinding();

}

Tip!

Page 27: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Informazioni sullo schema

• ITypedList permette ai controlli di scoprire lo schema a runtime.

• Permette di conoscere il tipo che si 'nasconde' dietro una proprietà.– Metodo GetItemProperties. Se la proprietà è un tipo complesso

(collection) bisogna fornirgli l'elenco delle proprietà del tipo che si nasconde dietro la collection.

– Metodo GetListName. Se la proprietà è una collection, noi dovremo fornirgli il nome (stringa) del tipo collezionato

Page 28: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Informazioni sullo schema• Con ITypedList possiamo fare a meno delle TableStyles:

– cambiare nomi alle colonne (per esempio "NomeAzienda" in "Nome Azienda")– ordinare le colonne in modo arbitrario– nascondere le colonne non desiderate

public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors){

// prende il nome dell'oggetto collezionato con una funzione customType t = GetPropertyType(listAccessors); // vedi esempio per l'implementazione

PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(t);// dentro pdc ci sono tutte le informazioni sul tipo, compresi gli attributi

pdc = pdc.Sort(Customer.PropertyNames); // ordina le colonneint Len = Customer.PropertyNames.Length;PropertyDescriptor[] props = new PropertyDescriptor[Len];

for(int i=0; i<Len; i++){

if(Array.IndexOf(Customer.PropertyNames, pdc[i].Name) == -1)continue; // elimina le colonne non presenti nell'array

props[i] = new BizPropertyDescriptor(pdc[i], null); // PropertyDescriptor custom}

PropertyDescriptorCollection newpdc = new PropertyDescriptorCollection(props);return newpdc;

}

public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors){

// prende il nome dell'oggetto collezionato con una funzione customType t = GetPropertyType(listAccessors); // vedi esempio per l'implementazione

PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(t);// dentro pdc ci sono tutte le informazioni sul tipo, compresi gli attributi

pdc = pdc.Sort(Customer.PropertyNames); // ordina le colonneint Len = Customer.PropertyNames.Length;PropertyDescriptor[] props = new PropertyDescriptor[Len];

for(int i=0; i<Len; i++){

if(Array.IndexOf(Customer.PropertyNames, pdc[i].Name) == -1)continue; // elimina le colonne non presenti nell'array

props[i] = new BizPropertyDescriptor(pdc[i], null); // PropertyDescriptor custom}

PropertyDescriptorCollection newpdc = new PropertyDescriptorCollection(props);return newpdc;

}

Page 29: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Gerarchica

• Nessuna interfaccia. Questo viene gratis quando si implementa bene la ITypedList e il nostro object model è costruito secondo i canoni classici

NWindSaleNWindSale

OrderOrder

OrderDetailOrderDetail ProductProduct

Orders

OrderOrder

OrderDetails

OrderDetailOrderDetail

OrderDetailOrderDetail

ProductProduct

ProductProductProducts

Products

Products

Si potrebbe scrivere una implementazione che nel binding mostri i dati sulla stessa riga mentre gli

oggetti restano separatiMa non ha forse più senso un business object che

li inglobi entrambi?

Nota: l'articolo: http://support.microsoft.com/kb/325682/EN-US/ mostra come creare una JoinView per mostrare dati di due DataTable

Page 30: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Validazione ed errori

• Per gestire la validazione dei dati è necessario implementare IDataErrorInfo nella classe collezionata

• Metodo IDataErrorInfo.Item (indexer)– ci passa il nome della colonna (stringa)

– restituiamo stringa vuota oppure l'errore da mostrare come tooltip

• Proprietà get di IDataErrorInfo.Error– restituiamo stringa vuota oppure l'errore da mostrare nell'header di riga

della grid

– Analogo a impostare la proprietà RowError della DataRow

Page 31: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Modifiche transazionali

• L'oggetto collezionato può decidere di gestire la transazionalità delle modifiche implementando IEditableObject

• Bisogna implementare i 'famosi' metodi BeginEdit, EndEdit, CancelEdit tenendo da parte i valori temporanei delle proprietà fino al commit o rollback della transazione

• Per evitare equivoci, stiamo parlando di modifiche sull'interfaccia grafica, non del database

• Per esempio: Utente sceglie la riga

BeginEditEndEdit CancelEdit

Pressione tasto ESCCambio di riga Inizio modifiche

Page 32: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

625 righe di collection Dov'è il trucco?• Parola d'ordine: "case tools" cioè generatori di codice

– Se usano CodeDom è meglio perchè possono generare codice VB.net, C#, ....

– Se usano StringBuilder è facile realizzarli

• Generare cosa?– Il business object base a partire dalla tabella del db

Attenzione! I business object spesso non mappano 1:1 con le tabelle quindi un lavoro manuale è sempre dovuto

– La collection super-accessoriata. Questo è un task semplice: basta partire dall'esempio di questa sessione ed eseguire un banale replace con una regular expression

Page 33: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Alla fine ci siamo rifatti il DataSet ...• [Serializable]

public class DataSet : MarshalByValueComponent,IListSource, ISupportInitialize, ISerializable

• [Serializable]public class DataTable :

MarshalByValueComponent, IListSource, ISupportInitialize, ISerializable

• public class DataView : // Analogo della collection di custom objectMarshalByValueComponent, IBindingList, IList, ICollection, IEnumerable, ITypedList, ISupportInitialize

• public class DataRowView : // Analogo del custom objectICustomTypeDescriptor, IEditableObject, IDataErrorInfo

• public class DataViewManager : // Conserva una lista di impostazioni delle DefaultView MarshalByValueComponent, IBindingList, IList, ICollection, IEnumerable, ITypedList

• [Serializable]public class DataRow

Page 34: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Ne vale la pena?

• Se si sviluppa RAD (vita dell'app molto corta), è meglio il DataSet

• Se il progetto è molto semplice può convenire usare il DataSet

• Negli altri casi io preferisco custom entities:– I business objects e le sue regole non si possono sempre

deformare per farli assomigliare ad un database.– I business object non sono tabellari e non hanno

necessariamente una semplice relazione 1:1 o 1:molti– Il db deve rimanere sempre solo un contenitore per la

persistenza

Page 35: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

La parola ai benchmark ...... per quello che valgono• CustomClass vs DataSet• Solo operazioni in memoria (no binding)• CustomClass "full-optional"• DataSet tipizzato o meno non cambia

• Risultato velocità:

• Risultato memoria (bytes in all heaps):– DataSet 231'891'952 byte 11,33%– Custom 208'285'852 byte 10,18%(misure eseguite con perfmon)

public class CustomClass{

private int _c0, _c1;private decimal _c2, _c3;private double _c4, _c5;private string _c6, _c7, _c8, _c9;// ...

}

public class CustomClass{

private int _c0, _c1;private decimal _c2, _c3;private double _c4, _c5;private string _c6, _c7, _c8, _c9;// ...

}

public TimeSpan DoTest(ITestBiz obj){

DateTime d1 = DateTime.Now;for(int i=0; i<1000000; i++){

obj.AddOne();obj.DeleteOne();

}

for(int j=0; j<100; j++){

for(int i=0; i<10000; i++)obj.AddOne();

obj.Clear();}DateTime d2 = DateTime.Now;return d2 - d1;

}

public TimeSpan DoTest(ITestBiz obj){

DateTime d1 = DateTime.Now;for(int i=0; i<1000000; i++){

obj.AddOne();obj.DeleteOne();

}

for(int j=0; j<100; j++){

for(int i=0; i<10000; i++)obj.AddOne();

obj.Clear();}DateTime d2 = DateTime.Now;return d2 - d1;

}

Page 36: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Cosa ci manca ancora?

• Mantenimento dello stato pre-modifica (aggiornamento e cancellazione)

• Flag per marcare i nuovi oggetti nella collection• Un analogo della DataView• Un sistema di persistenza su DataBase ... ORM

Page 37: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Cosa ci porta VS2005?

• Gestione del DBNull nell'infrastruttura del binding!(niente più problemi con controlli come il datetimepicker)

• Proprietà NullMapping per la sostituzione del DBNull• Gestione visuale delle "List" in controlli come la combo• "Smart Captions". Se la colonna si chiama "NomeAzienda" il binding

la cambierà automaticamente in "Nome Azienda: " con una regex• Eventi "string-typed" dalle colonne del dataset tipizzato (oggi sono

generiche)• Uso delle partial classes per i DataSet tipizzati• L'oggetto TableAdapter per i dataset (che supporta nativamente i

DBNull)

• BindingSource e BindingNavigator (nella beta1 si chiamavano DataConnector e DataNavigator)... comodo ma non può far altro che usare reflection e quindi è meno efficente della soluzione presentata

Page 38: Windows Forms Databinding Raffaele Rialdi MVP C# malta@vevy.com.

Domande?

E poi non dite che è la solita sessione ...... dove si racconta la solita storia ...

... che si fa tutto senza scrivere una riga di codice