0
votes

I recently started trying out MVVM pattern in school and was wondering what the best way (if any) is to notify View from the ViewModel, letting the view know to run a method without breaking MVVM? Basically letting the view know if something was successful, like a login attempt or trying to connect to a database?

An example could be a login page, where the mainwindow should change content to a new page only if the login was successful, if not, a messagebox should show up

Edit:

I'm using .NET

What I have tried so far:

View:

<Page
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:View.Pages" xmlns:ViewModels="clr-namespace:ViewModel.ViewModels;assembly=ViewModel" x:Class="View.Pages.Start_Page"
      mc:Ignorable="d" 
      d:DesignHeight="720" d:DesignWidth="1280"
      Title="Start_Page">

    <Page.DataContext>
        <ViewModels:Start_Page_ViewModel/>
    </Page.DataContext>

Code behind it:

public Start_Page()
        {
            InitializeComponent();

            Start_Page_ViewModel currentDataContext = DataContext as Start_Page_ViewModel;

            currentDataContext.CurrentUserIDGotten += GoToMenu;
        }

        private void GoToMenu(int result)
        {
            if (result == -1)
            {
                MessageBox.Show("User credentials incorrect");
            }
            else if (result == -2)
            {
                MessageBox.Show("Connection failed");
            }
            else
            {
                Application.Current.MainWindow.Content = new Menu_Page();
            }
        }

ViewModel:

public class Start_Page_ViewModel
    {

        private string userName;
        private string userPassword;

        public string UserName { get => userName; set => userName = value; }

        public string UserPassword { get => userPassword; set => userPassword = value; }

        private RelayCommand logIn;

        public RelayCommand LogIn => logIn;


        public delegate void CurrentUserIDGottenEventHandler(int result);
        public event CurrentUserIDGottenEventHandler CurrentUserIDGotten;

        public Start_Page_ViewModel()
        {
            logIn = new RelayCommand(LogInToProgram, CanLogIn);
        }

        public void LogInToProgram(object o)
        {
            PasswordBox passwordBox = o as PasswordBox;

            ViewModelController.Instance.CurrentUserID = Database_Controller.Instance.SignIn(userName, passwordBox.Password);

            OnUserIDGotten(ViewModelController.Instance.CurrentUserID);
        }

        public bool CanLogIn(object o)
        {
            if (userName != null)
            {
                return true;
            }
            return false;
        }

        protected virtual void OnUserIDGotten(int result)
        {
            if (CurrentUserIDGotten != null)
            {
                CurrentUserIDGotten(result);
            }
        }
    }
2
Could you show us what you have tried so far? What you are asking is the function of the MVVM pattern. What Framework are you using? - RobertBaron

2 Answers

0
votes

Generally, the ViewModel communicates with the View via databindings. The ViewModel might expose a property, like LoginSuccessful, that the the View would bind to. Then, when the property updates, the View would receive a PropertyChanged notification and change some aspect of its appearance. How it would do this varies; for example, a text property in XAML could be bound directly to an underlying ViewModel property:

<TextBox Text="{Binding Source={StaticResource UserViewModel}, Path=Username}"/>

The ViewModel might look like:

public class UserViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;

    public string Username {
        get { return _username; }
        set {
            _username = value;
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Username"));
        }
    }
    private string _username;

    public UserViewModel() { }
}

Whenever the Username property is changed on the UserViewModel class, the text box will update to display the new value.

However, this approach doesn't work for all situations. When working with boolean values, it's often useful to implement data triggers:

<TextBox Text="{Binding Source={StaticResource UserViewModel}, Path=Username}">
    <TextBlock.Style>
        <Style TargetType="{x:Type TextBox}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Source={StaticResource UserViewModel}, Path=IsTaken}" Value="True">
                            <Setter Property="Background" Value="Red"></Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBlock.Style>
</TextBox>

This code extends the previous example to color the background of the text box red if the IsTaken property is set to true on the ViewModel. A nice thing about data triggers is that they reset themselves; for example, if the value is set to false the background will revert to its original color.

If you want to go the other way, and notify the ViewModel of user input or a similarly important event, you can use commands. Commands can be bound to properties in XAML, and are implemented by the ViewModel. They are called when the user performs a certain action, such a clicking a button. Commands can be created by implementing the ICommand interface.

0
votes

In pure way, without specified framework.

  1. Create a event delegate (or listener interface), associate with view model
  2. Register the event handler on view
  3. Fire the event when view model is changed

Likes this

using System;

public class MainClass {
  public static void Main (string[] args) {
    ViewModel m = new ViewModel();
    View v = new View();
    v.Model = m;
    m.MakeSomeChange();
  }
}

public class View {
  private IViewModel _model;
  public IViewModel Model {
    get {
      return _model;
    }
    set {
      if(_model != null) {
        _model.OnChanged -= OnChanged;
      }
      if(value != null) {
        value.OnChanged += OnChanged;
      }
      _model = value;
    }
  }
  private void OnChanged(){
    //update view
    Console.WriteLine ("View Updated");
  }
}

public delegate void ViewChangeDelegate();

public interface IViewModel {
  event ViewChangeDelegate OnChanged;
}

public class ViewModel: IViewModel {
  public event ViewChangeDelegate OnChanged;

  public void MakeSomeChange() {
    //make some change in the view Model
    OnChanged.Invoke();
  }
}