AntonioGanci

Il blog di Antonio Ganci
posts - 201, comments - 420, trackbacks - 31

Binding di dati ad una lista sviluppato utilizzando il TDD parte seconda

Leggi la prima parte qui

Scelgo le ultime due colonne da cui partire per la valorizzazione in quanto sono le più semplici, infatti, ho un valore per ogni titolarità.

Abbiamo bisogno di:

  • Creare le colonne Rendita e Valore
  • Mappare le colonne con i campi RenditaCatastale e ValoreFabbricato della classe Titolarita
  • Formattare il numero come 1.000,00
  • Aggiungere una riga per ogni Titolarita

Partiamo dalla grid. L'implementazione attuale è la seguente e non fa ancora nulla:

    public class Grid
    {
        public Cell this[int row, int col] { get { return null; } }
        public List<ColumnHeader> ColumnHeaders { get { return null; } }
    }

Come griglia userò una ListView alla quale dobbiamo impostare alcune property per farla funzionare come una grid:

  • La prima colonna la facciamo invisibile in quanto si comporta diversamente dalle altre come documentato nelle msdn qui. In pratica viene ignorato il text align nella prima colonna.
  • Dobbiamo settare la DetailsView
  • Dobbiamo visualizzare le righe
  • La selezione deve coprire tutte le celle di una riga
  • dobbiamo poter aggiungere colonne e celle

Ecco il test per l'inizializzazione della grid:

[Test]
public void ShouldInitializeListView()
{
    ListView listView = new ListView();
    Grid grid = new Grid(listView);
 
    grid.Initialize();
 
    Assert.That(listView.Columns[0].Width, Is.EqualTo(0));
    Assert.That(listView.View, Is.EqualTo(View.Details));
    Assert.That(listView.FullRowSelect, Is.True);
    Assert.That(listView.GridLines, Is.True);
}

Farlo passare è banale:

public void Initialize()
{
    m_listView.View = View.Details;
    m_listView.Columns.Add("").Width = 0;
    m_listView.FullRowSelect = true;
    m_listView.GridLines = true;
}

Aggiungiamo la possibilità di creare colonne:

        [Test]
        public void ShouldAppendColumnToListView()
        {
            m_grid.Initialize();
            m_grid.AppendColumn("title");
 
            Assert.That(m_listView.Columns[1].Text, Is.EqualTo("title"));
        }

Aggiungiamo una riga vuota:

        [Test]
        public void ShouldAppendRowWithoutCells()
        {
            m_grid.Initialize();
 
            m_grid.AppendEmptyRow();
 
            Assert.That(m_listView.Items.Count, Is.EqualTo(1));
        }

Ed ora una cella:

        [Test]
        public void ShouldAppendCellToTheLastRow()
        {
            m_grid.Initialize();
            m_grid.AppendEmptyRow();
 
            m_grid.AppendCell("some text");
 
            Assert.That(m_listView.Items[0].SubItems[1].Text, Is.EqualTo("some text"));
        }

A questo punto abbiamo bisogno di un data component processor che aggiunga una riga per ogni titolarità:

        [Test]
        public void ShouldAppendRowToTheGrid()
        {
            ListView listView = new ListView();
            var processor = new RowAppenderComponentProcessor<Titolarita>(new Grid(listView));
 
            processor.Process(new Titolarita());
 
            Assert.That(listView.Items.Count, Is.EqualTo(1));
        }

Formattiamo il numero decimale:

        [Test]
        public void ShouldFomatValue()
        {
            Money money = new Money(1234.56M);
 
            string formatted = money.Format();
 
            Assert.That(formatted, Is.EqualTo("1.234,56"));
        }

Ci siamo quasi, manca ora un oggetto che prenda il valore del campo e lo formatti:

        [Test]
        public void ShouldAppendCellFormattingComponentValue()
        {
            ListView listView = new ListView();
            Grid grid = new Grid(listView);
            grid.AppendEmptyRow();
            var processor = new CellAppenderComponentProcessor<Titolarita, Money>(
                titolarita => new Money(titolarita.RenditaCatastale), 
                grid);
 
            processor.Process(new Titolarita {RenditaCatastale = 1000M});
 
            Assert.That(listView.Items[0].SubItems[1].Text, Is.EqualTo("1.000,00"));
        }

Di questo riporto anche l'implementazione perchè forse non risulta ovvia:

    public class CellAppenderComponentProcessor<TComponent, TData> : IDataComponentProcessor<TComponent>
    {
        private readonly Func<TComponent, TData> m_getValueFunc;
        private readonly Grid m_grid;
 
        public CellAppenderComponentProcessor(Func<TComponent, TData> func, Grid grid)
        {
            m_getValueFunc = func;
            m_grid = grid;
        }
 
        public void Process(TComponent dataComponet)
        {
            TData data = m_getValueFunc.Invoke(dataComponet);
            m_grid.AppendCell(data.ToString());
        }
    }

Ora è il momento di mettere tutti i pezzi insieme:

Grid grid = new Grid(m_fabbricatiListView);
grid.Initialize();
grid.AppendColumn("Rendita catastale");
grid.AppendColumn("Valore fabbricato");
var praticaProcessor = new OneToManyDataComponentRelation<Pratica, Fabbricato>(
    pratica => pratica.Fabbricati,
    new OneToManyDataComponentRelation<Fabbricato, Titolarita>(
        fabbricato => fabbricato.Titolarita,
        new MultiComponentProcessor<Titolarita>(
            new RowAppenderComponentProcessor<Titolarita>(grid),
            new CellAppenderComponentProcessor<Titolarita, Money>(
                titolarita => new Money(titolarita.RenditaCatastale), grid),
            new CellAppenderComponentProcessor<Titolarita, Money>(
                titolarita => new Money(titolarita.ValoreFabbricato), grid))));

Avendo la necessità eseguire più Processor ho creato la classe MultiComponentProcessor:

    public class MultiComponentProcessor<T> : IDataComponentProcessor<T>
    {
        private readonly IDataComponentProcessor<T>[] m_processors;
 
        public MultiComponentProcessor(params IDataComponentProcessor<T>[] processors)
        {
            m_processors = processors;
        }
 
        public void Process(T dataComponet)
        {
            foreach (var processor in m_processors)
            {
                processor.Process(dataComponet);
            }
        }
    }

La costruzione degli oggetti che ho scritto sopra non è forse leggibilissima, ma il problema si può risolvere facilmente creando un oggetto Builder con una fluent interface.

Print | posted on Wednesday, December 30, 2009 4:26 PM | Filed Under [ Extreme Programming ]

Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET