如何在UWP中创建浮动ContentDialog及可拖动的MessageBox?
嘿,这个需求我之前做项目的时候刚好研究过,给你分享两种靠谱的实现思路:一种是改造原生ContentDialog让它能拖动,另一种是用Popup做非模态的浮动窗口,按需选就行~
一、方案1:自定义可拖动的ContentDialog(模态)
原生ContentDialog默认是模态的(会阻塞主窗口操作),但我们可以修改它的模板,给标题栏加上拖动逻辑,让用户能拖动它的位置。
步骤1:自定义ContentDialog的模板
先在你的页面资源或者全局ResourceDictionary里定义一个带可拖动标题栏的模板:
<ControlTemplate x:Key="DraggableContentDialogTemplate" TargetType="ContentDialog"> <Border x:Name="Container" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="8"> <Grid> <!-- 可拖动的标题栏 --> <Grid x:Name="TitleBar" Height="48" Background="Transparent"> <TextBlock x:Name="Title" Margin="16,0" VerticalAlignment="Center" FontSize="18" Text="{TemplateBinding Title}" FontWeight="SemiBold"/> <Button x:Name="CloseButton" HorizontalAlignment="Right" Margin="0,8,8,8" Style="{StaticResource CloseButtonStyle}"/> </Grid> <!-- 内容滚动区域 --> <ScrollViewer x:Name="ContentScrollViewer" Margin="0,48,0,0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> <ContentPresenter x:Name="ContentPresenter" Content="{TemplateBinding Content}" Margin="16" ContentTemplate="{TemplateBinding ContentTemplate}"/> </ScrollViewer> <!-- 底部按钮栏 --> <StackPanel x:Name="ButtonPanel" Orientation="Horizontal" HorizontalAlignment="Right" Margin="16,0,16,16" VerticalAlignment="Bottom"> <Button x:Name="PrimaryButton" Style="{StaticResource AccentButtonStyle}" Content="{TemplateBinding PrimaryButtonText}" IsEnabled="{TemplateBinding IsPrimaryButtonEnabled}"/> <Button x:Name="SecondaryButton" Margin="8,0,0,0" Content="{TemplateBinding SecondaryButtonText}" IsEnabled="{TemplateBinding IsSecondaryButtonEnabled}"/> </StackPanel> </Grid> </Border> </ControlTemplate>
步骤2:写拖动逻辑的代码
创建一个继承自ContentDialog的自定义类,把拖动逻辑封装进去:
public class DraggableContentDialog : ContentDialog { private Point _dragStartPoint; private bool _isDragging; public DraggableContentDialog() { // 应用自定义模板 Template = Application.Current.Resources["DraggableContentDialogTemplate"] as ControlTemplate; } protected override void OnApplyTemplate() { base.OnApplyTemplate(); // 找到标题栏控件,绑定鼠标事件 var titleBar = GetTemplateChild("TitleBar") as Grid; if (titleBar == null) return; titleBar.PointerPressed += TitleBar_PointerPressed; titleBar.PointerMoved += TitleBar_PointerMoved; titleBar.PointerReleased += TitleBar_PointerReleased; titleBar.PointerExited += TitleBar_PointerExited; } private void TitleBar_PointerPressed(object sender, PointerRoutedEventArgs e) { var pointerPoint = e.GetCurrentPoint(this); _dragStartPoint = pointerPoint.Position; _isDragging = true; // 捕获鼠标,避免拖动时失去焦点 (sender as UIElement).CapturePointer(pointerPoint.Pointer); e.Handled = true; } private void TitleBar_PointerMoved(object sender, PointerRoutedEventArgs e) { if (!_isDragging) return; var pointerPoint = e.GetCurrentPoint(this); var currentPos = pointerPoint.Position; // 计算偏移量 var offsetX = currentPos.X - _dragStartPoint.X; var offsetY = currentPos.Y - _dragStartPoint.Y; // 更新对话框的Margin来实现移动 Margin = new Thickness( Margin.Left + offsetX, Margin.Top + offsetY, Margin.Right - offsetX, Margin.Bottom - offsetY); e.Handled = true; } private void TitleBar_PointerReleased(object sender, PointerRoutedEventArgs e) { _isDragging = false; (sender as UIElement).ReleasePointerCapture(e.Pointer); e.Handled = true; } private void TitleBar_PointerExited(object sender, PointerRoutedEventArgs e) { // 鼠标离开标题栏时停止拖动 _isDragging = false; (sender as UIElement).ReleasePointerCapture(e.Pointer); e.Handled = true; } }
步骤3:使用自定义对话框
在页面里直接实例化调用就行,和原生ContentDialog用法一致:
var dialog = new DraggableContentDialog { Title = "可拖动的对话框", Content = "试试拖动标题栏移动我呀~", PrimaryButtonText = "确定", SecondaryButtonText = "取消" }; // 显示对话框(还是模态的,会阻塞主窗口) await dialog.ShowAsync();
二、方案2:用Popup实现非模态浮动窗口
如果想要完全浮动、不阻塞主窗口操作的效果,用Popup会更合适,它可以自由控制位置,而且是非模态的,体验更像独立的MessageBox。
步骤1:定义Popup的UI结构
在你的Page里添加Popup和内部的对话框内容:
<Popup x:Name="FloatingMsgBox" IsOpen="False" Placement="Center"> <Border Width="320" Height="220" Background="White" CornerRadius="8" BorderBrush="#E0E0E0" BorderThickness="1" ShadowEffect.Color="#33000000" ShadowEffect.BlurRadius="12"> <Grid> <!-- 可拖动的标题栏 --> <Grid x:Name="PopupTitleBar" Height="44" Background="#F8F8F8"> <TextBlock Margin="16,0" VerticalAlignment="Center" Text="浮动提示框" FontSize="16" FontWeight="SemiBold"/> <Button x:Name="ClosePopupBtn" Width="36" Height="36" HorizontalAlignment="Right" Style="{StaticResource TransparentButtonStyle}" Click="ClosePopupBtn_Click"> <SymbolIcon Symbol="Close" Foreground="#666"/> </Button> </Grid> <!-- 内容区域 --> <StackPanel Margin="16" VerticalAlignment="Center"> <TextBlock Text="这是一个可以自由拖动的非模态浮动窗口~" TextWrapping="Wrap"/> </StackPanel> </Grid> </Border> </Popup>
步骤2:添加拖动逻辑
在Page的代码-behind里写拖动相关的事件:
private Point _popupDragStart; private bool _isPopupDragging; protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); // 绑定标题栏的鼠标事件 PopupTitleBar.PointerPressed += PopupTitleBar_PointerPressed; PopupTitleBar.PointerMoved += PopupTitleBar_PointerMoved; PopupTitleBar.PointerReleased += PopupTitleBar_PointerReleased; } private void PopupTitleBar_PointerPressed(object sender, PointerRoutedEventArgs e) { var pointerPoint = e.GetCurrentPoint(FloatingMsgBox.Child); _popupDragStart = pointerPoint.Position; _isPopupDragging = true; (sender as UIElement).CapturePointer(pointerPoint.Pointer); e.Handled = true; } private void PopupTitleBar_PointerMoved(object sender, PointerRoutedEventArgs e) { if (!_isPopupDragging || !FloatingMsgBox.IsOpen) return; var pointerPoint = e.GetCurrentPoint(Window.Current.Content as UIElement); var currentPos = pointerPoint.Position; // 计算偏移量 var offsetX = currentPos.X - _popupDragStart.X; var offsetY = currentPos.Y - _popupDragStart.Y; // 更新Popup的位置 FloatingMsgBox.HorizontalOffset = offsetX; FloatingMsgBox.VerticalOffset = offsetY; e.Handled = true; } private void PopupTitleBar_PointerReleased(object sender, PointerRoutedEventArgs e) { _isPopupDragging = false; (sender as UIElement).ReleasePointerCapture(e.Pointer); e.Handled = true; } // 关闭Popup的方法 private void ClosePopupBtn_Click(object sender, RoutedEventArgs e) { FloatingMsgBox.IsOpen = false; } // 打开浮动窗口的方法 private void OpenFloatingMsgBox() { // 初始化到屏幕中心 var screenBounds = Window.Current.Bounds; FloatingMsgBox.HorizontalOffset = (screenBounds.Width - 320) / 2; FloatingMsgBox.VerticalOffset = (screenBounds.Height - 220) / 2; FloatingMsgBox.IsOpen = true; }
三、额外小技巧
如果你的应用是Windows 10 1809及以上的桌面桥接应用,还可以用AppWindow来创建完全独立的浮动窗口,它自带系统级的拖动、最小化/最大化功能,体验和原生MessageBox几乎一样,而且完全非模态,适合更复杂的场景。
内容的提问来源于stack exchange,提问作者Shakir Ahmed
相关产品推荐
相关产品推荐

