This project has moved and is read-only. For the latest updates, please go here.

How to implement DataGridComboBoxColumn

Oct 20, 2012 at 2:23 PM
Edited Oct 22, 2012 at 10:52 AM

I would like to use DataGridComboBoxColumn in BookLibrary's BookListView, instead of the popup dialogue window (LendToWindow.xaml).

I added a new ComboBox column called "Lend To (Combo)" and the associated static resource to BookListView.xaml :

   <UserControl.Resources>
        <ObjectDataProvider x:Key="xKeySource"
            ObjectType="{x:Type source:DGComoboBox}" 
            MethodName="GetPersons">
        </ObjectDataProvider>
    </UserControl.Resources>

<DataGrid x:Name="bookTable" ...>

	<DataGrid.Columns>

		<DataGridComboBoxColumn x:Name="xComboBox" Header="Lend To Combo" 
  			ItemsSource="{Binding Source={StaticResource x:Key="xKeySource"
                        SelectedValuePath="Id"
                        DisplayMemberPath="Firstname"
                        SelectedValueBinding="{Binding Path=Id, Mode=TwoWay}" >
        	</DataGridComboBoxColumn>

	</DataGrid.Columns>

</DataGrid>

 

Resource Class:

    public static class DGComoboBox
    {
        public static IEnumerable <Person> GetPersons()
        {
            BookLibraryEntities entities = new BookLibraryEntities();

            return entities.Persons;
        }
    }

 

This populates the ComboBox drop-down list with Persons but does not populate the new column.

Furthermore, if I select a person from the drop-down list, I get the following un-handled exception:

 

System.InvalidCastException was unhandled
  Message=Unable to cast object of type 'Waf.BookLibrary.Library.Domain.Person' to type 'Waf.BookLibrary.Library.Applications.DataModels.BookDataModel'.
Source=BookLibrary

in

private void DataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            foreach (BookDataModel book in e.RemovedItems)
            {
                ViewModel.SelectedBooks.Remove(book);
            }
            foreach (BookDataModel book in e.AddedItems)
            {
                ViewModel.SelectedBooks.Add(book);
            }
        }

Another approach would be to use CollectionViewSource as the static source (<CollectionViewSource x:Key="xKeySource" Source="{Binding Persons, Mode=OneTime}"/>).  But this wouldn't work either as Persons is not part of BookListView.xaml's DataContext.  This method doesn't even populate ComboBox's drop-down list.

I should be able to use all the already defined entities and view models such as EntityService and BookDataModel to define the static resource; but I don't know how.  Any help would be greatly appreciated.

UPDATE:

I used the following resource:



<
CollectionViewSource x:Key="xKeySource" Source="{Binding Books, Mode=OneTime}"/>

and modified DataGridComboBoxColumn to:


<DataGridComboBoxColumn x:Name="xComboBox" Header="Lend To (Combo)"  
ItemsSource="{Binding Source={StaticResource xKeySource}}" SelectedValuePath="Book.LendTo.Id" DisplayMemberPath="Book.LendTo.Firstname" SelectedValueBinding="{Binding Path=Book.LendTo.Id, Mode=TwoWay}"
	</DataGridComboBoxColumn>
	</DataGrid.Columns>
</DataGrid>

This does now populate the new column but the drop-down list is, obviously, a copy of the new column, including the blank fields (and not the listing from Persons).  Moreover, selecting a person does not generate un-handled exception, as above.  However, if I select a person on an empty field no error is generated, the new person is shown on the ComboBox column without triggering the OnPropertyChanged in the Book class, but if I try to change an existing person in the column, I get this following error:

"The property 'Id' is part of the object's key information and cannot be modified."

in the ReportPropertyChanging("Id") section of class Person in BookLibraryModel.Designer.cs .

AGAIN, I would highly appreciate it if someone guide me to get this working in the same way the Hyperlink command and the popup window does.

Oct 28, 2012 at 12:27 PM
Edited Oct 28, 2012 at 1:46 PM

Hi there;

Surely there must be someone out there to be able to help me or give me a direction to resolve my problem?

This is a great framework and I am using it as the basis for the applications I am developing.

One of the applications is for property management; it's main window is a DataGrid with various columns for expenses, ownership, VAT, rental income, transaction amount, transaction dates, etc.  The DataGrid is bound to a database table called "Expense" with most of it's columns linked to other (foreign) tables in the database.


I have used the BookLibrary example to develop my application, where "Expense" resembles the Book table and the foreign tables are similar to the Person table.

I have extended the BookLibrary to cater for updating more than one column of the Book table (DataGrid), using the popup window approach.  For example, updating the Author column from another table.

Now, I am in the process of implementing DataGridComboBoxColumn for updating some of the columns of my DataGrid, which would provide a faster data entry interface for the user; but I am facing difficulties. I have searched the web and tried some of the methods suggested but haven't been able to get them working on the WAf BookLibrary.  However, I managed to get it working on the following standalone example, where no WAF, MEF or MVVM is implemented:

 


 

 

XAML:

<Window 
    x:Class="BookLibrary.Presentation.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:domain="clr-namespace:BookLibrary.Domain;assembly=BookLibrary.Domain"
    Title="MainWindow" mc:Ignorable="d" 
    d:DesignHeight="500" d:DesignWidth="900" SizeToContent="WidthAndHeight"
    Loaded="Window_Loaded" Closing="Window_Closing" 
    >
    <Window.Resources>
        <CollectionViewSource x:Key="xBooksViewSource" Source="{Binding Books, Mode=OneTime}" />
        <CollectionViewSource x:Key="xBooksLendToViewSource" Source="{Binding Path=LendTo, Source={StaticResource xBooksViewSource}, Mode=OneTime}" />
    </Window.Resources>
    <Grid>
        <DataGrid Name="bookDataGrid" AutoGenerateColumns="False"
                  Height="360" HorizontalAlignment="Left" VerticalAlignment="Top" Width="800" 
                  RowDetailsVisibilityMode="VisibleWhenSelected" EnableRowVirtualization="True"
                  ItemsSource="{Binding}" 
>
            <DataGrid.Columns>
                <DataGridTextColumn x:Name="personIDColumn" Header="Borrower ID" Width="*" 
                        Binding="{Binding Path=PersonId}" />

                <DataGridComboBoxColumn x:Name="xComboBox" Header="Lend To (Combo)" Width="*" CanUserSort="False" 
                        ItemsSource="{Binding Source={StaticResource xBooksLendToViewSource}}"
                        SelectedItemBinding="{Binding LendTo, Mode=TwoWay}"
                        DisplayMemberPath="Firstname"
                        SelectedValuePath="LendTo.Id" 
                >
               </DataGridComboBoxColumn>
                <DataGridTextColumn x:Name="TitleId" Binding="{Binding Path=Title}" Header="Book Title" Width="*" />
                <DataGridTextColumn x:Name="AuthorName" Binding="{Binding Path=Author}" Header="Author" Width="*" />
            </DataGrid.Columns>
        </DataGrid>
        <Button Content="Save" Name="SaveButton" Click="SaveButton_Click" Margin="26,367,0,0" Height="23" Width="75" HorizontalAlignment="Left" VerticalAlignment="Top" />
       </Grid>
</Window>

 

 


 

 

Code behind:

using System.ComponentModel;
using System.Data.Objects;
using System.Windows;
using System.Windows.Data;
using BookLibrary.Domain;

namespace BookLibrary.Presentation
{
    public partial class MainWindow : Window
    {  
        BookLibraryEntities bookLibraryEntities = new BookLibraryEntities();

        public MainWindow()
        {
            InitializeComponent();

            this.DataContext = bookLibraryEntities.Books;
        }

        private void Window_Closing(object sender, CancelEventArgs e)
        {
            bookLibraryEntities.Dispose();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            CollectionViewSource booksViewSource = ((CollectionViewSource)(this.FindResource("xBooksViewSource")));
            ObjectQuery<Book> booksQuery = this.GetBooksQuery(bookLibraryEntities);
            booksViewSource.Source = booksQuery.Execute(MergeOption.AppendOnly);

            CollectionViewSource booksLendToViewSource = ((CollectionViewSource)(FindResource("xBooksLendToViewSource")));
            ObjectQuery<Person> personsQuery = this.GetPersonsQuery(bookLibraryEntities);
            booksLendToViewSource.Source = personsQuery.Execute(MergeOption.AppendOnly);
        }

        private ObjectQuery<Book> GetBooksQuery(BookLibraryEntities entities)
        {
            entities.ContextOptions.LazyLoadingEnabled = false;

            ObjectQuery<Book> booksQuery = entities.Books;

            booksQuery = booksQuery.Include("LendTo");

            return booksQuery;
        }

        private ObjectQuery<Person> GetPersonsQuery(BookLibraryEntities entities)
        {
            ObjectQuery<Person> personsQuery = entities.Persons;

            return personsQuery;
        }

         private void SaveButton_Click(object sender, RoutedEventArgs e)
        {
            bookLibraryEntities.SaveChanges();
        }
    }
 }

 

 


 

I shouldn't need to make modifications to the code behind; because all the entities are already defined and constructed but I don't know how to utilise them.

Thanks for your help in advance.

Sabba

Nov 10, 2012 at 1:08 AM

Hi Sabba,

I prefer to use the following technique to achieve the desired comboBox functionality rather than the DataGridComboBoxColumn.
It allows more fine grained control. Hope this helps.

Mark 

 <DataGridTemplateColumn Helpers:DataGridUtil.ColName="TargetAssemblyGroupColumn">

                    <DataGridTemplateColumn.HeaderTemplate>
                        <DataTemplate>
                            <TextBlock TextWrapping="Wrap">
                                <TextBlock.Text>
                                    <Binding Converter="{StaticResource TextWrappedLocalizationConverter}" ConverterParameter="TargetAssemblyGroupCodeLabel" />                                </TextBlock.Text>
                            </TextBlock>
                        </DataTemplate>
                    </DataGridTemplateColumn.HeaderTemplate>

                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <ComboBox x:Name="TargetAssemblyGroupComboBoxColumn" 
                                    DisplayMemberPath="AssemblyGroupCode"
                                    Loaded="TargetAssemblyGroupComboBoxColumn_Loaded"
                                    SelectedValue="{Binding Path=TargetAssemblyGroupCode,       
                                                      Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"                                             SelectedValuePath="AssemblyGroupCode"                                          SelectionChanged="TargetAssemblyGroupComboBoxColumn_SelectionChanged" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>

                   <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock>
                                <TextBlock.Text>
                                    <Binding Path="TargetAssemblyGroupCode" />
                                </TextBlock.Text>
                            </TextBlock>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
           </DataGridTemplateColumn>

 

 

Nov 11, 2012 at 12:49 AM

Hi Mark,

Many thanks for your help and suggestion. I came to the same conclusion to use ComboBox and implemented the following approach, which is a bit basic compared to yours:


 

              <DataGridTemplateColumn Header="Lend To (ComboBox)" Width="100">
                    
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <ComboBox Name="personComboBox"
                                    ItemsSource="{Binding Source={StaticResource xKeySource}}"
                                    SelectedValue="{Binding Path=PersonId, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
                                    SelectedValuePath="Id"
                                    DisplayMemberPath="Id">
                                <ComboBox.ItemTemplate>
                                    <DataTemplate>
                                        <StackPanel Orientation="Horizontal">
                                            <TextBlock Text="{Binding Firstname}" />
                                            <TextBlock Text=" " />
                                            <TextBlock Text="{Binding Lastname}" />
                                        </StackPanel>
                                    </DataTemplate>
                                </ComboBox.ItemTemplate>
                            </ComboBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>

                    <DataGridTemplateColumn.CellTemplate >
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Text="{Binding Book.LendTo.Firstname, ValidatesOnDataErrors=True}" />
                                <TextBlock Text=" " />
                                <TextBlock Text="{Binding Book.LendTo.Lastname, ValidatesOnDataErrors=True}" />
                            </StackPanel>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>

                </DataGridTemplateColumn>

 


The dropdown list is correct (displays all the entries from the Person table) and populates the ComboBox column, as it should. My problem is that when I selected an item from the dropdown, I get the following error, as before, in the DataGridSelectionChanged method:



System.InvalidCastException was unhandled Message=Unable to cast object of type 'Waf.BookLibrary.Library.Domain.Person' to type 'Waf.BookLibrary.Library.Applications.DataModels.BookDataModel'.

 

The program does not go through the normal updating processes like other changes one makes to the DataGrid cells.  I think this is because I have defined the static resource xKeySource outside WAF and MEF.  As mentioned before, I should be able to use the existing definitions but I don't know how.


Do I need to do anything in the ComboBox events, as per your example?

Nov 25, 2012 at 9:53 PM
Edited Nov 26, 2012 at 12:55 AM

Hi,

I kind of managed to get DataGridComboBoxColumn working; but had to use the foreign key of Person's ID in the 
Book table (PersonId).  These are the changes I made:

1) Update the BookLibraryModel.edmx data model to include the PersonId foreign key in the Book entity type.

2) Create the following static class:



public static class DGComoboBox { public static IEnumerable<Person> GetPersons() { BookLibraryEntities entities = new BookLibraryEntities(); return entities.Persons; } }

3) Add the following resource to the ResourceDictionary of DataResources.xaml:



<ObjectDataProvider x:Key="xKeySource" ObjectType="{x:Type local:DGComoboBox}" MethodName="GetPersons"> </ObjectDataProvider>

4) Define a DisplayName property in Domain.Person.cs:



public string DisplayName { get { return (string)Firstname + " " + Lastname; } }

5) Add this entry to BookListView.xaml:



<DataGridComboBoxColumn Header="Lend To (DGComboBox)" ItemsSource="{Binding Source={StaticResource xKeySource}, Mode=OneWay}" DisplayMemberPath="DisplayName" SelectedValuePath="Id" SelectedValueBinding="{Binding xBook.PersonId, UpdateSourceTrigger=PropertyChanged}" />


Note the OneWay mode attribute; TwoWay mode will generate an inner exception of "Two-way binding requires Path or XPath." 

6) And finally, modify the DataGridSelectionChanged method to avoid the exception I was getting before:


    private void DataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.Source == e.OriginalSource)
        {
            foreach (BookDataModel book in e.RemovedItems)
            {
                ViewModel.SelectedBooks.Remove(book);
            }
            foreach (BookDataModel book in e.AddedItems)
            {
                ViewModel.SelectedBooks.Add(book);
            }
        }
    }


Most likely, this can be achieved in a more elegant way; but for now, it does the job for me.
Thanks.
Sabba