WPF/C#:使用Stylet中的IWindowManager用于显示等待窗体、对话框与消息框

前言

显示等待框意义

在创建WPF应用的时候,如果我们要执行一个耗时的操作,那么给用户显示一个等待窗体是很常见的需求,通过显示一个等待窗体让用户明白运行的这个软件并没有崩溃,能有效消除用户的焦虑与不确定性,同时能极大提升用户体验,展示软件的专业性和品质,将无聊的等待转化为可预期的、安心的过程。

显示对话框意义

对话框能有效地捕获用户焦点。作为模态窗口,它会强制用户必须完成当前任务(如确认、输入信息或做出选择),之后才能继续操作主窗口。这保证了关键业务流程(如保存文件前的确认)的完整性,防止用户误操作。其次,对话框是信息收集与展示的专用容器。它将特定任务(如设置、登录、详情查看)的UI元素和逻辑封装起来,使主窗口界面保持简洁,同时提升了代码的模块化和可维护性。

显示信息框意义

信息框的核心价值在于即时性与强制性。对于操作成功、失败或出现警告等重要反馈,它能立刻捕获用户注意力,通过模态显示确保用户看到并处理该信息,避免了关键提示被忽略。其次,它为用户提供了快速决策的途径。除了纯信息展示,信息框可附带"是/否"、"确定/取消"等按钮,让用户在不离开当前上下文的情况下,就能对简单询问做出迅速回应,如确认删除操作。

Stylet介绍

Stylet是一个轻量级、高性能的MVVM框架,专为WPF设计。它摆脱了传统MVVM的样板代码,通过基于约定的特性,使得View和ViewModel的自动关联、依赖注入等变得极其简单。开发者几乎无需配置即可快速上手,其内置的功能如IConductor屏幕管理和事件聚合器,能优雅地处理复杂的应用程序导航与模块间通信。Stylet的目标是让开发者专注于业务逻辑本身,而非繁琐的框架配置,从而显著提升WPF应用的开发效率与代码质量。

本文通过使用Stylet内置的IWindowManager可以很容易实现这个需求。

显示等待框

要显示等待窗体那么必须先做一个等待窗体。

创建一个WaitingView.xaml:

csharp 复制代码
<Window x:Class="Rouyan.Pages.View.WaitingView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
        xmlns:local="clr-namespace:Rouyan.Pages.View"
        mc:Ignorable="d"
        Title="WaitingWindow" 
        Height="200" 
        Width="350"
        WindowStyle="ToolWindow"
        WindowStartupLocation="CenterScreen"
        ResizeMode="NoResize"
        Topmost="True">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        
        <Border Grid.Row="0"
                Background="{DynamicResource PrimaryHueMidBrush}"
                Padding="16 8">
            <TextBlock HorizontalAlignment="Center"
                       Text="{Binding Text}"
                       FontSize="16"/>
        </Border>
        
        <!-- 进度条区域 -->
        <StackPanel Grid.Row="1"
                    Orientation="Vertical"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Margin="20">
            
            <ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}"
                         Foreground="{DynamicResource SecondaryHueMidBrush}"
                         Width="60"
                         Height="60"
                         IsIndeterminate="True"/>
                
            <TextBlock Text="请稍候..."
                       FontSize="14"
                       HorizontalAlignment="Center"
                       Foreground="{DynamicResource MaterialDesignBody}"
                       Margin="0,10,0,0"/>
                
        </StackPanel>
    </Grid>
</Window>

效果:

分为两行,上面一行用于显示要显示的信息,下面一行用于显示ProgressBar与"请稍候..."的组合。

现在再来创建对应的ViewModel:

csharp 复制代码
 public class WaitingViewModel : Screen
 {      
     public string Text { get; set; } = "处理中...";

 }

显示这个等待窗体,比如当我们运行一个耗时任务的时候,可以先显示这个等待窗体给用户:

csharp 复制代码
 // 显示等待窗体          
 waitingVm = _container.Get<WaitingViewModel>();
 waitingVm.Text = "正在分析请求,请稍候...";
 _windowManager.ShowWindow(waitingVm);

显示等待窗体主要使用了Stylet中自带的一个组件IWindowManager,我们可以在构造函数中进行依赖注入:

csharp 复制代码
private readonly IContainer _container;
private readonly IWindowManager _windowManager;

public TerminalAgentViewModel(IContainer container,IWindowManager windowManager)
{
    _container = container;
    _windowManager = windowManager;
}

由于等待窗体是一个Window,因此需要使用IWindowManager的ShowWindow方法。

Stylet会帮我们将所有View与ViewModel都以瞬态的形式注入依赖注入容器中了,因此可以不用自己在这里new一个viewmodel而是可以直接从容器中取一个出来:

csharp 复制代码
 waitingVm = _container.Get<WaitingViewModel>();

IWindowManager的ShowWindow方法的使用,反直觉的一点是传入的不是这个窗体,而是这个窗体对应的ViewModel,Stylet会根据这个ViewModel自动找到对应的View,这也体现了Stylet中ViewModel First的思想。

效果:

这时候可能会有一个疑问就是那么怎么关闭这个窗体呢?

如果是获取对应的Window,我们调用close方法就可以了,现在获取的是对应的ViewModel该如何关闭这个窗体呢?

Stylet已经考虑到了这一个,只要这样写就可以关闭这个窗体:

csharp 复制代码
 waitingVm.RequestClose();

显示对话框

使用Stylet中的IWindowManager显示对话框同样也很简单。

首先创建一个HumanApprovalDialogView:

csharp 复制代码
<Window x:Class="Rouyan.Pages.View.HumanApprovalDialogView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:s="https://github.com/canton7/Stylet"
        mc:Ignorable="d"
        Title="{Binding Title}"
        Height="200"
        Width="420"
        WindowStartupLocation="CenterOwner"
        ResizeMode="NoResize"
        Topmost="True">
    <Window.InputBindings>
        <KeyBinding Command="{s:Action Approve}" Key="Y"/>
        <KeyBinding Command="{s:Action Reject}" Key="N"/>
    </Window.InputBindings>
    <Grid Margin="16">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- 顶部消息行 -->
        <ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto">
            <TextBlock Text="{Binding Message}"
                       TextWrapping="Wrap"
                       FontSize="14"/>
        </ScrollViewer>

        <!-- 底部按钮行:左侧同意,右侧拒绝 -->
        <Grid Grid.Row="1" Margin="0,16,0,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            <Button Grid.Column="0"
                    Content="同意(Y)"
                    Width="100"
                    Height="30"
                    HorizontalAlignment="Left"
                    IsDefault="True"
                    Command="{s:Action Approve}"/>

            <Button Grid.Column="1"
                    Content="拒绝(N)"
                    Width="100"
                    Height="30"
                    HorizontalAlignment="Right"
                    IsCancel="True"
                    Command="{s:Action Reject}"/>
        </Grid>
    </Grid>
</Window>

效果:

创建对应的ViewModel:

csharp 复制代码
public class HumanApprovalDialogViewModel : Screen
{
    public HumanApprovalDialogViewModel()
    {
        Title = "执行审批";
    }

    private string _title = string.Empty;
    public string Title
    {
        get => _title;
        set => SetAndNotify(ref _title, value);
    }

    private string _message = string.Empty;
    public string Message
    {
        get => _message;
        set => SetAndNotify(ref _message, value);
    }

    // Approve the action
    public void Approve()
    {
        RequestClose(true);
    }

    // Reject the action
    public void Reject()
    {
        RequestClose(false);
    }
}

使用方式与ShowWindow也很相似:

csharp 复制代码
var dialogVm = new HumanApprovalDialogViewModel
{
    Title = "函数调用审批",
    Message = $"是否同意这个操作?"
};

bool? result = _windowManager.ShowDialog(dialogVm);

最大的区别就是模态与非模态。

模态:

当一个模态窗口(如对话框)出现时,它会独占用户的操作焦点。在用户完成该窗口的任务(例如点击"确定"或"取消")并关闭它之前,无法与应用程序的其他任何窗口进行交互。程序流程也会在此处暂停,等待窗口关闭后返回结果。这就像一个强制性的"选择题",你必须回答才能继续。常见场景包括文件保存、确认删除等需要用户明确决策的流程。

非模态:

非模态窗口则像一个开放的助手。它显示后,用户可以随时在它和应用程序的其他窗口之间自由切换,无需关闭它。程序也会继续执行,不会被窗口的开启或关闭打断。这就像一个可以随时查阅的"便签"或"计算器"。它常用于工具箱、属性面板或辅助信息窗口,让用户在主任务进行时能方便地获取额外功能或信息。

简言之就是模态就是会阻塞程序运行,要你明确给一个反馈,非模态不会阻塞程序的运行,就单纯显示一个窗体。

效果:

同意就返回true,拒绝或者关闭窗体就返回false。

显示信息框

我们可以发现IWindowManager除了有ShowWindow与ShowDialog方法外还有一个ShowMessage方法,现在来看下这个方法的使用吧!!

1、基本用法 - 只显示消息

csharp 复制代码
_windowManager.ShowMessageBox("你好");

效果:

2、带标题的消息框

csharp 复制代码
_windowManager.ShowMessageBox("操作完成", "提示");

效果:

3、带确认和取消按钮的消息框

csharp 复制代码
 var result1 = _windowManager.ShowMessageBox("确定要删除这个文件吗?", "确认删除",
     MessageBoxButton.OKCancel, MessageBoxImage.Question);

效果:

4、带是/否/取消按钮和警告图标的消息框

csharp 复制代码
var result2 = _windowManager.ShowMessageBox("文件已修改,是否保存?", "保存确认",
    MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);

效果:

5、带自定义按钮标签的消息框 (使用YesNoCancel按钮以展示更多选项)

csharp 复制代码
var customButtons = new Dictionary<MessageBoxResult, string>
{
    { MessageBoxResult.Yes, "继续" },
    { MessageBoxResult.No, "停止" },
    { MessageBoxResult.Cancel, "取消" }
};
var result3 = _windowManager.ShowMessageBox("检测到潜在风险,是否继续操作?", "安全警告",
    MessageBoxButton.YesNoCancel, MessageBoxImage.Exclamation, MessageBoxResult.No, MessageBoxResult.Cancel, customButtons);

效果:

6、带文本对齐和流方向的消息框

csharp 复制代码
_windowManager.ShowMessageBox("这是一个从右到左显示的消息框文本", "RTL示例",
    MessageBoxButton.OK, MessageBoxImage.Information, MessageBoxResult.None, MessageBoxResult.None,
    null, FlowDirection.RightToLeft, TextAlignment.Center);

效果:

7、完整参数示例

csharp 复制代码
 var fullResult = _windowManager.ShowMessageBox(
     "这是一个完整的消息框示例,包含了所有参数的使用",
     "完整示例",
     MessageBoxButton.OKCancel,
     MessageBoxImage.Information,
     MessageBoxResult.OK,
     MessageBoxResult.Cancel,
     null,
     FlowDirection.LeftToRight,
     TextAlignment.Left);

效果:

最后

本文梳理了Stylet中IWindowManager的用法,分别是ShowWindow、ShowDialog与ShowMessageBox希望对你有所帮助。