none
ComboBox inside DataGrid

    Question

  • I have a DataGrid in which I have a column containing ComboBox.

    On selectionchanged event of the combobox, I want to know what item has been selected in the combobox and what row (in datagrid) has fired this.

    Now there are two problems:

    (1) I cannot get the command binding work. My code looks like below:

    My View (VM binding is done in codebehind of View):

    <Base:ViewBase xmlns:Base="clr-namespace:Resonant.eEnrol.Silverlight.Common.Base;assembly=Resonant.eEnrol.Silverlight.Common"
    	x:Class="Resonant.eEnrol.Silverlight.UI.Modules.Student.View.EnrolmentView"
        x:Name="EnrolmentLayoutRoot"
        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"
        mc:Ignorable="d"
    	xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Regions;assembly=Microsoft.Practices.Prism"
        xmlns:command="clr-namespace:Microsoft.Practices.Prism.Commands;assembly=Microsoft.Practices.Prism" 
        xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
        xmlns:customCommand="clr-namespace:Resonant.eEnrol.Silverlight.UI.Base.Commands"
        xmlns:baseCommand="clr-namespace:Resonant.eEnrol.Silverlight.Common.Commands;assembly=Resonant.eEnrol.Silverlight.Common"
        MinHeight="500" Width="1010" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" Height="500">
    
    	<Grid  HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
            <Grid.RowDefinitions>
                <RowDefinition Height="175*"></RowDefinition>
                <RowDefinition Height="325*"></RowDefinition>
            </Grid.RowDefinitions>
            <sdk:Label Height="18" HorizontalAlignment="Left" Margin="30,12,0,0" Content="Courses" VerticalAlignment="Top" Width="120" FontSize="14" Grid.Row="0" />
            <sdk:DataGrid Height="119" Margin="30,36,30,0" Width="950" AutoGenerateColumns="False" ItemsSource="{Binding Path=EnrolmentModels, Mode=OneWay}" x:Name="dataGrid" 
                          customCommand:DataGridDoubleClickCommand.Command="{Binding EditItemCommand, Mode=TwoWay}"
                          customCommand:DataGridDoubleClickCommand.CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem}"
                          SelectedItem="{Binding SelectedCourse, Mode=TwoWay}" >
                <sdk:DataGrid.Columns>
                    <sdk:DataGridTextColumn Binding="{Binding Path=Course.Code}" Header="Course" Width="547" CanUserReorder="True" CanUserSort="True" IsReadOnly="True"></sdk:DataGridTextColumn>
                    <sdk:DataGridTextColumn Binding="{Binding Path=CourseStartDate}" Header="Start Date" Width="100"></sdk:DataGridTextColumn>
                    <sdk:DataGridTextColumn Binding="{Binding Path=CourseEndDate}" Header="End Date" Width="100"></sdk:DataGridTextColumn>              
                    <sdk:DataGridTemplateColumn Header="Status" Width="200">
                        <sdk:DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <ComboBox SelectedItem="{Binding CurrentStatus, Mode=TwoWay}" ItemsSource="{Binding PossibleStatuses}" baseCommand:ComboBoxSelectionChangedCommand.Command="{Binding ElementName=EnrolmentLayoutRoot,  Path=DataContext.StatusChangedCommand}"  baseCommand:ComboBoxSelectionChangedCommand.CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem}" MaxWidth="170"  DisplayMemberPath="Descr" HorizontalAlignment="Left" />
                            </DataTemplate>
                        </sdk:DataGridTemplateColumn.CellTemplate>
                    </sdk:DataGridTemplateColumn>                
                </sdk:DataGrid.Columns>
            </sdk:DataGrid>
            <sdk:TabControl Grid.Row="1" Margin="0,10,0,0">
                <sdk:TabItem Header="Outcomes"></sdk:TabItem>
                <sdk:TabItem Header="Attendance"></sdk:TabItem>
                <sdk:TabItem Header="Finance"></sdk:TabItem>
                <sdk:TabItem Header="Invoices"></sdk:TabItem>            
            </sdk:TabControl>
            <Button Content="Enrol to a new course" Height="23" Command="{Binding Path=NewEnrolmentCommand}" HorizontalAlignment="Left" Margin="837,12,0,0" Name="button1" VerticalAlignment="Top" Width="143" />
        </Grid>
    </Base:ViewBase>
    

    I also tried this for my combobox:

    <ComboBox SelectedItem="{Binding CurrentStatus, Mode=TwoWay}" ItemsSource="{Binding PossibleStatuses}" baseCommand:ComboBoxSelectionChangedCommand.Command="{Binding RelativeSource={RelativeSource AncestorType=Base:ViewBase}, Path=DataContext.StatusChangedCommand}"  baseCommand:ComboBoxSelectionChangedCommand.CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem}" MaxWidth="170"  DisplayMemberPath="Descr" HorizontalAlignment="Left" />

    My command class is:

    namespace Resonant.eEnrol.Silverlight.Common.Commands
    {
        public class ComboBoxSelectionChangedCommand
        {
            #region Variables
    
            private static readonly DependencyProperty CommandComboBoxSelectionChangedBehaviorProperty =
               DependencyProperty.RegisterAttached("CommandComboBoxSelectionChangedBehavior", typeof(CommandComboBoxSelectionChangedBehavior),
                                                   typeof(ComboBoxSelectionChangedCommand), null);
    
            public static readonly DependencyProperty CommandProperty =
                DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(ComboBoxSelectionChangedCommand),
                new PropertyMetadata(CommandPropertyChanged));
    
            public static readonly DependencyProperty CommandParameterProperty =
               DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(ComboBoxSelectionChangedCommand),
               null);
    
            #endregion
    
            #region Methods
    
            public static ICommand GetCommand(ComboBox comboBox)
            {
                return (ICommand)comboBox.GetValue(CommandProperty);
            }
    
            public static void SetCommand(ComboBox comboBox, ICommand value)
            {
                comboBox.SetValue(CommandProperty, value);
            }
    
            public static object GetCommandParameter(ComboBox comboBox)
            {
                return comboBox.GetValue(CommandParameterProperty);
            }
    
            public static void SetCommandParameter(ComboBox comboBox, object value)
            {
                comboBox.SetValue(CommandParameterProperty, value);
            }
    
            private static void CommandPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
            {
                ComboBox element = o as ComboBox;
                if (element != null)
                {
                    if (e.OldValue != null)
                    {
                        UnhookCommand(element, (ICommand)e.OldValue);
                    }
                    if (e.NewValue != null)
                    {
                        HookCommand(element, (ICommand)e.NewValue);
                    }
                }
            }
    
            private static void HookCommand(ComboBox element, ICommand command)
            {
                CommandComboBoxSelectionChangedBehavior behavior = new CommandComboBoxSelectionChangedBehavior(element, command);
                behavior.Attach();
                element.SetValue(CommandComboBoxSelectionChangedBehaviorProperty, behavior);
            }
    
            private static void UnhookCommand(ComboBox element, ICommand command)
            {
                CommandComboBoxSelectionChangedBehavior behavior = (CommandComboBoxSelectionChangedBehavior)element.GetValue(CommandComboBoxSelectionChangedBehaviorProperty);
                behavior.Detach();
                element.ClearValue(CommandComboBoxSelectionChangedBehaviorProperty);
            }
    
            #endregion
    
            #region ClassDefination
    
            private class CommandComboBoxSelectionChangedBehavior
            {
    
                #region Variables
    
                private readonly WeakReference elementReference;
                private readonly ICommand command;
    
                #endregion
    
                #region  Constructor
    
                public CommandComboBoxSelectionChangedBehavior(ComboBox element, ICommand command)
                {
                    this.elementReference = new WeakReference(element);
                    this.command = command;
                }
    
                #endregion
    
                #region Methods
    
                public void Attach()
                {
                    ComboBox element = GetElement();
                    if (element != null)
                    {
                        element.SelectionChanged += element_SelectionChanged;
                        command.CanExecuteChanged += command_CanExecuteChanged;
                        SetIsEnabled(element);
                    }
                }
    
                public void Detach()
                {
                    command.CanExecuteChanged -= command_CanExecuteChanged;
                    ComboBox element = GetElement();
                    if (element != null)
                    {
                        element.SelectionChanged -= element_SelectionChanged;
                    }
                }
    
                void command_CanExecuteChanged(object sender, EventArgs e)
                {
                    ComboBox element = GetElement();
                    if (element != null)
                    {
                        SetIsEnabled(element);
                    }
                    else
                    {
                        Detach();
                    }
                }
    
                private void SetIsEnabled(ComboBox element)
                {
                    element.IsEnabled = command.CanExecute(element.GetValue(ComboBoxSelectionChangedCommand.CommandParameterProperty));
                }
    
                private static void element_SelectionChanged(object sender, EventArgs e)
                {
                    DependencyObject element = (DependencyObject)sender;
                    ICommand command = (ICommand)element.GetValue(CommandProperty);
                    object commandParameter = element.GetValue(CommandParameterProperty);
                    command.Execute(commandParameter);
                }
    
                private ComboBox GetElement()
                {
                    return elementReference.Target as ComboBox;
                }
    
                #endregion
    
            }
    
            #endregion
        }
    }
    

    And my VM has this:

            public ICommand StatusChangedCommand
            {
                get
                {
                    if (statusChangedCommand == null)
                    {
                        statusChangedCommand = new DelegateCommand<EntityType>(OnStatusChangedCommandExecuted);                   
                    }
                    return statusChangedCommand;
                }
            }
    
    ....
    
            protected void OnStatusChangedCommandExecuted(EntityType entityType)
            {
                var x = entityType;
    
            }

    The problem is OnStatusChangedCommandExecuted never gets hit when I change selection of combobox in any of the datagrid rows.

    (2) Even if the above works, I don't know if I will get the selected datagrid row in "SelectedCourse" property.

    Thanks in advance

    Saturday, April 14, 2012 7:12 AM

Answers

  • Had a simialr problem to you yesterday.

    The reason is ElementNames in DataTemplate all exists as separate scope to the usercontrol to prevent clashes. 

    The resolution is to use resources instead, which work up the visual tree.

    Google for DataContextProxy, and then bind your combobox like this:

    baseCommand:ComboBoxSelectionChangedCommand.Command="{Binding Source={StaticResource MyDataContextProxy},  Path=DataContext.StatusChangedCommand}" 

    Wednesday, April 18, 2012 5:08 PM
  • Thanks @teyc,

    I referred to the below link, which explains in depth about DataContextProxy:

    http://weblogs.asp.net/dwahlin/archive/2009/08/20/creating-a-silverlight-datacontext-proxy-to-simplify-data-binding-in-nested-controls.aspx

    Thanks everyone,

    Dharmesh

    Wednesday, April 18, 2012 8:38 PM

All replies

  • Hi dharmbhav,

    On selectionchanged event of the combobox, I want to know what item has been selected in the combobox and what row (in datagrid) has fired this.

    Regarding to this question, you may try a sample that based on a sample in this blog. Below is the core codes of this issue, you may have a try.

     string selectedrow;    
    
            private void dataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
            {
                selectedrow = dataGrid.SelectedIndex.ToString();
            }
    
            private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
            {
                ComboBox comb = sender as ComboBox;
                object selectedItem = comb.SelectedItem;
                this.textBlock1.Text = "The "+selectedrow+"th row of the DataGrid select "+((selectedItem == null) ? string.Empty : ((City)selectedItem).CityName);
            }

    I cannot get the command binding work

    Regarding to the command binding issue, I will suggest you take a look at:

    Step by Step Guide to Silverlight 4 Command Binding

    http://www.codeproject.com/Articles/79872/Step-by-Step-Guide-to-Silverlight-4-Command-Bindin

    Binding Command Inside DataGrid Using MVVM

    http://asimsajjad.blogspot.com/2011/06/binding-command-inside-datagrid-using.html 

    Hope they can help you.

    Best Regards,

    Monday, April 16, 2012 11:42 PM
    Moderator
  • Hi Haixia,

    Thanks for your reply. However neither of the links help. Asim Sajjad's blog is about using static reference and the other blog is using codebehind in the usercontrol.

    Anyways, I hacked this by partly using code behind in my view. Dirty solution but works...

    Thanks,

    Dharmesh

    Wednesday, April 18, 2012 5:45 AM
  • Please check the class structure you used to bind the data Grid ,  normally when a collection is the item source of  a collection control then the inner control should  be blinded to the inner property tree structure of the class  

    Like

    If school (view Model) is a class and It contain class object (property) of a student class where the student  contain List<student Name> and a selected Student property should be in the student class

     

    Like

    The Collection control Blinded to School

    Inner combo Blinded to School.student.student name

    combo  Selected item bind   to School.student.selectedstudent

    Wednesday, April 18, 2012 12:50 PM
  • Had a simialr problem to you yesterday.

    The reason is ElementNames in DataTemplate all exists as separate scope to the usercontrol to prevent clashes. 

    The resolution is to use resources instead, which work up the visual tree.

    Google for DataContextProxy, and then bind your combobox like this:

    baseCommand:ComboBoxSelectionChangedCommand.Command="{Binding Source={StaticResource MyDataContextProxy},  Path=DataContext.StatusChangedCommand}" 

    Wednesday, April 18, 2012 5:08 PM
  • Thanks @teyc,

    I referred to the below link, which explains in depth about DataContextProxy:

    http://weblogs.asp.net/dwahlin/archive/2009/08/20/creating-a-silverlight-datacontext-proxy-to-simplify-data-binding-in-nested-controls.aspx

    Thanks everyone,

    Dharmesh

    Wednesday, April 18, 2012 8:38 PM