WPF多窗口開(kāi)發(fā)實(shí)戰(zhàn):從入門到精通的完整指南
你是否還在為WPF應(yīng)用中的窗口跳轉(zhuǎn)而頭疼?多個(gè)窗口之間如何優(yōu)雅地傳遞數(shù)據(jù)?模態(tài)和非模態(tài)窗口有什么區(qū)別?其實(shí)這里與Winform基本一回事,這里講的不是導(dǎo)航。作為一名C#開(kāi)發(fā)者,掌握多窗口開(kāi)發(fā)技巧是構(gòu)建專業(yè)級(jí)桌面應(yīng)用的必備技能。
今天,我將通過(guò)一個(gè)完整的實(shí)戰(zhàn)項(xiàng)目,帶你從零開(kāi)始掌握WPF多窗口開(kāi)發(fā)的所有核心技術(shù)。這不僅僅是理論講解,而是一套可以直接應(yīng)用到項(xiàng)目中的完整解決方案!
為什么需要多窗口開(kāi)發(fā)?
現(xiàn)實(shí)開(kāi)發(fā)痛點(diǎn)
在實(shí)際項(xiàng)目中,我們經(jīng)常遇到這些場(chǎng)景:
- 設(shè)置窗口需要獨(dú)立的配置界面
 - 數(shù)據(jù)錄入復(fù)雜表單需要分步驟完成
 - 信息展示詳情頁(yè)面需要獨(dú)立顯示
 - 工具窗口調(diào)試或輔助功能窗口
 
單窗口應(yīng)用雖然簡(jiǎn)單,但用戶體驗(yàn)往往不夠友好,多窗口設(shè)計(jì)能帶來(lái)更好的交互體驗(yàn)和功能分離。
核心技術(shù)解析
模態(tài) vs 非模態(tài)窗口
模態(tài)窗口(Modal)特點(diǎn):
- 阻塞父窗口操作
 - 必須處理完當(dāng)前窗口才能繼續(xù)
 - 適用于:確認(rèn)對(duì)話框、設(shè)置頁(yè)面、數(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; } = "無(wú)數(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, "來(lái)自子窗口的消息");
    }
}
// 主窗口訂閱事件
thirdWindow.DataReceived += (sender, data) => {
    AddMessage($"接收到數(shù)據(jù):{data}");
};完整實(shí)戰(zhàn)代碼
主窗口核心代碼
public partial class MainWindow : Window
{
    private List<Window> openWindows = new List<Window>();
    // 打開(kāi)模態(tài)窗口
    private void btnOpenSecond_Click(object sender, RoutedEventArgs e)
    {
        string userName = txtUserName.Text.Trim();
        if (string.IsNullOrEmpty(userName))
        {
            MessageBox.Show("請(qǐng)先輸入您的姓名!", "提示");
            return;
        }
        SecondWindow secondWindow = new SecondWindow(userName);
        secondWindow.Owner = this; // ? 關(guān)鍵:設(shè)置父窗口
        bool? result = secondWindow.ShowDialog();
        if (result == true)
        {
            AddMessage($"接收到數(shù)據(jù):{secondWindow.ReturnData}");
        }
    }
    // 打開(kāi)非模態(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("請(qǐng)選擇職業(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="打開(kāi)第二個(gè)窗口 (模態(tài))"
                        Height="35" Margin="5" FontSize="14"
                        Background="#87CEEB" BorderBrush="#4682B4"
                        Click="btnOpenSecond_Click"/>
                <Button x:Name="btnOpenThird" Content="打開(kāi)第三個(gè)窗口 (非模態(tài))"
                        Height="35" Margin="5" FontSize="14"
                        Background="#98FB98" BorderBrush="#32CD32"
                        Click="btnOpenThird_Click"/>
                <Button x:Name="btnShowAllWindows" Content="顯示所有打開(kāi)的窗口"
                        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("請(qǐng)先輸入您的姓名!", "提示",
                                  MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }
                SecondWindow secondWindow = new SecondWindow(userName);
                secondWindow.Owner = this; // 設(shè)置父窗口
                secondWindow.WindowClosed += SecondWindow_WindowClosed;
                openWindows.Add(secondWindow);
                AddMessage($"正在以模態(tài)方式打開(kā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($"打開(kāi)窗口時(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)方式打開(kāi)第三個(gè)窗口,用戶:{userName}");
                // 非模態(tài)顯示
                thirdWindow.Show();
                UpdateWindowInfo();
            }
            catch (Exception ex)
            {
                MessageBox.Show($"打開(kāi)窗口時(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)前沒(méi)有打開(kāi)的子窗口");
                return;
            }
            string windowsList = string.Join("\n",
                openWindowsList.Select(w => $"- {w.Title} ({w.GetType().Name})"));
            MessageBox.Show($"當(dāng)前打開(kāi)的窗口:\n{windowsList}",
                          "窗口列表", MessageBoxButton.OK, MessageBoxImage.Information);
            AddMessage($"顯示了 {openWindowsList.Count} 個(gè)打開(kāi)的窗口信息");
        }
        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 = $"窗口信息:主窗口 | 打開(kāi)的子窗口數(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="測(cè)試工程師"/>
                        <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; } = "無(wú)數(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("請(qǐng)選擇您的職業(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è)置窗口初始位置(相對(duì)于父窗口偏移)
            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("請(qǐng)輸入要發(fā)送的消息!", "提示",
                              MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            string fullMessage = $"[來(lái)自 {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();
            }
        }
    }
}
圖片
圖片


開(kāi)發(fā)中的常見(jiàn)陷阱
內(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; // 清理事件訂閱
}窗口層級(jí)管理
// ? 設(shè)置父窗口,確保層級(jí)關(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();
    }
}線程安全問(wèn)題
// ? UI更新必須在主線程執(zhí)行
Dispatcher.Invoke(() => {
    txtMessages.Text += $"[{DateTime.Now:HH:mm:ss}] {message}\n";
});高級(jí)應(yīng)用場(chǎng)景
企業(yè)級(jí)應(yīng)用架構(gòu)
在實(shí)際項(xiàng)目中,多窗口開(kāi)發(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ù)
    }
}收藏級(jí)代碼模板
通用窗口基類
public abstract class BaseWindow : Window
{
    protected virtual void OnWindowLoaded()
    {
        this.Owner = Application.Current.MainWindow;
        this.WindowStartupLocation = WindowStartupLocation.CenterOwner;
    }
    protected virtual void OnWindowClosing()
    {
        // 子類重寫(xiě)實(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é)與展望
通過(guò)本文的完整實(shí)戰(zhàn)演示,我們掌握了WPF多窗口開(kāi)發(fā)的三個(gè)核心要點(diǎn):
- 窗口類型選擇根據(jù)使用場(chǎng)景選擇模態(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)代軟件開(kāi)發(fā)中,良好的多窗口設(shè)計(jì)不僅提升了用戶體驗(yàn),更體現(xiàn)了開(kāi)發(fā)者的技術(shù)水準(zhǔn)。















 
 
 















 
 
 
 