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));
}
}
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 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..
I've been trying to create a user control, using this article as my start.
My end result would be supplying a collection and using the TextBox to filter the collection, displaying the filtered result in the ListView.
Problem is, it seems that my binding isn't working correctly. The collection isn't being passed through to the UserControl. Bear in mind, this is my first shot at creating a UserControl. Why would this happen?
SearchList.Xaml
<UserControl x:Class="CreatingControls.SearchList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CreatingControls"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Loaded="SearchList_OnLoaded">
<StackPanel>
<TextBox Name="txtFilter"
TextChanged="txtFilter_TextChanged"
Margin="5" FontSize="20" />
<TextBox IsEnabled="False" Text="Search:"
FontSize="16" BorderThickness="0" />
<ListView Name="listView"
SelectedValue="{Binding Path=SelectedItem}"
DisplayMemberPath="Value"
BorderBrush="LightGray" Margin="5" />
</StackPanel>
SearchList.xaml.cs
public partial class SearchList : UserControl
{
#region AllItems
public List<Collection> AllItems
{
get { return (List<Collection>)GetValue(AllItemsProperty); }
set { SetValue(AllItemsProperty, value); }
}
public static readonly DependencyProperty AllItemsProperty =
DependencyProperty.Register("AllItems", typeof(List<Collection>),
typeof(SearchList), new PropertyMetadata(default(List<Collection>)));
#endregion
#region SelectedItem
public Collection SelectedItem
{
get { return (Collection)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(Collection),
typeof(SearchList), new PropertyMetadata(default(Collection)));
#endregion
public SearchList()
{
InitializeComponent();
listView.ItemsSource = AllItems;
if (listView.ItemsSource != null)
{
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(listView.ItemsSource);
view.Filter = ItemsFilter;
}
}
private void SearchList_OnLoaded(object sender, RoutedEventArgs e)
{
if (listView.ItemsSource == null)
return;
CollectionViewSource.GetDefaultView(listView.ItemsSource).Filter = ItemsFilter;
}
private void txtFilter_TextChanged(object sender, TextChangedEventArgs e)
{
if (listView.ItemsSource == null)
return;
CollectionViewSource.GetDefaultView(listView.ItemsSource).Refresh();
}
private bool ItemsFilter(object item)
{
if (listView.ItemsSource == null)
return false;
if (String.IsNullOrEmpty(txtFilter.Text))
return true;
var collectionItem = (Collection)item;
return (collectionItem.Value.StartsWith(txtFilter.Text, StringComparison.OrdinalIgnoreCase) || collectionItem.Value.StartsWith(txtFilter.Text, StringComparison.OrdinalIgnoreCase));
}
}
Collection.cs
public class Collection : INotifyPropertyChanged
{
private string _id;
public string Id
{
get { return _id; }
set { SetField(ref _id, value, "Id"); }
}
private string _value;
public string Value
{
get { return _value; }
set { SetField(ref _value, value, "Value"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
MainWindow.xaml (Window that calls the UserControl created)
<Window x:Class="CreatingControls.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uControls="clr-namespace:CreatingControls"
mc:Ignorable="d"
d:DataContext="Models."
Title="MainWindow" >
<Grid>
<uControls:SearchList x:Name="slist" />
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public static List<Collection> items { get; set; }
public User SelectedUser { get; set; }
public MainWindow()
{
InitializeComponent();
items = new List<Collection>();
items.Add(new Collection { Id = "1", Value = "A" });
items.Add(new Collection { Id = "2", Value = "B" });
items.Add(new Collection { Id = "3", Value = "C" });
items.Add(new Collection { Id = "4", Value = "D" });
items.Add(new Collection { Id = "5", Value = "E" });
items.Add(new Collection { Id = "6", Value = "F" });
items.Add(new Collection { Id = "7", Value = "G" });
items.Add(new Collection { Id = "8", Value = "H" });
slist.AllItems = items;
}
}
You are assigning
listView.ItemsSource = AllItems;
in the SearchList constructor. Later, in the MainWindow constructor, you do
slist.AllItems = items;
Now you seem to be under the impression that listView.ItemsSource magically holds a reference to the items collection of your MainWindow. That is not the case.
Instead of the direct assignment, use data binding. In the SearchList XAML, write this:
<ListView ItemsSource="{Binding AllItems,
RelativeSource={RelativeSource AncestorType=UserControl}}" .../>
and remove listView.ItemsSource = AllItems from the SearchList constructor.
I'm no sure what the right way to bind the combobox control is to my view model.
I'm using a MVVM approach so in my viewmodel i'm loading all the CDType data and am binding their source in combination with the actual record properties model.CDType.id and model.CDType.name.
What happens is that when i change the text? - i keep my old id (from the load routine) and get a new text value from the combobox binding therefor always writing over the existing record instead of creating a new one.
How can i make my combobox set the id to 0 / undefined if the text isn't in the list? (manually? - kind of lame)
Anything will help - thanks!
TL;DR: Editable Combo-box not updating ID on text change.
Sample Xaml binding:
<ComboBox x:Name="ddlCDType"
IsTextSearchEnabled="True"
IsTextSearchCaseSensitive="False"
StaysOpenOnEdit="True"
TextSearch.TextPath="Name"
ItemsSource="{Binding CDTypes}"
SelectedValue="{Binding Assignment.CDType.ID}"
Text="{Binding Assignment.CDType.Name,
UpdateSourceTrigger=PropertyChanged,
NotifyOnValidationError=True,
ValidatesOnDataErrors=True}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
IsEditable="True"
HorizontalAlignment="Left"
Margin="98,10,0,0"
VerticalAlignment="Top"
Width="136" />
as I can understand you need update the previous last selected value when the name of selected ComboBox item is changed manually and this name is not presented in the ComboBox ItemSource. Here is my suggestion, it is combination of your ComboBox control data binding and logic defined in the ViewModel.
Explanation of binding
Since the ComboBox ItemsSource is the collection of Cd (the collection of ComboModels), thus the TextSearch.TextPath binding should be defined as CDType.Name where the CdType is property defined in the ComboModel that describes the sub-model, and the Name is an actual search path which is describing the sub-model.
The ComboBox Text property is binded to AssignmentText property to trigger the updating logic when the combo is lost the focus (as defined in binding).
The ItemsSource is trivial as defined in your sample code.
Selected value will bring a whole model to update (in case we change the selected value name).
Since the ComboBox ItemsSource is the collection of Cd (let's call this ComboModel) the DisplayMemberPath binding should be defined as CDType.Name where the CdType is property defined in the ComboModel that describes the sub-model, and the Name is an actual search path which is describing the sub-model.
Xaml Code:
<Grid VerticalAlignment="Bottom">
<Grid.DataContext>
<comboBoxWhenNoAnySelectedHelpAttempt:ComboboxDataContext/>
</Grid.DataContext>
<StackPanel Orientation="Vertical">
<ComboBox x:Name="ddlCDType"
IsTextSearchEnabled="True"
IsTextSearchCaseSensitive="False"
StaysOpenOnEdit="True"
TextSearch.TextPath="CDType.Name"
Text="{Binding AssignmentText,
UpdateSourceTrigger=LostFocus, Mode=TwoWay,
ValidatesOnDataErrors=True}"
ItemsSource="{Binding CDTypes}"
SelectedValue="{Binding Assignment}"
DisplayMemberPath="CDType.Name"
IsEditable="True"
HorizontalAlignment="Left"
Margin="98,10,0,0"
VerticalAlignment="Top"
Width="136" />
<!--added to see changes in updated combo box item-->
<TextBlock >
<Run Text="Name:"/>
<Run Text="{Binding Assignment.CDType.Name, UpdateSourceTrigger=PropertyChanged}"></Run>
<Run Text="Id:"/>
<Run Text="{Binding Assignment.CDType.ID, UpdateSourceTrigger=PropertyChanged}"></Run>
</TextBlock>
</StackPanel>
</Grid>
VM code
public class ComboboxDataContext:BaseObservableObject
{
private ObservableCollection<ComboModel> _cdTypes;
private ComboModel _assignment;
private string _assignmentText;
private string _lastAcceptedName;
public ComboboxDataContext()
{
CDTypes = new ObservableCollection<ComboModel>
{
new ComboModel
{
CDType = new ComboModelSubModel
{
Name = "Cd-1",
ID = "1",
},
},
new ComboModel
{
CDType = new ComboModelSubModel
{
Name = "Cd-2",
ID = "2",
}
},
new ComboModel
{
CDType = new ComboModelSubModel
{
Name = "Cd-3",
ID = "3",
},
},
new ComboModel
{
CDType = new ComboModelSubModel
{
Name = "Cd-4",
ID = "4",
}
}
};
Assignment = CDTypes.FirstOrDefault();
}
public ObservableCollection<ComboModel> CDTypes
{
get { return _cdTypes; }
set
{
_cdTypes = value;
OnPropertyChanged();
}
}
public ComboModel Assignment
{
get { return _assignment; }
set
{
if (value == null)
_lastAcceptedName = _assignment.CDType.Name;
_assignment = value;
OnPropertyChanged();
}
}
//on lost focus when edit is done will check and update the last edited value
public string AssignmentText
{
get { return _assignmentText; }
set
{
_assignmentText = value;
OnPropertyChanged();
UpDateSourceCollection(AssignmentText);
}
}
//will do the the update on combo lost focus to prevent the
//annessasary updates (each property change will make a lot of noice in combo)
private void UpDateSourceCollection(string assignmentText)
{
var existingModel = CDTypes.FirstOrDefault(model => model.CDType.Name == assignmentText);
if (existingModel != null) return;
if (_lastAcceptedName == null)
{
CDTypes.Add(new ComboModel{CDType = new ComboModelSubModel{ID = string.Empty, Name = assignmentText}});
}
else
{
var existingModelToEdit = CDTypes.FirstOrDefault(model => model.CDType.Name == _lastAcceptedName);
if(existingModelToEdit == null) return;
existingModelToEdit.CDType.Name = assignmentText;
existingModelToEdit.CDType.ID = string.Empty;
}
}
}
public class ComboModel:BaseObservableObject
{
private ComboModelSubModel _cdType;
public ComboModelSubModel CDType
{
get { return _cdType; }
set
{
_cdType = value;
OnPropertyChanged();
}
}
}
public class ComboModelSubModel:BaseObservableObject
{
private string _id;
private string _name;
public string ID
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged();
}
}
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
}
BaseObservableObject code
/// <summary>`enter code here`
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
Regards.
I would like to save which items have been checked in a multiselectlist so that when the page is navigated from and then back to the checked items may be shown in the list. Currently when I navigate away after checking items and then go back to the Multiselectlist item page, the checked states are not saved. So far what I have is as follows
Multiselectlist.xaml
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ScrollViewer>
<!--<toolkit:MultiselectList x:Name="ColorList" ItemsSource="{Binding}" Height="88" HorizontalAlignment="Left" VerticalAlignment="Top" >-->
<toolkit:MultiselectList x:Name="ColorList" HorizontalAlignment="Left" VerticalAlignment="Top" Tap="ColorList_Tap">
<toolkit:MultiselectList.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="12,0,0,0" Grid.ColumnSpan="2">
<!--<Rectangle Fill="{Binding Brush}" Width="50" Height="50"/>-->
<CheckBox Background="{Binding Brush}"/>
<TextBlock Text="{Binding Name}" Margin="12,10,0,0"/>
</StackPanel>
</DataTemplate>
</toolkit:MultiselectList.ItemTemplate>
</toolkit:MultiselectList>
</ScrollViewer>
Multiselectlist.xaml.cs
public ColorListPage()
{
InitializeComponent();
solidColorBrushList = new List<ColorItem>()
{
//Color Color = (Color)ColorConverter.ConvertFromString("#FFF0F8FF");
new ColorItem { Brush = ColorHelper.ToSolidColorBrush("#FFF0F8FF"), Name = "alice blue" },
new ColorItem { Brush = ColorHelper.ToSolidColorBrush("#FFFAEBD7"), Name = "antique white" },
new ColorItem { Brush = ColorHelper.ToSolidColorBrush("#FF00FFFF"), Name = "aqua" },
new ColorItem { Brush = ColorHelper.ToSolidColorBrush("#FF7FFFD4"), Name = "aquamarine" },
new ColorItem { Brush = ColorHelper.ToSolidColorBrush("#FFF0FFFF"), Name = "azure" }, //dont translate!?
};
this.ColorList.ItemsSource = solidColorBrushList;
this.Loaded += new RoutedEventHandler(ColorListPage_Loaded);
}
void ColorListPage_Loaded(object sender, RoutedEventArgs e)
{
//show checkboxes when page is loaded
this.ColorList.IsSelectionEnabled = true;
}
private void ColorList_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
DependencyObject tappedElement = e.OriginalSource as UIElement;
MultiselectItem tappedItem = this.FindParentOfType<MultiselectItem>(tappedElement);
ColorItem selectedItem = null;
if (tappedItem != null)
{
// DataContext contains reference to data item
selectedItem = tappedItem.DataContext as ColorItem;
}
if (selectedItem != null)
{
MessageBox.Show(selectedItem.Name + " Tapped");
}
}
private T FindParentOfType<T>(DependencyObject element) where T : DependencyObject
{
T result = null;
DependencyObject currentElement = element;
while (currentElement != null)
{
result = currentElement as T;
if (result != null)
{
break;
}
currentElement = VisualTreeHelper.GetParent(currentElement);
}
return result;
}
So how would I save the checked state of the items in the Multiselectlist so that if a user navigates away and then back to the page, those checked items are shown?
You can use the multiselectlist's SelectedItems property to select items. If you use the SelectionChanged event on the list you can update a list in your viewmodel to save when navigating away from the page. Using the blend sdk and its Interactivity dll, you can bind the event to a command in your viewmodel as well.
<phone:PhoneApplicationPage
<!-- the usual xmlns attributes -->
xmlns:vm="clr-namespace:MyApplication.Presentation.ViewModels"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<phone:PhoneApplicationPage.DataContext>
<vm:MyPageViewModel />
</phone:PhoneApplicationPage.DataContext>
<toolkit:MultiselectList x:Name="selectionlist" ItemsSource="{Binding Items}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction
Command="{Binding UpdateSelectedCommand}"
CommandParameter="{Binding ElementName=selectionlist, Path=SelectedItems}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction
Command="{Binding LoadCommand}"
CommandParameter="{Binding ElementName='selectionlist'}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<toolkit:MultiselectList.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</toolkit:MultiselectList.ItemTemplate>
</toolkit:MultiselectList>
</phone:PhoneApplicationPage>
Then, in your viewmodel you can create the appropriate commands and methods like this.
namespace MyApplication.Presentation.ViewModels {
public sealed class MyPageViewModel : DependencyObject {
private readonly ObservableCollection<ItemViewModel> items;
private readonly RoutedCommand load;
private readonly RoutedCommand saveCommand;
private readonly RoutedCommand updateSelectedCommand;
public MyPageViewModel() {
items = new ObservableCollection<ItemViewModel>();
load = new RoutedCommand<MultiselectList>(
m => {
IEnumerable<Item> store = loadItems();
IEnumerable<Item> selected = loadSelectedItems();
populateSelectionList(m, store, selected);
});
updateSelectedCommand = new RoutedCommand<IList>(setSelected);
// use the savecommand on a button or a BindableApplicationBarButton
// or execute the command when you're navigating away from the page
saveCommand = new RoutedCommand<object>(o => storeItems(items));
}
public ICommand LoadCommand {
get { return load; }
}
public ICommand UpdateSelectedCommand {
get { return updateSelectedCommand; }
}
public ICommand SaveCommand {
get { return saveCommand; }
}
private void populateSelectionList(MultiselectList list, IEnumerable<Item> storage, IEnumerable<Item> selected) {
foreach (Item item in selected) {
ItemViewModel viewModel = new ItemViewModel(item);
list.SelectedItems.Add(viewModel);
items.Add(viewModel);
}
foreach (string item in storage) {
bool found = false;
foreach (Item select in selected) {
if (select == item) {
found = true;
break;
}
}
if (!found) {
ItemViewModel viewModel = new ItemViewModel(item);
items.Add(viewModel);
}
}
}
private void setSelected(IList list) {
// triggered when user selects or deselects an item in GUI
foreach (ItemViewModel viewModel in items) {
viewModel.IsSelected = false;
}
foreach (object item in list) {
ItemViewModel item = (ItemViewModel)item;
foreach (ItemViewModel tvm in items) {
if (tvm == item) {
tvm.IsSelected = true;
break;
}
}
}
}
private static void storeItems(IEnumerable<ItemViewModel> enumerable) {
// get all selected items
List<ItemViewModel> selected = new List<ItemViewModel>();
foreach (ItemViewModel viewModel in enumerable) {
if (viewModel.IsSelected) {
selected.Add(viewModel);
}
}
// save selected items in storage
saveSelectedItems(selected);
// save enumerable (i.e. all items) in storage
saveItems(enumerable);
}
private static void saveItems(IEnumerable<ItemViewModel> items) {
// todo: save enumerable to storage
foreach (ItemViewModel item in items) {
//saveItem(item.Model);
}
}
private static IEnumerable<Item> loadItems() {
// todo: load from storage
}
private static void saveSelectedItems(IEnumerable<Item> items) {
// todo: save selected to storage
}
private static IEnumerable<Item> loadSelectedItems() {
// todo: load from storage
}
}
}
Notice the todo items. You need to store and load the items from some storage, for instance a file. Take a look at IsolatedStorage in case you want to store it in a file.
You'll find the RoutedCommand class if you search on google for instance, it's inserted below for simplicity.
The Item and ItemViewModel classes are very basic, they just expose the fields necessary for displaying your item.
public class ItemViewModel : INotifyPropertyChanged {
private Item model;
public ItemViewModel(Item item) {
model = item;
item.PropertyChanged += (o,e) => onPropertyChanged(e.Property);
}
public bool IsSelected { get; set; }
public string Name {
get { return model.Name; }
set { model.Name = value; }
}
public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(string property) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(property));
}
}
}
public sealed class Item : INotifyPropertyChanged {
private string name;
public string Name {
get { return name; }
set {
if (name != value) {
name = value;
onPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(string property) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(property));
}
}
}
You can put the RoutedCommand class in a separate namespace if you'd like, and include it in your xaml file and in your viewmodels using statements.
public class RoutedCommand<TArg> : ICommand {
private readonly Action<TArg> execute;
private readonly Func<TArg, bool> canexecute;
public RoutedCommand(Action<TArg> execute) : this(execute, o => true) { }
public RoutedCommand(Action<TArg> execute, Func<TArg, bool> canexecute) {
this.execute = execute;
this.canexecute = canexecute;
}
public bool CanExecute(object parameter) {
return canexecute((TArg)parameter);
}
public void Execute(object parameter) {
if (CanExecute(parameter)) {
execute((TArg)parameter);
}
}
public event EventHandler CanExecuteChanged;
}