Esporta (0) Stampa
Espandi tutto

Associazione dei dati in Windows Presentation Foundation: Parte 2

Shawn Wildermuth

Marzo 2006

Relativo a:
Microsoft Windows Presentation Foundation

Riassunto: la seconda parte della serie continua con l'illustrazione dell'utilizzo dell'associazione di dati basata su XAML per la manipolazione dei dati nei progetti Microsoft Windows Presentation Foundation (23 pagine stampate).

Sommario

Introduzione
Associazione ai dati del database
A che punto siamo?
Riferimenti

Introduzione

La maggior parte degli esempi di WPF di cui si parla nella community riguardano il motore grafico. La maggior parte degli sviluppatori di interfacce utente dedica gran parte del lavoro alla creazione di form di immissione dei dati nel mondo dello sviluppo aziendale. WPF rappresenta una soluzione per i problemi degli sviluppatori? Certo...

Associazione ai dati del database

Nella prima parte di questa serie è stata analizzata la sintassi delle associazioni non elaborate e l'associazione di oggetti semplici a oggetti XAML. Sebbene questa sia una parte fondamentale del puzzle, nella maggior parte delle situazioni il problema reale è l'associazione ai dati che sono archiviati in un database. In genere il supporto per l'associazione in due scenari diversi è il seguente: Dati del database (ad esempio, DataSet, DataTable e DataRow) e oggetti business personalizzati.

Associazione ai dati del database

I database rappresentano tuttora il fulcro del lavoro di sviluppo odierno, in particolare per lo sviluppo aziendale. Per fornire un esempio, è possibile analizzare una semplice finestra di dialogo WPF che consente all'utente di ricercare i dipendenti in un database. Si desidera visualizzare una quantità ridotta di informazioni, compresa un'immagine del dipendente, nel browser. Sarà quindi necessario caricare una tabella con tutte le informazioni desiderate. A tal fine è possibile creare una DataTable nuova con le informazioni del database:

In C#:

DataTable theTable = new DataTable();
string connString = 
  ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
string query = @"SELECT EmployeeID, FirstName, LastName, Title, HireDate, Photo 
                 FROM Employees";

// Fill the Set with the data
using (SqlConnection conn = new SqlConnection(connString))
{
  SqlDataAdapter da = new SqlDataAdapter(query, conn);
  da.Fill(theTable);
}

In Visual Basic .NET:

Dim theTable As DataTable =  New DataTable() 
String connString = 
  ConfigurationManager.ConnectionStrings("Northwind").ConnectionString
String query = "SELECT EmployeeID, FirstName, LastName, Title, HireDate, Photo " + _
               "  FROM Employees"
 
' Fill the Set with the data
Using conn as New SqlConnection(connString))
  Dim da As SqlDataAdapter =  New SqlDataAdapter(query,conn) 
  da.Fill(theTable)
End Using

Una volta ottenuti i dati, sarà possibile utilizzarli per impostare DataContext in modo da consentire l'associazione in XAML:

In C#:

// Set the Data Context
DataContext = theTable;

In Visual Basic .NET:

' Set the Data Context
DataContext = theTable

Una volta ottenuti e forniti i dati alla finestra, sarà possibile eseguire l'associazione dei dati in XAML. Binding nel ComboBox si limita a istruire l'associazione in modo da ottenere i dati dal DataContext della tabella padre (in questo caso, si risale nella struttura di controllo fino a che non si trova un DataContext in Window):

    <ComboBox Name="theCombo" 
              IsSynchronizedWithCurrentItem="True"                   ItemsSource="{Binding}" 
              ... />

L'attributo IsSynchronizedWithCurrentItem è importante perchè, quando la selezione viene modificata, è l'attributo che modifica la "voce corrente" per quanto riguarda la finestra. Il motore WPF ottiene così l'informazione che verrà utilizzato questo oggetto per modificare la voce corrente. Senza questo attributo, la voce corrente in DataContext non verrebbe modificata, con la conseguente presunzione che nella casella di testo sia ancora attiva la prima voce dell'elenco.

Per visualizzare i nomi dei dipendenti nella casella combinata, è necessario creare associazioni in ItemsTemplate per visualizzare FirstName e LastName dal DataTable:

    <DataTemplate x:Key="EmployeeListTemplate">
      <StackPanel Orientation="Horizontal">
      <TextBlock Text="{Binding Path=FirstName}" />
      <TextBlock Text=" " />
      <TextBlock Text="{Binding Path=LastName}" />
      </StackPanel>
    </DataTemplate>

In seguito si aggiungono delle caselle per ottenere nome, titolo e data di assunzione in:

      <TextBlock Canvas.Top="5">First Name:</TextBlock>
      <TextBox Canvas.Top="5" Text="{Binding Path=FirstName}" />
      <TextBlock Canvas.Top="25">Last Name:</TextBlock>
      <TextBox Canvas.Top="25" Text="{Binding Path=LastName}" />
      <TextBlock Canvas.Top="45">Title:</TextBlock>
      <TextBox Canvas.Top="45" Text="{Binding Path=Title}" />
      <TextBlock Canvas.Top="65">Hire Date:</TextBlock>
      <TextBox Canvas.Top="65" Text="{Binding Path=HireDate}" />

Poiché si desidera visualizzare anche la foto, è necessario aggiungere un'immagine a XAML:

      <Image Name="theImage" Canvas.Top="5" Canvas.Left="5" Width="75"/>

L'unico problema posto dall'immagine è che non è supportata l'associazione automatica dei dati della foto all'immagine. Per semplificare l'operazione, è possibile gestire l'evento SelectionChanged del ComboBox per l'inserimento dell'immagine:

    <ComboBox Name="theCombo" 
              IsSynchronizedWithCurrentItem="True"                   
              Width="200" 
              ItemsSource="{Binding}" 
              ItemTemplate="{StaticResource EmployeeListTemplate}"
              SelectionChanged="theCombo_OnSelectionChanged" />

Nel codice è necessario caricare l'immagine dal DataTable e creare un oggetto BitmapImage per compilare il tag Image. È da notare che non si tratta di un Bitmap da GDI+ (System.Drawing), ma di un oggetto Bitmap nuovo in WPF:

// Handler to show the image
void theCombo_OnSelectionChanged(object sender, RoutedEventArgs e)
{
  ShowPhoto();
}

// Shows the Photo for the currently selected item
void ShowPhoto()
{
  object selected = theCombo.SelectedItem;
  DataRow row = ((DataRowView)selected).Row;
  
  // Get the raw bytes of the image
  byte[] photoSource = (byte[])row["Photo"];

  // Create the bitmap object
  // NOTE: This is *not* a GDI+ Bitmap object
  BitmapImage bitmap = new BitmapImage();
  MemoryStream strm = new MemoryStream();

  // Well-known work-around to make Northwind images work
  int offset = 78;
  strm.Write(photoSource, offset, photoSource.Length - offset);

  // Read the image into the bitmap object
  bitmap.BeginInit();
  bitmap.StreamSource = strm;
  bitmap.EndInit();

  // Set the Image with the Bitmap
  theImage.Source = bitmap;
  
}

In Visual Basic .NET:

' Handler to show the image
Sub theCombo_OnSelectionChanged(ByVal sender As Object, ByVal e As RoutedEventArgs)

  ShowPhoto();

End Sub

// Shows the Photo for the currently selected item
Sub ShowPhoto()

  Dim selected As Object =  theCombo.SelectedItem 
  Dim row As DataRow = (CType(selected, DataRowView)).Row 
 
  ' Get the raw bytes of the image
  Dim photoSource() As Byte = CType(row("Photo"), Byte())
 
  ' Create the bitmap object
  ' NOTE: This is *not* a GDI+ Bitmap object
  Dim bitmap As BitmapImage =  New BitmapImage() 
  Dim strm As MemoryStream =  New MemoryStream() 
 
  ' Well-known work-around to make Northwind images work
  Dim offset As Integer =  78 
  strm.Write(photoSource, offset, photoSource.Length - offset)
 
  ' Read the image into the bitmap object
  bitmap.BeginInit()
  bitmap.StreamSource = strm
  bitmap.EndInit()
 
  ' Set the Image with the Bitmap
  theImage.Source = bitmap
 
End Sub

Si ottiene il SelectedItem dal ComboBox e lo si converte in un DataRow in modo da ottenere i dati di interesse. Quindi si ottiene la matrice di byte dalla colonna Photo. Questa è la foto archiviata nel database Northwind. È possibile utilizzare un flusso in memoria per trasferire i byte della foto in un oggetto BitmapImage. L'unica modifica è una soluzione utilizzata di solito per saltare i primi 78 byte dell'intestazione dell'immagine Northwind, che non viene più utilizzata. Dopo la lettura, è possibile assegnare il flusso nel bitmap all'oggetto Image come sorgente.

È necessario accertarsi che l'associazione di dati sia biunivoca, quindi si crea un pulsante che consenta di visualizzare le informazioni correnti in modo da individuare il DataRow desiderato:

In C#:

void SaveButton_OnClick(object sender, RoutedEventArgs e)
{
  object selected = theCombo.SelectedItem;
  DataRow row = ((DataRowView)selected).Row;

  MessageBox.Show(string.Format("{0} {1} {2} - {3:d}", 
    row["Title"], row["FirstName"], row["LastName"],  row["HireDate"]));
}

In Visual Basic .NET:

Sub SaveButton_OnClick(ByVal sender As Object, ByVal e As RoutedEventArgs)

  Dim selected As Object =  theCombo.SelectedItem 
  Dim row As DataRow = (CType(selected, DataRowView)).Row 
 
  MessageBox.Show(String.Format("{0} {1} {2} - {3:d}", _
    row("Title"), row("FirstName"), row("LastName"),  row("HireDate")))

End Sub

Al termine il file XAML completo avrà il seguente aspetto:

<Window x:Class="ExampleCS.EmployeeBrowser"
    xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
    Title="Employee Browser"
    Loaded="OnLoaded" 
    Width="300"
    Height="170" 
    WindowStartupLocation="CenterScreen"
    >
  <Window.Resources>
    <DataTemplate x:Key="EmployeeListTemplate">
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding Path=FirstName}" />
        <TextBlock Text=" " />
        <TextBlock Text="{Binding Path=LastName}" />
      </StackPanel>
    </DataTemplate>
  </Window.Resources>
  <Window.Background>
    <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
      <LinearGradientBrush.GradientStops>
        <GradientStop Color="DarkGray" Offset="0" />
        <GradientStop Color="White" Offset=".75" />
        <GradientStop Color="DarkGray" Offset="1" />
      </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>
  </Window.Background>
  <StackPanel Name="theStackPanel" 
              VerticalAlignment="Top">
    <ComboBox Name="theCombo" 
              IsSynchronizedWithCurrentItem="True" 
              Width="200" 
              ItemsSource="{Binding}" 
              ItemTemplate="{StaticResource EmployeeListTemplate}"
              SelectionChanged="theCombo_OnSelectionChanged" />
    <Canvas>
      <Canvas.Resources>
        <Style TargetType="{x:Type TextBox}">
          <Setter Property="Canvas.Left" Value="160" />
          <Setter Property="Padding" Value="0" />
          <Setter Property="Height" Value="18" />
          <Setter Property="Width" Value="120" />
        </Style>
        <Style TargetType="{x:Type TextBlock}">
          <Setter Property="Canvas.Left" Value="90" />
          <Setter Property="Padding" Value="0" />
          <Setter Property="Height" Value="18" />
          <Setter Property="FontWeight" Value="Bold" />
        </Style>
      </Canvas.Resources>
      <Image Name="theImage" Canvas.Top="5" Canvas.Left="5" Width="75"/>
      <TextBlock Canvas.Top="5">First Name:</TextBlock>
      <TextBox Canvas.Top="5" Text="{Binding Path=FirstName}" />
      <TextBlock Canvas.Top="25">Last Name:</TextBlock>
      <TextBox Canvas.Top="25" Text="{Binding Path=LastName}" />
      <TextBlock Canvas.Top="45">Title:</TextBlock>
      <TextBox Canvas.Top="45" Text="{Binding Path=Title}" />
      <TextBlock Canvas.Top="65">Hire Date:</TextBlock>
      <TextBox Canvas.Top="65" Text="{Binding Path=HireDate}" />
      <Button Canvas.Top="85" Canvas.Left="90" Width="190" 
              Name="SaveButton" Click="SaveButton_OnClick">Save</Button>
    </Canvas>
  </StackPanel>
</Window>

Quando si avvia il browser, si ottiene un'interfaccia come la seguente figura 1:

Figura 1. Browser del dipendente

Questo esempio è piuttosto diretto, ma cosa accadrebbe se si utilizzassero DataTable correlati in un DataSet? Di seguito viene preso in esame uno scenario di questo tipo.

Associazione di DataTable correlati

È possibile estendere il browser dei dipendenti in modo da includere gli ordini procacciati da ciascun venditore. A tale scopo è necessario ottenere le informazioni sugli ordini. Per ottenere tali informazioni, si potrebbe creare una nuova query ogni volta che si cambia utente, ma in questo caso si caricheranno i dati in un DataSet insieme a Employee e si utilizzerà DataRelation per correlare le due informazioni:

In C#:

DataSet theSet = new DataSet();

string connString = ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
string employeeQuery = @"
  SELECT EmployeeID, FirstName, LastName, Title, HireDate, Photo 
  FROM Employees
";
string orderQuery = @"
  SELECT o.OrderID, EmployeeID, CompanyName, OrderDate, 
  SUM((UnitPrice * Quantity)* (1-Discount)) as OrderTotal
  FROM Orders o
  JOIN [Order Details] od on o.OrderID = od.OrderID
   JOIN Customers c on c.CustomerID = o.CustomerID
  GROUP BY o.OrderID, o.EmployeeID, o.OrderDate, CompanyName";

// Fill the Set with the data
using (SqlConnection conn = new SqlConnection(connString))
{
  SqlDataAdapter da = new SqlDataAdapter(employeeQuery, conn);
  da.Fill(theSet, "Employees");
  da.SelectCommand.CommandText = orderQuery;
  da.Fill(theSet, "Orders");
}

// Create the relationship
DataTable empTable = theSet.Tables["Employees"];
DataTable ordTable = theSet.Tables["Orders"];
theSet.Relations.Add("Emp2Ord", 
                     empTable.Columns["EmployeeID"], 
                     ordTable.Columns["EmployeeID"], 
                     false);

// Set the Context of the Window to be the 
// DataTable we've created
DataContext = empTable;

In Visual Basic .NET:

Dim theSet As DataSet =  New DataSet() 
 
Dim connString As String = _
    ConfigurationManager.ConnectionStrings("Northwind").ConnectionString 
String employeeQuery = _
  "SELECT EmployeeID, FirstName, LastName, Title, HireDate, Photo " + _
  "  FROM Employees"

String orderQuery = _
  "SELECT o.OrderID, EmployeeID, CompanyName, OrderDate, " + _
  "       SUM((UnitPrice * Quantity)* (1-Discount)) as OrderTotal " + 
  "FROM Orders o " +
  "JOIN (Order Details) od on o.OrderID = od.OrderID " +
  "JOIN Customers c on c.CustomerID = o.CustomerID " +
  "GROUP BY o.OrderID, o.EmployeeID, o.OrderDate, CompanyName"
 
' Fill the Set with the data
Using conn as New SqlConnection(connString)

  Dim da As SqlDataAdapter =  New SqlDataAdapter(employeeQuery,conn) 
  da.Fill(theSet, "Employees")
  da.SelectCommand.CommandText = orderQuery
  da.Fill(theSet, "Orders")

End Using
 
' Create the relationship
Dim empTable As DataTable =  theSet.Tables("Employees") 
Dim ordTable As DataTable =  theSet.Tables("Orders") 
theSet.Relations.Add("Emp2Ord", 
                     empTable.Columns("EmployeeID"), 
                     ordTable.Columns("EmployeeID"), 
                     False)

' Set the Context of the Window to be the 
' DataTable we've created
DataContext = empTable

Con il codice seguente si creerà un DataSet che contiene due tabelle: Employees e Orders. Le tabelle vengono associate a EmployeeID mediante una relazione denominata Emp2Ord. È comunque possibile impostare un'associazione al DataTable Employee, in modo che l'associazione di dati originale in XAML funzioni in modo corretto. Analogamente ai Windows Form o all'associazione di dati ASP.NET, è possibile impostare un'associazione al nome della relazione in modo da consentire l'associazione a una serie di record correlati:

        <ListBox Name="OrderList" Width="280" Height="200"
               ItemsSource="{Binding Emp2Ord}" 
               ItemTemplate="{StaticResource OrderListTemplate}" />

In questa casella di riepilogo viene ancora utilizzato lo stesso DataContext come il resto del browser del dipendente, ma con l'unico intento di specificare l'associazione tramite la relazione. Una volta associata la casella di riepilogo alla relazione, è possibile definire l'associazione ai singoli campi in ItemTemplate, come già visto nella casella combinata del dipendente:

    <DataTemplate x:Key="OrderListTemplate">
      <StackPanel Orientation="Horizontal">
        <TextBlock VerticalAlignment="Top" Width="100" 
                   Text="{Binding Path=CompanyName}" />
        <StackPanel>
          <TextBlock Text="{Binding Path=OrderID}" />
          <TextBlock Text="{Binding Path=OrderDate}" />
          <TextBlock Text="{Binding Path=OrderTotal}" />
        </StackPanel>
      </StackPanel>
    </DataTemplate>

Con questa ulteriore associazione di dati, ora viene visualizzata una casella di riepilogo con le informazioni sugli ordini correlate al solo utente selezionato:

Figura 2. Il browser del dipendente migliorato

In questo modo è possibile definire associazioni a dati più complessi, piuttosto che a semplici sezioni rettangolari di dati. In molte organizzazioni vengono utilizzati dei tipi .NET personalizzati (o oggetti business) per contenere i dati e la logica aziendale. WPF è in grado di garantire associazioni a tali oggetti con la stessa facilità di DataSet?

Associazione agli "oggetti business"

Nelle prime esemplificazioni di .NET, compresi Windows Form e ASP.NET, il DataSet e gli oggetti correlati erano gli elementi centrali. Era possibile associare i dati in modo semplice e funzionale. Se invece si decideva di creare modelli di oggetti o oggetti business per contenere i dati, era necessario eseguire manualmente l'associazione di dati tra controlli e oggetti. In .NET 2.0 gli oggetti sono divenuti le strutture portanti, con una conseguente semplificazione dell'associazione agli oggetti. In WPF è ancora così e l'associazione a oggetti e DataSet è altrettanto semplice.

Per creare il browser del dipendente con gli oggetti business, è necessario prima creare una classe che contenga l'Employee.

In C#:

public class Employee
{
  // Fields
  int _employeeID;
  string _firstName;
  string _lastName;
  string _title;
  DateTime _hireDate;
  BitmapImage _photo;

  // Constructor
  public Employee(IDataRecord record)
  {
    _employeeID = (int) record["EmployeeID"];
    _firstName = (string) record["FirstName"];
    _lastName = (string)record["LastName"];
    _title = (string)record["Title"];
    _hireDate = (DateTime)record["HireDate"];
    CreatePhoto((byte[])record["Photo"]);
  }

  // BitmapImage creation
  void CreatePhoto(byte[] photoSource)
  {
    // Create the bitmap object
    // NOTE: This is *not* a GDI+ Bitmap object
    _photo = new BitmapImage();
    MemoryStream strm = new MemoryStream();

    // Well-known hack to make Northwind images work
    int offset = 78;
    strm.Write(photoSource, offset, photoSource.Length - offset);

    // Read the image into the bitmap object
    _photo.BeginInit();
    _photo.StreamSource = strm;
    _photo.EndInit();

  }
}

In Visual Basic .NET:

Public Class Employee

  ' Fields
  Dim _employeeID As Integer
  Dim _firstName As String
  Dim _lastName As String
  Dim _title As String
  Dim _hireDate As DateTime
  Dim _photo As BitmapImage
 
  ' Constructor
  Public  Sub New(ByVal record As IDataRecord)

    _employeeID = CType(record("EmployeeID"), Integer)
    _firstName = CType(record("FirstName"), String)
    _lastName = CType(record("LastName"), String)
    _title = CType(record("Title"), String)
    _hireDate = CType(record("HireDate"), DateTime)
    CreatePhoto(CType(record("Photo"), Byte()))

  End Sub
 
  ' BitmapImage creation
  Private  Sub CreatePhoto(ByVal photoSource() As Byte)

    ' Create the bitmap object
    ' NOTE: This is *not* a GDI+ Bitmap object
    _photo = New BitmapImage()
    Dim strm As MemoryStream =  New MemoryStream() 
 
    ' Well-known hack to make Northwind images work
    Dim offset As Integer =  78 
    strm.Write(photoSource, offset, photoSource.Length - offset)
 
    ' Read the image into the bitmap object
    _photo.BeginInit()
    _photo.StreamSource = strm
    _photo.EndInit()
 
  End Sub
End Class

Questa classe comprende una classe IDataRecord (un singolo risultato da un DataReader, come si vedrà in seguito) e consente di compilare i medesimi campi utilizzati nell'esempio di DataTable precedente. È da notare che è stato necessario trasferire la creazione di BitmapImage all'oggetto aziendale per rendere più semplice l'utilizzo del dipendente nella classe UI.

A questo punto sono necessarie funzioni di accesso di proprietà per i campi:

In C#:

// Read-Only
public int EmployeeID
{
  get { return _employeeID; }
}

public string FirstName
{
  get { return _firstName; }
  set { _firstName = value; }
}

public string LastName
{
  get { return _lastName; }
  set { _lastName = value; }
}

public string Title
{
  get { return _title; }
  set { _title = value; }
}

public DateTime HireDate
{
  get { return _hireDate; }
  set { _hireDate = value; }
}

// Read-Only
public BitmapImage Photo
{
  get { return _photo; }
}

In Visual Basic .NET:

' Read-Only
Public ReadOnly Property EmployeeID() As Integer
  Get 
     Return _employeeID
  End Get
End Property
 
Public Property FirstName() As String
  Get 
     Return _firstName
  End Get
  Set (ByVal Value As String) 
     _firstName = value
  End Set
End Property
 
Public Property LastName() As String
  Get 
     Return _lastName
  End Get
  Set (ByVal Value As String) 
     _lastName = value
  End Set
End Property
 
Public Property Title() As String
  Get 
     Return _title
  End Get
  Set (ByVal Value As String) 
     _title = value
  End Set
End Property
 
Public Property HireDate() As DateTime
  Get 
     Return _hireDate
  End Get
  Set (ByVal Value As DateTime) 
     _hireDate = value
  End Set
End Property
 
' Read-Only
Public ReadOnly Property Photo() As BitmapImage
  Get 
     Return _photo
  End Get
End Property

È sufficiente consentire l'accesso in lettura/scrittura (o di sola lettura) ai campi della classe. Sarà quindi possibile scrivere un insieme che contenga i dipendenti:

In C#:

public class EmployeeList : ObservableCollection<Employee>
{
  public EmployeeList()
  {
    string connString =
           ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
    string query = @"
      SELECT EmployeeID, FirstName, LastName, Title, HireDate, Photo 
      FROM Employees
    ";

    // Fill the Set with the data
    using (SqlConnection conn = new SqlConnection(connString))
    {
      try
      {
        SqlCommand cmd = conn.CreateCommand();
        cmd.CommandText = query;

        conn.Open();
        SqlDataReader rdr = cmd.ExecuteReader();
        while (rdr.Read())
        {
          Add(new Employee(rdr));
        }
      }
      finally
      {
        if (conn.State != ConnectionState.Closed) conn.Close();
      }
    }
  }
}

In Visual Basic .NET:

Public Class EmployeeList
   Inherits ObservableCollection<Employee>
  Public  Sub New()
    String connString =
           ConfigurationManager.ConnectionStrings("Northwind").ConnectionString

    String query = _
      "SELECT EmployeeID, FirstName, LastName, Title, HireDate, Photo " + _
      "  FROM Employees"

    ' Fill the Set with the data
    Using conn as New SqlConnection(connString)
    
      Try
        Dim cmd As SqlCommand =  conn.CreateCommand() 
        cmd.CommandText = query
 
        conn.Open()
        Dim rdr As SqlDataReader =  cmd.ExecuteReader() 
        While rdr.Read()
          Add(New Employee(rdr))
        End While
      Finally
        If conn.State <> ConnectionState.Closed Then
            conn.Close()
        End If
      End Try
    
    End Using

  End Sub
End Class

La classe di base dell'insieme è la classe ObservableCollection, che costituisce un meccanismo che consente all'interfaccia utente di stabilire se vengono aggiunti nuovi membri all'insieme. Si trasferisce quindi l'accesso ai dati dalla pagina dell'interfaccia utente alla classe Collection. Quando si crea la classe, viene eseguita una query al database e vengono aggiunti nuovi dipendenti all'insieme dal DataReader. Dopo aver creato l'insieme e i singoli oggetti, sarà possibile importare le classi in XAML con un mapping (descritto in dettaglio nella prima parte di questa serie di articoli):

<?Mapping XmlNamespace="EmployeeNS" ClrNamespace="Example.Data" ?>
<Window
    ...
    xmlns:e="EmployeeNS"    DataContext="{StaticResource EmployeeList}"
    >
  <Window.Resources>
    <e:EmployeeList x:Key="EmployeeList" />
    ...
  </Window.Resources>
  ...
</Window>

Si importa la classe nel documento XAML con la dichiarazione ?Mapping e si specifica l'EmployeeList in Resources in modo da utilizzarla come DataContext della finestra. In questo modo il resto del file XAML resta identico al browser del dipendente originale, perchè l'associazione resta definita agli stessi nomi di campo dell'esempio di DataSet. L'unica modifica che è possibile apportare riguarda l'associazione di BitmapImage nel documento XAML, piuttosto che nel codice sottostante:

...
      <Image Name="theImage" Canvas.Top="5" Canvas.Left="5" Width="75" 
             Source="{Binding Path=Photo}"/>
...

A questo punto si ottiene un browser del dipendente che si comporta allo stesso modo:

Figura 3. Il browser del dipendente basato sull'oggetto business

Oltre all'utilizzo del mapping dei tipi, è possibile utilizzare ObjectDataProvider per richiamare gli oggetti nell'XAML. Come dimostrato nella prima parte dell'articolo, è sufficiente specificare una chiave e il nome del tipo:

    <ObjectDataProvider x:Key="EmployeeList" 
                        TypeName="Example.Data.EmployeeList, ExampleCS"/>

x:Key è solo un moniker che è possibile utilizzare nell'associazione, mentre Typename è il nome della classe e l'assembly (in questo caso lo stesso assembly in cui risiede l'interfaccia utente). Il resto dell'XAML resta identico, dal momento che vengono caricati gli stessi dati descritti in precedenza.

A che punto siamo?

Ora siamo in grado di caricare i dati da un database, con l'utilizzo di DataSet o di oggetti personalizzati, e di associare i dati in modo diretto agli oggetti WPF. È possibile quindi procedere alla creazione del primo progetto di database WPF.

Riferimenti

Mostra:
© 2014 Microsoft