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

How to display ContentDialog backdrop/smoke over custom TitleBar

Open Petrarca181 opened this issue 3 years ago • 17 comments

As in title. Current result is this. https://imgur.com/HKuIZ4E

How to dispay it over custom titlebar? Till preview 3 all was ok.

Petrarca181 avatar Jan 01 '22 21:01 Petrarca181

I think you can't, it seems to be a bug. Are you targeting Win11 exclusively or do you need backwards compatibility to Win10? In W11 you should switch from the old SetTitleBar() method to the new AppWindow system. Here is a snippet of my app that might help you:

public AppWindow AW { get; set; }
public bool IsCustomizationSupported { get; set; } = false;
public MainWindow()
{
	InitializeComponent();
	IsCustomizationSupported = AppWindowTitleBar.IsCustomizationSupported();

	if (IsCustomizationSupported)
	{
		AW = GetAppWindowForCurrentWindow();
		AW.TitleBar.ExtendsContentIntoTitleBar = true;
		CustomDragRegion.Height = 22;
		AW.Title = "ConTeXt IDE";
		//AW.Closing += AW_Closing;
		//AW.SetIcon(Path.Combine(Package.Current.Installed­Location.Path, @"Assets/", @"SquareLogo.png"));
	}
	else
	{
		CustomDragRegion.BackgroundTransition = null;
		CustomDragRegion.Background = null;
		ExtendsContentIntoTitleBar = true;
		CustomDragRegion.Height = 28;
		SetTitleBar(CustomDragRegion);
		Title = "ConTeXt IDE";
	}
}

private AppWindow GetAppWindowForCurrentWindow()
{
	IntPtr hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
	WindowId myWndId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);
	return AppWindow.GetFromWindowId(myWndId);
}

This way, at least in W11 (where IsCustomizationSupported is true) the smoke does work in the Titlebar.

WelterDevelopment avatar Jan 02 '22 21:01 WelterDevelopment

@pratikone and @bpulliam is this possible today?

StephenLPeters avatar Mar 02 '22 00:03 StephenLPeters

I'm experiencing this as well on my app using Windows App SDK V1.0 @StephenLPeters @gabbybilka With my custom title bar and showing a ContentDialog, the entire window is blocked by the ContentDialog except for the Min, Max, and Close window buttons of the title bar. This is not the expected behavior of the ContentDialog as it is expected to block the caption buttons as well (as seen in apps such as the Win 11 Notepad)

nlogozzo avatar May 02 '22 03:05 nlogozzo

this is not possible today unfortunately because of the way custom titlebar is implemented. we could try to make it work, maybe ? marking it as a bug .

pratikone avatar May 13 '22 18:05 pratikone

App sdk 1.1 stable. Still not fixed. I was using this style to completely hide default titlebar and make my own.

Style

But now if I use it drag region stops working.... help please.

Petrarca181 avatar Jun 11 '22 16:06 Petrarca181

The reason it doesn't fill the titlebar is that the shadow has a margin set on it when XAML Window has ExtendsContentIntoTitleBar.

So as a workaround that works on all OS, pass a function to the Loaded event on the ContentDialog containing one of the two code blocks. One is for C++, and the other is for C#.

// For C++
auto parent = winrt::Microsoft::UI::Xaml::Media::VisualTreeHelper::GetParent(*this);
auto child  = winrt::Microsoft::UI::Xaml::Media::VisualTreeHelper::GetChild(parent, 0);
auto frame  = child.as<winrt::Microsoft::UI::Xaml::Shapes::Rectangle>();
frame.Margin(ThicknessHelper::FromUniformLength(0));
frame.RegisterPropertyChangedCallback(
    FrameworkElement::MarginProperty(),
    [](DependencyObject const& sender, DependencyProperty const& dp) {
        if (dp == FrameworkElement::MarginProperty())
            sender.ClearValue(dp);
    }
);
// For C#
var parent = VisualTreeHelper.GetParent(this);
var child = VisualTreeHelper.GetChild(parent, 0);
var frame = (Microsoft.UI.Xaml.Shapes.Rectangle)child;
frame.Margin = new Thickness(0);
frame.RegisterPropertyChangedCallback(
    MarginProperty,
    (DependencyObject sender, DependencyProperty dp) =>
    {
        if (dp == MarginProperty)
            sender.ClearValue(dp);
    });

All the code does is override the margin that XAML sets, and then make sure that XAML can't change it.

Link1J avatar Jul 11 '22 21:07 Link1J

var child = VisualTreeHelper.GetChild(parent, 0);

It actually has to be:

var child = VisualTreeHelper.GetChild(parent, 2);

The third element is the smokeLayerBackground

Lightczx avatar Aug 02 '22 05:08 Lightczx

Taking advantage of the CommunityToolkit.WinUI.UI.Behaviors.BehaviorBase class

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Shapes;

/// <summary>
/// Make ContentDialog's SmokeLayerBackground dsiplay over custom titleBar
/// </summary>
public class ContentDialogBehavior : CommunityToolkit.WinUI.UI.Behaviors.BehaviorBase<FrameworkElement>
{
    /// <inheritdoc/>
    protected override void OnAssociatedObjectLoaded()
    {
        DependencyObject parent = VisualTreeHelper.GetParent(AssociatedObject);
        DependencyObject child = VisualTreeHelper.GetChild(parent, 2);
        Rectangle smokeLayerBackground = (Rectangle)child;

        smokeLayerBackground.Margin = new Thickness(0);
        smokeLayerBackground.RegisterPropertyChangedCallback(FrameworkElement.MarginProperty, OnMarginChanged);
    }

    private static void OnMarginChanged(DependencyObject sender, DependencyProperty property)
    {
        if (property == FrameworkElement.MarginProperty)
        {
            sender.ClearValue(property);
        }
    }
}

just add this to your project, and add xaml in your ContentDialog like below

<ContentDialog ...>
    <mxi:Interaction.Behaviors>
        <shcb:ContentDialogBehavior/>
    </mxi:Interaction.Behaviors>
</ContentDialog>

Works fine for me.

Lightczx avatar Aug 02 '22 05:08 Lightczx

I just set titlebar container height to 0 when call contentdialog. I like the last solution, but how make it work if I create dialog code behind?

Petrarca181 avatar Aug 02 '22 05:08 Petrarca181

ContentDialog dialog = new();
...
Microsoft.Xaml.Interactivity.Interaction.SetBehaviors(dialog, new BehaviorCollection(){new ContentDialogBehavior()});

Tested, and works. Just call SetBehaviors() before you call ShowAsync() of the dialog, I'm not sure if it will work after call rhe ShowAsync().

Lightczx avatar Aug 02 '22 05:08 Lightczx

You are my hero.

Petrarca181 avatar Aug 02 '22 06:08 Petrarca181

```cs
var child = VisualTreeHelper.GetChild(parent, 0);

It actually has to be:

var child = VisualTreeHelper.GetChild(parent, 2);

The third element is the smokeLayerBackground

VisualTreeHelper.GetChild(parent, 2); - Catastrophic failture VisualTreeHelper.GetChild(parent, 1); - Smoke null VisualTreeHelper.GetChild(parent, 0); - Actually works but need to test

Petrarca181 avatar Aug 02 '22 09:08 Petrarca181

```cs
var child = VisualTreeHelper.GetChild(parent, 0);

It actually has to be:

var child = VisualTreeHelper.GetChild(parent, 2);

The third element is the smokeLayerBackground

VisualTreeHelper.GetChild(parent, 2); - Catastrophic failture VisualTreeHelper.GetChild(parent, 1); - Smoke null VisualTreeHelper.GetChild(parent, 0); - Actually works but need to test

That‘s wierd. image

You can set a breakpoint and inspect the parent.Children

Lightczx avatar Aug 02 '22 09:08 Lightczx

```cs
var child = VisualTreeHelper.GetChild(parent, 0);

It actually has to be:

var child = VisualTreeHelper.GetChild(parent, 2);

The third element is the smokeLayerBackground

VisualTreeHelper.GetChild(parent, 2); - Catastrophic failture VisualTreeHelper.GetChild(parent, 1); - Smoke null VisualTreeHelper.GetChild(parent, 0); - Actually works but need to test

It cound be my issue, cause my dialog is modified from a UserContol template.

Lightczx avatar Aug 02 '22 10:08 Lightczx

Well... Just use GetChildrenCount and a for loop to test all elements

using CommunityToolkit.WinUI.UI.Behaviors;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Shapes;

/// <summary>
/// Make ContentDialog's SmokeLayerBackground dsiplay over custom titleBar
/// </summary>
public class ContentDialogBehavior : BehaviorBase<FrameworkElement>
{
    /// <inheritdoc/>
    protected override void OnAssociatedObjectLoaded()
    {
        DependencyObject parent = VisualTreeHelper.GetParent(AssociatedObject);

        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
        {
            DependencyObject current = VisualTreeHelper.GetChild(parent, i);
            if (current is Rectangle { Name: "SmokeLayerBackground" } background)
            {
                background.Margin = new Thickness(0);
                background.RegisterPropertyChangedCallback(FrameworkElement.MarginProperty, OnMarginChanged);
                break;
            }
        }
    }

    private static void OnMarginChanged(DependencyObject sender, DependencyProperty property)
    {
        if (property == FrameworkElement.MarginProperty)
        {
            sender.ClearValue(property);
        }
    }
}

@Petrarca181 This should totally work now.

Lightczx avatar Aug 02 '22 10:08 Lightczx

@Lightczx I don't know if the C# version works. The C++ version works 100% of the time, so the C# should work. If it doesn't then C# does something different to C++, and I don't know why.

Link1J avatar Aug 02 '22 11:08 Link1J

@Lightczx I don't know if the C# version works. The C++ version works 100% of the time, so the C# should work. If it doesn't then C# does something different to C++, and I don't know why.

My dialog isn't created by code, and it's initialized by XAML parser. I think it's the only difference.

Lightczx avatar Aug 02 '22 11:08 Lightczx

Ran into this today as well. A modal dialog is supposed to prevent any other input, but due to this issue the main menu is still clickable: image

The "fix" also does not seem to work in version 1.2.221209.1 on Windows 10. I am unable to find any "SmokeLayerBackground" in the visual tree.

rick-palmsens avatar Jan 10 '23 12:01 rick-palmsens

I too am still running into this. Windows App SDK 1.2.230217.4 for Windows 10.0.19041.0 on .NET 6.

image

Fixed using the approach mentioned above.

dialog.Loaded += OnLoaded;

void OnLoaded(object sender, RoutedEventArgs e)
{
    // Set the Theme

    dialog.RequestedTheme = ThemeHelper.ActualTheme;

    // Fix the smoke layer margin at the title bar
    
    DependencyObject popupRoot = VisualTreeHelper.GetParent(dialog);

    Rectangle smokeLayer = (Rectangle) VisualTreeHelper.GetChild(popupRoot, 0);
    smokeLayer.Margin = new Thickness(0);
    smokeLayer.RegisterPropertyChangedCallback(
        FrameworkElement.MarginProperty,
        (ss, dp) =>
        {
            if (dp == FrameworkElement.MarginProperty)
            {
                ss.ClearValue(dp);
            }
        });
}

// Show dialog

ContentDialogResult result = await dialog.ShowAsync();

EricMHeumann avatar Mar 07 '23 16:03 EricMHeumann

A note for anyone coming across this problem still. The AppWindow titlebar APIs in the Windows App SDK itself to not have this problem. This does mean you need to use version 1.2 or newer of Windows App SDK if you need Windows 10 support. But it is unlikely to break or be problematic, unlike the current fix of looking thru the XAML tree for the smoke effect and patching it.

And as a bonus AppWindow's version of the caption buttons match how Win32 and UWP draw them. I don't know how the XAML team messed up the caption buttons.

Link1J avatar Mar 07 '23 18:03 Link1J

I too am still running into this. Windows App SDK 1.2.230217.4 for Windows 10.0.19041.0 on .NET 6.

image

Fixed using the approach mentioned above.

dialog.Loaded += OnLoaded;

void OnLoaded(object sender, RoutedEventArgs e)
{
    // Set the Theme

    dialog.RequestedTheme = ThemeHelper.ActualTheme;

    // Fix the smoke layer margin at the title bar
    
    DependencyObject popupRoot = VisualTreeHelper.GetParent(dialog);

    Rectangle smokeLayer = (Rectangle) VisualTreeHelper.GetChild(popupRoot, 0);
    smokeLayer.Margin = new Thickness(0);
    smokeLayer.RegisterPropertyChangedCallback(
        FrameworkElement.MarginProperty,
        (ss, dp) =>
        {
            if (dp == FrameworkElement.MarginProperty)
            {
                ss.ClearValue(dp);
            }
        });
}

// Show dialog

ContentDialogResult result = await dialog.ShowAsync();

Hi, it's not related to this issue, but how do you make your "Back" arrow appear in the title bar? I know that I should change margin of the frame but how?

Pietro228 avatar Sep 02 '23 22:09 Pietro228

how do you make your "Back" arrow appear in the title bar? I know that I should change margin of the frame but how?

Hi, @Pietro228 I'm assuming you have already set ExtendContentIntoTitleBar to true, place this peice of code in any Resources where can be applied to your NavigationView

<Thickness x:Key="NavigationViewContentMargin">0,44,0,0</Thickness>

The top margin should be consisent with you visual title bar height

Lightczx avatar Sep 03 '23 01:09 Lightczx

it is fixed now with winappsdk 1.4 release. I overhauled the custom titlebar implementation and implemented so that content dialog works naturally with it. (no top offset)

pratikone avatar Oct 05 '23 19:10 pratikone