MVVM - Binding and changing - c#

Actual state: I click in the Login Button and the ViewModel changes to the new View.
Desired state: I click the LoginButton (the LoginViewModel binds the with the view to get the Email and Password and verifies in the server the authenticity of the user and if its ok the request receives as answer the info about the user and changes the view)
What I know: change the views, bind the textbox, communication with the server (handling the request and the answers)
What I don't know: send from the LoginViewModel to the GeneralViewModel the answer with the info about the user, don't know how to maintain the PasswordBox instead of the TextBox for binding.
CODE:
LoginView
<Grid Margin="0,0,-74.4,-11.8" HorizontalAlignment="Left" Width="800" Height="600" VerticalAlignment="Top">
<TextBox Text = "{Binding Email, Mode = TwoWay}" Style="{DynamicResource MyTextBox}" x:Name="textBoxEmail" VerticalContentAlignment="Bottom" HorizontalContentAlignment="Center" HorizontalAlignment="Center" Width="248" Margin="274,212,278,347" FontFamily="Segoe UI Semibold" />
<Image Source="C:\Users\Images\logo.png" x:Name="Logo" HorizontalAlignment="Left" Height="129" Margin="301,63,0,0" VerticalAlignment="Top" Width="151" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="0.091"/>
<TranslateTransform/>
</TransformGroup>
</Image.RenderTransform>
</Image>
Sign up now!
Forgot your Password? Click here!
LoginViewModel
class LoginViewModel : AViewModel
{
WifiAP wa;
#region fields
private string _email = null;
private TokenRequest tk;
public DelegateCommand LoginCommand { get; set; }
public string Email
{
get
{
return _email;
}
set
{
_email = value;
OnPropertyChanged("Email");
//Here's the magic
LoginCommand.RaiseCanExecuteChanged();
}
}
private string _password = null;
public string Password
{
get
{
return _password;
}
set
{
_password = value;
OnPropertyChanged("Password");
//Here's the magic
LoginCommand.RaiseCanExecuteChanged();
}
}
public string mac;
#endregion
public LoginViewModel()
{
wa = new WifiAP();
LoginCommand = new DelegateCommand(Login, CanLogin);
}
public bool CanLogin()
{
return !string.IsNullOrEmpty(Email);
}
public void Login()
{
//
}
#region auxiliaryMethods
public string getMac()
{
mac = wa.GetMACAddress();
return mac;
}
public string hashingMD5(string pass)
{
string pwd = pass;
System.Security.Cryptography.MD5 hs = System.Security.Cryptography.MD5.Create();
byte[] db = hs.ComputeHash(System.Text.Encoding.UTF8.GetBytes(pwd));
string result = Convert.ToBase64String(db);
return result;
}}
MainViewModel
public MainWindowViewModel{
this.AddViewModel(new LoginViewModel() { DisplayName = "Login", InternalName = "LoginViewModel" });
this.AddViewModel(new GeneralViewModel() { DisplayName = "General", InternalName = "GeneralViewModel" });
this.Current_ViewModel = this.GetViewModel("LoginViewModel");
Thanks in advance for your time.

At first, here you have the article that tells you what and how to cope with PasswordBox in MVVM. Secondly, how to pass the data through? I'm not very familiar with MVVM Light because I personally use PRSIM. You may though do something alike RegionContext.
At first you have to create a class as your RegionData. Notice, that this model also implements the OnPropertyChanged interface (the implementation comes form inherited ViewModelBase in MVVM Light).
public class HaveLoginData : ViewModelBase
{
private string _login;
public string Login
{
get { return _login; }
set
{
_login = value;
RaisePropertyChanged(() => Login);
}
}
}
Than, in the constructor you should make a common instance of this class for both of your ViewModels:
public MainWindowViewModel
{
var regionData = new HaveLoginData();
this.AddViewModel(new LoginViewModel() { RegionData = regionData });
this.AddViewModel(new GeneralViewModel() { RegionData = regionData });
this.Current_ViewModel = this.GetViewModel("LoginViewModel");
}
You will also have to add a new property to your ViewModels called RegionData:
using System.ComponentModel;
namespace YourApp.ViewModels
{
public class GeneralViewModel
{
private HaveLoginData _regionData;
public HaveLoginData RegionData
{
get { return _regionData; }
set
{
_regionData = value;
_regionData.PropertyChanged += _regionData_PropertyChanged;
}
}
private void _regionData_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Login")
{
// do the second view model login logic
}
}
}
}
And the Login ViewModel:
namespace YourApp.ViewModels
{
public class LoginViewModel
{
public HaveLoginData RegionData { get; set; }
public void Login()
{
// do the login conditions logic
if (true)
{
RegionData.Login = "new user login";
}
}
}
}
As you can see, when someone will set the RegionData, you will subscribe to the PropertyChanged event and be noticed in every ViewModel about the changes inside the Login and MD5Password properties. As you see, you also have to remeber about unsubscribing from the PropertyChanged event from your previous RegionData. The RegionData once created should not be changed, so it may not be neccessary to do it in the setter (you can make some kind of Dispose to delete the reference).

Related

How to disable a button if textbox and passwordbox is blank in wpf?

I basically used a Model's (UserAccount) Property from my ViewModel(CreateAccountViewModel) to bind to my View, and call to my Command (CreateAccountCommand).
My Model(UserAccount):
public class UserAccount : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _id;
private string _username;
private string _password;
private DateTime _dateTime;
public int Id
{
get { return _id; }
set { _id = value; OnPropertyChanged(nameof(Id)); }
}
public string Username
{
get { return _username; }
set { _username = value; OnPropertyChanged(nameof(Username)); }
}
public string Password
{
get { return _password; }
set { _password = value; OnPropertyChanged(nameof(Password)); }
}
public DateTime DateCreated
{
get { return _dateTime; }
set { _dateTime = value; OnPropertyChanged(nameof(DateCreated)); }
}
public virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
My ViewModel(CreateAccountViewModel):
public class CreateAccountViewModel: ViewModelBase
{
private UserAccount _userAccount;
public UserAccount CurrentUserAccount
{
get { return _userAccount; }
set { _userAccount = value; OnPropertyChanged(nameof(CurrentUserAccount)); }
}
public ICommand CreateAccountCommand{ get; }
public CreateAccountViewModel()
{
CreateAccountCommand= new CreateAccountCommand(this, Test);
CurrentUserAccount = new UserAccount();
}
public void Test()
{
MessageBox.Show("Random Message");
//I'm going to put my Create functionality here
}
}
My View (CreateAccountView):
<!--The TextBox for username-->
<TextBox Grid.Column="1"
Margin="10,0,0,0"
Text="{Binding Path=CurrentUserAccount.Username, UpdateSourceTrigger=PropertyChanged}" />
<!--The PasswordBox for password-->
<components:BindablePasswordBox Grid.Column="1"
Margin="10,0,0,0"
Password="{Binding Path=CurrentUserAccount.Password, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Grid.ColumnSpan="2" />
<!--The Create user button-->
<Button Grid.Row="2"
Margin="0,20,0,0"
HorizontalAlignment="Center"
Command="{Binding CreateAccountCommand}"
Content="Create Account" />
My Command(CreateAccountCommand):
public class CreateAccountCommand: ICommand
{
private readonly CreateAccountViewModel _viewModel;
private readonly Action RunCommand;
public CreateAccountCommand(CreateAccountViewModel viewModel , Action runCommand)
{
_viewModel = viewModel;
_viewModel.PropertyChanged += ViewModel_PropertyChanged;
RunCommand = runCommand;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
//This is supposed to check whether the Username textbox and Password passwordbox is blank (if both of them are blank, the button should be disabled, else disabled
return !string.IsNullOrEmpty(_viewModel.CurrentUserAccount.Username) && !string.IsNullOrEmpty(_viewModel.CurrentUserAccount.Password);
}
public void Execute(object parameter)
{
RunCommand();
}
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
CanExecuteChanged?.Invoke(this, new EventArgs());
}
}
My PasswordBox is bindable because I created a custom PasswordBox with DependencyProperty:
public partial class BindablePasswordBox : UserControl
{
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register("Password", typeof(string), typeof(BindablePasswordBox),
new PropertyMetadata(string.Empty));
public string Password
{
get { return (string)GetValue(PasswordProperty); }
set { SetValue(PasswordProperty, value); }
}
public BindablePasswordBox()
{
InitializeComponent();
}
//This method will notify us, whenever a password in our passwordBox changes
private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
Password = passwordBox.Password; //sets the value of the DependencyProperty (PasswordProperty)
}
}
My problem here, is that, the button in my View does not change enable/disable even if I set my command's CanExecute to do so. Am I missing something obvious here? I really have to ask because I've been stuck here since yesterday. (My Main goal here is to disable the Create Account button if the Textbox and PasswordBox have no input. Any solutions are okay)
Lets do a small refactoring.
use CallerMemberNameAttribute (see here how) to have shorter property setters in vm;
write once reusable ICommand implementation and use it for all commands, see DelegateCommand;
rise command CanExecuteChanged in vm when you change one of command canExecuted condition;
UserAccount needs notifications (you have done it in the edit), if it's a model, then you need an extra vm to act as a wrapper, otherwise you wouldn't be able to catch changes done by the bound controls;
Since the properties of UserAccount are part of command canExecuted, you need to monitor for them.
With all changes your button using the command should be property enabled/disabled.
Below is pseudo-code (can contain mistakes):
public class CreateAccountViewModel: ViewModelBase
{
UserAccount _userAccount;
public UserAccount CurrentUserAccount
{
get => _userAccount;
set
{
// unsubscribe
if(_userAccount != null)
_userAccount -= UserAccount_PropertyChanged;
_userAccount = value;
// subscribe
if(_userAccount != null)
_userAccount += UserAccount_PropertyChanged;
// notifications
OnPropertyChanged(); // shorter syntax with CallerMemberNameAttribute
CreateAccountCommand.RaiseCanExecuteChanged();
}
}
public ICommand CreateAccountCommand { get; }
public CreateAccountViewModel()
{
CurrentUserAccount = new UserAccount();
CreateAccountCommand = new DelegateCommand(Test,
o => !string.IsNullOrEmpty(CurrentUserAccount.Username) && !string.IsNullOrEmpty(CurrentUserAccount.Password));
}
void Test(object parameter)
{
MessageBox.Show("Random Message");
//I'm going to put my Create functionality here
}
void UserAccount_PropertyChanged(object sender, NotifyPropertyChangedEventArgs e) =>
CreateAccountCommand.RaiseCanExecuteChanged(); // rise always of check for specific properties changes
}
The CreateAccountCommand hooks up en event handler to the view model's PropertyChanged but there is no such event raised when you set the Username and Password properties of the UserAccount object.
Either implement INotifyPropertyChanged in UserAccount or bind to wrapper properties of the CreateAccountViewModel:
public string Username
{
get { return _userAccount?.Username; }
set
{
if (_userAccount != null)
_userAccount.Username = value;
OnPropertyChanged();
}
}
If you decide to implement INotifyPropertyChanged in UserAccount, you still need to notify the command when the properties have been updated.
Since your CurrentUserAccount property may be set to a new value dynamically, you should remove and add the event handler dynamically:
private UserAccount _userAccount;
public UserAccount CurrentUserAccount
{
get { return _userAccount; }
set
{
if (_userAccount != null)
_userAccount.PropertyChanged -= OnUserAccountPropertyChanged;
_userAccount = value;
if (_userAccount != null)
_userAccount.PropertyChanged += OnUserAccountPropertyChanged;
OnPropertyChanged(nameof(CurrentUserAccount));
}
}
private void OnUserAccountPropertyChanged(object sender, PropertyChangedEventArgs e) =>
OnPropertyChanged(null);

Understanding MVVM and ObjectDetailViewModel in Udemy Course

I've been doing a course for Xamarin and I am at the MVVM portion where all the xaml.cs code is moved to ViewModels.
This is the course (lecture #104, 105, 106): [url]https://www.udemy.com/course/complete-xamarin-developer-course-ios-and-android[/url]
So far I've been able to understand the concept of MVVM, however, the course seems to do a few things incorrectly (as I believe) and some of the code in previous lectures isn't in the new lectures (we'll leave that annoyance alone for now), moving forward, I am not sure how to code it correctly. I also understand that I am presenting a simple use case at the moment so let's think that Post is a huge object that shouldn't be re-created over and over.
For example, I have a PostDetailPage.xaml(.cs), in the xaml, I currently have the following (note that most of the Mode=TwoWay might not be used properly):
<ContentPage.Content>
<StackLayout Margin="10">
<Entry x:Name="experienceEntry"
Text="{Binding Experience, Mode=TwoWay}"
Margin="-5"></Entry>
<label x:name="venuename"
text="{binding post.venuename, mode=onetime}"
fontattributes="bold"/>
<label x:name="categoryname"
text="{binding post.categoryname, mode=onetime}"/>
<label x:name="address"
text="{binding post.address, mode=onetime}"/>
<label x:name="coordinatelabel"
text="{binding coordinates, mode=onetime}"/>
<label x:name="distance"
text="{binding post.distance, mode=onetime, stringformat='{0:0}'}"/>
<Button Text="Update"
x:Name="updateButton"
Command="{Binding UpdatePostCommand}"
CommandParameter="{Binding Post}" />
</StackLayout>
</ContentPage.Content>
Code behind is the following:
public partial class PostDetailPage : ContentPage
{
PostDetailViewModel viewModel;
public PostDetailPage(Post selectedPost)
{
InitializeComponent();
viewModel = new PostDetailViewModel(selectedPost);
BindingContext = viewModel;
}
}
Then I have an incomplete PostDetailViewModel:
public class PostDetailViewModel : INotifyPropertyChanged
{
public UpdatePostCommand UpdatePostCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public PostDetailViewModel(Post post)
{
UpdatePostCommand = new UpdatePostCommand(this);
SelectedPost = post;
}
//Can this become another ViewModel or ?
private Post selectedPost;
public Post SelectedPost
{
get { return selectedPost; }
set
{
selectedPost = value;
OnPropertyChanged("Post");
}
}
private string experience;
public string Experience
{
get { return experience; }
set
{
experience = value;
UpdatePostObject();
OnPropertyChanged("Experience");
}
}
private void UpdatePostObject() {
Post = new Post() {
Experience = experience,
//many more properties are needed here
//seems like if the solution was to grow,
//this would become unmanagable or something
}
}
private void UpdatePostObject()
{
//This is used to Trigger the change on Post
//I don't think this is the best way (hence my question(s))
SelectedPost = new Post()
{
Experience = experience
};
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public async void Update()
{
await App.Current.MainPage.Navigation.PushAsync(new HistoryPage());
}
}
From the course, the instructor recommends that when Experience is changed (or any other property related to Post), that we re-create the Post object again and again to trigger CanExecute and associated Execute. This seems incorrect to have to re-create the Post object again and again after each change, so would updating only what is needed be best?
So, what I am asking (or learning about) is...
Should I implement a generic PostViewModel that is updaded when Experience has changed and somehow attach the ICommand to it, if so, how would something like that look like? Of course, if this is not correct, please point me in the right direction if possible.
Is there some type of OnObjectChanged like method that could be used? I know of the ObservableCollection when dealing with collections.
Please let me know if there is anything else I can provide.
Thank you
Derek
First of all I cannot open your url, this course is missing.
Should I implement a generic PostViewModel that is updaded when Experience has changed and somehow attach the ICommand to it, if so, how would something like that look like? Of course, if this is not correct, please point me in the right direction if possible.
For the UpdatePostObject method, you do not need to new a Post object, If you want to set the new value for the selectedPost object, just set it like following code directly,
private void UpdatePostObject()
{
selectedPost.Experience = experience;
selectedPost.address= "new Adree";
selectedPost.categoryname = "new CateGoryName";
selectedPost.distance = "41";
selectedPost.venuename = "new venu";
//many more properties are needed here
//seems like if the solution was to grow,
//this would become unmanagable or something
// };
}
If you want to achieve the click command, you just declear the public ICommand UpdatePostCommand { get; set; } in PostDetailViewModel. Then achieve it in your PostDetailViewModel's constructor. Here is my all code about PostDetailViewModel
public class PostDetailViewModel
{
//: INotifyPropertyChanged
public ICommand UpdatePostCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public PostDetailViewModel(Post post)
{
UpdatePostCommand = new Command<Post>((key) => {
key.categoryname = "Command categoryname";
key.distance = "31";
key.address = "Command address";
key.venuename = "Command venuename";
key.Experience = "Command Experience";
});
SelectedPost = post;
}
//Can this become another ViewModel or ?
private Post selectedPost;
public Post SelectedPost
{
get { return selectedPost; }
set
{
selectedPost = value;
// OnPropertyChanged("SelectedPost");
}
}
private string experience;
public string Experience
{
get { return experience; }
set
{
experience = value;
UpdatePostObject();
// OnPropertyChanged("Experience");
}
}
private void UpdatePostObject()
{
selectedPost.Experience = experience;
selectedPost.address= "new Adree";
selectedPost.categoryname = "new CateGoryName";
selectedPost.distance = "41";
selectedPost.venuename = "new venu";
//many more properties are needed here
//seems like if the solution was to grow,
//this would become unmanagable or something
// };
}
//private void UpdatePostObject()
//{
// //This is used to Trigger the change on Post
// //I don't think this is the best way (hence my question(s))
// SelectedPost = new Post()
// {
// Experience = experience
// };
//}
//public event PropertyChangedEventHandler PropertyChanged;
//protected virtual void OnPropertyChanged(string propertyName)
//{
// PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
//}
public async void Update()
{
await App.Current.MainPage.Navigation.PushAsync(new HistoryPage());
}
}
}
If you want to achieve the attribute change at the running time, you should achieve the INotifyPropertyChanged interface for all of your attribute in your POST model.
public class Post:INotifyPropertyChanged
{
// public string _venuename { get; set; }
// public string categoryname { get; set; }
string _categoryname;
public string categoryname
{
set
{
if (_categoryname != value)
{
_categoryname = value;
OnPropertyChanged("categoryname");
}
}
get
{
return _categoryname;
}
}
//public string address { get; set; }
string _address;
public string address
{
set
{
if (_address != value)
{
_address = value;
OnPropertyChanged("address");
}
}
get
{
return _address;
}
}
// public string Experience { get; set; }
string _experience;
public string Experience
{
set
{
if (_experience != value)
{
_experience = value;
OnPropertyChanged("Experience");
}
}
get
{
return _experience;
}
}
// public string distance { get; set; }
string _distance;
public string distance
{
set
{
if (_distance != value)
{
_distance = value;
OnPropertyChanged("distance");
}
}
get
{
return _distance;
}
}
string _venuename;
public string venuename
{
set
{
if (_venuename != value)
{
_venuename = value;
OnPropertyChanged("venuename");
}
}
get
{
return _venuename;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And you layout have some errors, I change of them.
<ContentPage.Content>
<StackLayout Margin="10">
<Entry x:Name="experienceEntry"
Text="{Binding Experience, Mode=TwoWay}"
Margin="-5">
</Entry>
<Label x:Name="venuename"
Text="{Binding SelectedPost.venuename, Mode=TwoWay}"
FontAttributes="Bold"
/>
<Label x:Name="categoryname"
Text="{Binding SelectedPost.categoryname, Mode=TwoWay}"/>
<Label x:Name="address"
Text="{Binding SelectedPost.address, Mode=TwoWay}"/>
<Label x:Name="coordinatelabel"
Text="{Binding SelectedPost.coordinates, Mode=TwoWay}"/>
<Label x:Name="distance"
Text="{Binding SelectedPost.distance, Mode=TwoWay, StringFormat='{0:0}'}"/>
<Button Text="Update"
x:Name="updateButton"
Command="{Binding UpdatePostCommand}"
CommandParameter="{Binding SelectedPost}" />
</StackLayout>
</ContentPage.Content>
Here is my layout background code.
public MainPage()
{
InitializeComponent();
Post selectedPost=new Post() {
address= "address",
categoryname= "categoryname",
distance= "21",
venuename= "venuename" };
PostDetailViewModel viewModel = new PostDetailViewModel(selectedPost);
BindingContext = viewModel;
}
}
Is there some type of OnObjectChanged like method that could be used? I know of the ObservableCollection when dealing with collections.
In your PostDetailViewModel it is no need to achieve that. If you want to use ObservableCollection to add Post , it achieve the INotifyPropertyChanged, If you Post items will increate or decrease, it will change automatically, But if value of Post will be changed, it will not change, you have to achieve the INotifyPropertyChanged in your Post model.
Here is my running GIF.Normally, it have some default value, if I enter the value in the Entry, this value of post will be changed, if I click the Button, the value of post will be changed to another value
Here is a helpful article about it, you can refer to it.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-bindings-to-mvvm

NavigateCommand only called once

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.

Very very simple MVVM problem

I am trying to make my very first Silverlight App ever, but I can't get the LogOn function to work, can you help me? This should properly be super simple for all of you, I will show you my two files: LogOn.xaml.cs and LogOnViewModel.cs
Apparently the problem is that UserId gets not set early enough to be availble in LogOn.xaml.cx when I need it, can you help me make it work, that would lift my moment quite a bit :-)
public partial class LogOn : PhoneApplicationPage
{
public LogOn()
{
InitializeComponent();
this.DataContext = LogOnViewModel.Instance;
}
private void btnLogOn_Click(object sender, RoutedEventArgs e)
{
if ((!string.IsNullOrEmpty(txtEmailAddress.Text)) && (!string.IsNullOrEmpty(txtPassword.Password)))
{
txbLogonMessage.Text = "";
LogOnViewModel.Instance.UserLogin(txtEmailAddress.Text, txtPassword.Password);
if (LogOnViewModel.Instance.UserId > 0)
NavigationService.Navigate(new Uri("/_2HandApp;component/Views/Main.xaml", UriKind.Relative));
else
txbLogonMessage.Text = "Login was unsuccessful. The user name or password provided is incorrect. Please correct the errors and try again. ";
}
}
}
public sealed class LogOnViewModel : INotifyPropertyChanged
{
public static LogOnViewModel Instance = new LogOnViewModel();
//public static int userId;
private SHAServiceClient WS;
private int userId;
public int UserId
{
get
{
return userId;
}
set
{
userId = value;
this.RaisePropertyChanged("UserId");
}
}
private LogOnViewModel()
{
WS = new SHAServiceClient();
WS.UserLoginCompleted += new EventHandler<UserLoginCompletedEventArgs>(WS_UserLoginCompleted);
}
void WS_UserLoginCompleted(object sender, UserLoginCompletedEventArgs e)
{
if (e.Error == null)
{
this.UserId = e.Result;
}
}
public void UserLogin(string email, string password)
{
WS.UserLoginAsync(email, password);
}
/* Implementing the INotifyPropertyChanged interface. */
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null))
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The cause of the problem is what has been highlighted by #flq. You're making an asynchronous call, meaning that you won't get the expected result right away (in your case, the UserId being assigned), but instead, you can subscibe to the Completed event (or provide a callback) to handle things when the asynchronous task finishes.
Now, the "MVVM way" to do this (or at least what I would do) is as follows: first of all, go get MVVM Light! it's a lightweight MVVM framework which would be very helpful. You should have your ViewModel class implement the ViewModelBase base class from MVVMLight, this would provide the change notification and messaging as well as other useful stuff. Then, you should encapsulate the login functionality in a command to be able to wire up it up from xaml, for that you can use MVVMLight's RelayCommand. Once the login is complete, you can just send a message to your view letting it know that (in a pretty decoupled way), and the view can simply initiate the navigation.
Here's the bits of code for that:
public class LogOnViewModel : ViewModelBase
{
private SHAServiceClient WS;
public LogOnViewModel()
{
WS = new SHAServiceClient();
WS.UserLoginCompleted += new EventHandler<UserLoginCompletedEventArgs>(WS_UserLoginCompleted);
LoginCommand = new RelayCommand(UserLogin);
}
private int userId;
public int UserId
{
get { return userId; }
set
{
userId = value;
RaisePropertyChanged(()=>UserId);
}
}
private int password;
public int Password
{
get { return password; }
set
{
password = value;
RaisePropertyChanged(()=>Password);
}
}
private int username;
public int Username
{
get { return username; }
set
{
username = value;
RaisePropertyChanged(()=>Username);
}
}
private int loginErrorMessage;
public int LoginErrorMessage
{
get { return loginErrorMessage; }
set
{
loginErrorMessage = value;
RaisePropertyChanged(()=>LoginErrorMessage);
}
}
void WS_UserLoginCompleted(object sender, UserLoginCompletedEventArgs e)
{
if (e.Error == null)
{
this.UserId = e.Result;
// send a message to indicate that the login operation has completed
Messenger.Default.Send(new LoginCompleteMessage());
}
}
public RelayCommand LoginCommand {get; private set;}
void UserLogin()
{
WS.UserLoginAsync(email, password);
}
}
for the xaml:
<TextBox Text="{Binding Username, Mode=TwoWay}"/>
<TextBox Text="{Binding Password, Mode=TwoWay}"/>
<Button Command="{Binding LoginCommand}"/>
<TextBlock Text="{Binding LoginErrorMessage}"/>
in the code behind:
public partial class LogOn : PhoneApplicationPage
{
public LogOn()
{
InitializeComponent();
this.DataContext = new LogOnViewModel();
Messenger.Default.Register<LoginCompletedMessage>(
this,
msg=> NavigationService.Navigate(
new Uri("/_2HandApp;component/Views/Main.xaml",
UriKind.Relative) );
}
....
}
You can see that there is a little bit more (but straightforward) code in the ViewModel and less in the code behind. This also took advantage of DataBinding which is in the heart of MVVM.
Hope this helps :)
P.S: the LoginCompletedMessage class is just an empty class in this case (used just to define the type message), but you can use it to send more info (maybe you still want to have the UserId sent)
Well, you're calling an async version of a login WS.UserLoginAsync, which means the execution moves on and indeed there is no user id when you check for it.
You aren't really doing MVVVM here, but anyway, let's go with the flow. Have an event on your "Viewmodel" that is raised when the login process is finished (WS_UserLoginCompleted). You can handle it and trigger Navigation in an event-handler of that event.

MVVM - implementing 'IsDirty' functionality to a ModelView in order to save data

Being new to WPF & MVVM I struggling with some basic functionality.
Let me first explain what I am after, and then attach some example code...
I have a screen showing a list of users, and I display the details of the selected user on the right-hand side with editable textboxes. I then have a Save button which is DataBound, but I would only like this button to display when data has actually changed. ie - I need to check for "dirty data".
I have a fully MVVM example in which I have a Model called User:
namespace Test.Model
{
class User
{
public string UserName { get; set; }
public string Surname { get; set; }
public string Firstname { get; set; }
}
}
Then, the ViewModel looks like this:
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Input;
using Test.Model;
namespace Test.ViewModel
{
class UserViewModel : ViewModelBase
{
//Private variables
private ObservableCollection<User> _users;
RelayCommand _userSave;
//Properties
public ObservableCollection<User> User
{
get
{
if (_users == null)
{
_users = new ObservableCollection<User>();
//I assume I need this Handler, but I am stuggling to implement it successfully
//_users.CollectionChanged += HandleChange;
//Populate with users
_users.Add(new User {UserName = "Bob", Firstname="Bob", Surname="Smith"});
_users.Add(new User {UserName = "Smob", Firstname="John", Surname="Davy"});
}
return _users;
}
}
//Not sure what to do with this?!?!
//private void HandleChange(object sender, NotifyCollectionChangedEventArgs e)
//{
// if (e.Action == NotifyCollectionChangedAction.Remove)
// {
// foreach (TestViewModel item in e.NewItems)
// {
// //Removed items
// }
// }
// else if (e.Action == NotifyCollectionChangedAction.Add)
// {
// foreach (TestViewModel item in e.NewItems)
// {
// //Added items
// }
// }
//}
//Commands
public ICommand UserSave
{
get
{
if (_userSave == null)
{
_userSave = new RelayCommand(param => this.UserSaveExecute(), param => this.UserSaveCanExecute);
}
return _userSave;
}
}
void UserSaveExecute()
{
//Here I will call my DataAccess to actually save the data
}
bool UserSaveCanExecute
{
get
{
//This is where I would like to know whether the currently selected item has been edited and is thus "dirty"
return false;
}
}
//constructor
public UserViewModel()
{
}
}
}
The "RelayCommand" is just a simple wrapper class, as is the "ViewModelBase". (I'll attach the latter though just for clarity)
using System;
using System.ComponentModel;
namespace Test.ViewModel
{
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
{
protected ViewModelBase()
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public void Dispose()
{
this.OnDispose();
}
protected virtual void OnDispose()
{
}
}
}
Finally - the XAML
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Test.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:UserViewModel/>
</Window.DataContext>
<Grid>
<ListBox Height="238" HorizontalAlignment="Left" Margin="12,12,0,0" Name="listBox1" VerticalAlignment="Top"
Width="197" ItemsSource="{Binding Path=User}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Firstname}"/>
<TextBlock Text="{Binding Path=Surname}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Label Content="Username" Height="28" HorizontalAlignment="Left" Margin="232,16,0,0" Name="label1" VerticalAlignment="Top" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="323,21,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Path=User/UserName}" />
<Label Content="Surname" Height="28" HorizontalAlignment="Left" Margin="232,50,0,0" Name="label2" VerticalAlignment="Top" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="323,52,0,0" Name="textBox2" VerticalAlignment="Top" Width="120" Text="{Binding Path=User/Surname}" />
<Label Content="Firstname" Height="28" HorizontalAlignment="Left" Margin="232,84,0,0" Name="label3" VerticalAlignment="Top" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="323,86,0,0" Name="textBox3" VerticalAlignment="Top" Width="120" Text="{Binding Path=User/Firstname}" />
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="368,159,0,0" Name="button1" VerticalAlignment="Top" Width="75" Command="{Binding Path=UserSave}" />
</Grid>
</Window>
So basically, when I edit a surname, the Save button should be enabled; and if I undo my edit - well then it should be Disabled again as nothing has changed.
I have seen this in many examples, but have not yet found out how to do it.
Any help would be much appreciated!
Brendan
In my experience, if you implement IsDirty in your view model, you probably also want the view model to implement IEditableObject.
Assuming that your view model is the usual sort, implementing PropertyChanged and a private or protected OnPropertyChanged method that raises it, setting IsDirty is simple enough: you just set IsDirty in OnPropertyChanged if it isn't already true.
Your IsDirty setter should, if the property was false and is now true, call BeginEdit.
Your Save command should call EndEdit, which updates the data model and sets IsDirty to false.
Your Cancel command should call CancelEdit, which refreshes the view model from the data model and sets IsDirty to false.
The CanSave and CanCancel properties (assuming you're using a RelayCommand for these commands) just return the current value of IsDirty.
Note that since none of this functionality depends on the specific implementation of the view model, you can put it in an abstract base class. Derived classes don't have to implement any of the command-related properties or the IsDirty property; they just have to override BeginEdit, EndEdit, and CancelEdit.
I've done some work on implementing IsDirty for models that is wrapped in my ViewModel.
The result really simplified my ViewModels:
public class PersonViewModel : ViewModelBase
{
private readonly ModelDataStore<Person> data;
public PersonViewModel()
{
data = new ModelDataStore<Person>(new Person());
}
public PersonViewModel(Person person)
{
data = new ModelDataStore<Person>(person);
}
#region Properties
#region Name
public string Name
{
get { return data.Model.Name; }
set { data.SetPropertyAndRaisePropertyChanged("Name", value, this); }
}
#endregion
#region Age
public int Age
{
get { return data.Model.Age; }
set { data.SetPropertyAndRaisePropertyChanged("Age", value, this); }
}
#endregion
#endregion
}
Code # http://wpfcontrols.codeplex.com/
Check under the Patterns assembly and MVVM folder, you'll find a ModelDataStore class.
P.S.
I haven't done a full scale test on it, just the really simple test you'll find the Test assembly.
I would suggest you to use GalaSoft MVVM Light Toolkit as it is much more easier to implement than DIY approach.
For dirty reads, you need to keep the snapshot of each fields, and return true or false from UserSaveCanExecute() method, which will enable / disable command button accordingly.
If you wanted to take a framework approach rather than writing the infrastructure yourself, you could use CSLA (http://www.lhotka.net/cslanet/) - Rocky's framework for developing business objects. Object state is managed for you on property changes, and the code base also includes an example ViewModel type which supports an underlying model, a Save verb, and a CanSave property. You may be able to take inspiration from the code, even you didn't want to use the framework.
I have come up with a working solution. This may of course not be the best way, but I am sure I can work on it as I learn more...
When I run the project, if I cange any item, the list box is disabled, and the save button enabled. If I undo my edits, then the list box is enabled again, and the save button disabled.
I have changed my User Model to implement INotifyPropertyChanged, and I have also created a set of private variables to store the "original values" and some logic to check for "IsDirty"
using System.ComponentModel;
namespace Test.Model
{
public class User : INotifyPropertyChanged
{
//Private variables
private string _username;
private string _surname;
private string _firstname;
//Private - original holders
private string _username_Orig;
private string _surname_Orig;
private string _firstname_Orig;
private bool _isDirty;
//Properties
public string UserName
{
get
{
return _username;
}
set
{
if (_username_Orig == null)
{
_username_Orig = value;
}
_username = value;
SetDirty();
}
}
public string Surname
{
get { return _surname; }
set
{
if (_surname_Orig == null)
{
_surname_Orig = value;
}
_surname = value;
SetDirty();
}
}
public string Firstname
{
get { return _firstname; }
set
{
if (_firstname_Orig == null)
{
_firstname_Orig = value;
}
_firstname = value;
SetDirty();
}
}
public bool IsDirty
{
get
{
return _isDirty;
}
}
public void SetToClean()
{
_username_Orig = _username;
_surname_Orig = _surname;
_firstname_Orig = _firstname;
_isDirty = false;
OnPropertyChanged("IsDirty");
}
private void SetDirty()
{
if (_username == _username_Orig && _surname == _surname_Orig && _firstname == _firstname_Orig)
{
if (_isDirty)
{
_isDirty = false;
OnPropertyChanged("IsDirty");
}
}
else
{
if (!_isDirty)
{
_isDirty = true;
OnPropertyChanged("IsDirty");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then, my ViewModel has changed a bit too....
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Input;
using Test.Model;
using System.ComponentModel;
namespace Test.ViewModel
{
class UserViewModel : ViewModelBase
{
//Private variables
private ObservableCollection<User> _users;
RelayCommand _userSave;
private User _selectedUser = new User();
//Properties
public ObservableCollection<User> User
{
get
{
if (_users == null)
{
_users = new ObservableCollection<User>();
_users.CollectionChanged += (s, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
// handle property changing
foreach (User item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += (s1, e1) =>
{
OnPropertyChanged("EnableListBox");
};
}
}
};
//Populate with users
_users.Add(new User {UserName = "Bob", Firstname="Bob", Surname="Smith"});
_users.Add(new User {UserName = "Smob", Firstname="John", Surname="Davy"});
}
return _users;
}
}
public User SelectedUser
{
get { return _selectedUser; }
set { _selectedUser = value; }
}
public bool EnableListBox
{
get { return !_selectedUser.IsDirty; }
}
//Commands
public ICommand UserSave
{
get
{
if (_userSave == null)
{
_userSave = new RelayCommand(param => this.UserSaveExecute(), param => this.UserSaveCanExecute);
}
return _userSave;
}
}
void UserSaveExecute()
{
//Here I will call my DataAccess to actually save the data
//Save code...
_selectedUser.SetToClean();
OnPropertyChanged("EnableListBox");
}
bool UserSaveCanExecute
{
get
{
return _selectedUser.IsDirty;
}
}
//constructor
public UserViewModel()
{
}
}
Finally, the XAML
I changed the bindings on the Username, Surname & Firstname to include UpdateSourceTrigger=PropertyChanged
And then I bound the listbox's SelectedItem and IsEnabled
As I said in the beginning - it may not be the best solution, but it seems to work...
Since your UserSave command is in the ViewModel, I would do the tracking of the "dirty" state there. I would databind to the selected item in the ListBox, and when it changes, store a snapshot of the current values of the selected user's properties. Then you can compare to this to determine if the command should be enabled/disabled.
However, since you are binding directly to the model, you need some way to find out if something changed. Either you also implement INotifyPropertyChanged in the model, or wrap the properties in a ViewModel.
Note that when the CanExecute of the command changes, you may need to fire CommandManager.InvalidateRequerySuggested().
This is how I have implemented IsDirty. Create a wrapper for every property of User class (inheriting User class with IPropertyChanged and implementing onpropertychanged in User class wont help) in your ViewModal. You need to change your binding from UserName to WrapUserName.
public string WrapUserName
{
get
{
return User.UserName
}
set
{
User.UserName = value;
OnPropertyChanged("WrapUserName");
}
}
Now have a property
public bool isPageDirty
{
get;
set;
}
Since your viewmodal inherits from baseviewmodal and baseviewmodal implements onPropertyChanged.
UserViewModel.PropertyChanged += (s, e) => { isPageDirty = true; };
In case any of the propertychanges,isPageDirty will be true, So while saving you chan check isPageDirty.

Categories

Resources