Un controllo custom è un componente che estende le funzionalità proprie di un controllo già esistente, di cui vengono modificati comportamenti e/o l'aspetto grafico. Questo tipo di controlli non devono essere confusi con gli user control.
Un controllo custom deriva infatti da altri controlli (come Button
) o da primitive (come Control
e Panel
), mentre un user control deriva dalla classe UserControl
e il suo scopo è quello di combinare controlli esistenti in un unico "blocco" riutilizzabile.
Un controllo custom ha un template dinamico, definito generalmente tramite un ResourceDictionary
contenuto in un file separato (Generic.xaml
), il che permette di modificare l'aspetto del controllo custom nei vari progetti in cui viene riutilizzato. Al contrario, la definizione XAML di un user control è strettamente accoppiata al corrispondente code behind (alla stregua di una pagina o di un qualunque altro controllo XAML), con il risultato che la UI di un user control non può essere modificata da progetto a progetto.
Il modo più semplice di creare un controllo custom per applicazioni Windows Store è quello di cliccare con il tasto destro sul progetto in Visual Studio, cliccare su Add New Item e selezionare l'elemento Templated Control, come mostrato nella prossima immagine:
Una volta selezionato l'elemento, Visual Studio aggiungerà tre elementi al progetto Windows Store: una classe che incapsulerà la logica del nuovo custom control, una directory denominata Themes
e, all'interno di questa, un file Generic.xaml
contenente il template di default per il controllo stesso. Sia il nome della cartella che quello del file di stile sono necessari per permettere al framework XAML di caricare il controllo in modo automatico. La prossima immagine mostra il progetto in Visual Studio con i tre elementi aggiunti di default:
Quella che segue è la definizione di default del nuovo controllo:
namespace Demo.Html.it.MyCustomControls.CS
{
public sealed class MyCustomControl : Control
{
public MyCustomControl()
{
this.DefaultStyleKey = typeof(MyCustomControl);
}
}
}
La prima cosa da notare è che, per default, il nuovo controllo eredita dalla classe base Control
. Come già accennato, è possibile derivare il nostro controllo custom da qualunque altro controllo, in base alle funzionalità di cui abbiamo bisogno. Per questo la scelta del controllo da cui ereditare è un momento importante nella progettazione della nostra UI: scegliendo il controllo più appropriato, possiamo infatti concentrarci sull'aggiunta di nuove funzionalità, piuttosto che reinventare comportamenti e feature già esposte da altri controlli. Ad esempio, se vogliamo creare un controllo custom che possa essere cliccato, è sufficiente derivarlo dalla classe Button
per ereditare tutte le proprietà e gli eventi tipici di un pulsante (come l'evento di Click).
In secondo luogo, la classe che incapsula la logica del nuovo controllo custom (MyCustomControl
, nel nostro esempio) è marcata come sealed
, il che significa che non è possibile derivarla ulteriormente.
La terza cosa da notare, infine, è la proprietà DefaultStyleKey
esposta dalla classe base Control
(e quindi anche da tutti i controlli che da questa classe derivano). Questa proprietà indica al framework quale stile deve essere applicato al controllo custom. Come si è già anticipato, questo template è definito nel file Generic.xaml
, attraverso un ResourceDictionary
. Il prossimo snippet mostra la definizione di default del template:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Demo.Html.it.MyCustomControls.CS">
<Style TargetType="local:MyCustomControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyCustomControl">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Lo stile del controllo è basato su un template (associato al controllo custom tramite la proprietà TargetType
). Nel framework XAML, la classe ControlTemplate
permette di definire l'aspetto grafico e i comportamenti di un controllo, combinando diversi oggetti FrameworkElement
(o derivati) in un singolo controllo. Vale la pena di osservare come questa classe possa avere un unico oggetto FrameworkElement
come elemento root (ad esempio un Border
, come nel file Generic.xaml).
Come si può vedere nel codice sopra riportato, nella definizione standard del template i valori delle proprietà Background
, BorderBrush
e BorderThickness
sono messe in binding con i valori delle corrispondenti proprietà esposte dal controllo custom.
Per poter sfruttare il controllo custom appena aggiunto, è sufficiente dichiarare il relativo namespace nel codice XAML della pagina, come si farebbe con un user control. Il seguente snippet mostra questo punto.
<Page
x:Class="Demo.Html.it.MyCustomControls.CS.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Demo.Html.it.MyCustomControls.CS"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<local:MyCustomControl Width="300" Height="300" Background="Orange" BorderBrush="OrangeRed" />
</Grid>
</Page>
Se adesso eseguite l'applicazione, dovreste ottenere un risultato simile a quello mostrato nella prossima immagine:
Aggiungere nuove proprietà al controllo custom
I controlli custom possono essere estesi aggiungendo nuove dependency property custom (Una dependency property rappresenta un tipo di proprietà il cui valore viene tracciato dal "dedicated property system" che fa parte del Windows Runtime. In particolare, come il termine "dependency" suggerisce, il valore di una dependency property dipende dal valore di altri input, come preferenze dell'utente, proprietà just-in-time, risorse, stili, e così via. Per maggiori informazioni su questo argomento si rinvia alla documentazione MSDN).
Il codice che segue definisce due nuove dependency property, denominate ImagePathProperty
e ImageLabelProperty
.
public sealed class MyCustomControl : Control
{
public MyCustomControl()
{
this.DefaultStyleKey = typeof(MyCustomControl);
}
public static readonly DependencyProperty ImagePathProperty = DependencyProperty.Register("ImagePath", typeof(ImageSource), typeof(MyCustomControl), new PropertyMetadata(null));
public static readonly DependencyProperty ImageLabelProperty = DependencyProperty.Register("ImageLabel", typeof(String), typeof(MyCustomControl), new PropertyMetadata(null));
public ImageSource ImagePath
{
get { return (ImageSource)GetValue(ImagePathProperty); }
set { SetValue(ImagePathProperty, value); }
}
public String ImageLabel
{
get { return (String)GetValue(ImageLabelProperty); }
set { SetValue(ImageLabelProperty, value); }
}
}
Queste due nuove dependency property devono essere registrate presso il Windows Runtime tramite il metodo DependencyProperty
.Register
. Questo metodo accetta come parametri una stringa che rappresenta il nome della sottostante proprietà (vedi oltre), il tipo di proprietà da aggiungere, il tipo di controllo al quale la proprietà si riferisce ed eventualmente i relativi metadati. Il suffisso "Property" aggiunto al termine del nome della dependency property segue la naming convention adottata dal framework XAML (ad esempio, l'identificativo per la proprietà TextBlock.Text
è TextBlock.TextProperty
). Da notare come ciascuna dependency property sia marcata come public static readonly
.
Definite le due dependency property, il codice implementa le due proprietà cui le prime fanno riferimento: ImagePath
e ImageLabel
. Queste due proprietà rappresentano, rispettivamente, il path verso l'immagine che verrà utilizzata come ImageSource
e la didascalia (label) che accompagnerà l'immagine stessa. Queste due proprietà non sono altro che wrapper attorno alle effettive dependency property (i metodi GetValue
e SetValue
, ereditati dalla classe DependencyObject
, da cui la classe Control
a sua volta deriva, non fanno altro che leggere e impostare il valore di queste ultime).
Dopo che le nuove dependency property sono state registrate, possono essere usate nel codice XAML come qualunque altra proprietà. Il seguente snippet mostra una versione modificata del precedente listato per sfruttare le due nuove proprietà a disposizione (le modifiche sono evidenziate in grassetto):
<Page
x:Class="Demo.Html.it.MyCustomControls.CS.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Demo.Html.it.MyCustomControls.CS"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<local:MyCustomControl Width="300" Height="300" ImagePath="Assets/devleap.png" ImageLabel="http://www.devleap.com" />
</Grid>
</Page>
Adesso non resta che modificare il template del controllo custom, così come definito nel file Generic.xaml
file. Il prossimo snippet mostra questo punto (in grassetto le modifiche):
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Demo.Html.it.MyCustomControls.CS">
<Style TargetType="local:MyCustomControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyCustomControl">
<!-- qui le modidiche-->
<Border Background="LightGray" BorderBrush="DarkGray"
BorderThickness="2" HorizontalAlignment="Center">
<StackPanel HorizontalAlignment="Center">
<Image Source="{TemplateBinding ImagePath}" Margin="5" Height="200" />
<TextBlock TextAlignment="Center" Text="{TemplateBinding ImageLabel}" FontFamily="Segoe UI" FontWeight="Light" FontSize="26" Foreground="Black" />
</StackPanel>
</Border>
<!-- fine modidiche-->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
La prossima immagine mostra il risultato finale: