MultiSelectListView with Edge Action

MultiSelectListViewLead

Good news! The ListView control allows multiple selection with proper styling out of the box. The bad news? You can’t tap on the left edge of the item to activate it like you can in the email or messaging app. Also, if you haven’t noticed, the animation that shows the checkboxes are horrid. The great news? I’ve created a control to solve those issues (until Microsoft decides to release a toolkit that solves the same issue).

The Material

VS_IconCODEPLEX – view source + contribute
VS_IconNUGET – download and use

The Problem

Veteran Windows Phone users will know that whenever they see the option to do multiple selection in the AppBar, most likely there they can tap on the left edge of any item in a list to select it and bring up all of the checkboxes to select more afterwards. One of the first ListView limitation I noticed when moving to WPRT is that while multiple selection is there, you can’t activate it by tapping on the left edge like you can in the LongListMultiSelector.

You can, however, programmatically put it into multiple selection mode where you’ll see a bland, and frankly, terrible slide in animation that will show all of the multiple selection checkboxes. Almost every single animation in Windows Phone operates on some kind of easing so it is natural and fluid. This is true not only for the native experience found throughout the OS but also for almost all of the shipped WPRT controls… All except, of course, for the animation that transition a ListView into multiple selection mode.

The Solution

The MultiSelectListView is basically a ListView with a retemplated ListViewItemContainerStyle and some added event handling. The new ListViewItemContainerStyle has an additional component called the EdgeSelectButton that switches the MultiSelectListView’s SelectionMode. This button is what allows you to tap the left edge to enable multiple selection.

MultiSelectListViewHitTarget

I actually spent a good amount of time on the EdgeSelectButton despite being such a simple component. Just believe me when I say that the animation for the pressed state is nearly identical to the native counterpart. Not only that, I matched the native visual and touch target sizes for the best experience. The visual feedback of the EdgeSelectButton only takes up 15px horizontally so it doesn’t touch the ListViewItem just like the native version. But 15px is way too slim to guarantee a pleasant experience using touch. That’s why my EdgeSelectButton and the native version has an invisible touch target overhang that makes it effectively 45px wide. This is the red square in the picture above. After it’s pressed, the ListViewItems and the hidden checkboxes slide in while the EdgeSelectButton fades out with a dramatic animation curve that is nearly identical to the native experience. It’s hard to get it just right but I think I’ve come close enough to please most picky devs and users.

MultiSelectListViewSelectingMultiSelectListViewSelected

Additional to the please UX, the MultiSelectListView also handles the back button press to leave multiple selection mode automatically and I’ve also added a SelectionModeChanged event. The new event is something that I was surprised to find out that was missing from the ListView control. It’s triggered every time the SelectionMode property changes and it’s a great way to watch when the user activates multiple selection mode by tapping on the left edge of an item.

What Did I Break?

The unfortunate news for anyone using my AlphaJumpList or GenericJumpList controls is that I had to modify some margins for the header templates. I had to add 19px left margin to the header templates and remove the 19px left padding on the JumpList controls themselves. Effectively, the headers will not change and will look like the same way before this update. What you will notice is every ListView item in a JumpList will shift to the left. The fix for this is just to add a left margin of 19px to the affect item templates. JumpLists using the  new MultiSelectListView will not have this issue since the EdgeSelectButton will account from this 19px change. Sorry :).

Limitations

There’s one thing that I can’t seem to figure out that I’m hoping someone can shed a light on. Right now, the handling of the back button press is not ideal. Most WP8.1 apps will be using the NavigationHelper class in the shipped project templates. Unfortunately, the NavigationHelper hooks into the back button pressed event upon app start by default. This means that I cannot handle the back button press since the NavigationHelper will handle it first. So by the time my logic to handle the back button press, the NavigationHelper has already handled it first and invoked the go back command and thus preventing me from stopping page navigation so I can deal with my MultiSelectListView.

Currently, I’m just traversing the visual tree to get to the Frame and canceling navigation instead. This works for page navigation but it doesn’t work for any other case. For example, if the MultiSelectListView is in the multiple selection mode and is hosted in a popup or flyout, the first back button press should, ideally, revert the MultiSelectListView to a no selection mode. Instead, the back button just closes the popup or flyout and the MultiSelectListView remains in multiple selection mode. Obviously, there must be some not well known way of handling the back button press first before anything else gets to it to handle transient UI or control states where returning to default state is expected. The ComboBox is such a control that does this along as well as any popup control. Until a solution is found, this is the best I can do for now.

Advertisements

7 thoughts on “MultiSelectListView with Edge Action

  1. Not sure why EdgeSelectButton derives from ButtonBase, not Button (which takes care of the visual states). And this causes a small issue:

    If the list is short and not scrollable, EdgeSelectButton remains visible after the pointer exits.

    Or will deriving from Button cause worse issues?

    1. Oops, the pointer exit/enter is supposed to be there. I must’ve accidentally removed those two override methods.

      But deriving from button might be a better idea. I just had code lying around from a different project so I copied it. The default button styling was mucking up my efforts on the other project but it seems to work fine here since it’s such a simple control template. I’ll switch it to derive from Button.

  2. Hi
    Thanks for the interesting post. Regarding the handling of back button, i didn’t have any issue in using the NavigationHelper + custom handling to go out of selection mode.
    It seems that custom handling that you add goes before the NavigationHelper.

    when i click the appbar button (for instance, but could be the edge action as well) to go to selection mode :
    list.SelectionMode = ListViewSelectionMode.Multiple;
    Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;

    The handler code is :
    private void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e)
    {
    list.SelectionMode = ListViewSelectionMode.None;
    e.Handled = true;
    Windows.Phone.UI.Input.HardwareButtons.BackPressed -= HardwareButtons_BackPressed;

    }

    because of e.Handled = true (i guess), the go back command of NavigationHelper is not called.

    1. Can you create a small repro project so I can take a look at your solution. The issue I was having is that the NavigationHelper class always seemed to handle the back button first so since it marked it as handled, my code never executes. So I experienced the reverse of what you are experiencing.

      1. Sorry you were right, after some more testing i saw my implementation doesn’t work.
        To make it work using your component, i used the event you created to hook/unhook NavigationHelper :

        private void SelectionMode_Changed(object sender, RoutedEventArgs e)
        {
        var list = sender as MultiSelectListView;
        if (list.SelectionMode != ListViewSelectionMode.Multiple)
        {
        // selection mode is off
        Windows.Phone.UI.Input.HardwareButtons.BackPressed -= HardwareButtons_BackPressed;
        Windows.Phone.UI.Input.HardwareButtons.BackPressed += _navigationHelper.HardwareButtons_BackPressed;
        }
        else
        {
        // selection mode is on (multiple)
        Windows.Phone.UI.Input.HardwareButtons.BackPressed -= _navigationHelper.HardwareButtons_BackPressed;
        Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;
        }
        }

        and my implementation of the event handler in the current class :

        private void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e)
        {
        var list = (pivot.SelectedItem as PivotItem).GetFirstDescendantOfType();
        if (list.SelectionMode == ListViewSelectionMode.Multiple)
        {
        list.SelectionMode = ListViewSelectionMode.None;
        e.Handled = true;
        }
        }

        When i enter selection mode: NavigationHelper events are unhooked, local events are hooked (so that back will just change list SelectionMode).
        When i leave selection mode, local events are unhooked and NavigationHelper events are hooked to keep the WP back behavior.

        Thanks for the component, very nice and works like a charm.

  3. I actually managed to use a ComboBox to detect back button presses, but it is too hacky and might stop working in a future Windows Runtime update. What about using something similar to the following?

    public static class BackPressListener
    {
    private static bool _initialized;

    public static event EventHandler BackPressed;

    public static void Initialize()
    {
    if (_initialized)
    {
    return;
    }

    HardwareButtons.BackPressed += OnBackPressed;
    _initialized = true;
    }

    private static void OnBackPressed(object sender, BackPressedEventArgs e)
    {
    var handler = BackPressed;
    if (handler != null)
    {
    handler(null, e);
    }
    }
    }

    A little configuration (initializing BackPressListener early) will be necessary, which guarantees that the ListView will get the BackPressed event before NavigationHelper.

    1. Forgot to mention that the BackPressListener is mainly meant for scenarios where a MultiSelectListView is on the first page. For flyouts using a ComboBox is the only workaround I’ve found.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s