Home > General > DataGrid de Silverlight 3 que agrega nuevas filas

DataGrid de Silverlight 3 que agrega nuevas filas

noviembre 1, 2009

El DataGrid de Silverlight 3 es una contradicción: Por un lado es mucho más flexible que el de Windows Forms porque es muy fácil agregar cualquier control que queramos a una columna (Solo se agrega el control a la propiedad DataGridTemplateColumn) pero por otro es tan primitivo que no permite que el usuario agregue nuevas filas a la colección.

Para un proyecto que estoy realizando, agregar filas al DataGrid es una funcionalidad básica así que me puse a buscar la solución a este problema. La respuesta que dan por todos lados es agregar un elemento adicional a la colección y eliminarlo si el usuario lo dejó en blanco (Y prepararse para distinguir entre un objeto nuevo y uno modificado).

En mi caso, era una molestia agregar ese elemento en blanco ya que esta funcionalidad se necesitaba en muchos lugares. Así que, en vez de repetir esa lógica por cada DataGrid, decidí buscar la forma de obtener el mismo resultado sin tener que modificar la colección original. La solución a la que llegué me gustó tanto que decidí compartirla en este blog.

Este es el resultado:

Primero un micro-repaso del uso del DataGrid.

Vamos a crear un nuevo proyecto Silverlight con el nombre DataGridAddRow y le decimos que no cree un proyecto Web ya que no lo necesitamos para probar. Abrimos MainPage.xaml y agregamos un DataGrid:

<Grid x:Name="LayoutRoot" Margin="20">
    <data:DataGrid x:Name="m_dgMovies">
    </data:DataGrid>
</Grid>

En MainPage.xaml.cs está el código de la página. En el constructor le pasamos los datos al DataGrid. En este caso es una lista que estoy creando en el método ListOfPixarMovies() pero bien pueden ser datos que salen de un servicio web o algo así.

public partial class MainPage : UserControl
{
	public MainPage()
	{
		InitializeComponent();

		m_dgMovies.ItemsSource = ListOfPixarMovies();
	}

	private List<Movie> ListOfPixarMovies()
	{
		return new List<Movie>
		{
			new Movie { Name = "The Incredibles", Year = 2004 },
			new Movie { Name = "Chicken Little", Year = 2005 },
			new Movie { Name = "Ratatouille", Year = 2007 },
			new Movie { Name = "WALL·E", Year = 2008 },
			new Movie { Name = "Up", Year = 2009 },
		};
	}
}

Y, por supuesto, tenemos que crear la clase Movie. El único requerimiento de esta solución es que la clase debe implementar INotifyPropertyChanged pero si uno está utilizando data binding para editar un objeto normalmente termina implementando INotifyPropertyChanged también. Esta es la clase que utilicé en el ejemplo:

public class Movie
		: System.ComponentModel.INotifyPropertyChanged
{
	string m_name;

	public string Name
	{
		get { return m_name; }
		set
		{
			m_name = value;
			OnPropertyChanged(new PropertyChangedEventArgs("Name"));
		}
	}

	int m_year;

	public int Year
	{
		get { return m_year; }
		set
		{
			m_year = value;
			OnPropertyChanged(new PropertyChangedEventArgs("Year"));
		}
	}

	public event PropertyChangedEventHandler PropertyChanged;

	protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
	{
		if (PropertyChanged != null)
			PropertyChanged(this, e);
	}
}

Nada del otro mundo: Una clase con dos propiedades y cada vez que se modifican esas propiedades ejecutamos el evento PropertyChanged que requiere la interfaz INotifyPropertyChanged.

En este punto podemos ejecutar el proyecto y nos encontramos con un DataGrid con varias filas y podemos editar los datos pero no podemos agregar nuevas filas, como este:

La solución: AppendableCollection

Para permitirle al usuario agregar nuevas filas todo lo que tenemos que hacer es agregar la clase AppendableCollection y en el constructor de la página modificamos la línea en la que pasábamos la lista de películas para envolverla con una instancia de AppendableCollection<Movie>:

public MainPage()
{
	InitializeComponent();

	//m_dgMovies.ItemsSource = ListOfPixarMovies();
	m_dgMovies.ItemsSource = new AppendableCollection<Movie>(ListOfPixarMovies());
}

El resultado es una fila vacía al final de los datos y el usuario ahora puede agregar nuevas filas editando esa fila vacía. Por lo menos hasta que salga una nueva versión del DataGrid con soporte para agregar nuevas filas, esta es probablemente la solución más sencillas para simular esta funcionalidad.

Realmente eso es todo lo que que hay que hacer: Agregar la clase, implementar INotifyPropertyChanged y envolver la colección en AppendableCollection. Ahora puede saltar hasta el final del artículo y descargar el código. Para los tres que se quedaron, así es como funciona:

El DataGrid solo requiere que le pasemos un IEnumerable entonces AppendableCollection necesita implementar esa interfaz. En el método GetEnumerator() lo que hacemos es devolver el Enumerator de la colección original pero al final le agregamos la nueva instancia:

public class AppendableCollection<T>
	: System.Collections.Generic.IEnumerable<T>
	where T : System.ComponentModel.INotifyPropertyChanged, new()
{
	public IEnumerator<T> GetEnumerator()
	{
		return m_collection.Union(m_newItem).GetEnumerator();
	}

	readonly T[] m_newItem;
...
}

Solo nos falta detectar cuando el usuario ha modificado el objeto. Por eso la clase de la colección debe implementar INotifyPropertyChanged. Cuando recibimos el evento de que una propiedad ha sido modificada agregamos la instancia actual a la colección original y creamos una nueva:

void newItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
	m_newItem[0].PropertyChanged -= newItem_PropertyChanged;
	m_collection.Add(m_newItem[0]);
	m_newItem[0] = new T();
	m_newItem[0].PropertyChanged += newItem_PropertyChanged;
}

Solo nos falta avisarle al DataGrid que agregamos un nuevo elemento. Para eso implementamos la interfaz INotifyCollectionChanged y ejecutamos el evento CollectionChanged:

public class AppendableCollection<T>
	: System.Collections.Generic.IEnumerable<T>
	, System.Collections.Specialized.INotifyCollectionChanged
	where T : System.ComponentModel.INotifyPropertyChanged, new()
{
	public event NotifyCollectionChangedEventHandler CollectionChanged;

	protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
	{
		if (CollectionChanged != null)
			CollectionChanged(this, e);
	}

	void newItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
	{
		m_newItem[0].PropertyChanged -= newItem_PropertyChanged;
		m_collection.Add(m_newItem[0]);
		m_newItem[0] = new T();
		m_newItem[0].PropertyChanged += newItem_PropertyChanged;
		OnCollectionChanged(new NotifyCollectionChangedEventArgs(
			NotifyCollectionChangedAction.Add, m_newItem[0],
			m_collection.Count));
	}
...
}

Descargar la clase y el ejemplo

El código de la clase está bajo licencia MIT y puede descargarse desde aquí: DataGridAddRow.zip – Agregar nuevas filas a un DataGrid de Silverlight 3.

Categories: General