ComboBox Binding in WPF - wpf

I am not able to set the selected value of a combobox.
this is how i am doing.
ComboBox x:Name="cmbProjectStatus" ItemsSource="{Binding ItemListCollection}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
SelectedValue="{Binding Path=ItemList.ID}"
SelectedItem="{Binding Path=ItemList}"
HorizontalAlignment="Stretch" VerticalAlignment="Center" />
I am using MVVM pattern in my project
Please Help...

but wait, your selected value is defined because you set selecteditem and selectedvaluepath ;)you don't have to set selectedvalue, andEDITItemList seted as SelectedItem exists in ItemListCollection
This should work
ComboBox x:Name="cmbProjectStatus" ItemsSource="{Binding ItemListCollection}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
SelectedItem="{Binding Path=ItemList}"
HorizontalAlignment="Stretch" VerticalAlignment="Center" />
if you want to get it worked in your case just override Equals method in your Item class like this
public class Item
{
...
public override bool Equals(object obj)
{
Item i = (Item)obj;
if (i.ID == this.ID)
return true;
return base.Equals(obj);
}
...
}

Related

Setting ComboBox.SelectedItem at bind time?

I have a ComboBox in a DataTemplate which is being selected by a cell template selector in a DataGrid.
How do I set the SelectedItem to zero when the ComboBox is bound to its ItemsSource? There's often just one item and I want it to appear immediately instead of having to be selected by the user.
My DataGrid column looks like this:
<DataGridTemplateColumn Header="Qty Avl">
<DataGridTemplateColumn.CellTemplateSelector>
<selectors:PartAvailableSelector StrTemplate="{StaticResource PartAvailableAtStrTemplate}">
<selectors:PartAvailableSelector.NetTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding AltLocations}"
DisplayMemberPath="Name"
SelectedItem="0"
/>
</DataTemplate>
</selectors:PartAvailableSelector.NetTemplate>
</selectors:PartAvailableSelector>
</DataGridTemplateColumn.CellTemplateSelector>
</DataGridTemplateColumn>
My selector has DataTemplate properties, just because it's easier. I inlined the NetTemplate template for this post. I normally have it in my window resources.
SelectedItem will the hold entire object from ItemsSource, to set the 0 item as selected you need to set SelectedIndex="0" or in ViewModel u need to bind SelectedItem="{Binding SLocation}" to AltLocations[0]
<ComboBox ItemsSource="{Binding AltLocations}"
DisplayMemberPath="Name"
SelectedIndex="0"
/>
Or
<ComboBox ItemsSource="{Binding AltLocations}"
DisplayMemberPath="Name"
SelectedItem="{Binding SLocation}"
/>
Vm
private Location sLocation
public Location SLocation
{
get { return sLocation; }
set
{
sLocation= value;
OnPropertyChanged(new PropertyChangedEventArgs("SLocation"));
}
}
//Ctor
SLocation=AltLocations[0];

WPF MVVM datagrid update property on change of another

I'm trying to create a datagrid for creating an work assignment list (AssignmentPlanItem class), which has comboboxes for Employee, Assignment and Workcenter (all separate classes and foreign keys of the AssignmentPlanItem. The plan is filled straight to the datagrid. I know this might be easier if adding items was done through a form, but I think this is a snappy method, and I don't want to change it.
After numerous days on this issue I have got everything else working, but I also have a DefaultAssignmentId as a property of the Employee class, and I would like the DefaultAssignment to be fetched automatically to the datagrid's assignment field when the employee is selected. This is my first WPF application, so it might be that my code works only by some miraculous chance, so feel free to give general hints. I feel I have tried every possible combination for the bindings, so now I have to ask for help, as I couldn't find anything with Google.
XAML:
<Grid Margin="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<DataGrid x:Name="assignmentPlanItemsDataGrid" Margin="0,3,0,0" ItemsSource="{Binding DataGridRows, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" AutoGenerateColumns="False" CanUserAddRows="False" SelectedItem="{Binding CurrentItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" IsSynchronizedWithCurrentItem="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Employee" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path = DataContext.EmployeeComboRows, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"
SelectedItem="{Binding DataContext.SelectedEmployee,Mode=TwoWay, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"
SelectedValue="{Binding EmployeeId, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Id"
IsEditable="True"
DisplayMemberPath="FullName">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Assignment" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.AssignmentComboRows, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"
SelectedItem="{Binding DataContext.SelectedAssignment,Mode=TwoWay, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"
SelectedValuePath="Id"
DisplayMemberPath="Description"
SelectedValue="{Binding AssignmentId, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
IsEditable="True"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
The ViewModel:
public class AssignmentPlanItemViewModel : ViewModelBase
{
DataContext context = new DataContext();
//the datagrid collection
private ObservableCollection<AssignmentPlanItem> _dataGridRows = new ObservableCollection<AssignmentPlanItem>();
//datagrid selected item
private AssignmentPlanItem _currentItem;
//combobox itemssource collections
public ObservableCollection<Employee> EmployeeComboRows { get; set; }
public ObservableCollection<Assignment> AssignmentComboRows { get; set; }
public ObservableCollection<WorkStation> WorkStationComboRows { get; set; }
//the source event for the current assignment plan
public Event CurrentEvent;
public AssignmentPlanItemViewModel()
{
//populate combobox collections
EmployeeComboRows = new ObservableCollection<Employee>(context.Employees);
AssignmentComboRows = new ObservableCollection<Assignment>(context.Assignments);
WorkStationComboRows = new ObservableCollection<WorkStation>(context.WorkStations);
//getting the current event (yes, non-MVVM, I know)
CurrentEvent = context.Events.Find(AssignmentPlanWindow.eventId);
var planItems = CurrentEvent.AssignmentPlans.Last().AssignmentPlanItems;
DataGridRows = new ObservableCollection<AssignmentPlanItem>(planItems);
}
public AssignmentPlanItem CurrentItem
{
get { return _currentItem; }
set
{
if (value != _currentItem)
{
_currentItem = value;
OnPropertyChanged("CurrentItem");
OnPropertyChanged("DataGridRows");
}
}
}
public ObservableCollection<AssignmentPlanItem> DataGridRows
{
get { return _dataGridRows; }
set
{
_dataGridRows = value;
OnPropertyChanged("DataGridRows");
}
}
private Employee _selectedEmployee;
public Employee SelectedEmployee
{
get
{
return _selectedEmployee;
}
set
{
if (CurrentItem != null)
{
_selectedEmployee = value;
if (_selectedEmployee != null)
{
CurrentItem.EmployeeId = _selectedEmployee.Id;
var defaultAssigment = context.Assignments.Find((int)_selectedEmployee.DefaultAssignmentId);
CurrentItem.Assignment = defaultAssigment;
CurrentItem.AssignmentId = (int)_selectedEmployee.DefaultAssignmentId;
OnPropertyChanged("CurrentItem");
}
}
}
}
private Assignment _selectedAssignment;
public Assignment SelectedAssignment
{
get
{
return _selectedAssignment;
}
set
{
if (CurrentItem != null)
{
_selectedAssignment = value;
if (_selectedAssignment != null)
{
CurrentItem.AssignmentId = _selectedAssignment.Id;
CurrentItem.Assignment = _selectedAssignment;
OnPropertyChanged("CurrentItem");
}
}
}
}
}
So, I use the SelectedEmployee and SelectedAssignment properties to try to change the selected item of the datagrid (CurrentItem). The item is changed, but the change is not updated to the grid. When I save the grid, close and get back, the assignment has also changed.
In the XAML Assignment Combobox I tried
<SelectedValue="{Binding DataContext.CurrentItem.AssignmentId, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"/>
which actually got the view updating, but it changed all the assignment fields for all the rows in the datagrid to the same value as in the CurrentItem, even though I had IsSynchronizedWithCurrentItem=False everywhere.
My model classes do not implement INotifyPropertyChanged and the ViewModelBase I ripped somewhere from the web.
So, can anyone tell me what I'm doing wrong?
OK, I got it working with the help of ΩmegaMan. The solution was to have AssignmentPlanItem inherit from ViewModelBase (i.e. implement INotifyPropertyChanged), and change the AssignmentId property from
public AssignmentId {get; set; }
to
private int _assignmentId;
public int AssignmentId
{
get { return _assignmentId; }
set
{
_assignmentId = value;
OnPropertyChanged("AssignmentId");
}
}
The datagrid comboboxes had to have the following setup (not quite sure still if there is something superfluous):
<DataGrid x:Name="assignmentPlanItemsDataGrid" Margin="0,3,0,0" ItemsSource="{Binding DataGridRows, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False" CanUserAddRows="False" SelectedItem="{Binding CurrentItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsSynchronizedWithCurrentItem="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Employee" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path = DataContext.EmployeeComboRows, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"
SelectedItem="{Binding DataContext.SelectedEmployee,Mode=OneWayToSource, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"
SelectedValue="{Binding EmployeeId}"
SelectedValuePath="Id"
IsEditable="True"
DisplayMemberPath="FullName"
IsSynchronizedWithCurrentItem="False">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Assignment" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.AssignmentComboRows, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"
SelectedItem="{Binding DataContext.SelectedAssignment,Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"
SelectedValuePath="Id"
DisplayMemberPath="Description"
SelectedValue="{Binding AssignmentId}"
IsEditable="True"
IsSynchronizedWithCurrentItem="False"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
And the SelectedEmployee in the ViewModel had the following code to change the Assignment:
private Employee _selectedEmployee;
public Employee SelectedEmployee
{
get
{
return _selectedEmployee;
}
set
{
if (CurrentItem!= null)
{
_selectedEmployee = value;
OnPropertyChanged("SelectedEmployee");
if (SelectedEmployee != null)
{
CurrentItem.EmployeeId = SelectedEmployee.Id;
var defaultAssigment = Context.Assignments.Find((int)SelectedEmployee.DefaultAssignmentId);
CurrentItem.AssignmentId = (int)SelectedEmployee.DefaultAssignmentId;
CurrentItem.Assignment = defaultAssigment;
}
}
}
There was still one tricky part, namely setting the ComboBox SelectedItem binding mode to OneWayToSource. Without this, all the comboboxes in the column would get the Assignment of the CurrentItem. So to my feeble understanding this means that the ComboBox binding mode handles the updating to the ViewModel and Model, and the property change notification on the Model takes it back to the view through the SelectedValue. I'm still not sure whether it works or should work like this, but anyway it functions fully the way I want.
There are many levels of INotifyPropertyChange which needs to be understood.
When assigning to a list type structure, the notify is only for when the reference to the list changes; aka a new list has been created. The notify event does not flag any changes to what is, or is not in the list, nor any individual items in the list which may have a property change.
An observable collection does send notifications on when an item in its list is added or removed, but not when an individual item in the list's property changes.
If you want an item's property to be reflected in the data grid after a change to its property, then that object instance must adhere to INotifyPropertyChanged and the property must call the PropertyChanged with its property name to be broadcasted.
What you most likely have is a DTO object which does not adhere to INotifyPropertyChanged hence even though the current instance is properly referenced in your Selected... reference, the control that contains/displays the specific property value has no way of knowing the property has been changed; because it only monitors for a change event of that properties name.
What you need to do in your situation is create a Partial class off of your working classes and add INotifyPropertyChanged to the partial and supply an override to properties with change calls (PropertyChanged("FirstName)` (or whatever your method call is)) which will need to show their change.
Though this doesn't speak to your direct situation, here is my blog article that does show how one can effectively use INotifyPropertyChanged. Its on a VM, but the same methods can be applied to your partial DTO object.
Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding

How to bind itemsource in combobox based on another combox items in wpf?

If i have items in one combobox are CFG_REG,INT_REG,ST_REG,CMD_REG(which are defined in enum), if i select item CFG_REG then i should display GCR,PCR,LCR,CR,GSR,PSR in another combobox similarly,if i select INT_REG i should display IE,IS like that,.. How do i do that?
<ComboBox Grid.Column="2"
Grid.Row="1"
SelectedIndex="{Binding CMDIndex, Mode=TwoWay}"
x:Name="Combobox1"
Margin="0,0,1,0"
VerticalAlignment="Top">
</ComboBox>
<ComboBox Grid.Column="3" IsTextSearchEnabled="True"
Grid.Row="1"
x:Name="combobox2"
ItemsSource="{Binding }"
SelectedItem="{Binding RegisterIndex,Mode=TwoWay}"
VerticalAlignment="Top" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="Name"
Margin="0,0,1,0">
</ComboBox>
You should bind a collection of items (i.e. ICollection or Observable collection) in your view model/Code to the first Combo box's itemsSource. You can bind the 'SelectedItem' of the first combo box to a property in the code behind/view model and then in the setter of this property, you should filter out another Collection which will be bound to other Combo box. I hope you get the idea.
For example:
<ComboBox ItemsSource ={Binding Collection1} SelectedItem ={Binding SelectedItem} .../>
In the Code:
public ICollection Collection1 {get;set;}
public ICollection Collection2 {get;set;}
public string SelectedItem
{
get {..}
set{
SelectedItem = value;
ChangeSecondCollection(value);
}
public void ChangeSecondCollection(string value)
{
Collection2 = //Filter your second collection here.
}

Wpf Combobox with databinding: initial value is empty

In the following xaml fragment SessoList is a list of string ("M" and "F").
<ComboBox IsEditable="False" Margin="5" SelectedValue="{Binding Sesso}" ItemsSource="{Binding SessoList}" Width="40" Height="28"/>
The combobox works as expected and it is pre-populated reflecting the value of Sesso in the viewmodel.
The combobox selectable items are only two and fixed so I tried to simplify defining them in xaml:
<ComboBox IsEditable="False" Margin="5" SelectedValue="{Binding Sesso}" SelectedValuePath="{Binding Tag}" Width="40" Height="28" Name="Primo">
<ComboBoxItem Content="M" Tag="M" />
<ComboBoxItem Content="F" Tag="F" />
</ComboBox>
This combobox is capable of updating the viewmodel property sesso, but is not pre-populated with the correct value.
The following error is reported:
BindingExpression path error: 'Tag' property not found on 'object'
How can I successfully define the combobox items in xaml and have it display the right value based on SelectedValue databinding ?
Forgot to mention I'm using .Net 4.0
As I can understand, you want to define the ComboBox ItemsSource in XAML,
here is the solution that worked for me:
Xaml Window resources:
<Window.Resources>
<x:Array x:Key="Array" Type="{x:Type nirHelpingOvalButton:ComboObjectModel}">
<nirHelpingOvalButton:ComboObjectModel Content="M_Content" Tag="M_Tag"/>
<nirHelpingOvalButton:ComboObjectModel Content="F_Content" Tag="F_Tag"/>
</x:Array>
Xaml Combo:
<Grid>
<ComboBox IsSynchronizedWithCurrentItem="True" IsEditable="False" SelectedIndex="0" Margin="5" ItemsSource="{StaticResource Array}"
SelectedValue="{Binding Content, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
SelectedValuePath="Tag" Width="90" Height="28" Name="Primo">
<i:Interaction.Behaviors>
<nirHelpingOvalButton:CustomComboSelectionBehavior/>
</i:Interaction.Behaviors>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox></Grid>
View model combo SelectedValue binded property:
public string Content
{
get { return _content; }
set
{
_content = value;
OnPropertyChanged("Content");
}
}
List item Behavior Code:
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
var firstItem = AssociatedObject.ItemsSource.Cast<object>().FirstOrDefault();
AssociatedObject.SelectedItem = firstItem;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= OnLoaded;
}
regards

Binding a Path in the class to an int property

I would like to bind a property in the source data to an int. Take for instance:
<ComboBox Grid.Row="1" Grid.Column="2" VerticalAlignment="Center" ItemsSource="{Binding Makes}" SelectedItem="{Binding Path=Make_ID}" DisplayMemberPath="MakeDesc" />
From the ViewModel:
public short Make_ID { get { return Vehicle.Make_ID; } set { Vehicle.Make_ID = value; OnPropertyChanged("Make_ID"); } }
Makes is a class that has ID, MakeDesc, etc. My View Model is interested in the selected make but only it's ID. I know I could do this with IValueConverters but I'd rather not have to do that -- I think there is a way to do that on the binding, I just can't remember how.
The answer is to use SelectedValue and SelectedPath instead.
<ComboBox Grid.Row="1" Grid.Column="2" VerticalAlignment="Center" ItemsSource="{Binding Makes}" SelectedValue="{Binding Path=Make_ID}" SelectedValuePath="ID" DisplayMemberPath="MakeDesc" />

Resources