I cannot understand the behavior of the combobox.
I have an edit view to edit existing Order data. Here is my VM of some part of Order data:
public class OrderDataViewModel : ViewModelBase, IOrderDataViewModel
{
private readonly ICustomersListProviderService _customersListProviderService;
private readonly Order _order;
public OrderDataViewModel(Order order, ICustomersListProviderService customersListProviderService)
{
Assign.IfNotNull(ref _order, order);
Assign.IfNotNull(ref _customersListProviderService, customersListProviderService);
}
public DateTime CreationDate
{
get { return _order.CreationDate ?? (_order.CreationDate = DateTime.Now).Value; }
set
{
if (_order.CreationDate == value) return;
_order.CreationDate = value;
OnPropertyChanged();
}
}
public Customer Customer
{
get { return _order.Customer; }
set
{
if (_order.Customer == value) return;
_order.Customer = value;
OnPropertyChanged();
}
}
private IList<Customer> _customersList;
public IList<Customer> CustomersList
{
get { return _customersList ?? (_customersList = _customersListProviderService.GetAllCustomers().ToList()); }
}
}
And XAML binding:
<ComboBox Grid.Row="2" Grid.Column="1"
SelectedItem="{Binding OrderDataViewModel.Customer}"
DisplayMemberPath="Name"
ItemsSource="{Binding OrderDataViewModel.CustomersList}"
/>
Description. Order comes from the database by the Repository, _customersListProviderService gets all customers from database also. I know that maybe it could be done better, but it's not the point of the question.
And... the issue is. After loading a window, my combobox has items collection filled (dropdown list is not empty) but the value is not set (its blank). Checking SelectedItem by code-behind results with null. I read a lot and found out, that you cannot set SelectedItem of combobox to the item that is not in ItemsSource.
Ok, my workaround was to change the Customer getter to:
public Customer Customer
{
get
{ return CustomersList.Single(c => c.Id == _order.Customer.Id); }
set
{
if (_order.Customer == value) return;
_order.Customer = value;
OnPropertyChanged();
}
}
now it works, but it does not look good to me.
Is there any better solution?
you can override Equals() and GetHashCode() in your entities and return Id.Equals() and Id.GetHashCode() respectively
I just had a similar issue within an UWP app. I was binding to a string array.
<ComboBox SelectedItem="{Binding Carrier, Mode=TwoWay}" ItemsSource="{Binding Carriers}" />
The problem solved when I changed ItemsSource to be before SelectedItem:
<ComboBox ItemsSource="{Binding Carriers}" SelectedItem="{Binding Carrier, Mode=TwoWay}" />
Just in case someone has a similar issue.
Perhaps adding SelectedValuePath="Name" to your xaml like below will help
<ComboBox Grid.Row="2" Grid.Column="1"
SelectedItem="{Binding OrderDataViewModel.Customer}"
DisplayMemberPath="Name"
ItemsSource="{Binding OrderDataViewModel.CustomersList}"
SelectedValuePath="Name"
/>
Create a ViewModel with both sets of data you need i.e set to fill ComboBox and the Record. I will use Customer and year for convenience.
class CustomerDetailsViewModel
{
public CustomertModel CutomerModel { get; set; }
public YearListModel YearList { get; set; }
public CustomerDetailsViewModel(CustomerModel _CustomerModel)
{
CustomerModel = _CustomerModel;
YearList = new YearListModel();
}
}
So I fill the combobox with a list of years and I have my selected Customer record.
<ComboBox x:Name="cbCustomerDetailsYear" Margin="128,503,0,0"
DataContext="{Binding DataContext,
ElementName=CustomerDetailsPage}"
ItemsSource="{Binding YearList.Years, Mode=OneWay}"
DisplayMemberPath="Description"
SelectedValue="{Binding CustomerModel.YearID}"
SelectedValuePath="id" />
The viewmodel is assigned to the Page Datacontext and I bind this to the Combobox.
The combobox is populated with Itemsource and Displaymember with the Year model list from my Viewmodel. The description is just a string saying 1999, 2000 or whatever
The SelectedValue is bound to the Year foreign key in the Customer record also in the ViewModel. The SelectedValuePath is the magic ingredient that binds the 2 together. So the id represents the id of the year but will be bound to the customer YearID field and set by the Combobox id value.
Hope this is clear ?!?!?!
I hope this would work:
Check if Customer, is already binded to another Control. If so, remove all other Bindings which involve Customer.
I had the same problem where the IsChecked property of a CheckBox was also Binded to the viewModel but using a converter to check if it's null or not. I changed its binding to a boolean and the problem was no more.
Related
My goal is to have the bound property value update with the correct value if the selected profile changes its Name. Right now it maintains the old value which breaks binding on serialization/deserialization.
My XAML;
<ComboBox SelectedValue="{Binding value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinWidth="120" SelectedValuePath="Name" ItemsSource="{Binding Profiles}, Mode=OneWay}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:Profile}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox
My collection of selectible profiles;
ObservableCollection<Profile> profiles = new ObservableCollection<Profile>();
my Profile class;
public partial class Profile: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return _name; }
set { if (value != _name) { _name = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name")); } }
}
private string _name = "[Default]";
}
INotify seems to be working just fine because the displayed Name in the combobox does update for any profiles listed when their name changes. But when serialized the prop bound to SelectedValue known as value does not contain the new value for Name.
You are correct. Looks like SelectedValue is only updated when the selection changes in the ComboBox and changing the Name doesn't actually change the current selection thus no update. You could instead bind the SelectedItem to a property. That would contain the updated Name. Any controls would bind to SelectedItem.Name instead of Value.
If you must use SelectedValue then you could unselect and reselect the comboBox's selection (yuck!) or force the SelectedValue to update whenever you make changes to the Name property, for example:
if (comboBox1.SelectedItem != null) {
if (!comboBox1.SelectedValue.Equals(((Profile) comboBox1.SelectedItem).Name)) {
comboBox1.SelectedValue = ((Profile) comboBox1.SelectedItem).Name;
}
}
again, not that pretty but it works. There may be a way to get the SelectedValue's BindingExpression and force the update but simply calling UpdateSource on it doesn't seem to do anything unless the selection has changed.
comboBox1.GetBindingExpression (ComboBox.SelectedValueProperty).UpdateSource ();
I want to set selected value of a combobox
I am receiving a DataTable from the database looking like this:
this Datatable is bound to this combobox.
<ComboBox
DisplayMemberPath="KommuneNavn"
SelectedValuePath="KommuneNr"
ItemsSource="{Binding KommuneNavne}"
SelectedValue="{Binding KommuneNr, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="3"
IsEnabled="{Binding IsUdenlandskAdresse, Converter={StaticResource BooleanNotConverter}}" />
In my viewmodel I have a specific KommuneNr stored in a property. I would like to have my combobox set to show the KommuneNavn that matches with this KommuneNr.
Example:
I have the KommuneNr 101 stored in my viewmodel, the KommuneNavn that matches with this is København I would then like to have my combobox be set to København.
This was pretty difficult to explain, I hope I am making sense. Otherwise feel free to ask.
KommuneNavne must be an ObservableCollection, and in your ViewModel you should implement INotifyPropertyChanged (not described in this example)
<ComboBox
ItemsSource="{Binding KommuneNavne}"
SelectedValue="{Binding KommuneNr, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="KommuneNavn"
SelectedValuePath="KommuneNr"
/>
ViewModel
public class MyViewModel : INotifyPropertyChanged
{
public ObservableCollection<KommuneNavn> KommuneNavne
{
get { value = _kommuneNavne; } //=> _kommuneNavne;
set
{
_kommuneNavne = value;
OnPropertyChanged(nameof(KommuneNavne));
}
}
public long KommuneNr
{
get { value = _kommuneNr; } //=> _kommuneNr;
set
{
if (_kommuneNr == value) return;
_kommuneNr = value;
OnPropertyChanged(nameof(KommuneNr));
}
}
private void SetValue(int valueToSet)
{
KommuneNr = valueToSet;
}
}
Below is the XAML snippet for my combo-box in a datagrid.
<data:DataGridTemplateColumn Header="Entry Mode">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=EntryModeCombo,Mode=TwoWay}" DisplayMemberPath="Name" SelectedValuePath="Id" SelectedValue="{Binding Path=selectedEntryMode,Mode=TwoWay}" ></ComboBox>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
Entrymode is an entity in the system and the Id and Name properties of this entity are used to set the DisplayMemberPath and SelectedValuePath of the combo.
public class A
{
private ObservableCollection<EntryMode> _EntryModeCombo;
public ObservableCollection<EntryMode> EntryModeCombo
{
get { return _EntryModeCombo; }
set
{
_EntryModeCombo = value;
RaisePropertyChanged("EntryModeCombo");
}
}
private string _selectedEntryMode;
public string selectedEntryMode
{
get { return _selectedEntryMode; }
set
{
_selectedEntryMode = value;
RaisePropertyChanged("selectedEntryMode");
}
}
}
In my viewModel, I am making an observable collection of the class A, and using that to bind a grid. All works well in the ADD mode, but in the edit mode, when I try to set the selected value of the combobox in the grid, it does not work. The population of the combo-box happens, but it remains unselected. Not sure why the selectedEntryMode property is getting set, but not affecting the combo selection in the grid.
Any suggestions will be appreciated.Thanks.
SelectedValue can only be used for getting value. not setting. use SelectedItem insted
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
I am databinding a view to a viewmodel and am having trouble initializing a combobox to a default value. A simplification of the class I'm using in the binding is
public class LanguageDetails
{
public string Code { get; set; }
public string Name { get; set; }
public string EnglishName { get; set; }
public string DisplayName
{
get
{
if (this.Name == this.EnglishName)
{
return this.Name;
}
return String.Format("{0} ({1})", this.Name, this.EnglishName);
}
}
}
The combobox is declared in the view's XAML as
<ComboBox x:Name="LanguageSelector" Grid.Row="0" Grid.Column="1"
SelectedItem="{Binding SelectedLanguage,Mode=TwoWay}"
ItemsSource="{Binding AvailableLanguages}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
and the viewmodel contains this code
private List<LanguageDetails> _availableLanguages;
private LanguageDetails _selectedLanguage;
public LoginViewModel()
{
_availableLanguages = LanguageManager.GetLanguageDetailsForSet(BaseApp.AppLanguageSetID);
_selectedLanguage = _availableLanguages.SingleOrDefault(l => l.Code == "en");
}
public LanguageDetails SelectedLanguage
{
get { return _selectedLanguage; }
set
{
_selectedLanguage = value;
OnPropertyChanged("SelectedLanguage");
}
}
public List<LanguageDetails> AvailableLanguages
{
get { return _availableLanguages; }
set
{
_availableLanguages = value;
OnPropertyChanged("AvailableLanguages");
}
}
At the end of the constructor both _availableLanguages and _selectedLanguage variables are set as expected, the combobox's pulldown list contains all items in _availableLanguages but the selected value is not displayed in the combobox. Selecting an item from the pulldown correctly displays it and sets the SelectedLanguage property in the viewmodel. A breakpoint in the setter reveals that _selectedLanguage still contains what it was initialized to until it is overwritten with value.
I suspect that there is some little thing I'm missing, but after trying various things and much googling I'm still stumped. I could achieve the desired result in other ways but really want to get a handle on the proper use of databinding.
You need to change the order of you bindings in XAML so that your ItemsSource binds before the SelectedItem.
<ComboBox x:Name="LanguageSelector" Width="100"
ItemsSource="{Binding AvailableLanguages}"
SelectedItem="{Binding SelectedLanguage,Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
If you set a breakpoint on the 'get' of both the SeletedLanguage and AvailibleLanguage, you will notice that the SelectedLanguage gets hit before your AvailibleLanguage. Since that's happening, it's unable to set the SelectedLanguage because the ItemsSource is not yet populated. Changing the order of the bindings in your XAML will make the AvailibleLanguages get hit first, then the SelectedLanguage. This should solve your problem.
1) When you assign the SelectedLanguage, use the public property SelectedLanguage instead of the private _selectedLanguage, so that the setter gets executed,
2) You need to move the assignment of the selectedlanguage to the moment that the view has been loaded. You can do it by implementing the Loaded event handler on the View. If you want to be "mvvm compliant" then you should use a Blend behavior that will map UI loaded event to a viewmodel command implementation in which you would set the selected language.