Associazione dei dati in Windows Presentation Foundation: Parte 1

Shawn Wildermuth

Marzo 2006

Relativo a:
Microsoft Windows Presentation Foundation

Riassunto: si illustra l'utilizzo dell'associazione di dati basata su XAML per la manipolazione dei dati nei progetti Microsoft Windows Presentation Foundation (23 pagine stampate).

Sommario

Introduzione
Associazioni XAML semplificate
A che punto siamo?
Riferimenti

Introduzione

Con Windows Presentation Foundation (in precedenza denominato Avalon) è stata avviata una profonda innovazione nello sviluppo di interfacce utente per rich client. Per la prima volta in WPF la progettazione dell'interfaccia utente viene separata dal codice. Una conseguenza di tale separazione è che, come per ASP.NET, il markup viene memorizzato in un file e il codice in un altro. Tuttavia, la separazione avviene solo al momento della compilazione. Il file di markup viene utilizzato per la generazione del codice combinato con il file di codice, in modo da produrre l'applicazione.

Per facilitare la progettazione, in Microsoft è stato sviluppato un linguaggio markup arricchito denominato XAML. XAML e un linguaggio markup basato su XML che supporta un nuovo modello di sviluppo delle applicazioni con supporto nativo per molti diversi concetti di interfaccia utente, ad esempio disegni 2D e 3D, animazioni, contenimento dei controlli, flusso di controlli e documenti, oltre al modello di associazione di dati arricchiti. Con questo articolo si intende offrire una panoramica sull'associazione di dati in WPF. Per la redazione di questo articolo, si è partiti dalla presunzione che il lettore abbia già una conoscenza almeno superficiale di WPF. In caso contrario, leggere questo articolo di Tim Sneath Architectural Overview of the Windows Presentation Foundation Beta 1 Release (in inglese).

Perché utilizzare l'associazione dei dati

Quando si è alle prime armi con WPF, sorge il dubbio che sia magari più semplice evitare di apprendere le tecniche di associazione dei dati e scrivere invece del codice per la manipolazione dei dati in un progetto. Sebbene questo rappresenti un approccio comunque valido per ottenere dei risultati, è molto probabile che dopo un rapido apprendimento uno sviluppatore non vorrà più fare a meno dell'associazione dei dati basata su XAML. Di seguito è riportato un piccolo esempio.

Nella Figura 1 è illustrata un'interfaccia utente per un semplice progetto WPF. Si tratta di un editor per un feed RSS che consente agli utenti di visualizzare e modificare i feed.

Fare clic qui per ingrandire l'immagine

Figura 1. Editor RSS (fare clic per ingrandire l'immagine)

L'organizzazione dell'editor è piuttosto semplice, come si evince dal codice XAML riportato di seguito.

<Window x:Class="ExampleCS.Window1"
    xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
    Title="ExampleCS"
    Loaded="Window1_Loaded"
    >
  <StackPanel>
    <TextBlock HorizontalAlignment="Center" 
          FontWeight="Bold">
      BlogEditor
    </TextBlock>
    <StackPanel Orientation="Horizontal" 
           HorizontalAlignment="Center">
      <ListBox Name="entryListBox" 
          Height="300" 
          SelectionChanged="entryListBox_Changed"/>
      <Grid Width="500" Margin="5">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="50" />
          <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="25" />
          <RowDefinition Height="25" />
          <RowDefinition Height="25" />
          <RowDefinition Height="*" />
          <RowDefinition Height="25" />
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0" Grid.Column="0">Title:</TextBlock>
        <TextBox Grid.Row="0" Grid.Column="1" Name="titleText" />
        <TextBlock Grid.Row="1" Grid.Column="0">Url:</TextBlock>
        <TextBox Grid.Row="1" Grid.Column="1" Name="urlText" />
        <TextBlock Grid.Row="2" Grid.Column="0">Date:</TextBlock>
        <TextBox Grid.Row="2" Grid.Column="1" Name="dateText" />
        <TextBlock Grid.Row="3" Grid.Column="0">Body:</TextBlock>
        <TextBox Grid.Row="3" Grid.Column="1" 
            Name="bodyText" 
            TextWrapping="Wrap" />
        <Button Grid.Row="4" 
                  Grid.ColumnSpan="2" 
                  Grid.Column="1"
            Click="updateButton_Click">
          Update
        </Button>
      </Grid>
    </StackPanel>
  </StackPanel>
</Window>

Prestare particolare attenzione ai gestori di eventi in grassetto, dove si concentra la maggior parte del codice per il caricamento del feed RSS e la compilazione dei controlli WPF.

C#:

XmlDocument blog = new XmlDocument();
const string BLOGURL = @"z:\adoguy.RSS";

public Window1()
{
  InitializeComponent();
  blog.Load(BLOGURL);
}

void Window1_Loaded(object sender, RoutedEventArgs e) 
{
  FillListBox();
}

void FillListBox()
{
  entryListBox.Items.Clear();

  XmlNodeList nodes = blog.SelectNodes("//item");
  foreach (XmlNode node in nodes)
  {
    ListBoxItem item = new ListBoxItem();
    item.Tag = node;
    item.Content = node["title"].InnerText;
    entryListBox.Items.Add(item);
  }
}

Visual Basic .NET:

  Dim blog As New XmlDocument
  Const BLOGURL As String = "z:\adoguy.RSS"

  Public Sub New()
    InitializeComponent()
    blog.Load(BLOGURL)
  End Sub

  Sub Window1_Loaded(ByVal sender As Object, _
                     ByVal e As RoutedEventArgs) 
    FillListBox()

  End Sub

  Sub FillListBox()

    entryListBox.Items.Clear()

    Dim nodes As XmlNodeList = blog.SelectNodes("//item")

    For Each node As XmlNode In nodes

      Dim item As New ListBoxItem
      item.Tag = node
      item.Content = node("title").InnerText
      entryListBox.Items.Add(item)

    Next

  End Sub

Come è possibile notare dal codice, viene caricato un documento XML con il feed RSS e la ListBox viene compilata con i titoli delle voci.

A questo punto è necessario gestire gli eventi che si verificano quando si esegue una selezione nella ListBox.

C#:

void entryListBox_Changed(object sender, RoutedEventArgs e)
{
  if (entryListBox.SelectedIndex != -1)
  {
    XmlNode node = ((ListBoxItem)entryListBox.SelectedItem).Tag as XmlNode;
    if (node != null)
    {
      titleText.Text = node["title"].InnerText;
      urlText.Text = node["guid"].InnerText;
      dateText.Text = node["pubDate"].InnerText;
      bodyText.Text = node["description"].InnerText;
    }
  }
}

Visual Basic .NET:

  Sub entryListBox_Changed(ByVal sender As Object, _
                           ByVal e As SelectionChangedEventArgs) 

    If entryListBox.SelectedIndex <> -1 Then

      Dim node As XmlNode = CType(entryListBox.SelectedItem, ListBoxItem).Tag
      If Not node Is Nothing Then

        titleText.Text = node("title").InnerText
        urlText.Text = node("guid").InnerText
        dateText.Text = node("pubDate").InnerText
        bodyText.Text = node("description").InnerText

      End If

    End If

  End Sub

Quando la ListBox viene modificata, le TextBox vengono compilate con la voce del feed RSS selezionata. A tal fine è necessario ottenere il testo interno della voce del feed. Si noti che è anche necessario conservare una copia del nodo con la ListBoxItem ed eseguire il casting necessario per ciascun gestore eventi.

Infine è necessario gestire gli eventi che si verificano quando si preme il pulsante di aggiornamento.

C#:

void updateButton_Click(object sender, RoutedEventArgs e)
{
  if (entryListBox.SelectedIndex != -1)
  {
    XmlNode node = ((ListBoxItem)entryListBox.SelectedItem).Tag as XmlNode;
    if (node != null)
    {
      node["title"].InnerText = titleText.Text;
      node["guid"].InnerText = urlText.Text;
      node["pubDate"].InnerText = dateText.Text;
      node["description"].InnerText = bodyText.Text;

      blog.Save(BLOGURL);

      FillListBox();
    }
  }
}

Visual Basic .NET:

  Sub updateButton_Click(ByVal sender As Object, _
                         ByVal e As RoutedEventArgs)

    If entryListBox.SelectedIndex <> -1 Then

      Dim node As XmlNode = CType(entryListBox.SelectedItem, ListBoxItem).Tag

      If Not node Is Nothing Then

        node("title").InnerText = titleText.Text
        node("guid").InnerText = urlText.Text
        node("pubDate").InnerText = dateText.Text
        node("description").InnerText = bodyText.Text

        blog.Save(BLOGURL)

        FillListBox()

      End If

    End If

  End Sub

In questa sezione di codice vengono aggiornati i nodi in base alle nuove informazioni, viene salvato il documento XML e avviata la ricompilazione della ListBox, nel caso il titolo sia stato modificato. Come si vede, a fronte di una piccola applicazione molto semplice, è necessaria una quantità notevole di codice. È possibile semplificare queste operazioni con l'associazione dei dati XAML? Certo, come è possibile notare dall'applicazione dell'associazione dei dati XAML allo stesso progetto.

<StackPanel xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
  xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">
  <StackPanel.Resources>
    <XmlDataProvider x:Key="RssFeed" Source="z:\adoguy.RSS" />
  </StackPanel.Resources>
  <TextBlock HorizontalAlignment="Center" 
      FontWeight="Bold">
    Blog Editor
  </TextBlock>
  <StackPanel Orientation="Horizontal" 
    HorizontalAlignment="Center">
    <ListBox Name="entryListBox" 
        Height="300" 
        ItemsSource="{Binding Source={StaticResource RssFeed}, XPath=//item}"
    >
      <ListBox.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding XPath=title}" />
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>
    <Grid Width="500" 
        Margin="5" 
        DataContext="{Binding ElementName=entryListBox, Path=SelectedItem}" >
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="50" />
        <ColumnDefinition Width="*" />
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
        <RowDefinition Height="25" />
        <RowDefinition Height="25" />
        <RowDefinition Height="25" />
        <RowDefinition Height="*" />
        <RowDefinition Height="25" />
      </Grid.RowDefinitions>
      <TextBlock Grid.Row="0" Grid.Column="0">Title:</TextBlock>
      <TextBox Grid.Row="0" Grid.Column="1" 
          Name="titleText" 
          Text="{Binding XPath=title}" />
      <TextBlock Grid.Row="1" Grid.Column="0">Url:</TextBlock>
      <TextBox Grid.Row="1" Grid.Column="1" 
          Name="urlText" 
          Text="{Binding XPath=guid}" />
      <TextBlock Grid.Row="2" Grid.Column="0">Date:</TextBlock>
      <TextBox Grid.Row="2" Grid.Column="1" 
          Name="dateText" 
          Text="{Binding XPath=pubDate}" />
      <TextBlock Grid.Row="3" Grid.Column="0">Body:</TextBlock>
      <TextBox Grid.Row="3" Grid.Column="1" 
          Name="bodyText" 
          TextWrapping="Wrap" 
          Text="{Binding XPath=description}" />
      <Button Grid.Row="4" Grid.ColumnSpan="2" Grid.Column="1" >
          Update
      </Button>
    </Grid>
  </StackPanel>
</StackPanel>

Questo esempio di XAML funziona senza codice sottostante. Nella Figura 2 è illustrata l'applicazione che funziona con il solo XAML (con l'utilizzo di XAMLPad).

Fare clic qui per ingrandire l'immagine

Figura 2. Editor RSS in XAMLPad (fare clic per ingrandire l'immagine)

È giusto chiedersi come possa funzionare. Il segreto sta nell'utilizzo dell'associazione XAML per la maggior parte delle operazioni da compiere. Ciascuna delle tecniche XAML vengono trattate nel dettaglio nel resto dell'articolo, ma ora è possibile esaminare rapidamente alcuni aspetti dell'associazione dei dati in modo da spiegare come funziona.

Prima di tutto, si crea un oggetto XmlDataProvider per il caricamento e la gestione del documento XML per l'editor.

<XmlDataProvider x:Key="RssFeed" Source="z:\adoguy.RSS" />

In questo modo si richiede all'XAML il caricamento di un documento XML dall'unità Z: per la compilazione del form.

Quindi si associa la ListBox.

<ListBox Name="entryListBox" 
        Height="300" 
        ItemsSource="{Binding Source={StaticResource RssFeed}, XPath=//item}"
    >

Si istruisce in tal modo la casella di riepilogo in modo che venga rilevata nell'XAML la risorsa denominata RssFeed da utilizzare come origine dati. È inoltre previsto che per la casella di riepilogo si ottenga l'elenco delle voci da associare dall'espressione XPath (in questo caso, tutti gli elementi del documento).

Quindi si specificano gli elementi da visualizzare nell'elenco di voci della casella di riepilogo, indicando il modello di dati.

<DataTemplate>
  <TextBlock Text="{Binding XPath=title}" />
</DataTemplate>

Grazie a queste informazioni ListBox è in grado di creare un oggetto TextBlock per ciascuna voce e utilizzare XPath per determinare gli elementi da visualizzare in ciascun TextBlock.

Si procede quindi all'associazione della griglia che contiene tutti i dettagli.

<Grid Width="500" 
        Margin="5" 
        DataContext="{Binding ElementName=entryListBox, Path=SelectedItem}" >

Qui si richiede all'XAML l'associazione del contenitore (in questo caso Grid) alla voce selezionata nella ListBox. Si utilizza ElementName in luogo di Source perché si desidera eseguire l'associazione a un controllo del documento XAML invece che a dati esterni. Quando si imposta il DataContext, si consente l'impostazione di tutti i controlli all'interno di Grid sul medesimo oggetto (in questo caso SelectedItem).

Si associa quindi ciascuna TextBox alla parte di documento XML desiderata.

<TextBlock Grid.Row="0" Grid.Column="0">Title:</TextBlock>
      <TextBox Grid.Row="0" Grid.Column="1" 
          Name="titleText" 
          Text="{Binding XPath=title}" />
      <TextBlock Grid.Row="1" Grid.Column="0">Url:</TextBlock>
      <TextBox Grid.Row="1" Grid.Column="1" 
          Name="urlText" 
          Text="{Binding XPath=guid}" />
      <TextBlock Grid.Row="2" Grid.Column="0">Date:</TextBlock>
      <TextBox Grid.Row="2" Grid.Column="1" 
          Name="dateText" 
          Text="{Binding XPath=pubDate}" />
      <TextBlock Grid.Row="3" Grid.Column="0">Body:</TextBlock>
      <TextBox Grid.Row="3" Grid.Column="1" 
          Name="bodyText" 
          TextWrapping="Wrap" 
          Text="{Binding XPath=description}" />

Poiché si è già impostato il DataContext, è possibile semplificare specificando l'espressione XPath per ottenere le sezioni di feed RSS necessarie.

Gli elementi di riflessione proposti sono già moltissimi per un semplice inizio. È bene soffermarsi un attimo a questo punto, senza pretendere di apprendere tutto in un colpo solo. Nella sezione che segue, vengono analizzate singolarmente molte delle idee accennate nell'esempio precedente.

Associazioni XAML semplificate

Per esemplificare il funzionamento dell'oggetto Binding, è possibile esaminare il semplicissimo documento XAML riportato di seguito.

<Window xmlns="http://schemas.microsoft.com/winfx/avalon/2005">
  <Canvas>
    <TextBox Text="This is a TextBox" />
    <TextBlock Canvas.Top="25" >Test</TextBlock>
  </Canvas>
</Window>

Si crea in questo modo un semplice pannello con i due controlli, come illustrato nella Figura 3.

Figura 3. Un semplice pannello XAML

Può essere necessario associare il TextBlock in modo che venga visualizzato il testo di TextBox durante la digitazione. A tal fine, occorre un oggetto Binding che colleghi i due oggetti. La prima operazione da eseguire consiste nell'aggiungere un nome al TextBox, in modo che sia possibile farvi riferimento tramite il nome dell'elemento.

<TextBox Name="theTextBox" />

Quindi è necessario aggiungere un Binding all'elemento Text del TextBlock.

<TextBlock Canvas.Top="25">
  <TextBlock.Text>
    <Binding ElementName="theTextBox" Path="Text" />
  </TextBlock.Text>
</TextBlock>

Si definisce così che il Text del TextBlock deve essere impostato mentre l'utente digita il testo all'interno del controllo theTextBox.

Assemblando tutti gli elementi si produce il codice riportato di seguito.

<Window xmlns="http://schemas.microsoft.com/winfx/avalon/2005">
  <Canvas>
    <TextBox Name="theTextBox" Text="Hello" />
    <TextBlock Canvas.Top="25">
      <TextBlock.Text>
        <Binding ElementName="theTextBox" Path="Text" />
      </TextBlock.Text>
    </TextBlock>
  </Canvas>
</Window>

Con questo XAML si crea un form che modifica il TextBlock durante la digitazione in TextBox, come illustrato nella Figura 4.

Figura 4. Un controllo associato

Bene. Ecco la prima associazione. Ma la sintassi XML è ancora un po' ridondante ed esiste di certo un metodo di scrittura migliore.

Utilizzo dell'associazione a sintassi abbreviata

Nell'esempio precedente si è creata un'associazione dei dati aggiungendo degli elementi Binding alle proprietà. In questo semplice esempio non sembra troppo faticoso eseguire l'associazione in questo modo. Tuttavia, nel caso di documenti XAML più complessi, è probabile che questa sintassi estesa diventi ridondante. Per risolvere il problema, XAML supporta una versione abbreviata della sintassi di associazione.

Quando, ad esempio, si utilizza la sintassi abbreviata, l'esempio illustrato in precedenza diventa come segue.

<Window xmlns="http://schemas.microsoft.com/winfx/avalon/2005">
  <Canvas>
    <TextBox Name="theTextBox" Text="Hello" />
    <TextBlock Canvas.Top="25"
               Text="{Binding ElementName=theTextBox, Path=Text}" />
  </Canvas>
</Window>

La sintassi abbreviata è racchiusa in parentesi graffa ({}), inizia con Binding e contiene coppie di nome/valore per gli attributi dell'associazione. Lo scopo della sintassi abbreviata è ridurre il numero di digitazioni e ottenere una maggiore leggibilità per l'associazione dei dati. Nella parte rimanente dell'articolo si utilizzerà la sintassi abbreviata.

Associazione di origini di dati

Fino a ora negli esempi di associazione si è utilizzato come origine un altro controllo. In genere nei progetti basati su XAML è però necessario definire associazioni a origini di dati diverse dai controlli esterni. Un elemento chiave della maggior parte delle associazioni di dati è la proprietà Source. Negli esempi precedenti si utilizza la proprietà ElementName per ottenere l'associazione a un controllo, al posto della proprietà Source. Di solito nelle applicazioni si eseguono associazioni a origini dati più significative, quali oggetti XML o .NET. Il supporto in XAML è garantito grazie agli oggetti provider di dati. In XAML sono integrati due tipi di provider di dati: ObjectDataProvider e XmlDataProvider. ObjectDataProvider viene utilizzato per l'associazione a e da oggetti .NET, mentre XmlDataProvider viene utilizzato per l'associazione a e da frammenti e documenti XML. È possibile specificare un provider di dati nella sezione risorse di qualsiasi contenitore XAML.

Utilizzo di XmlDataProvider

Ad esempio:

<StackPanel xmlns="http://schemas.microsoft.com/winfx/avalon/2005" 
            xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">
  <StackPanel.Resources>
    <XmlDataProvider x:Key="FavoriteColors">      <Colors xmlns="">
        <Color>Blue</Color> 
        <Color>Black</Color> 
        <Color>Green</Color> 
        <Color>Red</Color> 
      </Colors>    </XmlDataProvider>
  </StackPanel.Resources>
  <TextBlock HorizontalAlignment="Center" 
             FontWeight="Bold">
    XML Example
  </TextBlock>
  <ListBox Width="200" Height="300" 
           ItemsSource="{Binding Source={StaticResource FavoriteColors}, XPath=/Colors/Color}">
  </ListBox>
</StackPanel>

All'interno delle risorse di StackPanel è disponibile un oggetto XmlDataProvider. x:Key è il nome da utilizzare per riferimento negli oggetti Binding. All'interno del provider si è creato un XML inline, da utilizzare come origine dell'associazione di dati. Nel Binding per la ListBox è stato specificato il provider come Source dell'associazione. Quando un'origine dati si trova nel documento XAML, è necessario specificare che l'oggetto è una risorsa statica, come illustrato. Infine, si utilizza un'istruzione XPath per specificare l'insieme del documento XML da utilizzare per la compilazione della ListBox. Il codice consente di generare il form illustrato nella Figura 5.

Figura 5. Associazione dei dati XML

In alternativa è possibile specificare che il provider deve utilizzare un percorso o URL per individuare l'origine dell'XML e creare il medesimo form, tramite l'indicazione dell'attributo Source di XmlDataProvider.

<XmlDataProvider x:Key="FavoriteColors" 
                 Source="D:\Writing\msdn\avalondatabindingpt1\xaml\colors.xml"
/>

È possibile che l'attributo Source di XmlDataProvider punti verso URL standard, per consentire la creazione di un accesso rapido alle API XML, ad esempio RSS.

<StackPanel xmlns="http://schemas.microsoft.com/winfx/avalon/2005" 
            xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">
  <StackPanel.Resources>
    <XmlDataProvider x:Key="MyRSS" 
      Source="http://adoguy.com/RSS"
    />
  </StackPanel.Resources>
  <TextBlock HorizontalAlignment="Center" 
             FontWeight="Bold">
    XML Example
  </TextBlock>
  <ListBox Width="500" Height="300" 
           ItemsSource="{Binding Source={StaticResource MyRSS}, 
           XPath=//item/title}">
  </ListBox>
</StackPanel>

Se si richiama un feed RSS, sarà possibile creare una pagina in cui vengano rapidamente visualizzati in una ListBox gli argomenti di un blog, ad esempio, come illustrato nella Figura 6.

Figura 6. Elenco degli argomenti del blog

Utilizzo di ObjectDataProvider

A volte è necessario ricorrere all'associazione a oggetti .NET. Sono questi i casi in cui torna utile ObjectDataProvider. Con questo provider di dati è possibile creare associazioni per dati .NET.

È, ad esempio, possibile creare un semplice insieme di stringhe in .NET, come indicato di seguito.

public class MyStrings : List<String>
{
  public MyStrings()
  {
    this.Add("Hello");
    this.Add("Goodbye");
    this.Add("Heya");
    this.Add("Cya");
  }
}

- oppure -

Public Class MyStrings
  Inherits List(Of String)

  Public Sub New()
    Me.Add("Hello")
    Me.Add("Goodbye")
    Me.Add("Heya")
    Me.Add("Cya")
  End Sub

End Class

Quindi è possibile creare un ObjectDataProvider per generare e utilizzare un'istanza di questa classe.

<Window xmlns="http://schemas.microsoft.com/winfx/avalon/2005" 
              xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
              Title="Simple Source Binding"
              x:Class="XamlExamples.SimpleSource"
 >
  <Window.Resources>
    <ObjectDataProvider TypeName="XamlExamples.MyStrings, XamlExamples"
x:Key="MyStringData" />
  </Window.Resources>  
    <StackPanel>
      <TextBlock HorizontalAlignment="Center" 
               FontWeight="Bold">
      Simple Source Example
    </TextBlock>
    <ListBox Name="theListBox" Width="200" Height="300" 
      ItemsSource="{Binding Source={StaticResource MyStringData}}"/>
  </StackPanel>
</Window>

Si noti come in ObjectDataProvider si specifichino TypeName e Key. TypeName viene utilizzato per individuare e caricare un oggetto da sfruttare come origine dei dati. Con il provider si crea un'istanza di questa classe specificata nell'attributo TypeName. Per eseguire associazioni a oggetti che già esistono per oggetti runtime o statici, non è possibile utilizzare la sintassi XAML.

Oltre a poter utilizzare ObjectDataProvider per l'associazione a oggetti CLR, è possibile utilizzare un'istruzione di elaborazione nel documento XAML per l'aggiunta di un intero spazio dei nomi di oggetti CLR ai tipi supportati del documento.

È, ad esempio, possibile eseguire il mapping di tutte le classi del progetto nell'XAML nel modo seguente:

<?Mapping XmlNamespace="ProjectTypes" ClrNamespace="XamlExamples" ?>
<Window xmlns="http://schemas.microsoft.com/winfx/avalon/2005" 
              xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
              Title="Mapped Source Binding"
              x:Class="XamlExamples.MappedSource"
              xmlns:c="ProjectTypes"
 >
  <Window.Resources>
    <c:MyStrings x:Key="MyStringData" />
  </Window.Resources>  
    <StackPanel>
      <TextBlock HorizontalAlignment="Center" 
               FontWeight="Bold">
      Simple Source Example
    </TextBlock>
    <ListBox Name="theListBox" Width="200" Height="300" 
      ItemsSource="{Binding Source={StaticResource MyStringData}}" />
  </StackPanel>
</Window>

Con le istruzioni di mapping si imposta il compilatore XAML in modo da caricare tutti i tipi dello spazio dei nomi XamlExamples nel documento XAML. Sarà quindi possibile importare lo spazio dei nomi con l'attributo xmlns. È importante notare come il nome nell'attributo xmlns sia identico all'attributo XmlNamespace nell'istruzione di mapping. Lo spazio dei nomi .NET da importare è specificato da ClrNamespace.

Dopo l'importazione dello spazio dei nomi, sarà possibile aggiungere una risorsa che fa riferimento alla classe dell'origine dati, specificando il nome della classe (ad esempio, c:MyStrings). L'associazione dei dati è identica a quella vista negli esempi precedenti, poiché nel codice XAML non si tiene conto del tipo di origine dati, ma solo del fatto che si tratta di un'origine dati.

Modalità di associazione

In genere si impostano associazioni dei dati biunivoche. L'oggetto Binding supporta varie modalità per gli utilizzi più diversi, come illustrato nella tabella 1.

Tabella 1. Modalità di associazione

Modalità di associazione Descrizione
TwoWay Consente di estendere le modifiche da un controllo associato, o dall'origine dell'associazione, all'altro e viceversa (modalità predefinita).
OneWay Consente di estendere le modifiche solo dall'origine al controllo. Quando si verificano modifiche nell'origine, vengono modificati anche i dati del controllo associato.
OneTime I dati vengono associati solo all'avvio e le modifiche apportate all'origine vengono ignorate dopo la prima compilazione del controllo con dei dati.

Per specificare la modalità, è sufficiente includerla nel markup come indicato di seguito.

{Binding ElementName=theTextBox, Path=Text, Mode=OneTime}

Per avere una dimostrazione del modo in cui opera l'associazione bidirezionale, creare un oggetto Canvas con due caselle di testo, una associata all'altra.

<Window xmlns="http://schemas.microsoft.com/winfx/avalon/2005">
  <Canvas>
    <TextBox Name="theTextBox" Text="Hello" />
    <TextBox Canvas.Top="25"
             Text="{Binding ElementName=theTextBox, Path=Text, Mode=TwoWay}" />
  </Canvas>
</Window>

Se si incolla questo codice nello strumento XAMLPad fornito con SDK, si noterà che la casella di testo di origine aggiorna la casella di testo associata durante la digitazione, mentre l'aggiornamento dal controllo associato al controllo di origine ha luogo solo quando il controllo associato passa allo stato non attivo. Se si modifica Mode in OneWay o OneTime, si osserverà come queste due modalità comportino un diverso funzionamento dell'associazione.

Controllo della fase di associazione

Oltre alla modalità, è possibile specificare anche il momento in cui devono essere apportate le modifiche dipendenti dall'associazione, utilizzando UpdateSourceTrigger. È possibile specificare che l'associazione deve avviare la modifica solo in determinati momenti definendo il tipo di UpdateSourceTrigger.

{Binding ElementName=theTextBox, Path=Text, UpdateSourceTrigger=LostFocus}

Con la proprietà UpdateSourceTrigger si definisce il momento in cui deve essere aggiornata l'origine. Questa funzione è valida solo per le associazioni Mode=TwoWay (predefinite). I valori validi per UpdateSourceTrigger sono riportati nella Tabella 2.

Tabella 2. Valori di UpdateSourceTrigger

UpdateSourceTrigger Descrizione
Explicit L'origine viene aggiornata solo quando viene esplicitamente richiamato il metodo BindingExpression.UpdateSource.
LostFocus L'origine viene aggiornata non appena il controllo associato non è più attivo.
PropertyChanged Le modifiche vengono apportate all'origine ogni volta che la proprietà viene modificata (modalità predefinita).
Utilizzo di DataContext

L'ultimo concetto che si esamina in questo articolo è l'utilizzo di DataContext. DataContext viene utilizzato in modo specifico per definire che tutti i controlli all'interno di un determinato contenitore verranno associati a un oggetto comune.

Di seguito è riportato un esempio in cui si utilizza Canvas per visualizzare un valore e del testo di un nodo specifico nel documento XML.

<StackPanel xmlns="http://schemas.microsoft.com/winfx/avalon/2005" 
            xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">
  <StackPanel.Resources>
    <XmlDataProvider x:Key="FavoriteColors">
      <Colors xmlns="">
        <Color ID="1">Blue</Color> 
        <Color ID="2">Black</Color> 
        <Color ID="3">Green</Color> 
        <Color ID="4">Red</Color> 
      </Colors>
    </XmlDataProvider>
  </StackPanel.Resources>
  <TextBlock HorizontalAlignment="Center" 
             FontWeight="Bold">
    XML DataContext Example
  </TextBlock>
  <Canvas DataContext="{Binding Source={StaticResource FavoriteColors}, XPath='/Colors/Color[@ID=2]'}">
    <TextBlock Text="{Binding XPath=@ID}" />
    <TextBlock Canvas.Top="25" Text="{Binding XPath=.}" />
  </Canvas>
</StackPanel>

Se si imposta DataContext sul documento XML (e un'espressione XPath specifica), si definiranno tutti i controlli in Canvas che non contengono una Source in modo che possano utilizzare il DataContext del contenitore. Sarà quindi possibile associare i TextBlock specificando solo le espressioni XPath. È necessario sottolineare come le espressioni XPath di ciascun TextBlock siano espressioni XPath relative. Lo stesso discorso vale per l'associazione degli oggetti.

<Window xmlns="http://schemas.microsoft.com/winfx/avalon/2005" 
              xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
              Title="Simple Source Binding"
              x:Class="XamlExamples.SimpleSource"
 >
  <Window.Resources>
    <ObjectDataProvider TypeName="XamlExamples.MyStrings, XamlExamples"
                        x:Key="MyStringData" />
  </Window.Resources>  
    <StackPanel>
      <TextBlock HorizontalAlignment="Center" 
               FontWeight="Bold">
      Object DataContext Example
    </TextBlock>
    <Canvas DataContext="{Binding Source={StaticResource MyStringData}}">
      <TextBlock Text="{Binding Path=Length}" />
      <TextBlock Canvas.Top="25" Text="{Binding Path=Item[0]}" />
    </Canvas>
  </StackPanel>
</Window>

Per utilizzare degli oggetti in luogo dell'XML, è sufficiente specificare espressioni Path invece di XPath.

A che punto siamo?

Si è dimostrato come associare dati direttamente in XAML con l'oggetto Binding, sia nella versione estesa che abbreviata. Grazie all'adozione di provider di dati XML o oggetto, è possibile impostare nelle applicazioni delle associazioni a diversi tipi di oggetti. Si è inoltre illustrato in che modo sia possibile eseguire associazioni ad altri controlli utilizzando la sintassi ElementName dell'oggetto Binding. Nella parte successiva di questa serie si vedrà come sfruttare queste informazioni per definire associazioni a dati di database reali, utilizzando i propri oggetti o i contenitori di dati .NET (ad esempio, DataSources e DataSets).

Riferimenti

Mostra: