I want to pass a method in the MainViewModel to a delegate variable in the LoginViewModel object like this:
public class ApplicationViewModel : INotifyPropertyChanged
{
private LoginViewModel loginViewModel;
public ApplicationViewModel()
{
loginViewModel = new LoginViewModel();
this.loginViewModel.Login += this.checkLoginData; //stays null..
CurrentPageViewModel = this.loginViewModel; //works fine
}
private void checkLoginData(string username, string password)
{
//validating data
}
}
But for some reason, the loginViewModel.Login is null...
And this Command in the LoginViewModel keeps firing this at start, and telling me that the Login == null, which is not what I expect because I initialize the delegate at the MainViewModel constructor.
I'm not a expert at MVVM/WPF, but I trying to work for it.
EDIT: extra information.
And loginViewModel.Login is a delegate variable like this:
class LoginViewModel : ObservableObject, IPageViewModel
{
public delegate void DelegateLogin(string username, string password);
private DelegateLogin _login;
public DelegateLogin Login
{
get { return this._login; }
set
{
/*
if(this._login != value)
{
this._login = value;
OnPropertyChanged("Login");
}*/
this._login = value;
OnPropertyChanged("Login");
}
}
public ICommand CheckLoginCommand
{
get
{
if (Login != null)
{
this.checkLoginCommand = new Command(p => { Login(this._username, this._password); });
}
else
{
System.Windows.MessageBox.Show("Login DELEGATE IS EMPTY!?!?!"); //keeps firing...
}
return this.checkLoginCommand;
}
}
}
Try this:
public ICommand CheckLoginCommand
{
get
{
if (this.checkLoginCommand == null)
this.checkLoginCommand = new Command(p => {
if (Login != null)
Login(this._username, this._password);
else
System.Windows.MessageBox.Show("Login DELEGATE IS EMPTY!?!?!"); //keeps firing...
});
return this.checkLoginCommand;
}
}
This has the advantage of creating the command regardless of whether the Login delegate set. Other things aside, hopefully Login will be ready by the time the command gets invoked.
Related
I'm new to WPF. I'm creating a POS desktop application by using WPF MVVM pattern as front-end development. (I have try my best to make this question as short as possible.)
Scenario: I have a MainViewModel which will show AuthView (and AuthViewModel) by default whenever user open the application. After user fill in the form and click the Login button in AuthView, LoginCommand will be called on the view, if login successful, they will be redirect to DashboardView.
MainWindow.xaml
<Grid>
<ContentControl Content="{Binding SelectedViewModel}"/>
</Grid>
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
if (SelectedViewModel == null)
{
SelectedViewModel = new AuthViewModel();
}
else
{
SelectedViewModel = new DashboardViewModel();
}
}
private ViewModelBase _selectedViewModel;
public ViewModelBase SelectedViewModel
{
get { return _selectedViewModel; }
set { _selectedViewModel = value; OnPropertyChanged(nameof(SelectedViewModel)); }
}
public void ChangeToDashboard()
{
SelectedViewModel = new DashboardViewModel();
}
}
AuthViewModel.cs
public class AuthViewModel : ViewModelBase
{
public AuthViewModel()
{
loginCommand = new RelayCommand(Login);
}
#region Login
private RelayCommand loginCommand;
public RelayCommand LoginCommand
{
get { return loginCommand; }
}
private async void Login()
{
try
{
Response = await callLoginAPI; //some custom login occurs here
if (Response.Status == "ok")
{
//change viewModel to DashboardViewModel screen
MainViewModel MainViewModel = new MainViewModel();
MainViewModel.ChangeToDashboard();
}
}
catch (Exception e)
{
//
}
}
#endregion
}
Problem: I have go through a lot of SA solution but still unable to switch the view after user login successfully.
Question: How can I trigger the MainViewModel to change UI after I have change the SelectedViewModel property (after user login successfully, response.status == ok)? or is there any other better (as simple as possible) way to achieve what I am trying to do?
AuthViewModel can generate event about login
public class AuthViewModel : ViewModelBase
{
public AuthViewModel()
{
loginCommand = new RelayCommand(Login);
}
public event EventHandler LoginCompleted;
protected virtual void OnLoginCompleted(EventArgs e)
{
EventHandler handler = LoginCompleted;
handler?.Invoke(this, e);
}
private RelayCommand loginCommand;
public RelayCommand LoginCommand
{
get { return loginCommand; }
}
private async void Login()
{
try
{
Response = await callLoginAPI(); //some custom login occurs here
if (Response.Status == "ok")
{
OnLoginCompleted(EventArgs.Empty);
}
}
catch (Exception e)
{
//
}
}
}
and MainViewModel can handle that event:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
if (SelectedViewModel == null)
{
var vm = new AuthViewModel();
vm.LoginCompleted += (sender, e) => ChangeToDashboard();
SelectedViewModel = vm;
}
else
{
SelectedViewModel = new DashboardViewModel();
}
}
private ViewModelBase _selectedViewModel;
public ViewModelBase SelectedViewModel
{
get { return _selectedViewModel; }
set { _selectedViewModel = value; OnPropertyChanged(nameof(SelectedViewModel)); }
}
private void ChangeToDashboard()
{
SelectedViewModel = new DashboardViewModel();
}
}
I'm new to xamarin and mvvmcross and I would like to wire up a simple button click from my ios project to my viewmodel.
using System;
using MvvmCross.Binding.BindingContext;
using MvvmCross.iOS.Views;
using Colingual.Core.ViewModels;
namespace Colingual.iOS
{
public partial class LoginView : MvxViewController
{
public LoginView() : base("LoginView", null)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
var set = this.CreateBindingSet<LoginView, LoginViewModel>();
set.Bind(Username).To(vm => vm.Username);
set.Bind(Password).To(vm => vm.Password);
set.Bind(btnLogin).To(vm => vm.MyAwesomeCommand);
set.Apply();
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
}
}
I would like to wire up btnlogin to myawesomecommand.
using MvvmCross.Core.ViewModels;
namespace Colingual.Core.ViewModels
{
public class LoginViewModel : MvxViewModel
{
readonly IAuthenticationService _authenticationService;
public LoginViewModel(IAuthenticationService authenticationService)
{
_authenticationService = authenticationService;
}
string _username = string.Empty;
public string Username
{
get { return _username; }
set { SetProperty(ref _username, value); }
}
string _password = string.Empty;
public string Password
{
get { return _password; }
set { SetProperty(ref _password, value); }
}
public bool AuthenticateUser() {
return true;
}
MvxCommand _myAwesomeCommand;
public IMvxCommand MyAwesomeCommand
{
get
{
DoStuff();
return _myAwesomeCommand;
}
}
void DoStuff()
{
string test = string.Empty;
}
}
}
As you can see I've got mvxCommand with the name of MyAwesomecommand but I want to handle some logic from a button click in my other project. Anyone know what I should do?
I've done more looking around and found an answer here.
MvxCommand _myAwesomeCommand;
public IMvxCommand MyAwesomeCommand
{
get { return new MvxCommand(DoStuff); }
}
void DoStuff()
{
string test = string.Empty;
}
The idea is to have your mvxcommand getter which returns a new command that takes the method as a parameter.
When clicking the button btnLogin, you can access void DoStuff in viewmodel.
In order to familiarize myself with MVVM for WinRT I have been looking at the example MvvmLight WinRT project. Currently I'm running into an issue where my RelayCommand is only called once (on construction of viewmodel). What I want to do is go to the MainViewModel if the user is authorized. If I remove the conditional check of the login in the LoginCommand method, the RelayCommand works as expected. Any thoughts as to what I'm doing wrong? Should I not being doing my validation within the LoginCommand?
LoginViewModel (some code has been removed):
public class LoginViewModel : ViewModelBase {
private readonly IDataService _dataService;
private readonly INavigationService _navigationService;
private RelayCommand _navigateCommand;
private Login login; //contains username and password
/// <summary>
/// Gets the NavigateCommand.
/// THIS DOES NOT GET FIRED UPON BUTTON CLICK
/// </summary>
public RelayCommand LoginCommand{
get {
if (login != null && login.UserName.Equals("Test"))
return _navigateCommand ?? (_navigateCommand = new RelayCommand(() => _navigationService.Navigate(typeof(MainPage))));
return _navigateCommand;
}
}
LoginPage.xaml.cs (some code has been removed):
public sealed partial class LoginPage {
public LoginViewModel Vm {
get { return (LoginViewModel)DataContext; }
}
public LoginPage() {
InitializeComponent();
}
protected override void LoadState(object state) {
var casted = state as LoginPageState;
if (casted != null) {
Vm.Load(casted);
}
}
protected override object SaveState() {
return new LoginPageState {
Credentials = new Login {
UserName = txtUserName.Text,
Password = txtPassword.Text
}
};
}
public class LoginPageState {
public Login Credentials { get; set; }
}
}
}
LoginPage.xaml (some code has been removed)
<Button Content="Login"
x:Name="NextPageButton"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="10"
Command="{Binding LoginCommand}" />
The problem is the condition. When your LoginPage loads, it tries to bind your Button to the LoginCommand. In order to achieve that, it gets the LoginCommand from your ViewModel. At that point in time Login is null and therefore the property will return _navigateCommand which is null. After that the page will not try to use the LoginCommand because it already knows its value.
To solve this you could move the condition inside the lambda expression:
public RelayCommand LoginCommand
{
get
{
return _navigateCommand ?? (_navigateCommand = new RelayCommand(
() =>
{
if (login != null && login.UserName.Equals("Test"))
{
_navigationService.Navigate(typeof(MainPage));
}
}));
}
}
An even better solution would be to move the authorization to another class. Something like:
public RelayCommand LoginCommand
{
get
{
return _navigateCommand ?? (_navigateCommand = new RelayCommand(
() =>
{
if (_authorizationService.UserAuthorized(login))
{
_navigationService.Navigate(typeof(MainPage));
}
}));
}
}
Hope this helps.
I'm using MVVM light for a WPF application. I have a view model with several commands that use the RelayCommand. Since the code is very similar for each command, I created a GetCommand Method. But the resulting RelayCommand does not work if I use the param inside the RelayCommand. If I don't use the param everything works fine (except that I can't pass a value).
Can someone explain why this happens and what other solution there is to reuse the code without copy & paste?
Below is a very reduced version of my code that shows only the important parts:
public class MainViewModel {
public RelayCommand commandOne = GetCommand("one");
public RelayCommand commandTwo = GetCommand("two");
public RelayCommand GetCommand(string param) {
return new RelayCommand(() => {
// Do something accessing other properties of MainViewModel
// to detect if another action is alreay running
// this code would need to be copy & pasted everywhere
if(param == "one")
_dataService.OneMethod();
else if(param == "two")
_dataService.TwoMethod();
else
_dataService.OtherMethod();
var name = param;
});
}
}
This is how I usually use RelayCommands where I just bind the commands to methods.
public class MainViewModel {
public MainViewModel()
{
CommandOne = new RelayCommand<string>(executeCommandOne);
CommandTwo = new RelayCommand(executeCommandTwo);
}
public RelayCommand<string> CommandOne { get; set; }
public RelayCommand CommandTwo { get; set; }
private void executeCommandOne(string param)
{
//Reusable code with param
}
private void executeCommandTwo()
{
//Reusable code without param
}
}
You may be looking for something like the following
public partial class MainWindow : Window
{
private RelayCommand myRelayCommand ;
private string param = "one";
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
public RelayCommand MyRelayCommand
{
get
{
if (myRelayCommand == null)
{
myRelayCommand = new RelayCommand((p) => { ServiceSelector(p); });
}
return myRelayCommand;
}
}
private void DoSomething()
{
MessageBox.Show("Did Something");
}
private void ServiceSelector(object p)
{
DoSomething();
if (param == "one")
MessageBox.Show("one");
else if (param == "two")
MessageBox.Show("two");
else
MessageBox.Show("else");
var name = param;
}
}
Given a class like below:
public class LoginInfo
{
public int UserId;
public string Username;
}
and another class
public class OtherClass
{
public static LoginInfo Info
{
get
{
return SessionBll.GetLoginInfo(someInt);
}
set
{
SessionBll.UpdateLoginInfo(value, someInt);
}
}
}
and given this:
OtherClass.LoginInfo.Username = "BillyBob";
How can I call the LoginInfo setter when a property of LoginInfo changes? I know I can do:
LoginInfo info = OtherClass.LoginInfo;
info.Username = "BillyBob";
OtherClass.LoginInfo = info;
but I want to do this without these three lines. I want it to be automatic
Thanks
Ended up subscribing to the event in the LoginInfo class
There are many different approaches. The most straightforward is to have LoginInfo implement INotifyPropertyChanged and have OtherClass subscribe to the PropertyChanged event and then you can have that event handler call the setter as needed.
No matter what, you have to do some work to get this wired up correctly.
Try the following amendments to your class
public class LoginInfo : INotifyPropertyChanged
{
private int _userID;
private string _UserName;
public event PropertyChangedEventHandler PropertyChanged;
public int UserId
{
get { return this._userID; }
set
{
if (value != this._userID)
{
this._userID = value;
NotifyPropertyChanged("UserID");
}
}
}
public string Username
{
get { return this._UserName; }
set
{
if (value != this._UserName)
{
this._UserName = value;
NotifyPropertyChanged("UserName");
}
}
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Then in your other class you need to have a reference to the loginfo class and subscribe to the PropertyChangedEvent.
_privateLogInfo.PropertyChanged += new PropertyChangedEventHandler(methodToBeCalledToHandleChanges);
Then handle any changes in methodToBeCalledToHandleChanges.