We are migrating our WPF project from the MVVMLight library to the Microsoft CommunityToolkit library.
We went ahead following the shared Microsoft migration documentation and updated all our (about 1200) commands accordingly.
When we got the build afterwards, we noticed that the events were not triggered correctly in the project and the commands were not working.
However, the SetProperty() method does not run my RelayCommands.
Below we have resolved this issue for just one command of our user model. There are more then 100+ Models and 1200ish commands exist.
BaseViewModel.cs
public class BaseViewModel : ObservableObject, IDisposable
{
protected bool _disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public virtual void Dispose(bool disposing)
{
if (_disposed.Equals(false))
{
if (disposing)
{
}
_disposed = true;
}
}
private bool _isSaved;
public bool IsSaved
{
get { return _isSaved; }
set
{
SetProperty(ref _isSaved, value, nameof(_isSaved));
}
}
}
LoginViewModel
public class LoginViewModel : BaseViewModel
{
public LoginViewModel()
{
User = new UserModel();
User.PropertyChanged += User_PropertyChanged; // WHAT WE NEWLY ADDED
LoginCommand = new RelayCommand<Window>(OnLoginCommandExecuted, CanLoginCommandExecute);
CancelCommand = new RelayCommand<Window>(OnCancelCommandExecuted, CanCancelCommandExecute);
}
private void User_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (LoginCommand is not null) LoginCommand.NotifyCanExecuteChanged(); // We should notify it manually..
}
private UserModel _user;
public UserModel User
{
get { return _user; }
set
{
SetProperty(ref _user, value, nameof(_user));
}
}
public RelayCommand<Window>? LoginCommand { get; private set; }
public async void OnLoginCommandExecuted(Window window)
{
//DO STUFF
}
public bool CanLoginCommandExecute(Window window)
{
//DO STUFF
}
}
LoginWindow.xaml
<Window
x:Class="ProjectName.LoginWindow"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
x:Name="loginWindow">
<Button x:Name="btnLogin" Command="{Binding LoginCommand}" CommandParameter="{Binding ElementName=loginWindow}" />
</Window>
Is there any way to trigger NotifyCanExecuteChanged manager for all commands in active models?
What can i do in this situation? Does the following implementation need to be applied for all models and commands? Is this an expected situation? Thanks and happy codings.
EDIT:
I changed whole project with annotations.
LoginViewModel
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(LoginCommandExecutedCommand))]
private UserModel _user = new();
[RelayCommand(CanExecute = nameof(CanLoginCommandExecute))]
private async void OnLoginCommandExecuted(Window window)
{
//DO STUFF
}
private bool CanLoginCommandExecute(Window window)
{
// DO STUFF LOGIC
}
UserModel
[ObservableProperty]
private string _username;
partial void OnUsernameChanged(string value)
{
XtraMessageBox.Show("I HAVE CHANGED - USERNAME");
IsDirty = true;
}
[ObservableProperty]
private string _password;
partial void OnPasswordChanged(string value)
{
XtraMessageBox.Show("I HAVE CHANGED - PASSWORD");
IsDirty = true;
}
It fires the changes but notifying commands. Cause UserModel not changing. UserModels properties are changed.
That we can see model properties are changed.
And LoginViewModel's User Model updated.
But it is not executing my commands. Because its only GET'ing my UserModel, not setting. Autogenerated code only notify when UserModel sets.
What should i do?
There are several things you could change.
In a partial class viewmodel, you can do:
[ObservableProperty]
[AlsoNotifyChangeFor(nameof(FullName))]
[AlsoNotifyCanExecuteFor(nameof(SaveCommand))]
private bool _isSaved = true;
partial void OnIsSavedChanged(bool newValue)
{
// Called when IsSaved property changes
}
The boiler plate code for the IsSaved property and change notification is created in a partial class by the code generator. That will reduce the risk of breaking change notification (like you have by using the field rather than property) because you just have the private member to define.
You may add partial onchanged and onchanging methods which will be called when the matching property name changes or is about to change.
When IsSaved changes, the attribute will raise canexecutechanged for the command.
A relaycommand has an explicit notifycanexecutechanged method
https://learn.microsoft.com/en-us/dotnet/api/microsoft.toolkit.mvvm.input.relaycommand.notifycanexecutechanged?view=win-comm-toolkit-dotnet-7.1
If none of the attributes suit you could just do:
CommandManager.InvalidateRequerySuggested()
Or you could iterate through relaycommands in a viewmodel and call notifycanexecutechanged on them.
Note also though that the source code for mvvmlight is available.
I've used it in one .net6 project.
Related
I am struggling with Text binding in my WPF app.
Lets imagine that I have another working app (ex. windows service) with some data in it.
In my WPF app I would like to have folder "DATA" with class where data are introduced and in same folder another class which would include a void which will query my windows service
I would like to show this data in my WPF window.
To make it simpler - one class with data, one class with data changing and WPF window with showing this data.
Unfortunately I can not achieve this... When I am executing below code, my window is showing 0 instead 123.
I would like to achive that my window will show value 123.
file "Database.cs" in folder "Data" in project "example"
namespace example.Data
{
public class Database
{
private int _testInt = 0;
public int testInt
{
get { return _testInt; }
set { _testInt = value; }
}
}
}
file "Query.cs" in folder "Data" in project "example"
namespace example.Data
{
public class Query
{
public Database _database;
public void execute()
{
_database = new Database();
_database.testInt = 123;
}
}
}
file "MainWindow.xaml.cs" in project "example"
namespace example
{
public partial class MainWindow : Window
{
public Data.Database _database;
public Data.Query _query;
public int testInt
{
get { return _database.testInt; }
set { _database.testInt = value; OnPropertyChanged(); }
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
_database = new Data.Database();
_query = new Data.Query();
_query.execute();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion
}
}
File MainWindow.xaml
<Window>
<TextBlock Text="{Binding testInt}"
Foreground="White"
FontSize="15"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="20,10,10,0" />
</Window>
P.S. If I will put
_database.testInt = 987;
to MainWindow.xaml.cs it is working properly - window is showing value 987 in textblock.
You have multiple instances of the Database object, a new one each time Query.execute is called and one in MainWindow constructor.
It's the data in the later that is displayed.
You should modify the content of this instance to see any change, for that, you must inject it in the Query object:
_query = new Data.Query(_database);
// ...
public class Query
{
private readonly Database _database;
public Query(Database database)
{
_database = database;
}
public void Execute()
{
_database.testInt = 123;
}
}
Finally you need a way to notify the view that the content as changed, that why Database should implement INotifyPropertyChanged.
But at this point it's badly named, because it's a model in the MVVM pattern.
you need to implement INotifyPropertyChanged
public partial class MainWindow : Window, INotifyPropertyChanged
from the MVVM view, I think these answers from Orace and Jason are on a good way, both do not solve the problem completely.
Let the Mainwindow implement INotifyPropertyChanged
Let the query accept the new value:
public void execute(int value)
{
//_database = new Database();
// inject _database like in the answer above
_database.testInt = value;
}
When your testInt changes, let the _query deliver the change down to the "database" (btw: you do it vice versa) See code below:
`public int testInt
{get { return _database.testInt; }
`set { _query.execute(value); OnPropertyChanged(); }`
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
_database = new Data.Database();
// the property change will change both the view and the model
testInt = 987;
}
Well, you have changed both model and view with one property change then, Good or not?!
Just for future users. There is small bug in Orace's answer: (It should be without "readonly" parameter, because below You are writing to it.
private Database _database;
public Query(Database database)
{
_database = database;
}
I'm developing a Windows application (UWP) that has two pages, I want the best practice to pass parameters between pages.
it's my scenario:
We have two pages, each open and remain at the middle of the screen and a Button on each page, which send the message to the other page when we click on it.
I also want to pass information continuously and repeatedly.
in Page1.cs:
Page2 page2;
public Page1()
{
this.InitializeComponent();
CreatPage2();
}
// creat page 2
private async void CreatPage2()
{
var NewWindow = CoreApplication.CreateNewView();
int NewWindowid = 0;
await NewWindow.Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
{
Frame newframe = new Frame();
newframe.Navigate(typeof(Page2), this);
Window.Current.Content = newframe;
Window.Current.Activate();
ApplicationView.GetForCurrentView().Title = "page2";
NewWindowid = ApplicationView.GetForCurrentView().Id;
});
await Windows.UI.ViewManagement.ApplicationViewSwitcher.TryShowAsStandaloneAsync(NewWindowid);
}
//Button
private void ChangeP2_Click(object sender, RoutedEventArgs e)
{
// send a message to the texblock in the page2
page2.TexBlock2.Text=$"From page1 :{e.ToString()}";
// change text color of the texblock in the page2
page2.Foreground= new SolidColorBrush(Windows.UI.Colors.Red);
}
in Page2.cs:
Page1 page1;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
page1 = e.Parameter as Page1;
base.OnNavigatedTo(e);
}
public Page2()
{
this.InitializeComponent();
}
//Button
private void ChangeP1_Click(object sender, RoutedEventArgs e)
{
// send a message to the texblock in the page1
page1.TexBlock1.Text=$"From page2 :{e.ToString()}";
// change text color of the texblock in the page1
page1.Foreground= new SolidColorBrush(Windows.UI.Colors.Red);
}
the above code just work for the page2 to the page1. (it can change the textblock of pagea).
Please help me, I can't find a solution that work on two pages
Naah… the best way is to use a standard pattern that consist of an app ViewModel class, which contains all the common app data that you want to use in the logic layer.
I always do it like this:
1) I use the MainPage automatically created as the "shell" of the app, with a property that is the AppViewModel.
The MainPage (and thus the AppViewModel) can be accessed from everywhere in the app, by setting itself as a static field in its own class.
This is the code, simpler than you think:
public sealed partial class MainPage : Page
{
public AppViewModel ViewModel { get; set; } = new AppViewModel();
public static MainPage Current { get; set; }
public MainPage()
{
this.InitializeComponent();
Current = this;
}
}
2) The AppViewModel itself is a class that must implement the INotifyPropertyChanged interface, in order to enable bindable properties and functions.
It is common, among developers, to create a base class that implements it and then derive all the classes that needs bindable properties from it.
Here it is:
public class BaseBind : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetProperty<T>(ref T storage, T value,
[CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
}
Then you derive AppViewModel class (and all the other model and viewmodel classes) from it… populating it with all the common properties that you need to share across pages.
I have even added a derived property, in order to show how you can share even multiple data types at once, and a function:
public class AppViewModel : BaseBind
{
public AppViewModel()
{
// ...
}
// All common app data
private string sampleCommonString;
public String SampleCommonString
{
get { return sampleCommonString; }
set { SetProperty(ref sampleCommonString, value); OnPropertyChanged(nameof(SampleDerivedProperty1)); OnPropertyChanged(nameof(SampleDerivedProperty2)); }
}
public String SampleDerivedProperty1 => "return something based on SampleCommonString";
public String SampleDerivedProperty2
{
get
{
<<evaluate SampleCommonString>>
return "Same thing as SampleDerivedProperty1, but more explicit";
}
}
// This is a property that you can use for functions and internal logic… but it CAN'T be binded
public String SampleNOTBindableProperty { get; set; }
public void SampleFunction()
{
// Insert code here.
// The function has to be with NO parameters, in order to work with simple {x:Bind} markup.
// If your function has to access some specific data, you can create a new bindable (or non) property, just as the ones above, and memorize the data there.
}
}
3) Then, in order to access all this from another Page, just create an AppViewModel field in that page, as seen below:
public sealed partial class SecondPage : Page
{
public AppViewModel ViewModel => MainPage.Current.ViewModel;
public SecondPage()
{
this.InitializeComponent();
}
}
...and you can easily bind XAML controls properties to the AppViewModel itself:
<TextBlock Text="{x:Bind ViewModel.SampleCommonString, Mode=OneWay}"/>
<Button Content="Sample content" Click="{x:Bind ViewModel.SampleFunction}"/>
(Mode=OneWay is for real-time binding, in order that the property is immediately updated even in the UI, while Mode=TwoWay is used for those properties that can be edited from the control itself, by the user, in order to interact with app logic).
Hope this helped.
Best regards and happy new year.
So I am a little confused as to how the MVVM architecture can help me and how to use it in this situation:
I am using Xamarin and have created my view and view controller in iOS as an example. I have implemented MVVMLight toolkit as well, and have created my ViewModel for the view and view controller.
I am creating a login screen, so the user inputs their username and password and they are updated in the model through RaisePropertyChanged() events. My question is where I need to call the function to validate this information and actually log them into the system?
I have implemented a RelayCommand that will call a method on the ViewModel whenever the button is clicked as I have seen in other tutorials and such, but I am not sure if I am supposed to call the validation code here.
Some examples of what I have:
LoginViewModel.cs:
public class LoginViewModel : ViewModelBase
{
private string _username;
private string _password;
public RelayCommand LoginButtonCommand { get; private set; }
public bool CanExecuteLoginCommand { get; set; }
public LoginViewModel()
{
LoginButtonCommand = new RelayCommand(HandleLoginButtonCommand, () => CanExecuteLoginCommand);
CanExecuteLoginCommand = true;
}
public string Username
{
get
{
return _username;
}
set
{
_username = value;
RaisePropertyChanged(() => Username);
}
}
public string Password
{
get
{
return _password;
}
set
{
_password = value;
RaisePropertyChanged(() => Password);
}
}
private void HandleLoginButtonCommand()
{
CanExecuteLoginCommand = false;
//Validate login?
CanExecuteLoginCommand = true;
}
}
LoginViewController.cs:
public partial class LoginViewController : UIViewController
{
private Binding _usernameTextFieldBinding;
private Binding _passwordTextFieldBinding;
private LoginViewModel _viewModel;
public LoginViewController(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
_viewModel = Application.Locator.Login;
HideKeyboardHandling(UsernameTextField);
HideKeyboardHandling(PasswordTextField);
_usernameTextFieldBinding = this.SetBinding(
() => _viewModel.Username)
.ObserveSourceEvent("EditingDidEnd")
.WhenSourceChanges(() => _viewModel.Username = UsernameTextField.Text);
_passwordTextFieldBinding = this.SetBinding(
() => _viewModel.Username)
.ObserveSourceEvent("EditingDidEnd")
.WhenSourceChanges(() => _viewModel.Password = PasswordTextField.Text);
Loginbutton.SetCommand("TouchUpInside", _viewModel.LoginButtonCommand);
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
void HideKeyboardHandling(UITextField textField)
{
textField.ShouldReturn = TextField =>
{
TextField.ResignFirstResponder();
return true;
};
var gesture = new UITapGestureRecognizer(() => View.EndEditing(true));
gesture.CancelsTouchesInView = false;
View.AddGestureRecognizer(gesture);
}
}
It all depends on how strict you want to be with Single Responsibility Principle(SPR). Which in turn depends on how complex your application is. The more complex the application is, the more separated the responsibilities should be.
A typical MVVM implementation handles the commands in the ViewModel. And the ViewModel forwards the call into the Model. But his still puts two responsibilities(e.g. presentation and command handling) into a single component, a.k.a the ViewModel.
A more strict approach will be to have the ViewModel only handle presentation logic. Create a separate controller to host all the command handlers. And have the command handlers forward the calls to the Model.
A more relaxed approach will be to simply implement the business logic in the ViewModel. This implies you don't have a business logic layer. Which is fine if your application is simple enough that a business logic layer does not worth the effort.
Scenario
Some date are loaded into a program (e.g., evaluation of students in a class where each student is a distinct entity with his/her evaluation data) and a summary of them is shown on a datagrid. The user selects selects some of the students, and performs an analysis on their evaluation. The analysis process requires some parameters, therefore before analysis a window pops-up and lets user to specify his preferred parameters; then the analysis process executes.
Implementation summary
The datagrid is defined as following and binded to a ViewModel:
<DataGrid x:Name="CachedSamplesDG" ItemsSource="{Binding cachedDataSummary}">
<DataGrid.Columns>
<DataGridTextColumn Header="name" Binding="{Binding name}"/>
<DataGridTextColumn Header="score" Binding="{Binding score}"/>
</DataGrid.Columns>
</DataGrid>
The button that starts the process is defined as following:
<Button x:Name="AnalysisBT" Content="Analyze" Command="{Binding AnalyzeCommand}" CommandParameter="{Binding ElementName=CachedSamplesDG, Path=SelectedItems}"/>
The ViewModel is pretty basic and summarized as following:
internal class CachedDataSummaryViewModel
{
public CachedDataSummaryViewModel()
{
_cachedDataSummary = new ObservableCollection<CachedDataSummary>();
AnalyzeCommand = new SamplesAnalyzeCommand(this);
}
private ObservableCollection<CachedDataSummary> _cachedDataSummary;
public ObservableCollection<CachedDataSummary> cachedDataSummary { get { return _cachedDataSummary; } }
public ICommand AnalyzeCommand { get; private set; }
}
And here is the definition of analysis command:
internal class SamplesAnalyzeCommand : ICommand
{
public SamplesAnalyzeCommand(CachedDataSummaryViewModel viewModel)
{
_viewModel = viewModel;
}
private CachedDataSummaryViewModel _viewModel;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
// canExecute logic
}
public void Execute(object parameter)
{
// process mess ...
// Here I need the selected rows of datagird, which "parameter" delegates them.
// I also need some other parameters for analysis which user can set through another view
}
}
An this is a diagram of my current process and what I would like to do next
Question
When the button is clicked
Apply some UI changes on MainWindow
Pop-up ProcessOptionsWindow
Get set parameters from ProcessOptionsWindow
Pass the selected datagrid rows and user specified parameters to SamplesAnalyzeCommand
What would be the best way to achieve this requirement ?
simply use a dialogservice like Good or bad practice for Dialogs in wpf with MVVM?.
then you can do something like this in your ViewModel
var result = this.uiDialogService.ShowDialog("Prozess Options Window", prozessOptionVM);
...
var parameter1 = prozessOptionVM.Parameter1;
You can define another Model and ViewModel for Process Options, and then in the SamplesAnalyzeCommand, display the ProcessOptionsView. When user is done with the ProcessOptionsView, the main ViewModel gets notified (e.g by an event handler) and completes the Process.
Something like this:
internal class SamplesAnalyzeCommand : ICommand {
...
public void Execute(object parameter)
{
this._viewModel.ShowProcessOptions(parameter);
}
}
internal class CachedDataSummaryViewModel {
public string Status {
get {
return this.status;
}
set {
if (!string.Equals(this.status, value)) {
this.status = value;
// Notify property change to UI
}
}
}
...
internal void ShowProcessOptions(object paramter) {
// Model
var processOptions = new ProcessOptionsModel() {
otherInfo = parameter
};
// View-Model
var processOptionsViewModel = new ProcessOptionsViewModel();
processOptionsViewModel.Model = processOptions;
// View
var processOptionsView = new ProcessOptionsView(
processOptionsViewModel
);
// Edit2: Update status
this.Status = "Selecting process options...";
// You can use the event handler or dialog result
processOptionsViewModel.OK += this.PerformProcess;
processOptionsView.ShowDialog();
}
private void PerformProcess(object sender, EventArgs e) {
var processOptionsView = sender as ProcessOptionsView;
var processOptionsModel = processOptionsView.Model;
var processOptions = processOptionsModel.Model;
// Edit2: Update status
this.Status = "Performing process...";
// use processOptions.OtherInfo for initial info
// use processOptions.* for process options info
// and perform the process here
// Edit2: Update status
this.Status = "Process Done.";
}
...
}
class ProcessOptionsModel {
public object OtherInfo {
get;
set;
public int Parameter1 {
get;
set;
}
public IList<ProcessItem> SelectedItems {
get;
set;
}
...
}
class ProcessOptionsViewModel {
public event EventHandler OK;
private SamplesAnalyzeCommand model;
private ICommand okCommand;
public ProcessOptionsViewModel() {
this.okCommand = new OKCommand(this.OnOK);
}
public SamplesAnalyzeCommand Model {
get {
return model;
}
set {
this.model = value;
// Property changed stuff here
}
}
private void OnOK(object parameter) {
if (this.OK != null) {
this.OK = value;
}
}
}
class ProcessOptionsView {
// Interacts with it's view-model and performs OK command if
// user pressed OK or something
}
Hope it helps.
Edit (1):
As blindmeis suggested, you may use some Dialog Service to make the connection between the views.
Edit (2):
Immidiate UI changes after button click can be done in ShowProcessOptions method of the ShowProcessOptions. I don't think you want reflect ui changes of the options window while user works with it, to the main window. UI changes after user closes options window can be done in PerformProcess.
If you want to make an abstraction for options selection (e.g reading from a file) as you mentioned in the comment below, you may define an IOptionsProvider interface, and put ProcessOptionsView and View-Model behind that but still you use the same model.
interface IOptionsProvider {
ProcessOptionsModel GetProcessOptions();
}
class ProcessOptionsView : IOptionsProvider {
public ProcessOptionsModel GetProcessOptions() {
if (this.ShowDialog()) {
return this.ModelView.Model;
}
return null;
}
}
class ProcessOptionsFromFile : IOptionsProvider {
public ProcessOptionsModel GetProcessOptions() {
// Create an instance of ProcessOptionsModel from File
}
}
Note that in this case I removed the OK event since the GetProcessOptions is supposed to block until user closes the main window. If you want a responsive approach in the FromFile case, you may need to work on the async stuff, maybe define GetProcessOptionsAsync instead.
In this case things may get a little bit complicated but I guess it is achievable in this way.
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.