Search code examples
c#.netwindowsxamlwinui-3

Setting NavigationCacheMode on page causes System.StackOverflowException with CallWindowProc


When I set NavigationCacheMode to "Enabled" or "Required" on a page, I get a System.StackOverflowException from CallWindowProc when navigating to a page, then back.

In my app, I have a ToggleButton that is toggled by the F6 key which works perfectly fine. I need to add a new windows procedure to do that which uses CallWindowProc and it gives me System.StackOverflowException when the NavigationCacheMode is enabled. I need NavigationCacheMode to save the state my buttons are in.

Reproduction

GitHub repo for reproduction

YouTube video showing crash

I am using Visual Studio 2022 with the Blank App, Packaged (WinUI 3 in Desktop) with the CsWin32 package installed.

MainPage.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<Page
    x:Class="NavigationProc.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="using:NavigationProc"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    NavigationCacheMode="Enabled"
    mc:Ignorable="d">

    <!--  NavigationCacheMode being set to Enabled causes the app to crash  -->

    <StackPanel
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Orientation="Horizontal">
        <ToggleButton x:Name="ToggleButton">F6</ToggleButton>
        <Button Click="Button_Click">To go page</Button>
    </StackPanel>
</Page>

MainPage.xaml.cs:

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.Input.KeyboardAndMouse;
using Windows.Win32.UI.WindowsAndMessaging;

// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.

namespace NavigationProc;

/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
    public MainPage()
    {
        InitializeComponent();
        Loaded += MainPage_Loaded;
    }

    private WNDPROC? origHotKeyProc;
    private WNDPROC? hotKeyProcD;

    private LRESULT HotKeyProc(HWND hWnd, uint Msg, WPARAM wParam, LPARAM lParam)
    {
        uint WM_HOTKEY = 0x0312; // HotKey Window Message

        if (Msg == WM_HOTKEY)
        {
            if (ToggleButton.IsEnabled)
            {
                ToggleButton.IsChecked = !ToggleButton.IsChecked;
            }
        }

        return PInvoke.CallWindowProc(origHotKeyProc, hWnd, Msg, wParam, lParam);
    }

    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        // Get window handle
        MainWindow window = App.MainWindow;
        HWND hWnd = new(WinRT.Interop.WindowNative.GetWindowHandle(window));

        // Register hotkey
        int id = 0x0000;
        _ = PInvoke.RegisterHotKey(hWnd, id, HOT_KEY_MODIFIERS.MOD_NOREPEAT, 0x75); // F6

        // Add hotkey function pointer to window procedure
        hotKeyProcD = HotKeyProc;
        IntPtr hotKeyProcPtr = Marshal.GetFunctionPointerForDelegate(hotKeyProcD);
        IntPtr wndPtr = PInvoke.SetWindowLongPtr(hWnd, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, hotKeyProcPtr);
        origHotKeyProc = Marshal.GetDelegateForFunctionPointer<WNDPROC>(wndPtr);
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        _ = Frame.Navigate(typeof(OtherPage));
    }
}

Related to Getting System.ExecutionEngineException in procedure when resizing window quickly


Solution

  • Have you tried adding a simple logic so the hotkey won't be registered mulitple times?

    private bool IsHotKeyRegistered { get; set; }
    
    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        if (IsHotKeyRegistered is true)
        {
            return;
        }
    
        IsHotKeyRegistered = true;
    
        ...
    }