Useful State Triggers for Universal Windows Platform apps

Windows 10 desktop & universal apps
Standard

I’ve previously blogged about how we expected Responsive design to be a prominent consideration in Windows 10 development (or Universal Windows Platform development as is now the term), and Microsoft echoed that sentiment at BUILD this year whilst providing us with a new toolset for XAML to build better responsive apps.

Today I’d like to share with you a couple of State Triggers which my colleagues at UI Centric have found useful whilst developing for Windows 10, Microsoft have made it very simple for developers to write custom triggers, once a class is extending StateTriggerBase and implementing ITriggerValue you can handle a wealth of scenarios which I’m quite sure I could never list out if I tried, so let’s start small.

Width and Boolean Trigger

Naturally one of the most frequently useful triggers is the Adaptive trigger, which Microsoft encourages the use of for responsive design. The premise of the trigger is simple, when a Windows is of a certain size (read: width), the trigger is active.

For a lot of scenarios that serves the responsive requirements perfectly. However we have found that some of our states rely on other conditions being true, for instance knowing that a page is currently not in it’s default state and is instead showing a secondary control or popup (such as a filter control).

Therefore it makes sense to have a trigger than binds to both screen size and a Boolean, where said Boolean must be true for the trigger to be active. You could also consider this a kill switch for the trigger:


public class WidthAndBooleanTrigger : StateTriggerBase, ITriggerValue
    {
        private WeakEventListener _sizeChangedWeakEventListener;

        public WidthAndBooleanTrigger()
        {
            _sizeChangedWeakEventListener = new WeakEventListener(Window.Current)
            {
                OnEventAction = (instance, source, eventArgs) => Current_SizeChanged(source, eventArgs),
                OnDetachAction = (weakEventListener) => Window.Current.SizeChanged -= weakEventListener.OnEvent
            };

            Window.Current.SizeChanged += _sizeChangedWeakEventListener.OnEvent;
        }

        private void Current_SizeChanged(object sender, WindowSizeChangedEventArgs e)
        {
            UpdateTrigger();
        }

        private void UpdateTrigger()
        {
            if (MaxWindowWidth > -1 && MinWindowWidth > -1)
                IsActive = Window.Current.Bounds.Width >= MinWindowWidth && Window.Current.Bounds.Width <= MaxWindowWidth && (IsReversed ? !Value : Value);
            else
            {
                if (MinWindowWidth > -1)
                    IsActive = Window.Current.Bounds.Width >= MinWindowWidth && (IsReversed ? !Value : Value);
                else
                    IsActive = Window.Current.Bounds.Width <= MaxWindowWidth && (IsReversed ? !Value : Value);
            }
        }

        public double MinWindowWidth
        {
            get { return (double)GetValue(MinWindowWidthProperty); }
            set { SetValue(MinWindowWidthProperty, value); }
        }

        public double MaxWindowWidth
        {
            get { return (double)GetValue(MaxWindowWidthProperty); }
            set { SetValue(MaxWindowWidthProperty, value); }
        }

        public bool Value
        {
            get { return (bool)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        public bool IsReversed
        {
            get { return (bool)GetValue(IsReversedProperty); }
            set { SetValue(IsReversedProperty, value); }
        }

        public static readonly DependencyProperty MinWindowWidthProperty =
            DependencyProperty.Register("MinWindowWidth", typeof(double), typeof(WidthAndBooleanTrigger), new PropertyMetadata(-1d, OnValuePropertyChanged));

        public static readonly DependencyProperty MaxWindowWidthProperty =
            DependencyProperty.Register("MaxWindowWidth", typeof(double), typeof(WidthAndBooleanTrigger), new PropertyMetadata(-1d, OnValuePropertyChanged));

        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(bool), typeof(WidthAndBooleanTrigger), new PropertyMetadata(null, OnValuePropertyChanged));

        public static readonly DependencyProperty IsReversedProperty =
            DependencyProperty.Register("IsReversed", typeof(bool), typeof(BooleanTrigger), new PropertyMetadata(false));

        private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var obj = (WidthAndBooleanTrigger)d;
            obj.UpdateTrigger();
        }

        #region ITriggerValue

        private bool m_IsActive;

        /// 
        /// Gets a value indicating whether this trigger is active.
        /// 
        /// true if this trigger is active; otherwise, false.
        public bool IsActive
        {
            get { return m_IsActive; }
            private set
            {
                if (m_IsActive != value)
                {
                    m_IsActive = value;
                    base.SetActive(value);
                    if (IsActiveChanged != null)
                        IsActiveChanged(this, EventArgs.Empty);
                }
            }
        }

        /// 
        /// Occurs when the  property has changed.
        /// 
        public event EventHandler IsActiveChanged;

        #endregion ITriggerValue
    }

You’ll notice that a weak event listener is used to manage the Window width changing. That’s a great tool from the Windows Phone Toolkit which you can find here and comes highly recommended when making custom controls that depend on events, as it allows for proper garbage collection.

It’s also worth pointing out that we added the option to use either MaxWidth or MinWidth (or both!) to this trigger, which can help in creating more complex layouts rather than relying only on MinWidth as per the built in Adaptive trigger.

Device Family Adaptive Trigger

If you’ve looked at State Triggers before this post, you’ll no doubt be aware of the superb work being done by Morten Nielsen (dotMorten) but just in case you’ve not come across his Repository of Windows State Triggers, you’ll find a plethora of useful tools above and beyond the limited scenarios I’m discussing in this post over on GitHub.

We’ve found the Device Family Trigger in that repository to be a very useful tool, although one that should be considered carefully before using, remember that UWP development tries to not consider the device capabilities unless absolutely necessary.

One useful addendum to that trigger though is to trigger when NOT on a certain device family. A simple use case would be to use one state for Mobile, a default state for Unknown, and then a specific state only valid when not on Mobile.

Ordinarily the recommendation would be to use a specific state for mobile and then have the default layout cater for all other instances, but a NOT condition may still be useful and can be achieved with a simple dependency property:


public bool Not
        {
            get { return (bool)GetValue(NotProperty); }
            set { SetValue(NotProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IsReversed.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty NotProperty =
            DependencyProperty.Register("Not", typeof(bool), typeof(DeviceFamilyAdaptiveTrigger), new PropertyMetadata(false));

        #region ITriggerValue

        private bool m_IsActive;

		/// 
		/// Gets a value indicating whether this trigger is active.
		/// 
		/// true if this trigger is active; otherwise, false.
		public bool IsActive
		{
			get { return m_IsActive; }
			private set
			{
                if (Not)
                    value = !value;

				if (m_IsActive != value)
				{
					m_IsActive = value;
					base.SetActive(value);
					if (IsActiveChanged != null)
						IsActiveChanged(this, EventArgs.Empty);
				}
			}
		}

The above is an addendum to the original source code on GitHub, which you can find directly here for your convenience.

These are just two triggers that we’ve been using as we prepare apps for Windows 10, we’re planning to commit these to an open source repository shortly and will continue to develop new triggers as challenges present themselves. In the meantime it’s exciting to see what the rest of the Windows development community is coming up with as we get even closer to Windows 10’s public release.

One thought on “Useful State Triggers for Universal Windows Platform apps

  1. Stef

    How does Microsoft Mail app work?
    When the app is fullscreen: clicking an e-mail, it will show that one on the right (updating page layout);
    when the app is reduced to page with mail list: clicking an e-mail, it will navigate to another page to show the e-mail; is it right? Does it mean that mail clicking event requires two methods?
    Thank you!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>