dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.03k stars 1.16k forks source link

System.Windows.Controls.Frame returns NullReferenceException when assigning content property to a variable or when trying to access it directly. #3161

Closed joshman1019 closed 4 years ago

joshman1019 commented 4 years ago

Version: .Net Core 3.1, Development Environment: Visual Studio 2019 Community WPF-MVVM Pattern When attempting to assign System.Windows.Controls.Frame.Content property to a new variable, I am receiving a NullReferenceException, which does not occur in .Net Framework 4.7.1.

The following is an exact copy of my StackOverflow question which has yielded no answers:

I am developing a WPF application using .Net Core 3.1 (MVVM pattern), and I have run across an issue that I have never had in .Net framework on other applications I've developed.

My main window has a frame which hosts the various pages within my application. That frame's content is set by a "PropertyChanged" event fired by the application viewmodel. I am doing it this way, instead of binding directly to the CurrentPage property, because I want to call an animation that fades out the page prior to the next one being loaded.

My application viewmodel looks like this: (NOTE: BaseViewmodel inherits INotifyPropertyChanged and I use PropertyChanged.Fody to handle the property change events)

    public class ApplicationViewmodel : BaseViewmodel
    {
        #region Constructor 

        /// <summary>
        /// Default constructor
        /// </summary>
        public ApplicationViewmodel()
        {
            SetInitialPage(); 
        }

        #endregion

        #region Properties 

        /// <summary>
        /// Current page hosted by the application 
        /// </summary>
        public ApplicationPageTypes CurrentPage { get; set; }

        #endregion

        #region Public Methods 

        /// <summary>
        /// Navigates to a new application page 
        /// </summary>
        /// <param name="page"></param>
        public void GoToPage(ApplicationPageTypes page)
        {
            CurrentPage = page; 
        }

        #endregion

        #region Public Methods 

        /// <summary>
        /// Sets the initial page for the application upon construction 
        /// </summary>
        private void SetInitialPage()
        {
            CurrentPage = ApplicationPageTypes.MainMenu;
        }

        #endregion
    }

My application's main window has a frame component named BaseFrame. That frame's content is set initially by the viewmodel and window constructor. After being initially set, I have subscribed to the PropertyChanged event on the viewmodel, and am specifically watching for CurrentPage to be changed. Once that occurs, the page is supposed to animate out (if a previous page exists within the frame) and then load in the next page. I usually handle this by creating a variable containing the current content, await the animate out, and then allowing the content to be updated. (NOTE: The enum ApplicationPageTypes is extended in my UI project, which fetches the proper page. You will see this extension method called within the window's interaction logic.)

My window's interaction logic looks like this:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            DataContext = SVMC.ApplicationViewmodel; 
            InitializeComponent();
            SetInitialPage();
            EventSubscriber(); 
        }

        private void SetInitialPage()
        {
            BaseFrame.Content = new BasePage(); 
            BaseFrame.Content = SVMC.ApplicationViewmodel.CurrentPage.GetBasePageFromPageType(); 
        }

        public void EventSubscriber()
        {
            SVMC.ApplicationViewmodel.PropertyChanged += async delegate (object sender, PropertyChangedEventArgs e)
            {
                if(e.PropertyName == nameof(SVMC.ApplicationViewmodel.CurrentPage))
                {
                    if (BaseFrame.Content != null)
                    {
                        BasePage currentPage = BaseFrame.Content as BasePage;
                        await currentPage.FadeOut();
                    }
                    BaseFrame.Content = SVMC.ApplicationViewmodel.CurrentPage.GetBasePageFromPageType();
                }
            };
        }
    }

My current BasePage, which all other pages inherit, looks like this:

    public class BasePage<T> : Page
       where T : BaseViewmodel, new()
    {
        public T ViewModel { get; private set; }

        public BasePage()
        {
            ViewModel = new T();
            DataContext = ViewModel;
            Loaded += async delegate (object sender, RoutedEventArgs e)
            {
                await FadeIn(); 
            };
        }

        public async Task FadeOut()
        {
            Opacity = 1;
            DoubleAnimation fadeInAnimation = new DoubleAnimation();
            fadeInAnimation.From = 1;
            fadeInAnimation.To = 0;
            fadeInAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(400));
            fadeInAnimation.EasingFunction = new QuadraticEase();

            Storyboard sb = new Storyboard();
            sb.Children.Add(fadeInAnimation);
            Storyboard.SetTarget(sb, this);
            Storyboard.SetTargetProperty(sb, new PropertyPath(OpacityProperty));
            sb.Begin();
            await Task.Delay(400); 
        }

        public async Task FadeIn()
        {
            Opacity = 0;
            DoubleAnimation fadeOutAnimation = new DoubleAnimation();
            fadeOutAnimation.From = 0;
            fadeOutAnimation.To = 1;
            fadeOutAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(400));
            fadeOutAnimation.EasingFunction = new QuadraticEase();

            Storyboard sb = new Storyboard();
            sb.Children.Add(fadeOutAnimation);
            Storyboard.SetTarget(sb, this);
            Storyboard.SetTargetProperty(sb, new PropertyPath(OpacityProperty));
            sb.Begin();
            await Task.Delay(400);
        }
    }

My problem is that when stepping through, the BaseFrame.Content property passes the first null check, but then fails with a NullReferenceException when I assign the content to a variable.

        public void EventSubscriber()
        {
            SVMC.ApplicationViewmodel.PropertyChanged += async delegate (object sender, PropertyChangedEventArgs e)
            {
                if(e.PropertyName == nameof(SVMC.ApplicationViewmodel.CurrentPage))
                {
                    if (BaseFrame.Content != null)
                    {
                        BasePage currentPage = BaseFrame.Content as BasePage;
                        await currentPage.FadeOut(); <==== ***NULL REFERENCE EXCEPTION HERE***
                    }
                    BaseFrame.Content = SVMC.ApplicationViewmodel.CurrentPage.GetBasePageFromPageType();
                }
            };
        }
joshman1019 commented 4 years ago

Attached are a series of screenshots where I am inspecting the Content property, and am receiving conflicting results.

ExceptionEx1 ExceptionEx2 ExceptionEx3

lindexi commented 4 years ago

@joshman1019 Could you push the demo code to GitHub? An isolated repo would help.

joshman1019 commented 4 years ago

Closing issue as (no matter how deep I dug) I missed the fact that I was attempting to cast to a BasePage when I had created a "BasePage<>" as well. A very big oversight. Thank you for the time.

lindexi commented 4 years ago

@joshman1019 LOL