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

Need help with ComboBox DropDown & Popup

Open naumenkoff opened this issue 1 year ago • 1 comments

Hello everyone.
I can't figure out how the ComboBox works: for some reason, it has a gray background, and I can't get rid of it.
For example, in NavigationView, I able to remove it by overriding NavigationViewContentBackground to Transparent.
But here it's a completely different situation: I've read tons of articles and looked through the code of many apps that use ComboBox, where it is rendered properly, but I still haven't found a solution.

What I am referring to:

  • In the Settings app (Windows 11), ComboBox has an opaque, non-gray background.
    Image
  • In the MVVM Toolkit Sample App (The Final Result, SubredditWidget), ComboBox has a transparent blurred, non-gray background.
    Image
  • DropDownButton
<DropDownButton Content="Say">
    <DropDownButton.Flyout>
        <MenuFlyout Placement="Bottom">
            <MenuFlyoutItem Text="Hello" />
            <MenuFlyoutItem Text="World" />
        </MenuFlyout>
    </DropDownButton.Flyout>
</DropDownButton>

Image

But for me, everything looks completely different. I don't know how this happened. And why no solutions are working!

ComboBox

It differs from DropDownButton.

<ComboBox>
    <ComboBoxItem Content="Hello"/>
    <ComboBoxItem Content="World"/>
</ComboBox>

Image

ComboBox with overridden ComboBoxDropDownBackground

<ComboBox>
    <ComboBoxItem Content="Hello"/>
    <ComboBoxItem Content="World"/>
    <ComboBox.Resources>
        <SolidColorBrush x:Key="ComboBoxDropDownBackground" Color="{ThemeResource AcrylicInAppFillColorDefaultBrush}" />
    </ComboBox.Resources>
</ComboBox>

Image

I referred to these Issues:

  • https://github.com/microsoft/microsoft-ui-xaml/issues/7176
  • https://github.com/microsoft/microsoft-ui-xaml/issues/7200
  • https://github.com/microsoft/microsoft-ui-xaml/issues/8396
  • https://github.com/microsoft/microsoft-ui-xaml/issues/8657
  • https://github.com/microsoft/microsoft-ui-xaml/issues/9523

Versions:

  • Nuget: WindowsAppSDK 1.6.240923002, Runtime: 1.6.1
  • net8.0-windows10.0.26100.0
  • Windows 11 Pro 26100.2033

naumenkoff avatar Oct 12 '24 13:10 naumenkoff

It's a bug in WinUI 3. Currently only in WinUI 2 (UWP) there is an acrylic background, sadly.

whiskhub avatar Oct 12 '24 16:10 whiskhub

https://github.com/DGP-Studio/Snap.Hutao/blob/main/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Behavior/ComboBoxSystemBackdropWorkaroundBehavior.cs Just use ContentExternalBackdropLink

Lightczx avatar Apr 16 '25 09:04 Lightczx

Hi @naumenkoff, can you please confirm if your issue is solved with above mentioned workaround?

snigdha011997 avatar May 29 '25 07:05 snigdha011997

For what it's worth, I have used the code from @Lightczx and it does work, however, NOT if the app is AOT-compiled. (Then nothing happens, as if the code wasn't there) I tried investigating a bit and I assume some of the internals of WinUI are different (trimmed ?) which makes the code unable to do what it tries to do. (I couldn't confirm with enough confidence due to the limited debugging experience available in AOT, but it seemed like the code might never find the Popup in that case) The workaround is also somewhat slow in some situations (e.g. switching to a (cached?) page with a dozen ComboBox will freeze for a noticeable amount of time). I assume that this is related to some of the component allocations and swapping around of the UI tree that takes place in the workaround. As none of that would be needed if this was directly supported by WinUI, I assume it would be at least somewhat faster in that case. Additionally, It would seem that the workaround entirely removes the background texture instead of mixing it like it is supposed to be (In the W11 settings app, if you go to the Date & Time page, you can easily see both texture and background. Altough the texture used there is possibly more subtle than the aggressive one provided by WinUI 3) This may not be a problem with the workaround itself, though, as WinUI3 doens't seem to ever be able to do this mixing between texture and background anywhere except in the mostly useless "in-app" AcrylicBrush, as actionable solutions for other flyouts also require setting the background to transparent (which comes with other visual problems…), thus removing the texture.

Honestly, I would never have spent the tiniest amount of time trying to understand WinRT internals and make this work, so I'm just glad that someone took time to try understanding these cryptic APIs and provide a workaround in a working state (except for AOT). Thanks @Lightczx ! Hopefully, this will help getting a proper native WinUI 3 implementation of that feature that has been missing for years now, which would benefit everybody.

hexawyz avatar May 29 '25 22:05 hexawyz

Oh, The code I use is credit to @HO-COOH I just somehow translate his code from C++ to C#

Lightczx avatar May 30 '25 00:05 Lightczx

In the W11 settings app, if you go to the Date & Time page, you can easily see both texture and background.

The settings app uses CoreWindow, and the popup create by combobox is constrained to root bound by default. And so it uses in-app acrylic brush. But for WASDK WinUI 3, the combobox creates the popup that doesn't constrained to root bound by default(set in style). And I believe it's intentional done because they want to avoid the issue that combobox's popup can be overlapped(covered) by titlebar if you set ExtendsContentIntoTitleBar to true and that makes some items unclickable.

Lightczx avatar May 30 '25 01:05 Lightczx

Hmm, so, in the end, this actually motivated me to look at this again.

So…

It turns out that in AOT, VisualTreeHelper is completely broken and will return objects that are the wrong type. Everything returned is typed explicitly as FrameworkElement instead of the actual derived types. This will unsurprisingly break the Community Toolkit helpers, although some form of them might work, TBH I didn't investigate deeply enough. Point is most of the code of Initialize was trimmed because of what it inferred from Community Toolkit helpers. (Also: I assume VisualTreeHelper is still "broken" in non-AOT scenarios, but we are saved by the automatic dynamic interface casts, so it is hidden)

I fixed it like there: https://github.com/hexawyz/Exo/commit/e23ac45a9684f4d3f22d9a8947c0cda68f0bf93c

My fix is a bit more complex than strictly required, as I implemented a TryAs method because (obviously…) the ultra complex WinRT stuff does not choose to expose a cast that doesn't throw. You will be fine with just .As<Popup>() if you accept the highly unlikely risk of error :)

Oh, The code I use is credit to @HO-COOH I just somehow translate his code from C++ to C#

I actually saw the C++ code and I was disappointed to see that it was not C# 😅 So, again, thank you 😉

But for WASDK WinUI 3, the combobox creates the popup that doesn't constrained to root bound by default(set in style). And I believe it's intentional done because they want to avoid the issue that combobox's popup can be overlapped(covered) by titlebar if you set ExtendsContentIntoTitleBar to true and that makes some items unclickable.

Hmm, so that one was actually a request by us (the users), as popups limited to the client area were very annoying. Both for what you said and just generally not being able to escape the window if too small or if the ComboBox was near an edge. 😁

Good catch on the W11 settings stuff, though. It does indeed seem that it is unable to escape the confines of the parent window!

hexawyz avatar May 30 '25 01:05 hexawyz

Everything returned is typed explicitly as FrameworkElement instead of the actual derived types.

Yeah, use TryAs<Popup> to check the actual type since the native abi should stay the same between AOT and not. Actually you can implment TryAs like this (WinRTExtension.As also +0 ref):

        public static bool TryAs<T>(IWinRTObject obj, Guid iid, out T value)
        {
            nint abi = 0;
            try
            {
                // +1 ref
                if (obj.NativeObject.TryAs(iid, out abi) > 0)
                {
                    // +1 -1 ref
                    value = MarshalInspectable<T>.FromAbi(abi);
                    return true;
                }

                value = default!;
                return false;
            }
            finally
            {
                if (abi != 0)
                {
                    // -1 ref
                    MarshalInspectable<object>.DisposeAbi(abi);
                }
            }
        }

Lightczx avatar May 30 '25 08:05 Lightczx

Ah, thanks, I had to lose myself in the arcana of the CsWinRT codebase to verify that it does the same stuff despite the very different code paths, and your version looks like it should be better in some cases so I updated to it. (A bit worrying that my version based on CastExtensions.As<T> has such a different code path…)

hexawyz avatar May 30 '25 15:05 hexawyz

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 7 days. It will be closed if no further activity occurs within 7 days of this comment.