Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WP7 Toolkit - How can I ignore events like ListPicker's "SelectionChanged" and ToggleSwitch's "Checked" events when changed by code (not user input)?

I'm new to XAML and C#, but have been enjoying working with it in the couple of weeks I've played with it. I've started working on an App and have put together a simple "Settings" page in XAML; now I'm trying to hook up events to the controls to (a) update application state when the user interacts with them, and (b) have the current state upon visiting the page.

I've hit two (related) road-blocks though:

  • the toolkit:ListPicker control doesn't seem to work well when I define the "ListPickerItem"s in XAML, so in the SettingsPage constructor, I set the contents manually:

    lpColour.ItemsSource = new List<string>()
    {
        "Red","Blue","Green","Custom…"
    };
    lpColour.SelectedIndex = 1;  // set the currently selected item to "Blue"
    

    However, because the control (lpColour in this example) has an Event on SelectionChanged, two events get fired (one with "Red" selected as the box is populated, then another when "Blue" is selected). I don't want to process the "SelectionChanged" at this moment; only when the user has interacted with the control themselves (eg. if they select "Custom…", I can reveal a separate text-box and give that focus; but I don't want to do that when I'm setting up the page and they had "Custom…" previously selected, as otherwise the user gets the keyboard appearing as soon as they open the Settings page...)

  • Similarly, I've found that ToggleSwitch controls will fire "Checked" and "Unchecked" events when the "IsChecked" Property is changed to something new. Again, is there a way to ignore or suppress this event when changed by code? (I kind-of got around this for now by just using "Clicked", but from a learning standpoint, it'd be nice to know how to deal with it).

I was thinking maybe there was some way to get the "origin" (eg. "code" or "user input") of the Event from the "SelectionChangedEventArgs" or "RoutedEventArgs"... but maybe not?

I also tried setting an "initialized" bool value ("false" by default, set to "true" after Constructor is run, and wrap the Event-handling code in something like "if (initialized) { ... }"; but the event still seemed to be fired after the Constructor was done for the "lpColour.ItemSource=..." and "lpColour.SelectedIndex = 1" code that was done while "initialized" was "false". Very strange. :P

I hope I'm explaining that clearly - I've never posted here before!

I'd appreciate any help you could offer. Thanks!

UPDATE - thanks to @MyKuLLSKI's answer, that's given me a great place to work from.

As a side note, building on the idea, I tried keeping them as 'List's initially and having the "IgnoreSelectionChanged" as an int that would 'count down' (so before setting the ListPicker's ItemSource, I'd set "IgnoreSelectionChanged+=2" (to account for the two events that would get fired); similarly I'd set "IgnoreSelectionChanged++" just before setting the SelectedIndex manually... that seemed to work too.

However, using the "ObservableCollection" bound to the ListPicker and relying on that to tell of changes seems perhaps a better way than using the ListPicker's own "SelectionChanged" event, so I'll modify my code to use that instead.

Thanks again!

like image 751
CyberDog Avatar asked Jan 12 '12 05:01

CyberDog


1 Answers

I'll try to answer all your questions/problems

  1. The reason why you are having trouble setting the ItemSource in XAML is because im almost certain you have some Binding issues. For Bindings to work you need to have a DataContext and Binding on a UIElement.

  2. Something that is bought to a property must be a DependencyProperty or INotifyPropertyChanged

  3. Also a List is not a good Collection type to bind a ListPicker to. Instead you would probably want to use as ObservableCollextion() instead. This if this collection is bound to the ListPicker and the items change the ListPicker will be automatically updated.

  4. The reason why the SelectionChanged Event gets fired 2 times is because you are changed it twice. When the ListPicker is first created the Selected item is null or -1 because no items are in it. Then when you set the ItemSource it automatically changed the SelectedIndex to 0 then you change it to 1.

  5. One way is to add a flag every time the user you know your changing the variable in code

  6. Silverlight lacks an IsLoaded Property so you ma want to add a bool when the Page gets loaded to true.

  7. When Binding doesn't change the property in the UIElement. Instead change the property its bound to.

Below is my solution that should solve all your issues (WP7.1):

XAML

  <phone:PhoneApplicationPage 
       x:Class="WP7Sandbox.MainPage"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
       xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
       xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
       xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
       xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
       DataContext="{Binding RelativeSource={RelativeSource Self}}"
       mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
       FontFamily="{StaticResource PhoneFontFamilyNormal}"
       FontSize="{StaticResource PhoneFontSizeNormal}"
       Foreground="{StaticResource PhoneForegroundBrush}"
       SupportedOrientations="Portrait" Orientation="Portrait"
       shell:SystemTray.IsVisible="True"
       Loaded="PhoneApplicationPageLoaded">

    <Grid>
        <StackPanel>
            <toolkit:ListPicker ItemsSource="{Binding ListPickerCollection, Mode=TwoWay}" SelectionChanged="ListPickerSelectionChanged" SelectedIndex="{Binding ListPickerSelectedIndex, Mode=TwoWay}"/>
            <Button Click="ButtonClick" Content="Selection Change and Ignore Event"/>
            <Button Click="Button2Click" Content="Selection Change and Trigger Event"/>

            <toolkit:ToggleSwitch IsChecked="{Binding ToggleSwitchValue, Mode=TwoWay}"/>
        </StackPanel>
    </Grid>
</phone:PhoneApplicationPage>

Code Behind

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Controls;

namespace WP7Sandbox
{
    public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

        }

        private bool IsLoaded;

        private bool IgnoreSelectionChanged;

        public ObservableCollection<string> ListPickerCollection { get; private set; }

        private bool _ToggleSwitchValue;
        public bool ToggleSwitchValue
        {
            get
            {
                 return _ToggleSwitchValue;
            }

            set
            {

                _ToggleSwitchValue = value;
                OnPropertyChanged("ToggleSwitchValue");
            }
        }

        private int _ListPickerSelectedIndex;
        public int ListPickerSelectedIndex
        {
            get
            {
                return _ListPickerSelectedIndex;
            } 

            set
            {
                _ListPickerSelectedIndex = value;
                OnPropertyChanged("ListPickerSelectedIndex");
            }
        }

        public MainPage()
        {
            InitializeComponent();

            ListPickerCollection = new ObservableCollection<string>()
            {
                "Red",
                "Blue",
                "Green",
                "Custom…"
            };
        }

        private void PhoneApplicationPageLoaded(object sender, RoutedEventArgs e)
        {
            IsLoaded = true;
        }

        private void ListPickerSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (IsLoaded && !IgnoreSelectionChanged)
            {
            }

            IgnoreSelectionChanged = false;
        }

        private void ButtonClick(object sender, RoutedEventArgs e)
        {
            // I want to ignore this SelectionChanged Event
            IgnoreSelectionChanged = true;
            ChangeListPickerSelectedIndex();
        }

        private void Button2Click(object sender, RoutedEventArgs e)
        {
            // I want to trigger this SelectionChanged Event
            IgnoreSelectionChanged = false; // Not needed just showing you
            ChangeListPickerSelectedIndex();
        }

        private void ChangeListPickerSelectedIndex()
        {
            if (ListPickerSelectedIndex - 1 < 0)
                ListPickerSelectedIndex = ListPickerCollection.Count - 1;

            else
                ListPickerSelectedIndex--;
        }
    }
}

A lot is there but it should help

like image 87
MyKuLLSKI Avatar answered Nov 02 '22 23:11

MyKuLLSKI