WPF多窗口開發(fā)實(shí)戰(zhàn):從入門到精通的完整指南
你是否還在為WPF應(yīng)用中的窗口跳轉(zhuǎn)而頭疼?多個(gè)窗口之間如何優(yōu)雅地傳遞數(shù)據(jù)?模態(tài)和非模態(tài)窗口有什么區(qū)別?其實(shí)這里與Winform基本一回事,這里講的不是導(dǎo)航。作為一名C#開發(fā)者,掌握多窗口開發(fā)技巧是構(gòu)建專業(yè)級桌面應(yīng)用的必備技能。
今天,我將通過一個(gè)完整的實(shí)戰(zhàn)項(xiàng)目,帶你從零開始掌握WPF多窗口開發(fā)的所有核心技術(shù)。這不僅僅是理論講解,而是一套可以直接應(yīng)用到項(xiàng)目中的完整解決方案!
為什么需要多窗口開發(fā)?
現(xiàn)實(shí)開發(fā)痛點(diǎn)
在實(shí)際項(xiàng)目中,我們經(jīng)常遇到這些場景:
- 設(shè)置窗口需要獨(dú)立的配置界面
- 數(shù)據(jù)錄入復(fù)雜表單需要分步驟完成
- 信息展示詳情頁面需要獨(dú)立顯示
- 工具窗口調(diào)試或輔助功能窗口
單窗口應(yīng)用雖然簡單,但用戶體驗(yàn)往往不夠友好,多窗口設(shè)計(jì)能帶來更好的交互體驗(yàn)和功能分離。
核心技術(shù)解析
模態(tài) vs 非模態(tài)窗口
模態(tài)窗口(Modal)特點(diǎn):
- 阻塞父窗口操作
- 必須處理完當(dāng)前窗口才能繼續(xù)
- 適用于:確認(rèn)對話框、設(shè)置頁面、數(shù)據(jù)錄入
非模態(tài)窗口(Modeless)特點(diǎn):
- 不阻塞父窗口
- 可以同時(shí)操作多個(gè)窗口
- 適用于:工具欄、實(shí)時(shí)監(jiān)控、輔助功能
// 模態(tài)窗口顯示
SecondWindow modal = new SecondWindow();
bool? result = modal.ShowDialog(); // 阻塞執(zhí)行
// 非模態(tài)窗口顯示
ThirdWindow modeless = new ThirdWindow();
modeless.Show(); // 立即返回,不阻塞窗口間通信的三種方式
1?? 構(gòu)造函數(shù)傳參(單向傳遞)
public SecondWindow(string userName)
{
InitializeComponent();
this.userName = userName;
txtWelcome.Text = $"歡迎,{userName}!";
}2?? 屬性返回值(模態(tài)窗口返回?cái)?shù)據(jù))
public class SecondWindow : Window
{
publicstring ReturnData { get; privateset; } = "無數(shù)據(jù)";
private void btnConfirm_Click(object sender, RoutedEventArgs e)
{
ReturnData = "用戶提交的數(shù)據(jù)";
this.DialogResult = true; // 關(guān)鍵:設(shè)置返回值
}
}
// 主窗口接收
bool? result = secondWindow.ShowDialog();
if (result == true)
{
string data = secondWindow.ReturnData;
}3?? 事件機(jī)制(實(shí)時(shí)通信)
public class ThirdWindow : Window
{
public event EventHandler<string> DataReceived;
private void SendMessage()
{
DataReceived?.Invoke(this, "來自子窗口的消息");
}
}
// 主窗口訂閱事件
thirdWindow.DataReceived += (sender, data) => {
AddMessage($"接收到數(shù)據(jù):{data}");
};完整實(shí)戰(zhàn)代碼
主窗口核心代碼
public partial class MainWindow : Window
{
private List<Window> openWindows = new List<Window>();
// 打開模態(tài)窗口
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; // ? 關(guān)鍵:設(shè)置父窗口
bool? result = secondWindow.ShowDialog();
if (result == true)
{
AddMessage($"接收到數(shù)據(jù):{secondWindow.ReturnData}");
}
}
// 打開非模態(tài)窗口
private void btnOpenThird_Click(object sender, RoutedEventArgs e)
{
ThirdWindow thirdWindow = new ThirdWindow(txtUserName.Text);
thirdWindow.Owner = this;
thirdWindow.DataReceived += (sender, data) => {
AddMessage($"實(shí)時(shí)消息:{data}");
};
openWindows.Add(thirdWindow);
thirdWindow.Show(); // 非阻塞顯示
}
}子窗口設(shè)計(jì)要點(diǎn)
模態(tài)窗口(數(shù)據(jù)收集)
public partial class SecondWindow : Window
{
publicstring ReturnData { get; privateset; }
private void btnConfirm_Click(object sender, RoutedEventArgs e)
{
// ? 數(shù)據(jù)驗(yàn)證
if (cmbProfession.SelectedItem == null)
{
MessageBox.Show("請選擇職業(yè)!");
return;
}
// ? 收集并格式化數(shù)據(jù)
ReturnData = BuildUserData();
this.DialogResult = true; // 設(shè)置返回結(jié)果
}
private string BuildUserData()
{
var profession = ((ComboBoxItem)cmbProfession.SelectedItem).Content;
var experience = (int)sliderExperience.Value;
return $"職業(yè): {profession}, 經(jīng)驗(yàn): {experience}年";
}
}非模態(tài)窗口(實(shí)時(shí)交互)
public partial class ThirdWindow : Window
{
public event EventHandler<string> DataReceived;
private DispatcherTimer timer;
public ThirdWindow(string userName)
{
InitializeComponent();
InitializeTimer(); // ? 初始化定時(shí)器
}
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); // ? 觸發(fā)事件
txtMessage.Clear();
}
}
}完整代碼
MainWindow
<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="多窗口應(yīng)用程序演示"
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="打開第二個(gè)窗口 (模態(tài))"
Height="35" Margin="5" FontSize="14"
Background="#87CEEB" BorderBrush="#4682B4"
Click="btnOpenSecond_Click"/>
<Button x:Name="btnOpenThird" Content="打開第三個(gè)窗口 (非模態(tài))"
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="退出應(yīng)用程序"
Height="30" Width="120"
Background="#FFB6C1" BorderBrush="#DC143C"
HorizontalAlignment="Right"
Click="btnExit_Click"/>
</Grid>
</Window>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)
{
// 關(guān)閉所有子窗口
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; // 設(shè)置父窗口
secondWindow.WindowClosed += SecondWindow_WindowClosed;
openWindows.Add(secondWindow);
AddMessage($"正在以模態(tài)方式打開第二個(gè)窗口,用戶:{userName}");
// 模態(tài)顯示
bool? result = secondWindow.ShowDialog();
if (result == true)
{
AddMessage("第二個(gè)窗口返回了確定結(jié)果");
}
else
{
AddMessage("第二個(gè)窗口被取消或關(guān)閉");
}
}
catch (Exception ex)
{
MessageBox.Show($"打開窗口時(shí)發(fā)生錯(cuò)誤:{ex.Message}", "錯(cuò)誤",
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; // 設(shè)置父窗口
thirdWindow.WindowClosed += ThirdWindow_WindowClosed;
thirdWindow.DataReceived += ThirdWindow_DataReceived;
openWindows.Add(thirdWindow);
AddMessage($"正在以非模態(tài)方式打開第三個(gè)窗口,用戶:{userName}");
// 非模態(tài)顯示
thirdWindow.Show();
UpdateWindowInfo();
}
catch (Exception ex)
{
MessageBox.Show($"打開窗口時(shí)發(fā)生錯(cuò)誤:{ex.Message}", "錯(cuò)誤",
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("當(dāng)前沒有打開的子窗口");
return;
}
string windowsList = string.Join("\n",
openWindowsList.Select(w => $"- {w.Title} ({w.GetType().Name})"));
MessageBox.Show($"當(dāng)前打開的窗口:\n{windowsList}",
"窗口列表", MessageBoxButton.OK, MessageBoxImage.Information);
AddMessage($"顯示了 {openWindowsList.Count} 個(gè)打開的窗口信息");
}
private void btnExit_Click(object sender, RoutedEventArgs e)
{
if (MessageBox.Show("確定要退出應(yīng)用程序嗎?", "確認(rèn)退出",
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($"第二個(gè)窗口已關(guān)閉,返回?cái)?shù)據(jù):{window.ReturnData}");
UpdateWindowInfo();
}
}
private void ThirdWindow_WindowClosed(object sender, EventArgs e)
{
if (sender is ThirdWindow window)
{
openWindows.Remove(window);
AddMessage("第三個(gè)窗口已關(guān)閉");
UpdateWindowInfo();
}
}
private void ThirdWindow_DataReceived(object sender, string data)
{
AddMessage($"從第三個(gè)窗口接收到數(shù)據(jù):{data}");
}
private void AddMessage(string message)
{
string timeStamp = DateTime.Now.ToString("HH:mm:ss");
txtMessages.Text += $"[{timeStamp}] {message}\n";
// 自動(dòng)滾動(dòng)到底部
if (txtMessages.Parent is ScrollViewer scrollViewer)
{
scrollViewer.ScrollToBottom();
}
}
private void UpdateWindowInfo()
{
var openCount = openWindows.Count(w => w.IsLoaded);
txtWindowInfo.Text = $"窗口信息:主窗口 | 打開的子窗口數(shù)量:{openCount}";
}
}
}SecondWindow
<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="數(shù)據(jù)輸入" Margin="0,10"
FontWeight="Bold" Foreground="#8B4513">
<StackPanel Margin="10">
<StackPanel Orientation="Horizontal" Margin="0,5">
<TextBlock Text="選擇您的職業(yè):" Width="100" VerticalAlignment="Center"/>
<ComboBox x:Name="cmbProfession" Width="200" Height="25">
<ComboBoxItem Content="軟件工程師"/>
<ComboBoxItem Content="產(chǎn)品經(jīng)理"/>
<ComboBoxItem Content="UI設(shè)計(jì)師"/>
<ComboBoxItem Content="測試工程師"/>
<ComboBoxItem Content="其他"/>
</ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5">
<TextBlock Text="工作經(jīng)驗(yàn):" 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="個(gè)人偏好" Margin="0,10"
FontWeight="Bold" Foreground="#8B4513">
<StackPanel Margin="10">
<CheckBox x:Name="chkNewsletter" Content="訂閱技術(shù)通訊" Margin="0,3"/>
<CheckBox x:Name="chkUpdates" Content="接收產(chǎn)品更新" Margin="0,3"/>
<CheckBox x:Name="chkPromotions" Content="接收優(yōu)惠信息" 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="確認(rèn)" 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>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;
publicstring ReturnData { get; privateset; } = "無數(shù)據(jù)";
privatestring 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}!";
// 設(shè)置默認(rèn)值
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
{
// 驗(yàn)證必要輸入
if (cmbProfession.SelectedItem == null)
{
MessageBox.Show("請選擇您的職業(yè)!", "提示",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 收集數(shù)據(jù)
var selectedProfession = ((ComboBoxItem)cmbProfession.SelectedItem).Content.ToString();
var experience = (int)sliderExperience.Value;
var preferences = new List<string>();
if (chkNewsletter.IsChecked == true) preferences.Add("技術(shù)通訊");
if (chkUpdates.IsChecked == true) preferences.Add("產(chǎn)品更新");
if (chkPromotions.IsChecked == true) preferences.Add("優(yōu)惠信息");
var comments = txtComments.Text.Trim();
// 構(gòu)建返回?cái)?shù)據(jù)
ReturnData = $"職業(yè): {selectedProfession}, " +
$"經(jīng)驗(yàn): {experience}年, " +
$"偏好: {string.Join(",", preferences)}" +
(string.IsNullOrEmpty(comments) ? "" : $", 備注: {comments}");
// 顯示確認(rèn)信息
var result = MessageBox.Show(
$"確認(rèn)提交以下信息?\n\n{ReturnData}",
"確認(rèn)信息",
MessageBoxButton.YesNo,
MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
this.DialogResult = true;
this.Close();
}
}
catch (Exception ex)
{
MessageBox.Show($"處理數(shù)據(jù)時(shí)發(fā)生錯(cuò)誤:{ex.Message}", "錯(cuò)誤",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
ReturnData = "用戶取消操作";
this.DialogResult = false;
this.Close();
}
}
}ThirdWindow
<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="實(shí)時(shí)信息" Margin="0,5"
FontWeight="Bold" Foreground="#4B0082">
<StackPanel Margin="10">
<StackPanel Orientation="Horizontal" Margin="0,5">
<TextBlock Text="當(dāng)前時(shí)間:" Width="80" VerticalAlignment="Center"/>
<TextBlock x:Name="txtCurrentTime" FontSize="14"
FontWeight="Bold" Foreground="#8B008B"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5">
<TextBlock Text="窗口狀態(tài):" 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="發(fā)送消息到主窗口:" 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="發(fā)送消息"
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="關(guān)閉窗口"
Width="100" Height="30" Margin="0,15,0,0"
Background="#FFB6C1" BorderBrush="#DC143C"
HorizontalAlignment="Right"
Click="btnClose_Click"/>
</Grid>
</Window>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;
privatestring 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();
// 設(shè)置窗口初始位置(相對于父窗口偏移)
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("請輸入要發(fā)送的消息!", "提示",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
string fullMessage = $"[來自 {userName}]: {message}";
DataReceived?.Invoke(this, fullMessage);
MessageBox.Show("消息已發(fā)送到主窗口!", "成功",
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)
{
// 隨機(jī)改變窗口位置
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($"窗口已移動(dòng)到新位置\n坐標(biāo): ({newLeft:F0}, {newTop:F0})",
"位置變更", MessageBoxButton.OK, MessageBoxImage.Information);
}
private void btnClose_Click(object sender, RoutedEventArgs e)
{
if (MessageBox.Show("確定要關(guān)閉此窗口嗎?", "確認(rèn)關(guān)閉",
MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
{
this.Close();
}
}
}
}
圖片
圖片


開發(fā)中的常見陷阱
內(nèi)存泄漏風(fēng)險(xiǎn)
// ? 錯(cuò)誤做法:忘記移除事件訂閱
thirdWindow.DataReceived += SomeHandler;
// ? 正確做法:窗口關(guān)閉時(shí)清理資源
private void ThirdWindow_Closing(object sender, CancelEventArgs e)
{
timer?.Stop(); // 停止定時(shí)器
DataReceived = null; // 清理事件訂閱
}窗口層級管理
// ? 設(shè)置父窗口,確保層級關(guān)系
secondWindow.Owner = this;
// ? 應(yīng)用關(guān)閉時(shí)清理所有子窗口
private void MainWindow_Closing(object sender, CancelEventArgs e)
{
foreach (var window in openWindows.ToList())
{
if (window.IsLoaded) window.Close();
}
}線程安全問題
// ? UI更新必須在主線程執(zhí)行
Dispatcher.Invoke(() => {
txtMessages.Text += $"[{DateTime.Now:HH:mm:ss}] {message}\n";
});高級應(yīng)用場景
企業(yè)級應(yīng)用架構(gòu)
在實(shí)際項(xiàng)目中,多窗口開發(fā)常用于:
- 模塊化設(shè)計(jì)每個(gè)功能獨(dú)立窗口
- 權(quán)限控制根據(jù)用戶角色顯示不同窗口
- 數(shù)據(jù)流管理窗口間的數(shù)據(jù)同步和驗(yàn)證
窗口狀態(tài)管理
// 窗口狀態(tài)持久化
publicclass 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;
// ... 其他屬性恢復(fù)
}
}收藏級代碼模板
通用窗口基類
public abstract class BaseWindow : Window
{
protected virtual void OnWindowLoaded()
{
this.Owner = Application.Current.MainWindow;
this.WindowStartupLocation = WindowStartupLocation.CenterOwner;
}
protected virtual void OnWindowClosing()
{
// 子類重寫實(shí)現(xiàn)清理邏輯
}
}窗口管理器
public staticclass WindowManager
{
privatestatic readonly Dictionary<Type, Window> _windows = new();
publicstatic 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;
}
}總結(jié)與展望
通過本文的完整實(shí)戰(zhàn)演示,我們掌握了WPF多窗口開發(fā)的三個(gè)核心要點(diǎn):
- 窗口類型選擇根據(jù)使用場景選擇模態(tài)或非模態(tài)窗口
- 數(shù)據(jù)通信機(jī)制構(gòu)造函數(shù)傳參、屬性返回、事件機(jī)制三種方式
- 資源管理策略避免內(nèi)存泄漏,正確處理窗口生命周期
掌握這些技術(shù),你就能構(gòu)建出用戶體驗(yàn)優(yōu)秀、架構(gòu)清晰的桌面應(yīng)用程序。在現(xiàn)代軟件開發(fā)中,良好的多窗口設(shè)計(jì)不僅提升了用戶體驗(yàn),更體現(xiàn)了開發(fā)者的技術(shù)水準(zhǔn)。

































