Click here to Skip to main content
15,878,814 members
Articles / Desktop Programming / Windows Forms

Win10 TabletMode & WinForms.Net Form Event Order

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
28 Jul 2018CPOL2 min read 11.3K   4   2
Win10 TabletMode alters the order of Form events in WinForms.Net - this article describes how to correct the event order

Introduction

WinForms.Net has a specific order of events that occurs when a form is created and shown (see this link).

Windows 10 introduced a new UI mode called Tablet Mode which can be toggled on / off by the end user. This new mode forcibly alters the order of these events.

This article explains how to correct the order of events back to the original order.

Background

The altered order of events can cause code that is meant to happen in a specific order to execute out of order resulting in crashes or unexpected behavior.

The original order of events is:

  1. Control.HandleCreated
  2. Control.BindingContextChanged
  3. Form.Load
  4. Control.VisibleChanged
  5. Form.Activated
  6. Form.Shown

The TabletMode alters the order as such:

  1. Control.HandleCreated
  2. Form.Activated
  3. Control.VisibleChanged
  4. Control.BindingContextChanged
  5. Form.Load
  6. Form.Shown

Using the Code

There are three key steps to achieve to correcting the event order:

  1. Set Windows 10 compatibility in application manifest (necessary to be able to check OS version and get the correct version).
    • This step is skipped in this article. Web search for how to add an application manifest to complete this step.
  2. Detect when the PC is in TabletMode.
  3. Prevent the Activated & VisibleChanged event firing out of order.

Step 2: Detect TabletMode

When researching this problem, there were three common suggestions on how to detect tablet mode:

1. UIViewSettings
C#
UIViewSettings.GetForCurrentView().UserInteractionMode =
   Windows.UI.ViewManagement.UserInteractionMode.Touch

Pros: Simple & easy line of code

Cons: UWP code. This article is for WinForms.Net and referencing UWP DLLs is not easy.

2. GetSystemMetrics

GetSystemMetrics is a Windows API that can be called. The specific property suggested is SM_CONVERTIBLESLATEMODE. IE:

C#
private const int SM_CONVERTIBLESLATEMODE = 0x2003;
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "GetSystemMetrics")]
private static extern int GetSystemMetrics (int nIndex);

Pros: Simple. Declare the function, and call GetSystemMetrics(SM_CONVERTIBLESLTATEMODE) and compare the return to 0. If 0 (zero), then Windows is in Tablet Mode.

Cons: Doesn't work on Desktop PCs according to MSDN. TabletMode can be enabled on any Windows 10 PC that has only 1 active monitor. I tested on a Laptop and it still didn't accurately report the PC being in Tablet Mode.

So this mode is unreliable. It is unlikely a non-tablet PC will be used in Tablet Mode, but since Microsoft allows it, you should guard your program against it.

3. Registry

When TabletMode is turned on/off, Windows writes a value to HKCU\Software\Microsoft\Windows\CurrentVersion\ImmersiveShell\TabletMode.

C#
private bool GetTabletMode()
{
    return (bool)Microsoft.Win32.Registry.GetValue
    ("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\ImmersiveShell", "TabletMode", 0);
}
VB.NET
Private Function GetTabletMode() As Boolean
    Return CBool(Microsoft.Win32.Registry.GetValue
    ("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\ImmersiveShell", "TabletMode", 0))
End Function

Pros: It's in the HKCU area of the registry so permissions shouldn't be an issue. Retrieving a registry value is easy. Detection of TabletMode was reliable.

Cons: None(?)

3. Prevent the Activated & VisibleChanged

To prevent these two events from firing out of order, we need to override the OnActivated and the SetVisibleCore functions and prevent the base functions from being called in the wrong order.

I found the most effective logic was to detect if we were in TabletMode inside the SetVisibleCore - then call the appropriate event handlers in the correct order. A very basic Form1 example is as follows:

C#
using System;
using System.Windows.Forms;

namespace TabletModeFormEventOrderExample
{
    public partial class Form1 : Form
    {
        private bool _loadedEventFired = false;
        private bool _onActivatedAllowed = false;

        public Form1()
        {
            InitializeComponent();

            this.Load += Form1_Load;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            if (_loadedEventFired) return; // Prevent event from firing twice.
            //Do whatever needs to be done in Load event.
            _loadedEventFired = true;
        }

        protected override void OnActivated(EventArgs e)
        {
            if (GetTabletMode() && !_onActivatedAllowed) return;
            base.OnActivated(e);
        }

        protected override void SetVisibleCore(bool value)
        {
            // We only need special logic if:
            //  a. Visible is being set to true and the Loaded event hasn't fired yet.
            //  b. The OS is Windows 10 or later.
            //  c. TabletMode is turned on.
            if (value && !_loadedEventFired)
            {
                // Version.Major = 6 unless you set Windows 10 support in application manifest.
                if ((Environment.OSVersion.Platform == PlatformID.Win32NT) && 
                    (Environment.OSVersion.Version.Major >= 10))
                {
                    // Get TabletMode from registry.
                    if (GetTabletMode())
                    {
                        // Handle should already be created, but ensure it is.
                        if (this.Handle == IntPtr.Zero) this.CreateHandle();

                        // Ensure BindingContext is properly set and 
                        // fire the BindingContextChanged event.
                        // This example doesn't include setting a BindingContext.

                        // Manually fire the load event.
                        base.OnLoad(EventArgs.Empty);

                        // Now set the visible core.
                        base.SetVisibleCore(value);

                        // Now allow OnActivated to fire.
                        _onActivatedAllowed = true;
                        // Calling this.Activate doesn't seem to work 
                        // once we've hijacked the chain of events back from Tablet mode. 
                        // Instead call OnActivated to force it to fire.
                        if (this.ShowWithoutActivation) OnActivated(EventArgs.Empty);

                        return;
                    }
                }
            }
            base.SetVisibleCore(value);
        }

        private bool GetTabletMode()
        {
            return (bool)Microsoft.Win32.Registry.GetValue
            ("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\ImmersiveShell", 
             "TabletMode", 0);
        }
    }
}
VB.NET
Imports System
Imports System.Windows.Forms

Namespace TabletModeFormEventOrderExample
    Public Partial Class Form1
        Inherits Form

        Private _loadedEventFired As Boolean = False
        Private _onActivatedAllowed As Boolean = False

        Public Sub New()
            InitializeComponent()
            Me.Load += AddressOf Form1_Load
        End Sub

        Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs)
            'Prevent event from firing twice.
            If _loadedEventFired Then Exit Sub
            'Do whatever needs to be done in Load event.
            _loadedEventFired = True
        End Sub

        Protected Overrides Sub OnActivated(ByVal e As EventArgs)
            If GetTabletMode() AndAlso Not _onActivatedAllowed Then Exit Sub
            MyBase.OnActivated(e)
        End Sub

        Protected Overrides Sub SetVisibleCore(ByVal value As Boolean)
            'We only need special logic if:
            ' a. Visible is being set to true and the Loaded event hasn't fired yet.
            ' b. The OS is Windows 10 or later.
            ' c. TabletMode is turned on.
            If value AndAlso Not _loadedEventFired Then
                'Version.Major = 6 unless you set Windows 10 support in application manifest.
                If (Environment.OSVersion.Platform = PlatformID.Win32NT) _
                    AndAlso (Environment.OSVersion.Version.Major >= 10) Then
                    'Get TabletMode from registry.
                    If GetTabletMode() Then
                        'Handle should already be created, but ensure it is.
                        If Me.Handle = IntPtr.Zero Then Me.CreateHandle()

                        'Ensure BindingContext is properly set and 
                        'fire the BindingContextChanged event.
                        'This example doesn't include setting a BindingContext.

                        'Manually fire the load event.
                        MyBase.OnLoad(EventArgs.Empty)

                        'Now set the visible core.
                        MyBase.SetVisibleCore(value)

                        'Now allow OnActivated to fire.
                        _onActivatedAllowed = True
                        'Calling Me.Activate doesn't seem to work 
                        'once we have hijacked the chain of events back from Tablet mode. 
                        'Instead call OnActivated to force it to fire.
                        If Me.ShowWithoutActivation Then OnActivated(EventArgs.Empty)

                        Exit Sub
                    End If
                End If
            End If

            MyBase.SetVisibleCore(value)
        End Sub

        Private Function GetTabletMode() As Boolean
            Return CBool(Microsoft.Win32.Registry.GetValue_
            ("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\ImmersiveShell", _
             "TabletMode", 0))
        End Function
    End Class
End Namespace

History

  • 7/27/2018: Initial draft

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
PraiseThanks for the improved formatting Pin
B.O.B.28-Jul-18 2:40
B.O.B.28-Jul-18 2:40 
GeneralOther use of WinForm.Net Pin
Member 1392908928-Jul-18 2:29
Member 1392908928-Jul-18 2:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.