WinUI 3自定义组件开发:如何创建支持鼠标事件的GDI绘图自定义控件?
在WinUI 3中创建基于GDI绘图的自定义控件(支持鼠标交互)
如果你要做一个WinUI 3的自定义控件,不用UserControl组合,而是纯GDI绘图,还要支持点击、悬停、拖拽、双击这些交互,按下面的步骤来就行:
1. 建自定义控件类,继承FrameworkElement
首先定义一个继承自FrameworkElement的控件类,用来存交互状态(比如是不是悬停、有没有在拖拽):
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Input; using System.Drawing; using System.Drawing.Drawing2D; using System.Runtime.InteropServices; namespace WinUI3GDIControlDemo { public class GDIDrawControl : FrameworkElement { // 标记交互状态 private bool _isHovered; private bool _isDragging; private bool _isElementClicked; private Point _dragStartPos; // 自定义绘制元素的位置(示例用矩形) private Point _elementPos = new(100, 100); public GDIDrawControl() { // 绑定指针事件 PointerEntered += GDIDrawControl_PointerEntered; PointerExited += GDIDrawControl_PointerExited; PointerPressed += GDIDrawControl_PointerPressed; PointerReleased += GDIDrawControl_PointerReleased; PointerMoved += GDIDrawControl_PointerMoved; DoubleTapped += GDIDrawControl_DoubleTapped; } } }
2. 处理鼠标/指针事件
在控件里实现各个事件的逻辑,更新状态后触发重绘:
private void GDIDrawControl_PointerEntered(object sender, PointerRoutedEventArgs e) { _isHovered = true; InvalidateVisual(); // 告诉控件要重绘了 } private void GDIDrawControl_PointerExited(object sender, PointerRoutedEventArgs e) { _isHovered = false; _isDragging = false; InvalidateVisual(); } private void GDIDrawControl_PointerPressed(object sender, PointerRoutedEventArgs e) { var pointerPoint = e.GetCurrentPoint(this); _dragStartPos = new((int)pointerPoint.Position.X, (int)pointerPoint.Position.Y); // 判断是不是点到了我们绘制的元素上(示例是100x60的矩形) var elementRect = new Rectangle(_elementPos.X, _elementPos.Y, 100, 60); _isElementClicked = elementRect.Contains(_dragStartPos); // 如果要拖拽时超出控件也能响应,这里可以加CapturePointer(e.Pointer) InvalidateVisual(); } private void GDIDrawControl_PointerReleased(object sender, PointerRoutedEventArgs e) { _isElementClicked = false; _isDragging = false; // 释放指针捕获(如果之前加了的话) // ReleasePointerCapture(e.Pointer); InvalidateVisual(); } private void GDIDrawControl_PointerMoved(object sender, PointerRoutedEventArgs e) { if (_isElementClicked) { _isDragging = true; var pointerPoint = e.GetCurrentPoint(this); var currentPos = new((int)pointerPoint.Position.X, (int)pointerPoint.Position.Y); // 计算偏移,更新元素位置 _elementPos.X += currentPos.X - _dragStartPos.X; _elementPos.Y += currentPos.Y - _dragStartPos.Y; _dragStartPos = currentPos; InvalidateVisual(); } } private void GDIDrawControl_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e) { // 双击就把元素重置回初始位置 _elementPos = new(100, 100); InvalidateVisual(); }
3. 重写OnRender,用GDI绘图
WinUI 3的OnRender本来用DrawingContext,但要调用GDI的话,得先拿到控件所在窗口的HDC,再用GDI+的API来画:
protected override void OnRender(Microsoft.UI.Xaml.Media.DrawingContext drawingContext) { base.OnRender(drawingContext); // 获取当前控件所在的窗口句柄 var currentWindow = Window.GetWindow(this); if (currentWindow == null) return; var hwnd = WindowNative.GetWindowHandle(currentWindow); // 从HDC创建GDI的Graphics对象 using var g = Graphics.FromHdc(GetDC(hwnd)); g.SmoothingMode = SmoothingMode.AntiAlias; // 开抗锯齿,画出来更顺 // 画控件背景:悬停时变浅蓝,平时浅灰 var bgBrush = _isHovered ? Brushes.LightBlue : Brushes.LightGray; g.FillRectangle(bgBrush, 0, 0, (int)ActualWidth, (int)ActualHeight); // 画可交互的矩形元素:点击时橙色,拖拽时深橙,平时蓝 var elementBrush = _isElementClicked ? Brushes.Orange : (_isDragging ? Brushes.DarkOrange : Brushes.CornflowerBlue); g.FillRectangle(elementBrush, _elementPos.X, _elementPos.Y, 100, 60); g.DrawRectangle(Pens.Black, _elementPos.X, _elementPos.Y, 100, 60); // 画状态文本,显示当前交互状态 using var font = new Font("Arial", 12); var statusText = _isDragging ? "拖拽中..." : (_isHovered ? "悬停中" : "就绪"); g.DrawString(statusText, font, Brushes.Black, 10, 10); // 释放HDC ReleaseDC(hwnd, g.GetHdc()); } // 导入Win32 API来获取和释放HDC [DllImport("user32.dll")] private static extern IntPtr GetDC(IntPtr hWnd); [DllImport("user32.dll")] private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
4. 在XAML里用这个控件
直接在页面的XAML里引用就行,记得加命名空间:
<Window x:Class="WinUI3GDIControlDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:WinUI3GDIControlDemo" Title="GDI自定义控件演示"> <Grid> <local:GDIDrawControl Width="400" Height="300"/> </Grid> </Window>
要注意的点
- 得先装
System.Drawing.Common这个NuGet包,.NET 6及以上版本需要手动安装,不然用不了GDI+的API。 - 如果需要拖拽时鼠标移出控件还能继续响应,记得在
PointerPressed里调用CapturePointer(e.Pointer),PointerReleased里调用ReleasePointerCapture(e.Pointer)。 - 只要状态变了,一定要调用
InvalidateVisual(),不然控件不会重绘,UI就不会更新。 - GDI是基于Win32的互操作,和WinUI 3本身的渲染机制不是一套,频繁重绘的场景可能会有性能问题,按需使用。
内容的提问来源于stack exchange,提问作者user3765739
相关产品推荐
相关产品推荐

