WPF Binding Enum on a ComboBox MVVM - wpf

In my project I need to bing the following enum
public enum eFlex_IO_COMP_CM
{
ePan = 0,
eComp = 1,
}
into a combobox. I've a public list, valorized as follows:
public IEnumerable<eFlex_IO_COMP_CM> EnumFlexIoCompCm { get; set; }
EnumFlexIoCompCm = Enum.GetValues(typeof(eFlex_IO_COMP_CM)).Cast<eFlex_IO_COMP_CM>();
This is the XAML code:
<ComboBox
Grid.Column="20"
Width="50"
ItemsSource="{Binding Path=EnumFlexIoCompCm, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:ViewerFlexConfig}}"
SelectionChanged="Combo_SelectionChanged"
DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding ConfigObject.COMP_CounterMode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
/>
Basically I want to display the name of enum's item and set the key as value because I need to store in the database the int and not the description.
The problem is that I cannot even display the name of enum's item and set correctly the value, as If I try to save, the value from that combobox is name. Any helps?

Related

How to get the SelectedItem property from a ComboBox column in a DataGrid wpf

recently we started to use WPF at work.
Now I want to create a DataGrid from a list of objects (DataGrid ItemSource) that contains a project role, the employee that should do the job and a list of employees that could also do that job. Lets call this list the "MainList". In that DataGrid is a ComboBox Column that uses another list of objects as ItemSource where you can change the employee for the job. I will call this list the "ChildList". This list is included in the MainList (as allready mentioned) and I bind it by using the correct BindingPath. So far so good. Now I have to set the SelectedItem (to show which Employee is currently selected). From the MainList I can get the employee that should be selected from the ChildList. Obviously I cant do this by Binding. Unfortunatly I cant get the SelectedItem property in the code-behind. Basically I need to go through every row from the DataGrid and get the Item that should be selected in the ComboBox. Then I would go through the ComboBox Items until I find the matching Item and set this as SelectedItem. But I cant find a way to do this.
I tried using a DataGridComboBoxColumn aswell but it has only the SelectedItemBinding property and since you cant compare while binding this should not work.
I also tried to get every cell in the code-behind, that is a ComboBox but without success so far.
<DataGrid x:Name="DgvProjectTeam" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" AutoGenerateColumns="False" Margin="0" RowHeight="40" CanUserAddRows="False" BorderThickness="1" VerticalScrollBarVisibility="Auto" HorizontalGridLinesBrush="#FFA2B5CD" VerticalGridLinesBrush="#FFA2B5CD" ItemsSource="{Binding}" VirtualizingStackPanel.IsVirtualizing="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Resource" Width="200" x:Name="DgtProjectCoreTeam">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="CbxResource" ItemsSource="{Binding Path=ListOfPossibleResources}" DisplayMemberPath="ResourceOfQMatrix.Fullname"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The DataGrid shows everything I need. I just dont know how I can set the SelectedItem for each of the generated ComboBoxCells in the code-behind.
Anyone an idea?
Here's a quick example of what you can do. First, define your view models that will be bound to the DataGrid. Ideally, these view models would raise PropertyChanged or CollectionChanged when their properties are changed, but for this simple example this is not needed.
public class ViewModel
{
public List<ProjectRoleViewModel> ProjectRoles { get; set; }
}
public class ProjectRoleViewModel
{
public string Role { get; set; }
public string Employee { get; set; }
public List<string> OtherEmployees { get; set; }
public string SelectedOtherEmployee { get; set; }
}
I've hard-coded some dummy values to have data in the view models:
var viewModel = new ViewModel
{
ProjectRoles = new List<ProjectRoleViewModel>
{
new ProjectRoleViewModel
{
Role = "Designer",
Employee = "John Smith",
OtherEmployees = new List<string> {"Monica Thompson", "Robert Gavin"}
},
new ProjectRoleViewModel
{
Role = "Developer",
Employee = "Tom Barr",
OtherEmployees = new List<string> {"Jason Ross", "James Moore"}
}
}
};
This view model then needs to be assigned to the DataContext of your Window or UserControl that contains the DataGrid. Here's the XAML for the DataGrid:
<DataGrid
ItemsSource="{Binding ProjectRoles}"
AutoGenerateColumns="False"
>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Role}" />
<DataGridTextColumn Binding="{Binding Employee}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding OtherEmployees}"
SelectedItem="{Binding SelectedOtherEmployee, UpdateSourceTrigger=PropertyChanged}"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
After the user picks the "other" employee that can do the job, the view model's SelectedOtherEmployee will have the chosen value. You don't need any code-behind in this case, everything's contained in the view models.

why is it displaying class name instead of property?

I have the following combobox in the xaml:
<ComboBox x:Name="cmbCharacters1" HorizontalAlignment="Left" Margin="18,21,0,0" VerticalAlignment="Top" Width="136" SelectedIndex="0" Height="32" RenderTransformOrigin="1.53,-1.281"
ItemsSource="{Binding CharacterEntity}" SelectedItem="{Binding Name}" SelectedValue="{Binding Tag}"/>
and the following class and binding code
public class CharacterEntity
{
public string Name { get; set; }
public string Tag { get; set; }
}
....
cmbCharacters1.ItemsSource = characters;//it is a List<CharacterEntity>
when I run it displays the class name instead of the content of Name property, what am I doing wrong?
I think you forgot to use this: DisplayMemberPath="Tag" Or "Name" whatever you wish to display!
You need to set the DisplayMemberPath in your ComboBox XAML.
This isn't a binding, since the ItemsSource is already bound - you just reference the field name, like so:
<ComboBox DisplayMemberPath="Name" ...
In the XAML you are setting the ItemsSource to a class CharacterEntity instead of List<CharacterEntity>, since you are setting the Itemssource in the code-behind remove it from the XAML and try. Also, you need to set DisplayMemberPath="Name" and set either SelectedItem or SelectedValue not both, if you are using SelectedValue then also use SelectedValuePath="Name"
<ComboBox x:Name="cmbCharacters1" SelectedItem="{Binding someCharacter}" DisplayMemberPath="Name" />
See also this answer: https://stackoverflow.com/a/3797074/424129
C#
public class CharacterEntity
{
public string Name { get; set; }
public string Tag { get; set; }
}
// Look up how to implement INotifyPropertyChanged, I didn't bother here
public class MyViewModel : INotifyPropertyChanged {
public MyViewModel(IEnumerable<CharacterEntity> chars)
{
CharacterEntities = new List<CharacterEntity>(chars);
}
private IEnumerable<CharacterEntity> _characterEntities;
public IEnumerable<CharacterEntity> CharacterEntities {
get { return _characterEntities; }
set { _characterEntities = value;
OnPropertyChanged("CharacterEntities"); }
}
private CharacterEntity _characterEntity
public CharacterEntity SelectedCharacterEntity
}
XAML
ItemsSource is the source for the items. Your binding made no sense. You want to give it a list of CharacterEntity, but you bind to the CharacterEntity class? What list are you talking about? And don't set it in code behind. XAML makes much more sense if you use a viewmodel.
Now, somehow the above MyViewModel class needs to be made the DataContext of some control that owns the ComboBox.
<ComboBox HorizontalAlignment="Left" Margin="18,21,0,0"
VerticalAlignment="Top" Width="136" SelectedIndex="0" Height="32"
RenderTransformOrigin="1.53,-1.281"
ItemsSource="{Binding CharacterEntities}"
SelectedItem="{Binding SelectedCharacterEntity}"
DisplayMemberPath="Name"
/>
When you have it like this:
SelectedItem="{Binding Path=Name}"
it will use what ever is now selected in combobox, that class property of Name is being used as Selected. Without Path, you are binding to that combobox Name object. BUT anyways, this shouldn't yet work in your case with Path. So to have it work as you want it to, try this:
Have a SelectedItem binded to CharacterEntity class:
SelectedItem="{Binding SelectedEntity}" // Class instance of CharacterEntity
And then you have a Text binded to that selected entity class property of Name:
Text="{Binding Path=Name}" // Binded to property of Name
SelectedValue="{Binding Path=Tag}" // Binded to property of Tag
This way it should work. You should have a combobox binded to viewmodel and that viewmodel should have a property(class instance of CharacterEntity) of SelectedEntity. Hopefully this helps:
public class CharacterViewModel
{
public CharacterEntity SelectedEntity {get;set;}
public List<CharacterEntity> characters {get;set;} // use ObservableCollection insteand of List(Automatically update UI if list changes)
}
And XAML:
<ComboBox x:Name="cmbCharacters1" HorizontalAlignment="Left" Margin="18,21,0,0" VerticalAlignment="Top" Width="136" SelectedIndex="0" Height="32" RenderTransformOrigin="1.53,-1.281"
ItemsSource="{Binding characters}" Text="{Binding Path=Name}" SelectedItem="{Binding SelectedEntity}" SelectedValue="{Binding Path=Tag}"/>
Also has in codebehind e.g in constructor:
CharacterViewModel charViewModel = new CharacterViewModel();
cmdCharacters1.DataContext = charViewModel;
cmdCharacters1.ItemsSource= charViewModel.characters;
I'm terrible at explaining this, but I hope it makes sense with my code.

WPF datagrid with combobox bound to a different list of values on each row

I have a WPF user control bound to EmployeeDeductionViewModel with a DataGrid bound to an ObservableCollection of state tax parameters:
public ObservableCollection<Models.StateTaxParmModel> StateTaxSettings
{
get { return _stateTaxSettings; }
set
{
if (_stateTaxSettings != value)
{
_stateTaxSettings = value;
OnPropertyChanged();
}
}
}
Here is the binding on the datagrid (shortened to make it easier to read):
<DataGrid ItemsSource="{Binding Path=StateTaxSettings}" SelectedItem="{Binding Path=StateTaxSetting, Mode=TwoWay}"...
Inside each StateTaxParamModel is a list of possible values the ComboBox needs to bind to:
public ObservableCollection<ParamValue> Values
{
get { return _values; }
set
{
if (_values != value)
{
_values = value;
OnPropertyChanged();
}
}
}
The ParamValue class is really simple:
public class ParamValue
{
public int ValueID { get; set; }
public string ValueText { get; set; }
}
However, the list of possible Values (List of ParamValue) varies with each row. Therein lies the problem. I can bind a ComboBox inside of a DataGrid as long as the list is part of the UserControl DataContext but because the list varies with each row, I can't bind it to the main DataContext, I have to bind it to the ObservableCollection of Values (List of ParamValue) that's unique to each row. Can anyone please help me understand how this is accomplished?
Here is my DataGridTemplateColumn where I'm trying to bind to the row:
<DataGridTemplateColumn Header="Value" MinWidth="60">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Value, Mode=OneWay}" ToolTip="The value of the state tax parameter for the employee."/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=Values, Mode=OneWay}" DisplayMemberPath="ValueText" SelectedValuePath="ValueText" SelectedValue="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
Will had the right idea and in fact, that's where the list of values was located. As it turns out, everything laid out in my question is correct, I just wasn't populating the list of possible values at the right time. Lists work much better when there's something in the list! You'd think I'd know that after 18 years of doing this! ;-)

WPF Datagrid MVVM : Combobox Binding using DatagridTemplateColumn

Have a WPFDatagrid binded to combobox using Datagridtemplatecolumn. Finding difficult to get the selectedItem of the combobox binding. Have found similar examples around but that's not resolving my problem.
Please find the code snippet of my XAML and the data structure below:
public class X
{
public X ()
{
abc = new ObservableCollection<P>();
}
public ObservableCollection<P> Y
{
get { return abc; }
set { abc = value; PropertyChanged("Y"); }
}
}
public class P
{
private string id;
private string name;
public string ID
{
get
{
return id;
}
set
{
id = value;
InvokePropertyChanged("ID");
}
}
public string Name
{
get
{
return name;
}
set
{
name = value;
InvokePropertyChanged("Name");
}
}
}
I have a datastructure defined above that implements the INotifyPropertychanged interface.
<controls:DataGrid Name="datagrid" AutoGenerateColumns="False" ItemsSource="{Binding XList}" Grid.Row="0"
SelectedItem="{Binding SelectedX, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<controls:DataGrid.Columns>
<controls:DataGridTemplateColumn Header="Yl">
<controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Y}"
IsSynchronizedWithCurrentItem="False" DisplayMemberPath="Name"
SelectedValue="{Binding Path=SelectedY, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnTargetUpdated=True}"
SelectedValuePath="SelectedY"
SelectedItem="{Binding SelectedY, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</controls:DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumn>
</controls:DataGrid.Columns>
</controls:DataGrid>
Now, in view model, have a observablecollection of List of X i.e., XList and that is binded to the datagrid in the XAML. and have Y within each row of the datagrid binded to the Combobox. Have a property as SelectedY, binded to the SelectedItem of the combobox.
Have also a property as SelectedX binded to the selectedItem of the datagrid, which works fine.
Issue is not able to get the Selected Item binding of the Combobox. Not able to set the selected item for the combobox when the selection has changed.
Can anybody help me out to set the selecteditem binding of the combo box?
Where is set your datacontext ?
You can do something like that :
<controls:UserControl x:Name=MainControl>
<controls:UserControl.DataContext>
<classpath:X/>
</controls:UserControl.DataContext>
<controls:DataGrid ItemsSource={Binding YourItemsContainer}>
<controls:DataGrid.Columns>
<controls:DataGridComboBoxColumn ItemsSource={Binding ElementName=MainControl,Path=DataContext.Y}
SelectedItem={Binding ElementName=MainControl,Path=DataContext.SelectedY}
DisplayMemberPath=Name />
</controls:DataGrid.Columns>
</controls:DataGrid>
</controls:UserControl>
The idea is to set a name to the root element connected to your datacontext, then you can access to it's datacontext property easily by the path. When you are inside of a template, the datacontext is the ItemsSource objects.
Hope it will help you a little !

WPF DataGridTemplateColumn with ComboBox Binding (MVVM pattern)

I'm going bonkers with the following WPF DataGrid+ComboBox scenario.
I have a set of classes which look like;
class Owner
{
int ID { get; }
string Name { get; }
public override ToString()
{
return this.Name;
}
}
class House
{
int ID { get; }
Owner HouseOwner { get; set; }
}
class ViewModel
{
ObservableCollection<Owner> Owners;
ObservableCollection<House> Houses
}
Now my desired outcome is a DataGrid which shows a list of rows of type House, and in one of the columns, is a ComboBox which allows the user to change the value of House.HouseOwner.
In this scenario, the DataContext for the grid is ViewModel.Houses and for the ComboBox, I want the ItemsSource to be bound to ViewModel.Owners.
Is this even possible? I'm going mental with this... the best I've been able to do is to correctly get the ItemsSource bound, however the ComboBox (inside a DataGridTemplateColumn) is not showing the correct values for House.HouseOwner in each row.
NOTE: If I take the ComboBox out of the picture and put a TextBlock in the DataTemplate instead, I can correctly see the values for each row, but getting both an ItemsSource as well as show the correct value in the selection is not working for me...
Inside my code behind, I have set the DataContext on the Window to ViewModel and on the grid, the DataContext is set to ViewModel.Houses. For everything except this combobox, it's working...
My XAML for the offending column looks like;
<DataGridTemplateColumn Header="HouseOwner">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="Name"
SelectedItem="{Binding HouseOwner, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
SelectedValue="{Binding HouseOwner.ID, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Mode=OneWay}"
SelectedValuePath="ID" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Would love some help on this one... seems like a bit of Voodoo is required though...
as default.kramer said, you need to remove the RelativeSource from your bindings for the SelectedItem and SelectedValue like this (notice that you should add Mode=TwoWay to your binding so that the change in the combobox is reflected in your model).
<DataGridTemplateColumn Header="House Owner">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="Name"
SelectedItem="{Binding HouseOwner, Mode=TwoWay}"
SelectedValue="{Binding HouseOwner.ID}"
SelectedValuePath="ID"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
However, unlike he said, you don't have to remove the binding for the SelectedValue. In fact, if you remove it, it won't work (both SelectedValue and SelectedValuePath should be set here, as you've done), because that's what's allowing the binding mechanism to identify the selection from the combobox to the DataGrid's HouseOwner property.
SelectedValue/SelectedValuePath combination is very interesting. SelectedValuePath tells the databinding that the ID property of the Owner object currently selected represents its value, SelectedValue tells it that that value should be bound to the HouseOwner.ID which is the selected object on the DataGrid.
Therefore, if you remove those binding, the only thing the databinding mechanism will know is "what object is selected", and to make the correspondence between the selected item in the ComboBox and the HouseOwner property on the selected item in the DataGrid, they have to be "the same object reference". Meaning that, for example, the following wouldn't work:
Owners = new ObservableCollection<Owner>
{
new Owner {ID = 1, Name = "Abdou"},
new Owner {ID = 2, Name = "Moumen"}
};
Houses = new ObservableCollection<House>
{
new House {ID = 1, HouseOwner = new Owner {ID = 1, Name = "Abdou" }},
new House {ID = 2, HouseOwner = new Owner {ID = 2, Name = "Moumen"}}
};
(notice that the "HouseOwners" of the Houses collection are different (new) from the ones in the Owners collection). However, the following would work:
Owners = new ObservableCollection<Owner>
{
new Owner {ID = 1, Name = "Abdou"},
new Owner {ID = 2, Name = "Moumen"}
};
Houses = new ObservableCollection<House>
{
new House {ID = 1, HouseOwner = Owners[0]},
new House {ID = 2, HouseOwner = Owners[1]}
};
Hope this helps :)
Update: in the second case, you can get the same result without having the references being the same by overriding Equals on the Owner class (naturally, since it's used to compare the objects in the first place). (thanks to #RJ Lohan for noting this in the comments below)
Thanks for the help all - I finally worked out why I couldn't select the ComboBox items - was due to a mouse preview event handler I had attached to the cell style when I was using a DataGridComboBoxColumn.
Have slapped myself for that one, thanks for the other assistance.
Also, as a note; the only way this will work for me is with an additional;
IsSynchronizedWithCurrentItem="False"
Added to the ComboBox, else they all show the same value for some reason.
Also, I don't appear to require the SelectedValue/SelectedValuePath properties in my Binding, I believe because I have overridden Equals in my bound Owner type.
And lastly, I have to explicitly set;
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged
In the Binding in order for the values to be written back to the bound items when the ComboBox has changed.
So, the final (working) XAML for the binding looks like this;
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path=DataContext.Owners,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
IsSynchronizedWithCurrentItem="False"
SelectedItem="{Binding HouseOwner, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Cheers!
rJ
This is definitely possible and you're on the right track using an AncestorType binding for the ItemsSource. But I think I see a couple of mistakes.
First, your ItemsSource should be binding to DataContext.Owners, not DataContext.Houses, correct? You want the viewmodels' collection of Owners to show up in the drop-down. So first, change the ItemsSource and take out the Selection-related stuff, like this:
<ComboBox ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="Name" />
Now test it out and make sure the ItemsSource is working correctly. Don't try messing around with selection until this part works.
Regarding selection, I think you should be binding SelectedItem only - not SelectedValue. For this binding, you do not want a RelativeSource binding - the DataContext will be a single House so you can bind directly its HouseOwner. My guess is this:
<ComboBox ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="Name"
SelectedItem="{Binding HouseOwner}" />
Finally, for debugging bindings you can see the Visual Studio Output window or step up to a tool like Snoop or WPF Inspector. If you plan on doing a lot of WPF, I would recommend getting started with Snoop sooner than later.
Full example based on AbdouMoumen's suggestion. Also removed SelectedValue & SelectedValuePath.
//---------
//CLASS STRUCTURES.
//---------
//One grid row per house.
public class House
{
public string name { get; set; }
public Owner ownerObj { get; set; }
}
//Owner is a combobox choice. Each house is assigned an owner.
public class Owner
{
public int id { get; set; }
public string name { get; set; }
}
//---------
//FOR XAML BINDING.
//---------
//Records for datagrid.
public ObservableCollection<House> houses { get; set; }
//List of owners. Each house record gets an owner object assigned.
public ObservableCollection<Owner> owners { get; set; }
//---------
//INSIDE “AFTER CONTROL LOADED” METHOD.
//---------
//Populate list of owners. For combobox choices.
owners = new ObservableCollection<Owner>
{
new Owner {id = 1, name = "owner 1"},
new Owner {id = 2, name = "owner 2"}
};
//Populate list of houses. Again, each house is a datagrid record.
houses = new ObservableCollection<House>
{
new House {name = "house 1", ownerObj = owners[0]},
new House {name = "house 2", ownerObj = owners[1]}
};
<DataGrid ItemsSource="{Binding Path=houses, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" >
<DataGrid.Columns>
<DataGridTextColumn Header="name" Binding="{Binding name}" />
<DataGridTextColumn Header="owner (as value)" Binding="{Binding ownerObj.name}"/>
<DataGridTemplateColumn Header="owner (as combobox)" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path=owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="name"
SelectedItem="{Binding ownerObj, Mode=TwoWay}"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

Resources