Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
1 of 2 tasks
Balkoth opened this issue Aug 12, 2022 · 35 comments
Assignees
Labels
area-Windowing bug Something isn't working product-winui3 WinUI 3 issues team-CompInput Issue for IXP (Composition, Input) team tempo Issue seen in Tempo app

Comments

@Balkoth
Copy link

Balkoth commented Aug 12, 2022

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
  • Win32

Device form factor

Desktop

Windows version

Windows 10 (21H2): Build 19044

Additional context

No response

@Balkoth Balkoth added the bug Something isn't working label Aug 12, 2022
@ghost ghost added the needs-triage Issue needs to be triaged by the area owners label Aug 12, 2022
@gabbybilka gabbybilka added team-Reach Issue for the Reach team product-winui3 WinUI 3 issues area-Windowing labels Aug 19, 2022
@msalandro
Copy link

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

@Balkoth
Copy link
Author

Balkoth commented Sep 16, 2022

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

@pratikone pratikone self-assigned this Nov 28, 2022
@pratikone
Copy link
Contributor

looking at it

@MikeHillberg MikeHillberg added the tempo Issue seen in Tempo app label Nov 28, 2022
@pratikone
Copy link
Contributor

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.

@ghost ghost removed the needs-triage Issue needs to be triaged by the area owners label Nov 28, 2022
@Balkoth
Copy link
Author

Balkoth commented Nov 29, 2022

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!

@ghost ghost added the needs-triage Issue needs to be triaged by the area owners label Nov 29, 2022
@pratikone
Copy link
Contributor

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

@pratikone pratikone reopened this Dec 6, 2022
@pratikone pratikone removed the needs-triage Issue needs to be triaged by the area owners label Dec 6, 2022
@pratikone
Copy link
Contributor

pratikone commented Dec 8, 2022

@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 :)

@Balkoth
Copy link
Author

Balkoth commented Dec 8, 2022

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

@pratikone
Copy link
Contributor

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.

@Balkoth
Copy link
Author

Balkoth commented Dec 8, 2022

I will try it tomorrow and report back.

@Balkoth
Copy link
Author

Balkoth commented Dec 13, 2022

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
Copy link
Author

Balkoth commented Dec 13, 2022

Here is the sample project: WindowActivateTest.zip

@pratikone
Copy link
Contributor

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

@pratikone
Copy link
Contributor

pratikone commented Dec 21, 2022

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 pratikone reopened this Dec 21, 2022
@ghost ghost added the needs-triage Issue needs to be triaged by the area owners label Dec 21, 2022
@pratikone pratikone closed this as not planned Won't fix, can't repro, duplicate, stale Dec 21, 2022
@ghost ghost added the needs-triage Issue needs to be triaged by the area owners label Jan 16, 2023
@pratikone
Copy link
Contributor

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 pratikone reopened this Jan 20, 2023
@bpulliam bpulliam removed the needs-triage Issue needs to be triaged by the area owners label Mar 14, 2023
@Jay-o-Way
Copy link
Contributor

Still investigating?

@Jay-o-Way
Copy link
Contributor

Jay-o-Way commented Mar 23, 2023

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

@abbasghomi
Copy link

abbasghomi commented Apr 19, 2023

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);
}        

@Balkoth
Copy link
Author

Balkoth commented Jul 13, 2023

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

@bpulliam bpulliam added team-CompInput Issue for IXP (Composition, Input) team and removed team-Reach Issue for the Reach team labels Aug 22, 2023
@microsoft-github-policy-service microsoft-github-policy-service bot added the needs-triage Issue needs to be triaged by the area owners label Aug 22, 2023
@bpulliam bpulliam removed the needs-triage Issue needs to be triaged by the area owners label Aug 29, 2023
@AnalogFeelings
Copy link

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

@Balkoth
Copy link
Author

Balkoth commented Oct 22, 2023

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.

@Jimex
Copy link

Jimex commented Oct 28, 2023

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.

@tipa
Copy link
Contributor

tipa commented Nov 16, 2023

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.

@castorix
Copy link

castorix commented Nov 16, 2023

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)

@tipa
Copy link
Contributor

tipa commented Nov 16, 2023

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

@castorix
Copy link

@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);

@ritesh3103
Copy link

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;
}

@jingliancui
Copy link

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

@Balkoth
Copy link
Author

Balkoth commented Apr 11, 2024

Will this issue anytime soon be fixed?

@DeshaunZ
Copy link

It still exists in Windows App SDK 1.5.1 (1.5.240311000).
The company project started using WinUI 3 last month. I ran into this problem today, and I solved it like this.

private static void OnActivated(object? sender, AppActivationArguments args)
{
    ExtendedActivationKind kind = args.Kind;
    LogInfo($"已存在程序,激活回调: 激活次数: {activationCount++} - {ArgsMessage(kind, args)}");
    //WindowHelper.MainWindow.Activate();
    WindowHelper.MainWindow.DispatcherQueue.EnqueueAsync(() =>
    {
        //设置窗口置顶
        if (JOJO.WinUI.WindowsApi.Win32Api.SetTopMost())
        {
            //将窗口提到前台
            JOJO.WinUI.WindowsApi.Win32Api.SetForegroundWindow();
        }
    });
}

Using the Win32 API:
SetWindowPos
SetForegroundWindow

    public static class Win32Api
    {
        public static MessageBoxResults MessageBox(string content, string title, MessageBoxButtons messageBoxButtons = MessageBoxButtons.Ok) => (MessageBoxResults)Windows.Win32.PInvoke.MessageBox(new Windows.Win32.Foundation.HWND(WindowHelper.GetWindowHandle()), content, title, (Windows.Win32.UI.WindowsAndMessaging.MESSAGEBOX_STYLE)messageBoxButtons);

        public static bool SetForegroundWindow() => Windows.Win32.PInvoke.SetForegroundWindow(new Windows.Win32.Foundation.HWND(WindowHelper.GetWindowHandle()));

        /// <summary>
        /// 置顶
        /// </summary>
        public static bool SetTopMost() => Windows.Win32.PInvoke.SetWindowPos(new Windows.Win32.Foundation.HWND(WindowHelper.GetWindowHandle()), new Windows.Win32.Foundation.HWND(new IntPtr(0)), 0, 0, 0, 0, Windows.Win32.UI.WindowsAndMessaging.SET_WINDOW_POS_FLAGS.SWP_NOMOVE | Windows.Win32.UI.WindowsAndMessaging.SET_WINDOW_POS_FLAGS.SWP_NOSIZE);
    }

Using NuGet
Microsoft.Windows.CsWin32
CommunityToolkit.WinUI.Extensions

My English is not good.Hope it helps you.

@pratikone
Copy link
Contributor

Thanks. It is in the list of bugs we have for fixing. Looking into it. Will update once I have something

@pratikone
Copy link
Contributor

pratikone commented May 9, 2024

Some update : i have found the root issue and the fix. Now remains is the internal discussion over the fix.
Here is some explanation of why it happpens :

This is due to the fact Window.Activate() internally calls SetActiveWindow since its inception (source) .
WPF on the other hand, calls SetForegroundWindow(). Hence, this issue is only for winui and not WPF.
Now, which behavior is correct ? WPF is the established one but prone to focus stealing behavior. WinUI is more controlled but there are legitimate reasons like this where it should be moving focus.

, WinUI could start calling SetForegroundWindow too but it would be a breaking change. We are currently assessing the impact on backward compatibility and then we will do the fix.
Hope this provides some clarity.

@Balkoth
Copy link
Author

Balkoth commented May 23, 2024

IMHO this is exactly how Activate should always have behaved and is documented:
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-Windowing bug Something isn't working product-winui3 WinUI 3 issues team-CompInput Issue for IXP (Composition, Input) team tempo Issue seen in Tempo app
Projects
None yet
Development

No branches or pull requests