views:

675

answers:

6

Suppose I have a dataset with those two immortal tables: Employee & Order
Emp -> ID, Name
Ord -> Something, Anotherthing, EmpID
And relation Rel: Ord (EmpID) -> Emp (ID)

It works great in standard master/detail scenario
(show employees, follow down the relation, show related orders),
but what when I wan't to go the opposite way (show Ord table with Emp.Name)?

Something like this:

<stackpanel>   // with datacontext set from code to dataset.tables["ord"]
   <TextBox Text="{Binding Something}"/>
   <TextBox Text="{Binding Anotherthing}"/>
   <TextBox Text="{Binding ???}"/> // that's my problem, how to show related Emp.Name 
</stackpanel>

Any ideas? I can create value converter, but if I wan't to use dataset instance which I get from parent module it gets tricky.

A: 

Assuming that you are using a strongly-typed DataSet, in order to bind the TextBox to the 'EmpRow.Name' property, you will probably have to expose it as a property on the 'OrdDataTable' class.

Since Visual Studio generates the typed DataSet code with partial classes, you could add the property to the 'OrdDataTable' class this way:

using System.Data;

public partial class OrdDataTable : DataTable
{
    public string EmpName
    {
        get { return this.EmpRow.Name; }
    }
}

Then you would be able to bind to the 'EmpName' property of the 'OrdDataTable' object in the data context.

Enrico Campidoglio
A: 

That's of course possible, but I was thinking about more 'generic' way. We've got lots of lookup tables in our database, and showing such a solution is not the best possible way to convince my skeptical boss, that WPF is great platform, and we should start working with it.
I'll try with markup extensions, maybe I'll manage to expose for textboxes set of similiar properties to those of combobox. Any other toughts?

cz_dl
A: 

Hi David,
have you tried to do it in your code?
That was my initial idea, but I can't manage to make it work.
Actually I use in my test project USERS and COUNTRIES tables,
that's binding expression:

<TextBox Text="{Binding NAME}"/> //shows correctly current user's name
<TextBox x:Name="tbC" Text="{Binding COUNTRIESRow.NAME}"/> //doesn't show related country name

To check spelling and such stuff I tried this in code-behind (and it works like a charm):

tbC.Text = ds.USERS[0].COUNTRIESRow.NAME;

And here's what my binding says to me:

BindingExpression path error: 'COUNTRIESRow' property not found on 'object'  'USERSDataTable' (HashCode=6518513)'. BindingExpression:Path=COUNTRIESRow; DataItem='USERSDataTable' (HashCode=6518513); target element is 'TextBox' (Name='tbC'); target property is 'Text' (type 'String')

So? Am I doing something wrong or this binding magic got some problem here?

cz_dl
A: 

What is the DataContext for the two TextBox controls?
For the second binding to work the DataContext must be set to an instance of the 'USERSDataTable'. Since these are contained in an array in the DataSet, you have to explicitly tell which table you want to bind to. Something like:

<StackPanel>
    <StackPanel.Resources>
        <ObjectDataProvider x:Key="ds" ObjectType="{x:Type mynamespace:MyDataSet}" />
    </StackPanel.Resources>

    <!-- Notice we set the data context to the first item in the array of tables -->
    <StackPanel DataContext="{Binding Source={StaticResource ds}, Path=USERS[0]}">
        <TextBox Text="{Binding NAME}"/>
        <TextBox Text="{Binding COUNTRIESRow.NAME}"/>
    </StackPanel>
</StackPanel>
Enrico Campidoglio
A: 

Hi Enrico,
thanks, that's for sure a step forward. Now it works.
But when I bind to an instance of USERSRow - that's your solution if I got it right (when I set path to USERS[0] the bound object is an instance USERSRow, not an instance of USERSDataTable), I'm losing all those nice possibilites which come with binding to some view (eg. table default view, Path=USERS). I mean: synchronizing with listboxes in context, navigating through records and so on.
It still can be done, of course, by hooking to some events (listbox SelectedIndex change) changing textboxes datacontext from code, etc., but that's not the WPF way, I believe.
Am I still missing something?

cz_dl
+1  A: 

If you want to synchronize the contents of multiple controls, you will need to have them share the same binding source through the DataContext set on a common parent control. Here is an example:

<StackPanel>
    <StackPanel.Resources>
        <ObjectDataProvider x:Key="ds" ObjectType="{x:Type mynamespace:MyDataSet}" />
    </StackPanel.Resources>

    <!-- We set the data context to the collection of rows in the table -->
    <StackPanel DataContext="{Binding Source={StaticResource ds}, Path=USERS.Rows}">
        <ListBox ItemsSource="{Binding}"
                 DisplayMemberPath="NAME"
                 IsSynchronizedWithCurrentItem="True" />
        <TextBox Text="{Binding Path=NAME}"/>
        <TextBox Text="{Binding Path=COUNTRIESRow.NAME}"/>
    </StackPanel>
</StackPanel>

Setting the IsSynchronizedWithCurrentItem property to 'True' will cause the ListBox.SelectedItem property to be automatically synchronized with the CollectionView.CurrentItem of the binding source, that is the collection of rows set at the DataContext. This means that the currently selected row in the ListBox becomes the binding source for the two TextBox controls.

Enrico Campidoglio