WPF 高级界面开发:多窗口通信与生命周期管理

90 阅读4分钟

前言

WPF 应用开发中,多窗口管理是一个常见且重要的需求。无论是设置窗口、数据录入窗口,还是信息展示窗口,多窗口设计都能显著提升用户体验和功能分离度。

然而,窗口间的数据传递、模态与非模态窗口的区别以及开发中的常见陷阱,常常让开发者感到头疼。

本文将通过一个完整的实战项目,详细讲解 WPF 多窗口开发的核心技术,并提供可直接应用到项目中的完整解决方案。

正文

为什么需要多窗口开发?

在实际项目中,多窗口开发的需求无处不在。例如,设置窗口需要独立的配置界面,复杂表单需要分步骤完成,详情页面需要独立显示,调试或辅助功能需要工具窗口。

单窗口应用虽然简单,但用户体验往往不够友好,多窗口设计则能带来更好的交互体验和功能分离。

核心功能

1、模态 vs 非模态窗口

模态窗口会阻塞父窗口的操作,必须处理完当前窗口才能继续,适用于确认对话框、设置页面、数据录入等场景。非模态窗口则不会阻塞父窗口,可以同时操作多个窗口,适用于工具栏、实时监控、辅助功能等场景。

// 模态窗口显示
SecondWindow modal = new SecondWindow();
bool? result = modal.ShowDialog(); // 阻塞执行

// 非模态窗口显示
ThirdWindow modeless = new ThirdWindow();
modeless.Show(); // 立即返回,不阻塞

2、窗口间通信的三种方式

构造函数传参(单向传递)

public SecondWindow(string userName)
{
    InitializeComponent();
    this.userName = userName;
    txtWelcome.Text = $"欢迎,{userName}!";
}

属性返回值(模态窗口返回数据)

public class SecondWindow : Window
{
    public string ReturnData { get; private set; } = "无数据";
    private void btnConfirm_Click(object sender, RoutedEventArgs e)
    {
        ReturnData = "用户提交的数据";
        this.DialogResult = true; // 关键:设置返回值
    }
}
// 主窗口接收
bool? result = secondWindow.ShowDialog();
if (result == true)
{
    string data = secondWindow.ReturnData;
}

事件机制(实时通信)

public class ThirdWindow : Window
{
    public event EventHandler<string> DataReceived;
    private void SendMessage()
    {
        DataReceived?.Invoke(this, "来自子窗口的消息");
    }
}
// 主窗口订阅事件
thirdWindow.DataReceived += (sender, data) => {
    AddMessage($"接收到数据:{data}");
};

完整实战代码

1、主窗口核心代码

public partial class MainWindow : Window
{
    private List<Window> openWindows = new List<Window>();
    // 打开模态窗口
    private void btnOpenSecond_Click(object sender, RoutedEventArgs e)
    {
        string userName = txtUserName.Text.Trim();
        if (string.IsNullOrEmpty(userName))
        {
            MessageBox.Show("请先输入您的姓名!", "提示");
            return;
        }
        SecondWindow secondWindow = new SecondWindow(userName);
        secondWindow.Owner = this; // ⭐ 关键:设置父窗口
        bool? result = secondWindow.ShowDialog();
        if (result == true)
        {
            AddMessage($"接收到数据:{secondWindow.ReturnData}");
        }
    }
    // 打开非模态窗口
    private void btnOpenThird_Click(object sender, RoutatedEventArgs e)
    {
        ThirdWindow thirdWindow = new ThirdWindow(txtUserName.Text);
        thirdWindow.Owner = this;
        thirdWindow.DataReceived += (sender, data) => {
            AddMessage($"实时消息:{data}");
        };
        openWindows.Add(thirdWindow);
        thirdWindow.Show(); // 非阻塞显示
    }
}

2、子窗口设计要点

模态窗口(数据收集)

public partial class SecondWindow : Window
{
    public string ReturnData { get; private set; }
    private void btnConfirm_Click(object sender, RoutedEventArgs e)
    {
        // ⭐ 数据验证
        if (cmbProfession.SelectedItem == null)
        {
            MessageBox.Show("请选择职业!");
            return;
        }
        // ⭐ 收集并格式化数据
        ReturnData = BuildUserData();
        this.DialogResult = true; // 设置返回结果
    }
    private string BuildUserData()
    {
        var profession = ((ComboBoxItem)cmbProfession.SelectedItem).Content;
        var experience = (int)sliderExperience.Value;
        return $"职业: {profession}, 经验: {experience}年";
    }
}

非模态窗口(实时交互)

public partial class ThirdWindow : Window
{
    public event EventHandler<string> DataReceived;
    private DispatcherTimer timer;
    public ThirdWindow(string userName)
    {
        InitializeComponent();
        InitializeTimer(); // ⭐ 初始化定时器
    }
    private void InitializeTimer()
    {
        timer = new DispatcherTimer
        {
            Interval = TimeSpan.FromSeconds(1)
        };
        timer.Tick += (s, e) => {
            txtCurrentTime.Text = DateTime.Now.ToString("HH:mm:ss");
        };
        timer.Start();
    }
    private void btnSendMessage_Click(object sender, RoutedEventArgs e)
    {
        string message = txtMessage.Text.Trim();
        if (!string.IsNullOrEmpty(message))
        {
            DataReceived?.Invoke(this, message); // ⭐ 触发事件
            txtMessage.Clear();
        }
    }
}

常见问题

1、内存泄漏风险

// ❌ 错误做法:忘记移除事件订阅
thirdWindow.DataReceived += SomeHandler;

// ✅ 正确做法:窗口关闭时清理资源
private void ThirdWindow_Closing(object sender, CancelEventArgs e)
{
    timer?.Stop(); // 停止定时器
    DataReceived = null; // 清理事件订阅
}

2、窗口层级管理

// 设置父窗口,确保层级关系
secondWindow.Owner = this;

// ⭐ 应用关闭时清理所有子窗口
private void MainWindow_Closing(object sender, CancelEventArgs e)
{
    foreach (var window in openWindows.ToList())
    {
        if (window.IsLoaded) window.Close();
    }
}

3、线程安全问题

// UI更新必须在主线程执行
Dispatcher.Invoke(() => {
    txtMessages.Text += $"[{DateTime.Now:HH:mm:ss}] {message}n";
});

应用场景

1、企业级应用架构

在实际项目中,多窗口开发常用于模块化设计、权限控制和数据流管理。

例如,每个功能独立窗口,根据用户角色显示不同窗口,以及窗口间的数据同步和验证。

2、窗口状态管理

// 窗口状态持久化
public class WindowStateManager
{
    public static void SaveWindowState(Window window, string key)
    {
        Properties.Settings.Default[$"{key}_Left"] = window.Left;
        Properties.Settings.Default[$"{key}_Top"] = window.Top;
        Properties.Settings.Default[$"{key}_Width"] = window.Width;
        Properties.Settings.Default[$"{key}_Height"] = window.Height;
        Properties.Settings.Default.Save();
    }
    public static void RestoreWindowState(Window window, string key)
    {
        var left = Properties.Settings.Default[$"{key}_Left"];
        if (left is double leftValue) window.Left = leftValue;
        // ... 其他属性恢复
    }
}

本文通过一个完整的实战项目,详细讲解了 WPF 多窗口开发的核心技术,包括模态与非模态窗口的区别、窗口间通信的三种方式(构造函数传参、属性返回值、事件机制)、开发中的常见陷阱(内存泄漏、窗口层级管理、线程安全)以及高级应用场景(企业级应用架构、窗口状态管理)。

同时,提供了可直接应用到项目中的完整代码示例,帮助开发快速掌握 WPF 多窗口开发。

总结

通过本文的完整实战演示,我们掌握了 WPF 多窗口开发的三个核心要点:

1、窗口类型选择:根据使用场景选择模态或非模态窗口。

2、数据通信机制:构造函数传参、属性返回、事件机制三种方式。

3、资源管理策略:避免内存泄漏,正确处理窗口生命周期。

关键词

WPF、多窗口开发、模态窗口、非模态窗口、窗口通信、内存泄漏、线程安全

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

来源:mp.weixin.qq.com/s/-FlWm6ckPFgT4RKwn_KMCg