Allowing Narrator gestures to zoom at the explorable map

While making the demo video associated with Updating the explorable map to add support for scrolling with a screen reader, which shows how to zoom in and zoom out on the map using Narrator, I did feel that it was pretty tedious to have to swipe so many times to reach the Zoom buttons. Narrator was working great, but it just didn't seem that my app was doing everything it could to make the zooming experience as efficient as I wanted it to be. I wasn't the only person who thought this, as I got feedback querying whether Narrator gestures could be used for zooming the app.

From a UIA perspective, I can expose the zooming capabilities of my custom control by implementing the ITransformProvider2 interface. (Remember that I already exposed my control's scrolling functionality by implementing the IScrollProvider interface.) ITransformProvider2 has methods such as Zoom() and ZoomByUnit(), which seem that they'd serve my purposes just fine. Today few screen readers leverage the ITransformProvider2 interface, so while I'll probably implement that at some point, I thought it might be interesting to see if I could first expose the zooming functionality using other more commonly leveraged interfaces.

An important note: What I'm about to say next technically works, but at the moment is completely undiscoverable to users of my app. If users have no way of knowing this functionality exists in the app, the functionality might as well not be there. But it was still very interesting for me to see if I could get it to work anyway.

One of the very commonly used UIA patterns for interactable controls is the Invoke pattern. For example, all "clickable" buttons will support this. So how about I add support for the Invoke pattern to my control, and when the user invokes it, the map zooms? Sounds easy enough given that the only member on the Invoke pattern is the parameterless Invoke() methods. It's the simplest pattern support I could add.

That would be fine for zooming in, but what about zooming out? Given that for this experiment I'm saying an invoke of the control performs a zoom, I need a way for the user to be able to control whether the zoom action is a zoom in or zoom out. So I'll also add support for the Toggle Pattern. That pattern means that the control has an "On" state and an "Off" state. It's up to the control to decide what those states actually mean, so I'll say that the map’s to zoom in when the control’s "On", and zoom out when the control’s "Off".

The code for all this is below, and I'm pretty shocked that there’s so little code required to add the zooming support. By adding this code snippet to the app, it means that when I'm using Narrator to interact with the map, a triple-tap will zoom the map, and a double-tap will toggle whether a triple-tap zooms in or zooms out. This new functionality coupled with the 2-finger swipe I already supported to pan the map, allows me to really race around the map finding places of interest.

I’ve uploaded a new version of the State Your Name Please app with this new functionality to the Windows Store.

 

    class CustomAutomationPeer : FrameworkElementAutomationPeer,
                                                       IScrollProvider, IInvokeProvider, IToggleProvider
    {
        // When UIA asks this custom AutomationPeer for an object which implements
        // the Scroll Pattern, Invoke Pattern or Toggle Pattern, return the peer itself.

        protected override object GetPatternCore(PatternInterface patternInterface)
        {
            if ((patternInterface == PatternInterface.Scroll) ||
                (patternInterface == PatternInterface.Invoke)  ||
                (patternInterface == PatternInterface.Toggle))
            {
                return this;
            }

            return base.GetPatternCore(patternInterface);
        }

        private bool zoomInOnInvoke = true;

        // Implement the UIA Invoke pattern.

        public void Invoke()
        {
            double increment = (zoomInOnInvoke ? 1 : -1);

            mapContainer.MapControl.ZoomLevel += increment;
        }

        // Implement the UIA Toggle pattern.

        public void Toggle()
        {
            zoomInOnInvoke = !zoomInOnInvoke;
        }

        public ToggleState ToggleState
        {
            get
            {
                return zoomInOnInvoke ? ToggleState.On : ToggleState.Off;
            }
            set
            {
                zoomInOnInvoke  = (value == ToggleState.On);
            }
        }