microsoft / microsoft-ui-xaml

Windows UI Library: the latest Windows 10 native controls and Fluent styles for your applications
MIT License
6.31k stars 676 forks source link

Proposal: Borderless transparent window background support in apps using WinUI #1247

Open hansmbakker opened 5 years ago

hansmbakker commented 5 years ago

Proposal: Transparent background support in apps using WinUI

Summary

Currently, WPF applications can have a transparent and borderless window while UWP applications cannot have that. Request is to support the creation of transparent borderless apps using WinUI.

Rationale

Scope

Capability Priority
This proposal will allow developers to accomplish transparent windows Must
This proposal will allow developers to accomplish borderless windows Must

Important Notes

This feature could enable designs as shown in this video made by @Niels9001 (source tweet).

This is currently not possible, because:

hansmbakker commented 5 years ago

cc @marb2000 @niels9001

marb2000 commented 5 years ago

This is a very frequent scenario in Win32, We should consider this scenario at least for WinUI Desktop and WinUI XAML Islands.

riverar commented 4 years ago

Adding a data point, our app (EarTrumpet) requires this functionality.

riverar commented 4 years ago

Anything we can do to get this out of the freezer? https://github.com/microsoft/microsoft-ui-xaml/projects/4#card-25842420

I believe filling in functionality and API gaps should rank much higher. Frankly, I think it's critical to WinUI 3 XAML adoption.

hansmbakker commented 4 years ago

@SavoySchuler @jevansaks ?

shaheedmalik commented 4 years ago

Those designs in the tweet look great.

marb2000 commented 4 years ago

@riverar this is WinUI 2 backlog, and for WinUI 2 is freezer. When WinUI 3 backlog will be active, we will unfreeze it again.

riverar commented 4 years ago

@marb2000 Thanks, it's very confusing how we all talk about WinUI 3 shipping soon yet the repository doesn't reflect WinUI 3 status very well. All we have are the community calls, tags, and word on the street right now.

hansmbakker commented 4 years ago

Can you please give a status update on this?

Alikont commented 4 years ago

We'd also like to see transparent borderless windows in WinUI

We want to:

  1. Compose different windows of different processes on top of each other
  2. Apply custom styling to windows

In this picture you can see our product, which is a Windows 10 device with 3 running applications, 2 in yellow and one in red rectangles.

Right now it's achieved via hooking into native and undocumented places of ApplicationFrameHost.dll and Windows.UI.Xaml.dll, but we'd like to avoid that.

Fedorov113 commented 4 years ago

This feature is needed for Windows Terminal. microsoft/terminal#603 Having transparent terminal is convenient and expected.

jtbrower commented 4 years ago

I became really excited once I realized that WinUI could be used in Desktop applications, though I figured I would run into an obstacle that made it impossible. It didn't take long to find the show stopper; this seems to at least one of them and I am sure there are more. @Alikont would you mind sharing the unpublished workaround you are using? Since the day UWP came out in 2012 I have wanted to migrate a boatload of WPF to a more performant XAML flavor. If it requires unpublished trickory I will do whatever it takes.

jtbrower commented 4 years ago

For WinUI Desktop Apps

As simple as this looks, it wasn't as simple to figure out with the information available to us 7/3/2020. I tested this against both 32/64 bit platforms running WinUI Preview 1 with .Net 5.0 preview 6

Disclaimers

namespace WinUI_InDesktop { public static class Interop { public static IntPtr SetWindow(IntPtr hWnd, int nIndex, IntPtr dwNewLong) { return Environment.Is64BitProcess ? SetWindowLongPtr(hWnd, nIndex, dwNewLong) : SetWindowLong(hWnd, nIndex, dwNewLong); }

    [DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLong")]
    private static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

    [DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLongPtr")]
    private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

    //If you have WPF experience, this is similar to WindowStyle="None"
    public static void SetMainWindowBorderless()
    {
        const int GWL_STYLE = -16;

        //WS_VISIBLE | WS_POPUP
        var WS_VISIBLE_POPUP = new IntPtr(unchecked((int)0x90000000));

        using var process = Process.GetCurrentProcess();
        var success = WS_VISIBLE_POPUP != SetWindow(
            process.MainWindowHandle, 
            GWL_STYLE, 
            WS_VISIBLE_POPUP);

        //Helpful when your day goes south
        Debug.WriteLineIf(!success, new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()).Message);
    }
}

}

marb2000 commented 4 years ago

@jtbrower Thanks for sharing this piece of code. I was curious about and I tested the SetMainWindowBorderless method. However, It never removes the borders.. Where did you call the method? Even better, can you share a simple repro?

niels9001 commented 4 years ago

@marb2000 Are there any updates on this topic :)? Would be nice to hear the latest status/plans on this on the next Community Call!

Alikont commented 4 years ago

@jtbrower , about the workaround. It's not trivial.

There are 2 places that we hook to achieve our UWP transparency

  1. ApplicationFrameHost.dll - here we patch all calls to Visual::SetContent to always set content as nullptr. This achieves removal of background and splashscreen. This is not stable across windows updates, and it breaks visuals for all UWP windows for all applications on the system. It's also used by Explorer.exe, so there is a chance that if you patch it incorrectly, the explorer won't start.
  2. In app, we hook Compositor::CreateSpriteVisual and SpriteVisual::SetContent. We capture first created SpriteVisual (which is the background), and then redirect all calls that attempt to set it's content to set nullptr instead. Because it's a VMT hook, it's pretty stable so far.

This information might be relevant to this discussion, because it limits what WinUI can achieve without cooperation from Windows UWP stack, because for UWP applications part of the appearance, including borders and background, is controlled by the OS.

But I don't see technical reason why it can't work in Desktop applications right now. If you set WS_EX_NOREDIRECTIONBITMAP style, Windows will already make a transparent window for you, and you can easily layer Direct Composition visuals there with transparency and effects.

hansmbakker commented 4 years ago

@niels9001 maybe also good to put the question in https://github.com/microsoft/microsoft-ui-xaml/issues/2867

jtbrower commented 4 years ago

@marb2000 I am heading out the door for an appointment, but will share a solution later today. My solution is an example of a WinUI .Net 5.0 preview 6 desktop application that has a .Net Standard view model library, WinUI custom control library and a native methods library with the Window extension methods.

This solution will demonstrate how to

  1. Dynamically make a Window borderless and change it back to a normal Window.
  2. Change the transparency on a Window by entering a percentage.
  3. Demonstrates how to use a ContentDialog for confirmation via an IDialogService when the user attempts to set the Window more than 90% transparent.
  4. Demonstrates how to provide Window DragMove so that a user can have a borderless window and still give the end user the capability to move the window around the screen.
  5. Shows how to obtain the WindowHandle and provides the end user multiple ways to get it.
  6. Shows how to maximize/minimize and normalize a window while still being borderless.
  7. Window handles the DragMove while a custom Page has the buttons to perform the above actions. I designed it in a way that prevents static singleton instance coupling.

I also want to show how to change the window size and plan to implement a SizeToContent feature. I have all of the above working except my DragMove needs a little adjustment because my calculations for how much the pointer moved are currently lagging behind reality. I think its related to a DIP unit vs. a screen unit situation. When I went to bed last night, out of nowhere I began seeing a InvalidOperationException: unsupported value type exception while setting my Page.Window custom dependency property to the Window value. It has to be something stupid I did because it was working fine.

I promise I will share it all today. In the meantime, regarding the example I shared above, that was also done using WinUI desktop with .Net 5.0 preview 6. This can be accomplished by adding a reference to the <PackageReference Include="Microsoft.Windows.CsWinRT" Version="0.1.0-prerelease.200623.5" /> package in your desktop application's csproj. Its given me the opportunity to continue evalutating WinUI while still using .Net5 preview 6.

jtbrower commented 4 years ago

@marb2000 I apologize for being a couple days later than I said I would be, regarding the example code for the features I mentioned above. I got into a recursive loop of "let me just fix this one last thing".

Edited on 7/30/2020 to match the current Repo Layout and use Permalink Paths I am going to do my best to document my transition from WPF to WinUI. My focus so far has been primarily on coming up with work-arounds for missing Window features. I created a github repo here.

As for the code that removes the Border/Titlebar it can be achieved using HideWin32NonClientArea(IntPtr windowHandle) that you will find in this NativeMethods.WindowStyles.cs file.

When you run the sample's solution, you can DragMove the window around your screen by touching anywhere on the window using either mouse or touch. The logic to achieve that can be found in DragMoveBehavior.cs . I also created a behavior that makes it easier to add a DropShadow with rounded corners.

Hopefully, by the time others read this in the future, it will be much further along, but I came back to update the paths to use permalinks because I refactor constantly.

jthoward64 commented 4 years ago

@marb2000 Hate to clog your mentions, buuuuut, any updates on this since preview 2 was released?

ShankarBUS commented 4 years ago

Hey @hansmbakker, a suggestion just for your round corners problem

If don't plan to add shadows around the window, you can do the rounded corners easily using CreateRoundedRectRgn and SetWindowRgn or better you can leave it to WPF's Window chrome feature

This implementation was suggested to me by @michalleptuch on Twitter see https://github.com/michalleptuch/RoundedCorners

But if want to add drop shadow around the window, you will experience some problems https://twitter.com/ShankarBUS/status/1307233994992488450?s=19

But that doesn't stop you. You can add margin & shadow to the content on WPF side and set the rounded region on the xaml islands child window.

I'm trying to implement this in one of my apps I will notify you once I successfully complete my implementation.

hansmbakker commented 4 years ago

Thank you for your suggestion, but I believe those rounded corners of the wpf window are not possible with acrylic.

For acrylic you need UWP, so you need XAML islands to embed it in a WPF window, and that XAML islands part will always be rectangular.

If you give the UWP part rounded corners as well then the XAML islands part still is rectangular with an opaque black background.

That is why I believe this is not possible to be solved today.

ShankarBUS commented 4 years ago

First of all I'm sorry for spamming your inboxes 😅

@hansmbakker Did you even see the repo I specified? I'm 100% sure it worked for me

SetWindowRgn (Which is what WindowChrome in WPF uses to set the CornerRadius) works! It clips not only the WPF Window but also to the "DesktopWindowXamlSource" xaml islands child window!

image

I know that xaml islands create a child window over the WPF window (this causes the "Air Space" issue)

I saw a post by @michalleptuch about his Ink Workspace app, which had rounded corners and shadow. Out of curiosity I asked him how he did it and I mentioned this issue and the related one in WCT. He then suggested this approach to me. This approach will be really useful if you plan to add no margin or no drop shadow within the UWP xaml. If you are happy with this condition you're good to go!

If you do want to add margin/shadow you will end up with this (I added some margin within the UWP xaml since we need some space to see the shadow)

image

But I'm planning on another approach. Which is, make the WPF window transparent & borderless and add some margin & shadow to the XamlHost control from WCT within the WPF xaml. Since the UWP xaml is hosted on a child window, we can use FindWindowEx to get its HWND and use CreateRoundedWindowRgn + SetWindowRgn on it.

Hey @marb2000, sorry for the interruption. Since you work on xaml islands, could you verify this?

hansmbakker commented 4 years ago

Ah, I didn't know that the SetWindowRgn method performs clipping. That would help indeed when no shadow is needed! As for the shadow, frankly speaking I have no experience with shadows in WPF (would need to look that up), but it is nice that you are working on a workaround!

Samuel12321 commented 3 years ago

Has there been any progress on this yet @marb2000?

selastingeorge commented 3 years ago

Is there any progress on the transparency issue.

jthoward64 commented 3 years ago

from the patch is clearly visible that Background="Transparent" was supported a long time and Opacity too.

You may have missed that we had to use a private API to get this working. Everything looks easy through that lens.

Without us using that API, XAML enforces the presence of a black background under all controls as an "emergency" measure to explicitly prevent transparency.

Originally posted by @DHowett in https://github.com/microsoft/terminal/issues/603#issuecomment-923125091

selastingeorge commented 3 years ago

If it was UWP I could have used some private API like this: IWindowPrivate, but when it comes to WinUI, I am not familiar and why did they need to enforce the presence of a black background to prevent transparency when people need transparency, at least it must be optional. Windows.UI.Composition can render on transparent window, so i believe Microsoft.UI.Composition can also do same, if the root visual is intentionally painted with black color, I don't think adding a dependency property to enable transparency requires years of work.

jthoward64 commented 3 years ago

If it was UWP I could have used some private API like this: IWindowPrivate, but when it comes to WinUI, I am not familiar and why did they need to enforce the presence of a black background to prevent transparency when people need transparency, at least it must be optional. Windows.UI.Composition can render on transparent window, so i believe Microsoft.UI.Composition can also do same, if the root visual is intentionally painted with black color, I don't think adding a dependency property to enable transparency requires years of work.

Thankfully they managed to sort it (no release yet but transparency is merged onto main in microsoft/terminal now) and that issue is closed, but I don't think there is any way for individual developers to use their method for other apps

selastingeorge commented 3 years ago

do you have any links or docs

jthoward64 commented 3 years ago

PR: microsoft/terminal#11180 Doc PR: MicrosoftDocs/terminal#416

selastingeorge commented 3 years ago

Thankyou

michalleptuch commented 2 years ago

Is there any progress? 😉

appindus-ibiller commented 2 years ago

April 2022 not fixed and no update in almost 2 years..?

castorix commented 2 years ago

SetLayeredWindowAttributes works

A test with a completely transparent window, with just a visible Button :

WinUI3_Transparent

mdtauk commented 2 years ago

SetLayeredWindowAttributes works

A test with a completely transparent window, with just a visible Button :

WinUI3_Transparent

Does this block input from what is behind the Transparent window?

michalleptuch commented 2 years ago

@castorix Could you share source code?

castorix commented 2 years ago

Does this block input from what is behind the Transparent window?

Yes, while the ex. style has not WS_EX_TRANSPARENT (but this style "kills" the UI and it needs more code to manage it...)

castorix commented 2 years ago

@castorix Could you share source code?

It depends on which exact behaviour is wanted I used OverlappedPresenter.SetBorderAndTitleBar to remove title bar/borders, added WS_EX_LAYERED ex. style then SetLayeredWindowAttributes, but without title bar, code must be added to move the window for example (like WM_NCHITTEST)

castorix commented 2 years ago

Or maybe it can be better with a Border if the title bar is removed. I tested with PointerMoved, PointerPressed, PointerReleased to move the window but not sure if it is the simplest method...

WinUI3_Transparent2

appindus-ibiller commented 2 years ago

SetLayeredWindowAttributes works A test with a completely transparent window, with just a visible Button : WinUI3_Transparent

Can you share your source code?

FWIW this did the job. I don't really understand why interop and multiple libraries were required to obtain a class that has the properties of what the baseline WPF window xaml was capable of, but I'm very appreciative of you sharing this!

zadjii-msft commented 2 years ago

@marb2000 / @ranjeshj We might be able to close this out now since this is seemingly supported in WASDK 1.0.

michalleptuch commented 2 years ago

@castorix Could you share source code?

It depends on which exact behaviour is wanted I used OverlappedPresenter.SetBorderAndTitleBar to remove title bar/borders, added WS_EX_LAYERED ex. style then SetLayeredWindowAttributes, but without title bar, code must be added to move the window for example (like WM_NCHITTEST)

@castorix Sorry, I don't get it 😞 Do I need to set WS_EX_LAYERED style on parent or content window? How to exactly set SetLayeredWindowAttributes and when? I just want to remove window background like this https://github.com/microsoft/microsoft-ui-xaml/issues/1247#issuecomment-1092858148

Samuel12321 commented 2 years ago

@marb2000 / @ranjeshj We might be able to close this out now since this is seemingly supported in WASDK 1.0.

I strongly disagree, the fact still stands that you can't just set the window to be transparent easily like wpf.

castorix commented 2 years ago

@castorix Sorry, I don't get it 😞 Do I need to set WS_EX_LAYERED style on parent or content window? How to exactly set SetLayeredWindowAttributes and when? I just want to remove window background like this #1247 (comment)

I uploaded a test sample that I have improved a bit : WinUI3_Transparent.zip (tested with VS 2022 on Windows 10 21H1, with WindowsAppSDK 1.1.0-preview1)

jthoward64 commented 2 years ago

@marb2000 / @ranjeshj We might be able to close this out now since this is seemingly supported in WASDK 1.0.

I strongly disagree, the fact still stands that you can't just set the window to be transparent easily like wpf.

I'm with you, at the very least this should be better documented, maybe even with a just decent minimal reproducible example.

After that I think making it easier to do should be it's own issue as this one is long and old and focuses more on the technical side.

appindus-ibiller commented 2 years ago

If Window had a direct line to the AppWindow class without passing through interop that would be a big improvement. This solution is clunky, but at least it does work. Its more of a usable workaround than mission accomplished.

michalleptuch commented 2 years ago

It doesn't work for me when I'm using separated MSIX project. Border and shadow are still visible. It works only in single project 👌

arpadbarta commented 2 years ago

@riverar this is WinUI 2 backlog, and for WinUI 2 is freezer. When WinUI 3 backlog will be active, we will unfreeze it again.

Is there an update on this? WinUI 3 has been released, but this issue is still in the freezer... Please consider moving it up on the priority list.

mdtauk commented 2 years ago

Ideally it would be possible with something like.

Window.Style = WindowsStyle.Transparent