DeveloperLV
DeveloperLV

Reputation: 1781

How to load a list into a ComboBox DataGrid and display selected value from another list?

Goal

I am aiming to achieve the following:

Visual Output

enter image description here

Code...

Models

I have 2 models

public class Person
{
    public string Name { get; set; }
    public Position Position { get; set; }
}

public class Position
{
    public int PositionId { get; set; }
    public string PositionTitle { get; set; }
}

View Model

public class ViewModel : BaseViewModel
{
    public ViewModel()
    {
        People = new ObservableCollection<Person>();
        People.Add(new Person { Name = "Name 1", Position = new Position { PositionId = 1, PositionTitle = "Position Title 1" } });
        People.Add(new Person { Name = "Name 2", Position = new Position { PositionId = 1, PositionTitle = "Position Title 1" } });
        People.Add(new Person { Name = "Name 3", Position = new Position { PositionId = 2, PositionTitle = "Position Title 2" } });

        Positions = new ObservableCollection<Position>();
        Positions.Add(new Position { PositionId = 1, PositionTitle = "Position Title 1" });
        Positions.Add(new Position { PositionId = 2, PositionTitle = "Position Title 2" });
    }

    private ObservableCollection<Person> people;

    public ObservableCollection<Person> People
    {
        get { return people; }
        set
        {
            people = value;
            OnPropertyChanged();
        }
    }

    private ObservableCollection<Position> _positions;

    public ObservableCollection<Position> Positions
    {
        get { return _positions; }
        set
        {
            _positions = value;
            OnPropertyChanged();
        }
    }


}

public class Person
{
    public string Name { get; set; }
    public Position Position { get; set; }
}

public class Position
{
    public int PositionId { get; set; }
    public string PositionTitle { get; set; }
}

View

<DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
        <DataGridTemplateColumn Header="Position Title">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding Path=DataContext.Positions, RelativeSource={RelativeSource AncestorType=DataGrid}}"
                              DisplayMemberPath="PositionTitle"
                              SelectedValue="{Binding Path=Position}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

Question

How do I set the SelectedItem/Index of Position to what the Person's Position is set to?

Upvotes: 0

Views: 53

Answers (2)

EldHasp
EldHasp

Reputation: 7943

            <DataTemplate>
                <ComboBox ItemsSource="{Binding Path=DataContext.Positions,
                                       RelativeSource={RelativeSource AncestorType=DataGrid}}"
                          DisplayMemberPath="PositionTitle"
                          SelectedItem="{Binding Path=Position,
                                         UpdateSourceTrigger=PropertyChanged,
                                         Mode=TwoWay}" />
            </DataTemplate>

Nope.. doesn't work

Full answer, for all inconsistencies in the code:

  1. If the type is used for binding, then for correct operation it must either be immutable or implement the INotifyPropertyChanged interface:
namespace PeoplePosition
{
    public class Position
    {
        public int PositionId { get; }
        public string PositionTitle { get; }

        public Position(int positionId, string positionTitle)
        {
            PositionId = positionId;
            PositionTitle = positionTitle;
        }
    }
}
using Simplified;

namespace PeoplePosition
{
    public class Person : BaseInpc // Implementation of the "INPC" interface
    {
        private string _name;
        private Position _position;

        public string Name { get => _name; set => Set(ref _name, value); }
        public Position Position { get => _position; set => Set(ref _position, value); }
    }
}
  1. If you need to ensure equality of instances by value, then you need to override the Equals and GetHashCode methods (as @mm8 already wrote). But for mutable types, this is a bad decision, which in some cases can lead to bugs.

  2. If you need to set the values of a reference type corresponding to some collection, then you do not need to re-create instances of this type, but assign one of the elements of the collection that already contains all the valid values.

using System.Collections.ObjectModel;

namespace PeoplePosition
{
    public class ViewModel
    {
        public ViewModel()
        {
            Positions.Add(new Position(1, "Position Title 1"));
            Positions.Add(new Position(2, "Position Title 2"));

            People.Add(new Person { Name = "Name 1", Position = Positions[0] });
            People.Add(new Person { Name = "Name 2", Position = Positions[0] });
            People.Add(new Person { Name = "Name 3", Position = Positions[1] });
        }

        public ObservableCollection<Person> People { get; }
            = new ObservableCollection<Person>();

        public ObservableCollection<Position> Positions { get; }
            = new ObservableCollection<Position>();

    }
}
  1. Complete XAML code example demonstrating correct operation.
<Window x:Class="PeoplePosition.PeoplePositionsWindow"
        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:PeoplePosition"
        mc:Ignorable="d"
        Title="PeoplePositionsWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <UniformGrid Columns="1">
        <DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False" IsReadOnly="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
                <DataGridTemplateColumn Header="Position Title">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox ItemsSource="{Binding Path=DataContext.Positions,
                                                    RelativeSource={RelativeSource AncestorType=DataGrid}}"
                                      DisplayMemberPath="PositionTitle"
                                      SelectedItem="{Binding Path=Position,
                                                    UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
        
        <DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False" IsReadOnly="True">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
                <DataGridTextColumn Binding="{Binding Position.PositionId, Mode=OneWay}"
                                    Header="PositionId"/>
                <DataGridTextColumn Binding="{Binding Position.PositionTitle, Mode=OneWay}"
                                    Header="Position Title"/>
            </DataGrid.Columns>
        </DataGrid>
    </UniformGrid>
</Window>

Upvotes: 0

mm8
mm8

Reputation: 169360

You could override the Equals method of your Position class to define that two objects with the same id should be considered equal:

public class Position
{
    public int PositionId { get; set; }
    public string PositionTitle { get; set; }

    public override bool Equals(object obj) =>
        obj is Position p && PositionId == p.PositionId;

    public override int GetHashCode() => PositionId.GetHashCode();
}

Upvotes: 1

Related Questions