Struggling to pick up MVVM Basics - c#

I am brand new to MVVM (some experience with WPF) and I am getting really confused with regards to what I assume as the basics. I'm trying to make a simple registration form. The user enters their name, a username and a password. For the sake of learning MVVM and not over-complicating things, the only check I am doing on the password is if it contains an upper case letter. No hashing, encryption etc.. for now.
So I have a model that is a User, generated from entity framework. Here is my first bit of confusion. It looks like so:
public partial class User : INotifyPropertyChanged
{
public short Id { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public string Password { get; set; }
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
if (_firstName == value)
{
return;
}
_firstName = value;
OnPropertyChanged("FirstName");
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
As you can see, it's in a little bit of a mismatch. The first section was generated by Entity, which I have modified with regards to the FirstName to look like what I believe is more MVVM. Is the Model the location where I create get/set, do they also need to be in the ViewModel?
Moving onto my ViewModel, I am confused as to whether or not I need to recreate the properties of the User (FirstName, LastName ....) again, or are they accessible through the Model. Can I create a User in the ViewModel that exposes all of the properties of a User? Here is the code for my ViewModel so far:
internal class NewUserViewModel : BaseViewModel
{
private User _newUser;
public User NewUser
{
get => _newUser;
set
{
if (_newUser == value)
{
return;
}
_newUser = value;
OnPropertyChanged("NewUser");
}
}
private string _password;
public string Password
{
get => _password;
set
{
if (_password == value)
return;
_password = value;
OnPasswordChanged();
OnPropertyChanged("Password");
}
}
#region RegisterCommand
private DelegateCommand _registerCommand;
public ICommand RegisterCommand
{
get
{
_registerCommand = new DelegateCommand(param => Register(), param => CanRegister());
return _registerCommand;
}
}
private bool CanRegister()
{
return _isPasswordValid;
}
private bool _isPasswordValid;
public void OnPasswordChanged()
{
_isPasswordValid = Password.Any(char.IsUpper);
}
private void Register()
{
using (var context = new WorkstreamContext())
{
var users = context.Set<User>();
users.Add(_newUser);
context.SaveChanges();
}
}
#endregion
}
As of now I have recreated the Password property so I can access it, however this settles uneasily for me and I feel like I either expose all the properties or use NewUser, however I am unsure about how to do this. Currently the code half works. The Save button is grayed out, however it does not become enabled when the password contains an uppercase letter which is what I would expect. The actual registration form:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image Source="pack://application:,,,/Resources/NewUserForm/NewUser.jpg" HorizontalAlignment="Center" VerticalAlignment="Center" Height="100"/>
<TextBlock Grid.Row="1" Text="Please Enter Your Details" HorizontalAlignment="Center" Foreground="DarkSlateGray" FontSize="16"/>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Height="{Binding ActualHeight, ElementName=FirstNameTextBox}" Source="pack://application:,,,/Resources/NewUserForm/FirstName.jpg" Margin="5,5,0,5"/>
<xctk:WatermarkTextBox Grid.Column="1" x:Name="FirstNameTextBox" Watermark="first name" Text="{Binding NewUser.FirstName, Mode=TwoWay}" />
</Grid>
<Grid Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Height="{Binding ActualHeight, ElementName=LastNameTextBox}" Source="pack://application:,,,/Resources/NewUserForm/LastName.jpg" Margin="5,5,0,5"/>
<xctk:WatermarkTextBox Grid.Column="1" x:Name="LastNameTextBox" Watermark="last name" Text="{Binding NewUser.LastName, Mode=TwoWay}" />
</Grid>
<Grid Grid.Row="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Height="{Binding ActualHeight, ElementName=UsernameTextBox}" Source="pack://application:,,,/Resources/NewUserForm/User.jpg" Margin="5,5,0,5"/>
<xctk:WatermarkTextBox Grid.Column="1" x:Name="UsernameTextBox" Watermark="username" Text="{Binding NewUser.Username, Mode=TwoWay}" />
</Grid>
<Grid Grid.Row="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Height="{Binding ActualHeight, ElementName=PasswordTextBox}" Source="pack://application:,,,/Resources/NewUserForm/Password.jpg" Margin="5,5,0,5"/>
<xctk:WatermarkTextBox Grid.Column="1" x:Name="PasswordTextBox" Watermark="password" Text="{Binding NewUser.Password, Mode=TwoWay}"/>
</Grid>
<DockPanel Grid.Row="6">
<Button Content="Save" Command="{Binding RegisterCommand, Mode=OneWay, Source={StaticResource NewUserViewModel}}" HorizontalAlignment="Right"/>
<Button HorizontalAlignment="Right"/>
</DockPanel>
</Grid>
Is the way that I have bound the TextBoxes to the NewUser exposed on the ViewModel the correct way to operate in MVVM? I appreciate that there are ALOT of tutorials on MVVM, I have read/viewed many. However, I am getting to the stage where I am feeling more and more confused, and would really appreciate if someone would give me a breakdown on my code and pointers on where I am going wrong, why the code is not working and where I can improve.

Your question is very broad. But if the User class implements the INotifyPropertyChanged interface, it is effectively a kind of a view model and you can bind directly to the properties of this one like you are doing:
{Binding NewUser.FirstName}
If NewUser was some kind of DTO object, you could wrap it in your view model and bind to the view model properties:
public string Password
{
get { return _user.Password; }
set { return _user.Password = value; OnNotifyPropertyChanged(); }
}
A real "model" is rather a service or some kind of business logic object.
The Save button is grayed out, however it does not become enabled when the password contains an uppercase letter which is what I would expect.
Does the setter of your Password property even get hit? Bind to the Password property of the view model:
Text="{Binding Password}"
...and call the RaiseCanExecuteChanged() of the command to refresh its status:
private string _password;
public string Password
{
get => _password;
set
{
if (_password == value)
return;
_password = value;
OnPasswordChanged();
OnPropertyChanged("Password");
_registerCommand.RaiseCanExecuteChanged(); //<--
}
}

Related

Input to and Output from User Control in WPF

I made this minimalistic project to learn output and input with user control and it's working as intended. I want to ask, is this a good approach or is there something which is not necessary?
I also want to post this, because there is tons of post with specific user cases, but not one with a simple example to learn binding mechanics.
Main Window:
<Window x:Class="OutputFromUserControl.View.OutputFromUserControlWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:OutputFromUserControl.View"
xmlns:uc="clr-namespace:OutputFromUserControl.View.Controls"
xmlns:vm="clr-namespace:OutputFromUserControl.ViewModel"
mc:Ignorable="d"
Title="Output From User Control" Height="450" Width="800">
<Window.DataContext>
<vm:MainVM x:Name="MainVM"/>
</Window.DataContext>
<StackPanel HorizontalAlignment="Left">
<Label Content="Form elements:"/>
<Border CornerRadius="5" BorderBrush="Blue" BorderThickness="1">
<Grid HorizontalAlignment="Left" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Label Content="Name Input: " Grid.Row="0" Grid.Column="0"/>
<TextBox Grid.Row="0" Grid.Column="1"
Text="{Binding NameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<Label Content="Surname Input: " Grid.Row="1" Grid.Column="0"/>
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding SurnameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<Label Content="Name Output from Control: " Grid.Row="2" Grid.Column="0"/>
<TextBlock Grid.Row="2" Grid.Column="1"
Text="{Binding FullName}"
Width="200"
/>
</Grid>
</Border>
<Label Content="User Control:" Margin="0,10,0,0"/>
<Border CornerRadius="5" BorderBrush="Red" BorderThickness="1">
<uc:NameConcatControl x:Name="NameUC"
NameInput="{Binding NameInput}"
SurnameInput="{Binding SurnameInput}"
NameOutput="{Binding FullName, Mode=TwoWay}"
/>
</Border>
</StackPanel>
</Window>
MainVM:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
namespace OutputFromUserControl.ViewModel
{
public class MainVM : INotifyPropertyChanged
{
private string nameInput;
public string NameInput {
get { return nameInput; }
set
{
nameInput = value;
OnPropertyChanged(nameof(NameInput));
}
}
private string surnameInput;
public string SurnameInput {
get { return surnameInput; }
set {
surnameInput = value;
OnPropertyChanged(nameof(SurnameInput));
}
}
private string fullName;
public string FullName {
get { return fullName; }
set {
fullName = value;
OnPropertyChanged(nameof(FullName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Control xaml:
<UserControl x:Class="OutputFromUserControl.View.Controls.NameConcatControl"
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:OutputFromUserControl.View.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Label Content="Name Input: " Grid.Row="0" Grid.Column="0"/>
<TextBlock Grid.Row="0" Grid.Column="1"
Text="{Binding NameInput}"
x:Name="NameInputTextBlock"
/>
<Label Content="Surname Input: " Grid.Row="1" Grid.Column="0"/>
<TextBlock Grid.Row="1" Grid.Column="1"
Text="{Binding SurnameInput}"
x:Name="SurnameInputTextBlock"
/>
<Label Content="Name Output: " Grid.Row="2" Grid.Column="0"/>
<TextBlock Grid.Row="2" Grid.Column="1"
Text="{Binding NameOutput}"
x:Name="OutputNameTextBlock"
/>
</Grid>
</UserControl>
User control .cs:
using System.Windows;
using System.Windows.Controls;
namespace OutputFromUserControl.View.Controls
{
/// <summary>
/// Interaction logic for NameConcatControl.xaml
/// </summary>
public partial class NameConcatControl : UserControl
{
public string NameInput {
get { return (string)GetValue(NameInputProperty); }
set { SetValue(NameInputProperty, value); }
}
public static string defaultNameInput = "NameInput";
public static readonly DependencyProperty NameInputProperty =
DependencyProperty.Register("NameInput", typeof(string), typeof(NameConcatControl), new PropertyMetadata(defaultNameInput, SetNameOutput));
public string SurnameInput {
get { return (string)GetValue(SurnameInputProperty); }
set { SetValue(SurnameInputProperty, value); }
}
public static string defaultSurnameInput = "Surname Input";
public static readonly DependencyProperty SurnameInputProperty =
DependencyProperty.Register("SurnameInput", typeof(string), typeof(NameConcatControl), new PropertyMetadata(defaultSurnameInput, SetNameOutput));
public string NameOutput {
get { return (string)GetValue(NameOutputProperty); }
set { SetValue(NameOutputProperty, value); }
}
public static string defaultNameOutput = "Name Output";
public static readonly DependencyProperty NameOutputProperty =
DependencyProperty.Register("NameOutput", typeof(string), typeof(NameConcatControl), new PropertyMetadata(defaultNameOutput));
private static void SetNameOutput(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
NameConcatControl control = (NameConcatControl)d;
string nameInput = "";
string surnameInput = "";
if(e.Property.Name == "NameInput")
{
string newValue = (string)e.NewValue;
nameInput = string.IsNullOrEmpty(newValue) ? "" : newValue;
}
else
{
nameInput = string.IsNullOrEmpty(control.NameInputTextBlock.Text)
? ""
: control.NameInputTextBlock.Text;
}
if(e.Property.Name == "SurnameInput")
{
string newValue = (string)e.NewValue;
surnameInput = string.IsNullOrEmpty(newValue) ? "" : newValue;
}
else
{
surnameInput = string.IsNullOrEmpty(control.SurnameInputTextBlock.Text)
? ""
: control.SurnameInputTextBlock.Text;
}
string fullName = $"{nameInput} {surnameInput}";
control.OutputNameTextBlock.Text = fullName;
control.NameOutput = fullName;
}
public NameConcatControl()
{
InitializeComponent();
}
}
}
This question has a very wide answers. Different people with different approaches can use for their applications.
But we always follow one common formula.
Each view - will have its own view model. (Again in this approach, someone might say might not be true all the time).
From your code (xaml and code), below are my observations.
<Window.DataContext>
<vm:MainVM x:Name="MainVM"/>
</Window.DataContext>
I generally don't like setting data context in xaml. Instead I prefer to set it on the code-behind (mostly from constructor)
Instead of creating a dependency properties in user control and bind the MainVM properties to the dependency properties of User control.
I prefer to do it this way.
I prefer to create a separate UserControlViewModel.cs and add required properties to it.
public class UserControlViewModel : INotifyPropertyChanged
{
private string nameInput;
public string NameInput {
get { return nameInput; }
set
{
nameInput = value;
OnPropertyChanged(nameof(NameInput));
}
}
private string surnameInput;
public string SurnameInput {
get { return surnameInput; }
set {
surnameInput = value;
OnPropertyChanged(nameof(SurnameInput));
}
}
private string fullName;
public string FullName {
get { return fullName; }
set {
fullName = value;
OnPropertyChanged(nameof(FullName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Then I prefer to add this as a property in MainVM.cs
public class MainVM : INotifyPropertyChanged
{
private UserControlViewModel _userControlViewModel;
public UserControlViewModel UserControlViewModel
{
get { return _userControlViewModel; }
set
{
_userControlViewModel = value;
OnPropertyChanged(nameof(UserControlViewModel));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
// Rest of your code
// You don't need existing properties any more here.
// If you want to access these properties from MainVM then use the UserControlViewModel property and access the members of it.
}
Then I prefer to set the data-context of my UserControl to this property like below in my MainWindow.xaml
<uc:NameConcatControl x:Name="NameUC" ="{Binding UserControlViewModel}" />
My usercontrol contorl binding's still remain same as the property names are same and we moved to UserControlViewModel.cs
Now you can remove all dependency properties from code behind of UserControl.xaml.cs
Note :- As I stated at the beginning of my answer, this question has wide area for answers and there are lot of possibilities to answer this question.
I hope I have tried to give you some inputs from my end. I guess this should give you some idea to develop rest..
You can try making those changes and let me know in case if you face any errors or binding issues.
Assuming you just want the full-name view to be something like "Surname, Name", you could actually remove the FullName property from your view model, and just use a MultiBinding (btw the StringFormat property can be used with both MultiBindings and regular Bindings, its pretty nifty if you aren't familiar with it).
As for the Labels, it's good to make a habit of using the simplest control required to get the job done, and in this case, TextBlocks would do just fine, since you don't appear to be using any of the extended functionality the Label offers (i.e., BorderBrush, Padding, ContentTemplate, etc.).
You don't generally need to create your own dependency properties in UserControl derived classes, since they are usually designed with a particular viewmodel in mind. They are more useful when the view is independent from the viewmodel, and the dependency properties serve as an api, through which other controls/viewmodels can interact with it.
<Window x:Class="OutputFromUserControl.View.OutputFromUserControlWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:OutputFromUserControl.View"
xmlns:uc="clr-namespace:OutputFromUserControl.View.Controls"
xmlns:vm="clr-namespace:OutputFromUserControl.ViewModel"
mc:Ignorable="d"
Title="Output From User Control" Height="450" Width="800">
<Window.DataContext>
<vm:MainVM x:Name="MainVM"/>
</Window.DataContext>
<StackPanel HorizontalAlignment="Left">
<Label Content="Form elements:"/>
<Border CornerRadius="5" BorderBrush="Blue" BorderThickness="1">
<Grid HorizontalAlignment="Left" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Name Input:" Grid.Row="0" Grid.Column="0"/>
<TextBox Grid.Row="0" Grid.Column="1"
Text="{Binding NameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<TextBlock Text="Surname Input:" Grid.Row="1" Grid.Column="0"/>
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding SurnameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<TextBlock Text="Name Output from Control:" Grid.Row="2" Grid.Column="0"/>
<TextBlock Grid.Row="2" Grid.Column="1" Width="200">
<MultiBinding StringFormat="{}{0}, {1}">
<Binding Path="SurnameInput"/>
<Binding Path="NameInput"/>
</MultiBinding>
</TextBlock>
</Grid>
</Border>
<Label Content="User Control:" Margin="0,10,0,0"/>
<Border CornerRadius="5" BorderBrush="Red" BorderThickness="1">
<uc:NameConcatControl x:Name="NameUC"
NameInput="{Binding NameInput}"
SurnameInput="{Binding SurnameInput}"
NameOutput="{Binding FullName}"
/>
</Border>
</StackPanel>

How can I add a new entry to my ObservableCollection

I'm using an ObservableCollection to generate a List in my WPF app.
public partial class NameList : ObservableCollection<SetCredentialsForAD>
{
public NameList() : base()
{
using var forest = Forest.GetCurrentForest();
Forest currentForest = Forest.GetCurrentForest();
DomainCollection domains = currentForest.Domains;
foreach (Domain objDomain in domains)
{
Add(new SetCredentialsForAD("domain", "name", "password"));
}
}
}
public class SetCredentialsForAD
{
private string loginName;
private string passWord;
private string domainName;
public SetCredentialsForAD(string domain, string lName, string pWord)
{
this.domainName = domain;
this.loginName = lName;
this.passWord = pWord;
}
public string DomaineName
{
get { return domainName; }
set { domainName = value; }
}
public string LoginName
{
get { return loginName; }
set { loginName = value; }
}
public string PassWord
{
get { return passWord; }
set { passWord = value; }
}
}
Xaml:
<ListBox x:Name="CredentialList" Width="auto" Height="auto"
Grid.Column="0" Grid.Row="2" Style="{StaticResource CredentialList}"
SelectionMode="Multiple"
ItemsSource="{Binding Source={StaticResource NameListData}}"
ItemTemplate="{StaticResource NameItemTemplate}"
IsSynchronizedWithCurrentItem="True"/>
xaml datatemplate:
<c:NameList x:Key="NameListData"/>
<DataTemplate x:Key="NameItemTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="DomainNameForCredentials" FontSize="18" Grid.Row="2" Grid.Column="0" Text="{Binding Path=DomaineName, Mode=TwoWay}" Style="{StaticResource CredentialListTextBlock}" ></TextBlock>
<Label Grid.Row="1" Grid.Column="1" Content="samAccountName" Style="{StaticResource CredentialListLabel}" ></Label>
<Label Grid.Row="1" Grid.Column="2" Content="Passwort" Style="{StaticResource CredentialListLabel}"></Label>
<TextBox x:Name="samAccountNameForCredentials" Grid.Row="2" Grid.Column="1" Text="{Binding Path=LoginName, Mode=TwoWay}" Style="{StaticResource CredentialListTextBox}" />
<TextBox x:Name="passwordForCredentials" Grid.Row="2" Grid.Column="2" Text="{Binding Path=PassWord, Mode=TwoWay}" Style="{StaticResource CredentialListTextBox}"/>
</Grid>
</DataTemplate>
and I need to add a new Item on Click to the List to display it in my UI and do other stuff with it.
I've tried some things to add en empty listItem like:
var setCredentialsforAD = new NameList();
setCredentialsforAD.Add(new SetCredentialsForAD("","",""));
What is the right way to solve my problem?
thank you
I would look into how to use an MVVM pattern in your application. I think the approach you are taking makes things more difficult. How to do that is beyond the scope of an answer. I only can answer this question because I am familiar with your other post.
How to get Data from WPF Data Template with c#
The point of an ObservableCollection is that it implements INotifyPropertyChanged on Adds/Removes. Which means when you Add an item the UI is notified of that change and will update accordingly. You do not want to reinstantiate ObservableCollections.
Based on the information in the previous post and what is above to accomplish what you need would be
private void SaveDomainCredentials_OnClick(object sender, RoutedEventArgs e)
{
if ( CredentialList.ItemsSource is NameList nameList )
{
nameList.Add(new SetCredentialsForAD("", "", ""));
}
}

Should these members be in a model? and if so, how?

I'm new to WPF and Prism, so, I'm trying to figure very basic things out at the moment. My little experiment looks like this when running:
I have a Views\Registration.xaml that looks like this:
<UserControl x:Class="Configurator.Views.Registration"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="Email:" HorizontalContentAlignment="Right" Margin="6"/>
<TextBox Grid.Column="1" Grid.Row="0" x:Name="email" Margin="6" Text="{Binding Email}"/>
<Label Grid.Column="0" Grid.Row="1" Content="Password:" HorizontalContentAlignment="Right" Margin="6"/>
<PasswordBox Grid.Column="1" Grid.Row="1" x:Name="password" Margin="6" />
<Label Grid.Column="0" Grid.Row="2" Content="Password Confirmation:" HorizontalContentAlignment="Right" Margin="6"/>
<PasswordBox Grid.Column="1" Grid.Row="2" x:Name="passwordConfirmation" Margin="6"/>
<Button Grid.Column="1" Grid.Row="3" Content="Register" HorizontalAlignment="Left" Margin="6" Padding="6,2" Command="{Binding RegisterCommand}"/>
</Grid>
</UserControl>
and then a ViewModels\RegistrationViewModel.cs that looks like this:
namespace Configurator.ViewModels {
public class RegistrationViewModel : BindableBase {
private string _email;
public string Email {
get { return _email; }
set { SetProperty(ref _email, value); }
}
private string _password;
public string Password {
get { return _password; }
set { SetProperty(ref _password, value); }
}
private string _passwordConfirmation;
public string PasswordConfirmation {
get { return _passwordConfirmation; }
set { SetProperty(ref _passwordConfirmation, value); }
}
public DelegateCommand RegisterCommand { get; private set; }
public RegistrationViewModel() {
Console.WriteLine("RegistrationViewModel");
RegisterCommand = new DelegateCommand(Register);
}
private void Register() {
Console.WriteLine("Registering");
Console.WriteLine($"Email: {Email}");
Console.WriteLine($"Password: {Password}");
Console.WriteLine($"Password Confirmation: {PasswordConfirmation}");
}
}
}
Should the Email, Password and PasswordConfirmation go into a model when following MVVM? If it should go into a model, how is the wiring done? I can't find any examples.
Your implementation looks all good to me, i.e. you bind to properties of a view model.
Sometimes people tend to refer to view models as "models" but the actual model would in this case be represented by a service that performs the actual registration.
You could inject you view model with an interface that this service implements and then call a method of the service through this interface in your Register() method, e.g.:
public class RegistrationViewModel : BindableBase
{
...
private readonly IRegistrationService _registrationService;
public RegistrationViewModel(IRegistrationService registrationService)
{
_registrationService = registrationService ?? throw new ArgumentNullException(nameof(registrationService));
RegisterCommand = new DelegateCommand(Register);
}
private void Register()
{
_registrationService.Register(...);
}
}
Since you are using MVVM, the properties should be in a Model.
You then create a property of that Model in your ViewModel make it call the PropertyChanged-Event from INotifyPropertyChanged.
In the View you then bind the Element-Names of the Model-Property from your ViewModel.
You then just should decide wheter you make your Model implement INotifyPropertyChanged as well or you find another way.
Your ViewModel and your XAML are fine. Yes, you should expose the Email, Password etc. as part of the ViewModel.
The missing part in your example is the binding of the ViewModel to the UI. It is possible to achieve this by declaring it in your XAML, or in the constructor in the code-behind page you could have
public Registration(RegistrationViewModel model)
{
DataContext = model;
}
What wiring are you enquiring about because it all looks pretty good to me.

How to make INotifyDataErrorInfo errors persistent until corrected?

My validation works in the first instance but when I go from one Tab Item to another and back again the validation is gone.
I would have expected this state to be persistent until the user inputs the valid passing criteria in this scenario.
Maybe this is expected behaviour, and I have missed something in the documentation.
In my application I have a MainWindow.xaml, and a MainWindowViewModel.cs as its DataContext which inherits from a BaseViewModel.cs that implements INotifyPropertyChanged, and INotifyDataErrorInfo. Then I have created a custom ValidationAttribute called FilePath.cs.
The MainWindow.xaml consists of a Tab Control with two Tab Items. Only one has a text boxs which I have implemented my validation on. This works in the sense that it notifies me when the validation is incorrect. In this example the file path does not exist.
MainWindow.xaml
<TabControl>
<TabItem Header="Boxs">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Grid.Row="0"
Content="File Path" />
<TextBox Grid.Column="1"
Grid.Row="0"
Margin="5,5,5,5"
Text="{Binding FilePath,
Mode=TwoWay,
NotifyOnValidationError=True,
ValidatesOnNotifyDataErrors=True,
UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</TabItem>
<TabItem Header="Blank"/>
</TabControl>
MainViewModel.cs
public class MainWindowViewModel : BaseViewModel
{
private string filePath;
[FilePath]
public string FilePath
{
get { return filePath; }
set
{
filePath = value;
ValidateProperty(value);
NotifyPropertyChanged(FilePath);
}
}
}
FilePath.cs
public sealed class FilePath : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (!Directory.Exists((string)value))
{
return new ValidationResult("Requires Valid File Path",
new string[] { validationContext.MemberName });
}
return ValidationResult.Success;
}
}
Not sure about your case, but you can give a try,
TextBox with validation loses ErrorTemplate on tab change
btw, i'm not the downvoter :)

MVVM Creating the ViewModel

Can somebody explain to me how exactly to create a ViewModel for the MVVM Pattern.
I tried to understand the the tutorial here: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx , but I was unable to understand what exactly is happening in the code.
Let's say we want to create a basic application about getting and adding people from and to a local database and displaying them in the View. How should the ViewModel look like and how to create the RelayCommands for it. First why do we set the variables twice: once privately and then again publicaly.
EDIT: Thanks for the help so far. I have one more thing that I don't know to do - how to bind the View to the ViewModel and Vice Versa
Here is the Model:
public class Student : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string name;
private string surname;
private string age;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public string Surname
{
get
{
return surname;
}
set
{
surname = value;
OnPropertyChanged("Surname");
}
}
public string Age
{
get
{
return age;
}
set
{
age = value;
OnPropertyChanged("Age");
}
}
}
and here is the ViewModel:
public class MainViewModel : ViewModelBase
{
ObservableCollection<Student> studentList;
Student selectedPerson;
public MainViewModel()
{
//populate some sample data
studentList = new ObservableCollection<Student>()
{
new Student(){Name="John", Surname="Smith", Age="28"},
new Student(){Name="Barbara", Surname="Anderson", Age="23"}
};
}
public ObservableCollection<Student> StudentList
{
get { return studentList; }
}
public Student SelectedPerson
{
get { return selectedPerson; }
set
{
selectedPerson = value;
RaisePropertyChanged("SelectedPerson");
}
}
private RelayCommand _addStudentCommand;
public ICommand AddStudentCommand
{
get
{
return _addStudentCommand
?? (_addStudentCommand = new RelayCommand(() =>
{
Student student = new Student();
studentList.Add(student);
}));
}
}
}
I have found a way to bind the ViewModel to the View using some code for the view in Csharp but the question how to bind the View to the ViewModel is still on my mind. To be more specific how to create a new student using the values a user has entered in the View.
Here is the View's XAML code
<Window x:Class="MVVMLight.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
SizeToContent="WidthAndHeight">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="NameTextBlock"
Text="Name"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock x:Name="SurnameTextBlock"
Grid.Row="1"
Text="Surname"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock x:Name="AgeTextBlock"
Grid.Row="2"
Text="Age"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBox x:Name="NameTextBox"
Grid.Column="1"
Style="{StaticResource TextBoxTextStyle}"/>
<TextBox x:Name="SurnameTextBox"
Grid.Row="1"
Grid.Column="1"
Style="{StaticResource TextBoxTextStyle}"/>
<TextBox x:Name="AgeTextBox"
Grid.Row="2"
Grid.Column="1"
Style="{StaticResource TextBoxTextStyle}"/>
<ListBox x:Name="StudentListBox"
Grid.ColumnSpan="2"
Grid.Row="4"
Style="{StaticResource ListBoxStyle}"
ItemsSource="{Binding StudentList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock Text="{Binding Surname}"
Grid.Column="1"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock Text="{Binding Age}"
Grid.Column="2"
Style="{StaticResource TextBlockTextStyle}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="AddButton"
Grid.Row="7"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
Content="Add"
Margin="7,7,7,7"
Command="{Binding AddStudentCommand}"/>
</Grid>
And here is the View's Csharp code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
I have some questions concerning the Binding between the View and The ViewModel:
What are the pros and cons of using this type of binding?
What is the best way of binding if I am going to use a database?
Is this how the ViewModel and Model should look like
How to create a RelayCommand for adding a student to the ObservableCollection
Why do we set things first privately and then again publically [Answered]
How to bind the View to the ViewModel and Vice Versa
in your property setters you should check to see if the new value is equal to the old value, if it is you should return and not fire the PropertyChanged event.
As for your questions:
Yes this looks fine.
There are a couple of ways to setup your relay commands. I prefer
private RelayCommand<Student> _addStudentCommand;
public ICommand AddStudentCommand
{
get
{
return _addStudentCommand
?? (_addStudentCommand = new RelayCommand<Student>((student) =>
{
studentList.Add(student);
}));
}
}
another way without passing in a student object
private RelayCommand _addStudentCommand;
public ICommand AddStudentCommand
{
get
{
return _addStudentCommand
?? (_addStudentCommand = new RelayCommand(() =>
{
Student student = new Student();
studentList.Add(student);
}));
}
}
That is how properties work in .net, You could use automatic properties, but since you need to fire change notification in the setter you have to declare the field that the property will work against.
Also since it looks like you are using mvvm light you should try the code snippets. They make properties very easy to create. type mvvvminpc then hit tab twice. then fill in the highlighted part and hit tab till you are finished.
You can bind the View To the Viewmodel a couple of ways. I know that it is an Antipattern but you could use a locator. The basic idea is to set the viewmodel as the views datacontext.
public class Locator
{
public Viewmodel1 Viewmodel1
{
return new Viewmodel1();
}
}
You then in you app.xaml you add this class
<Application.Resources>
<Locator x:key="VMLocator" />
</Application.Resources>
Then in your view in the xaml
<Page DataContext="{Binding Source="{StaticResource VMLocator}" Path=ViewModel1}">
</Page>

Categories