I have a combo box that I have bound to a list that exists in my viewmodel. Now when a users makes a selection in that combo box I want a second combo box to update its content.
So, for example, combobox1 is States and combobox2 should contain only the Zipcodes of that state.
But in my case I don't have a predefined lists before hand for combobox2, I need to go fetch from a db.
Also, if needed, I could get all the potential values for combobox2 (for each combobox1 value) before hand, but I'd like to avoiding that if I can.
How do I implement in WPF and using MVVM? I'm fairly new to this whole wpf\databinding\mvvm world.
Something like the following. Note that the code is drastically simplified for the sake of example. In reality, your ViewModel would implement INotifyPropertyChanged and raise PropertyChanged events when the properties were modified.
The key though is the setter of SelectedState. Your ComboBox would bind its SelectedValue property to the ViewModel's SelectedState property. When the property changed, the ZipCodes collection gets re-loaded which another combobox would be bound to.
class MyViewModel {
public ObservableCollection<string> States {
get;
private set;
}
public ObservableCollection<string> ZipCodes {
get;
private set;
}
public string SelectedState {
get { return _selectedState; }
set {
_selectedState = value;
LoadZipCodes(_selectedState);
}
}
public string SelectedZipCode {
get;
set;
}
void LoadZipCodes(string state) {
// repopulate the ZipCodes property
}
}
Another solution. The approximate model:
class StateViewModel
{
public string StateName
{
get {...}
set {...}
}
public ObservableCollection<ZipCodeViewModel> ZipCodes
{
get {...}
set {...}
}
}
class ZipCodeViewModel
{
public string ZipCodeName
{
get {...}
set {...}
}
}
class MainViewModel
{
public ObservableCollection<StateViewModel> States
{
get {...}
set {...}
}
}
And XAML:
<ComboBox ItemsSource="{Binding Path=States}" IsSynchronizedWithCurrentItem="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=StateName}"></Label>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ContentControl Content="{Binding Path=States}">
<ContentControl.ContentTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=ZipCodes}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=ZipCodeName}"></Label>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Related
I have difficulties binding the selectedindex of a combobox to an object.
This is my code:
(Part of) CustomerClass
public class Customer : INotifyPropertyChanged
{
public int CountryCode
{
get { return _CountryCode; }
set { _CountryCode = value; NotifyPropertyChanged(); }
}
}
2a. (Part of) CustomListItem
<ComboBox x:Name="cboCountryCode" Grid.Column="5" ItemsSource="{Binding}" DisplayMemberPath="LongName" SelectedIndex="{Binding CountryCode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
2b. (Part of) CustomListItem
public partial class CustomerListItem : UserControl
{
public CustomerListItem()
{
InitializeComponent();
ObservableCollection<CountryCode> Liste = CountryCodes.Instance.List;
cboCountryCode.DataContext = Liste;
}
(Part of) MainPage
<ItemsControl Name="itcCustomers" Style="{StaticResource ItemsControlVirtualizedStyle}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:CustomerListItem/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The Combobox List Items are shown correctly.
But the selected index is not working at all
See this screenshot
I found the problem. I tried to bind the Combobox to two different Datasources. One for the collection and one for the selectedindex. Now I combinde these two Datasources into one class and bound to it, now it works fine
I am having a hard time finding the right syntax for binding to a ComboBox's SelectedItem's property. This is the XAML I am trying to use for the binding. Where you see SelectedItem.Mode is the idea I am having difficulty with. Note that CurrentMode is in the ViewModel and has the same type as SelectedItem.Mode
<ComboBox SelectedItem.Mode="{Binding Path=CurrentMode, Mode=TwoWays}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding ImageSource}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<local:ModeItem Mode="Free" ImageSource="pencil.png"/>
<local:ModeItem Mode="Arrow" ImageSource="arrow.png"/>
</ComboBox>
A local:ModeItem looks like this
public class ModeItem : DependencyObject, INotifyPropertyChanged
{
public static readonly DependencyProperty ModeProperty = DependencyProperty.Register("Mode", typeof(AnnotationMode), typeof(ModeItem));
public AnnotationMode Mode
{
get { return (AnnotationMode)GetValue(ModeProperty); }
set { SetValue(ModeProperty, value); }
}
public string ImageSource { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
I am using MVVM and trying to bind the AnnotationMode (CurrentMode) of the ViewModel to that of the ComboBox's SelectedItem's AnnotationMode (Mode)
Just do this
SelectedItem="{Binding CurrentMode}
You don't have to do all this extra stuff you are doing. Note You do need to make the datacontext of the combobox is pointing to your viewmodel.
Edit :-
You should be able to do this
SelectedValue="{Binding CurrentMode, Mode=TwoWay}"
SelectedValuePath="Mode"
I am using below Datagrid, (using MVVM pattern), here what I want is when I select something in the combobox, some kind of notification should happen in the ViewModel saying that this Row’s combobox selectedItem is changed to this value. Right now the notification is happening in the Set method of SelectedEname which is inside class SortedDetails(custom entity) and not a part of viewmodel. Please have a look at the code below and let me know If we can send the notification to videmodel in any way using MVVM pattern.
<c1:C1DataGrid x:Name="datagrid1" ItemsSource="{Binding Path=SortedDetailsList,Mode=TwoWay}" AutoGenerateColumns="False">
<c1:C1DataGrid.Columns>
<c1:DataGridTextColumn Header="Name" Binding="{Binding Name, Mode=TwoWay}"/>
<c1:DataGridTemplateColumn Header="ENGAGEMENT">
<c1:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="cmbEngagement" ItemsSource="{Binding EDetails, Mode=TwoWay}" DisplayMemberPath="EName" SelectedItem="{Binding SelectedEName,Mode=TwoWay}">
</ComboBox>
</DataTemplate>
</c1:DataGridTemplateColumn.CellTemplate>
</c1:DataGridTemplateColumn>
</c1:C1DataGrid.Columns>
</c1:C1DataGrid>
SortedDetailsList is a list of SortedDetails entity, which looks like this :-
public class SortedDetails
{
Private string name;
Private ObservableCollection<details> eDetails;
Private details selectedEname;
public string Name
{
get { return name; }
set { name = value; }
}
public ObservableCollection<details> EDetails
{
get { return eDetails; }
set { eDetails = value; }
}
public details SelectedEname
{
get { return selectedEname; }
set { selectedEname = value; }
}
}
public class Details
{
Private string eName;
Private int eId;
public string EName
{
get { return eName; }
set { eName = value; }
}
public int EId
{
get { return eId; }
set { eId = value; }
}
}
The reason i was asking this question was because i was looking to avoid writing code in codebehind, but in this case not able to avoid the same. So, here is the solution (for me) :-
Add an event delegate or any mediator pattern which will inform the ViewModel that selection is changed from the SelectionChanged event of Combobox...
You can put your ViewModel in the View's resources and bind to the property of ViewModel:
<ComboBox x:Name="cmbEngagement" ItemsSource="{Binding EDetails, Mode=TwoWay}" DisplayMemberPath="EName" SelectedItem="{Binding SelectedEName, Mode=TwoWay, Source={StaticResource ViewModel}">
where SelectedEName is a property of your ViewModel.
You want to use some mechanism for allowing events to invoke commands or verbs (methods) on your view model.
For example, using Actions in Caliburn.Micro, you could write the following:
<ComboBox x:Name="cmbEngagement" ...
cal:Message.Attach="[Event SelectionChanged] = [Action EngagementChanged($dataContext)]>
and in your view model:
public void EngagementChanged(SortedDetails details)
{
// access details.Name here
}
Note that actions in Caliburn.Micro bubble, so it would first look for an EngagementChanged method on SortedDetails type, and then look on your view model.
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.