I am developing Blazor MAUI Hybrid application. One of the requirements is to remove top bar (with minimize, maximize, close buttons) and create my own. Then be able to drag it and move windows in same way as it would be native one. After some work, I end up in solution, which looks finally (almost) working. Only issue with this solution is that user can move mouse much faster than window is moving. Does anyone know what reason of that behavior is and how to eventually solve it? If you drag any other application window, like Visual Studio, or Browser or even MAUI app with default top bar, it moves as fast as mouse cursor.
My current code is this:
- I have MAUI app and I have library with Blazor components (so Blazor components have no direct access to MAUI window)
- I don't use MouseEventArgs objects in Blazor events because they were returning completely wrong values
- This is closest working solution I was able to get, so far, but can't figure out the speed issue.
- MauiApp / MauiProgram.cs - This code removes top bar
public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureLifecycleEvents(events => { #if WINDOWS events.AddWindows(windowsLifecycleBuilder => { windowsLifecycleBuilder.OnWindowCreated(window => { var handle = WinRT.Interop.WindowNative.GetWindowHandle(window); var id = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(handle); var appWindow = AppWindow.GetFromWindowId(id); if (appWindow.Presenter is OverlappedPresenter presenter) { presenter.SetBorderAndTitleBar(false, false); presenter.IsMinimizable = false; presenter.IsMaximizable = false; presenter.IsResizable = false; } }); }); #endif }) ... other code- Blazor components project defines interface for accessing MAUI specific methods:
public interface IMauiManipulater { void OnPointerPressed(); void OnPointerMoved(); void OnPointerReleased(); }- MAUI project implements it:
internal class MauiManipulator : IMauiManipulator { private bool isDragging = false; private Point initialCursorPosition; private Point initialWindowPosition; #if WINDOWS [DllImport("user32.dll")] public static extern bool SetCapture(IntPtr hWnd); [DllImport("user32.dll")] public static extern bool ReleaseCapture(); [DllImport("user32.dll")] public static extern bool GetCursorPos(out POINT lpPoint); public struct POINT { public int X; public int Y; } private Point GetCursorPosition() { GetCursorPos(out POINT point); return new Point(point.X, point.Y); } #endif public void OnPointerPressed() { isDragging = true; #if WINDOWS // Capture pointer globally var mauiWindow = App.Current.Windows.First(); var nativeWindow = mauiWindow.Handler.PlatformView; IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(nativeWindow); SetCapture(windowHandle); // Get initial cursor position initialCursorPosition = GetCursorPosition(); // Get initial window position var appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId( Microsoft.UI.Win32Interop.GetWindowIdFromWindow(windowHandle)); initialWindowPosition = new Point(appWindow.Position.X, appWindow.Position.Y); #endif } public void OnPointerMoved() { if (!isDragging) return; Debug.WriteLine(DateTime.Now.Ticks); #if WINDOWS // Get current cursor position var currentCursorPosition = GetCursorPosition(); // Calculate offset var offsetX = currentCursorPosition.X - initialCursorPosition.X; var offsetY = currentCursorPosition.Y - initialCursorPosition.Y; // Move window var mauiWindow = App.Current.Windows.First(); var nativeWindow = mauiWindow.Handler.PlatformView; IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(nativeWindow); var appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId( Microsoft.UI.Win32Interop.GetWindowIdFromWindow(windowHandle)); appWindow.Move(new Windows.Graphics.PointInt32( (int)(initialWindowPosition.X + offsetX), (int)(initialWindowPosition.Y + offsetY) )); #endif } public void OnPointerReleased() { #if WINDOWS // Release pointer capture var mauiWindow = App.Current.Windows.First(); var nativeWindow = mauiWindow.Handler.PlatformView; IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(nativeWindow); ReleaseCapture(); #endif isDragging = false; }4. And finally usage in Blazor components be like:<div class="top-bar" @onmousedown="HandleMouseDown" @onmouseup="HandleMouseUp" @onmousemove="HandleMouseMove" @onmouseleave="HandleMouseUp"> // Top bar content</div> private bool isDragging = false; [Inject] private IMauiManipulator MauiManipulator { get; set; } = default!; private void HandleMouseDown(MouseEventArgs e) { Debug.WriteLine("Mouse down"); isDragging = true; MauiManipulator.OnPointerPressed(); } private void HandleMouseUp(MouseEventArgs e) { Debug.WriteLine("Mouse up"); if (isDragging) { isDragging = false; MauiManipulator.OnPointerReleased(); } } private async Task HandleMouseMove(MouseEventArgs e) { if (isDragging) { MauiManipulator.OnPointerMoved(); } }