如何在 WPF 中显示窗口而不丢失控件?

Posted

技术标签:

【中文标题】如何在 WPF 中显示窗口而不丢失控件?【英文标题】:How can I show the window in WPF without lost controls? 【发布时间】:2021-12-30 17:08:05 【问题描述】:

我登录时有一个程序,我必须等待从服务器加载数据库。所以我创建了一个“正在加载...”窗口。加载数据库后,这将自动关闭“加载”窗口、“登录”窗口并打开 MainWindow.xaml。 但是我的程序有一个问题:当我在 Login.xaml.cs 中使用 waitForm.show() 时,它运行良好,但是“等待”窗口上的控件,如进度条、文本块,它不显示。如果我使用 waitForm.showdialog (),它将显示进度条和文本块控件。但它不会自动关闭。所以我的 Mainwindow.xaml 没有打开。 是否可以使用 show() 但显示控件? 对不起我的英语不好。谢谢。

Waiting.xaml

<Window x:Class="TMO.Waiting"
        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:TMO"
        mc:Ignorable="d"
        Title="Waiting" Height="100" Width="300" WindowStartupLocation="CenterScreen" WindowStyle="None">    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <ProgressBar Margin="10,20,10,5" Minimum="0" Maximum="100" Name="pbStatus" IsIndeterminate="True" Grid.Row="0"/>
    <TextBlock Grid.Row="1" Margin="0,5,0,5" TextAlignment="Center" VerticalAlignment="Center" FontSize="20" FontWeight="Bold" >Loading...</TextBlock>
    </Grid>
</Window>

Waiting.xaml.cs

using System.Windows;

namespace TMO

    /// <summary>
    /// Interaction logic for Waiting.xaml
    /// </summary>
    public partial class Waiting : Window
    
        public static MessageBoxResult result;
        public Waiting()
        
            InitializeComponent();
        
        public Waiting(Window parent1)
        
            InitializeComponent();             

        
        public void CloseWaiting()
        
            DialogResult = true;
            this.Close();           
               
    

WaitFunc.cs

using System.Threading;
using System.Windows;
namespace TMO

    public class WaitFunc
    
        Waiting wait;
        Thread loadthread;
        public void show()
        
            loadthread = new Thread(new ThreadStart(LoadingProcess));
            loadthread.Start();
        
        public void show(Window parent)
        
            loadthread=new Thread(new ParameterizedThreadStart(LoadingProcess));
            loadthread.Start(parent);
        
        public void Close()
        
            if(wait!=null)
            
                wait.Dispatcher.BeginInvoke(new System.Threading.ThreadStart(wait.CloseWaiting));
                wait=null;
                loadthread=null;    
            
        
        public void LoadingProcess()
        
            wait=new Waiting();
            wait.ShowDialog();
        
        public void LoadingProcess(object parent)
        
            Window parent1 = parent as Window;
            wait=new Waiting(parent1);
            wait.ShowDialog();
        
    

Login.xaml.cs

private void btnLogin_Click(object sender, RoutedEventArgs e)

    this.Hide();
    Waiting waitForm = new Waiting();
    waitForm.Show();      //If I use waitForm.showdialog() it will display all control
    Thread.Sleep(500);
    MainWindow main = new MainWindow(txtUserName.Text);              
    this.Hide();   
    waitForm.Close();
    main.Show();
    this.Close();

【问题讨论】:

线程和窗口确实不能很好地混合。我不是 WPF 人(所以我不能告诉你这样做的正确方法,但是那个额外的线程可能是问题所在。你需要弄清楚如何打开“等待”窗口,以及它何时关闭发送向另一个窗口显示消息。另外,了解await Task.Delay;在用户界面中使用Thread.Sleep 是个坏主意。 谢谢。该程序是用 WinForms 编写的,但现在我将其更改为 WPF。我是 WPF 新手,所以我对 WPF 有很多问题。 对于 Winforms,看看我对这个问题的回答。您也许可以将其适应 WPF:***.com/questions/62936319/… @FlyDog57 关于线程和 Windows 是正确的。在幕后,您无意中在一个线程上创建了一个表单,但在另一个线程上初始化了组件。 C# 中的 Windows 始终在初始应用线程上创建,无论它们在何处初始化。 这里的线程实例太多。也从未见过从 Dispatcher 调用创建线程。总是有新意。不要这样做。 【参考方案1】:

您应该从应用程序的入口点管理应用程序启动顺序的所有窗口。因此,我建议将启动 MainWindow 的逻辑从 Login.xaml.cs 移动到 App.xaml.cs

此外,要创建一个可以显示控件并允许输入处理的 UI 线程,您必须将该线程显式配置为 STA 线程并另外启动 Dispatcher 消息循环。

请注意,如果您在必要时使用异步 API 和后台线程(使用 Task.Run 而不是 Thread),那么您不需要额外的 UI 线程来显示初始屏幕。

您应该将LoginSuccessfulLoginFailed 事件添加到LoginWindow

您的流程可以这样实现:

App.xaml

<Application Startup="App_OnStartup" />

App.xaml.cs

public partial class App : Application

  private Window SplashScreen  get; set; 

  private void App_OnStartup(object sender, StartupEventArgs e)
  
    ShutdownMode = ShutdownMode.OnExplicitShutdown;

    var loginScreen = new LoginWindow();
    loginScreen.LoginSuccessful += RunApplication_OnLoginSuccessful;
    loginScreen.LoginFailed += ShutdownApplication_OnLoginFailed;
    loginScreen.Show();
  

  private void RunApplication_OnLoginSuccessful(object sender, EventArgs e)
  
    var newUiThread = new Thread(new ThreadStart(ShowSplashScreen))
    
      ApartmentState = ApartmentState.STA,
      IsBackground = false,
    ;

    newUiThread.Start();

    var mainWindow = new MainWindow();
    mainWindow.Loaded += CloseSplashScreen_OnMainWindowLoaded;
    mainWindow.Closed += ShutdownApplication_OnMainWindowClosed;

    // TODO::Initialize application

    mainWindow.Show();
    

  private void ShowSplashScreen()
  
    this.SplashScreen = new Window()  Content = "SplashScreen" ;
    this.SplashScreen.Show();

    // Start event queue
    Dispatcher.Run();
    

  private void CloseSplashScreen_OnMainWindowLoaded(object sender, RoutedEventArgs e)
  
    this.SplashScreen.Dispatcher.InvokeAsync(() =>
    
      this.SplashScreen.Close();
      this.SplashScreen.Dispatcher.InvokeShutdown();
    );
    

  private void ShutdownApplication_OnMainWindowClosed(object sender, EventArgs e) => Shutdown();

  private void ShutdownApplication_OnLoginFailed(object sender, EventArgs e) => Shutdown();

【讨论】:

以上是关于如何在 WPF 中显示窗口而不丢失控件?的主要内容,如果未能解决你的问题,请参考以下文章

wpf combobox 关闭窗口后,再次调用不显示已经选择的值?

在 WPF 的网格中显示对其他控件的控件

如何在 webbrowser WPF 顶部打开用户控件作为弹出窗口

测试 WPF 控件而不将其添加到窗口

如何从作为wpf mvvm模式中的窗口打开的视图模型中关闭用户控件?

WPF 将对象从窗口的用户控件传递到另一个窗口的用户控件