sábado, 14 de abril de 2012

Agregar controles dinámicamente a página ya diseñada

 

Hoy quiero compartir, algunos temas que aprendí y que pueden servirles de ayuda tanto como a mí.

La necesidad que tenía era colocar un control de forma dinámica a una página ya construida, con el propósito de dar avisos, visualmente, dada la información ingresada en ciertos controles enlazados a propiedades dadas (Vale la pena decir, que se usaba el patrón MVVM).

El primer tema a resolver era obtener el control asociado a la propiedad que cambiaba en el ViewModel, sin embargo, el patrón MVVM se creo con el propósito o idea de que el ViewModel no conociera los detalles asociados a la vista que se le asocia, para de esta forma garantizar el desacople necesario para el patrón.

Lo que se requería, entonces, era:

Dado el código:

 <TextBox x:Name="NombreTextBox" Text="{Binding Path=Persona.Nombre,Mode=TwoWay}" Grid.Column="1"></TextBox>

Con la propiedad Nombre, poder obtener que el nombre del control, en este caso NombreTextBox.


Por tanto, toda la información requerida para resolver la primera parte del problema se encontraba en la propia página XAML. Por tanto, necesitaba una forma para leer la información o el XAML asociado a una página dada y la encontré.


Se usa la clase XDocument de System.Xml.Linq, para cargar la información de la ruta donde se encuentra el XAML asociado a la página.


Así:

public VerXAML()
{
InitializeComponent();
}

private void ObtenerXAMLButton_Click(object sender, RoutedEventArgs e)
{
XDocument doc = XDocument.Load("/PruebasSilverligth;component/Blog/VerXAML.xaml");
ContenidoXAMLTextBox.Text = doc.ToString();
}

Fijarse como al método Load, se le indica la ruta donde se halla la página XAML, con la siguiente sintaxis:


“/<Nombre_Proyecto_Silverlight>;component/<Ruta_Completa_PaginaXAML>”


No obstante, puedes acceder de forma rápida para obtener la ruta de tu página, si en el constructor de la misma te ubicas sobre la llamada a InicializeComponent() y les das F12. Esto te lleva a un código auto generado, de donde puedes tomar el URL asociado a tu página:


AddCtrlDinamico1


AddCtrlDinamico2



Este código tan simple, te permite obtener el código XAML de tu página, como se ilustra a continuación:


AddCtrlDinamico3


Ahora era cuestión de buscar en el código XAML de la página, el control enlazado a una propiedad determinada.


Eso se logró con el siguiente método:

/// <summary>
///
Obtiene el nombre del control al que se le hace Binding de una propiedad determinada
/// </summary>
/// <param name="rutaPagina">
Ruta del XAML asociado a la página</param>
/// <param name="propiedad">
Es la propiedad del ViewModel asociado a la página a la que
/// se le hace Binding</param>
/// <returns>
El nombre del control</returns>
public static string ObtenerNombreControlAsociadoAPropiedad(string rutaPagina, string propiedad)
{
XDocument doc = XDocument.Load(rutaPagina);

XElement control = (from c in doc.Descendants()
where c.Attributes().Any(a => a.Value.Contains("Binding") && a.Value.Contains(propiedad))
select c).FirstOrDefault();
string nombreControl = (from a in control.Attributes()
where a.Name.ToString().Contains("Name")
select a.Value).FirstOrDefault();
return nombreControl;

}

Como puedes adivinar, el prerrequisito para que esto funcione es que los controles enlazados a propiedades tengan su respectivo nombre.


Una vez se tiene el nombre del control enlazado a la propiedad, se procede a buscarlo en la jerarquía de controles de la página, para colocar el control que requieras al lado de éste ( en mi caso, el control para la presentación de avisos). También, puedes eliminar el control que agregaste, cuando ya no lo requieras. Esto se logró con los siguientes métodos:

/// <summary>
///
Agrega un control dado al lado de un control de nombre dado que se encuentra colocado en un Panel Grid
/// </summary>
/// <param name="nombreControl">
Nombre del control al lado del cual se agregará el control dado</param>
/// <param name="elementoRaiz">
Nombre del Panel raiz que contiene la jerarquía de controles</param>
/// <param name="elementoAAgregar">
Control a agregar</param>
public static void AgregarControlToVisual(string nombreControl, UIElement elementoRaiz, UIElement elementoAAgregar)
{
Panel panel = elementoRaiz as Panel;
foreach (UIElement child in panel.Children)
{
if (child is Grid)
{
foreach (UIElement element in ((Panel)child).Children)
{
if (((FrameworkElement)element).Name.Equals(nombreControl))
{
Grid gridPadre = child as Grid;
if (gridPadre.Children.Contains(elementoAAgregar))
{
return;
}
gridPadre.ColumnDefinitions.Add(new ColumnDefinition());

Grid.SetRow((FrameworkElement)elementoAAgregar, Grid.GetRow((FrameworkElement)element));
Grid.SetColumn((FrameworkElement)elementoAAgregar, gridPadre.ColumnDefinitions.Count - 1);
gridPadre.Children.Add(elementoAAgregar);
return;
}
}
}
}
}
/// <summary>
///
Elimina un control dado al lado del control del nombre indicado que se encuentra en un Panel Grid
/// </summary>
/// <param name="nombreControl">
Nombre del control al lado del cual se encuentra el control a eliminar</param>
/// <param name="elementoRaiz">
Nombre del Panel raiz que contiene la jerarquía de controles</param>
/// <param name="elementoAEliminar">
Control a eliminar</param>
public static void EliminarControlFromVisual(string nombreControl, UIElement elementoRaiz, UIElement elementoAEliminar)
{
Panel panel = elementoRaiz as Panel;
foreach (UIElement child in panel.Children)
{
if (child is Grid)
{
foreach (UIElement element in ((Panel)child).Children)
{
if (((FrameworkElement)element).Name.Equals(nombreControl))
{
Grid gridPadre = child as Grid;
if (!gridPadre.Children.Contains(elementoAEliminar))
{
return;
}
gridPadre.Children.Remove(elementoAEliminar);
gridPadre.ColumnDefinitions.RemoveAt(gridPadre.ColumnDefinitions.Count - 1);
return;
}
}
}
}
}



Nota: Para la necesidad que tenía, los controles enlazados a las propiedades estaban ubicados, con la siguiente disposición:


Panel principal


…Grid


……..Control enlazado a propiedad


….Fin Grid


Fin Panel principal


Y para estructuras de organización de controles similares, funcionarán los métodos suministrados.


Ejemplo:


XAML:

<UserControl x:Class="PruebasSilverligth.Blog.PruebaHallarControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<
StackPanel x:Name="LayoutRoot" >
<
Grid x:Name="FormularioGrid" Background="White">
<
Grid.RowDefinitions>
<
RowDefinition Height="30"></RowDefinition>
<
RowDefinition Height="30"></RowDefinition>
<
RowDefinition Height="30"></RowDefinition>
<
RowDefinition Height="30"></RowDefinition>
<
RowDefinition Height="30"></RowDefinition>
<
RowDefinition Height="30"></RowDefinition>
</
Grid.RowDefinitions>
<
Grid.ColumnDefinitions>
<
ColumnDefinition></ColumnDefinition>
<
ColumnDefinition></ColumnDefinition>
</
Grid.ColumnDefinitions>
<
TextBlock x:Name="NombreTextBlock" Text="Nombre"></TextBlock>
<
TextBox x:Name="NombreTextBox" Text="{Binding Path=Persona.Nombre,Mode=TwoWay}" Grid.Column="1"></TextBox>
<
TextBlock x:Name="ApellidoTextBlock" Text="Apellido" Grid.Row="1"></TextBlock>
<
TextBox x:Name="ApellidoTextBox" Text="{Binding Path=Persona.Apellido,Mode=TwoWay}" Grid.Row="1" Grid.Column="1"></TextBox>
<
TextBlock x:Name="EdadTextBlock" Text="Edad" Grid.Row="2"></TextBlock>
<
TextBox x:Name="EdadTextBox" Text="{Binding Path=Persona.Edad,Mode=TwoWay}" Grid.Row="2" Grid.Column="1"></TextBox>
<
Button x:Name="GrabarButton" Content="Grabar" Grid.Row="3" Grid.ColumnSpan="2" Command="{Binding SaveCommand}"></Button>
<
Button x:Name="AgregarControlButton" Content="Agregar control" Grid.Row="4" Grid.ColumnSpan="2" Click="AgregarControlButton_Click"></Button>
<
Button x:Name="EliminarControlButton" Content="Eliminar control" Grid.Row="5" Grid.ColumnSpan="2" Click="EliminarControlButton_Click"></Button>
</
Grid>
</
StackPanel>
</
UserControl>


Code Behind:

public partial class PruebaHallarControl : UserControl
{
Button _botonAAgregar;
string _nombreControlAsoPropiedad;
public PruebaHallarControl()
{
InitializeComponent();
PruebaHallarControlViewModel viewModel = new PruebaHallarControlViewModel();
this.DataContext = viewModel;
_botonAAgregar = new Button(){Content="Botón agregado"};
_nombreControlAsoPropiedad = ObtenerNombreControlAsociadoAPropiedad("/PruebasSilverligth;component/Blog/PruebaHallarControl.xaml",
"Edad");
}

/// <summary>
///
Obtiene el nombre del control al que se le hace Binding de una propiedad determinada
/// </summary>
/// <param name="rutaPagina">
Ruta del XAML asociado a la página</param>
/// <param name="propiedad">
Es la propiedad del ViewModel asociado a la página a la que
/// se le hace Binding</param>
/// <returns>
El nombre del control</returns>
public static string ObtenerNombreControlAsociadoAPropiedad(string rutaPagina, string propiedad)
{
XDocument doc = XDocument.Load(rutaPagina);

XElement control = (from c in doc.Descendants()
where c.Attributes().Any(a => a.Value.Contains("Binding") && a.Value.Contains(propiedad))
select c).FirstOrDefault();
string nombreControl = (from a in control.Attributes()
where a.Name.ToString().Contains("Name")
select a.Value).FirstOrDefault();
return nombreControl;

}
/// <summary>
///
Agrega un control dado al lado de un control de nombre dado que se encuentra colocado en un Panel Grid
/// </summary>
/// <param name="nombreControl">
Nombre del control al lado del cual se agregará el control dado</param>
/// <param name="elementoRaiz">
Nombre del Panel raiz que contiene la jerarquía de controles</param>
/// <param name="elementoAAgregar">
Control a agregar</param>
public static void AgregarControlToVisual(string nombreControl, UIElement elementoRaiz, UIElement elementoAAgregar)
{
Panel panel = elementoRaiz as Panel;
foreach (UIElement child in panel.Children)
{
if (child is Grid)
{
foreach (UIElement element in ((Panel)child).Children)
{
if (((FrameworkElement)element).Name.Equals(nombreControl))
{
Grid gridPadre = child as Grid;
if (gridPadre.Children.Contains(elementoAAgregar))
{
return;
}
gridPadre.ColumnDefinitions.Add(new ColumnDefinition());

Grid.SetRow((FrameworkElement)elementoAAgregar, Grid.GetRow((FrameworkElement)element));
Grid.SetColumn((FrameworkElement)elementoAAgregar, gridPadre.ColumnDefinitions.Count - 1);
gridPadre.Children.Add(elementoAAgregar);
return;
}
}
}
}
}
/// <summary>
///
Elimina un control dado al lado del control del nombre indicado que se encuentra en un Panel Grid
/// </summary>
/// <param name="nombreControl">
Nombre del control al lado del cual se encuentra el control a eliminar</param>
/// <param name="elementoRaiz">
Nombre del Panel raiz que contiene la jerarquía de controles</param>
/// <param name="elementoAEliminar">
Control a eliminar</param>
public static void EliminarControlFromVisual(string nombreControl, UIElement elementoRaiz, UIElement elementoAEliminar)
{
Panel panel = elementoRaiz as Panel;
foreach (UIElement child in panel.Children)
{
if (child is Grid)
{
foreach (UIElement element in ((Panel)child).Children)
{
if (((FrameworkElement)element).Name.Equals(nombreControl))
{
Grid gridPadre = child as Grid;
if (!gridPadre.Children.Contains(elementoAEliminar))
{
return;
}
gridPadre.Children.Remove(elementoAEliminar);
gridPadre.ColumnDefinitions.RemoveAt(gridPadre.ColumnDefinitions.Count - 1);
return;
}
}
}
}
}

private void AgregarControlButton_Click(object sender, RoutedEventArgs e)
{

AgregarControlToVisual(_nombreControlAsoPropiedad, LayoutRoot, _botonAAgregar);
}

private void EliminarControlButton_Click(object sender, RoutedEventArgs e)
{
EliminarControlFromVisual(_nombreControlAsoPropiedad, LayoutRoot, _botonAAgregar);
}

}


Y se obtiene esta funcionalidad:


AddCtrlDinamico4


Espero que les sirva.

domingo, 1 de abril de 2012

Controles que muestran colecciones de objetos – ItemsControl

Los controles que muestran colecciones de objetos son indispensables en nuestras aplicaciones. Silverlight incluye, entre otros, los siguientes: ComboBox, ListBox, TabControl y probablemente el más importante de todos, la DataGrid. La DataGrid puede ser utilizada para mostrar los datos en una representación similar a Excel.

En esta serie de cápsulas entraremos a estudiar este tipo de controles e iniciaremos con el ItemsControl.

El ItemsControl es un control genérico para mostrar lista de datos.

Se puede utilizar para presentar un conjunto fijo de elementos o para mostrar una lista obtenida del enlace de datos a un objeto.

El ItemsControl permite definir una plantilla para definir el panel que se usará para los items (StackPanel, Canvas, Grid, etc.) a través de la propiedad ItemsPanel donde se define el ItemsPanelTemplate, así como una plantilla para definir la apariencia con que se presentarán los datos, usando la propiedad ItemTemplate, en donde se define el DataTemplate.

Si deseas conocer más información acerca de este control visita:

http://msdn.microsoft.com/es-es/library/system.windows.controls.itemscontrol(VS.95).aspx

Si tienen comentarios, sugerencias o preguntas. Favor, realizarlas, estaré atento para colaborarles en lo que pueda.