From 73baa136e86d10832cae6c745ccaaf12ee574a2f Mon Sep 17 00:00:00 2001 From: Meik Date: Thu, 5 Mar 2026 17:04:40 +0100 Subject: [PATCH] fix(layout): enforce rounded window via native region clip --- Changelog.md | 2 +- MainWindow.xaml.cs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 55b93f2..364ad00 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,7 +10,7 @@ - Hauptlayout von festen `Window.ActualWidth`-Bindings entkoppelt, damit Header/Content nicht über die gerundete Innenfläche hinausragen. - Feste Hauptlayout-Höhen (Navigation/Content) auf die verfügbare Innenhöhe abgestimmt und Clipping im Dock-Bereich aktiviert, damit keine Inhalte in die Rundungsbereiche überlaufen. - Initialisierung des Rounded-Clips auf den finalen Layout-Zeitpunkt erweitert (Loaded/Render), damit die Rundungsbegrenzung stabil greift. -- MainWindow-Ecküberstände beseitigt: Navigation-Rail unten links an den Fensterradius angepasst, Content-Bereich im `DockPanel` als Fill-Element geführt, zusätzlicher Surface-Clip auf Radius `20` gesetzt, den gesamten `MainWindowContentRoot` als konstanten `1px`-Inset innerhalb des Rahmens geführt, den Body-Host per Geometrie mit zum Innenradius passenden gerundeten unteren Ecken begrenzt und zusätzlich eine On-Top-Corner-Maske (`Rectangle - RoundedRectangle`) ergänzt, die Überzeichnung außerhalb der Rundung zuverlässig abdeckt (WPF-Typen für Geometriepunkte/-größen explizit qualifiziert, um Mehrdeutigkeiten mit `System.Drawing` zu vermeiden). +- MainWindow-Ecküberstände beseitigt: Navigation-Rail unten links an den Fensterradius angepasst, Content-Bereich im `DockPanel` als Fill-Element geführt, zusätzlicher Surface-Clip auf Radius `20` gesetzt, den gesamten `MainWindowContentRoot` als konstanten `1px`-Inset innerhalb des Rahmens geführt, den Body-Host per Geometrie mit zum Innenradius passenden gerundeten unteren Ecken begrenzt, eine On-Top-Corner-Maske (`Rectangle - RoundedRectangle`) ergänzt und zusätzlich per Win32-Window-Region (`SetWindowRgn`) die Fensterkontur selbst auf Rundung begrenzt (WPF-Typen für Geometriepunkte/-größen explizit qualifiziert, um Mehrdeutigkeiten mit `System.Drawing` zu vermeiden). - Sichtbarkeit des Main-Contents wiederhergestellt: Navigations-/Content-Host auf feste Breitenaufteilung (`75 + 425`) umgestellt, damit das Inhaltspanel nicht mehr durch einen Zero-Width-Viewport abgeschnitten wird. ### Navigation und Interaktion diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index d07c0a4..60f6cc2 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -10,12 +10,14 @@ using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Reflection; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Forms; using System.Windows.Input; +using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shell; @@ -77,6 +79,15 @@ namespace C4IT_CustomerPanel private bool _mainSurfaceClipInitialized = false; internal DateTime lastUpdate; + [DllImport("gdi32.dll")] + private static extern IntPtr CreateRoundRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, int nWidthEllipse, int nHeightEllipse); + + [DllImport("user32.dll")] + private static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw); + + [DllImport("gdi32.dll")] + private static extern bool DeleteObject(IntPtr hObject); + //private Dictionary> announcementIDCollection = new Dictionary>(); public bool _userIsAuthenticated = false; public enumOnlineState OnlineState = enumOnlineState.Initializing; @@ -234,11 +245,14 @@ namespace C4IT_CustomerPanel { ComputerInfoCtrl.FillMainInfoGrid(); UpdateMainSurfaceClip(); + ApplyRoundedWindowRegion(); if (!_mainSurfaceClipInitialized) { Dispatcher.BeginInvoke(new Action(UpdateMainSurfaceClip), DispatcherPriority.Loaded); Dispatcher.BeginInvoke(new Action(UpdateMainSurfaceClip), DispatcherPriority.Render); + Dispatcher.BeginInvoke(new Action(ApplyRoundedWindowRegion), DispatcherPriority.Loaded); + Dispatcher.BeginInvoke(new Action(ApplyRoundedWindowRegion), DispatcherPriority.Render); } NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(AddressChangedCallback); @@ -2167,6 +2181,7 @@ namespace C4IT_CustomerPanel private void Window_SizeChanged(object sender, SizeChangedEventArgs e) { UpdateMainSurfaceClip(); + ApplyRoundedWindowRegion(); SetLocation(); } @@ -2245,6 +2260,37 @@ namespace C4IT_CustomerPanel return mask; } + private void ApplyRoundedWindowRegion() + { + if (!IsLoaded || WindowState == WindowState.Minimized) + return; + + IntPtr hwnd = new WindowInteropHelper(this).Handle; + if (hwnd == IntPtr.Zero) + return; + + PresentationSource source = PresentationSource.FromVisual(this); + if (source?.CompositionTarget == null) + return; + + Matrix toDevice = source.CompositionTarget.TransformToDevice; + int pxWidth = Math.Max(1, (int)Math.Ceiling(ActualWidth * toDevice.M11)); + int pxHeight = Math.Max(1, (int)Math.Ceiling(ActualHeight * toDevice.M22)); + + const double radiusDip = 20d; + int ellipseWidth = Math.Max(2, (int)Math.Ceiling(radiusDip * 2d * toDevice.M11)); + int ellipseHeight = Math.Max(2, (int)Math.Ceiling(radiusDip * 2d * toDevice.M22)); + + IntPtr hRgn = CreateRoundRectRgn(0, 0, pxWidth + 1, pxHeight + 1, ellipseWidth, ellipseHeight); + if (hRgn == IntPtr.Zero) + return; + + if (SetWindowRgn(hwnd, hRgn, true) == 0) + { + DeleteObject(hRgn); + } + } + } public class cMainFunctionInfo