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

Deshabilitar el analizador de Resharper en un fichero o parte de él.

marzo 18, 2010 12:09 by marcossanchez

Resharper es una herramienta imprescindible en el desarrollo de aplicaciones con Visual Studio. Sin embargo, hay situaciones en las que tenemos archivos demasiado grandes y el analizador de Resharper puede tardar una eternidad en procesar el fichero, o dar incluso una OutOfMemoryException. Para solucionarlo, podemos:

  • Crear una Region con un nombre especial para que Resharper no analice su contenido
    • En el menú de opciones de Resharper, en la opción Settings, podemos usar como nombre de la Region uno de los que aparecen en la lista, o añadir un elemento nuevo.
  • Deshabilitar el analizador para ficheros concretos:
    • Podemos hacerlo pulsando [Ctrl+8] o en el menú de opciones de Resharper, en la opción Settings, boton Advanced…

Actualmente calificado con 3.0 por 1 personas

  • Currently 3/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

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

Extensiones de marcado en XAML

abril 30, 2009 12:13 by marcossanchez

Una extensión de marcado se puede utilizar para proporcionar un valor a un atributo. La extensión de marcado se identifica mediante llaves ({  }). El token de cadena que sigue inmediatamente a la llave de apertura identifica el tipo de extensión de marcado.

Las extensiones de marcado más comunes utilizadas en la programación de WPF son:

StaticResource proporciona un valor para una propiedad XAML sustituyendo el valor de un recurso ya definido. StaticResource no permite referencias adelantadas, es decir, la definicion de la referencia debe estar declarada ANTES de su utilización dentro del archivo XAML.

DynamicResource proporciona un valor para una propiedad XAML aplazando ese valor para que sea una referencia a un recurso en tiempo de ejecución. Una referencia de recurso dinámica fuerza una nueva búsqueda cada vez que se tiene acceso a este tipo de recurso.

Binding proporciona un valor enlazado a datos para una propiedad, según el contexto de datos que se aplique al elemento. Esta extensión de marcado es relativamente compleja, porque habilita una sintaxis fundamentalmente incluida en el propio código para especificar un enlace de datos.

RelativeSource proporciona información de origen para un objeto Binding que puede navegar por varias posibles relaciones en el árbol de elementos en tiempo de ejecución.

TemplateBinding permite que una plantilla de control utilice valores para propiedades con plantilla procedentes de propiedades definidas por el modelo de objetos de la clase que utilizará la plantilla.

Ejemplo de utilización:

<!-- DataTemplate para elementos de tipo VTablaValidacion de un comboBox -->

<DataTemplate :Key="ComboBoxSelectorDataTemplate">
<StackPanel Height="20" Width="Auto" Orientation="Horizontal">
<TextBlock Margin="3,0,0,0" x:Name="textIndice" Text="{Binding Path=Indice}" Foreground="{DynamicResource Brush5}" TextWrapping="Wrap" FontSize="14" Width="30" HorizontalAlignment="Left"/>
<TextBlock x:Name="textNombre" Text="{Binding Path=Nombre}" TextWrapping="Wrap" FontSize="14" HorizontalAlignment="Right" Margin="10,0,0,0"/>
</StackPanel>
<DataTemplate.Triggers>

<!-- Con este Trigger se consigue que el color de fuente de los elementos de tipo ComboBoxItem cambie cuando el elemento está seleccionado-->

<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="True">
<Setter Property="Foreground" Value="{DynamicResource Brush1}" TargetName="textIndice" />
<Setter Property="Foreground" Value="White" TargetName="textNombre" />
</DataTrigger>

<!-- Igual que el anterior pero cuando el ratón está encima del elemento (el elemento aún no está seleccionado...)-->

<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsMouseOver}" Value="True">

<Setter Property="Foreground" Value="{DynamicResource Brush1}" TargetName="textIndice" />
<Setter Property="Foreground" Value="White" TargetName="textNombre" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>

Las extensiones que hacen referencia a brochas o colores están en este caso definidas como DynamicResource debido a que su definición se encuentra en un diccionario de recursos diferente. En general, en cuanto a rendimiento es mejor utilizar la extensión de marcado StaticResource, ya que se resuelve en tiempo de compilación, mientras que DynamicResource lo hace en tiempo de ejecución, y tantas veces como sea utilizado.


Sea el primero en calificar este post

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