Search code examples
c#wpfmvvm

Best way to implement navigation in mvvm for wpf application


I implement MVVM in my current WPF application assignment.

I created a base class which inherit Window and then every other window inherit this class.

public class ApplicationScreenBase : Window
    {
        public ApplicationScreenBase()
        {
            AppMessenger.Register(this, OnMessageToApp);
            this.Unloaded += ApplicationScreenBase_Unloaded;
        }

        private void ApplicationScreenBase_Unloaded(object sender, RoutedEventArgs e)
        {
            AppMessenger.Unregister(this, OnMessageToApp);
        }

        private void OnMessageToApp(AppMessage message)
        {
            switch (message.MessageType)
            {
                case AppMessageType.Navigate:
                    {
                        var CurrentWindow = Activator.CreateInstance(Locator.NavigationPageLocator.LocateNavigateTypeByEnum((NavigationScreens)message.MessageData)) as Window;
                        CurrentWindow.Show();
                        this.Close();
                        break;
                    }
                case AppMessageType.NewWindow:
                    {
                        var CurrentWindow = Activator.CreateInstance(Locator.NavigationPageLocator.LocateNavigateTypeByEnum((NavigationScreens)message.MessageData)) as Window;
                        CurrentWindow.Show();
                        break;
                    }
                case AppMessageType.MessageBox:
                    {
                        MessageBox.Show(message.MessageData.ToString());
                        break;
                    }
                case AppMessageType.Close:
                    {
                        this.Close();
                        break;
                    }
                default:
                    break;
            }
        }
    }

This is my navigation class which return me Type of window to open.

public static class NavigationPageLocator
    {
        public static Type LocateNavigateTypeByEnum(NavigationScreens navigationPage)
        {
            switch (navigationPage)
            {
                case NavigationScreens.LoginOnline:
                    return typeof(LoginOnline);
                case NavigationScreens.MainWindow:
                    return typeof(MainWindow);
                case NavigationScreens.Home:
                    return typeof(Home);
            }
            return default(Type);
        }
    }

This is how I use AppMessenger

public enum AppMessageType
    {
        Navigate,
        NewWindow,
        Close,
        MessageBox
    }
    public class AppMessage
    {
        public AppMessageType MessageType { get; set; }
        public object MessageData { get; set; }
    }
    public class AppMessenger
    {
        public static void Register(object recipient, Action<AppMessage> action)
        {
            Messenger.Default.Register<AppMessage>(recipient, action);
        }

        public static void Unregister(object recipient, Action<AppMessage> action)
        {
            Messenger.Default.Unregister<AppMessage>(recipient, action);
        }

        public static void Send(AppMessage message)
        {
            Messenger.Default.Send<AppMessage>(message);
        }

And this is some way I control the flow from ViewModel-

AppMessenger.Send(new AppMessage() { MessageType = AppMessageType.Navigate, MessageData = NavigationScreens.Home });

Now the problem is I register to window successfully and I found that a single instance of AppMessenger is register to per Window but when I notify messenger to invoke some event it fires twice. For e.g.

AppMessenger.Send(new AppMessage() { MessageType = AppMessageType.MessageBox, MessageData = "Authentication failed." });

This will show two time MessageBox.

Why did it fire twice. How can I prevent this?


Solution

  • Ok, here i found my solution

     public ApplicationScreenBase()
            {
                this.Loaded +=ApplicationScreenBase_Loaded;
                this.Unloaded += ApplicationScreenBase_Unloaded;
                this.Activated += ApplicationScreenBase_Activated;
                this.Deactivated += ApplicationScreenBase_Deactivated;
            }
    
            void ApplicationScreenBase_Deactivated(object sender, EventArgs e)
            {
                AppMessenger.Unregister(this, OnMessageToApp);
            }
    
            void ApplicationScreenBase_Activated(object sender, EventArgs e)
            {
                AppMessenger.Register(this, OnMessageToApp);
            }
    

    What is happening is, if i open two windows then AppMessenger send message twice as both windows are currently registered with AppMessenger. So using Window life cycle i unregister Appmessenger to the window which is in background and register which is in forground this way, only one window will be registered to the AppMessenger, but i doubt what happen if both windows are in foreground in minimize state. Hope this may help someone in future.