Technology Experience

Contenuti gestiti da Igor Damiani
posts - 949, comments - 2741, trackbacks - 15120

My Links

News

  • Questo blog si propone di raccogliere riflessioni, teoriche e pratiche, su tutto quello che riguarda il world-computing che mi sta attorno: programmazione in .NET, software attuale e futuro, notizie provenienti dal web, tecnologia in generale, open-source.

    L'idea è quella di lasciare una sorta di patrimonio personale, una raccolta di idee che un giorno potrebbe farmi sorridere, al pensiero di dov'ero e cosa stavo facendo.

    10/05/2005,
    Milano

Archives

Post Categories

Generale

(Parte 1) Le interfacce INotifyPropertyChanged e IBindingList

Non c'entra nulla con l'esame con cui sto bombardando pacificamente il mio blog , però in questi giorni mi sono posto il problema di far vivere un oggetto business all'interno di una WF, facendo in modo di usare il data-binding per riempire i controlli e di mantenere sincronizzato lo stato dell'oggetto in questione in base alle operazioni fatte dall'utente attraverso la UI.

Dedico questo post a mio fratello, se non altro per avermi ispirato con un bell'oggetto HockeyPlayer da quando ha installato sul suo PC NHL non-so-che-versione, dovuto all'euforia di Torino 2006 .  Ho creato questo oggetto con qualche semplice proprietà e con un'altra un po' più complessa. Vediamo parte del codice.

using System;
using System.ComponentModel;

namespace DataBindingSample
{
    
public class HockeyPlayer
    {
        
private string name;
        
private decimal height;
        
private decimal weight;
        
private FaultsCollection faults;

        
public string Name
        {
            
get return name; }
            
set { name = value; }
        }

        
public decimal Height
        {
            
get return height; }
            
set { height = value; }
        }

        
public decimal Weight
        {
            
get return weight; }
            
set { weight = value; }
        }

        
public FaultsCollection Faults
        {
            
get return faults; }
        }

        
public HockeyPlayer(string Name, decimal Height, decimal Weight)
        {
            name = Name;
            height = Height;
            weight = Weight;
            faults = 
new FaultsCollection();
        }

        
static public HockeyPlayer CreateHockeyPlayer()
        {
            
return (new HockeyPlayer("Name", 0, 0));
        }
    }
}

La classe HockeyPlayer espone 4 proprietà: Name, Height, Weight ed un'altra FaultsCollection così definita.

using System;
using System.Collections.ObjectModel;

namespace DataBindingSample
{
    
public class FaultsCollection : Collection<DateTime> { }
}

La classe ci permette di utilizzare un oggetto che rappresenta un giocatore di hockey, con un elenco di tutti i falli che subìto durante la partita, espressi tramite una banale Collection<DateTime>. In questo modo, posso istanziare un oggetto in modo abbastanza semplice:

HockeyPlayer pl = HockeyPlayer.CreateHockeyPlayer();
pl.Name = "Brian Storm";
pl.Height = 182;
pl.Weight = 74;
pl.Faults.Add(
new DateTime(2006, 2, 22, 12, 51, 00));

A questo punto, ho creato un WF con 3 TextBox ed 1 ListBox per visualizzare i membri della classe. Questa WF si basa sui seguenti concetti:

  1. Espone una proprietà pubblica Player che mappa un oggetto della classe HockeyPlayer. Tale proprietà fa ovviamente riferimento ad un membro privato player.
  2. Ho creato un metodo internal chiamato SetupDataBindings() che si occupa di inizializzare per ogni Control la sua datasource. Tale metodo viene chiamato solo una volta durante il Load del form stesso
  3. Ho messo un pulsante di test che modifica alcuni membri dell'oggetto privato player, aspettandomi che i controls bindati a questo oggetto vengano refreshati automaticamente

Detto questo, vi mostro parte del codice relativo al form che ho creato.

// membro privato
private HockeyPlayer player;
// proprietà pubblica da esporre
public HockeyPlayer Player
{
    
get return player; }
    
set { player = value; }
}
// handler dell'evento Load
private void FormHockeyPlayer_Load(object sender, EventArgs e)
{
    SetupDataBindings();
}
// metodo privato che inizializza il data-binding
internal void SetupDataBindings()
{
    txtName.DataBindings.Add("Text", player, "Name");
    txtWeight.DataBindings.Add("Text", player, "Weight");
    txtHeight.DataBindings.Add("Text", player, "Height");
    lstFaults.DataSource = player.Faults;
}

Facciamo un ulteriore passo avanti: ho creato un Main() che innanzitutto crea un oggetto HockeyPlayer, e lo passa al form di cui sopra per poterne mostrare i valori. Ecco il codice:

static void Main()
{
    HockeyPlayer pl = HockeyPlayer.CreateHockeyPlayer();
    pl.Name = "Brian Storm";
    pl.Height = 182;
    pl.Weight = 74;
    pl.Faults.Add(
new DateTime(2006, 2, 22, 12, 51, 00));

    FormHockeyPlayer frm = 
new FormHockeyPlayer();
    frm.Player = pl;
    frm.ShowDialog();
}

Il form appare, i controlli sono valorizzati e posso sorridere compiaciuto.  Fin qua nulla di particolare, le magie del data-binding le conosciamo bene, e non c'è alcun motivo per gioire in modo particolare. Se non chè....ho avuto un problema: ho accennato prima nel punto (3) di aver messo sul form un Button che va a modificare alcuni valori del mio HockeyPlayer. Ad esempio, cambia il nome del giocatore, cambia il suo peso e la sua altezza e - vero problema - aggiungo un elemento DateTime alla sua proprietà Faults:

private void button1_Click(object sender, EventArgs e)
{
    
string text = string.Format("Matthew Werder Brema");
    player.Name = text;
    player.Weight = 98.5M;
    player.Height = 190M;
    player.Faults.Add(DateTime.Now);
}

Uno potrebbe pensare che se modifico l'oggetto player privato, in realtà vengono aggiornati anche i controls verso i quali questo oggetto è bindato, ma non è così. Sì, è vero, il mio membro player è il data-source per i miei controlli, ma la cosa non è così automatica. Questa estate, quando ho sbranato "Windows Forms Programming in C#" di Chris Sells, ho capito come risolvere il problema: basta che nel mio oggetto business vado ad implementare l'interfaccia INotifyPropertyChanged che, come dice il nome stesso, permette di notificare al FX che una proprietà è stata modificata. Il FX di conseguenza gestisce la situazione e provoca un refresh dei controlli bindati. Ecco un piccolo estratto del codice della classe HockeyPlayer (riporto solo la proprietà Name):

public class HockeyPlayer : INotifyPropertyChanged
{
    
private string name;

    
public string Name
    {
        
get return name; }
        
set { name = value; NotifyPropertyChanged("Name"); }
    }

    
public event PropertyChangedEventHandler PropertyChanged;

    
private void NotifyPropertyChanged(String info)
    {
        
if (PropertyChanged != null)
        {
            PropertyChanged(
thisnew PropertyChangedEventArgs(info));
        }
    }
}

In pratica, nel costrutto set di ogni property che vogliamo notificare, dobbiamo sollevare un evento di tipo PropertyChangedEventHandler. Tale evento viene gestito in automatico dal FX, che si occupa di provocare un refresh dei controlli che sono in binding con la classe stessa. Adesso, quando eseguo il progetto e clicco sul Button di test, vedo le 3 TextBox che cambiano valore in modo completamente trasparente, e senza scrivere codice apposta.

Ma....c'è sempre una ma!
Notare una cosa: nel codice button1_Click() che ho incollato sopra, in realtà aggiungo un elemento DateTime alla proprietà Faults del mio oggetto. Mi aspetto quindi che la ListBox venga refreshata di conseguenza, ma non è così. Ci risiamo, dunque. Ieri sera ho ripreso in mano il libro di Chris Sells alla ricerca di una soluzione, e l'ho trovata solo a metà. Vi riporto quello che scrive Chris alla fine del capitolo 13, pagina 516, del suo volume "Windows Forms Programming in C#":

A more full-featured integration with the data-grid including enablig the datagrid to edit the data and the ability to keep the datagrid up-to-date as the list data-source is modified - requires an implementation of the IBindingList interface, something that is beyond the scope of this book.

Dal momento che Chris non ha avuto tempo di farvi vedere come fare 'sta cosa, scriverò un post dedicato, ma ovviamente non è nulla di trascendentale: come dice la sua nota qui sopra, è sufficiente implementare l'interfaccia IBindingList all'interno della nostra classe FaultsCollection inclusa in HockeyPlayer.

powered by IMHO 1.2

Print | posted on Thursday, February 23, 2006 11:18 AM | Filed Under [ Sviluppo .NET ]

Feedback

Gravatar

# re: (Parte 1) Le interfacce INotifyPropertyChanged e IBindingList

IBindingList non è poi così banale da implementare e sopratutto col Fx2.0 esiste una scorciatoria: Usare BindingList<T>
2/23/2006 12:00 PM | Corrado Cavalli
Gravatar

# (Parte 2) Le interfacce INotifyPropertyChanged e IBindingList

2/23/2006 12:31 PM | Technology Experience
Gravatar

# re: (Parte 1) Le interfacce INotifyPropertyChanged e IBindingList

Per Lorenzo:

Il binding di controlli ad un business object IMHO non è un problema e non è neanche detto che mettere la validazione sul layer di presentazione sia "la" soluzione. anzi...

La validazione "è" logica di business, quindi deve stare nel business layer.

Esempio:
la UI binda la form con il business object, alla pressione di un tasto ok, chiamo il validator e gli passo il mio business object, mi verrà restituito true o false a seconda dei casi, o nelle situazioni più complesse oggetti notification (pattern di fowler, io uso questo meccanismo).
In questo modo anche la validazione sta sul business layer e non mi vedo costretto a replicarla su progetti web o interfacce su palmari.
2/23/2006 2:09 PM | Giancarlo Sudano
Gravatar

# re: (Parte 1) Le interfacce INotifyPropertyChanged e IBindingList

Ottimo post. Stavo lavorando alla stessa cosa per preparare un po' di materiale per le demo per i CommunityDays.
Ne parleremo ancora ;-)
2/24/2006 10:15 AM | Emanuele DelBono
Gravatar

# Re: (Parte 1) Le interfacce INotifyPropertyChanged e IBindingList

sarò anche volubile... ma Giancarlo mi ha convinto. Effettivamente lo stato presentation, in quanto tale, dovrebbe essere totalmente privo di business logic... chiaramente dipende anche molto dal contesto. Se mi obbligano a risparmiare roundtrip per motivi vari, secondo me, un minimo di validazione lato client male non fa... d'altronde non tutte le applicazioni devono avere 3 tipi di interfaccia diversa. E' chiaro che se, effettivamente non ho vincoli strettissimi o esigenze stringenti di scalabilità, una chiamata a un validatore con AJAX allo strato business è sicuramente la soluzione migliore.
2/24/2006 1:48 PM | Lorenzo Melato
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET