microsoft / WindowsAppSDK

The Windows App SDK empowers all Windows desktop apps with modern Windows UI, APIs, and platform features, including back-compat support, shipped via NuGet.
https://docs.microsoft.com/windows/apps/windows-app-sdk/
MIT License
3.81k stars 321 forks source link

Tabs in titlebar #1626

Closed jarno9981 closed 1 year ago

jarno9981 commented 2 years ago

Please add possibilty to for example add the tabs in to title bar (titlebar extended just like uwp)

ParkhutRoman commented 2 years ago

titlebar extended just like uwp

https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.xaml.window.extendscontentintotitlebar?view=winui-3.0-preview

beeradmoore commented 2 years ago

@ParkhutRoman AFAIK that is still broken for WinUI3.

ParkhutRoman commented 2 years ago

@beeradmoore its only have restrictions that did not exist before, like-

Don’t set the background property on a UIElement being used as a custom title bar. The min/max/close buttons will be hidden as the background color gets drawn over them.

The custom title bar works best when it is the top-most child of the parent container of your app. Deep nesting the UIElement within the XAML tree might cause unpredictable layout behaviors.

https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.xaml.window.settitlebar?view=winui-3.0

ParkhutRoman commented 2 years ago

For example if we dont need title bar background color, like all new WinUi windows, we can add to our own ResourceDictionary edited WindowChromeStyle

((1.0.0-preview2\lib\net5.0-windows10.0.18362.0) difference only in added Canvas.ZIndex and removed TitleBarMinMaxCloseContainer.Background)

<Style x:Key="WindowChromeStyle" TargetType="ContentControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ContentControl">
                    <Grid x:Name="LayoutRoot">
                        <Grid
                            x:Name="TitleBarMinMaxCloseContainer"
                            HorizontalAlignment="Right"
                            VerticalAlignment="Top"
                            Canvas.ZIndex="1"
                            Visibility="{Binding CaptionVisibility, RelativeSource={RelativeSource TemplatedParent}}">
                            <Grid
                                x:Name="MinMaxCloseContainer"
                                HorizontalAlignment="Right"
                                VerticalAlignment="Top"
                                ColumnDefinitions="Auto,Auto,Auto">
                                <Button
                                    x:Name="MinimizeButton"
                                    Grid.Column="0"
                                    AutomationProperties.AutomationId="_MinimizeButton"
                                    AutomationProperties.Name="Minimize"
                                    Content="M 0 0 H 10"
                                    Style="{StaticResource WindowCaptionButton}" />

                                <Button
                                    x:Name="MaximizeButton"
                                    Grid.Column="1"
                                    AutomationProperties.AutomationId="_MaximizeButton"
                                    AutomationProperties.Name="Maximize"
                                    Content="M 1.516 -0.001 L 7.451 0.009 C 8.751 0.019 9 1 8.981 1.477 L 9.002 7.558 M 9.002 7.547 C 8.929 8.669 8 9 7.43 9.015 L 1.464 9.005 C 0.374 8.973 0 8 -0.004 7.484 L -0.004 1.477 C 0 1 0.415 0.009 1.527 -0.001"
                                    Style="{StaticResource WindowCaptionButton}" />
                                <Button
                                    x:Name="CloseButton"
                                    Grid.Column="2"
                                    AutomationProperties.AutomationId="_CloseButton"
                                    AutomationProperties.Name="Close"
                                    Content="M 0 0 L 10 10 M 10 0 L 0 10"
                                    Style="{StaticResource WindowCaptionButton}">
                                    <Button.Resources>
                                        <ResourceDictionary>
                                            <ResourceDictionary.ThemeDictionaries>
                                                <ResourceDictionary x:Key="Light">
                                                    <StaticResource x:Key="WindowCaptionButtonBackgroundPointerOver" ResourceKey="CloseButtonBackgroundPointerOver" />
                                                    <StaticResource x:Key="WindowCaptionButtonBackgroundPressed" ResourceKey="CloseButtonBackgroundPressed" />
                                                    <StaticResource x:Key="WindowCaptionButtonStrokePointerOver" ResourceKey="CloseButtonStrokePointerOver" />
                                                    <StaticResource x:Key="WindowCaptionButtonStrokePressed" ResourceKey="CloseButtonStrokePressed" />
                                                </ResourceDictionary>
                                                <ResourceDictionary x:Key="Dark">
                                                    <StaticResource x:Key="WindowCaptionButtonBackgroundPointerOver" ResourceKey="CloseButtonBackgroundPointerOver" />
                                                    <StaticResource x:Key="WindowCaptionButtonBackgroundPressed" ResourceKey="CloseButtonBackgroundPressed" />
                                                    <StaticResource x:Key="WindowCaptionButtonStrokePointerOver" ResourceKey="CloseButtonStrokePointerOver" />
                                                    <StaticResource x:Key="WindowCaptionButtonStrokePressed" ResourceKey="CloseButtonStrokePressed" />
                                                </ResourceDictionary>
                                                <ResourceDictionary x:Key="HighContrast">
                                                    <StaticResource x:Key="WindowCaptionButtonBackgroundPointerOver" ResourceKey="CloseButtonBackgroundPointerOver" />
                                                    <StaticResource x:Key="WindowCaptionButtonBackgroundPressed" ResourceKey="CloseButtonBackgroundPressed" />
                                                    <StaticResource x:Key="WindowCaptionButtonStrokePointerOver" ResourceKey="CloseButtonStrokePointerOver" />
                                                    <StaticResource x:Key="WindowCaptionButtonStrokePressed" ResourceKey="CloseButtonStrokePressed" />
                                                </ResourceDictionary>
                                            </ResourceDictionary.ThemeDictionaries>
                                        </ResourceDictionary>
                                    </Button.Resources>
                                </Button>
                            </Grid>
                        </Grid>
                        <ContentPresenter
                            x:Name="ClientAreaPresenter"
                            Canvas.ZIndex="0"
                            Content="{TemplateBinding Content}"
                            ContentTemplate="{TemplateBinding ContentTemplate}"
                            ContentTransitions="{TemplateBinding ContentTransitions}"
                            Foreground="{TemplateBinding Foreground}" />
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal">
                                    <VisualState.Setters>

                                        <Setter Target="MinimizeButton.Foreground" Value="{ThemeResource WindowCaptionForeground}" />
                                        <Setter Target="MinimizeButton.Background" Value="{ThemeResource WindowCaptionButtonBackground}" />
                                        <Setter Target="MaximizeButton.Foreground" Value="{ThemeResource WindowCaptionForeground}" />
                                        <Setter Target="MaximizeButton.Background" Value="{ThemeResource WindowCaptionButtonBackground}" />
                                        <Setter Target="CloseButton.Foreground" Value="{ThemeResource WindowCaptionForeground}" />
                                        <Setter Target="CloseButton.Background" Value="{ThemeResource WindowCaptionButtonBackground}" />
                                    </VisualState.Setters>
                                </VisualState>
                                <VisualState x:Name="WindowInactive">
                                    <VisualState.Setters>

                                        <Setter Target="MinimizeButton.Foreground" Value="{ThemeResource WindowCaptionForegroundDisabled}" />
                                        <Setter Target="MinimizeButton.Background" Value="{ThemeResource WindowCaptionButtonBackground}" />
                                        <Setter Target="MaximizeButton.Foreground" Value="{ThemeResource WindowCaptionForegroundDisabled}" />
                                        <Setter Target="MaximizeButton.Background" Value="{ThemeResource WindowCaptionButtonBackground}" />
                                        <Setter Target="CloseButton.Foreground" Value="{ThemeResource WindowCaptionForegroundDisabled}" />
                                        <Setter Target="CloseButton.Background" Value="{ThemeResource WindowCaptionButtonBackground}" />
                                    </VisualState.Setters>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

And in page

<Page ...>
...
    <Grid>
        <Border
            HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch"
            Background="{ThemeResource SystemChromeMediumColor}"
            Canvas.ZIndex="0"
            IsHitTestVisible="False" />

        <winui:NavigationView
...
            x:Name="navigationView"
            Padding="32"
            HorizontalContentAlignment="Stretch"
            VerticalContentAlignment="Stretch"
            IsSettingsVisible="True"
            IsTitleBarAutoPaddingEnabled="True"
            PaneDisplayMode="Left" >

            <winui:NavigationView.ContentOverlay>
                <Grid
                    x:Name="appTitleBar"
                    Height="44"
                    Margin="46,0,0,0"
                    Padding="0"
                    VerticalAlignment="Top"
                    Canvas.ZIndex="3"
                    IsHitTestVisible="False">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <Image
                        x:Name="AppFontIcon"
                        Width="16"
                        Height="16"
                        HorizontalAlignment="Left"
                        VerticalAlignment="Center"
                        Source="/Images/Square44x44Logo.png" />
                    <TextBlock
                        x:Name="AppTitle"
                        Grid.Column="1"
                        Margin="12,0,0,0"
                        VerticalAlignment="Center"
                        Style="{StaticResource CaptionTextBlockStyle}"
                        Text="..." />
                </Grid>
            </winui:NavigationView.ContentOverlay>
...
            <Grid
                x:Name="grid"
                HorizontalAlignment="Stretch"
                VerticalAlignment="Stretch"
                Canvas.ZIndex="0">

                <Frame x:Name="shellFrame" />

            </Grid>
        </winui:NavigationView>
    </Grid>
</Page>

Dont forget ExtendsContentIntoTitleBar = true and SetTitleBar();

ParkhutRoman commented 2 years ago

And if app have ThemeSelector, and you need get rid of wrong colors in WindowCaptionButtons, we can duct tape WindowChromeStyle befor we get normal api.

<Grid x:Name="LayoutRoot" RequestedTheme="{Binding ElementName=ClientAreaPresenter, Path=Content.RequestedTheme}">

var window = App.ActivationService.GetWindow() as MainWindow;
            window.DispatcherQueue.TryEnqueue(() => 
            {
                if (window.Content is ShellPage page)
                {
                    page.RequestedTheme = Theme;
                    window.ExtendsContentIntoTitleBar = true;
                    window.SetTitleBar(page.AppTitleBar);
                }
            });
martibravo commented 2 years ago

I recommend you use the new Windowing APIs (Microsoft.UI.Windowing) to do all of these things. To extend client area to the titlebar, use AppWindow.Titlebar.ExtendsContentIntoTitlebar, and AppWindow.Titlebar.Button... properties to set the colors of the titlebar. Also, AppWindow.Titlebar.SetDragRectangles to define the area(s) where the user can drag the app.

martibravo commented 2 years ago

The AppWindow object for the current Window can be obtained by using WindowNative and Win32Interop APIs. Sample in C# below:

      var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
      WindowId myWndId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);
     AppWindow apw = AppWindow.GetFromWindowId(myWndId);

More info in the Microsoft Documentation for the Windows App SDK

martibravo commented 2 years ago

Although I think AppWindow should be easier to obtain. The Window class should have a GetAppWindow method of some sort.

ParkhutRoman commented 2 years ago

@martibravo it's hot mess with TitleBar api. Anyway you need app Microsoft.UI.Xaml.Window to set custom TitleBar, and with switching between dark and light in app we just need api for access RequestedTheme of apps root control like <Grid x:Name="LayoutRoot"> or even deeper, because now we only can change Microsoft.UI.Xaml.Window RequestedTheme, while WindowChromeStyle will have system's theme, which will set the same RequestedTheme to Titlebar.Button, and we will have hot mess where apps Titlebar.Buttons Foreground have almost the same Titlebar-App Background. My duct tape metod only for this case. If app have static colors and no need for custom TitleBar its ok to use AppWindow.TitleBar to set and forget ab it, because you should not use this api with custom TitleBar from Microsoft.UI.Xaml.Window.SetTitleBar. If im correct when we use Microsoft.UI.Xaml.Window.ExtendsContentIntoTitleBar we geting WindowChromeStyle buttons (you can see system TitleBar buttons when resizing window relay fast). And when you will use AppWindow.TitleBar.ExtendsContentIntoTitleBar window will shrink/hide systems TitleBar and only have buttons left. Screenshot 2021-10-23 144854 Screenshot 2021-10-23 145118 For me editing WindowChromeStyle is more useful and less painful then having systems TitleBar buttons even with Windows 11 feachers

ParkhutRoman commented 2 years ago

I know that this is not the place to submit it, but I can't unsee this from now on - explorer.exe Windows 11 22483.1000 Screenshot 2021-10-23

martibravo commented 2 years ago

I would like to point out that SetTitlebar is kind of buggy and setting the "Drag" rectangles is better. Calculate them on launch and update them when resizing. It works perfect for me!

beeradmoore commented 2 years ago

@ParkhutRoman , from what I had which was broken turns out it was missing the giant code snippet which was your WindowChromeStyle. From what I understand of what it's doing, its re-drawing the min/max/close buttons from scratch?

The first docs you link says

Extending app content into the title bar does not affect the window buttons (Minimize, Maximize, and Close).

I originally followed the doc but found that my min/max/close buttons disappeared. After some searching I found people who said the same thing and it appeared to be a shortcoming of WinUI3 (also correct me if I am wrong but WinUI3 = WindowsAppSDK?)

Removing WindowChromeStyle, and then setting background to transparent to all views made the original min/max/close buttons appear but non functional. Setting my root grid to have IsHitTestVisible="False" made them functional but broke everything else.

Is there no way to use the OS native min/max/close buttons? Or is by what you are saying with ((1.0.0-preview2\lib\net5.0-windows10.0.18362.0) is that native min/max/close buttons don't exist and are dependent on the framework version? (I am using WinUI3 0.8.5 not 1.0 preview)

ParkhutRoman commented 2 years ago

@beeradmoore as

The first docs you link says

if you dont edit WindowChromeStyle you need

Don’t set the background property on a UIElement being used as a custom title bar. The min/max/close buttons will be hidden as the background color gets drawn over them.

The custom title bar works best when it is the top-most child of the parent container of your app. Deep nesting the UIElement within the XAML tree might cause unpredictable layout behaviors.

idk its poor wording or not finished part of api, you can see in original WindowChromeStyle TitleBarMinMaxCloseContainer is under ClientAreaPresenter, and 2 line

The custom title bar works best when it is the top-most

will not help because in upper right corner where min/max/close buttons you need to dont have anything, even if custom title bar its top-most child of the parent container, for example if root grid of main page have drawn Background it will be ln front of TitleBarMinMaxCloseContainer, but b of nature min/max/close buttons we can see them. We can click through simple controls like grid when they dont have background(!=transparent), or have IsHitTestVisible="False". There are 2 ways out of this

  1. Dont have any control in upper right corner with Background or they need to have IsHitTestVisible="False", or just set Margin for controls for not go ower it.
  2. Edit WindowChromeStyle (for 0.8.4 .nuget\packages\microsoft.projectreunion.winui\0.8.4\lib\net5.0-windows10.0.18362.0\Microsoft.WinUI\Themes)

p.s. its more like - Don’t set the background property on and under a UIElement being used as a custom title bar.

ParkhutRoman commented 2 years ago

@beeradmoore

Is there no way to use the OS native min/max/close buttons? Or is by what you are saying with ((1.0.0-preview2\lib\net5.0-windows10.0.18362.0) is that native min/max/close buttons don't exist and are dependent on the framework version? (I am using WinUI3 0.8.5 not 1.0 preview)

dont use

window.ExtendsContentIntoTitleBar = true;
window.SetTitleBar(page.AppTitleBar);

and use as @martibravo says

I recommend you use the new Windowing APIs (Microsoft.UI.Windowing) to do all of these things. To extend client area to the titlebar, use AppWindow.Titlebar.ExtendsContentIntoTitlebar, and AppWindow.Titlebar.Button... properties to set the colors of the titlebar. Also, AppWindow.Titlebar.SetDragRectangles to define the area(s) where the user can drag the app.

  public static class AppWindowExtensions
    {
        public static AppWindow GetAppWindow(this Microsoft.UI.Xaml.Window window)
        {
            IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(window);
            return GetAppWindowFromWindowHandle(windowHandle);
        }
        private static AppWindow GetAppWindowFromWindowHandle(IntPtr windowHandle)
        {
            WindowId windowId;
            windowId = Win32Interop.GetWindowIdFromWindow(windowHandle);
            return AppWindow.GetFromWindowId(windowId);
        }
    }

var appWindow = AppWindowExtensions.GetAppWindow(window);

WinUI3 = WindowsAppSDK?

yes

beeradmoore commented 2 years ago

Thanks for the feedback!

I will dig into these further soon. I do know when I was playing with things mentioned earlier Win32Interop couldn't be found nor could I find if it's meant to be in any particular nuget. It seemed odd that so many examples were using it but VS was like 🤷‍♂️ so not entirely sure what is going on there.

I was confused where themes were coming from because I was searching this repo. I'll try nuget cache.

In my sample app I set every view to have background transparent but I still was not able to click through. It was only when setting IsHitTestVisible that I was able to interact with them.

I also noticed something I haven't noticed on windows before when looking around at different windows and their min/max/close sizing. A lot for windows reduce the height of these when you are maximised, but also a lot don't. I think now what has been seen cannot be unseen and it's going to haunt me every time I maximise a window 😂

ParkhutRoman commented 2 years ago

have background transparent but I still was not able to click through

transparent != dont have background. Just dont set it up. Imagine Grid as normal window in house, if you set background="transparent" its like putting clear glass into windows frame, after this you cant climb through the window.

lukeblevins commented 2 years ago

@martibravo Can you share how this can be accomplished? I'm familiar with the drag rectangles API, but setting a drag rectangle seems to break something and now only a small region isn't draggable at the top left of my main AppWindow.

Maybe someone from the team can share the limitations on allowed values for this early preview?

beeradmoore commented 2 years ago

I thought I commented on this last night but I don't see it here anymore.

====

I was 90% confident that this wouldn't work and I had stumbled upon a bug with WinUI because I thought I had attempted this in one of my many iterations. But sure enough, no background works. People may also need to make sure whatever element is in the top isn't set to fill the width or it may cover the buttons.

Attached a repro of it working in case someone comes along and doens't understand the above they can download this and give it a try. CustomTitlebarTest.zip

I tried to use AppWindowExtensions class above but couldn't find Win32Interop. Is that something in the WindowsAppSDK 1.0 experimental/preview?

====

Once again, thanks for the fast feedback on something that is now partially off topic. With your help I was able to throw this fix into DLSS Swapper. FCdlcwJX0AkOAf6

martibravo commented 2 years ago

@martibravo Can you share how this can be accomplished? I'm familiar with the drag rectangles API, but setting a drag rectangle seems to break something and now only a small region isn't draggable at the top left of my main AppWindow.

Maybe someone from the team can share the limitations on allowed values for this early preview?

All I did was get the AppWindow.Titlebar Height, Left Inset and Right Insert (for RTL language compatibility) properties and get the ActualHeight and ActualWidth of the clickable elements in the titlebar, and then calculate the drag rectangle/rectangles needed between the elements.

Say you have the following titlebar and need to enable the drag rectangles (marked with a red outline).

titlebar sample

EDIT: the caption buttons are like 20-30px tall, not 40. The text in the picture is wrong. Sorry!

How do you do it?

Get the Left and Right Inset properties from the titlebar, Get the ActualWidth and ActualHeight of the titlebar elements you don't want to be used as drag area.

Create an array of the type RectInt32[], calculate the drag rectangles and add them to the array. Pass them to AppWindowTitlebar.SetDragRectangles(array); To calculate the drag rectangles, simply start from the left and keep going. For example, the first drag rectangle is represented by a RectInt32 with the properties { width = SearchBox.ActualOffset.X - FirstButton.ActualWidth, height = 40, x = 0, y = FirstButton.ActualWidth }

This is the method I use in my Music app and it works great. image

WelterDevelopment commented 2 years ago

@martibravo Thank you for sharing this! Does your solution work in WinAppSDK 1.0? Are your interactive elements actually clickable in their whole height? When I use AppWindow.TitleBar.ExtendsContentIntoTitleBar = true together with SetDragRectangles(), the DragRectangle(s) are getting successfully set but a non-draggable TitleBar region (~22px high) still remains. Within this region, nothing is clickable. If your example really works, please provide a code snippet. Her is mine:

<Grid>
  <TabView>
      <TabView.TabStripFooter>
          <Grid x:Name="CustomDragRegion" Background="Red" IsHitTestVisible="False" SizeChanged="CustomDragRegion_SizeChanged" />
      </TabView.TabStripFooter>
      <TabView.TabItems>
          <TabViewItem Header="TabItem1">
              <TextBlock Text="Item 1" />
          </TabViewItem>
          <TabViewItem Header="TabItem2"></TabViewItem>
          <TabViewItem Header="TabItem3"></TabViewItem>
          <TabViewItem Header="TabItem4"></TabViewItem>
      </TabView.TabItems>
  </TabView>
  <ToggleButton VerticalAlignment="Top" IsChecked="False" Content="This Button cannot be clicked beause it is overlapped by a ~22px high invisible title bar" ></ToggleButton>
</Grid>
public sealed partial class MainWindow : Window
    {
        public AppWindow AW { get; set; }
        public bool IsCustomizationSupported { get; set; } = false;

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

        public MainWindow()
        {
            this.InitializeComponent();

            IsCustomizationSupported = AppWindowTitleBar.IsCustomizationSupported();

            if (IsCustomizationSupported)
            {
                AW = GetAppWindowForCurrentWindow();
                AW.TitleBar.ExtendsContentIntoTitleBar = true;
                AW.Title = "App title goes here";
            }
        }

        private void CustomDragRegion_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (IsCustomizationSupported && AW != null)
            {
                int x = AW.Size.Width - (int)e.NewSize.Width;
                int y = 0;
                int width = (int)e.NewSize.Width;
                int height = (int)e.NewSize.Height;

                AW.TitleBar.SetDragRectangles(new RectInt32[] { new RectInt32(x, y, width, height) });
            }
        }
    }

Neither the TabViewItems nor the ToggleButton are clickable. So I guess AppWindow.TitleBar.ExtendsContentIntoTitleBar is just as broken as Window.ExtendsContentIntoTitleBar?

beeradmoore commented 2 years ago

Now that WindowsAppSDK v1.0 stable has been released I decided to look into using AppWindow stuff again. All sorts of crashes using it. I need to repro on my other computer (Win11) but I wonder if its because IsCustomizationSupported is always returning false here. And it's doing that because the docs say:

In the current release, title bars can be customized only on Windows 11. Use this property to determine whether the title bar can be customized.

If so that is a little annoying that we have to do things very differently in Win10 vs Win11.

EDIT: The crashing was from using CoreApplication.GetCurrentView() and ApplicationView.GetForCurrentView() which both generate System.Runtime.InteropServices.COMException: 'Element not found. (0x80070490)'. My understanding is don't use these because we are not using UWP.

AppWindowTitleBar.IsCustomizationSupported() does indeed return true on Win11 and

var appWindow = GetAppWindowForCurrentWindow();
appWindow.TitleBar.ExtendsContentIntoTitleBar = true;

no longer is a null reference exception (because TitleBar is null on Win10).

Unlike @WelterDevelopment above I was able to use

var appWindow = GetAppWindowForCurrentWindow();
appWindow.TitleBar.SetDragRectangles(new Windows.Graphics.RectInt32[]
{
    new Windows.Graphics.RectInt32(0, 0, 400, 100),
});

and have a window which is dragable while also having a button which is outside this rectangle being clickable.

Using the window.ExtendsContentIntoTitleBar = true; window.SetTitleBar(myAppTitleBar); method also prevents Win11 features like snap group flyout from working.

So it seems the only way to us this is either to

WelterDevelopment commented 2 years ago

I finally found the issue in my code: it is actually a bug. Setting the Rectangle too early, when the UI is not yet fully constructed, will hard-code the non-draggable and non-interactive "titlebar ghost" that I described. The draggable Rectangle will get updated on every CustomDragRegion_SizeChanged event but the "ghost" will stay forever. Adding the event handler at a later stage, e.g. when the TabView got initialized, does the trick:

<TabView Loaded="TabView_Loaded" TabCloseRequested="TabView_TabCloseRequested">
private void TabView_Loaded(object sender, RoutedEventArgs e)
{
    CustomDragRegion.SizeChanged += CustomDragRegion_SizeChanged;
}

private void TabView_TabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args)
{
    sender.TabItems.Remove(args.Item);
}

Then, changes to the Window size and and closing tabs will properly recalculate the draggable region and the interactive elements stay clickable.

WelterDevelopment commented 2 years ago

@beeradmoore You are right about the 3 possibilities. I think number 2 is the way to go. I summarized everything I learned about AppWindowTitlebar and Window.SetTitleBar() into this example: https://github.com/WelterDevelopment/WinUI3TitleBar. Here, The UI in W10 looks roughly the same as in W11, where in W11 you have the new features (namely Snap Layouts and the custom drag rectangles).

WelterDevelopment commented 2 years ago

Follow-Up (sorry for the spam but it's quite important to know): TitleBar.SetDragRectangles() seems to use raw pixels instead of view pixels. Why this is the case is beyond me; I just figured this out by accident because I use 125% scaling at my laptop and I wondered why my DragRectangles are shifted and distorted. So if you want your app to support the system's dpi scale factor (e.g. 125%) you need to calculate the view pixels yourself with XamlRoot.RasterizationScale (whis is e.g. 1.25):

private void CustomDragRegion_SizeChanged(object sender, SizeChangedEventArgs e)
{
  int width = (int)(RootGrid.XamlRoot.RasterizationScale * e.NewSize.Width);
  int height = (int)(RootGrid.XamlRoot.RasterizationScale * e.NewSize.Height);
  int x = (int)(RootGrid.XamlRoot.RasterizationScale * InteractiveElement.ActualWidth);
  int y = 0;

  AW.TitleBar.SetDragRectangles(new RectInt32[] { new RectInt32(x, y, width, height) });
}

Dragging the app to a second display with another scale factor (e.g. 100%) will conveniently also trigger your SizeChanged event(s), so the DragRectangle(s) are properly set according to the new XamlRoot.RasterizationScale (1.00).

adamplonka commented 2 years ago

For unpackaged apps the AppWindow.GetFromWindowId(windowId) throws the following exception after re-running the application:

System.Reflection.TargetInvocationException: „Exception has been thrown by the target of an invocation.”

SEHException: External component has thrown an exception

I found in another thread that the following must be added to the app.manifest file under <assembly>

    <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <application>
            <!-- Windows 10 and Windows 11 -->
            <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
        </application>
    </compatibility>

After adding this and recompiling the project it works flawlessly. It also fixes window dragging when using window.SetTitleBar()

simon-knuth commented 2 years ago

Even with @WelterDevelopment's trick I'm unable to get rid of the ghost rectangles. They continue to accumulate as my app rescales when the DPI changes or the drag rectangles are replaced. The only way I could get this working is by calling AppWindowTitleBar.ResetToDefault, which does get rid of every single rectangle but causes severe visual glitches due to AppWindowTitleBar.ExtendsContentIntoTitleBar being set to false for a split second there.

jarno9981 commented 2 years ago

I finally found the issue in my code: it is actually a bug. Setting the Rectangle too early, when the UI is not yet fully constructed, will hard-code the non-draggable and non-interactive "titlebar ghost" that I described. The draggable Rectangle will get updated on every CustomDragRegion_SizeChanged event but the "ghost" will stay forever. Adding the event handler at a later stage, e.g. when the TabView got initialized, does the trick:

<TabView Loaded="TabView_Loaded" TabCloseRequested="TabView_TabCloseRequested">
private void TabView_Loaded(object sender, RoutedEventArgs e)
{
  CustomDragRegion.SizeChanged += CustomDragRegion_SizeChanged;
}

private void TabView_TabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args)
{
  sender.TabItems.Remove(args.Item);
}

Then, changes to the Window size and and closing tabs will properly recalculate the draggable region and the interactive elements stay clickable.

Can you supply the code for the windows app sdk tabs in titlebar where the buttons work

Krakenus00 commented 1 year ago

@jarno9981

My answer is probably too late, but it could help anyone else who is still fighting with it.

I got things working using the answers in this thread and a bit of ChatGPT magic. The SetTitleBar() approach didn't work for me: the controls on my custom title bar were not responding to mouse events. AppWindow, however, works just fine. I also managed to fix the incorrect background colours of the caption buttons.

I used C++/WinRT in my project. It will add some variety to this thread. It is not that hard to rewrite the code in C# anyway. Also, I used Windows 11 SDK 10.0.22621.0 and WindowsAppSDK 1.2.230217.4. If my solution does not work for you, try to update these components.

So here is what I've done:

XAML layout:

<Grid>
    <TabView x:Name="TitleBar"
             Loaded="OnTitleBarLoaded">
        <TabView.TabStripFooter>
            <Grid x:Name="CustomDragRegion"
                  IsHitTestVisible="False" />
        </TabView.TabStripFooter>
        <TabView.TabItems>
            <TabViewItem Header="TabItem1"
                         IsClosable="False">
                <TextBlock Text="Item 1"></TextBlock>
            </TabViewItem>
            <TabViewItem Header="TabItem2">
                <Button x:Name="myButton">Click Me</Button>
            </TabViewItem>
            <TabViewItem Header="TabItem3"></TabViewItem>
            <TabViewItem Header="TabItem4"></TabViewItem>
        </TabView.TabItems>
    </TabView>
</Grid>

I used the layout provided by @WelterDevelopment. Remember to set the Loaded event here to get rid of the "ghost" title bar that blocks your controls.

Header file:

struct MainWindow : MainWindowT<MainWindow>
{
    MainWindow();

private:
    void GetAppWindow();
    double GetScaleAdjustment();
    Windows::Foundation::IReference<Windows::UI::Color> GetColor(winrt::hstring const& brushName);

public: // Events
    void OnThemeChanged(Microsoft::UI::Xaml::FrameworkElement const& sender, Windows::Foundation::IInspectable const&);
    void OnTitleBarLoaded(Windows::Foundation::IInspectable const&, Microsoft::UI::Xaml::RoutedEventArgs const&);
    void OnTitleBarSizeChanged(Windows::Foundation::IInspectable const&, Microsoft::UI::Xaml::SizeChangedEventArgs const& args);

private:
    HWND handler{ 0 };
    Microsoft::UI::WindowId windowId{ 0 };
    Microsoft::UI::Windowing::AppWindow appWindow{ nullptr };
    Microsoft::UI::Windowing::AppWindowTitleBar titleBar{ nullptr };
    Microsoft::UI::Xaml::FrameworkElement rootElement{ nullptr };
    // Event revokers
    Microsoft::UI::Xaml::FrameworkElement::ActualThemeChanged_revoker themeChangedRevoker;
    Microsoft::UI::Xaml::Controls::Grid::Loaded_revoker titleBarLoadedRevoker;
    Microsoft::UI::Xaml::Controls::Grid::SizeChanged_revoker titleBarSizeChangedRevoker;
};

Source file:

MainWindow::MainWindow()
{
    InitializeComponent();
    GetAppWindow();

    if (!AppWindowTitleBar::IsCustomizationSupported())
    {
        // Why? Because I don't care
        throw std::runtime_error("Unsupported OS version.");
    }

    titleBar = appWindow.TitleBar();
    titleBar.ExtendsContentIntoTitleBar(true);
    IReference<Windows::UI::Color> btnColor(Colors::Transparent());
    // Make the background transparent so it matches my custom title bar
    titleBar.BackgroundColor(btnColor);
    titleBar.ButtonBackgroundColor(btnColor);
    titleBar.InactiveBackgroundColor(btnColor);
    titleBar.ButtonInactiveBackgroundColor(btnColor);

    // Application theme
    rootElement = this->Content().try_as<FrameworkElement>();
    if (rootElement)
    {
        // Initial state
        OnThemeChanged(rootElement, { nullptr });

        themeChangedRevoker = rootElement.ActualThemeChanged(auto_revoke, { this, &MainWindow::OnThemeChanged });
    }
}

void MainWindow::GetAppWindow()
{
    // There is no way to do it in WinUI, need to play with handlers.
    auto windowNative{ this->try_as<IWindowNative>() };
    check_bool(windowNative);
    windowNative->get_WindowHandle(&handler);
    windowId = GetWindowIdFromWindow(handler);
    appWindow = AppWindow::GetFromWindowId(windowId);
}

double MainWindow::GetScaleAdjustment()
{
    DisplayArea displayArea = DisplayArea::GetFromWindowId(windowId, DisplayAreaFallback::Primary);
    HMONITOR hMonitor = GetMonitorFromDisplayId(displayArea.DisplayId());
    // Get DPI
    UINT dpiX, dpiY;
    check_hresult(GetDpiForMonitor(hMonitor, MDT_DEFAULT, &dpiX, &dpiY));
    // Magic numbers from Microsoft. Have no idea what is going on.
    UINT scaleFactorPercent = (dpiX * 100 + 48) / 96;
    return scaleFactorPercent / 100.;
}

IReference<Windows::UI::Color> MainWindow::GetColor(winrt::hstring const& brushName)
{
    return Application::Current().Resources().Lookup(box_value(brushName)).as<SolidColorBrush>().Color();
}

void MainWindow::OnTitleBarLoaded(IInspectable const&, RoutedEventArgs const&)
{
    titleBarSizeChangedRevoker = CustomDragRegion().SizeChanged(auto_revoke, { this, &MainWindow::OnTitleBarSizeChanged });
}

void MainWindow::OnTitleBarSizeChanged(IInspectable const&, SizeChangedEventArgs const& args)
{
    Size newSize = args.NewSize();
    auto scale = GetScaleAdjustment();

    int x = (appWindow.Size().Width - newSize.Width) * scale;
    int y = 0;
    int width  = newSize.Width  * scale;
    int height = newSize.Height * scale;

    titleBar.SetDragRectangles({ { x, y, width, height } });
}

void MainWindow::OnThemeChanged(Microsoft::UI::Xaml::FrameworkElement const& sender, Windows::Foundation::IInspectable const&)
{
    // Update caption buttons
    titleBar.ButtonForegroundColor(GetColor(L"TextFillColorPrimaryBrush"));
    titleBar.ButtonHoverBackgroundColor(GetColor(L"SubtleFillColorSecondaryBrush"));
    titleBar.ButtonPressedBackgroundColor(GetColor(L"SubtleFillColorTertiaryBrush"));
}

The code is pretty much self-explanatory, so I don't know what to add here. I change the title bar drag region when the size of the footer changes and change the caption button colours when the theme changes. The neat part was getting colour resources in C++ because there is no documentation for C++/WinRT in WinUI 3. Keep in mind that SetDragRectangles() expects physical pixels, so you should make it DPI-aware. I also did not set the TabView::Closed event because it revokes when my window destroys anyway. Maybe I should've, but... Well, whatever.

Hope it will help someone.

jarno9981 commented 1 year ago

Here is a c# version of the code

using Microsoft.UI.Windowing;
using Microsoft.UI.Windowing.Interop;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using Windows.Foundation;
using Windows.Foundation.Metadata;
using Windows.UI;

public struct MainWindow : MainWindowT<MainWindow>
{
    public MainWindow()
    {
        InitializeComponent();
        GetAppWindow();

        if (!AppWindowTitleBar.IsCustomizationSupported())
        {
            // Why? Because I don't care
            throw new Exception("Unsupported OS version.");
        }

        titleBar = appWindow.TitleBar;
        titleBar.ExtendsContentIntoTitleBar = true;
        IReference<Color> btnColor = Colors.Transparent;
        // Make the background transparent so it matches my custom title bar
        titleBar.BackgroundColor = btnColor;
        titleBar.ButtonBackgroundColor = btnColor;
        titleBar.InactiveBackgroundColor = btnColor;
        titleBar.ButtonInactiveBackgroundColor = btnColor;

        // Application theme
        rootElement = Content as FrameworkElement;
        if (rootElement != null)
        {
            // Initial state
            OnThemeChanged(rootElement, null);

            themeChangedRevoker = rootElement.RegisterPropertyChangedCallback(FrameworkElement.ActualThemeProperty, OnThemeChanged);
        }
    }

    private void GetAppWindow()
    {
        // There is no way to do it in WinUI, need to play with handlers.
        IWindowNative windowNative = this.As<IWindowNative>();
        if (windowNative == null)
        {
            throw new NotSupportedException("IWindowNative not supported.");
        }

        IntPtr handler = windowNative.WindowHandle;
        windowId = GetWindowIdFromWindow(handler);
        appWindow = AppWindow.GetFromWindowId(windowId);
    }

    private double GetScaleAdjustment()
    {
        DisplayArea displayArea = DisplayArea.GetFromWindowId(windowId, DisplayAreaFallback.Primary);
        IntPtr hMonitor = GetMonitorFromDisplayId(displayArea.DisplayId);
        // Get DPI
        uint dpiX, dpiY;
        GetDpiForMonitor(hMonitor, MONITOR_DPI_TYPE.MDT_Default, out dpiX, out dpiY);
        // Magic numbers from Microsoft. Have no idea what is going on.
        uint scaleFactorPercent = (dpiX * 100 + 48) / 96;
        return scaleFactorPercent / 100.0;
    }

    private IReference<Color> GetColor(string brushName)
    {
        return Application.Current.Resources[brushName] is SolidColorBrush brush ? brush.Color : null;
    }

    public void OnTitleBarLoaded(object sender, RoutedEventArgs args)
    {
        titleBarSizeChangedRevoker = CustomDragRegion.SizeChanged += OnTitleBarSizeChanged;
    }

    public void OnTitleBarSizeChanged(object sender, SizeChangedEventArgs args)
    {
        Size newSize = args.NewSize;
        double scale = GetScaleAdjustment();

        int x = (int)((appWindow.Size.Width - newSize.Width) * scale);
        int y = 0;
        int width = (int)(newSize.Width * scale);
        int height = (int)(newSize.Height * scale);

        titleBar.SetDragRectangles(new Rect(x, y, width, height));
    }

    public void OnThemeChanged(DependencyObject sender, DependencyProperty dp)
    {
        // Update caption buttons
        titleBar.ButtonForegroundColor = GetColor("TextFillColorPrimaryBrush");
        titleBar.ButtonHoverBackgroundColor = GetColor("SubtleFillColorSecondaryBrush");
        titleBar.ButtonPressedBackgroundColor = GetColor("SubtleFillColorTertiaryBrush");
    }

    private HWND handler;
    private WindowId windowId;
    private AppWindow
jarno9981 commented 1 year ago

@jarno9981

My answer is probably too late, but it could help anyone else who is still fighting with it.

I got things working using the answers in this thread and a bit of ChatGPT magic. The SetTitleBar() approach didn't work for me: the controls on my custom title bar were not responding to mouse events. AppWindow, however, works just fine. I also managed to fix the incorrect background colours of the caption buttons.

I used C++/WinRT in my project. It will add some variety to this thread. It is not that hard to rewrite the code in C# anyway. Also, I used Windows 11 SDK 10.0.22621.0 and WindowsAppSDK 1.2.230217.4. If my solution does not work for you, try to update these components.

So here is what I've done:

XAML layout:

<Grid>
    <TabView x:Name="TitleBar"
             Loaded="OnTitleBarLoaded">
        <TabView.TabStripFooter>
            <Grid x:Name="CustomDragRegion"
                  IsHitTestVisible="False" />
        </TabView.TabStripFooter>
        <TabView.TabItems>
            <TabViewItem Header="TabItem1"
                         IsClosable="False">
                <TextBlock Text="Item 1"></TextBlock>
            </TabViewItem>
            <TabViewItem Header="TabItem2">
                <Button x:Name="myButton">Click Me</Button>
            </TabViewItem>
            <TabViewItem Header="TabItem3"></TabViewItem>
            <TabViewItem Header="TabItem4"></TabViewItem>
        </TabView.TabItems>
    </TabView>
</Grid>

I used the layout provided by @WelterDevelopment. Remember to set the Loaded event here to get rid of the "ghost" title bar that blocks your controls.

Header file:

struct MainWindow : MainWindowT<MainWindow>
{
    MainWindow();

private:
    void GetAppWindow();
    double GetScaleAdjustment();
    Windows::Foundation::IReference<Windows::UI::Color> GetColor(winrt::hstring const& brushName);

public: // Events
    void OnThemeChanged(Microsoft::UI::Xaml::FrameworkElement const& sender, Windows::Foundation::IInspectable const&);
    void OnTitleBarLoaded(Windows::Foundation::IInspectable const&, Microsoft::UI::Xaml::RoutedEventArgs const&);
    void OnTitleBarSizeChanged(Windows::Foundation::IInspectable const&, Microsoft::UI::Xaml::SizeChangedEventArgs const& args);

private:
    HWND handler{ 0 };
    Microsoft::UI::WindowId windowId{ 0 };
    Microsoft::UI::Windowing::AppWindow appWindow{ nullptr };
    Microsoft::UI::Windowing::AppWindowTitleBar titleBar{ nullptr };
    Microsoft::UI::Xaml::FrameworkElement rootElement{ nullptr };
    // Event revokers
    Microsoft::UI::Xaml::FrameworkElement::ActualThemeChanged_revoker themeChangedRevoker;
    Microsoft::UI::Xaml::Controls::Grid::Loaded_revoker titleBarLoadedRevoker;
    Microsoft::UI::Xaml::Controls::Grid::SizeChanged_revoker titleBarSizeChangedRevoker;
};

Source file:

MainWindow::MainWindow()
{
    InitializeComponent();
    GetAppWindow();

    if (!AppWindowTitleBar::IsCustomizationSupported())
    {
        // Why? Because I don't care
        throw std::runtime_error("Unsupported OS version.");
    }

    titleBar = appWindow.TitleBar();
    titleBar.ExtendsContentIntoTitleBar(true);
    IReference<Windows::UI::Color> btnColor(Colors::Transparent());
    // Make the background transparent so it matches my custom title bar
    titleBar.BackgroundColor(btnColor);
    titleBar.ButtonBackgroundColor(btnColor);
    titleBar.InactiveBackgroundColor(btnColor);
    titleBar.ButtonInactiveBackgroundColor(btnColor);

    // Application theme
    rootElement = this->Content().try_as<FrameworkElement>();
    if (rootElement)
    {
        // Initial state
        OnThemeChanged(rootElement, { nullptr });

        themeChangedRevoker = rootElement.ActualThemeChanged(auto_revoke, { this, &MainWindow::OnThemeChanged });
    }
}

void MainWindow::GetAppWindow()
{
    // There is no way to do it in WinUI, need to play with handlers.
    auto windowNative{ this->try_as<IWindowNative>() };
    check_bool(windowNative);
    windowNative->get_WindowHandle(&handler);
    windowId = GetWindowIdFromWindow(handler);
    appWindow = AppWindow::GetFromWindowId(windowId);
}

double MainWindow::GetScaleAdjustment()
{
    DisplayArea displayArea = DisplayArea::GetFromWindowId(windowId, DisplayAreaFallback::Primary);
    HMONITOR hMonitor = GetMonitorFromDisplayId(displayArea.DisplayId());
    // Get DPI
    UINT dpiX, dpiY;
    check_hresult(GetDpiForMonitor(hMonitor, MDT_DEFAULT, &dpiX, &dpiY));
    // Magic numbers from Microsoft. Have no idea what is going on.
    UINT scaleFactorPercent = (dpiX * 100 + 48) / 96;
    return scaleFactorPercent / 100.;
}

IReference<Windows::UI::Color> MainWindow::GetColor(winrt::hstring const& brushName)
{
    return Application::Current().Resources().Lookup(box_value(brushName)).as<SolidColorBrush>().Color();
}

void MainWindow::OnTitleBarLoaded(IInspectable const&, RoutedEventArgs const&)
{
    titleBarSizeChangedRevoker = CustomDragRegion().SizeChanged(auto_revoke, { this, &MainWindow::OnTitleBarSizeChanged });
}

void MainWindow::OnTitleBarSizeChanged(IInspectable const&, SizeChangedEventArgs const& args)
{
    Size newSize = args.NewSize();
    auto scale = GetScaleAdjustment();

    int x = (appWindow.Size().Width - newSize.Width) * scale;
    int y = 0;
    int width  = newSize.Width  * scale;
    int height = newSize.Height * scale;

    titleBar.SetDragRectangles({ { x, y, width, height } });
}

void MainWindow::OnThemeChanged(Microsoft::UI::Xaml::FrameworkElement const& sender, Windows::Foundation::IInspectable const&)
{
    // Update caption buttons
    titleBar.ButtonForegroundColor(GetColor(L"TextFillColorPrimaryBrush"));
    titleBar.ButtonHoverBackgroundColor(GetColor(L"SubtleFillColorSecondaryBrush"));
    titleBar.ButtonPressedBackgroundColor(GetColor(L"SubtleFillColorTertiaryBrush"));
}

The code is pretty much self-explanatory, so I don't know what to add here. I change the title bar drag region when the size of the footer changes and change the caption button colours when the theme changes. The neat part was getting colour resources in C++ because there is no documentation for C++/WinRT in WinUI 3. Keep in mind that SetDragRectangles() expects physical pixels, so you should make it DPI-aware. I also did not set the TabView::Closed event because it revokes when my window destroys anyway. Maybe I should've, but... Well, whatever.

Hope it will help someone.

Which package did you use nuget

Krakenus00 commented 1 year ago

Which package did you use nuget

I used a "Blank App, Packaged (WinUI 3 in Desktop)" project template in Visual Studio. It comes with some NuGet packages. Here is what I have:

Also, you can clone my solution, and check it yourself: https://github.com/Krakenus00/KrakenRenderer.

jarno9981 commented 1 year ago

@Krakenus00 can you provide c# example

Krakenus00 commented 1 year ago

@Krakenus00 can you provide c# example

Sorry, I don't have C# templates installed. I currently don't plan to mess with C# WinUI. The C++/WinRT code is very similar to C#. I actually wrote this all using C# examples. Here are the main differences:

  1. In C#, you have only one .cs file with both definitions and implementation, while in C++, it is .h for definitions and .cpp for implementation.
  2. In C#, you access sub-namespaces using . operator, and in C++ it is ::.
  3. In C#, you don't need revokers to work with events. Simply write Event += myFunction; to subscribe, or -= to unsubscribe.
  4. WinRT's IInspectable is basically C#'s object.
  5. Retrieving the AppWindow is a bit different in C#. Use the following code as a reference: Source: https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/windowing/windowing-overview

    // MainWindow.xaml.cs
    private void myButton_Click(object sender, RoutedEventArgs e)
    {
    // Retrieve the window handle (HWND) of the current (XAML) WinUI 3 window.
    var hWnd =
        WinRT.Interop.WindowNative.GetWindowHandle(this);
    
    // Retrieve the WindowId that corresponds to hWnd.
    Microsoft.UI.WindowId windowId =
        Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);
    
    // Lastly, retrieve the AppWindow for the current (XAML) WinUI 3 window.
    Microsoft.UI.Windowing.AppWindow appWindow =
        Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);
    
    if (appWindow != null)
    {
        // You now have an AppWindow object, and you can call its methods to manipulate the window.
        // As an example, let's change the title text of the window.
        appWindow.Title = "Title text updated via AppWindow!";
    }
    }
  6. To get DPI, you need to use system API, so it is a bit tricky in C#. Here is a code provided by Microsoft: Source: https://learn.microsoft.com/en-us/windows/apps/develop/title-bar?tabs=wasdk#interactive-content.
    
    [DllImport("Shcore.dll", SetLastError = true)]
    internal static extern int GetDpiForMonitor(IntPtr hmonitor, Monitor_DPI_Type dpiType, out uint dpiX, out uint dpiY);

internal enum Monitor_DPI_Type : int { MDT_Effective_DPI = 0, MDT_Angular_DPI = 1, MDT_Raw_DPI = 2, MDT_Default = MDT_Effective_DPI }

private double GetScaleAdjustment() { IntPtr hWnd = WindowNative.GetWindowHandle(this); WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd); DisplayArea displayArea = DisplayArea.GetFromWindowId(wndId, DisplayAreaFallback.Primary); IntPtr hMonitor = Win32Interop.GetMonitorFromDisplayId(displayArea.DisplayId);

// Get DPI.
int result = GetDpiForMonitor(hMonitor, Monitor_DPI_Type.MDT_Default, out uint dpiX, out uint _);
if (result != 0)
{
    throw new Exception("Could not get DPI for monitor.");
}

uint scaleFactorPercent = (uint)(((long)dpiX * 100 + (96 >> 1)) / 96);
return scaleFactorPercent / 100.0;

}


7. In C#, there are properties instead of getter/setter functions. For example, `titleBar.BackgroundColor(btnColor)` in C++ equals to `titleBar.BackgroundColor = btnColor` in C#.
8. In C#, you can access `ResourceDictionary`'s elements simply with `[]` operator. Also, you don't need to use wide strings (`L""`).
ghost1372 commented 1 year ago

@Krakenus00 can you provide c# example

I tried to create a c# example everything worked fine. Just that there is no place to drag windows.

Krakenus00 commented 1 year ago

there is no place to drag windows.

You need to call SetDragRectangles() to create draggable regions. In the code provided, I use TabView's footer as a draggable region.

ghost1372 commented 1 year ago

there is no place to drag windows.

You need to call SetDragRectangles() to create draggable regions. In the code provided, I use TabView's footer as a draggable region.

I didn't pay attention to this. Tnx

jarno9981 commented 1 year ago

@Krakenus00 i forget to add these Microsoft.Windows.CppWinRT 2.0.230225.1 / Microsoft.Windows.ImplementationLibrary 1.0.230202.1

Try it but did work but still not draggeble

ghost1372 commented 1 year ago

@jarno9981 @Krakenus00 i fixed issue with help of ChatGPT😁

this is a c# example that is working fine with draggable OnTitleBarSizeChanged codes is a little confusing and can be written better

private AppWindow appWindow;
        private AppWindowTitleBar titleBar;
        public MainWindow()
        {
            this.InitializeComponent();

            var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);

            WindowId windowId = Win32Interop.GetWindowIdFromWindow(hWnd);

            appWindow = AppWindow.GetFromWindowId(windowId);

            if (!AppWindowTitleBar.IsCustomizationSupported())
            {
                // Why? Because I don't care
                throw new Exception("Unsupported OS version.");
            }

            titleBar = appWindow.TitleBar;
            titleBar.ExtendsContentIntoTitleBar = true;
            var btnColor = Colors.Transparent;
            titleBar.BackgroundColor = btnColor;
            titleBar.ButtonBackgroundColor = btnColor;
            titleBar.InactiveBackgroundColor = btnColor;
            titleBar.ButtonInactiveBackgroundColor = btnColor;
        }

        public void OnTitleBarLoaded(object sender, RoutedEventArgs args)
        {
            CustomDragRegion.SizeChanged += OnTitleBarSizeChanged;
        }
        public void OnTitleBarSizeChanged(object sender, SizeChangedEventArgs args)
        {
           double scaleAdjustment = GetScaleAdjustment();

            CustomDragRegion.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            var customDragRegionPosition = CustomDragRegion.TransformToVisual(null).TransformPoint(new Point(0, 0));

            List<Windows.Graphics.RectInt32> dragRectsList = new();

            Windows.Graphics.RectInt32 dragRectL;
            dragRectL.X = (int)(customDragRegionPosition.X * scaleAdjustment);
            dragRectL.Y = (int)(customDragRegionPosition.Y * scaleAdjustment);
            dragRectL.Height = (int)((CustomDragRegion.ActualHeight - customDragRegionPosition.Y) * scaleAdjustment);
            dragRectL.Width = (int)((CustomDragRegion.ActualWidth / 2) * scaleAdjustment);
            dragRectsList.Add(dragRectL);

            Windows.Graphics.RectInt32 dragRectR;
            dragRectR.X = (int)((customDragRegionPosition.X + CustomDragRegion.ActualWidth / 2) * scaleAdjustment);
            dragRectR.Y = (int)(customDragRegionPosition.Y * scaleAdjustment);
            dragRectR.Height = (int)((CustomDragRegion.ActualHeight - customDragRegionPosition.Y) * scaleAdjustment);
            dragRectR.Width = (int)((CustomDragRegion.ActualWidth / 2) * scaleAdjustment);
            dragRectsList.Add(dragRectR);

            Windows.Graphics.RectInt32[] dragRects = dragRectsList.ToArray();

            appWindow.TitleBar.SetDragRectangles(dragRects);
        }

        [DllImport("Shcore.dll", SetLastError = true)]
        internal static extern int GetDpiForMonitor(IntPtr hmonitor, Monitor_DPI_Type dpiType, out uint dpiX, out uint dpiY);

        internal enum Monitor_DPI_Type : int
        {
            MDT_Effective_DPI = 0,
            MDT_Angular_DPI = 1,
            MDT_Raw_DPI = 2,
            MDT_Default = MDT_Effective_DPI
        }

        private double GetScaleAdjustment()
        {
            IntPtr hWnd = WindowNative.GetWindowHandle(this);
            WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd);
            DisplayArea displayArea = DisplayArea.GetFromWindowId(wndId, DisplayAreaFallback.Primary);
            IntPtr hMonitor = Win32Interop.GetMonitorFromDisplayId(displayArea.DisplayId);

            // Get DPI.
            int result = GetDpiForMonitor(hMonitor, Monitor_DPI_Type.MDT_Default, out uint dpiX, out uint _);
            if (result != 0)
            {
                throw new Exception("Could not get DPI for monitor.");
            }

            uint scaleFactorPercent = (uint)(((long)dpiX * 100 + (96 >> 1)) / 96);
            return scaleFactorPercent / 100.0;
        }
jarno9981 commented 1 year ago

@ghost1372 does clicking tab buttons work to

ghost1372 commented 1 year ago

@ghost1372 does clicking tab buttons work to

Yes

jarno9981 commented 1 year ago

@ghost1372 does clicking tab buttons work to

Yes

The example works super