Sunday, February 13, 2011

How do I handle foreign keys with WPF databinding?

I have a ListView in WPF that is databound to a basic table that I pull from the database. The code for the ListView is as follows:

<ListView Canvas.Left="402" Canvas.Top="480" Height="78" ItemsSource="{Binding}" Name="lsvViewEditCardPrint" Width="419">
   <ListView.View>
      <GridView>
         <GridViewColumn DisplayMemberBinding="{Binding Path=IdCst}">Set</GridViewColumn>
         <GridViewColumn DisplayMemberBinding="{Binding Path=Language}">Language</GridViewColumn>
         <GridViewColumn DisplayMemberBinding="{Binding Path=Number}">Number</GridViewColumn>
         <GridViewColumn DisplayMemberBinding="{Binding Path=IdArt}">Artwork</GridViewColumn>
      </GridView>
   </ListView.View>
</ListView>

The IdCst column is a foreign key to a separate table, and I'd like to display the actual name field from that table instead of just the Id. Does anybody know how to set a databinding, or is there an event, such as OnItemDataBound, that I could intercept to modify the display?

  • This blog post may help:

    ...I assumed the foreign key should be bound to the ‘SelectedValue’ property, and there is an ItemSource that I can bind to my fact table so the drop down is populated.

    At this point my dropdown worked, but nothing would appear in the combobox. I finally noticed a ‘SelectedItemPath’ property - I assumed this would be the name of the field in my dropdown that was associated to my foreign key. Sure enough, that’s exactly what it is.

  • I'd add a new property to your underlying class:

    Public ReadOnly Property NameCst() as String
        Get
            Return Names.LookupName(Me.IdCst)
        End Get
    End Property
    

    or something similar. Note that you'll probably have to include a Notify Property Changed event in your .IdCst setter for "NameCst".

    An alternative is to write a ValueConverter that does the lookup, but that's pretty heavy weight for something so simple.

    From Bob King
  • BOO YAH!!!

    I looked at the samples here, dug off a few of the references from other posts here, and found the answer... IValueConverter ... an interface that can be used with WPF that will convert values at the point of binding. It is a little tricky to put together at first, but not that difficult.

    The first step is to create a simple lookup or converter class that implements the IValueConverter interface. For my solution, I did this:

    Namespace TCRConverters
    
       Public Class SetIdToNameConverter
          Implements 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
             Dim taCardSet As New TCRTableAdapters.CardSetTableAdapter
             Return taCardSet.GetDataById(DirectCast(value, Integer)).Item(0).Name
          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
    
    End Namespace
    

    Note: I am not utilizing the ConvertBack method, but it is required by the interface.

    From there you need to add a reference to the namespace in your XAML header section:

    <Window x:Class="Main" Loaded="Main_Loaded"
        // Standard references here...
        xmlns:c="clr-namespace:TCR_Editor.TCRConverters"
        Title="TCR Editor" Height="728" Width="1135" Name="Main">
    

    Then in your Windows.Resources section, you can reference the converter, and in my case, I created a static reference to the CollectionViewSource that would be storing the data:

    <Window.Resources>
       <CollectionViewSource Source="{Binding Source={x:Static Application.Current}, Path=CardDetails}" x:Key="CardDetails">         
       </CollectionViewSource>
    
       <c:SetIdToNameConverter x:Key="SetConverter"/>      
    </Window.Resources>
    

    Then finally, in the ListView that was part of the initial problem, you add the converter reference:

    <ListView Canvas.Left="402" Canvas.Top="480" Height="78" ItemsSource="{Binding}" Name="lsvViewEditCardPrint" Width="419">
       <ListView.View>
          <GridView>
             <GridViewColumn DisplayMemberBinding="{Binding Path=IdCst, Converter={StaticResource SetConverter}}">Set</GridViewColumn>
             // Other Columns here...
          </GridView>
       </ListView.View>
    </ListView>
    

    So the great part now is that when I trigger an event that has a Card Id, all I need to do is reset set the CollectionViewSource...

    DirectCast(Me.FindResource("CardDetails"), CollectionViewSource).Source = taCardDetails.GetDataById(CardId)
    

    ...and all the binding elements of WPF do the rest!

    The nice thing about is is that I can easily create other converters, add them to various DataTemplates or columns elsewhere in the application, and once I get all of the data into the WPF app itself, the conversions can be conducted without going to the database.

    Dillie-O : Quick note. I found that compiling the converter class before adding all the XAML code seems to help eliminate some of the "nags" that it will give you until everything is compiled together.
    Robert Jeppesen : Sorry to burst your bubble, but this converter will hit the database at least once for each row.
    Dillie-O : True true. However that is currently for a small set that populates some lookup codes. For some larger work, I'm definitely doing some optimization, or some local cached data to minimize that kind of impact. But is good to remember, thanks.
    From Dillie-O

0 comments:

Post a Comment