I currently have a Listview box with 3 comboboxes. I am populating them with from a sql database. For each row, I want to have the 3rd combobox change it's contents based on the selected values of the 2nd combobox.
The comboboxes will be: cmbx1 (employee[jack, jill, tom, lisa]), cmbx2(products[pen, pencil, stapler]), cmbx3(color - will be dynamic based on what color is available for the product)
product and color options: pen[red, blue, black]; pencil[black, orange, red]; stapler[pink, teal, purple, brown]
If for Row1, the user selects a pen, then only the available colors for that product will be listed in the color combobox for that row. The next row could have a different color option based on the product selected.
Is this possible or should i find another way to achieve the results?
here's what a currently have...
<ListView.View>
<GridView>
<GridViewColumn Header="Employee" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding lStrEmployee}" Width="120" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Product" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding lStrProduct}" Width="120" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Color" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding lStrColor}" Width="120" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</ListView.View>
code behind
List<Int32> liEmployee = new List<Int32>();
List<string> lsEmployee = new List<string>();
List<Int32> liProduct = new List<Int32>();
List<string> lsProduct = new List<string>();
List<Int32> liColor = new List<Int32>();
List<string> lsColor = new List<string>();
SqlConnection conn = new SqlConnection("Data Source=localhost\\SQLEXPRESS;Initial Catalog=testDB;Persist Security Info=True;User ID=USER;Password=PASSWORD;");//Connect Timeout=900
SqlCommand cmd1 = new SqlCommand("select id,employee from testDB.dbo.dmEmployee where inactive=0", conn);
SqlCommand cmd2 = new SqlCommand("select id,Product from testDB.dbo.tblProductList where inactive=0", conn);
SqlCommand cmd3 = new SqlCommand("select id,Color from testDB.dbo.Color where inactive=0", conn);
conn.Open();
SqlDataReader dr1 = cmd1.ExecuteReader();
while (dr1.Read())
{
liEmployee.Add(dr1.GetInt32(dr1.GetOrdinal("id")));
lsEmployee.Add(dr1.GetString(dr1.GetOrdinal("employee")));
}
conn.Close();
conn.Open();
SqlDataReader dr2 = cmd2.ExecuteReader();
while (dr2.Read())
{
liProduct.Add(dr2.GetInt32(dr2.GetOrdinal("id")));
lsProduct.Add(dr2.GetString(dr2.GetOrdinal("Product")));
}
conn.Close();
conn.Open();
SqlDataReader dr3 = cmd3.ExecuteReader();
while (dr3.Read())
{
liColor.Add(dr3.GetInt32(dr3.GetOrdinal("id")));
lsColor.Add(dr3.GetString(dr3.GetOrdinal("Color")));
}
conn.Close();
List<lvItem> itemFound = new List<lvItem>();
itemFound.Clear();
lvItem puzzlePieces;
for (int cnt = 0; cnt < 10; cnt++)
{
puzzlePieces = new lvItem();
puzzlePieces.lStrEmployee = lsEmployee;
puzzlePieces.lStrDatabase = lsDatabase;
puzzlePieces.lStrProvider = lsProvider;
itemFound.Add(puzzlePieces);
}
list1.ItemsSource = itemFound;
Thanks!
I'm surprised that you didn't get any answers to your question. Maybe it's because you don't seem to be doing things the WPF way, or maybe because you're asking for so much?
First things first... you need to create a data type class that implements the INotifyPropertyChanged interface and contains all of the properties required for display in each row of the ListView. In your case, you need three collections and three selected item values. As an example, you could do something like this (implementing the INotifyPropertyChanged interface yourself):
public class RowData : INotifyPropertyChanged
{
public ObservableCollection<Employee> Employees { get; set; }
public Employee SelectedEmployee { get; set; }
public ObservableCollection<Product> Products { get; set; }
public Product SelectedProduct { get; set; }
public ObservableCollection<Brush> Colours { get; set; }
public Brush SelectedColour { get; set; }
}
Note the use of the Brush class rather than the Color struct, this is because Brush is a class, which means that we can bind to it and also because it is more predominantly used in WPF.
However, it is not optimal having the same collections in every object in every row, except for the Colours collection, which could be different for each row. Having said that, that is exactly what I'm going to do because it will be quicker for me to explain and you can improve your code yourself at a later stage:
So now you have your data type class, we need to add a property of that type to bind to your ListView control. If you are using the code behind of your MainWindow, then let's create a DependencyProperty for it:
public static readonly DependencyProperty RowDataProperty = DependencyProperty.
Register("RowData", typeof(ObservableCollection<RowData>), typeof(MainWindow),
new UIPropertyMetadata(new ObservableCollection<RowData>()));
public ObservableCollection<RowData> RowData
{
get { return (ObservableCollection<RowData>)GetValue(RowDataProperty); }
set { SetValue(RowDataProperty, value); }
}
After filling your collection, you can now bind it to the ListView control:
xmlns:Local="clr-namespace:YourWpfApplicationName"
...
<ListView ItemsSource="{Binding RowData, RelativeSource={RelativeSource AncestorType={
x:Type Local:MainWindow}}}">
...
</ListView>
In short, the RelativeSource Binding is simply looking for the property you defined in the code behind. Now, how to define that a ComboBox should appear in each GridViewColumn? You need to define the GridViewColumn.CellTemplate:
<GridViewColumn Header="Employees">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Employees}" SelectedItem="{Binding
SelectedEmployee}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
You'll need to define the other columns from this example. So the final part of this puzzle is how to update the content of the Colours ComboBox dependent on the selected values of the other ComboBoxes? The answer lies in your selected value properties in your RowData class:
public Employee SelectedEmployee
{
get { return selectedEmployee; }
set
{
selectedEmployee = value;
NotifyPropertyChanged(SelectedEmployee);
Colours = GetColours();
}
}
private ObservableCollection<Brush> GetColours()
{
ObservableCollection<Brush> newColours = new ObservableCollection<Brush>();
if (SelectedEmployee.Name == "Some Name" && SelectedProduct.Name ==
"Some Product") newColours.AddRange( new List<Brush>() { Brushes.Red,
Brushes.White, Brushes.Blue } );
else ...
}
There are many ways to do this and I'll leave that up to you. You should now have a working example and I now realise why nobody had answered your question... far too much for anyone sane to type! After spending so long on this, I would appreciate it if you try to solve any minor problems you find with this on your own and hope it helps you.
Related
I am looking for a way to know if my listview contains a value. Below is my code.
public class OnlineUserList
{
public string Name { get; set; }
public string Color { get; set; }
}
<ListView x:Name="lvOnlineUsers" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" SelectionMode="Single" SelectionChanged="lvOnlineUsers_SelectionChanged">
<ListView.View>
<GridView x:Name="lvOnlineUsersGridView" AllowsColumnReorder="False">
<GridViewColumn Header="Online Users" Block.TextAlignment="Center" TextOptions.TextFormattingMode="Display" TextBlock.FontWeight="Bold">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Name="tbOnlineUsersGridView" Text="{Binding Path=Name}" Foreground="{Binding Path=Color}" HorizontalAlignment="Center" VerticalAlignment="Center" TextOptions.TextFormattingMode="Display" Style="{StaticResource ResourceKey=lblLabel}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
public void AddUserToList(string username)
{
lvOnlineUsers.Items.Add(new OnlineUserList { Name = username, Color = "Black" });
}
Now this is where am having issue
public void RemoveUserFromList(string username)
{
if(lvOnlineUsers.Items.Contains(username))
lvOnlineUsers.Items.Remove(username);
}
You should learn MVVM.
In the mean time, put the items in an ObservableCollection and assign it to the listview's ItemsSource property in your codebehind. Thereafter, repeat after me: Never, ever touch lvOnlineUsers.Items. Never, never, never. Forget that it exists. Everything you do, you interact with the ObservableCollection. Search it, add items to it, remove items from it. The UI will magically and mysteriously update itself.
I'm going to assume this is in MainWindow. If this is in a different view, the constructor will have a different name.
public MainWindow()
{
InitializeComponent();
lvOnlineUsers.ItemsSource = _onlineUsers;
}
private ObservableCollection<OnlineUserList> _onlineUsers
= new ObservableCollection<OnlineUserList>();
public void AddUserToList(string username)
{
_onlineUsers.Add(new OnlineUserList { Name = username, Color = "Black" });
}
public void RemoveUserFromList(string username)
{
// We don't search _onlineUsers for the string username, because
// _onlineUsers doesn't contain strings. It contains your user class.
// So instead, we look for the user class instance that has the name
// we want.
var found = _onlineUsers.FirstOrDefault(ou => ou.Name == username);
if (found != null)
{
_onlineUsers.Remove(found);
}
}
Until you have looked into MVVM, try this:
for(int i = lvOnlineUsers.Items.Count - 1; i >= 0; --i)
{
OnlineUserList item = lvOnlineUsers.Items[i] as OnlineUserList;
if (item != null && item.Name == username)
lvOnlineUsers.Items.Remove(lvOnlineUsers.Items[i]);
}
I am having problem binding ObservableCollection as ItemsSource to a combo box (this combobox is in a listview of having some rows).
I followed A collection of StackPanel as ItemsSource of ComboBox but I did not get any clues for resolving my problem.
Problem:
I was able to add items to a combobox which is at the top on the form.
I have created a listview containing 3 text blocks and 1 combobox.
I am successful in populating data for the text blocks in listview.
But the problem lies with Combobox. First time, it shows all the items for each row in ListView. Once I select item or click on the combobox again to see the items, my list disappears. Only one combobox in the listview row shows all items. Other comboboxes shows blank.
Also, I was trying to save the index of the selected item and show the selected panel next time. But I did not get how to bind the stackpanel with selecteditem and selecteditemvalue.
I tried many ways of directly binding the items to the combobox in listview. But nothing worked. Request someone to help me on this.
Details of the code is given below:
I have XAML like below:
<Grid>
<Grid Height="40">
<ComboBox x:Name="cbList" />
</Grid>
<Grid Margin="0,56,0,168"></Grid>
<ListView x:Name="lvFirst" Margin="0,195,0,12">
<ListView.View>
<GridView >
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Width="50" x:Uid="tbListView1" Text="{Binding FirstName}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Width="50" x:Uid="tbListView2" Text="{Binding LastName}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Width="50" x:Uid="tbListView1" Text="{Binding ID}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox Width="100" x:Uid="cbListView" ItemsSource="{Binding Path=SPForComboBox}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
In the code behind I have a Contact class as below:
public class Contact : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void Notify(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
private string _fn;
private string _ln;
public string FirstName
{
get
{ return _fn; }
set
{ _fn = value; Notify("FirstName"); }
}
public string LastName
{
get
{ return _ln; }
set
{ _ln = value; Notify("LastName"); }
}
private int _id;
public int ID
{
get { return _id; }
set { _id = value; Notify("ID"); }
}
public StackPanel sp;
public override string ToString()
{
return FirstName + " " + LastName;
}
private ObservableCollection<StackPanel> _sp;
public ObservableCollection<StackPanel> SPForComboBox
{
get { return _sp; }
set { _sp = value; Notify("SPForComboBox"); }
}
}
To populate cbList Items, I am repeatedly calling below function after Initialization() function:
private void AddColours(string name, byte hexcolor)
{
//Add this ShiftType to Combo box
SolidColorBrush rectangleBrush = new SolidColorBrush();
Color color = new Color();
color.A = hexcolor;
rectangleBrush.Color = color;
System.Windows.Controls.StackPanel stkPanel = new StackPanel(); //stack panel to hold rectangle + text
stkPanel.Orientation = Orientation.Horizontal;
cbList.Items.Add(stkPanel);
Rectangle colorRect = new Rectangle(); //rectangle showing colour for shift
colorRect.Height = 12;
colorRect.Width = colorRect.Height;
colorRect.Fill = rectangleBrush;
stkPanel.Children.Add(colorRect);
System.Windows.Controls.TextBlock cboText = new TextBlock(); //Name of shift
cboText.Text = name;
cboText.Margin = new Thickness(5, 5, 5, 5);
stkPanel.Children.Add(cboText);
}
In the main window class, I have a created an ObservableCollection object as public static (object name is contacts).
public static ObservableCollection<Contact> contacts = new ObservableCollection<Contact>();
After the Initialization() function, I am populating contacts as below:
AddColours("First", 100);
AddColours("Second", 50);
AddColours("Third", 20);
AddColours("Fourth", 0);
AddColours("Fifth", 80);
Contact c1 = new Contact();
c1.FirstName = "Digo";
c1.LastName = "Maradona";
c1.ID = 0;
c1.SPForComboBox = new ObservableCollection<StackPanel>();
foreach (StackPanel sp in cbList.Items)
{
c1.SPForComboBox.Add(sp);
}
Contact c2 = new Contact();
c2.FirstName = "Brian";
c2.LastName = "Lara";
c2.ID = 1;
c2.SPForComboBox = new ObservableCollection<StackPanel>();
foreach (StackPanel sp in cbList.Items)
{
c2.SPForComboBox.Add(sp);
}
Contact c3 = new Contact();
c3.FirstName = "Sachin";
c3.LastName = "Tendulkar";
c3.ID = 2;
c3.SPForComboBox = new ObservableCollection<StackPanel>();
foreach (StackPanel sp in cbList.Items)
{
c3.SPForComboBox.Add(sp);
}
contacts.Add(c1);
contacts.Add(c2);
contacts.Add(c3);
lvFirst.ItemsSource = contacts;
HighCore, Thank you very much for the links. I have existing implementation and adding combobox to that.
I too felt that the method followed is not good. I shall certainly look at the alternatives provided by you and suggested by pushpraj.
Answer:
I thought referring objects in other combobox will work till the items exist in that combobox. But I need to create rectangle and textblock for reach combobox I am creating and for each entry in that combobox.
So certainly I need to do it in foreach loop.
Also, once I do this I can use SelectedIndex referring to the integer and SelectedItem to sp (individual stackpanel in that class).
This method is not good to follow but might be helpful for somebody.
Thanks.
ComboBox items do not reflect changes made from its source
Here is what I am trying to accomplish:
I have a WPF datagrid that binding to a database table, inside the datagrid there is a combobox(group ID) column bind to one of the columns from the database table; the combobox items are from another table(a list of group ID). The problem now is when the groupd ID list is changed from other table, the combo box items does not take effect.
Can anyone help? Have been stuct for a long time.
Here is XAML code:
<DataGridTemplateColumn Header="Group ID">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding GroupID, Mode=OneWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Name="ComboBoxTeamGrpID" SelectedItem="{Binding GroupID, Mode=TwoWay}" ItemsSource="{StaticResource ResourceKey=GroupIDList}">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
Here is the code for GroupIDList:
public class GroupIDList : List<string>
{
public GroupIDList()
{
try
{
string tmp = ConfigurationManager.AppSettings["DataSvcAddress"];
Uri svcUri = new Uri(tmp);
JP790DBEntities context = new JP790DBEntities(svcUri);
var deviceQry = from o in context.Devices
where o.GroupID == true
select o;
DataServiceCollection<Device> cList = new DataServiceCollection<Device>(deviceQry);
for (int i = 0; i < cList.Count; i++)
{
this.Add(cList[i].ExtensionID.Trim());
}
this.Add("None");
//this.Add("1002");
//this.Add("1111");
//this.Add("2233");
//this.Add("5544");
}
catch (Exception ex)
{
string str = ex.Message;
}
}
}
Here is another problem related, can anyone help? thank you.
It is either because your GroupIdList is a List and not an ObservableCollection, or because you're binding to a StaticResource, which WPF assumes is unchanged so is only loaded once.
Change your List<string> to an ObservableCollection<string> which will automatically notify the UI when it's collection gets changed, and if that still doesn't work than change your ItemsSource from a StaticResource to a RelativeSource binding, such as
ItemsSource="{Binding
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path=DataContext.GroupIdList}"
Edit
Your parent ViewModel which has your DataGrid's ItemsSource collection should look something like below. Simply add another public property for GroupIdList and have it return your list. Then use the above RelativeSource binding to access it, assuming your DataGrid's ItemsSource is bound in the form of <DataGrid ItemsSource="{Binding MyDataGridItemsSource}" ... />
public class MyViewModel
{
private ObservableCollection<MyDataObject> _myDataGridItemsSource;
public ObservableCollection<MyDataObject> MyDataGridItemsSource
{
get { return _myDataGridItemsSource; }
set
{
if (value != _myDataGridItemsSource)
{
_myObjects = value;
ReportPropertyChanged("MyDataGridItemsSource");
}
}
}
private ObservableCollection<string> _groupIdList = new GroupIdList();
public ObservableCollection<string> GroupIdList
{
get { return _groupIdList; }
}
}
WPF will not poll everytime and check if your list changed. In Order to do this, as Rachel pointed at you should do something like :
public class GroupIDList : ObseravableCollection<string>
EDIT :
Here is my suggestion :
I actually wouldn't do it the way you did. What I do is I create a View Model for the whole grid, that looks like :
public class MyGridViewModel : DependencyObject
Which I would use as data context for my grid:
DataContext = new MyGridViewModel ();
Now the implementation of MyGridViewModel will contain a list of ViewModel that represent my GridRows, which is an ObservableCollection
public ObservableCollection<RowGridViewModel> RowItemCollection { get; private set; }
I will this in my dataGrid as such :
<Grid>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding RowItemCollection}" SelectionMode="Extended" SelectionUnit="Cell">
<DataGrid.Columns>
and All you need to do, is to fill in you RowItemColleciton with the correct data, and then bind you Columns to the correct Property in RowGridViewModel...in your case it would look like (but you have to initialize the GroupIDList :
public class RowGridViewModel: DependencyObject
{
public List<String> GroudIDList { get; set;
}
}
Let me if that help
Im usign a Ribbon Window and in the "content area beneath" I have a grid in which I will be displaying UserControls. To demonstrate my problem lets take a look at this simple UserControl:
<ListView x:Name="lvPersonList">
<ListView.View>
<GridView>
<GridViewColumn Width="120" Header="Name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Width="120" Header="Height" DisplayMemberBinding="{Binding Height}"/>
</GridView>
</ListView.View>
</ListView>
And the code:
public partial class MyUserControl: UserControl
{
private List<Person> personList;
public TestSnpList()
{
InitializeComponent();
this.personList = new List<Person>();
this.personList.Add(new Person { Name = "Chuck Norris", Height = 210 });
this.personList.Add(new Person { Name = "John Rambo", Height = 200 });
this.lvPersonList.ItemsSource = personList;
}
}
public class Person
{
public string Name { get; set; }
public int Height { get; set; }
}
The parent Window:
<Grid x:Name="grdContent" DockPanel.Dock="Top">
<controls:MyUserControl x:Name="myUserControl" Visibility="Visible"/>
</Grid>
I don't understant why this binding doesn't work. Instead of values (Name and Height) I get full class names. If I use this code in a Window it works fine.
Any ideas? I would like this user contorl works for itself (it gets the data form the DB and represents it in a ListView)...
Thanks!
It seems the problem is with RibbonWindow.
If I use Window and UserControl binding works fine, but if I use RibbonWindow (Odyssey Ribbon) binding doesn't work. What I don't understand is that in design mode I can see proper values and in running mode I see only class names:
http://i977.photobucket.com/albums/ae255/HekoSLO/designModeVSrunning.png
I'm trying to get a WPF combobox working (inside the WPFToolkit Datagrid), and I'm having trouble getting all the pieces aligned correctly. I'm using Linq to Entities, and I'm setting the overall datacontext to the results of a Linq query:
private void LoadDonationGrid()
{
donationGrid.ItemsSource = from donations in entities.Donation
.Include("Family")
.Include("PledgeYear")
.Include("DonationPurpose")
from donationPurposes in entities.DonationPurpose
select new { donations, donationPurposes };
}
I also have a page property in my code-behind which provides the ItemsSource for the combobox:
private IEnumerable donationPurposeList;
public IEnumerable DonationPurposeList
{
get
{
if (donationPurposeList == null)
{
donationPurposeList = from dp in entities.DonationPurpose
select dp;
}
return donationPurposeList.ToList();
}
}
The XAML for the combobox looks like this:
<tk:DataGridTemplateColumn Header="Purpose">
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=donations.DonationPurpose.Description, Mode=TwoWay}" />
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
<tk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Name="cboDonationPurpose"
SelectedValue="{Binding Path=donations.DonationPurposeID, Mode=TwoWay}"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type Page},Mode=FindAncestor},Path=DonationPurposeList}"
DisplayMemberPath="Description"
SelectedValuePath="DonationPurposeID"
/>
</DataTemplate>
</tk:DataGridTemplateColumn.CellEditingTemplate>
</tk:DataGridTemplateColumn>
And everything seems to work correctly, i.e., the appropriate values are displayed in the ComboBox, right up to the point where focus leaves the ComboBox. At that point, the displayed value returns to the original value, not to the newly selected value. I've tried using SelectedItem instead of SelectedValue, but that ends up with the selected value not showing in the ComboBox. I'm kinda mystified: it seems like this bit should be working.
Edited 3/2/09: I'm still puzzling over this. I should note that in my datagrid, any simple data columns (e.g., "DataGridTextColumn") update the underlying data source just as you'd expect. But any update to any of my templated columns ("DataGridTemplateColumn") or ComboBox columns ("DataGridComboBoxColumn") don't work: the underlying data source never gets updated. Surely other folks have tried to use the WPF.Toolkit datagrid and gotten this scenario to work -- but I've looked at all the sample code out there, and I'm doing basically what it says to do (within the constraints of my solution), so I'm scratching my head why this isn't working.
Any thoughts?
I had a similar problem (on which I spent days of frustration), and I found that changing the UpdateSourceTrigger on the SelectedValue binding to PropertyChanged fixed it. I don't know why, but for me, the datasource wasn't being updated until I made this change.
<ComboBox
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UpdateTypesManager:MainWindow}}, Path=CardinalityTypes}"
DisplayMemberPath="CardinalityType"
SelectedValue="{Binding CardinalityTypeId, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Id" />
I was able to get this working. But I set things up a wee bit differently.
I created a ViewModel to act as a contract with the View.
I bound to the ComboBox.SelectedItem Property instead of ComboBox.SelectedValue Property
Since I didn't know what your data source was I made up my own to simulate the basic problem: having a comboBox bind correctly within a WPF DataGrid.
Here is the composition of my View Model:
public class RootViewModel
{
public List<State> USStates { get; set; }
public List<Customer> Customers { get; set; }
public ViewModel()
{
Customers = new List<Customer>();
Customers.Add(new Customer() { FirstName = "John", LastName = "Smith", State = new State() { ShortName = "IL" } });
Customers.Add(new Customer() { FirstName = "John", LastName = "Doe", State = new State() { ShortName = "OH" } });
Customers.Add(new Customer() { FirstName = "Sally", LastName = "Smith", State = new State() { ShortName = "IN" } });
USStates = new List<State>();
USStates.Add(new State() { ShortName = "OH" });
USStates.Add(new State() { ShortName = "IL" });
USStates.Add(new State() { ShortName = "IN" });
}
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public State State { get; set; }
}
public class State
{
public string ShortName { get; set; }
public State()
{
ShortName = string.Empty;
}
public override bool Equals(object obj)
{
if (obj is State)
{
State otherState = obj as State;
return ShortName.Equals(otherState.ShortName);
}
else
{
return false;
}
}
}
Before we begin, I set the DataContext of my Window to be an instance of my properly constructed RootViewModel.
<tk:DataGrid ItemsSource="{Binding Customers}" AutoGenerateColumns="False">
<tk:DataGrid.Columns>
<tk:DataGridTemplateColumn Header="State">
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=State.ShortName, Mode=TwoWay}" />
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
<tk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox
x:Name="cboDonationPurpose"
SelectedItem="{Binding Path=State, Mode=TwoWay}"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window},Mode=FindAncestor}, Path=DataContext.USStates}"
DisplayMemberPath="ShortName"
SelectedValuePath="ShortName" />
</DataTemplate>
</tk:DataGridTemplateColumn.CellEditingTemplate>
</tk:DataGridTemplateColumn>
</tk:DataGrid.Columns>
</tk:DataGrid>
In order for the SelectedItem to bind properly I need to ensure that I have overriden the Equals method on my entity since under the hood, WPF is using this method to determine who is the SelectedItem or not. I think this was fundamentally your problem from the beginning which caused you to try to bind to SelectedValue instead of SelectedItem.
You need to add
IsSynchronizedWithCurrentItem = "True"
in your Xaml.
It's as simple as that...