Matthew
Matthew

Reputation: 4056

save checked items in Multiselectlist when navigating from/to

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?

Upvotes: 0

Views: 483

Answers (1)

Patrick
Patrick

Reputation: 17973

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;
}

Upvotes: 3

Related Questions