1
votes

I have a C# WPF application following the Model View ("MainWindowView") ViewModel ("MainWindowViewModel") pattern. Everything works fine. No error messages of any kind.

However, things fall apart when i try to implement a Splash Screen Window ("SplashScreenWindow") at the start of the application using the following code: https://riptutorial.com/wpf/example/25400/creating-splash-screen-window-with-progress-reporting

The code for the Splash Screen Window works completely fine: the Splash Screen Window appears and closes properly. However, I am unable to transition from the SplashScreenWindow to my MainWindowView, getting the following "Exception Thrown" message:

System.Windows.Markup.XamlParseException: ''The invocation of the constructor on type 'Notes.ViewModels.MainWindowViewModel' that matches the specified binding constraints threw an exception.' Line number '20' and line position '10'.'

Inner Exception InvalidCastException: Unable to cast object of type 'Notes.Views.SplashScreenWindow' to type 'Notes.Views.MainWindowView'.

Note that Line 20 and Line Position 10 in MainWindowViewModel is the viewmodels:MainWindowViewModel line in the following code:

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

Note again that without the splash screen window, the application based on MainWindowView and MainWindowViewModel works completely fine. I am stuck because when i search the net for items related to the components of the error message, i don't seem to get to anything that is remotely related to closing a window and opening a MainWindow. Any advice on what might be causing this error would be greatly appreciated. Thanks!!

Here are the various code pieces:

App.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>  

App.xaml.cs

(source: 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>

View Model Constructor

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();
}
1
What is with your view model constructor? Would you mind to post the view model class? Are you referencing any Window there? Btw. the tutorial you are following is terrible. It looks outdated too.BionicCode
@BionicCode: Thank you for looking into my question. I just added the view model constructor at the end of the original post. I am not referencing any window therescorpiotomse
Thank you. Do you reference the main window e.g. Application.Current.MainWindow somewhere in your view model?BionicCode
@BionicCode: Yes i do have a reference to main window in my ViewModel: readonly MainWindowView MainWindowViewInstance = (MainWindowView)Application.Current.MainWindow; I use this to be able to refer to the instance of the MainWindowView.scorpiotomse

1 Answers

0
votes

The problem you are facing is the result of referencing the MainWindowView in your view model. This is not recommended as this kind of dependency generally introduces many problems. The advice is to remove this dependency.

I really don't like to advice this, but if want to continue to reference MainWindowView in your view model, you should inject the view into the view model. Add a constructor to the MainWindowViewModel that accepts a MainWindowView. Then remove the MainWindowViewModel instantiation from XAML. Now you wire up the hierarchy manually:

splashScreen.Close();

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

But I recommend against this. Can't stress this enough.
You can see a circular dependency between MainWindowView and MainWindowViewModel (both classes require each other). This special unwanted dependency always unveils a serious design problem. Simply refactor your design, so that MainWindowViewModel does not need MainWindowView. This is very simple and may only require to change your general programming style/approach.


Please replace your code with this modernized version. It requires .NET 4.5 (to use IProgress<T>). This version should fix your issues:

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