sábado, 5 de junio de 2010

WPF: Personalizar nuestra aplicación con Styles y Control Templates (II)

En la primera parte de este artículo aprendimos las distintas formas que nos ofrece WPF para personalizar nuestros controles, centrándonos en los estilos y las plantillas de control. Le dimos un repaso a la teoría básica sobre elementos comunes y diccionarios de recursos.



Si te lo perdiste o quieres darle un repaso aquí lo tienes:
Esta vez vamos a ir directamente al meollo de la cuestión, examinando a fondo como personalizar varios controles estándar de WPF como son los siguientes:
  • Button
  • Textbox
  • Scrollbars (Horizontales y Verticales)
  • ProgressBar

Button

El botón es uno de los controles más sencillos de personalizar, pero a su vez es uno de los controles más útiles y el usuario interactúa mucho con ellos, por lo que es importante hacerlos lo más atractivos que podamos:
botones
Algo muy importante a recordar cuando creamos nuestras propias plantillas es que estas substituyen completamente la plantilla por defecto del control, por lo que si queremos dar indicaciones visuales de que nuestro botón está activado, desactivado, pulsado, con foco o con el ratón sobre el, deberemos hacerlo nosotros, esto lo lograremos aprovechándonos de los Triggers del control.
Lo primero que vamos a hacer es definir un estilo nuevo para todos los controles del tipo Button, pues no queremos aplicarlo de forma selectiva a ciertos botones, queremos que sea la apariencia predeterminada de cualquier botón en nuestra aplicación:
<Style TargetType="{x:Type Button}">

</Style>


Simplemente en la cabecera Style definimos la propiedad TargetType al tipo Button.


Ahora vamos a incluir algunas propiedades por defecto para nuestro botón dentro de este bloque Style:


<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Foreground" Value="White" /> 


El objeto Setter es muy sencillo, simplemente tiene el atributo Property que especifica el nombre de la propiedad que queremos establecer y el atributo Value que especifica el valor a aplicar en esa propiedad. La propiedad OverridesDefaultStyle indica a nuestro control que nunca se usará el tema por defecto del control para ninguna propiedad, aunque esta no esté definida en nuestro estilo o plantilla, si no hemos implementado algo, es porque no lo necesitamos o lo suplimos de alguna otra forma. SnapsToDevicePixels indica a nuestro estilo que el dibujado de nuestro control debe usar pixeles dependientes del dispositivo. Por defecto WPF usa el DPI actual para obtener independencia de la resolución a la hora de dibujar, pero esto puede causar un efecto difuso o borroso, y en nuestro caso queremos que nuestros preciosos controles se vean bien, por lo que indicando esta propiedad evitaremos este dibujado difuso o borroso. Foreground, bueno no hay mucho que decir, en el caso de nuestro botón la propiedad Foreground afecta al texto del mismo. Puesto que nuestro botón es oscuro y por defecto el texto es de color negro, sería un incordio estar recorriendo todos los botones cambiando el texto a Blanco u otro color y precisamente queremos que modificar el aspecto de nuestra aplicación se haga de forma centralizada, por lo que al incluir esta propiedad todos los botones obtendrán texto blanco.


El último objeto Setter que necesitamos establecerá la propiedad Template del botón y dentro de este Setter definiremos nuestro Control Template:


<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Name="tmpltButton">
</ControlTemplate>
</Setter.Value>
</Setter>


A diferencia del estilo, que establecia su propiedad TargetType, en el ControlTemplate establecemos x:Name pues esta contenido dentro del estilo y se aplicará automáticamente allí donde se aplique el estilo.


Dentro del objeto ControlTemplate podemos empezar a definir nuestro botón, aquí es donde WPF te golpea con un derroche de realidad: ControlTemplate es una pizarra en blanco en la que puedes pintar TODO lo que tu quieras y el resultado final, será el aspecto de tu botón, no hay reglas (o no demasiadas), solo creatividad.


Nuestro botón se va a componer de 6 controles básicos: 1 Grid para mantener todo unido, 4 Borders para crear la forma y relleno y 1 ContentPresenter que presentará el contenido del botón, quedaría algo así:


<Grid>
<Border x:Name="BaseRectangle" 
Background="{StaticResource BaseColor}" 
CornerRadius="10,0,10,0">
</Border>
<Border x:Name="GlassRectangle" 
Background="{StaticResource GlassFX}" 
CornerRadius="10,0,10,0">
</Border>
<Border x:Name="GlowRectangle" 
Background="{StaticResource GlowFX}" 
CornerRadius="10,0,10,0" 
Opacity="0">
</Border>
<Border x:Name="ButtonBorder" 
CornerRadius="10,0,10,0" 
BorderBrush="Black" 
Opacity="1"  
BorderThickness="1">
</Border>
<ContentPresenter x:Name="ButtonContent" 
Opacity=".7" 
Content="{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}}" 
HorizontalAlignment="center" 
VerticalAlignment="center">
</ContentPresenter>
</Grid>


Simplemente tenemos un grid que contiene nuestros controles Border y el ContentPresenter, el primer Border BaseRectangle es nuestra capa base de pintura, como veis todos los colores se definen con enlace a recursos que ya establecimos en nuestro diccionario de recursos, de esta forma si cambiamos el color BaseColor, cambiará en todos los sitios donde esté aplicado. Todos los Border comparten el mismo valor de CornerRadius para que el control tenga la apariencia deseada, los 4 valores de CornerRadius siguen el orden de las agujas del reloj:



  • Esquina Superior Derecha


  • Esquina Superior Izquierda


  • Esquina Inferior Izquierda


  • Esquina Inferior Derecha



Aplicando un valor de 10 en las esquinas superior derecha e inferior izquierda y un valor de 0 en las otras conseguimos el efecto deseado.


La propiedad Background de los dos primeros Border define la apariencia general de nuestro botón, el primero aplicando un color base (nuestro recurso BaseColor) y el segundo aplicando un degradado llamado GlassFX que es el encargado de darle a nuestro botón el efecto de reflejo de cristal.


El tercer Border define en el background el recurso GlowFX y es el usado para la animación del foco del botón, por defecto establecemos su Opacity a 0, para animar esta propiedad más adelante con los Event Triggers de nuestra plantilla.


El último Border define el borde externo del botón, en este caso no definimos la propiedad Background por lo que es transparente pero definimos la propiedad BorderBrush que establece el color del borde y BorderThickness que establece el grosor del borde.


La apariencia de nuestro botón ya está casi completa, solo necesitamos mostrar el contenido que tenga cada botón, ya sea texto, imágenes o cualquier combinación de ambos, para esto usamos el control ContentPresenter. La propiedad más interesante en el es Content, la enlazamos directamente al Content establecido en nuestro botón:



  • Binding Path= Indica la propiedad a la que queremos enlazarnos.


  • RelativeSource= Indica la fuente de esta propiedad, en nuestro caso la dirigimos a una fuente relativa de tipo templatedparent, es decir el objeto padre de nuestra plantilla, que en este caso será cada botón que la use.



Con esto ya está totalmente terminada la apariencia de nuestro botón. si colocamos un botón en una ventana de pruebas veremos que usa nuestro estilo automáticamente. Al ejecutar, no obstante, notaremos que el botón no reacciona de ninguna manera al pasar el ratón sobre el o hacer click lo cual es muy poco intuitivo para el usuario final, esto lo resolveremos usando los Triggers del Controltemplate, justo después de la Grid, añadimos un objeto ControlTemplate.Triggers:


<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Button.MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource GlowOut}">
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Button.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource GlowIn}">
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Button.LostFocus">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource GlowOut}">
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Button.GotFocus">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource GlowIn}">
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<Trigger Property="Button.IsPressed" Value="True">
<Setter Property="Background" TargetName="GlowRectangle"
Value="{StaticResource GlowFXPressed}">
</Setter>
<Setter Property="Opacity" TargetName="ButtonContent" 
Value="1">
</Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" TargetName="ButtonContent" 
Value=".3">
</Setter>
<Setter Property="Opacity" TargetName="ButtonBorder" 
Value=".5">
</Setter>
<Setter Property="Background" TargetName="GlassRectangle" 
Value="{StaticResource GlassFXDisabled}">
</Setter>
</Trigger>
</ControlTemplate.Triggers>


Como podéis ver en este snippet, tenemos dos tipos de Triggers: Trigger y EventTrigger.


Los Trigger se disparan cuando la propiedad Indicada tiene el valor indicado, mientras que los EventTrigger se disparan cuando el evento indicado es lanzado. La mayor diferencia es que los EventTriggers nos permiten lanzar animaciones como indicación visual a un cambio en el estado del control.


En el caso de nuestro Botón respondemos a 4 eventos: MouseEnter, MouseLeave, LostFocus y GotFocus.


Cada EventTrigger define un objeto EventTrigger.Actions y dentro de este encontramos el objeto BeginStoryboard, que inicia la animación indicada en su propiedad Storyboard (En el primer capítulo ya hablamos sobre las animaciones y explicamos como crear animaciones sencillas).


También controlamos el cambio de 2 propiedades: IsPressed e IsEnabled.


Cada uno de estos 2 triggers se lanzará cuando la propiedad indicada tenga el valor indicado (Cuando IsPressed sea True y cuando IsEnabled sea false) y aplicará por medio de los objetos Setter valores a otras propiedades. Cada objeto Setter define Property que indica el nombre de la propiedad a modificar, TargetName que indica el nombre del objeto de nuestra plantilla al que afectará la modificación y value que indica el nuevo valor que tendrá la propiedad.


Si volvemos a ejecutar nuestro proyecto comprobaremos que ahora gracias a los Triggers, nuestro botón reacciona al pasar el ratón sobre el, al abandonarlo el ratón, al llegar a el con el tabulador o al salir de el, al presionarlo y al desactivarlo,con lo que conseguimos una respuesta visual muy importante para nuestro usuario final.


Textbox



En general nuestro control Textbox se compone de forma muy parecida al Botón, el mayor cambio en la composición del control viene dado porque en este caso, en vez de usar un control ContentPresenter usaremos un control ScrollViewer, (que personalizaremos más adelante también) para permitir que el usuario pueda usar las barras de desplazamiento vertical y horizontal en una caja de texto multilinea:


textboxes


El código xaml del objeto ScrollViewer es este:


<ScrollViewer x:Name="PART_ContentHost" 
Opacity=".7" 
Content="{Binding Path=Content, 
RelativeSource={RelativeSource TemplatedParent}}" 
HorizontalAlignment="{Binding Path=HorizontalAlignment, 
RelativeSource={RelativeSource TemplatedParent}}" 
VerticalAlignment="{Binding Path=VerticalAlignment, 
RelativeSource={RelativeSource TemplatedParent}}" 
Width="{Binding Path=Width, 
RelativeSource={RelativeSource TemplatedParent}}" 
Height="{Binding Path=Height, 
RelativeSource={RelativeSource TemplatedParent}}">
</ScrollViewer>


En este caso salvo la Opacity del ScrollViewer, el resto de propiedades (Content, HorizontalAlignment, VerticalAlignment, Width y Height) se han enlazado al control padre de la plantilla para ilustrar que cualquier propiedad compartida entre un control de la plantilla y el control padre puede ser enlazada.


En este caso, el nombre definido al ScrollViewer no es al azar, este control tiene código enlazado con el ScrollViewer en su plantilla original, esta es una pequeña limitación y una de las pocas que encontraremos, para que todo siga funcionando correctamente, debemos respetar el nombre, en este caso PART_ContentHost. Microsoft recomienda que siempre que tengamos que usar código contra un objeto parte de una plantilla lo llamemos PART_xxxxxxxx para de esta forma seguir una nomenclatura común.


En este caso los Triggers usados son más sencillos que los del botón, usamos 2 EventTrigger LostFocus y GotFocus y un Trigger a la propiedad IsEnabled:


<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="TextBox.LostFocus">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource GlowOut}">
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="TextBox.GotFocus">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource GlowIn}">
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" TargetName="PART_ContentHost" 
Value=".3">
</Setter>
<Setter Property="Opacity" TargetName="BorderExtern" 
Value=".5">
</Setter>
<Setter Property="Fill" TargetName="GlassRectangle" 
Value="{StaticResource GlassFXDisabled}">
</Setter>
</Trigger>
</ControlTemplate.Triggers>


Scrollbars



El control scrollbar nos va a dar algo más de trabajo que los dos primeros controles, pues involucra partes móviles y puede cambiar de orientación, de vertical a horizontal.


Scrollbars


Para este control Debemos definir varios estilos por separado y controltemplates independientes, uno para la barra vertical y otro para la barra horizontal.


Primero empezaremos definiendo los estilos de los controles que componen un scrollbar, LineButtons, PageButtons y ScrollThumb:


scrollbar_parts






LineButton:


Los LineButtons son los botones en los extremos del scrollbar, el tipo de control es repeatbutton y su controltemplate es muy sencillo:


<ControlTemplate x:Name="tmplRepeatButton">
<Border Background="#FF222222" BorderBrush="DarkBlue" 
BorderThickness="1" CornerRadius="3">
<Path HorizontalAlignment="Center" 
VerticalAlignment="Center" 
Data="{Binding Path=Content,
RelativeSource={RelativeSource TemplatedParent}}" 
Fill="Cyan" >
</Path>
</Border>
</ControlTemplate>


Se compone solo de dos controles, un Border que define el aspecto del control, y contiene un objeto Path, cuya propiedad Data asociamos a la propiedad Content del control al que añadiremos esta plantilla. La gran potencia del objeto Path viene dada por su propiedad Data, pues dispone de un “mini lenguaje” para especificar geometrías complejas de una forma rápida y sencilla. Podéis encontrar una referencia muy completa en msdn aquí.


Su nombre viene indicado por la acción que realiza, pues cada vez que se pulsa un LineButton se avanza o retrocede una linea del control que estemos usando.


PageButtons:


Los page buttons actúan como el fondo de la barra sobre la que desplazamos el scrollbar, como su nombre indica son los encargados de mover adelante o atrás una página completa, el tipo de control es también repeatbutton.


Su control template es muy sencillo y se compone de un solo objeto Border:


<ControlTemplate x:Name="tmplRepeatButton">
<Border Background="#66000000" BorderBrush="Black" 
BorderThickness="1" CornerRadius="3">
</Border>
</ControlTemplate>


Como podéis ver, tanto el template del LineButton como el del PageButton tienen el mismo nombre, esto se debe a que al ser plantillas incrustadas dentro de un estilo, su ámbito es el estilo y no colisionan entre sí.


ScrollThumb:


El scrollthumb es la parte móvil del scrollbar, se redimensiona a medida que es necesario más scroll o menos y podemos pincharlo y arrastrarlo para movernos por el contenido, el tipo de control usado es Thumb, puesto que incluye funcionalidad para Drag&Drop de forma nativa, su control template también es algo más complejo que los anteriores botones:


<ControlTemplate x:Name="tmplScrollThumb">
<Grid>
<Rectangle RadiusX="3" RadiusY="3" Fill="#66222222">
</Rectangle>
<Border CornerRadius="3" BorderBrush="DarkBlue" 
Background="{StaticResource GlassFX}">
<Path HorizontalAlignment="Center" 
VerticalAlignment="Center" 
Data="{Binding Path=Content,
RelativeSource={RelativeSource TemplatedParent}}">
</Path>
</Border>
</Grid>
</ControlTemplate>


Tenemos una grid que contendrá nuestros controles, un rectángulo que actúa como fondo base del Thumb. un Border para darle el efecto de cristal y dentro de este un objeto Path para que podamos incluir una geometría personalizada si lo deseamos.





Ahora que ya hemos definido las plantillas y estilos de nuestros controles base, podemos pasar a definir las plantillas de las Scrollbar tanto vertical como Horizontal:


Scrollbar vertical:


<ControlTemplate x:Key="VerticalScroll" TargetType="{x:Type ScrollBar}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="18"></RowDefinition>
<RowDefinition Height="0.00001*"></RowDefinition>
<RowDefinition Height="18"></RowDefinition>
</Grid.RowDefinitions>
<Border Grid.RowSpan="3" CornerRadius="3" 
BorderBrush="DarkBlue" BorderThickness="1" 
Opacity=".6">
</Border>
<RepeatButton Grid.Row="0" 
Style="{StaticResource LineButton}" 
Height="18" Command="ScrollBar.LineUpCommand" 
Content="M 0 4 L 8 4 L 4 0 Z">
</RepeatButton>
<Track Name="PART_Track" Grid.Row="1" IsDirectionReversed="True">
<Track.DecreaseRepeatButton>
<RepeatButton Style="{StaticResource PageButton}" 
Command="ScrollBar.PageUpCommand" />
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb Style="{StaticResource ScrollThumb}" />
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton Style="{StaticResource PageButton}" 
Command="ScrollBar.PageDownCommand" />
</Track.IncreaseRepeatButton>
</Track>
<RepeatButton Grid.Row="2" 
Style="{StaticResource LineButton}" 
Height="18" Command="ScrollBar.LineDownCommand" 
Content="M 0 0 L 4 4 L 8 0 Z">
</RepeatButton>            
</Grid>
</ControlTemplate>


Este ya es un control template más complejo, compuesto de más controles. Para empezar tenemos una grid a la que le definimos 3 Rows, la primera y la ultima contendrán los botones de subir y bajar usando repeatbuttons a los que les aplicamos el estilo que anteriormente creamos LineButton.


En la parte central de la grid definimos un objeto Track, puesto que esta parte de un scrollbar tiene código asociado le asignamos el mismo nombre que el del original: PART_Track y dentro del mismo tenemos 3 propiedades que debemos rellenar: DecreaseRepeatButton, Thumb e IncreaseRepeatButton. En el DecreaseRepeatButton e IncreaseRepeatButton definimos RepeatButtons con el estilo PageButton que creamos anteriormente y en Thumb definimos un control Thumb con el estilo ScrollThumb.


Algo a tener en cuenta es el Content de los RepeatButton usados con el estilo LineButton, estas combinaciones de letras y números son el “mini lenguaje” de geometría que usa el objeto Path para dibujar formas, es muy útil e interesante, en el apartado del estilo LineButton tenéis un enlace a una descripción completa de este lenguaje.


Scrollbar Horizontal:


El scrollbar horizontal es idéntico al scrollbar vertical, solo que en vez de dividir la Grid en Rows la dividimos en Columnas, usando la primera y ultima columna para los LineButtons y la columna central para el Track:


<ControlTemplate x:Key="HorizontalScroll" TargetType="{x:Type ScrollBar}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18"></ColumnDefinition>
<ColumnDefinition Width="0.00001*"></ColumnDefinition>
<ColumnDefinition Width="18"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Border Grid.ColumnSpan="3" CornerRadius="3" 
BorderBrush="DarkBlue" BorderThickness="1" 
Opacity=".6" >
</Border>
<RepeatButton Grid.Column="0" 
Style="{StaticResource LineButton}" 
Width="18" Command="ScrollBar.LineLeftCommand" 
Content="M 4 0 L 4 8 L 0 4 Z">
</RepeatButton>
<Track Name="PART_Track" Grid.Column="1" IsDirectionReversed="False">
<Track.DecreaseRepeatButton>
<RepeatButton Style="{StaticResource PageButton}" 
Command="ScrollBar.PageLeftCommand" />
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb Style="{StaticResource ScrollThumb}" />
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton Style="{StaticResource PageButton}" 
Command="ScrollBar.PageRightCommand" />
</Track.IncreaseRepeatButton>
</Track>
<RepeatButton Grid.Column="2" 
Style="{StaticResource LineButton}"
Width="18" Command="ScrollBar.LineRightCommand"
Content="M 0 0 L 4 4 L 0 8 Z">
</RepeatButton>
</Grid>
</ControlTemplate>





Por último solo nos queda definir un estilo común para el control Scrollbar que decida que plantilla aplicar dependiendo de la orientación designada por el control:


<Style TargetType="{x:Type ScrollBar}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Style.Triggers>
<Trigger Property="Orientation" Value="Vertical">
<Setter Property="Width" Value="18"></Setter>
<Setter Property="Height" Value="Auto"></Setter>
<Setter Property="Template" Value="{StaticResource VerticalScroll}">
</Setter>
</Trigger>
<Trigger Property="Orientation" Value="Horizontal">
<Setter Property="Width" Value="Auto"></Setter>
<Setter Property="Height" Value="18"></Setter>
<Setter Property="Template" Value="{StaticResource HorizontalScroll}">
</Setter>
</Trigger>            
</Style.Triggers>
</Style>


Este es un estilo sencillo, simplemente tenemos dos Triggers que controlan la propiedad Orientation, y dependiendo de su valor aplican una plantilla u otra.


ProgressBar



La barra de progreso es un control muy útil para ofrecer información al usuario a cerca de la realización de una tarea que se alarga en el tiempo un poco. En este caso a parte de darle una apariencia más atractiva también vamos a darle un toque más de funcionalidad, permitiendo indicar al usuario el porcentaje del trabajo realizado de forma numérica. También tenemos soporte para trabajos con duración indeterminada:


progressbar


En la imagen superior podemos ver 3 estados de una ProgressBar: Determinado, Indeterminado y Desactivado. Como podéis ver en los estados Determinado y desactivado se ofrece una información visual del % realizado en forma de número.


El Control template es muy parecido al de cualquier otro control que hayamos visto:


<ControlTemplate TargetType="{x:Type ProgressBar}">
<Grid MinHeight="14" MinWidth="20">
<Border x:Name="BaseRectangle" 
Background="{StaticResource BaseColor}" 
CornerRadius="10,0,10,0">
</Border>
<Border x:Name="GlassRectangle" CornerRadius="10,0,10,0"  
Background="{StaticResource GlassFX}">
</Border>
<Border x:Name="animation" CornerRadius="10,0,10,0" 
Opacity=".7" Background="{StaticResource GlowFXProgress}" 
HorizontalAlignment="Left">
</Border>
<Border x:Name="PART_Indicator" CornerRadius="10,0,10,0" 
Background="{StaticResource GlowFXProgress}" 
HorizontalAlignment="Left">
</Border>
<Border x:Name="PART_Track" BorderThickness="1" 
CornerRadius="10,0,10,0" BorderBrush="Black">
</Border>
<Border x:Name="BordeCabeceraSombra" BorderThickness="2" 
CornerRadius="10,0,10,0" BorderBrush="DarkGray" 
Opacity=".2" Margin="1,1,1,0">
</Border>
<Label x:Name="Progress" VerticalAlignment="Stretch" 
HorizontalAlignment="Stretch" 
HorizontalContentAlignment="Center" 
VerticalContentAlignment="Center" 
FontWeight="Bold" Foreground="White" Opacity=".7" 
Content="{Binding Path=Value, 
RelativeSource={RelativeSource TemplatedParent}}">
</Label>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsIndeterminate" Value="True">
<Setter Property="Visibility" TargetName="Progress" Value="Hidden">
</Setter>
<Setter Property="Background" TargetName="PART_Indicator">
<Setter.Value>
<MultiBinding>
<MultiBinding.Converter>
<wintheme:ProgressBarHighlightConverter/>
</MultiBinding.Converter>
<Binding Source="{StaticResource GlowFXProgressAnimated}"/>
<Binding Path="ActualWidth"  ElementName="BaseRectangle"/>
<Binding Path="ActualHeight" ElementName="BaseRectangle"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value=".5"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>


Esta plantilla es larga pero no es complicada, lo más interesante de la misma se encuentra en dos puntos, primero, el control label que compone la plantilla, cuyo contenido está enlazado a la propiedad Value de la progressbar, de esta forma siempre que cambie la propiedad value se reflejará en el control label.


El segundo se encuentra en el Trigger que controla cuando la propiedad IsIndeterminate es verdadera, cambiamos el valor de la propiedad background del control PART_Indicator usando un Multibinding Converter, esto es una clase que acepta varios valores de entrada y devuelve un valor de salida. En nuestro caso queremos conservar la funcionalidad de Windows Vista y 7 en las barras indeterminadas, para ello debemos incluir en nuestro proyecto una referencia al ensamblado: PressentationFramework.Aero y referenciarlo en el archivo xaml de nuestro tema de la siguiente forma:


xmlns:wintheme="clr-namespace:Microsoft.Windows.Themes;
assembly=PresentationFramework.Aero"


Con esto nuestra progressbar funcionará perfectamente.





Conclusión



El tema Black Crystal incluye otros controles como son Datagrid, TabControl y GroupBox. Os dejo como tarea para casa el analizarlos, con lo visto aquí veréis que no es complicado comprenderlos.


Un gran saludo y Happy Coding!

7 comentarios:

  1. Muy buen aporte, aunque muchas cosas no las entiendo porque estoy iniciandome en el WPF y no he visto nunca XAML es un trabajo muy interesante y ya me asaltan las primeras dudas de si los controles diseñados con WPF se pueden importar en un windowsapplication normal, ya que me parece de mas facil manejo a la hora de situar los controles en el formulario y ver las propiedades de los controles.

    Muchas gracias por los ejemplos.
    -------------------------------
    Sergio.

    ResponderEliminar
  2. Hola Sergio

    Me alegra de que te sea util y te guste.

    Para usar un control WPF en una aplicación windows forms puedes usar el control ElementHost de windowsForms, que te permite cargar un control WPF. De todas formas, aunque la curva de aprendizaje es algo pronunciada, te recomiendo que le metas horas a WPF, es el futuro y es increible lo bién que funciona y las maravillas que puedes hacer!

    Un saludo y muchas gracias por leerme!

    ResponderEliminar
  3. Hola de nuevo Josué.

    Estos ultimos dias he estado trasteando un poquillo creandome aplicaciones sencillas de WPF para ir habituandome al entorno, pero cuando me he puesto a programar el funcionamiento de "algo" en vb es como si volviera a empezar de cero porque desconozco todas las nuevas propiedades que añaden a los controles nuevas funcionalidades, otras similares ya conocidas que realizan mismas funciones pero las han cambiado de nombre y controles que se han añadido nuevos como Canvas Grid y algunos que ham desaparecido como SplitContainer por ejemplo.
    Mi pregunta seria, ¿Hay algun manual o tutorial que me explique las nuevas propiedades añadidas y un poco las nuevas cosas?, que nose si son nuevas o no, para alguien que realiza aplicaciones en WPF imagino que no, pero mi objetivo es moverme con total soltura como hago en una aplicacion de windowsform normal, entendiento todo sin tapujos.

    Muchas gracias de nuevo por el ejemplo, y gracias por contestar, un saludo.
    -------------------------
    Sergio Vasco.

    ResponderEliminar
  4. Josue, he leido tus articulos sobre wpf y me han parecido muy esclarecedores, por lo que trate de seguir paso por paso toda la parte de los botones, pero cuando mando a ejecutar me sale el siguiente mensage parandome la aplicacion
    "XamlParseException:
    Provide value on 'System.Windows.Markup.StaticResourceHolder' threw an exception"
    que crees que pueda estar ocurriendo??
    uso VS2010, como comprenderas no he podido seguir, gracias

    ResponderEliminar
  5. Hola Dennis, pues así a simple vista parece que se trata de algún StaticResource que te falta, si me envías el código le puedo hechar un vistazo y decirte exactamente donde está el fallo y por que :)
    P.D.: Siento no haber contestado antes, he estado todo el fin de semana de viaje y sin ordenador.

    Gracias por leerme :)

    ResponderEliminar
  6. Dennis, mi email está en mi tarjeta Virtual, un gran saludo!

    ResponderEliminar
  7. hola josue, ¿es posible descargar este proyecto?

    ResponderEliminar