I have a ComboBox cbo1.
I'm trying to change the item source using the ViewModel with CollectionChanged but the ComboBox items stay blank and won't update.
I've tried several examples and solutions here, but don't know how to implement them right.
XAML
<ComboBox x:Name="cbo1"
ItemsSource="{Binding cbo1_Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding cbo1_SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left"
Margin="0,0,0,0"
VerticalAlignment="Top"
Width="105"
Height="22" />
ViewModelBase Class
Bind ComboBox Items
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public ViewModelBase()
{
_cbo1_Items = new ObservableCollection<string>();
_cbo1_Items.CollectionChanged += cbo1_Items_CollectionChanged;
}
// Notify Collection Changed
//
public void cbo1_Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Notify("cbo1_Items");
}
// Item Source
//
public static ObservableCollection<string> _cbo1_Items = new ObservableCollection<string>();
public static ObservableCollection<string> cbo1_Items
{
get { return _cbo1_Items; }
set { _cbo1_Items = value; }
}
// Selected Item
//
public static string cbo1_SelectedItem { get; set; }
}
Example Class
In this class I want to change the ComboBox Item Source.
// Change ViewModel Item Source
//
ViewModelBase._cbo1_Items = new ObservableCollection<string>()
{
"Item 1",
"Item 2",
"Item 3"
};
// ...
// Change Item Source Again
//
ViewModelBase._cbo1_Items = new ObservableCollection<string>()
{
"Item 4",
"Item 5",
"Item 6"
};
Implement - RaisePropertyChanged("ComboBoxItemsource");/NotifyPropertyChanged("ComboBoxItemsource") in your property declaration.
Ex: -
In View
<ComboBox Width="40" Height="40" ItemsSource="{Binding ComboBoxItemsource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
In View Model-
private ObservableCollection<string> comboBoxItemsource;
public ObservableCollection<string> ComboBoxItemsource
{
get { return comboBoxItemsource; }
set
{
if (comboBoxItemsource != value)
{
comboBoxItemsource = value;
RaisePropertyChanged("ComboBoxItemsource");
}
}
}
In Class Constructor-
public ClassViewModel()
{
ComboBoxItemsource = new ObservableCollection<string>();
ComboBoxItemsource.Add("Item1");
ComboBoxItemsource.Add("Item2");
....
}
//Event on which you want to change the collection
public void OnClickEvent()
{
ComboBoxItemsource = new ObservableCollection<string>();
ComboBoxItemsource.Add("Item5");
ComboBoxItemsource.Add("Item6");
}
Class should Inherit and Implement INotifyPropertyChanged.
Hope this Helps..
Related
My picker stays empty. I already created a test list to test it in particular but it doesn't work either.
this is my XAML
<Picker x:Name="picker1" Grid.Row="1" Grid.Column="1" ItemsSource="{Binding TestList}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding AdditionSort}"/>
this is my code behind
List<AdditionSort> TestList
{
get => testList;
set => SetValue(ref testList, value);
}
List<AdditionSort> testList = new List<AdditionSort>();
void LoadList()
{
TestList.Add(new AdditionSort { Name = "test1" });
TestList.Add(new AdditionSort { Name = "test2" });
}
when I'm debugging I can see that my list is correct.
1)Use System.Generics.ObjectModel.ObservableCollection instead of List.
ObservableCollection notifies View on CollectionChanges where as List doesnot do that.
(Or)
2) Add items to List at initialization of List
List<AdditionSort> testList = new List<AdditionSort>()
{
new AdditionSort(),
new AdditionSort(),
new AdditionSort(),
new AdditionSort(),
}
I wrote a demo to make the binding work and you can check the code:
In code behind:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
myModel m = new myModel();
BindingContext = m;
}
}
public class myModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
List<AdditionSort> testList = new List<AdditionSort>();
public List<AdditionSort> TestList
{
get { return testList; }
set
{
testList = value;
OnPropertyChanged();
}
}
public myModel() {
LoadList();
}
void LoadList()
{
TestList.Add(new AdditionSort { Name = "test1" });
TestList.Add(new AdditionSort { Name = "test2" });
}
}
public class AdditionSort
{
public string Name { get; set; }
}
And in Xaml:
<StackLayout>
<!-- Place new controls here -->
<Picker x:Name="picker1" ItemsSource="{Binding TestList}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding AdditionSort}"/>
</StackLayout>
I uploaded my sample project here.
Also, here is the document: Setting a Picker's ItemsSource Property
I'm just getting used to MVVM and want to do without the code-behind and define everything in the view-models.
the combobox represents several selection options (works). I would like to query the elements that have been checked.
Unfortunately I can't access them. The textbox should display all selected elements as concatenated string.
View-Model
class MainViewModel : BaseViewModel
{
#region Fields
private ObservableCollection<EssayTypeViewModel> _essayTypes;
private EssayTypeViewModel _selectedEssayTypes;
#endregion
public ObservableCollection<EssayTypeViewModel> EssayTypes
{
get => _essayTypes;
set
{
if (_essayTypes == value) return;
_essayTypes = value; OnPropertyChanged("EssayTypes");
}
}
public EssayTypeViewModel SelectedEssayTypes
{
get => _selectedEssayTypes;
set { _selectedEssayTypes = value; OnPropertyChanged("SelectedEssayTypes"); }
}
public MainViewModel()
{
// Load Essay Types
EssayTypeRepository essayTypeRepository = new EssayTypeRepository();
var essayTypes = essayTypeRepository.GetEssayTypes();
var essayTypeViewModels = essayTypes.Select(m => new EssayTypeViewModel()
{
Text = m.Text
});
EssayTypes = new ObservableCollection<EssayTypeViewModel>(essayTypeViewModels);
}
}
XAML
<ListBox x:Name="Listitems" SelectionMode="Multiple" Height="75" Width="200" ItemsSource="{Binding EssayTypes}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding Checked}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Text="{Binding Path=SelectedEssayTypes}" Grid.Column="0" Width="160" Height="25" Margin="0,140,0,0"/>
You could hook up an event handler to the PropertyChanged event of all EssayTypeViewModel objects in the EssayTypes collection and raise the PropertyChanged event for a read-only property of the MainViewModel that returns all selected elements as concatenated string:
public MainViewModel()
{
// Load Essay Types
EssayTypeRepository essayTypeRepository = new EssayTypeRepository();
var essayTypes = essayTypeRepository.GetEssayTypes();
var essayTypeViewModels = essayTypes.Select(m =>
{
var vm = EssayTypeViewModel()
{
Text = m.Text
};
vm.PropertyChanged += OnPropertyChanged;
return vm;
});
EssayTypes = new ObservableCollection<EssayTypeViewModel>(essayTypeViewModels);
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Checked")
OnPropertyChanged("SelectedItems");
}
public string SelectedItems => string.Join(",", EssayTypes.Where(x => x.Checked).ToArray());
This requires the EssayTypeViewModel class to implement the INotifyPropertyChanged interface (by for example deriving from your BaseViewModel class).
You can apply Mode = Two way on the checkbox binding.
<CheckBox Content="{Binding Text}" IsChecked="{Binding Checked, Mode=TwoWay}"/>
then you can iterate through the essay types collection to check if the item entry was checked.
For ex. Sample code can be:
foreach (var essayTypeInstance in EssayTypes)
{
if(essayTypeInstance.Checked)
{
// this value is selected
}
}
Hope this helps.
mm8 answer works. In the meantime i came up with another approach. Not 100% MVVM compatible but it works and is quite simple.
XAML
<ListBox x:Name="ListItems" SelectionMode="Multiple" Height="75" Width="200" ItemsSource="{Binding CollectionOfItems}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding Checked, Mode=TwoWay}" Unchecked="GetCheckedElements" Checked="GetCheckedElements" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Text="{Binding SelectedItemsString, UpdateSourceTrigger=PropertyChanged}" Grid.Column="0" Width="160" Height="25" Margin="0,140,0,0"/>
Code Behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
private void GetCheckedElements(object sender, RoutedEventArgs e)
{
(DataContext as MainViewModel)?.FindCheckedItems();
(DataContext as MainViewModel)?.ConcatSelectedElements();
}
}
Model
public class Items
{
public bool Checked { get; set; }
public string Name { get; set; }
}
ItemsViewModel (BaseViewModel only implements INotifyPropertyChanged)
class ItemsViewModel : BaseViewModel
{
private bool _checked;
private string _name;
public bool Checked
{
get => _checked;
set
{
if (value == _checked) return;
_checked = value;
OnPropertyChanged("Checked");
}
}
public string Name
{
get => _name;
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged("Name");
}
}
}
MainViewModel
public class MainViewModel : BaseViewModel
{
private string _selectedItemsString;
private ObservableCollection<Items> _selectedItems;
public ObservableCollection<Items> CollectionOfItems { get; set; }
public ObservableCollection<Items> SelectedItems
{
get => _selectedItems;
set
{
_selectedItems = value;
OnPropertyChanged("SelectedItems");
}
}
public string SelectedItemsString
{
get => _selectedItemsString;
set
{
if (value == _selectedItemsString) return;
_selectedItemsString = value;
OnPropertyChanged("SelectedItemsString");
}
}
public MainViewModel()
{
CollectionOfItems = new ObservableCollection<Items>();
SelectedItems = new ObservableCollection<Items>();
CollectionOfItems.Add(new Items { Checked = false, Name = "Item 1" });
CollectionOfItems.Add(new Items { Checked = false, Name = "Item 2" });
CollectionOfItems.Add(new Items { Checked = false, Name = "Item 3" });
}
public void FindCheckedItems()
{
CollectionOfItems.Where(x => x.Checked).ToList().ForEach(y => SelectedItems.Add(y));
}
public void ConcatSelectedElements()
{
SelectedItemsString = string.Join(", ", CollectionOfItems.Where(x => x.Checked).ToList().Select(x => x.Name)).Trim();
}
}
I have a ComboBox bound to a static List.
I want to change the items in the List, but the ComboBox will not update to reflect the changes.
XAML
<ComboBox x:Name="cbo"
ItemsSource="{Binding ComboBox_Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding cbo_SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left"
Margin="0,0,0,0"
VerticalAlignment="Top"
Width="100" />
C Sharp
ViewModel Class
Get/Set ComboBox Items
public static List<string> _cbo_Items = new List<string>()
{
"Item 1",
"Item 2",
"Item 3"
};
public static List<string> ComboBox_Items
{
get { return _cbo_Items; }
set { _cbo_Items = value;}
}
public static string cbo_SelectedItem { get; set; }
Another Class
Update the List with new items
ViewModel._cbo_Items = new List<string>()
{
"Item 4",
"Item 5",
"Item 6"
};
Solution
I tried this, it crashes with null exception on viewModel.OnPropertyChanged("ComboBox_Items")
public static ViewModel viewModel;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public static List<string> ComboBox_Items
{
get { return _cbo_Items; }
set { _cbo_Items = value;
viewModel.OnPropertyChanged("ComboBox_Items");
}
}
The bind is broken when you 'new' the list, you can use observablecollection and clear then add items instead of creating new instance.
also fix the binding for the combobox's selecteditem property
<ComboBox x:Name="cbo"
ItemsSource="{Binding ComboBox_Items}"
SelectedItem="{Binding cbo_SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left"
Margin="0,0,0,0"
VerticalAlignment="Top"
Width="100" />
and it will also be better to remove the setter for the ComboBox_Items to prevent it from being re-created.
Overview:
I've set up a property with INPC that invokes a page navigation in the view code behind from the MainViewModel. This property is bound to the SelectedItem property of a list view in the bound view.
The INPC implementation is inherited from the ViewModelBase class which is implemented as follows, https://gist.github.com/BrianJVarley/4a0890b678e037296aba
Issue:
When I select an item from the list view, the property SelectedCouncilItem setter doesn't trigger. This property is bound to the SelectedItem property of the list view.
Debugging Steps:
Checked binding names for SelectedItem in list view property, which was the same as the property name in the MainViewModel.
Ran the solution and checked for any binding errors in the output window, which there were none.
Placed a break point on the SelectedCouncilItem which doesn't get triggered when I select from the list view.
Checked the data context setup for the view which verified that the view is set to the data context of the MainViewModel.
Question:
Does anyone know what other steps I can take in debugging the issue, or what the issue might be?
Code:
MainPage - (List View)
<Grid x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<phone:LongListSelector x:Name="MainLongListSelector"
Margin="0,0,-12,0"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedCouncilItem}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock Style="{StaticResource PhoneTextExtraLargeStyle}"
Text="{Binding CouncilAcronym}"
TextWrapping="Wrap" />
<TextBlock Margin="12,-6,12,0"
Style="{StaticResource PhoneTextSubtleStyle}"
Text="{Binding CouncilFullName}"
TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
MainViewModel - (summary)
namespace ParkingTagPicker.ViewModels
{
public class MainViewModel : ViewModelBase
{
//Dependency Injection private instances
private INavigationCallback _navCallBack = null;
public MainViewModel()
{
this.Items = new ObservableCollection<ItemViewModel>();
}
/// <summary>
/// Creates and adds a few ItemViewModel objects into the Items collection.
/// </summary>
public void LoadCouncilNamesData()
{
this.Items.Add(new ItemViewModel() { ID = "6", CouncilAcronym = "WTC", CouncilFullName = "Wicklow Town Council"});
this.Items.Add(new ItemViewModel() { ID = "7", CouncilAcronym = "TS", CouncilFullName = "Tallaght Stadium" });
this.Items.Add(new ItemViewModel() { ID = "8", CouncilAcronym = "GS", CouncilFullName = "Greystones" });
this.IsDataLoaded = true;
}
public ObservableCollection<ItemViewModel> Items { get; private set; }
public bool IsDataLoaded { get; private set; }
private ItemViewModel _selectedCouncilItem;
public ItemViewModel SelectedCouncilItem
{
get
{
return this._selectedCouncilItem;
}
set
{
this.SetProperty(ref this._selectedCouncilItem, value, () => this._selectedCouncilItem);
if (_selectedCouncilItem != null)
{
_navCallBack.NavigateTo(_selectedCouncilItem.ID);
}
}
}
public INavigationCallback NavigationCallback
{
get { return _navCallBack; }
set { _navCallBack = value; }
}
}
}
ViewModelBase - (detailing INPC implementation)
namespace ParkingTagPicker.ViewModels
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var propertyChanged = this.PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected bool SetProperty<T>(ref T backingField, T Value, Expression<Func<T>> propertyExpression)
{
var changed = !EqualityComparer<T>.Default.Equals(backingField, Value);
if (changed)
{
backingField = Value;
this.RaisePropertyChanged(ExtractPropertyName(propertyExpression));
}
return changed;
}
private static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
{
var memberExp = propertyExpression.Body as MemberExpression;
if (memberExp == null)
{
throw new ArgumentException("Expression must be a MemberExpression.", "propertyExpression");
}
return memberExp.Member.Name;
}
}
}
There is an issue with the control. Please try using custom LongListSeletor
public class ExtendedLongListSelector : Microsoft.Phone.Controls.LongListSelector
{
public ExtendedLongListSelector()
{
SelectionChanged += LongListSelector_SelectionChanged;
}
void LongListSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItem = base.SelectedItem;
}
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(LongListSelector),
new PropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = (LongListSelector)d;
selector.SelectedItem = e.NewValue;
}
public new object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
}
and implement in replace it in XAML with the existing List.
xmlns:controls="clr-namespace:ProjectName.FolderName"
<controls:ExtendedLongListSelector x:Name="MainLongListSelector"
Margin="0,0,-12,0"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedCouncilItem}">
</controls:ExtendedLongListSelector>
I have to combobox in my window:
<ComboBox x:Name="cbDeps"
ItemsSource="{Binding}" DisplayMemberPath="sName" SelectedValuePath="nDepartmentIdn"
Grid.Row="0" Margin="0,52,215,0" Grid.Column="1" SelectionChanged="cbUsers_SelectionChanged"/>
<ComboBox x:Name="cbUsersDep"
ItemsSource="{Binding}" DisplayMemberPath="sUserName" SelectedValuePath="nUserIdn"
Grid.Row="0" Margin="218,52,0,0" Grid.Column="1"/>
and I want when I select a value in the first combobox, the second be filled only by items of the selected item. I get these items using this method:
public ObservableCollection<DataModel.TB_USER> listUsersParDeps(int numDep)
{
var userDeps = (from DataModel.TB_USER ud in SessionContext.DBContext.TB_USER
where ud.nDepartmentIdn==numDep
select ud);
foreach (var userDep in userDeps)
{
userM.ListUserDep.Add(userDep);
}
return userM.ListUserDep;
}
and I put them in the datacontext:
cbUsersDep.DataContext = userVM.listUsersParDeps(int.Parse(cbDeps.SelectedValue.ToString()));
Instead of setting the data context of each combo box manually, you should set the data context of the outer component (usually the window, or some other “larger” component) to your view model. Then you expose two lists (ideally ObservableCollection) for your two combo boxes and bind the ItemSource properties of your combo boxes to those lists. And then, whenever the SelectedItem of your first combo box changes, you update the second list:
<StackPanel>
<ComboBox ItemsSource="{Binding Departments}" SelectedItem="{Binding SelectedDepartment}" />
<ComboBox ItemsSource="{Binding Users}" />
</StackPanel>
public class MainViewModel : INotifyPropertyChanged
{
// this holds the data
private Dictionary<string, List<string>> departmentUsers = new Dictionary<string,List<string>>();
private List<string> departments;
public List<string> Departments
{
get { return departments; }
set
{
if (value != departments)
{
departments = value;
OnNotifyPropertyChanged("Departments");
}
}
}
private string selectedDepartment;
public string SelectedDepartment
{
get { return selectedDepartment; }
set
{
if (value != selectedDepartment)
{
selectedDepartment = value;
OnNotifyPropertyChanged("SelectedDepartment");
// update users list
Users = departmentUsers[selectedDepartment];
}
}
}
private List<string> users;
public List<string> Users
{
get { return users; }
set
{
if (value != users)
{
users = value;
OnNotifyPropertyChanged("Users");
}
}
}
public MainViewModel()
{
// sample data
departmentUsers = new Dictionary<string, List<string>>();
departmentUsers.Add("Department 1", new List<string>() { "1.1", "1.2", "1.3" });
departmentUsers.Add("Department 2", new List<string>() { "2.1", "2.2", "2.3", "2.4", "2.5" });
departmentUsers.Add("Department 3", new List<string>() { "3.1", "3.2" });
departmentUsers.Add("Department 4", new List<string>() { "4.1", "4.2", "4.3" });
// initial state
Departments = new List<string>(departmentUsers.Keys);
SelectedDepartment = Departments[0];
}
// simple INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnNotifyPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}