首页 > 解决方案 > 关闭启动画面窗口并打开应用程序主窗口时出现 InvalidCastException 错误

问题描述

我有一个遵循 Model View ("MainWindowView") ViewModel ("MainWindowViewModel") 模式的 C# WPF 应用程序。一切正常。没有任何类型的错误消息。

但是,当我尝试使用以下代码在应用程序开始时实现启动画面窗口(“SplashScreenWindow”)时,事情就崩溃了:https ://riptutorial.com/wpf/example/25400/creating-splash-screen-带有进度报告的窗口

启动画面窗口的代码完全可以正常工作:启动画面窗口出现并正确关闭。但是,我无法从 SplashScreenWindow 转换到 MainWindowView,收到以下“抛出异常”消息:

System.Windows.Markup.XamlParseException: ''对匹配指定绑定约束的类型'Notes.ViewModels.MainWindowViewModel' 的构造函数的调用引发了异常。' 行号“20”和行位置“10”。

内部异常 InvalidCastException:无法将“Notes.Views.SplashScreenWindow”类型的对象转换为“Notes.Views.MainWindowView”类型。

请注意,MainWindowViewModel 中的第 20 行和第 10 行位置是以下代码中的 viewmodels:MainWindowViewModel 行:

<Window.DataContext>
    <viewmodels:MainWindowViewModel />
</Window.DataContext>

再次注意,如果没有启动屏幕窗口,基于 MainWindowView 和 MainWindowViewModel 的应用程序完全可以正常工作。我被卡住了,因为当我在网上搜索与错误消息的组件相关的项目时,我似乎没有找到与关闭窗口和打开 MainWindow 远程相关的任何内容。任何有关可能导致此错误的建议将不胜感激。谢谢!!

以下是各种代码片段:

应用程序.xaml

<Application x:Class="Notes.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:sys="clr-namespace:System;assembly=mscorlib"
         xmlns:local="clr-namespace:Notes" >
</Application>  

应用程序.xaml.cs

(来源: https ://riptutorial.com/wpf/example/25400/creating-splash-screen-window-with-progress-reporting )

using Notes.Views;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace Notes
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            //initialize the splash screen and set it as the application main window
            var splashScreen = new SplashScreenWindow();
            this.MainWindow = splashScreen;
            splashScreen.Show();

            //in order to ensure the UI stays responsive, we need to
            //do the work on a different thread
            Task.Factory.StartNew(() =>
            {
                //we need to do the work in batches so that we can report progress
                for (int i = 1; i <= 100; i++)
                {
                    //simulate a part of work being done
                    System.Threading.Thread.Sleep(30);

                    //because we're not on the UI thread, we need to use the Dispatcher
                    //associated with the splash screen to update the progress bar
                    splashScreen.Dispatcher.Invoke(() => splashScreen.Progress = i);
                }

                //once we're done we need to use the Dispatcher
                //to create and show the main window
                this.Dispatcher.Invoke(() =>
                {
                    //initialize the main window, set it as the application main window
                    //and close the splash screen
                    var mainWindowView = new MainWindowView();
                    this.MainWindow = mainWindowView;
                    mainWindowView.Show();
                    splashScreen.Close();
                });
            });
        }
    }
}

SplashScreenWindow.xaml:

<Window x:Class="Notes.Views.SplashScreenWindow"
        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:viewmodels="clr-namespace:Notes.ViewModels"
        xmlns:local="clr-namespace:Notes.Views"
        mc:Ignorable="d"
        Title="SplashScreenWindow" Height="300" Width="500" WindowStartupLocation="CenterScreen" ResizeMode="NoResize" WindowStyle="None" Background="DarkBlue">

    <Grid>

        <ProgressBar x:Name="progressBar" />
        <TextBlock HorizontalAlignment="Center"
                   VerticalAlignment="Center">Loading...</TextBlock>

    </Grid>
    
</Window>

SplashScreenWindow.xaml.cs:

using System.Windows;

namespace Notes.Views
{
    /// <summary>
    /// Interaction logic for SplashScreenWindow.xaml
    /// </summary>
    public partial class SplashScreenWindow : Window
    {
        public SplashScreenWindow()
        {
            InitializeComponent();
        }

        public double Progress
        {
            get { return progressBar.Value; }
            set { progressBar.Value = value; }
        }
    }
}

MainWindowView.xaml.cs

using Notes.Models;
using Notes.ViewModels;
using System;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Documents;

namespace Notes.Views
{
    /// <summary>
    /// Interaction logic for MainWindowView.xaml
    /// </summary>
    public partial class MainWindowView : Window
    {
        public MainWindowView()
        {
            InitializeComponent();
        }
    }
}

MainWindowView.xaml

<Window x:Class="Notes.Views.MainWindowView"
        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:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:viewmodels="clr-namespace:Notes.ViewModels"
        xmlns:models="clr-namespace:Notes.Models"
        xmlns:local="clr-namespace:Notes.Views"
        xmlns:usercontrols="clr-namespace:Notes.UserControls"
        xmlns:converters="clr-namespace:Notes.Converters"
        mc:Ignorable="d"
        Background="Black"
        d:DesignWidth="2736" d:DesignHeight="1500"
        WindowStartupLocation="CenterScreen"
        MinWidth="1550" MinHeight="1221">

    <Window.DataContext>
        <viewmodels:MainWindowViewModel />
    </Window.DataContext>
    
</Window>

查看模型构造函数

public MainWindowViewModel()
{
    // Assign application settings to application variables
    GetSettings();
    // Establish connection to database
    DatabaseMethods.EstablishConnection(DatabasesFolderPath, DatabaseMainFileName);
    // Open connection to database
    DatabaseMethods.OpenConnection();
    // Initialize SQL query statement
    SQLMethods.InitializeSQLComponents();

    InitializeVariables();
}

标签: c#wpfxaml

解决方案


您面临的问题是MainWindowView在您的视图模型中引用的结果。不推荐这样做,因为这种依赖通常会带来很多问题。建议是删除这种依赖关系。

我真的不喜欢这样建议,但是如果想继续MainWindowView在您的视图模型中引用,您应该将视图注入到视图模型中。将构造函数添加到MainWindowViewModel接受MainWindowView. 然后MainWindowViewModel从 XAML 中删除实例化。现在您手动连接层次结构:

splashScreen.Close();

var mainWindow = new MainWindowView(); 
var viewModel = new MainWindowViewModel(mainWindow); 
mainWindow.DataContext = viewModel; 
mainWindow.Show();

但我建议不要这样做。不能强调这一点。您可以看到和
之间的循环依赖(两个类相互需要)。这种特殊的不需要的依赖总是会暴露出一个严重的设计问题。只需重构您的设计,这样就不需要. 这非常简单,可能只需要更改您的一般编程风格/方法。MainWindowViewMainWindowViewModelMainWindowViewModelMainWindowView


请用这个现代化版本替换您的代码。它需要 .NET 4.5(使用IProgress<T>)。此版本应该可以解决您的问题:

// Declare method as async
protected override async void OnStartup(StartupEventArgs e)
{
  base.OnStartup(e);

  // Initialize the splash screen. 
  // The first displayed Window will be automatically assigned to Application.MainWindow
  var splashScreen = new SplashScreenWindow();
  splashScreen.Show();
      
  // Because when reporting progress we won't be on the UI thread, we need to use an instance of IProgress<T>, which is
  // associated with the UI thread, to update the progress bar
  IProgress<double> progressReporter = new Progress<double>(value => splashScreen.Progress = value);

  // In order to ensure the UI stays responsive, we need to
  // execute the work asynchronously
  await Task.Run(async () =>
  {
    for (int i = 1; i <= 100; i++)
    {
      // simulate a part of work being done
      await Task.Delay(30);

      // Post progress to the UI thread
      progressReporter.Report(i);
    }
  });

  splashScreen.Close();

  // Because no Window is currently displayed, 
  // the first displayed Window will be automatically assigned to Application.MainWindow again
  var mainWindow = new MainWindowView();
  mainWindow.Show();
}


推荐阅读