Guidance for ComboBox in DataGrid

Topics: Prism v2 - Silverlight 2
Apr 29, 2009 at 5:39 PM
Edited Apr 29, 2009 at 5:43 PM
I've run into what I believe to be a general Silverlight 2 limitation and was hoping to get MVVM/Prism guidance on how to overcome it. I'll give a brief description of the problem and provide the relevant code snippets.

Scenario:

I'm using Prism and the MVVM pattern on a project which has a DataGrid and I'd like to use a ComboBox in the CellEditingTemplate to allow the end user to select a new value from a list. To enable this scenario, I've exposed two properties on my ViewModel - Types and Items. Types is an array of a class containing properties Key and Value and is where I store my display text and actual value for the ComboBox. Items is an ObservableCollection of my model class which exposes a property which is the type of my Key/Value class.

The project is setup such that I have a ContainerControlledLifetimeManager Service which returns values to a ViewModel which is bound to by the View: (i.e. Service <--> ViewModel <--> View).

Problem:

When I set the ItemsSource of the DataGrid like so, ItemsSource={Binding Path=Items}, the ComboBox contained within the CellEditingTemplate is unable to bind to the Types property of my ViewModel because the relative binding path now has Items as the root.

Solution?:

The obvious solution would be to reset the root of the relative binding path back to the DataContext for the ComboBox (something like ItemsSource={Binding Path=Types, Source={DataContext}}, but I've searched high and low and haven't found a way to do that. All the other solutions I've seen involves setting up a class that exposes the property you want to bind to as a StaticReource. However, in my scenario that won't work (at least I don't think it will) because I'm using the ContainerControlledLifetimeManager Service to get the values for the Types property and as far as I know any class you markup as a resource needs to have an empty default constructor - which would prevent me from being able to pull the service from the container using constructor Dependency Injection.

For the time being, I've handled the binding in the code behind in the PreparingCellForEdit event handler, but I would definitly prefer a pure XAML/binding method if one exists.

The main code to reconstruct the scenario is below. Thanks in advance for any guidance you can provide.

Ryan

Module

using Microsoft.Practices.Composite.Modularity;
using Microsoft.Practices.Composite.Regions;
using Microsoft.Practices.Unity;
using SLPrismGridComboBox.ModuleA.Services;
using SLPrismGridComboBox.ModuleA.ViewModels;
using SLPrismGridComboBox.ModuleA.Views;

namespace SLPrismGridComboBox.ModuleA
{
    public class ModuleAModule : IModule
    {

        IUnityContainer _container;
        IRegionManager _regionManager;

        public ModuleAModule(IUnityContainer container, IRegionManager regionManager)
        {
            _container = container;
            _regionManager = regionManager;
        }

        public void Initialize()
        {
            RegisterTypes();

            IMiscView view = _container.Resolve<IMiscView>();
            _regionManager.AddToRegion("MainRegion", view);
        }

        private void RegisterTypes()
        {
            _container.RegisterType<IMiscView, MiscView>();
            _container.RegisterType<IMiscViewModel, MiscViewModel>();
            _container.RegisterType<IMiscService, MiscService>(new ContainerControlledLifetimeManager());
        }

    }
}

Models
namespace SLPrismGridComboBox.ModuleA.Models
{
    public class SimpleKeyValuePair
    {
        public string Key { get; set; }
        public string Value { get; set; }

        public SimpleKeyValuePair(string key, string value)
        {
            this.Key = key;
            this.Value = value;
        }

        public override bool Equals(object obj)
        {
            if (obj.GetType().Equals(this.GetType()))
            {
                if (((SimpleKeyValuePair)obj).Value.Equals(this.Value))
                {
                    return true;
                }
            }
            return false;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }
}

namespace SLPrismGridComboBox.ModuleA.Models
{
    public class MiscModel
    {

        public string Name { get; set; }
        public SimpleKeyValuePair Type { get; set; }

    }
}

Service
using System.Collections.ObjectModel;
using SLPrismGridComboBox.ModuleA.Models;

namespace SLPrismGridComboBox.ModuleA.Services
{
    public interface IMiscService
    {
        ObservableCollection<MiscModel> GetAllMiscModels();
        SimpleKeyValuePair[] GetAllTypes();
    }
}
using System.Collections.Generic;
using System.Collections.ObjectModel;
using SLPrismGridComboBox.ModuleA.Models;

namespace SLPrismGridComboBox.ModuleA.Services
{
    public class MiscService : IMiscService
    {
        public ObservableCollection<MiscModel> GetAllMiscModels()
        {
            ObservableCollection<MiscModel> result = new ObservableCollection<MiscModel>();
            SimpleKeyValuePair[] types = GetAllTypes();
            int i = -1;

            result.Add(new MiscModel() { Name = "Name 1", Type = types[i += 1] });
            result.Add(new MiscModel() { Name = "Name 2", Type = types[i += 1] });
            result.Add(new MiscModel() { Name = "Name 3", Type = types[i += 1] });
            result.Add(new MiscModel() { Name = "Name 4", Type = types[i += 1] });
            result.Add(new MiscModel() { Name = "Name 5", Type = types[i += 1] });
            result.Add(new MiscModel() { Name = "Name 6", Type = types[i += 1] });
            result.Add(new MiscModel() { Name = "Name 7", Type = types[i += 1] });

            return result;
        }

        public SimpleKeyValuePair[] GetAllTypes()
        {
            List<SimpleKeyValuePair> result = new List<SimpleKeyValuePair>();

            result.Add(new SimpleKeyValuePair("Type 1", "Value 1"));
            result.Add(new SimpleKeyValuePair("Type 2", "Value 2"));
            result.Add(new SimpleKeyValuePair("Type 3", "Value 3"));
            result.Add(new SimpleKeyValuePair("Type 4", "Value 4"));
            result.Add(new SimpleKeyValuePair("Type 5", "Value 5"));
            result.Add(new SimpleKeyValuePair("Type 6", "Value 6"));
            result.Add(new SimpleKeyValuePair("Type 7", "Value 7"));

            return result.ToArray();
        }
    }
}


ViewModel

using System.Collections.ObjectModel;
using SLPrismGridComboBox.ModuleA.Models;
namespace SLPrismGridComboBox.ModuleA.ViewModels
{
    public interface IMiscViewModel
    {
        ObservableCollection<MiscModel> Items { get; set; }
        SimpleKeyValuePair[] Types { get; }
    }
}

using System.Collections.ObjectModel;
using System.ComponentModel;
using SLPrismGridComboBox.ModuleA.Models;
using SLPrismGridComboBox.ModuleA.Services;

namespace SLPrismGridComboBox.ModuleA.ViewModels
{
    public class MiscViewModel : IMiscViewModel, INotifyPropertyChanged
    {
        IMiscService _miscService;

        private ObservableCollection<MiscModel> _items;
        public ObservableCollection<MiscModel> Items
        {
            get { return _items; }
            set
            {
                if (_items != value)
                {
                    _items = value;
                    RaisePropertyChanged("Items");
                }
            }
        }

        private SimpleKeyValuePair[] _types;
        public SimpleKeyValuePair[] Types
        {
            get { return _types; }
            private set
            {
                if (_types != value)
                {
                    _types = value;
                    RaisePropertyChanged("Types");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public MiscViewModel(IMiscService miscService)
        {
            _miscService = miscService;
            this.Items = _miscService.GetAllMiscModels();
            this.Types = _miscService.GetAllTypes();
        }

        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

    }
}

View
namespace SLPrismGridComboBox.ModuleA.Views
{
    public interface IMiscView
    {

    }
}

using System.Windows.Controls;
using SLPrismGridComboBox.ModuleA.ViewModels;

namespace SLPrismGridComboBox.ModuleA.Views
{
    public partial class MiscView : UserControl, IMiscView
    {
        IMiscViewModel _viewModel;

        public MiscView(IMiscViewModel viewModel)
        {
            InitializeComponent();

            _viewModel = viewModel;
            this.DataContext = _viewModel;
        }
    }
}

<UserControl x:Class="SLPrismGridComboBox.ModuleA.Views.MiscView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" 
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <data:DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Path=Items}">
            <data:DataGrid.Columns>
                <data:DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" />
                <data:DataGridTemplateColumn Header="Type">
                    <data:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Path=Type.Key}" />
                        </DataTemplate>
                    </data:DataGridTemplateColumn.CellTemplate>
                    <data:DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <ComboBox DisplayMemberPath="{Binding Path=Type.Key}" SelectedItem="{Binding Path=Type}" ItemsSource="{Binding Path=Types}" />
                        </DataTemplate>
                    </data:DataGridTemplateColumn.CellEditingTemplate>
                </data:DataGridTemplateColumn>
            </data:DataGrid.Columns>
        </data:DataGrid>
    </Grid>
</UserControl>
May 1, 2009 at 4:03 PM
Any help on this would be greatly appreciated.

Thanks,
Ryan
Jul 22, 2009 at 3:02 PM

Did you find the solution?

Jul 24, 2009 at 2:00 PM

Unfortunately, no but with SL3 this may now be possible with element to element binding, however, I haven't tried.