编辑
2025-10-02
C#
00

目录

🎯 为什么需要多窗口开发?
现实开发痛点
💡 核心技术解析
🔥 模态 vs 非模态窗口
🛠️ 窗口间通信的三种方式
1️⃣ 构造函数传参(单向传递)
2️⃣ 属性返回值(模态窗口返回数据)
3️⃣ 事件机制(实时通信)
🔧 完整实战代码
📋 主窗口核心代码
🎨 子窗口设计要点
模态窗口(数据收集)
非模态窗口(实时交互)
完整代码
MainWindow
SecondWindow
ThirdWindow
⚠️ 开发中的常见陷阱
🚨 内存泄漏风险
🚨 窗口层级管理
🚨 线程安全问题
🎯 高级应用场景
💼 企业级应用架构
🔄 窗口状态管理
🎁 收藏级代码模板
📝 通用窗口基类
🔗 窗口管理器
🎊 总结与展望

你是否还在为WPF应用中的窗口跳转而头疼?多个窗口之间如何优雅地传递数据?模态和非模态窗口有什么区别?其实这里与Winform基本一回事,这里讲的不是导航。作为一名C#开发者,掌握多窗口开发技巧是构建专业级桌面应用的必备技能。

今天,我将通过一个完整的实战项目,带你从零开始掌握WPF多窗口开发的所有核心技术。这不仅仅是理论讲解,而是一套可以直接应用到项目中的完整解决方案!

🎯 为什么需要多窗口开发?

现实开发痛点

在实际项目中,我们经常遇到这些场景:

  • 设置窗口:需要独立的配置界面
  • 数据录入:复杂表单需要分步骤完成
  • 信息展示:详情页面需要独立显示
  • 工具窗口:调试或辅助功能窗口

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

💡 核心技术解析

🔥 模态 vs 非模态窗口

模态窗口(Modal)特点:

  • 阻塞父窗口操作
  • 必须处理完当前窗口才能继续
  • 适用于:确认对话框、设置页面、数据录入

非模态窗口(Modeless)特点:

  • 不阻塞父窗口
  • 可以同时操作多个窗口
  • 适用于:工具栏、实时监控、辅助功能
C#
// 模态窗口显示 SecondWindow modal = new SecondWindow(); bool? result = modal.ShowDialog(); // 阻塞执行 // 非模态窗口显示 ThirdWindow modeless = new ThirdWindow(); modeless.Show(); // 立即返回,不阻塞

🛠️ 窗口间通信的三种方式

1️⃣ 构造函数传参(单向传递)

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

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

C#
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; }

3️⃣ 事件机制(实时通信)

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

🔧 完整实战代码

📋 主窗口核心代码

C#
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, RoutedEventArgs e) { ThirdWindow thirdWindow = new ThirdWindow(txtUserName.Text); thirdWindow.Owner = this; thirdWindow.DataReceived += (sender, data) => { AddMessage($"实时消息:{data}"); }; openWindows.Add(thirdWindow); thirdWindow.Show(); // 非阻塞显示 } }

🎨 子窗口设计要点

模态窗口(数据收集)

C#
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}年"; } }

非模态窗口(实时交互)

C#
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(); } } }

完整代码

MainWindow

C#
<Window x:Class="AppWpfWindows.MainWindow" 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:local="clr-namespace:AppWpfWindows" mc:Ignorable="d" Title="MainWindow" Height="620 " Width="800"> <Grid Margin="20"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="多窗口应用程序演示" FontSize="24" FontWeight="Bold" HorizontalAlignment="Center" Foreground="#2E8B57" Margin="0,0,0,20"/> <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,10"> <TextBlock Text="输入您的姓名:" VerticalAlignment="Center" FontSize="14" Width="100"/> <TextBox x:Name="txtUserName" Width="200" Height="30" FontSize="14" Padding="5"/> </StackPanel> <GroupBox Grid.Row="2" Header="窗口操作" Margin="0,20,0,10" FontWeight="Bold" Foreground="#2E8B57"> <StackPanel Margin="10"> <Button x:Name="btnOpenSecond" Content="打开第二个窗口 (模态)" Height="35" Margin="5" FontSize="14" Background="#87CEEB" BorderBrush="#4682B4" Click="btnOpenSecond_Click"/> <Button x:Name="btnOpenThird" Content="打开第三个窗口 (非模态)" Height="35" Margin="5" FontSize="14" Background="#98FB98" BorderBrush="#32CD32" Click="btnOpenThird_Click"/> <Button x:Name="btnShowAllWindows" Content="显示所有打开的窗口" Height="35" Margin="5" FontSize="14" Background="#F0E68C" BorderBrush="#DAA520" Click="btnShowAllWindows_Click"/> </StackPanel> </GroupBox> <GroupBox Grid.Row="3" Header="消息日志" Margin="0,10" FontWeight="Bold" Foreground="#2E8B57"> <ScrollViewer Height="100" Margin="5"> <TextBlock x:Name="txtMessages" FontSize="12" TextWrapping="Wrap" Foreground="#333"/> </ScrollViewer> </GroupBox> <TextBlock Grid.Row="4" x:Name="txtWindowInfo" FontSize="12" Foreground="#666" VerticalAlignment="Bottom" Margin="0,10"/> <Button Grid.Row="5" x:Name="btnExit" Content="退出应用程序" Height="30" Width="120" Background="#FFB6C1" BorderBrush="#DC143C" HorizontalAlignment="Right" Click="btnExit_Click"/> </Grid> </Window>
C#
using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace AppWpfWindows { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { private List<Window> openWindows = new List<Window>(); public MainWindow() { InitializeComponent(); this.Loaded += MainWindow_Loaded; this.Closing += MainWindow_Closing; } private void MainWindow_Loaded(object sender, RoutedEventArgs e) { AddMessage("主窗口已加载"); UpdateWindowInfo(); } private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) { // 关闭所有子窗口 foreach (var window in openWindows.ToList()) { if (window.IsLoaded) { window.Close(); } } } private void btnOpenSecond_Click(object sender, RoutedEventArgs e) { try { string userName = txtUserName.Text.Trim(); if (string.IsNullOrEmpty(userName)) { MessageBox.Show("请先输入您的姓名!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); return; } SecondWindow secondWindow = new SecondWindow(userName); secondWindow.Owner = this; // 设置父窗口 secondWindow.WindowClosed += SecondWindow_WindowClosed; openWindows.Add(secondWindow); AddMessage($"正在以模态方式打开第二个窗口,用户:{userName}"); // 模态显示 bool? result = secondWindow.ShowDialog(); if (result == true) { AddMessage("第二个窗口返回了确定结果"); } else { AddMessage("第二个窗口被取消或关闭"); } } catch (Exception ex) { MessageBox.Show($"打开窗口时发生错误:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } private void btnOpenThird_Click(object sender, RoutedEventArgs e) { try { string userName = txtUserName.Text.Trim(); if (string.IsNullOrEmpty(userName)) { userName = "匿名用户"; } ThirdWindow thirdWindow = new ThirdWindow(userName); thirdWindow.Owner = this; // 设置父窗口 thirdWindow.WindowClosed += ThirdWindow_WindowClosed; thirdWindow.DataReceived += ThirdWindow_DataReceived; openWindows.Add(thirdWindow); AddMessage($"正在以非模态方式打开第三个窗口,用户:{userName}"); // 非模态显示 thirdWindow.Show(); UpdateWindowInfo(); } catch (Exception ex) { MessageBox.Show($"打开窗口时发生错误:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } private void btnShowAllWindows_Click(object sender, RoutedEventArgs e) { var openWindowsList = openWindows.Where(w => w.IsLoaded).ToList(); if (openWindowsList.Count == 0) { AddMessage("当前没有打开的子窗口"); return; } string windowsList = string.Join("\n", openWindowsList.Select(w => $"- {w.Title} ({w.GetType().Name})")); MessageBox.Show($"当前打开的窗口:\n{windowsList}", "窗口列表", MessageBoxButton.OK, MessageBoxImage.Information); AddMessage($"显示了 {openWindowsList.Count} 个打开的窗口信息"); } private void btnExit_Click(object sender, RoutedEventArgs e) { if (MessageBox.Show("确定要退出应用程序吗?", "确认退出", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) { Application.Current.Shutdown(); } } private void SecondWindow_WindowClosed(object sender, EventArgs e) { if (sender is SecondWindow window) { openWindows.Remove(window); AddMessage($"第二个窗口已关闭,返回数据:{window.ReturnData}"); UpdateWindowInfo(); } } private void ThirdWindow_WindowClosed(object sender, EventArgs e) { if (sender is ThirdWindow window) { openWindows.Remove(window); AddMessage("第三个窗口已关闭"); UpdateWindowInfo(); } } private void ThirdWindow_DataReceived(object sender, string data) { AddMessage($"从第三个窗口接收到数据:{data}"); } private void AddMessage(string message) { string timeStamp = DateTime.Now.ToString("HH:mm:ss"); txtMessages.Text += $"[{timeStamp}] {message}\n"; // 自动滚动到底部 if (txtMessages.Parent is ScrollViewer scrollViewer) { scrollViewer.ScrollToBottom(); } } private void UpdateWindowInfo() { var openCount = openWindows.Count(w => w.IsLoaded); txtWindowInfo.Text = $"窗口信息:主窗口 | 打开的子窗口数量:{openCount}"; } } }

SecondWindow

C#
<Window x:Class="AppWpfWindows.SecondWindow" 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:local="clr-namespace:AppWpfWindows" mc:Ignorable="d" Title="SecondWindow" Height="500" Width="800"> <Grid Margin="20"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" x:Name="txtWelcome" FontSize="18" FontWeight="Bold" HorizontalAlignment="Center" Foreground="#8B4513" Margin="0,0,0,15"/> <GroupBox Grid.Row="1" Header="数据输入" Margin="0,10" FontWeight="Bold" Foreground="#8B4513"> <StackPanel Margin="10"> <StackPanel Orientation="Horizontal" Margin="0,5"> <TextBlock Text="选择您的职业:" Width="100" VerticalAlignment="Center"/> <ComboBox x:Name="cmbProfession" Width="200" Height="25"> <ComboBoxItem Content="软件工程师"/> <ComboBoxItem Content="产品经理"/> <ComboBoxItem Content="UI设计师"/> <ComboBoxItem Content="测试工程师"/> <ComboBoxItem Content="其他"/> </ComboBox> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0,5"> <TextBlock Text="工作经验:" Width="100" VerticalAlignment="Center"/> <Slider x:Name="sliderExperience" Width="150" Minimum="0" Maximum="20" Value="1" TickFrequency="1" IsSnapToTickEnabled="True"/> <TextBlock x:Name="txtExperienceValue" Text="1 年" VerticalAlignment="Center" Margin="10,0"/> </StackPanel> </StackPanel> </GroupBox> <GroupBox Grid.Row="2" Header="个人偏好" Margin="0,10" FontWeight="Bold" Foreground="#8B4513"> <StackPanel Margin="10"> <CheckBox x:Name="chkNewsletter" Content="订阅技术通讯" Margin="0,3"/> <CheckBox x:Name="chkUpdates" Content="接收产品更新" Margin="0,3"/> <CheckBox x:Name="chkPromotions" Content="接收优惠信息" Margin="0,3"/> </StackPanel> </GroupBox> <GroupBox Grid.Row="3" Header="备注" Margin="0,10" FontWeight="Bold" Foreground="#8B4513"> <TextBox x:Name="txtComments" TextWrapping="Wrap" AcceptsReturn="True" Margin="5" VerticalScrollBarVisibility="Auto"/> </GroupBox> <StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,15,0,0"> <Button x:Name="btnConfirm" Content="确认" Width="80" Height="30" Margin="5" Background="#90EE90" BorderBrush="#32CD32" Click="btnConfirm_Click"/> <Button x:Name="btnCancel" Content="取消" Width="80" Height="30" Margin="5" Background="#FFB6C1" BorderBrush="#DC143C" Click="btnCancel_Click"/> </StackPanel> </Grid> </Window>
C#
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace AppWpfWindows { /// <summary> /// Interaction logic for SecondWindow.xaml /// </summary> public partial class SecondWindow : Window { public event EventHandler WindowClosed; public string ReturnData { get; private set; } = "无数据"; private string userName; public SecondWindow(string userName) { InitializeComponent(); this.userName = userName; this.Loaded += SecondWindow_Loaded; this.Closing += SecondWindow_Closing; // 绑定滑块值变化事件 sliderExperience.ValueChanged += SliderExperience_ValueChanged; } private void SecondWindow_Loaded(object sender, RoutedEventArgs e) { txtWelcome.Text = $"欢迎,{userName}!"; // 设置默认值 cmbProfession.SelectedIndex = 0; UpdateExperienceDisplay(); } private void SecondWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) { WindowClosed?.Invoke(this, EventArgs.Empty); } private void SliderExperience_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) { UpdateExperienceDisplay(); } private void UpdateExperienceDisplay() { if (txtExperienceValue != null) { int years = (int)sliderExperience.Value; txtExperienceValue.Text = $"{years} 年"; } } private void btnConfirm_Click(object sender, RoutedEventArgs e) { try { // 验证必要输入 if (cmbProfession.SelectedItem == null) { MessageBox.Show("请选择您的职业!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); return; } // 收集数据 var selectedProfession = ((ComboBoxItem)cmbProfession.SelectedItem).Content.ToString(); var experience = (int)sliderExperience.Value; var preferences = new List<string>(); if (chkNewsletter.IsChecked == true) preferences.Add("技术通讯"); if (chkUpdates.IsChecked == true) preferences.Add("产品更新"); if (chkPromotions.IsChecked == true) preferences.Add("优惠信息"); var comments = txtComments.Text.Trim(); // 构建返回数据 ReturnData = $"职业: {selectedProfession}, " + $"经验: {experience}年, " + $"偏好: {string.Join(",", preferences)}" + (string.IsNullOrEmpty(comments) ? "" : $", 备注: {comments}"); // 显示确认信息 var result = MessageBox.Show( $"确认提交以下信息?\n\n{ReturnData}", "确认信息", MessageBoxButton.YesNo, MessageBoxImage.Question); if (result == MessageBoxResult.Yes) { this.DialogResult = true; this.Close(); } } catch (Exception ex) { MessageBox.Show($"处理数据时发生错误:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } private void btnCancel_Click(object sender, RoutedEventArgs e) { ReturnData = "用户取消操作"; this.DialogResult = false; this.Close(); } } }

ThirdWindow

C#
<Window x:Class="AppWpfWindows.ThirdWindow" 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:local="clr-namespace:AppWpfWindows" mc:Ignorable="d" Title="ThirdWindow" Height="500" Width="800"> <Grid Margin="15"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" x:Name="txtTitle" FontSize="20" FontWeight="Bold" HorizontalAlignment="Center" Foreground="#4B0082" Margin="0,0,0,15"/> <GroupBox Grid.Row="1" Header="实时信息" Margin="0,5" FontWeight="Bold" Foreground="#4B0082"> <StackPanel Margin="10"> <StackPanel Orientation="Horizontal" Margin="0,5"> <TextBlock Text="当前时间:" Width="80" VerticalAlignment="Center"/> <TextBlock x:Name="txtCurrentTime" FontSize="14" FontWeight="Bold" Foreground="#8B008B"/> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0,5"> <TextBlock Text="窗口状态:" Width="80" VerticalAlignment="Center"/> <TextBlock x:Name="txtWindowStatus" FontSize="12" Foreground="#8B008B"/> </StackPanel> </StackPanel> </GroupBox> <GroupBox Grid.Row="2" Header="消息中心" Margin="0,10" FontWeight="Bold" Foreground="#4B0082"> <Grid Margin="10"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="发送消息到主窗口:" Margin="0,0,0,5"/> <TextBox Grid.Row="1" x:Name="txtMessage" TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Auto" FontSize="12" Margin="0,0,0,10"/> <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right"> <Button x:Name="btnSendMessage" Content="发送消息" Width="80" Height="25" Margin="5,0" Background="#DDA0DD" BorderBrush="#9370DB" Click="btnSendMessage_Click"/> <Button x:Name="btnClearMessage" Content="清除" Width="60" Height="25" Margin="5,0" Background="#F0E68C" BorderBrush="#DAA520" Click="btnClearMessage_Click"/> </StackPanel> </Grid> </GroupBox> <GroupBox Grid.Row="3" Header="窗口操作" Margin="0,10" FontWeight="Bold" Foreground="#4B0082"> <StackPanel Orientation="Horizontal" Margin="10" HorizontalAlignment="Center"> <Button x:Name="btnMinimize" Content="最小化" Width="70" Height="25" Margin="5" Background="#87CEEB" BorderBrush="#4682B4" Click="btnMinimize_Click"/> <Button x:Name="btnToggleTopmost" Content="置顶切换" Width="70" Height="25" Margin="5" Background="#98FB98" BorderBrush="#32CD32" Click="btnToggleTopmost_Click"/> <Button x:Name="btnChangePosition" Content="改变位置" Width="70" Height="25" Margin="5" Background="#F0E68C" BorderBrush="#DAA520" Click="btnChangePosition_Click"/> </StackPanel> </GroupBox> <Button Grid.Row="4" x:Name="btnClose" Content="关闭窗口" Width="100" Height="30" Margin="0,15,0,0" Background="#FFB6C1" BorderBrush="#DC143C" HorizontalAlignment="Right" Click="btnClose_Click"/> </Grid> </Window>
C#
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using System.Windows.Threading; namespace AppWpfWindows { /// <summary> /// Interaction logic for ThirdWindow.xaml /// </summary> public partial class ThirdWindow : Window { public event EventHandler WindowClosed; public event EventHandler<string> DataReceived; private DispatcherTimer timer; private string userName; private Random random = new Random(); public ThirdWindow(string userName) { InitializeComponent(); this.userName = userName; this.Loaded += ThirdWindow_Loaded; this.Closing += ThirdWindow_Closing; this.StateChanged += ThirdWindow_StateChanged; InitializeTimer(); } private void ThirdWindow_Loaded(object sender, RoutedEventArgs e) { txtTitle.Text = $"{userName} 的工作空间"; UpdateWindowStatus(); // 设置窗口初始位置(相对于父窗口偏移) if (this.Owner != null) { this.Left = this.Owner.Left + 50; this.Top = this.Owner.Top + 50; } timer.Start(); } private void ThirdWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) { timer?.Stop(); WindowClosed?.Invoke(this, EventArgs.Empty); } private void ThirdWindow_StateChanged(object sender, EventArgs e) { UpdateWindowStatus(); } private void InitializeTimer() { timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; timer.Tick += Timer_Tick; } private void Timer_Tick(object sender, EventArgs e) { txtCurrentTime.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); } private void UpdateWindowStatus() { if (txtWindowStatus != null) { string status = this.WindowState switch { WindowState.Normal => "正常", WindowState.Minimized => "最小化", WindowState.Maximized => "最大化", _ => "未知" }; string topmost = this.Topmost ? " | 置顶" : ""; txtWindowStatus.Text = $"{status}{topmost}"; } } private void btnSendMessage_Click(object sender, RoutedEventArgs e) { string message = txtMessage.Text.Trim(); if (string.IsNullOrEmpty(message)) { MessageBox.Show("请输入要发送的消息!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); return; } string fullMessage = $"[来自 {userName}]: {message}"; DataReceived?.Invoke(this, fullMessage); MessageBox.Show("消息已发送到主窗口!", "成功", MessageBoxButton.OK, MessageBoxImage.Information); txtMessage.Clear(); } private void btnClearMessage_Click(object sender, RoutedEventArgs e) { txtMessage.Clear(); txtMessage.Focus(); } private void btnMinimize_Click(object sender, RoutedEventArgs e) { this.WindowState = WindowState.Minimized; } private void btnToggleTopmost_Click(object sender, RoutedEventArgs e) { this.Topmost = !this.Topmost; UpdateWindowStatus(); string status = this.Topmost ? "已启用" : "已禁用"; MessageBox.Show($"窗口置顶功能{status}", "窗口置顶", MessageBoxButton.OK, MessageBoxImage.Information); } private void btnChangePosition_Click(object sender, RoutedEventArgs e) { // 随机改变窗口位置 double screenWidth = SystemParameters.PrimaryScreenWidth; double screenHeight = SystemParameters.PrimaryScreenHeight; double newLeft = random.Next(0, (int)(screenWidth - this.Width)); double newTop = random.Next(0, (int)(screenHeight - this.Height)); this.Left = newLeft; this.Top = newTop; MessageBox.Show($"窗口已移动到新位置\n坐标: ({newLeft:F0}, {newTop:F0})", "位置变更", MessageBoxButton.OK, MessageBoxImage.Information); } private void btnClose_Click(object sender, RoutedEventArgs e) { if (MessageBox.Show("确定要关闭此窗口吗?", "确认关闭", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) { this.Close(); } } } }

image.png

image.png

image.png

image.png

⚠️ 开发中的常见陷阱

🚨 内存泄漏风险

C#
// ❌ 错误做法:忘记移除事件订阅 thirdWindow.DataReceived += SomeHandler; // ✅ 正确做法:窗口关闭时清理资源 private void ThirdWindow_Closing(object sender, CancelEventArgs e) { timer?.Stop(); // 停止定时器 DataReceived = null; // 清理事件订阅 }

🚨 窗口层级管理

C#
// ⭐ 设置父窗口,确保层级关系 secondWindow.Owner = this; // ⭐ 应用关闭时清理所有子窗口 private void MainWindow_Closing(object sender, CancelEventArgs e) { foreach (var window in openWindows.ToList()) { if (window.IsLoaded) window.Close(); } }

🚨 线程安全问题

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

🎯 高级应用场景

💼 企业级应用架构

在实际项目中,多窗口开发常用于:

  • 模块化设计:每个功能独立窗口
  • 权限控制:根据用户角色显示不同窗口
  • 数据流管理:窗口间的数据同步和验证

🔄 窗口状态管理

C#
// 窗口状态持久化 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; // ... 其他属性恢复 } }

🎁 收藏级代码模板

📝 通用窗口基类

C#
public abstract class BaseWindow : Window { protected virtual void OnWindowLoaded() { this.Owner = Application.Current.MainWindow; this.WindowStartupLocation = WindowStartupLocation.CenterOwner; } protected virtual void OnWindowClosing() { // 子类重写实现清理逻辑 } }

🔗 窗口管理器

C#
public static class WindowManager { private static readonly Dictionary<Type, Window> _windows = new(); public static T ShowSingle<T>() where T : Window, new() { if (_windows.TryGetValue(typeof(T), out var existing)) { existing.Activate(); return (T)existing; } var window = new T(); _windows[typeof(T)] = window; window.Closed += (s, e) => _windows.Remove(typeof(T)); window.Show(); return window; } }

🎊 总结与展望

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

  1. 🎯 窗口类型选择:根据使用场景选择模态或非模态窗口
  2. 🔄 数据通信机制:构造函数传参、属性返回、事件机制三种方式
  3. 🛡️ 资源管理策略:避免内存泄漏,正确处理窗口生命周期

掌握这些技术,你就能构建出用户体验优秀、架构清晰的桌面应用程序。在现代软件开发中,良好的多窗口设计不仅提升了用户体验,更体现了开发者的技术水准。


💡 思考题:

  1. 在你的项目中,如何设计窗口间的数据校验机制?
  2. 对于复杂的企业级应用,你会如何规划窗口的层级结构?

📚 延伸学习建议:

  • MVVM模式在多窗口中的应用
  • Prism框架的区域导航机制
  • WPF自定义控件开发

觉得这篇文章对你有帮助吗?请分享给更多需要的C#开发同行,让我们一起提升技术水平!🚀

🚀 WPF多窗口开发实战:从入门到精通的完整指南

相关信息

通过网盘分享的文件:AppWpfWindows.zip 链接: https://pan.baidu.com/s/1c7tqDJu9-yEaidHo6TV20g?pwd=wzn3 提取码: wzn3 --来自百度网盘超级会员v9的分享

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!