Di materiale in rete su WPF c’è né parecchio, faccio però fatica a trovare materiale riguardante un aspetto cruciale nelle realizzazione di una qualsiasi applicazione WPF: l’organizzazione delle risorse, dove col il termine risorse mi riferisco a qualsiasi cosa possa essere contenuta in un ResourceDictionary.
Immaginiamo,ad esempio,di dovere visualizzare un immagine e utilizzare uno stile in una finestra con la possibilità di sostituirli successivamente senza modifica l’applicazione principale.
Forti dell’esperienza maturata coi Windows Forms anche chi è digiuno di WPF può facilmente intuire che la soluzione consiste nel mettere immagine e stile in un assembly satellite e utilizzarli dall’applicazione principale.
Un esempio di come potrebbe essere organizzato il progetto è visibile nella figura che segue dove MainUI referenzia il progetto ResourceLibrary.

image

A questo punto dobbiamo raggruppare le nostre risorse all'interno del ResourceDictionary Dictionary1.xaml attraverso il seguente xaml:

<ResourceDictionary

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style x:Key="TextBoxStyle" TargetType="TextBox">
        <Setter Property="Background" Value="Yellow" />
        <Setter Property="FontSize" Value="24" />
    </Style>
    <Image x:Key="LogoPicture" Source="Games.png" />    
</ResourceDictionary>

Entrambe le risorse sono ora raggiungibili attraverso le rispeettive chiavi "TextBoxStyle" e  "LogoPicture".

A questo punto alcune precisazioni:
1-Il progetto ResourceLibrary è in realta una libreria di controlli WPF al quale ho rimosso il controllo di default.
2-Volendo utilizzare esclusivamente l'immagine Games.png avrei anche potuto evitare di usare Dictionary1.xaml
3-Dummy.cs è una classe vuota che serve per non fare arrabbiare il designer di Visual Studio 2008 (bello vero?)

Ora che la libreria è pronta, per utilizzarla nel progetto MainUI bisogna "caricare" il ResourceDictionary esterno modificando il contenuto di App.xaml in questo modo.

<Application x:Class="MainUI.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Window1.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/ResourceLibrary;component/Dictionary1.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

rendendo visibili le risorse esterne in tutta l'applicazione, infatti lo XAML che segue ha come risultato la figura a fianco:

<Window x:Class="MainUI.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:lib="clr-namespace:ResourceLibrary;assembly=ResourceLibrary"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <TextBox Margin="10" Style="{StaticResource TextBoxStyle}" Text="UGIdotNet" />
        <ContentControl MaxWidth="100" Content="{StaticResource LogoPicture}" />
    </StackPanel>
</Window>
image

Questa soluzione, sebbene funzionale è decisamente macchinosa,sopratutto per chi deve consumare le risorse esposte da ResourceLinrary, l'ideale sarebbe poter far riferimento a una risorsa senza preoccuparsi di dove questa sia definita creando a tutti gli effetti una libreria che espone delle risorse e non come in questo caso una libreria che espone un contenitore di risorse che andiamo a "fondere" con le eventuali altre risorse di applicazione.
Il problema è che la chiave utilizzata da StaticResource/DynamicResource per rintracciare una risorsa è una stringa e non è in grado di contenere un informazione essenziale per  il risultato che vogliamo ottenere ovvero: Quale assembly contiene la risorsa col nome "XYZ".
Ecco perchè per questo tipo di risorsa dobbiamo utilizzare un particolare tipo di chiave denominato ComponentResourceKey.
Per dimostrare l'uso di ComponentResourceKey ho creato una nuova solution composta da due progetti: un applicazione WPF (MainUI) e una library (ResourceLibrary) ottenuta partendo dal template WPF custom control library (per comodità). L'applicazione WPF che referenzia l'assembly WPF, ha come organizzazione la soluzionevisibile nella figura seguente:

image

Generic.xaml è un resource dictionary che contiene le risorse che abbiamo utilizzato nell'esempio precedente: la novità sta nel modo in cui è definita la relativa key.

Lo xaml di Generic.xaml è il seguente:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ResourceLibrary">
    
    <Image x:Key="{ComponentResourceKey
                    TypeInTargetAssembly={x:Type local:CustomResources},
                    ResourceId=LogoPictureImage}" Source="/ResourceLibrary;component/Images/Games.png" />

    <Style x:Key="{ComponentResourceKey 
                    TypeInTargetAssembly={x:Type local:CustomResources},
                    ResourceId=TextBoxStyle}" TargetType="TextBox">
        <Setter Property="Background" Value="Yellow" />
        <Setter Property="FontSize" Value="24" />
    </Style>    
</ResourceDictionary>

Come potete notare ora le key sono di tipo ComponentResourceKey la quale oltre all'identificativo con cui verrà rintracciata la risorsa (ResourceId) richiede che venga associato un tipo definito all'interno della stessa assembly che contiene la definizione della key (TargetTypeInAssembly)

Nel nostro caso il tipo utilizzato è una classe CustomResources il cui compito è quello di rendere più facile, lato client, recuperare la key che identifica una specifica risorsa, non a caso CustomResources è così definita:

public class CustomResources
    {
        public static ComponentResourceKey TextBoxStyleKey
        {
            get{return new ComponentResourceKey(typeof(CustomResources), "TextBoxStyle");}
        }

        public static ComponentResourceKey LogoPictureImageKey
        {
            get{return new ComponentResourceKey(typeof(CustomResources), "LogoPictureImage");}
        }
    }

ovvero è una classe che espone attraverso proprietà statiche istanze di ComponentResourceKey che puntano alle nostre risorse.

A questo punto lato per quanto riguarda la parte client le cose sono molto più semplici in quanto in Window1.xaml è sufficiente scrivere:

<Window x:Class="MainUI.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:lib="clr-namespace:ResourceLibrary;assembly=ResourceLibrary"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <TextBox Margin="10" Text="UGIdotNet"
                         Style="{StaticResource {x:Static lib:CustomResources.TextBoxStyleKey}}" />
        <ContentControl MaxWidth="100" 
                         Content="{StaticResource {x:Static lib:CustomResources.LogoPictureImageKey}}" />
    </StackPanel>
</Window>

Per ottenere lo stesso risultato di prima.

In breve: Se volete creare librerie di risorse esponendo le singole risorse in modo univoco e indipendente dalla loro organizzazione interna utilizzate ComponentResourceKey, che, non a caso, è esattamente l'approccio usato dalle librerie di Windows Presentation Foundation.

Technorati Tags: ,