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.

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.

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);
}
}
}