KyloRen
KyloRen

Reputation: 2741

MVVM, ComboBox Binding and DatagridTemplateColumn?

I have a DatagridTemplateColumn using a ComboBox, but the ItemSource will not fill with the bound collection.

I should mention that the DataGrid is being correctly bound and any other collections in the veiwModel is working, it is just this ComboBox in the datagrid does not work.

This is the MCVE sample code:

<UserControl 
         d:DataContext="{d:DesignInstance d:Type=viewModels:StaffInfoDetailViewModel, IsDesignTimeCreatable=False}">    

    <DataGrid Grid.Column="0" Grid.ColumnSpan="6" AutoGenerateColumns="False" ItemsSource="{Binding SectionStaffMasterDisplay}" Grid.Row="4" Grid.RowSpan="2" AlternationCount="2" CanUserAddRows="True" CanUserDeleteRows="True" GridLinesVisibility="None" VerticalAlignment="Top" CanUserSortColumns="False">

            <DataGridTemplateColumn Width="190" Header="資格">                    
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <ComboBox 
                           DisplayMemberPath="ItemName" 
                           SelectedValuePath="ItemName" 
                           SelectedItem="{Binding Path=Name, UpdateSourceTrigger=LostFocus}" 
                           ItemsSource="{Binding Path=LicenceComboBox}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>
           .....more XAML

And the ViewModel

public class StaffInfoDetailViewModel : CollectionViewModel<StaffInfoDetailWrapper>
{
    public StaffInfoDetailViewModel()
    {           
        LicenceComboBoxItems();
        MasterDataDisplay();
    } 


    public void LicenceComboBoxItems()
    {            
        foreach (var item in DataProvider.StartUpSection)
        {
             LicenceComboBox.Add(item);
        }
    }

    private ObservableCollection<Licence> _licenceComboBox = new ObservableCollection<Licence>(); 

    public ObservableCollection<Licence> LicenceComboBox
    {
        get { return _licenceComboBox; }
        set
        {
            _licenceComboBox = value;
            OnPropertyChanged();
        }
    }

    private string _name;

    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            OnPropertyChanged();
        }
    }

The Model class:

public partial class Licence
{
    public System.Guid Id { get; set; } // ID (Primary key)
    public string ItemName { get; set; } // ItemName (length: 50)
    public string Section { get; set; } // Section (length: 50)

    public Licence()
    {
        InitializePartial();
    }

    partial void InitializePartial();
}

The datagrid collection.

 private ObservableCollectionEx<StaffInfoDetail> _sectionStaffMasterDisplay = new ObservableCollectionEx<StaffInfoDetail>();

    public ObservableCollectionEx<StaffInfoDetail> SectionStaffMasterDisplay
    {
        get { return _sectionStaffMasterDisplay; }
        set
        {
            if (value != _sectionStaffMasterDisplay)
            {
                _sectionStaffMasterDisplay = value;
                OnPropertyChanged();
            }
        }
    }

The Entity class that the collection is filled by,

public partial class StaffInfoDetail
{
    public System.Guid Id { get; set; } // ID (Primary key)
    public byte[] Image { get; set; } // Image (length: 2147483647)
    public int? StaffNo { get; set; } // StaffNo
    public string SecondName { get; set; } // SecondName (length: 50)
    public string FirstName { get; set; } // FirstName (length: 50)
    public string Section { get; set; } // Section (length: 50)
    public string SubSection { get; set; } // SubSection (length: 50)
    public string Licence { get; set; } // Licence (length: 50)
    public System.DateTime? StartDate { get; set; } // StartDate
    public System.DateTime? EndDate { get; set; } // EndDate
    public long? NightShiftOne { get; set; } // NightShiftOne
    public long? NightShiftTwo { get; set; } // NightShiftTwo
    public long? Lunch { get; set; } // Lunch
    public long? Unplesant { get; set; } // Unplesant
    public string JobType { get; set; } // JobType (length: 50)
    public bool Kaizen { get; set; } // Kaizen
    public int KaizenPercentage { get; set; } // KaizenPercentage
    public bool? CurrentStaff { get; set; } // CurrentStaff
    public string Notes { get; set; } // Notes (length: 4000)

    public StaffInfoDetail()
    {
        InitializePartial();
    }

    partial void InitializePartial();
}

And the method that fills the collection, I added the caller to the original code from public StaffInfoDetailViewModel():

public void MasterDataDisplay()
    {
        SectionStaffMasterDisplay.AddRange(DataProvider.StaffInfos.Where(p => 
                                              p.CurrentStaff == true && 
                                              DataProvider.StartUpSection.Contains(p.Section)).ToObservable());
    }

I can't see an issue with the DataContext,but why won't this bind correctly when every other property is working?

And stepping through the code shows LicenceComboBox to be correctly filled.

Upvotes: 0

Views: 1264

Answers (3)

mm8
mm8

Reputation: 169200

Since the DataContext of the ComboBox in the DataGrid is a StaffInfoDetail object and the LicenceComboBox property belongs to the StaffInfoDetailViewModel class, you need to use a RelativeSource to be able bind to this property.

Try this:

<DataGridTemplateColumn Width="190" Header="資格">
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <ComboBox 
                DisplayMemberPath="ItemName" 
                SelectedValuePath="ItemName" 
                SelectedValue="{Binding Path=Licence, UpdateSourceTrigger=LostFocus}" 
                ItemsSource="{Binding Path=DataContext.LicenceComboBox, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

Upvotes: 1

Sir Rufo
Sir Rufo

Reputation: 19106

This issue is about the DataContext. Each row in a DataGrid has its own DataContext - the item of the collection from DataGrid.ItemsSource.

Lets have a very simple example on this

using System.Collections.ObjectModel;
using GalaSoft.MvvmLight;

namespace WpfApp4.ViewModel
{
    public class MainViewModel : ViewModelBase
    {
        public MainViewModel()
        {
            BarCollection = new ObservableCollection<BarModel>
            {
                new BarModel { Id = 1, Name = "Bar 1", },
                new BarModel { Id = 2, Name = "Bar 2", },
                new BarModel { Id = 3, Name = "Bar 3", },
            };

            FooCollection = new ObservableCollection<FooViewModel>
            {
                new FooViewModel{ Id = 1, },
                new FooViewModel{ Id = 2, },
                new FooViewModel{ Id = 3, },
            };

        }


        public ObservableCollection<BarModel> BarCollection { get; set; }
        public ObservableCollection<FooViewModel> FooCollection { get; set; }
    }

    public class FooViewModel : ViewModelBase
    {
        private BarModel _bar;

        public int Id { get; set; }
        public BarModel Bar { get => _bar; set => Set( ref _bar, value ); }
    }

    public class BarModel
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public override string ToString()
        {
            return Name;
        }
    }
}

We present a collection of FooViewModel (MainViewModel.FooCollection) in a DataGrid and want to edit the Bar property with a ComboBox. The possible values are located in MainViewModel.BarCollection.

And here is the XAML for the binding

<Window x:Class="WpfApp4.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:local="clr-namespace:WpfApp4"
        mc:Ignorable="d"
        DataContext="{Binding Source={StaticResource Locator}, Path=Main}"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <CollectionViewSource x:Key="BarCollectionSource" Source="{Binding Path=BarCollection}"/>
    </Window.Resources>

    <Grid>
        <DataGrid ItemsSource="{Binding Path=FooCollection}" AutoGenerateColumns="True">
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="Bar">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Bar}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <ComboBox 
                               DisplayMemberPath="Name" 
                               SelectedItem="{Binding Bar, UpdateSourceTrigger=LostFocus}" 
                               ItemsSource="{Binding Source={StaticResource BarCollectionSource}}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

The magic is done by declaring a CollectionViewSource and bind the BarCollection to it

<Window.Resources>
    <CollectionViewSource 
        x:Key="BarCollectionSource" 
        Source="{Binding Path=BarCollection}"/>
</Window.Resources>

and bind that to the ComboBox.ItemsSource

<ComboBox 
    DisplayMemberPath="Name" 
    SelectedItem="{Binding Bar, UpdateSourceTrigger=LostFocus}" 
    ItemsSource="{Binding Source={StaticResource BarCollectionSource}}"/>

Upvotes: 1

Naresh Ravlani
Naresh Ravlani

Reputation: 1620

I see here that Combobox ItemsSource is not part of DataGrid ItemsSource. So both of them are sharing different DataContext. DataContext for Combobox ItemsSource is ViewModel and I assume that ViewModel is DataContext of the datagrid. If my assumption is correct then you need to add relative source to binding of ItemsSource of Combobox.

Use below syntax for this :

<ComboBox 
    DisplayMemberPath="ItemName" 
    SelectedValuePath="ItemName"                            
    ItemsSource="{Binding Path=PathToProperty,
    RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}"
</ComboBox>

Use proper value for typeOfAncestor and your Combobox should get populated.

To read more about RelativeSource and AncestorType, go through this SO post.

Upvotes: 1

Related Questions