WPF 通用确认对话框组件:支持同步和异步调用

WPF 原生的 MessageBox 长得丑且不可定制样式。本文分享一个自定义确认对话框组件,圆角白色卡片、关闭按钮、确认/取消按钮,还支持同步和异步两种调用方式

效果预览

  • 居中弹出的模态对话框,圆角白色卡片 + 阴影
  • 标题 + 内容 + 确认/取消按钮
  • 右上角关闭按钮(等同取消)
  • 支持自定义按钮文字
  • 自动绑定到当前活跃窗口

MConfirmDialog.xaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<Window x:Class="WpfApp.Component.MConfirmDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="确认"
WindowStyle="None"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"
AllowsTransparency="True"
Background="Transparent"
SizeToContent="WidthAndHeight"
MinWidth="300"
MaxWidth="450">

<Border CornerRadius="8"
Background="#F5F5F5"
BorderBrush="#CCCCCC"
BorderThickness="1"
Padding="20">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

<!-- 关闭按钮 -->
<Button Grid.Row="0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Width="24" Height="24"
Background="Transparent"
BorderBrush="Transparent"
Content="✕"
FontSize="12"
Foreground="#999999"
Cursor="Hand"
Click="CloseButton_Click"/>

<!-- 标题 -->
<TextBlock x:Name="TitleText"
Grid.Row="0"
Text="确认"
FontSize="18"
FontWeight="Medium"
Foreground="#333333"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,30,0"/>

<!-- 内容 -->
<TextBlock x:Name="ContentText"
Grid.Row="1"
Text="确定要执行此操作吗?"
FontSize="14"
Foreground="#666666"
TextWrapping="Wrap"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,20,0,20"/>

<!-- 按钮区域 -->
<StackPanel Grid.Row="2"
Orientation="Horizontal"
HorizontalAlignment="Center">
<Button x:Name="CancelButton"
Content="取消"
MinWidth="80"
Height="32"
Background="Transparent"
BorderBrush="#CCCCCC"
BorderThickness="1"
Foreground="#666666"
Margin="0,0,10,0"
Click="CancelButton_Click"/>
<Button x:Name="ConfirmButton"
Content="确认"
MinWidth="80"
Height="32"
Background="#2196F3"
BorderBrush="Transparent"
Foreground="White"
Cursor="Hand"
Click="ConfirmButton_Click"/>
</StackPanel>
</Grid>
</Border>
</Window>

MConfirmDialog.xaml.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
using System.Windows;

namespace WpfApp.Component
{
/// <summary>
/// 通用确认对话框
///
/// 使用方式:
/// // 同步调用
/// if (MConfirmDialog.Show("确认删除", "确定要删除此项吗?"))
/// {
/// Delete();
/// }
///
/// // 异步调用
/// var result = await MConfirmDialog.ShowAsync("确认删除", "确定要删除此项吗?");
///
/// // 自定义按钮文字
/// MConfirmDialog.Show("提示", "保存修改?", confirmText: "保存", cancelText: "不保存");
/// </summary>
public partial class MConfirmDialog : Window
{
#region 属性

/// <summary>对话框结果:true=确认,false=取消</summary>
public new bool DialogResult { get; private set; }

#endregion

#region 构造

public MConfirmDialog()
{
InitializeComponent();
}

#endregion

#region 内容设置

/// <summary>
/// 设置对话框内容
/// </summary>
/// <param name="title">标题</param>
/// <param name="content">内容</param>
/// <param name="confirmText">确认按钮文字</param>
/// <param name="cancelText">取消按钮文字</param>
public void Setup(string title, string content,
string confirmText = "确认", string cancelText = "取消")
{
TitleText.Text = title;
ContentText.Text = content;
ConfirmButton.Content = confirmText;
CancelButton.Content = cancelText;
}

#endregion

#region 事件处理

private void CloseButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}

private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}

private void ConfirmButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}

#endregion

#region 静态调用

/// <summary>
/// 显示确认对话框(异步)
/// </summary>
/// <returns>true=确认,false=取消</returns>
public static Task<bool> ShowAsync(string title, string content,
string confirmText = "确认", string cancelText = "取消")
{
var dialog = new MConfirmDialog();
dialog.Setup(title, content, confirmText, cancelText);

// 绑定到当前活跃窗口
var owner = Application.Current?.Windows.OfType<Window>()
.FirstOrDefault(w => w.IsActive);
if (owner != null)
{
dialog.Owner = owner;
}

var tcs = new TaskCompletionSource<bool>();
dialog.Closed += (s, e) => tcs.TrySetResult(dialog.DialogResult);
dialog.ShowDialog();

return tcs.Task;
}

/// <summary>
/// 显示确认对话框(同步阻塞)
/// </summary>
/// <returns>true=确认,false=取消</returns>
public static bool Show(string title, string content,
string confirmText = "确认", string cancelText = "取消")
{
var dialog = new MConfirmDialog();
dialog.Setup(title, content, confirmText, cancelText);

var owner = Application.Current?.Windows.OfType<Window>()
.FirstOrDefault(w => w.IsActive);
if (owner != null)
{
dialog.Owner = owner;
}

dialog.ShowDialog();
return dialog.DialogResult;
}

#endregion
}
}

使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 同步调用(阻塞,最简单)
if (MConfirmDialog.Show("确认删除", "确定要删除此项吗?"))
{
DeleteItem();
}

// 异步调用(不阻塞,适合 async 方法)
var result = await MConfirmDialog.ShowAsync("保存确认", "有未保存的修改,是否保存?");
if (result)
{
await SaveAsync();
}

// 自定义按钮文字
MConfirmDialog.Show("提示", "确定要清空购物车吗?",
confirmText: "清空", cancelText: "再想想");

设计要点

1. new DialogResult 巧妙遮蔽

WPF 的 Window 自带一个 DialogResult 属性(类型是 bool?),用起来不方便。这里用 public new bool DialogResult 遮蔽了父类属性,改成非空 bool,调用方不需要处理 null

2. 同步 vs 异步双模式

  • 同步Show() 内部调 ShowDialog() 天然阻塞,返回 bool
  • 异步ShowAsync()TaskCompletionSource 包装,Closed 事件时 TrySetResult,支持 await

两种方式覆盖所有使用场景。

3. 自动绑定 Owner

通过 Application.Current.Windows 找到当前活跃窗口作为 Owner,确保对话框始终在父窗口之上,且关闭父窗口时子窗口跟着关闭。

4. 三种关闭方式等同取消

点击 ✕ 关闭按钮、点击取消按钮、按 Esc 键(如果绑定了),都返回 false。只有点击确认按钮才返回 true