Sean Holm
Sean Holm

Reputation: 329

WPF binding error (System.Windows.Data Error: 17)

I'm currently learning XAML & WPF and still getting to grips with databinding, mostly just banging my head on the wall until it works.

I am displaying the users table in a DataGrid and using comboboxes to lookup the 'name' values from the corresponding tables for the 'siteid' and 'roleid' values.

The form works as expected, i.e. it displays the datagrid with the comboboxes set correctly - and it also updates the user record just fine - but I am getting the following error message in the output window and its driving me insane:

System.Windows.Data Error: 17 : Cannot get 'Name' value (type 'String') from '' (type 'DataRowView'). BindingExpression:Path=Name; DataItem='DataRowView' (HashCode=25172842); target element is 'ComboBox' (Name=''); target property is 'NoTarget' (type 'Object') RowNotInTableException:'System.Data.RowNotInTableException: This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.

Question 1: What is causing the error?

The datagrid + comboboxes display correctly so what is wrong with the XAML below? I cannot see it!

My (simple) test project setup is nothing fancy:

XAML:

    <UserControl.Resources>
        <local:TestProjectDataSet x:Key="testProjectDataSet"/>

        <CollectionViewSource x:Key="usersViewSource" Source="{Binding Users, Source={StaticResource testProjectDataSet}}"/>
        <CollectionViewSource x:Key="rolesViewSource" Source="{Binding Roles, Source={StaticResource testProjectDataSet}}"/>
        <CollectionViewSource x:Key="sitesViewSource" Source="{Binding Sites, Source={StaticResource testProjectDataSet}}"/>

    </UserControl.Resources>

    <Grid Style="{StaticResource ContentRoot}" DataContext="{StaticResource usersViewSource}">
        <Grid.RowDefinitions>
            <RowDefinition Height="50"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>

        <TextBlock Text="User Management:" Style="{StaticResource Heading2}" Grid.Row="0"/>

        <DataGrid x:Name="UsersDataGrid" ItemsSource="{Binding}" EnableRowVirtualization="True" Grid.Row="1" RowDetailsVisibilityMode="VisibleWhenSelected" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn x:Name="idColumn" Width="SizeToHeader" IsReadOnly="True" Header="Id" Binding="{Binding Id}" />

                <DataGridTextColumn x:Name="usernameColumn" Width="Auto" Header="Username" Binding="{Binding Username}"/>

                <DataGridTextColumn x:Name="passwordColumn" Width="Auto" Header="Password" />

                <DataGridTemplateColumn x:Name="siteNameColumn" Header="Site" Width="Auto">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox IsSynchronizedWithCurrentItem="False"
                                      ItemsSource="{Binding Source={StaticResource sitesViewSource}}"
                                      DisplayMemberPath="Code"
                                      SelectedValuePath="Id"
                                      SelectedValue="{Binding SiteId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                      />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>

                <DataGridTemplateColumn x:Name="roleNameColumn" Header="Role" Width="Auto">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox IsSynchronizedWithCurrentItem="False"
                                      ItemsSource="{Binding Source={StaticResource rolesViewSource}}" 
                                      DisplayMemberPath="Name"
                                      SelectedValuePath="Id"
                                      SelectedValue="{Binding RoleId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                      />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>

        <Button Content="Save" Width="100" Height="50" Click="Button_Click" Grid.Row="2" />

    </Grid>

</UserControl>

Question 2: Is there a better way to do all this??

I had to use DataSets, TableAdapters & CollectionViewSources with some code-behind, e.g.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace TestProjectUI.Pages.Admin
{
    /// <summary>
    /// Interaction logic for ManageUsersPage.xaml
    /// </summary>
    public partial class ManageUsersPage : UserControl
    {
        private TestProjectDataSet _database;

        private TestProjectDataSetTableAdapters.UsersTableAdapter _usersAdapter;
        private TestProjectDataSetTableAdapters.RolesTableAdapter _rolesAdapter;
        private TestProjectDataSetTableAdapters.SitesTableAdapter _sitesAdapter;

        private CollectionViewSource _usersViewSource;
        private CollectionViewSource _rolesViewSource;
        private CollectionViewSource _sitesViewSource;

        public ManageUsersPage()
        {
            InitializeComponent();
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            _database = ((TestProjectDataSet)(FindResource("magazineInventoryDataSet")));

            _usersAdapter = new TestProjectDataSetTableAdapters.UsersTableAdapter();
            _usersAdapter.Fill(_database.Users);
            _usersViewSource = ((CollectionViewSource)(FindResource("usersViewSource")));
            _usersViewSource.View.MoveCurrentToFirst();

            _rolesAdapter = new TestProjectDataSetTableAdapters.RolesTableAdapter();
            _rolesAdapter.Fill(_database.Roles);
            _rolesViewSource = ((CollectionViewSource)(FindResource("rolesViewSource")));
            _rolesViewSource.View.MoveCurrentToFirst();

            _sitesAdapter = new TestProjectDataSetTableAdapters.SitesTableAdapter();
            _sitesAdapter.Fill(_database.Sites);
            _sitesViewSource = ((CollectionViewSource)(FindResource("sitesViewSource")));
            _sitesViewSource.View.MoveCurrentToFirst();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            _usersAdapter.Update(_database.Users);
        }

    }
}

I thought it was possible to do all this in pure XAML with no code behind but I've had no luck getting that to work so far (incorrect bindings?!)

If someone could show me a better way, or improve upon the code above it would be most appreciated.

I've recently come back to C# after many years Ruby and now WPF/XAML is kicking my butt!

Upvotes: 4

Views: 3649

Answers (1)

Sean Holm
Sean Holm

Reputation: 329

Ok, answering my own question (was going to delete, but instead will leave it incase it helps someone else out).

It seems that using tableadapter's is the wrong approach these days. I'm sure it works just fine in VS2012 / EF5 and earlier (no idea - haven't tested), but I just couldn't get the darn thing working properly in VS2013+ & EF6+, probably due to my inexperience with it.

Anyway, the recommended approach is to use 'Object' when setting up your datasources. Databinding works perfectly when dragging those object datasources onto your forms. Creating a master/detail form or lookup combos was a snap.

This is covered in the following article in the Getting Started section of the EF site, but I failed to spot it at the time even tho it was in a big bold font!:

Databinding with WPF

Additionally, I also found the following course on PluralSight EXTREMELY valuable:

WPF Databing in Depth - by Brian Noyes

So, TLDR:

1) Use 'Object' when creating your datasources (in VS2013+ & EF6+) - dont use 'Database' 2) read any getting started articles carefully :D

Hope that helps someone!

Upvotes: 3

Related Questions