0
votes

I have got my MainWindow which loads new UserControls and there ViewModel's into it's ContentControl, so the Views are switched.

However, I need to access a property in my MainWindow ViewModel from a ViewModel within the ContentControl.

MainWindowViewModel

namespace PhotoManagement
{
    public class MainWindowViewModel : NotifyUIBase
    {
        public ObservableCollection<ViewVM> Views { get; set; }

        private ObservableCollection<Logged> loggedUsers;
        public ObservableCollection<Logged> LoggedUsers
        {
            get
            {
                return loggedUsers;
            }
            set
            {
                loggedUsers.Add(value[0]);

                //There is a user logged in, switch to home and display menu
                if (loggedUsers.Count > 0)
                {
                    //Display menu, switch Windows
                    MessageBox.Show("Someone is logged in!");
                }
                else
                {
                    MessageBox.Show("No-one is logged in!");
                }
            }
        }

Below you can see the LoginViewModel which is in the MainWindow ContentControl, I have added a comment where i'm trying to add this new user to the ObservableCollection.

#region Login Methods
        private LoginVM loginVM;
        public LoginVM LoginVM
        {
            get
            {
                return loginVM;
            }
            set
            {
                loginVM = value;
                editEntity = editVM.TheEntity;
                RaisePropertyChanged();
            }
        }
        protected override void DoLogin()
        {
            //Check if email exists
            var exist = db.Users.Count(a => a.Email == LoginVM.TheEntity.Email);
            if (exist != 0)
            {
                //Fecth user details
                var query = db.Users.First(a => a.Email == LoginVM.TheEntity.Email);
                if (Common.Security.HashGenerator.CalculateHash(LoginVM.TheEntity.ClearPassword, query.Salt) == query.Hash)
                {
                    //Password is correct
                    MessageBox.Show("Details correct!");

                    //Set properties
                    LoginVM.TheEntity.FirstName = query.FirstName;
                    LoginVM.TheEntity.LastName = query.LastName;
                    LoginVM.TheEntity.UID = query.UID;

                    //Add the LoginVM to LoggedUsers 

Edit: This is where I add the Views in MainWindowViewModel

namespace PhotoManagement
{
    public class MainWindowViewModel : NotifyUIBase
    {
        public ObservableCollection<ViewVM> Views { get; set; }

        private ObservableCollection<Logged> loggedUsers;
        public ObservableCollection<Logged> LoggedUsers
        {
            get
            {
                return loggedUsers;
            }
            set
            {
                loggedUsers.Add(value[0]);

                //There is a user logged in, switch to home and display menu
                if (loggedUsers.Count > 0)
                {
                    //Display menu, switch Windows
                    MessageBox.Show("Someone is logged in!");
                }
                else
                {
                    MessageBox.Show("No-one is logged in!");
                }
            }
        }

        public string Version
        {
            get { return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); }
        }

        public MainWindowViewModel()
        {
            ObservableCollection<ViewVM> views = new ObservableCollection<ViewVM>
            {
                new ViewVM { IconGeometry=App.Current.Resources["home4"] as Geometry, ViewDisplay="Home", ViewType = typeof(LoginView), ViewModelType = typeof(LoginViewModel)},
                new ViewVM { IconGeometry=App.Current.Resources["instagram3"] as Geometry, ViewDisplay="Images", ViewType = typeof(LoginView), ViewModelType = typeof(LoginView)},
                new ViewVM { IconGeometry=App.Current.Resources["money674"] as Geometry, ViewDisplay="Sales", ViewType = typeof(LoginView), ViewModelType = typeof(LoginViewModel)},
                new ViewVM { IconGeometry=App.Current.Resources["printing1"] as Geometry, ViewDisplay="Print Queue", ViewType = typeof(LoginView), ViewModelType = typeof(LoginViewModel)},
                new ViewVM { IconGeometry=App.Current.Resources["cog2"] as Geometry, ViewDisplay="Settings", ViewType = typeof(IconLibaryView), ViewModelType = typeof(IconLibaryViewModel)},
                new ViewVM { IconGeometry=App.Current.Resources["upload40"] as Geometry, ViewDisplay="Upload", ViewType = typeof(IconLibaryView), ViewModelType = typeof(IconLibaryViewModel)}
            };
            Views = views;
            RaisePropertyChanged("Views");
            views[0].NavigateExecute();
        }
    }
}
2
When MainWindowViewModel creates the LoginVM to add to the Views collection, it should give it a property or delegate so it can set the currently logged in user. The alternative if MainWindowViewModel and LoginVM are completely unlinked would be to use a messaging system, such as MVVM Light's Messenger or Microsoft Prism's EventAggregator.Rachel
@Rachel , I have added the code where I add the Views, could you explain what you mean when you said it should give it a property so I can set the user. Not tried to use the Messenger before, will have to have a look into it.Martyn Ball
Loading UserControls in your ViewModel? Noooooooooooooooo0ope. You should be using ContentControls and ItemsControls bound to your view models and models, and DataTemplates which contain the UI that matches each. This is not MVVM, it's some kind of bastardization between the old codebehind and MVVM.user1228
@MartynBall Ok, so it looks like your MainViewModel does not directly reference the LoginViewModel at all, so in this case a messanging system would probably be better. I have some basic examples here on my blog, or just google around and you should find plenty of them.Rachel

2 Answers

1
votes

You simply need to use Ancestor binding from within ContentControl's any child element :

{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}},Path=DataContext.AnyPropertyOfMainWindowViewModel}

If Window has MainWindowViewModel as DataContext.

0
votes

I would go with events for ViewModel-to-ViewModel communication, and I prefer the IEventAggregator, available as a PubSub nuget package from Microsoft, but there are plenty to choose from (or roll your own if you prefer).

public MainViewModel() {
    Aggregator.GetEvent<UserLoggedInEvent>().Subscribe(user => ...do your magic);
}

And in your LoginViewModel, publish it after the user has logged in:

public DoLogin() {
    ... do other stuff here...
    Aggregator.GetEvent<UserLoggedInEvent>().Publish(userDetails);
}

Using the IEventAggregator from Prism, the event class is simple:

public class UserLoggedInEvent : PubSubEvent<User> {}

Btw - One of the main purposes for MVVM or any design pattern is to abstract UI from business code, so if you can remove all your App.Current.Resources stuff from your VM using a converter or something else then you've abstracted it from WPF (much more easily ported to other platforms like UWP).