Mostrar un texto en un DataGrid sin elementos en WPF

marzo 26, 2010 10:54 by MarcosSanchez

En muchas ocasiones utilizamos un DataGrid para realizar búsquedas. En estos casos, nos puede interesar informar al usuario de que no se han encontrado registros tras una búsqueda, de una forma más explícita que por el mero hecho de que no contenga elementos. Para ello, el DataGrid puede mostrar un texto con el mensaje que se considere apropiado. Para conseguir esto con el DataGrid de WPF (WPF Toolkit), podemos modificar el estilo del control añadiendo un objeto TextBlock, cuya visibilidad dependerá de que el DataGrid contenta o no elementos. En el código XAML que se muestra a continuación, vemos resaltado en negrita las modificaciones necesarias para hacer esto:

XAML:

<ControlTemplate TargetType="{x:Type Controls:DataGrid}">
  <Border >

      <Grid>
          <TextBlock Name="PART_EmptyText" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Collapsed"/>
              <ScrollViewer Focusable="false" >
                    <ScrollViewer.Template> 
               …

                   </ScrollViewer.Template> 
                   <ItemsPresenter Name="itemsPresenter" /> 
             </ScrollViewer>        

        </Grid>
  </Border>
  <ControlTemplate.Triggers>
    <Trigger Property="HasItems" Value="False">
      <Setter TargetName="PART_EmptyText" Property="Visibility" Value="Visible"/>
      <Setter TargetName="itemsPresenter" Property="Visibility" Value="Collapsed"/>
    </Trigger>

  </ControlTemplate.Triggers>
</ControlTemplate>

Una vez que tenemos el TextBlock, nos faltaría poder asignarle un texto a mostrar cuando no haya elementos. Podemos hacerlo con una AttachedProperty, que en este caso hemos llamado EmptyText:

C#:

public static readonly DependencyProperty EmptyTextProperty = DependencyProperty.RegisterAttached("EmptyText", typeof(string), typeof(DataGridBehavior), new PropertyMetadata(OnEmptyTextChanged));

private static void OnEmptyTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    DataGrid dataGrid = (d as DataGrid);
    if (dataGrid != null)   dataGrid.Loaded += dataGrid_Loaded;
}

private static void dataGrid_Loaded(object sender, RoutedEventArgs e)
{
    DataGrid dataGrid = (sender as DataGrid);
    if (dataGrid != null)
    {
        dataGrid.ApplyTemplate();

        if (!string.IsNullOrEmpty(GetEmptyText(dataGrid)))
        {
             //Accedemos al elemento PART_EmptyText de la plantilla aplicada al DataGrid, y asignamos su propiedad Text
          TextBlock texto = dataGrid.GetChild("PART_EmptyText", true) as TextBlock;
          if (texto != null) texto.Text = GetEmptyText(dataGrid);

        }
    }
}

La forma de utilizarlo en un DataGrid sería la siguiente:

XAML:

<Controls:DataGrid Behaviors:DataGridBehavior.EmptyText="No se han encontrado elementos"         />

DataGridSinDatos


Actualmente calificado con 5.0 por 1 personas

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Utilizar la clase DateTime en XAML

enero 15, 2010 13:21 by marcossanchez

Para utilizar DateTime.Today o DateTime.Now en XAML se puede utilizar:

{x:Static sys:DateTime.Today}

{x:Static sys:DateTime.Now}

Hay que referenciar sys de la siguiente forma:

xmlns:sys="clr-namespace:System;assembly=mscorlib"

En el siguiente ejemplo se llama a un converter al que se le pasa la fecha de hoy y devuelve la de mañana en formato dd/MM

Header="{Binding Source={x:Static sys:DateTime.Today}, Converter={App:FechaMasDiasToDDMMConverter}, ConverterParameter=1}"


Sea el primero en calificar este post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Mejoras en los Converters: MarkupExtension y Propiedades

enero 15, 2010 13:04 by marcossanchez

En un control XAML complejo, solemos encontrarnos con el problema de tener que crear gran cantidad de Converters para enlazar correctamente propiedades de controles gráficos con propiedades del ViewModel asociado. Para ello debemos crear, además de la clase Converter correspondiente(código C#), un Recurso en el control XAML y referenciarnos a él en el Binding donde queramos usarlo. Ejemplo:

XAML:

<UserControl.Resources >

    <Resources:NombreConverter x:Key="NombreConverter"/>

</UserControl.Resources >

                        …

{Binding Path=Propiedad, Converter={StaticResource NombreConverter}}

Esta forma de asignar el Converter puede resultar tediosa cuando tenemos gran cantidad de ellos. Para solucionarlo, podemos hacer que la clase Converter que creamos, extienda de MarkupExtension. De esta forma, podremos referenciarnos a él directamente, de la forma:  

XAML:

{Binding Path=Propiedad, Converter={Converters:NombreConverter}}

Otra mejora para determinados Converters es la posibilidad de pasarle parámetros. Aunque al Converter se le puede asignar un ConverterParameter, a veces es necesario pasar varios parámetros, y/o nos interesa que estos parámetros tengan nombre y tipo. Para ello, podemos crear en la clase Converter las propiedades que necesitemos y, si queremos, asignarles valores por defecto en un constructor. Ejemplo:

Código:

public class NombreConverter : MarkupExtension, IValueConverter
{
    public NombreConverter()
    {
        Propiedad= valor;
    }

    public valor Propiedad { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        //Lógica de conversión (Puede accederse a Propiedad)
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }
}

XAML:

    Converter={Converters:NombreConverter Propiedad=valor}


Sea el primero en calificar este post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Microsoft e Intel anuncian Silverlight para Moblin

septiembre 26, 2009 11:34 by EugenioEstrada

Microsoft e Intel acaban de anunciar el desarrollo de una implementación especifica de Silverlight para el S.O. basado en Linux de Intel, Moblin. Dicho sistema operativo tiene un repositorio de aplicaciones similar al del iPhone, donde sería distribuída dicho paquete.

Ha llamado especialmente la atención este anuncio ya que ya existe una implementación de Silverlight para Linux, Moonlight, tras el amparo de Novell. Pero Microsoft e Intel han justificado una nueva implementación en que mientras que Novell, con Moonlight, su target son S.O. de escritorio. Microsoft seguirá desarrollando implementaciones especificas, como puede ser la de Moblin, la de Nokia, etc. Ya que está sería con la ayuda de Intel para conseguir una mejor experiencia en equipos basados en Atom.

Para más información:

Programa de desarrollo de Intel Atom: http://appdeveloper.intel.com/en-us/
Moblin: http://moblin.org/
Moonlight: http://www.mono-project.com/Moonlight
Noticia: http://www.theregister.co.uk/2009/09/24/silverlight_to_linux/


Sea el primero en calificar este post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Silverlight: Como cambiar el titulo de la pagina

mayo 25, 2009 16:56 by eugenioestrada

En los foros de Silverlight preguntaban como enlazar el título de una página a un control. El problema está en que Silverlight por defecto no tiene una propiedad que nos permita acceder directamente al título de la página. Esto es debido a que Silverlight no está concebido para una relación uno a uno (HTML-Silverlight) sino como una forma de complementar la página HTML. Pero en cambio si que propone un sistema de interacción entre ambos mundos.

Por lo tanto, para poder abordar dicho problema crearé una clase llamada PageUtil:

using System.Windows.Browser;

public class PageUtil
{
    public static string PageTitle
    {
        get
        {
            var result = "";
            var elements =
                HtmlPage.Document.GetElementsByTagName("Title");
            if (elements != null && elements.Count > 0)
            {
                var titleElement = (HtmlElement)elements[0];
                result = (string)titleElement.GetProperty("innerHTML");
            }
            return result;
        }
        set
        {
            HtmlPage.Document.SetProperty("title", value);
        }
    }
}

Una vez que ya tenemos la clase creada debemos crear el recurso, ya sea a nivel local en el UserControl o a nivel de aplicación (para una clase tan genérica yo recomiendo que sea a nivel de aplicación).

<MyProject:PageUtil x:Key="PageUtilDataSource" /> 

Y luego solamente tenemos que enlazar la propiedad al control queramos:

<TextBox DataContext="{Binding Source={StaticResource PageUtilDataSource}}"
         Text="{Binding Path=PageTitle, Mode=TwoWay}"
         Height="30"
         VerticalAlignment="Top" />

Recuerdo que a mayores de hacer el enlace (en nuestro caso en la propiedad Text) debemos establecerle el DataContext con el recurso.

Como siempre espero que os sea útil.

Follow me! http://twitter.com/eugenioestrada


Sea el primero en calificar este post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Silverlight: El sistema de ficheros y configuraciones

mayo 22, 2009 00:26 by eugenioestrada

Una de las grandes limitaciones que tiene Silverlight es el acceso a disco, esto es debido a su ejecución aislada dentro del navegador. Habréis leído que Silverlight 3 tendrá capacidades de ejecución offline y out-of-browser, el resultado será el mismo ya que funcionaría igual que si siguiera alojada en el navegador.

Por muchos motivos podemos querer persistir en local algunos datos que hemos generado o que nos hemos traído del servidor.

Para ello Silverlight nos propone el Isolated Storage (almacenamiento aislado) donde podemos guardar ficheros y crear una jerarquía de carpetas. Antes de seguir quería comentar donde se encuentran estos directorios en la máquina:

  • Windows Vista/7: C:\Users\<Usuario>\AppData\LocalLow\Microsoft\Silverlight\is
  • Windows XP: C:\Documents and Settings\<Usuario>\Local Settings\Application Data\Microsoft\Silverlight\is
  • Mac OS X: /Users/<Usuario>/Library/Application Support/Microsoft/Silverlight/is

Dentro de esas carpetas lo organiza por grupos, definiendo un grupo por dominio (definiendo dominio como nombre de dominio, puerto y protocolo). De tal forma que los siguientes serían grupos diferentes:

En cambio los siguientes serían el mismo grupo:

La importancia de conocer estos grupos es que a cada grupo el Isolated Storage le va a asignar una cuota de espacio en disco. Por defecto esta cuota está establecida en 1.0 MB ya que para pequeñas configuraciones y un uso “standard” del Isolated Storage sería suficiente. Pero aún así, previo ser aceptado por el usuario esta se puede aumentar a voluntad del programador.

Pero ya ha llegado el momento de usarla. Principalmente tenemos dos clases con las que debemos jugar IsolatedStorageFile y IsolatedStorageSettings. La primera es para todo lo que he dicho hasta ahora acceso a disco, ficheros, carpetas, etc. IsolatedStorageSettings, en cambio, nos sirve para guardar objetos binarios.

El funcionamiento de ambas es muy sencillo, lo vamos a ver mejor con un ejemplo:

var store = 
IsolatedStorageFile.GetUserStoreForApplication(); using (var stream = store.CreateFile("Fichero.txt")) using (var writer = new StreamWriter(stream)) { writer.WriteLine("1"); writer.WriteLine("2"); }

Con la función IsolatedStorageFile.GetUserStoreForSite() podríamos obtener el Store del grupo o sitio. Y tras ejecutar el código, ahí tenemos el fichero:

image 

El store tiene muchos métodos a mayores, para comprobar si existe un fichero, ver y modificar la cuota, leer ficheros, crear directorios, etc.

En cambio con el IsolatedStorageSettings es mucho más simple:

IsolatedStorageSettings.ApplicationSettings["Fondo"] =
Colors.Blue;

También tenemos el SiteSettings donde podremos cambiar todas las propiedades a nivel de sitio.

Silverlight es bastante potente a nivel de sistema de ficheros, está limitado a su vez por la isla en la que encuentra,pero que debido a la anatomía de las aplicaciones Silverlight no debería de ser eso un problema.

Los comentarios son bienvenidos ;-)

Follow me! http://twitter.com/eugenioestrada


Sea el primero en calificar este post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tutorial: Usando el Visual State Manager

mayo 16, 2009 14:03 by EugenioEstrada

image

Tras varios meses trabajando con Silverlight uno empieza a tener buenas práctivas y ver como es más productivo. Por la forma de programar con Silverlight nos obliga a manipular el layout de nuestra aplicación constantemente. Para ello tenemos tres opciones: programáticamente desde C#, con StoryBoards o usando el Visual State Manager (VSM a partir de ahora).

Para nuestro tutorial vamos a crear un control llamado AnimatedImage con un UserControl normal con un Image dentro.

<UserControl
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	x:Class="EugenioEstrada.AnimatedImage"
	Width="Auto" Height="Auto">
	<Grid x:Name="LayoutRoot">
		<Image x:Name="InternalImage">
		
		</Image>
	</Grid>
</UserControl>

Lo que queremos hacer con este control es sencillo, que al pasar el Mouse por encima de la imagen ésta se escale un 1,5. Y luego que al hacer Click (controlando el MouseDown y MouseUp del ratón) haga una animación. Para hacer las animaciones debemos definir los RenderTransform tanto del Grid como del Image:

<Grid x:Name="LayoutRoot" RenderTransformOrigin="0.5,0.5">
	<Grid.RenderTransform>
		<TransformGroup>
			<ScaleTransform/>
        		<RotateTransform/>
    			<SkewTransform/>
</TransformGroup> </Grid.RenderTransform> <Image x:Name="InternalImage" RenderTransformOrigin="0.5,0.5"> <Image.RenderTransform> <TransformGroup> <ScaleTransform/> </TransformGroup> </Image.RenderTransform> </Image> </Grid>

Sólo hemos definido el Scale, Rotate y Skew porque son los únicos que vamos a modificar luego. Y el RenderTransformOrigin define que el origen de las transformaciones será el punto central del elemento. Dentro del Grid vamos a definir el VSM y también debemos importar el namespace del VSM. Quedando así al final:

<UserControl
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:w="clr-namespace:System.Windows;assembly=System.Windows"
	x:Class="EugenioEstrada.AnimatedImage"
	Width="Auto" Height="Auto">
	<Grid x:Name="LayoutRoot" RenderTransformOrigin="0.5,0.5">
		<Grid.RenderTransform>
			<TransformGroup>
				<ScaleTransform/>
        			<RotateTransform/>
<SkewTransform/>
       </TransformGroup>
</Grid.RenderTransform>
        <w:VisualStateManager.VisualStateGroups>
<!-- A partir de ahora aquí -->
</w:VisualStateManager.VisualStateGroups>
        <Image x:Name="InternalImage" RenderTransformOrigin="0.5,0.5">
     <Image.RenderTransform>
        <TransformGroup>
        <ScaleTransform/>

        </TransformGroup>
        </Image.RenderTransform>
     </Image>
    </Grid>
</UserControl>

Ahora seguiremos dentro del VisualStateGroups, para hacernos una idea de como funciona el VSM  este define ciertos grupos de estados, por ejemplo para poder agrupar por estados que responden al mouse, estados comunes, etc. Sirve para agrupar estados de forma lógica reduciendo la combinatoria de éstos. Luego cada estado lo define un StoryBoard, que es el que establecerá el estado de la UI y luego se podrían definir transiciones entre estados para que no haya inconsistencias en el diseño. En nuestro caso solamente tendremos tresestados: Normal, MouseOver, MouseUp y todos los agruparemos en el VisualStateGroup que llamaremos CommonStates.

<w:VisualStateGroup x:Name="CommonStates">
	<w:VisualState x:Name="Normal"/>
	<w:VisualState x:Name="MouseOver"/>
	<w:VisualState x:Name="MouseUp"/>
</w:VisualStateGroup>

Para hacer esto recomiendo el uso de Microsoft Expression Blend (http://expression.microsoft.com) ya que es mucho más sencillo:

image

Empezaremos definiendo el estado Normal para verlo de ejemplo lo que hará es que desde un escalado del 150% pasará al 100% y esto se haría así:

<w:VisualState x:Name="Normal">
    <Storyboard>
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
            Storyboard.TargetName="InternalImage"
            Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
            <EasingDoubleKeyFrame KeyTime="00:00:00" Value="1.5"/>
            <EasingDoubleKeyFrame KeyTime="00:00:00.2000000" Value="1"/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
            Storyboard.TargetName="InternalImage"
            Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
            <EasingDoubleKeyFrame KeyTime="00:00:00" Value="1.5"/>
            <EasingDoubleKeyFrame KeyTime="00:00:00.2000000" Value="1"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</w:VisualState>

 

Lo más complicado es el entender el TargetProperty que es el siguiente:

Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"

No es un acceso intuitivo (del estilo InternalImage.ScaleTransform.ScaleY) esto se debe a como funcionan las DependencyProperties de WPF/Silverlight que nos dará tema para otro artículo. Pero para hacer una vista muy rápida se debe a que las DependencyProperties son métodos estáticos que se encargan de hacer el get y el set mediante eventos. Esto nos permite luego usar todas capacidades de Data Binding que tiene tanto WPF como Silverlight. De una forma muy parecida vamos a crear los otros dos estados:

<w:VisualState x:Name="MouseOver">
	<Storyboard>
		<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="InternalImage"
			Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
			<EasingDoubleKeyFrame KeyTime="00:00:00.2000000" Value="1.5"/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="InternalImage"
			Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
			<EasingDoubleKeyFrame KeyTime="00:00:00.2000000" Value="1.5"/>
		</DoubleAnimationUsingKeyFrames>
	</Storyboard>
</w:VisualState>
<w:VisualState x:Name="MouseUp">
	<Storyboard>
	    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="LayoutRoot"
			Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)">
			<EasingDoubleKeyFrame KeyTime="00:00:00.1000000" Value="30"/>
			<EasingDoubleKeyFrame KeyTime="00:00:00.2000000" Value="-30"/>
			<EasingDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0"/>
		</DoubleAnimationUsingKeyFrames>
	</Storyboard>
</w:VisualState>

Ahora llega el momento de escribir un poco de C#. Debemos manejar los eventos MouseMove, MouseLeave y MouseLeftButtonUp del InternalImage:

<Image x:Name="InternalImage"
	RenderTransformOrigin="0.5,0.5"
MouseMove="InternalImage_MouseMove"
MouseLeftButtonUp="InternalImage_MouseLeftButtonUp"
MouseLeave="InternalImage_MouseLeave">

 

Y la clase AnimatedImage queda de la siguiente forma con los manejadores de eventos:

using System.Windows;
using System.Windows.Controls;

namespace EugenioEstrada
{
	public partial class AnimatedImage : UserControl
	{
		public AnimatedImage()
		{
			InitializeComponent();
			VisualStateManager.GoToState(this, "Normal", false);
		}

		private void InternalImage_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
		{
			VisualStateManager.GoToState(this, "MouseOver", false);
		}

		private void InternalImage_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
		{
			VisualStateManager.GoToState(this, "MouseUp", false);
		}

		private void InternalImage_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
		{
			VisualStateManager.GoToState(this, "Normal", false);
		}
	}
}

VisualStateManager es una clase estática y la firma de su método GoToState es la siguiente:

public static bool GoToState(Control control, string stateName, bool useTransitions);

El parámetro control es aquel objeto que queremos cambiarle el estado, el stateName es el nombre del estado al que queremos cambiar y el último parámetro, useTransitions, se usa para establecer si se usarán las transiciones de estado o no, en nuestro caso como no hemos definido ninguna lo establecemos en false.

Y finalmente el resultado es el siguiente:

Tras este artículo se nos abren varias opciones de publicación como Dependency Properties, Data Binding, etc.

 http://twitter.com/eugenioestrada (tweet me!)


Actualmente calificado con 5.0 por 1 personas

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Ejemplos avanzados de Binding

mayo 15, 2009 13:54 by marcossanchez

La extensión de marcado Binding en XAML es muy configurable, de forma que podemos usarlo de multitud de maneras para conseguir enlazar una propiedad de un control con cualquier otro elemento o propiedad que queramos. A continuación veremos algunos ejemplos:

Enlace entre controles:

      A) Directamente:

<Slider x:Name="slider" Minimum="0" Maximum="300" Value="50" />
<Rectangle Width="{Binding Path=Value, ElementName=slider}" Height="100" Fill="Red" />

 

     B) Buscando a través del árbol visual del elemento:

Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}"

Utilizando Converters:

Height="{Binding ElementName=txtCategoria1, Path=Text, Converter={StaticResource StringToHeight}} 

Con el Converter StringToHeight podemos convertir una cadena de caracteres en un entero que se aplicará a la propiedad Height del control. La regla de conversión la define el programador.

Enlace a un elemento de una colección:

<TextBlock Text="{Binding Path=Collection[index].PropertyName}"/>

 

Un ejemplo final que combina alguno de los anteriores. Se usa para asignar la propiedad Tooltip de un elemento (por ejemplo un TextBox) cuando la validación del elemento falla:

<Trigger Property="Validation.HasError" Value="True">
    <Setter Property="ToolTip" Value="{Binding Path=(Validation.Errors)[0].ErrorContent, RelativeSource={x:Static RelativeSource.Self}}" />
</Trigger>


Sea el primero en calificar este post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Vistas de colecciones: agrupando elementos de un ListBox

mayo 8, 2009 11:59 by marcossanchez

Con este ejemplo se pretende ilustrar las funcionalidades de las vistas de una colección de datos. Todas las colecciones en WPF tienen un objeto CollectionView predeterminado. WPF siempre enlaza a una vista en lugar de a una colección. Si enlaza directamente a una colección, WPF realmente establece el enlace con la vista predeterminada de esa colección. En este ejemplo, se crea una colección de objetos de tipo Elemento, cuya definición es:

public class Elemento
{
    public string Grupo { get; set; }
    public string Nombre { get; set; }
    public string PathFoto { get; set; }
}

Las vistas permiten ver la misma colección de datos de maneras diferentes, según los criterios de ordenación, filtrado o agrupación. Cada colección tiene una vista predeterminada compartida, que se utiliza como origen cuando se enlaza directamente una colección. Se puede obtener la vista predeterminada de una colección utilizando el método GetDefaultView de la clase CollectionViewSource. A continuación se muestra el código en el que se enlaza una colección de objetos Elemento a un ListBox y se configura su vista para la agrupación por el campo Grupo:

public UCListBoxAgrupado()
{
    InitializeComponent(); 
    ObservableCollection<Elemento> Items = new ObservableCollection<Elemento>()

                     {
                        //Rellenamos la colección con objetos de tipo Elemento 
                     };
    listBoxAgrupado.ItemsSource = Items;

    ICollectionView view = CollectionViewSource.GetDefaultView(listBoxAgrupado.ItemsSource);
    view.GroupDescriptions.Add(new PropertyGroupDescription("Grupo"));
    }

En XAML, definimos el estilo que tendrán las cabeceras de los grupos, añadiendo el nombre del grupo con un Binding al campo Grupo del primer elemento del grupo:

<ListBox x:Name="listBoxAgrupado">
    <ListBox.GroupStyle>
        <GroupStyle HeaderTemplate="{StaticResource GroupTemplate}"/>
    </ListBox.GroupStyle>
</ListBox>

<!-- DataTemplate para las cabeceras de grupo del ListBox -->

<DataTemplate x:Key="GroupTemplate">
    <Border Height="30" CornerRadius="4" Margin="5">
        <TextBlock Text="{Binding Path=Items[0].Grupo}" HorizontalAlignment="Left"/>
    </Border>
</DataTemplate>

El resultado final tiene la siguiente apariencia:

 


Sea el primero en calificar este post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Attached Properties

abril 30, 2009 16:20 by marcossanchez

Una Attached Property (propiedad asociada) es un concepto definido por XAML. Las propiedades asociadas están destinadas a utilizarse como un tipo de propiedad global configurable en cualquier objeto. En WPF, las propiedades asociadas se definen habitualmente como una forma especializada de propiedad de dependencia que no tiene el "contenedor" de propiedad convencional.

La mayoría de las propiedades asociadas que existen en tipos WPF se implementan como Dependency Properties (propiedades de dependencia). El escenario más típico donde WPF define una propiedad asociada es cuando un elemento primario admite una colección de elementos secundarios, y también implementa un comportamiento donde las características del comportamiento se indican individualmente para cada elemento secundario. Por ejemplo, la propiedad Grid.Row o Grid.Column se definen en cualquier objeto secundario de un Grid para establecer su posición en el mismo.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="0.5*"/>
        <ColumnDefinition Width="0.5*"/>
    </Grid.ColumnDefinitions>
    <ComboBox Grid.Column="1" Width="Auto" Height="26"/>
</Grid>

En el ejemplo que se propone en este post, se utilizarán las propiedades asociadas con otro fin:

El tipo que define la propiedad asociada se ha diseñado de modo que pueda manejar o administrar los objetos que establecen los valores de la propiedad asociada. El tipo tiene acceso de esta forma al objeto, obtiene los valores de sus propiedades y actúa sobre esos valores.

Para este fin se ha diseñado una clase llamada TextBoxBehavior, en la que definimos propiedades asociadas como Mask (máscara de entrada), WatermarkText (marca de agua) y otras. Un ejemplo de declaración sería:

public static readonly DependencyProperty WatermarkTextProperty = DependencyProperty.RegisterAttached("WatermarkText", typeof(string),typeof(TextBoxBehavior), new FrameworkPropertyMetadata(WaterMarkTextChangedCallback));

private static void WaterMarkTextChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    TextBox _this = (d as TextBox);
    if (_this != null)
  textBox.Loaded += textBox_Loaded;
}

Podemos ver que en el evento de cambio de la propiedad asociada, aprovechamos para suscribirnos al manejador del evento Loaded del TextBox. De esta forma, conseguimos añadir funcionalidad al mismo sin tener que derivar el control. En el evento Loaded del TextBox asignamos el valor de la propiedad asociada WaterMarkText a la propiedad Text de un objeto TextBlock que forma parte de la plantilla definida para nuestros objetos TextBox:

static void textBox_Loaded(object sender, RoutedEventArgs e)
{
    TextBox textBox = (sender as TextBox);
    if (textBox != null)
    {
        if (GetWatermarkText(textBox) != string.Empty)
        {
       //Accedemos al textBlock contenido en el Template del TextBox, y asignamos su propiedad Text
            DependencyObject dep=VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(textBox, 0), 1);
            ((TextBlock)dep).Text = GetWatermarkText(textBox);

        }
    }
}

La definición de plantilla del TextBox, nos muestra el control TextBlock en su interior y cuando debemos mostrarlo u ocultarlo:

<!--  Estilo de TEXTBOX por defecto-->
<Style TargetType="{x:Type TextBox}">
    <Setter Property="FontSize" Value="12" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBox}">
                <Grid>
                    <ScrollViewer x:Name="PART_ContentHost" Background="White" Height="23" />
                    <TextBlock x:Name="textBlock" Height="20" Margin="5" VerticalAlignment="Center" FontSize="14" Opacity="0.4" Visibility="Hidden" />
                </Grid>
                <ControlTemplate.Triggers>
                            <Trigger Property="Text" Value="">
                                   <Setter Property="Visibility" TargetName="textBlock" Value="Visible" />
                            </Trigger>

                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

De la misma forma que hemos implementado esta funcionalidad, podríamos hacer cosas mucho más complejas añadiendo más propiedades asociadas y/o suscribiéndonos a otros eventos del objeto que queramos administrar.


Sea el primero en calificar este post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5