microsoft-ui-xaml icon indicating copy to clipboard operation
microsoft-ui-xaml copied to clipboard

Window.Activate does not activate and bring window to foreground in WinUI3 if window is in background but not minimized

Open Balkoth opened this issue 3 years ago • 2 comments

Describe the bug

If Activate is called on a Window which is in the background of other windows, that window is not activated and brought to the foreground. If the window is minimized it works correctly.

Steps to reproduce the bug

Call Activate on a Window when it is in the backround of other windows.

Expected behavior

No response

Screenshots

No response

NuGet package version

WinUI 3 - Windows App SDK 1.1.4

Windows app type

  • [ ] UWP
  • [X] Win32

Device form factor

Desktop

Windows version

Windows 10 (21H2): Build 19044

Additional context

No response

Balkoth avatar Aug 12 '22 09:08 Balkoth

I ran into this issue when making my WinUI3 app single-instanced. Any idea when the bug could be resolved?

msalandro avatar Sep 16 '22 11:09 msalandro

Exactly where the bug hit me too. Had to call SetForegroundWindow. Please fix this.

Balkoth avatar Sep 16 '22 13:09 Balkoth

looking at it

pratikone avatar Nov 28 '22 19:11 pratikone

I just tried in the latest version of windows app sdk 1.2 and not able to reproduce it. I created a bunch of windows, stacked them one behind the other, called .Activate() on one of them and it showed up.

Can you verify that after updating to winappsdk 1.2, it goes away ? Feel free to re-open this bug if it still persists.

pratikone avatar Nov 28 '22 23:11 pratikone

This is not fixed in 1.2! Running code like this

// Wire up the activated event handler.
keyInstance.Activated += async (sender, e) =>
{
  // When activated, activate the main window and bring it to the foreground.
  await _mainWindow.DispatcherQueue.EnqueueAsync(() => _mainWindow.Activate());
  //NativeMethods.SetForegroundWindow(_mainWindow.GetWindowHandle());
};

outside the debugger, does not bring the window to the foreground. Only once the native call is uncommented the window is brought to the foreground!

Balkoth avatar Nov 29 '22 06:11 Balkoth

thanks for verifying it. i will try this out quickly.

pratikone avatar Dec 06 '22 22:12 pratikone

@Balkoth It looks like you are using Window community toolkit for dispatcher queue. Could you share a sample project which I can compile and build locally to investigate the issue?

Meanwhile, I also have a workaround : if you move away from windows community toolkit and directly use dispatcher queue which is part of windows app sdk 1.2 https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.dispatching.dispatcherqueue?view=windows-app-sdk-1.2

It solves this issue. However, we definitely need to fix community toolkit bug so we need the repro too :)

pratikone avatar Dec 08 '22 20:12 pratikone

Is there a sample available for the Windows App SDK dispatcher queue? The link you posted puts a big question mark over my head...

Balkoth avatar Dec 08 '22 20:12 Balkoth

WinUI Gallery source code has some parts which uses this inbuilt dispatcher queue : https://github.com/microsoft/WinUI-Gallery/blob/353c1a0dab0aec9485179ad2a8b76cab95b7c8b9/WinUIGallery/TabViewPages/TabViewWindowingSamplePage.xaml.cs#L179

See if it helps.

pratikone avatar Dec 08 '22 20:12 pratikone

I will try it tomorrow and report back.

Balkoth avatar Dec 08 '22 20:12 Balkoth

Using the Windows App SDK dispatcher queue does also not work...

// Wire up the activated event handler.
keyInstance.Activated += async (sender, e) =>
{
  var taskCompletionSource = new TaskCompletionSource();
  _mainWindow.DispatcherQueue.TryEnqueue(
      Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal,
      new Microsoft.UI.Dispatching.DispatcherQueueHandler(() =>
      {
        _mainWindow.Activate();
        taskCompletionSource.SetResult();
      }));

  await taskCompletionSource.Task;
};

Balkoth avatar Dec 13 '22 16:12 Balkoth

Here is the sample project: WindowActivateTest.zip

Balkoth avatar Dec 13 '22 16:12 Balkoth

thanks for confirming. i will try out this test app.

pratikone avatar Dec 15 '22 23:12 pratikone

Thanks for sample app. I debugged it. Turns out AppInstance.Activated event is not firing and that's why your window is not coming forward.

If you hook this code to any other event's firing like button clicked, it starts working. So Window activation is working as expected. image.

So, you can debug AppInstance code to see why it is not working. Closing the issue as it is a no repro.

pratikone avatar Dec 21 '22 19:12 pratikone

WTF are you talking about. Of course the event is called, if you put in the native call to SetForegroundWindow the window is activated an put to the foreground. Clearly you don't seem to be qualified to analyze this issue, please bring a colleage with more experience.

So just for the record:

  • AppInstance.Activated is f.e. fired when you try to start a second instance of the app and you have registered it with AppInstance.FindOrRegisterForKey
  • The sample you posted is severely flawed, because you call myWindow.Activate when the window is already activated. Your second call on the dispatcher queue will only ever activate the window and bring it to the foreground, if the window is minimized. If the window just sits in the background of other windows it will not be brought to the foreground.

The docs Window.Activate clearly state: Attempts to activate the application window by bringing it to the foreground and setting the input focus to it.

P.S.: Posting screenshots of code creates a bad experience for anyone trying to recreate what you did. This platform supports code tags which nicely format the text.

Balkoth avatar Dec 21 '22 19:12 Balkoth

So will this be reopened and investigated properly or do i have to create a new issue?

Balkoth avatar Jan 16 '23 07:01 Balkoth

My apologies. I was not launching the second instance. Now I am able to get the repro done. We are investigating this and I will keep the issue open until you validate the fix.

pratikone avatar Jan 20 '23 22:01 pratikone

Still investigating?

Jay-o-Way avatar Mar 23 '23 11:03 Jay-o-Way

Question: Why/when use this methiod over Show() ?

Jay-o-Way avatar Mar 23 '23 11:03 Jay-o-Way

This worked for me (I needed my app to be single instanced too and if user tried to run app again, already running instance's window get activated)

This worked for me

[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);

public enum ShowWindowCommands : int
{
	Hide = 0,
	ShowNormal = 1,
	ShowMinimized = 2,
	ShowMaximized = 3,
	Maximize = 3,
	ShowNormalNoActivate = 4,
	Show = 5,
	Minimize = 6,
	ShowMinNoActivate = 7,
	ShowNoActivate = 8,
	Restore = 9,
	ShowDefault = 10,
	ForceMinimized = 11
}

in app.Xaml.cs

protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
	// Get the activation args
	var appArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
	
	// Get or register the main instance
	var mainInstance = AppInstance.FindOrRegisterForKey("My.App");
	// If the main instance isn't this current instance
	
	if (!mainInstance.IsCurrent)
		{
			var activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
			await mainInstance.RedirectActivationToAsync(activatedEventArgs);
			Process.GetCurrentProcess().Kill();
			return;
		}
	// Otherwise, register for activation redirection
	AppInstance.GetCurrent().Activated += AppActivated;
}

private void AppActivated(object sender, AppActivationArguments e)
{
	IntPtr hWnd = WindowNative.GetWindowHandle(_mainWindow);
	//this line causing a delay in reactivating main window, it needed to restore window if it was minimized
	ShowWindow(hWnd, (int)NativeMethods.ShowWindowCommands.ShowNormal);
	//this method will bring main window to front
	SetForegroundWindow(hWnd);
}        

abbasghomi avatar Apr 19 '23 11:04 abbasghomi

This is still an issue in Windows App SDK 1.3.2 (1.3.230602002)

Balkoth avatar Jul 13 '23 07:07 Balkoth

Seems to still be a problem in WASDK 1.4, is there any ETA for this?

AnalogFeelings avatar Oct 22 '23 01:10 AnalogFeelings

Does the explorer team using WinUi3 also just work around bugs like this? I would get really mad if i was on that team and long-standing bugs like this are still open.

Balkoth avatar Oct 22 '23 07:10 Balkoth

IntPtr hWnd = WindowNative.GetWindowHandle(_mainWindow); //this line causing a delay in reactivating main window, it needed to restore window if it was minimized ShowWindow(hWnd, (int)NativeMethods.ShowWindowCommands.ShowNormal); //this method will bring main window to front SetForegroundWindow(hWnd);

Worked like a champ, thank you.

Jimex avatar Oct 28 '23 14:10 Jimex

I was hitting the same issue today as well. My goal is to activate/foreground the apps single-instanced window after the user clicks a toast notification of the same app (a pretty common scenario I would say). The SetForegroundWindow workaround posted above does not work if the debugger isn't attached due to the reasons here.

A similar behavior can be observed when using Windows.System.Launcher.LaunchUriAsync. Normally this would launch and foreground your web browser if you call it, e.g. after clicking a button in the main window. However if you call it in Program.Main it launches your browser, but doesn't foreground it.

tipa avatar Nov 16 '23 06:11 tipa

I was hitting the same issue today as well. My goal is to activate/foreground the apps single-instanced window after the user clicks a toast notification of the same app (a pretty common scenario I would say). The SetForegroundWindow workaround posted above does not work if the debugger isn't attached due to the reasons here.

An API which generally works is SwitchToThisWindow (they say "It may be altered...", but it is used by Task Manager for ages...) (I used it for example in this test sample to set the main window to foreground with a Hotkey : MainWindow.xaml.cs)

castorix avatar Nov 16 '23 07:11 castorix

@castorix sadly, this also doesn't work for me...

tipa avatar Nov 16 '23 07:11 tipa

@castorix sadly, this also doesn't work for me...

I converted an old C++ function into C#, mixing all APIs. If it does not work for you, I have no more idea...

    private void SwitchToWindow(IntPtr hWnd)
    {
        int nLockTimeOut = 0;
        IntPtr hCurrWnd = GetForegroundWindow();
        int nPID = 0;
        uint nThisTID = GetCurrentThreadId(), nCurrTID = GetWindowThreadProcessId(hCurrWnd, out nPID);
        if (nThisTID != nCurrTID)
        {
            AttachThreadInput(nThisTID, nCurrTID, true);
            SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, ref nLockTimeOut, 0);
            int nNewLockTimeOut = 0;
            SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, ref nNewLockTimeOut, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
            AllowSetForegroundWindow(-1);
        }
        if (IsIconic(hWnd))
            ShowWindow(hWnd, SW_RESTORE);
        IntPtr hWndLastActivePopup = GetLastActivePopup(hWnd);
        SwitchToThisWindow(hWndLastActivePopup, true);           
        SetForegroundWindow(hWnd);
        if (nThisTID != nCurrTID)
        {
            SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, ref nLockTimeOut, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
            AttachThreadInput(nThisTID, nCurrTID, false);
        }
    }

with :

    [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    //public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, IntPtr pvParam, uint fWinIni);
    public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, [In, Out] ref int pvParam, uint fWinIni);

    public const int SPIF_UPDATEINIFILE = 0x0001;
    public const int SPIF_SENDWININICHANGE = 0x0002;

    public const int SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000;
    public const int SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001;

    [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern IntPtr GetForegroundWindow();

    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern uint GetCurrentThreadId();

    [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);

    [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);

    [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool AllowSetForegroundWindow(int dwProcessId);

    [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

    public const int SW_HIDE = 0;
    public const int SW_SHOWNORMAL = 1;
    public const int SW_SHOWMINIMIZED = 2;
    public const int SW_SHOWMAXIMIZED = 3;
    public const int SW_RESTORE = 9;

    [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool IsIconic(IntPtr hWnd);

    [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern IntPtr GetLastActivePopup(IntPtr hWnd);

    [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool SwitchToThisWindow(IntPtr hWnd, bool fAltTab);

    [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool SetForegroundWindow(IntPtr hWnd);

castorix avatar Nov 16 '23 08:11 castorix

I did this as a temporary solution.

In App.xaml.cs

protected async override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
	var mainInstance = Microsoft.Windows.AppLifecycle.AppInstance.FindOrRegisterForKey("main");
	var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();

	if (!mainInstance.IsCurrent)
	{
   
		await mainInstance.RedirectActivationToAsync(activatedEventArgs);
		System.Diagnostics.Process.GetCurrentProcess().Kill();
		return;
	}
	m_window = new MainWindow();
	m_window.Activate();
	mainInstance.Activated += MainInstance_Activated;
}
private void MainInstance_Activated(object sender, Microsoft.Windows.AppLifecycle.AppActivationArguments e)
{
	General.WinPresenter.Minimize();
	General.WinPresenter.Restore();
}

In MainWindow:

public MainWindow()
{
    this.InitializeComponent();
    General.WndHnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
}
private void WinLoaded()
{
	AppWindow CurWin;
	WindowId WinID = Win32Interop.GetWindowIdFromWindow(General.WndHnd);
	CurWin = AppWindow.GetFromWindowId(WinID);

	General.WinPresenter = CurWin.Presenter as OverlappedPresenter;
}

common class in the project

internal class General
{
	public static IntPtr WndHnd;
	public static Microsoft.UI.Windowing.OverlappedPresenter WinPresenter;
}

ritesh3103 avatar Jan 25 '24 09:01 ritesh3103

hi Team, is there any update on this issue? Thanks.

jingliancui avatar Apr 08 '24 15:04 jingliancui