Cambiar de tema dinámicamente

lunes, 15 de octubre de 2012

Al elaborar un desarrollo para un cliente, cuando el que dirige el proyecto no es uno, sino varios individuos, te sueles encontrar con problemas con el diseño, más allá del funciona o no funciona, lo más importante es que esto es azul y no verde, o este tono no me gusta, y problemas de esa índole.

Un tiempo atrás, me encontré con un desarrollo, que cuando los clientes vieron la primera beta, la bautizaron como Matrix, la beta fallaba que daba gusto, hubo un cambio de última hora que arrastró varios fallos, pero lo más importante fue que la aplicación fue llamada Matrix, porque era negra con algunos detalles verdes.

Para evitar esto, lo más sencillo, es ofrecer un cambio dinámico en el conjunto de diseños de la aplicación, ojo, sin modificar la estructura, simplemente, haciendo participe al usuario final, del conjunto de colores, o incluso estilos que quiere darle a la aplicación.

Para evitar esto, surgieron varias propuestas de diseño, pero viendo que los diseñadores no terminaban de acertar con el gusto del cliente (de todos los individuos que estaban del lado del cliente) decidimos implementar un cambio dinámico, e incluso, un cambio en ciertos elementos como el tamaño de la fuente, la fuente, o detalles de color.

Creamos un proyecto nuevo de WPF, y le agregamos una carpeta "Theme" por ejemplo, y a ésta le vamos a agregar un diccionario de recursos.

Agregar Diccinario

Una vez agregado, lo referenciamos en el App.xaml, para que los estilos del diccionario estén accesibles de forma dinámica.

    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Theme/Principal.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>

Después agregamos un estilo para hacer un ejemplo que modifica los TextBlock, ya que es para lo que vamos a preparar un estilo. Una vez realizado este estilo, duplicaremos el diccionario de recursos y modificaremos los estilos para diferenciar los TextBlock entre diccionarios.

<!--Diccionario Principal-->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <Style x:Key="TextBlockStyle" TargetType="{x:Type TextBlock}">
        <Setter Property="OverridesDefaultStyle" Value="True"/>
        <Setter Property="FontWeight" Value="Bold"/>
        <Setter Property="FontSize" Value="20pt"/>
        <Setter Property="FontStyle" Value="Normal"/>
        <Setter Property="Foreground" Value="Blue"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="HorizontalAlignment" Value="Center"/>
    </Style>
</ResourceDictionary>
<!--Diccionario Alternativo 1-->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <Style x:Key="TextBlockStyle" TargetType="{x:Type TextBlock}">
        <Setter Property="OverridesDefaultStyle" Value="True"/>
        <Setter Property="FontWeight" Value="Bold"/>
        <Setter Property="FontSize" Value="16pt"/>
        <Setter Property="FontStyle" Value="Italic"/>
        <Setter Property="Foreground" Value="Green"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="HorizontalAlignment" Value="Center"/>
    </Style>
</ResourceDictionary>
<!--Diccionario Alternativo 2-->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <Style x:Key="TextBlockStyle" TargetType="{x:Type TextBlock}">
        <Setter Property="OverridesDefaultStyle" Value="True"/>
        <Setter Property="FontWeight" Value="Normal"/>
        <Setter Property="FontSize" Value="18pt"/>
        <Setter Property="FontStyle" Value="Oblique"/>
        <Setter Property="Foreground" Value="Red"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="HorizontalAlignment" Value="Center"/>
    </Style>
</ResourceDictionary>

A continuación, introducimos en la aplicación, un ComboBox y un TextBlock, el primero lo vamos a cargar con los estilos existentes y el TextBlock, recibirá el estilo, y se modificará el texto con el valor del ComboBox.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="300" Width="300">
    <Window.Resources>
        <CollectionViewSource x:Key="CvThemes"/>
    </Window.Resources>
    <StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
        <ComboBox x:Name="CmbThemes" ItemsSource="{Binding Source={StaticResource CvThemes}}" 
                  SelectionChanged="CmbThemesSelectionChanged" />
        <TextBlock Text="{Binding SelectedItem, ElementName=CmbThemes}" 
                   Style="{DynamicResource TextBlockStyle}"/>
    </StackPanel>
</Window>

El combo se está cargando con un CollectionViewSource, que a priori, no tiene elementos, para ello deberemos cargarlo desde el code behind. Pero, antes de cargar esta colección, debemos modificar unas propiedades de la compilación de los diccionarios alternativos, ya que lo que vamos a hacer es recorrer un directorio de la aplicación en busca de estos diccionarios, así podremos dejar una puerta abierta a la creación dinámica de diccionarios, bien por manualmente por el usuario, bien a través de un control de nuestra aplicación. Las propiedades que debemos modificar son las siguientes, Acción de compilación de Page a Contenido, y Copiar en el directorio a Siempre o a si es Posterior.

Compilación

Para obtener los diccionarios, tenemos que recorrer el directorio "Theme", que es donde hemos creado los distintos diccionarios, y los agregamos uno a uno al CollectionViewSource. Después adaptaremos el evento SelectionChanged del combo para que desencadene el cambio de estilo.

            var cv = (CollectionViewSource)TryFindResource("CvThemes");
            var list = new ArrayList {"Principal"};
            
            foreach (FileInfo file in new DirectoryInfo(Environment.CurrentDirectory + @"\Theme\").GetFiles("*.xaml"))
            {
                list.Add(file.Name.Replace(file.Extension, String.Empty));
            }
            cv.Source = list;

Para agregar un diccionario de forma dinámica, debemos crear un objeto ResourceDictionary, al cual le referenciamos una URI relativa del diccionario a agregar, después se limpia la caché de diccionarios agregados, y se agrega el que acabamos de crear.

using System;
using System.Collections;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        private const string PathTheme = "Theme/{0}.xaml";
        public MainWindow()
        {
            InitializeComponent();
            var cv = (CollectionViewSource)TryFindResource("CvThemes");
            var list = new ArrayList {"Principal"};
            
            foreach (FileInfo file in new DirectoryInfo(Environment.CurrentDirectory + @"\Theme\").GetFiles("*.xaml"))
            {
                list.Add(file.Name.Replace(file.Extension, String.Empty));
            }
            cv.Source = list;
        }

        private void CmbThemesSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var dictionary = new ResourceDictionary
                {
                    Source = new Uri(String.Format(PathTheme, CmbThemes.SelectedItem), 
                        UriKind.RelativeOrAbsolute)
                };
            Application.Current.Resources.MergedDictionaries.Clear();
            Application.Current.Resources.MergedDictionaries.Add(dictionary);
        }
    }
}
Etiquetas,

Deja un comentario

Buscar

Search