0

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.

KyloRen
  • 2,691
  • 5
  • 29
  • 59
  • Post your `Licence` class – Max Apr 08 '17 at 08:34
  • @Max, the Entity model? B/c that is what it is. Code first BTW. – KyloRen Apr 08 '17 at 08:36
  • And where is the Name property you are binding to? – Sir Rufo Apr 08 '17 at 08:50
  • BTW SelectedItem will bind the selected Licence instance. – Sir Rufo Apr 08 '17 at 08:52
  • @SirRufo, Added – KyloRen Apr 08 '17 at 08:52
  • You are trying to bind a string property to a Licence instance. Read the docs about SelectedItem and SelectedValue – Sir Rufo Apr 08 '17 at 08:53
  • @SirRufo, I am at a loss to what I am doing wrong. I tried binding to the licence instance and still no dice. – KyloRen Apr 08 '17 at 08:58
  • Well, doing more wrong is indeed the wrong direction. If I had such a model collection I tend to bind to the instance or the ID to have a unique identifier what was choosen. The example is also not complete (f.i. what is SectionStaffMasterDisplay) and so it is not an MCVE – Sir Rufo Apr 08 '17 at 09:40
  • @SirRufo, `SectionStaffMasterDisplay` is the collection that the entire datagrid binds to, which is binding correctly BTW. That is completely different to what the `Combobox` items bind to. Only the selection made in the `ComboBox` will affect that collection. What populates the `ItemSource` will have no effect or be affected by that collection. So you should not need it in this case??? – KyloRen Apr 08 '17 at 09:43
  • Well the DataContext of each DataGrid row is the item of the ItemsSource collection. You try to bind the ComboBox to the DataContext of the control and each row should bind the value to the Name property of the control DataContext!? It sounds very strange and makes no sense at all. Thats why I ask for a **complete** example – Sir Rufo Apr 08 '17 at 09:51
  • @SirRufo, OK, that would explain the `TextBox` part of the `ComboBox` not displaying the correct value, but still does not explain why the `Combobox` does not have any items in it when the drop down is clicked??? – KyloRen Apr 08 '17 at 09:54
  • Does the item of SectionStaffMasterDisplay (which is a collection?) has a property LicenceComboBox? No, then you know why you see no items inside the combobox – Sir Rufo Apr 08 '17 at 09:56
  • Please post all the related code. Don't expect people to assume what you have written and that will help us to help you – Naresh Ravlani Apr 08 '17 at 10:02
  • @NareshRavlani, I have posted all related code. – KyloRen Apr 08 '17 at 10:03
  • @KyloRen, I cannot see "SectionStaffMasterDisplay" property and how you are populating it – Naresh Ravlani Apr 08 '17 at 10:04
  • @NareshRavlani, OK, I will post it, then do inform how it helped you. I honestly don't know how it will but... – KyloRen Apr 08 '17 at 10:05
  • @KyloRen A little insight is always better ;) – Naresh Ravlani Apr 08 '17 at 10:06
  • @NareshRavlani, I really do hope it helps, I updated the code. Thanks for the help thus far. – KyloRen Apr 08 '17 at 10:11
  • @NareshRavlani, anything? – KyloRen Apr 08 '17 at 11:59

3 Answers3

1

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.

Community
  • 1
  • 1
Naresh Ravlani
  • 1,600
  • 13
  • 28
  • It should be "UserControl" as that is the parent of Datagrid and VM is attached to "UserControl" – Naresh Ravlani Apr 08 '17 at 12:39
  • That does nothing. Still not populated. – KyloRen Apr 08 '17 at 12:44
  • 1
    Noone can provide an exact answer on a question containing only fragments of the code. We had to guess too many things and we can guess it wrong. It is up to you to ask a precise question if want to have a precise answer. This answer is right and as clear as your question – Sir Rufo Apr 08 '17 at 12:45
  • @SirRufo, So I can't comment that the answer does not help? why bother? And I understand what it takes to help people as I help many people through Excel Forum and VBA here. – KyloRen Apr 08 '17 at 13:01
  • I believe that the answer is not helpful for you, but that was not my point. It is right and as clear as your question. I said nothing about *helpful for you* – Sir Rufo Apr 08 '17 at 13:11
  • @KyloRen : From your code I found this as the obvious reason. If its still not working than I am not sure what else it could be. – Naresh Ravlani Apr 08 '17 at 13:55
  • @NareshRavlani, I am going to UV you for for help, I hope you can appreciate that, but unfortunately this does not help. – KyloRen Apr 08 '17 at 13:57
  • @KyloRen Yes I do appreciate !! I hope I could have help you finding ultimate solution, but that is all I got for you now. – Naresh Ravlani Apr 08 '17 at 14:39
1

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}}"/>
Sir Rufo
  • 18,395
  • 2
  • 39
  • 73
1

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>
mm8
  • 163,881
  • 10
  • 57
  • 88