Tipps und Tricks

Frage (321) zu VB(9).NET (VS 2008), ADO.NET, WPF:

Wie kann man eine ADO.NET-DataSource an Steuerelemente eines XAML-Fensters binden?

Antwort:
Zuerst ist ein XAML-Fenster aufzubauen. Für eine Grid-Darstellung eignet sich eine ListBox.

[xml]

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
   <Grid Name="gr1">
    <ListBox Name="lb1" />
   </Grid>
</Window>
[/xml]

Damit der Container weiß, welche Daten die eingebetteten Steuerelemente anzuzeigen haben, ist dem Container das Dataset mit den geladenen Daten bekannt zu geben. Dazu ist im Codeteil des Fensters der folgende Code hinzuzufügen:

[vb]
Partial Public Class Window1

  Private Sub Window1_Loaded(ByVal sender As System.Object, _
                             ByVal e As System.Windows.RoutedEventArgs) _
                             Handles MyBase.Loaded

    Me.gr1.DataContext = DAL.GetDataSet

  End Sub

End Class
[/vb]

Zusätzlich muss die ListBox natürlich wissen, was sie anzuzeigen hat:. Dazu ist der ListBox mitzuteilen, was die Quelle für die Elemente (ItemsSource) ist.

[xml]
<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300" Name="Window1">
   <Grid Name="gr1">
     <ListBox Name="lb1" ItemsSource="{Binding Path=Tab1}" />
   </Grid>
</Window>
[/xml]

In den Zeilen werden die DataRow Objekte angezeigt (ToString Methode des dataRow Objektes). Um jedoch einen konkreten Feldinhalt anzuzeigen, ist eine "Vorlage" für das anzuzeigende Element  zu erstellen und zuzuweisen. In der Vorlage wird die gestaltung des anzuzeigenden Elementes festgelegt.

[xml]
<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300" Name="Window1">
   <Grid Name="gr1">
     
  <!--Data template-->
    <Grid.Resources>
      <DataTemplate x:Key="lb1ItemsTemplate">
        <TextBlock Text="{Binding Path=col1}"/>
      </DataTemplate>
    </Grid.Resources>
     
    <ListBox Name="lb1" ItemTemplate="{StaticResource lb1ItemsTemplate}" ItemsSource="{Binding Path=Tab1}" />
     
   </Grid>
</Window>
[/xml]

Um mehrere Feldinhalte anzuzeigen, muss die "Vorlage" für das anzuzeigende Element entsprechend aufgebaut werden.

[xml]
<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300" Name="Window1">

  <Window.Resources>
    <!--Data template-->
    <DataTemplate x:Key="lb1ItemsTemplate">
      <Border BorderThickness="1" BorderBrush="Gray" Padding="1" Margin="1" Name="border">
        <Grid>
          <Grid.RowDefinitions>
            <RowDefinition/>
          </Grid.RowDefinitions>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="30"/>
            <ColumnDefinition Width="40"/>
            <ColumnDefinition Width="120"/>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="50"/>
          </Grid.ColumnDefinitions>
          <!-- Spalteninhalt-->
          <TextBlock Name="tbID" Grid.Row="0" Grid.Column="0" Text="{Binding Path=ID}" Margin="0,3,0,0" />
          <TextBox Name="tbNr" Grid.Row="0" Grid.Column="1" Text="{Binding Path=nr}" />
          <TextBox Name="tbDatum" Grid.Row="0" Grid.Column="2" Text="{Binding Path=datum}"/>
          <TextBox Name="tbCol1" Grid.Row="0" Grid.Column="3" Text="{Binding Path=col1}" />
          <Image Name="tbPict" Grid.Row="0" Grid.Column="4" Source="{Binding Path=pict}" >
          </Image> 
        </Grid>
      </Border>
    </DataTemplate>
  </Window.Resources>

  <Grid Name="gr1">
    <ListBox Name="lb1" ItemTemplate="{StaticResource lb1ItemsTemplate}" ItemsSource="{Binding Path=Tab1}" />
  </Grid>
  
</Window>
[/xml]

Nachteilig bei dieser Variante ist, dass die Feldinhalte standardmäßig für die Anzeige in Zeichenketten konvertiert werden. Um eine individuelle Konvertierung zu erhalten, ist ein Wertekonverter zu erstellen, dem Fenster bekannt zu geben und auch zu nutzen. Konverter für Datumswerte und für Bildanzeige könnten so aussehen:

[vb]
Public Class clsDateConverter
  Implements System.Windows.Data.IValueConverter

  Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, _
                          ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) _
                          As Object Implements System.Windows.Data.IValueConverter.Convert
    Return CType(value, DateTime).ToShortDateString
  End Function

  Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, _
                              ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) _
                              As Object Implements System.Windows.Data.IValueConverter.ConvertBack
    Dim resultDateTime As DateTime
    If DateTime.TryParse(value.ToString, resultDateTime) Then Return resultDateTime
    Return value
  End Function

End Class

Public Class clsPictConverter
  Implements System.Windows.Data.IValueConverter

  Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, _
                          ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) _
                          As Object Implements System.Windows.Data.IValueConverter.Convert
    Using myMemoryStream As New System.IO.MemoryStream
      value.Save(myMemoryStream, System.Drawing.Imaging.ImageFormat.Jpeg)
      myMemoryStream.Position = 0
      Dim bmi As New BitmapImage
      With bmi
        .BeginInit()
        .StreamSource = myMemoryStream
        .EndInit()
      End With
      Return bmi
    End Using
  End Function

  Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, _
                              ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) _
                              As Object Implements System.Windows.Data.IValueConverter.ConvertBack
    Return Nothing
  End Function

End Class
[/vb]

Die Fenster-XAML würde dann so aussehen (wenn die Konverterklassen im Projekt sind):

[xml]
<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300" Name="Window1"
  xmlns:src="clr-namespace:WpfApplication1"
    >

  <Window.Resources>
    
    <src:clsDateConverter x:Key="myDateConverter"/>
    <src:clsPictConverter x:Key="myPictConverter"/>

    <!--Data template-->
    <DataTemplate x:Key="lb1ItemsTemplate">
      <Border BorderThickness="1" BorderBrush="Gray" Padding="1" Margin="1" Name="border">
        <Grid>
          <Grid.RowDefinitions>
            <RowDefinition/>
          </Grid.RowDefinitions>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="30"/>
            <ColumnDefinition Width="40"/>
            <ColumnDefinition Width="120"/>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="50"/>
          </Grid.ColumnDefinitions>
          <!-- Spalteninhalt-->
          <TextBlock Name="tbID" Grid.Row="0" Grid.Column="0" Text="{Binding Path=ID}" Margin="0,3,0,0" />
          <TextBox Name="tbNr" Grid.Row="0" Grid.Column="1" Text="{Binding Path=nr}" />
          <TextBox Name="tbDatum" Grid.Row="0" Grid.Column="2" Text="{Binding Path=datum, Converter={StaticResource myDateConverter}}"/>
          <TextBox Name="tbCol1" Grid.Row="0" Grid.Column="3" Text="{Binding Path=col1}" />
          <Image Name="tbPict" Grid.Row="0" Grid.Column="4" Source="{Binding Path=pict, Converter={StaticResource myPictConverter}}" >
          </Image> 
        </Grid>
      </Border>
    </DataTemplate>
  </Window.Resources>

  <Grid Name="gr1">
    <ListBox Name="lb1" ItemTemplate="{StaticResource lb1ItemsTemplate}" ItemsSource="{Binding Path=Tab1}" />
  </Grid>
  
</Window>
[/xml]

Um jetzt noch hierarchische Daten anzuzeigen, kann eine weitere ListBox genutzt werden. Das XAML-Fenster kann dann so aussehen:

[xml]
<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="600" Name="Window1"
  xmlns:src="clr-namespace:WpfApplication1"
    >

  <Window.Resources>
    
    <src:clsDateConverter x:Key="myDateConverter"/>
    <src:clsPictConverter x:Key="myPictConverter"/>

    <!--Data template-->
    <DataTemplate x:Key="lb1ItemsTemplate">
      <Border BorderThickness="1" BorderBrush="Gray" Padding="1" Margin="1" Name="border">
        <Grid>
          <Grid.RowDefinitions>
            <RowDefinition/>
          </Grid.RowDefinitions>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="30"/>
            <ColumnDefinition Width="40"/>
            <ColumnDefinition Width="120"/>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="50"/>
          </Grid.ColumnDefinitions>
          <!-- Spalteninhalt-->
          <TextBlock Name="tbID" Grid.Row="0" Grid.Column="0" Text="{Binding Path=ID}" Margin="0,3,0,0" />
          <TextBox Name="tbNr" Grid.Row="0" Grid.Column="1" Text="{Binding Path=nr}" />
          <TextBox Name="tbDatum" Grid.Row="0" Grid.Column="2" Text="{Binding Path=datum, Converter={StaticResource myDateConverter}}"/>
          <TextBox Name="tbCol1" Grid.Row="0" Grid.Column="3" Text="{Binding Path=col1}" />
          <Image Name="tbPict" Grid.Row="0" Grid.Column="4" Source="{Binding Path=pict, Converter={StaticResource myPictConverter}}" >
          </Image> 
        </Grid>
      </Border>
    </DataTemplate>

    <DataTemplate x:Key="lb2ItemsTemplate">
      <DockPanel>
        <CheckBox Name="cb1" IsThreeState="False" IsChecked="{Binding Path=col2}" />
        <TextBlock Name="lb2MCol1" Text="{Binding Path=col1}"></TextBlock>
      </DockPanel>
    </DataTemplate>
      
  </Window.Resources>
  
  <Grid Name="gr1">

    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="400" />
      <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="20"/>
      <RowDefinition />
    </Grid.RowDefinitions>

    <Label Grid.Column="0" Grid.Row="0">Master</Label>
    <ListBox Grid.Column="0" Grid.Row="1" Name="lb1" ItemTemplate="{StaticResource lb1ItemsTemplate}" ItemsSource="{Binding Path=Tab1}" IsSynchronizedWithCurrentItem="true" />

    <Label Grid.Column="1" Grid.Row="0" Content="{Binding Path=Tab1/col1}"/>
    <ListBox Grid.Column="1" Grid.Row="1" Name="lb2" ItemTemplate="{StaticResource lb2ItemsTemplate}" ItemsSource="{Binding Path=Tab1/rel1}" />

  </Grid>
  
</Window>
[/xml]

Das XAML-Fenster kann man zusätzlich noch mit Funktionalitäten ergänzen, um z.B. die Anzeige zu skalieren

[xml]
<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="600" Name="Window1"
  xmlns:src="clr-namespace:WpfApplication1"
    >

  <Window.Resources>
    
    <src:clsDateConverter x:Key="myDateConverter"/>
    <src:clsPictConverter x:Key="myPictConverter"/>

    <!--Data template-->
    <DataTemplate x:Key="lb1ItemsTemplate">
      <Border BorderThickness="1" BorderBrush="Gray" Padding="1" Margin="1" Name="border">
        <Grid>
          <Grid.RowDefinitions>
            <RowDefinition/>
          </Grid.RowDefinitions>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="30"/>
            <ColumnDefinition Width="30"/>
            <ColumnDefinition Width="40"/>
            <ColumnDefinition Width="120"/>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="50"/>
          </Grid.ColumnDefinitions>
          <!-- Spalteninhalt-->
          <Rectangle />
          <TextBlock Name="tbID" Grid.Row="0" Grid.Column="1" Text="{Binding Path=ID}" Margin="0,3,0,0" />
          <TextBox Name="tbNr" Grid.Row="0" Grid.Column="2" Text="{Binding Path=nr}" />
          <TextBox Name="tbDatum" Grid.Row="0" Grid.Column="3" Text="{Binding Path=datum, Converter={StaticResource myDateConverter}}"/>
          <TextBox Name="tbCol1" Grid.Row="0" Grid.Column="4" Text="{Binding Path=col1}" />
          <Image Name="tbPict" Grid.Row="0" Grid.Column="5" Source="{Binding Path=pict, Converter={StaticResource myPictConverter}}" >
          </Image> 
        </Grid>
      </Border>
    </DataTemplate>

    <DataTemplate x:Key="lb2ItemsTemplate">
      <DockPanel>
        <CheckBox Name="cb1" IsThreeState="False" IsChecked="{Binding Path=col2}" />
        <TextBlock Name="lb2MCol1" Text="{Binding Path=col1}"></TextBlock>
      </DockPanel>
    </DataTemplate>
      
  </Window.Resources>
  <Grid>
  <Grid Name="gr1" Margin="0,0,0,20">

    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="400" />
      <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="20"/>
      <RowDefinition />
    </Grid.RowDefinitions>

    <Label Grid.Column="0" Grid.Row="0">Master</Label>
    <ListBox Grid.Column="0" Grid.Row="1" Name="lb1" ItemTemplate="{StaticResource lb1ItemsTemplate}" ItemsSource="{Binding Path=Tab1}" IsSynchronizedWithCurrentItem="true" />

    <Label Grid.Column="1" Grid.Row="0" Content="{Binding Path=Tab1/col1}"/>
    <ListBox Grid.Column="1" Grid.Row="1" Name="lb2" ItemTemplate="{StaticResource lb2ItemsTemplate}" ItemsSource="{Binding Path=Tab1/rel1}" />

      <Grid.LayoutTransform>
        <ScaleTransform>
          <ScaleTransform.ScaleX>
            <Binding ElementName="sl1" Path="Value"/>
          </ScaleTransform.ScaleX>
          <ScaleTransform.ScaleY>
            <Binding ElementName="sl1" Path="Value"/>
          </ScaleTransform.ScaleY>
        </ScaleTransform>
      </Grid.LayoutTransform>

    </Grid>
    <Slider  Grid.Column="1" Grid.Row="2" Name="sl1" Height="20" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="100" Minimum=".25" Maximum="4" Value="1"  ></Slider>
  </Grid>
</Window>
[/xml]

Die Demo nutzt die folgende Klasse als Datenzugriffsschicht:

[vb]
Imports System.Data
Imports System.Windows.Forms
Imports System.Drawing

Public Class DAL
  Private Shared _ds As DataSet
  Public Shared ReadOnly Property GetDataTab1() As DataTable
    Get
      If _ds Is Nothing Then
        _ds = BuildDataSet()
      End If
      Return _ds.Tables("Tab1")
    End Get
  End Property
  Public Shared ReadOnly Property GetDataSet() As DataSet
    Get
      If _ds Is Nothing Then
        _ds = BuildDataSet()
      End If
      Return (_ds)
    End Get
  End Property
  Private Shared Function BuildDataSet() As DataSet
    Dim dset As New DataSet
    Dim dt1 As New DataTable("Tab1") ' Master
    dset.Tables.Add(dt1)
    With dt1
      With .Columns
        .Add("ID", GetType(Integer))
        With .Item(0)
          .AutoIncrement = True
          .AutoIncrementSeed = -1
          .AutoIncrementStep = -1
        End With
        .Add("nr", GetType(Integer))
        .Add("col1", GetType(String))
        .Add("datum", GetType(Date))
        .Add("pict", GetType(Bitmap))
      End With
      For i As Integer = 1 To 50
        Dim r As DataRow = .NewRow
        r.Item("nr") = i
        r.Item("col1") = String.Format("DataRow {0:000}", i)
        r.Item("datum") = DateSerial(1900 + CInt(100 * Rnd()), 1, 1).AddDays(365 * Rnd())
        Dim img As New Bitmap(50, 50)
        Using g = Graphics.FromImage(img)
          g.Clear(Color.FromArgb(255, CInt(255 * Rnd()), CInt(255 * Rnd()), CInt(255 * Rnd())))
          g.DrawString("Row " & i.ToString, New Font("Arial", 12, FontStyle.Bold, GraphicsUnit.Pixel), Brushes.White, 0, 0)
        End Using
        r.Item("pict") = img
        .Rows.Add(r)
      Next
    End With
    Dim dt2 As New DataTable("Tab2") ' Child
    dset.Tables.Add(dt2)
    With dt2
      With .Columns
        .Add("ID", GetType(Integer))
        With .Item(0)
          .AutoIncrement = True
          .AutoIncrementSeed = -1
          .AutoIncrementStep = -1
        End With
        .Add("FK", GetType(Integer))
        .Add("col1", GetType(String))
        .Add("col2", GetType(Boolean))
      End With
      For i As Integer = 1 To 1000
        Dim r As DataRow = .NewRow
        r.Item("FK") = -CInt(dt1.Rows.Count * Rnd() + 0.5)
        r.Item("col1") = "ChildRow " & i.ToString
        r.Item("col2") = (Rnd() > 0.5)
        .Rows.Add(r)
      Next
    End With
    dset.Relations.Add(New DataRelation("rel1", dt1.Columns(0), dt2.Columns(1)))
    Return dset
  End Function
End Class
[/vb]

Stand des Beitrages: 05.10.10 16:14, zuletzt geändert: 24.07.15 22:48



Bitte wählen sie den Themenbereich aus
Bitte geben sie einen Suchbegriff ein

Die hier dargestellten Tipps und Tricks sind das Ergebnis selbst ersteller Lösungsvarianten, die für Projekte und Schulungen erarbeitet wurden.