I have next xaml code.
<StackPanel Grid.Row="1" x:Name="spLogin">
<TextBlock Text="E-mail:"></TextBlock>
<TextBox Name="tbLogin" Text="{Binding User.Email, Mode=TwoWay}"></TextBox>
<TextBlock Text="Password:"></TextBlock>
<TextBox Name="tbPassword" Text="{Binding User.Password, Mode=TwoWay}"></TextBox>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Name="btnLogin" Content="Login">
<Interactivity:Interaction.Triggers>
<Interactivity:EventTrigger EventName="Click"
x:Name="SelectChangeEvent">
<Command:EventToCommand
Command="{Binding Login, Mode=TwoWay}" PassEventArgsToCommand="False" CommandParameter="{Binding}" />
</Interactivity:EventTrigger>
</Interactivity:Interaction.Triggers>
</Button>
<Button Name="btnClear" Content="Clear"/>
</StackPanel>
</StackPanel>
</Grid>
How I can pass tbLogin.Text and tbPassword.Text to ViewModel like as a field of one object User
public class User
{
public string Login{get;set;}
public string Password(get;set;)
}
Normally I would just say ... you don't need to, as the your ViewModel (deduced from your bindings) should contain both, a User property, and the Login command iself, so that you just can access the User property from your command's Excecute method.
However, I noticed that the user class your presented user class does not implement INotifyPropertyChanged. Therefore, the binding will not work correctly. To make it work you have two possibilities:
Implement INotifyPropertyChanged on your user class. -- Depending on your model and how it is generated this is not allways possible.
Duplicate the properties on your ViewModel and implementing INotifyPropertyChanged there. -- If you don't have control over the definition of you model (e.g. as it is generated by a web service proxy) this is the only way of doing it. But, even if you have control over your model this option is worth considering as gives your greater control over what is passed to your View.
So, the following sample assumes yoou go for the second option:
public class MvvmViewModel1 : ViewModelBase
{
private User _user;
#region [Login]
public const string LoginPropertyName = "Login";
public string Login {
get {
return this._user.Login;
}
set {
if (this._user.Login == value) {
return;
}
var oldValue = this._user.Login;
this._user.Login = value;
RaisePropertyChanged(LoginPropertyName);
}
}
#endregion
#region [Password]
public const string PasswordPropertyName = "Password";
public string Password {
get {
return this._user.Password;
}
set {
if (this._user.Password == value) {
return;
}
var oldValue = this._user.Password;
this._user.Password = value;
RaisePropertyChanged(PasswordPropertyName);
}
}
#endregion
#region [LoginCommand]
public RelayCommand _loginCommand;
public RelayCommand LoginCommand {
get {
return _loginCommand ?? (_loginCommand = new RelayCommand(
() => {
// perform your login action here
// access Login with: this.Login
// access Password with: this.Password
},
() => {
// can execute method - sample implementation
return (!string.IsNullOrEmpty(this.Login) && !string.IsNullOrEmpty(this.Password));
}
));
}
}
#endregion
}
Obviously you will have to modify your XAML to match this ViewModel:
<StackPanel Grid.Row="1" x:Name="spLogin">
<TextBlock Text="E-mail:"></TextBlock>
<TextBox Name="tbLogin" Text="{Binding Login, Mode=TwoWay}"></TextBox>
<TextBlock Text="Password:"></TextBlock>
<TextBox Name="tbPassword" Text="{Binding Password, Mode=TwoWay}"></TextBox>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Name="btnLogin" Content="Login">
<Interactivity:Interaction.Triggers>
<Interactivity:EventTrigger EventName="Click"
x:Name="SelectChangeEvent">
<Command:EventToCommand
Command="{Binding LoginCommand}" PassEventArgsToCommand="False" />
</Interactivity:EventTrigger>
</Interactivity:Interaction.Triggers>
</Button>
<Button Name="btnClear" Content="Clear"/>
</StackPanel>
</StackPanel>
Normally you only need to use CommandParameter if you are inside a data template (e.g. an item template inside a ListBox) and/or your command and your properties are not in the same ViewModel. In this case you have to define your command to accept a parameter:
#region [LoginCommand]
public RelayCommand _loginCommand;
public RelayCommand LoginCommand {
get {
return _loginCommand ?? (_loginCommand = new RelayCommand(
(p) => {
// perform your login action here
},
(p) => {
// can execute method - sample implementation
return (!string.IsNullOrEmpty(this.Login) && !string.IsNullOrEmpty(this.Password));
}
));
}
}
#endregion
And now you can use
CommandParameter="{Binding}"
inside your XAML to pass in the data context of the data template to your ViewModel.
And another side note: You normally do not need two way binding - except in the case when you want to write information back from your View to your ViewModel.
Also as a side note: You cannot generate/convert classes in XAML; so if you have a User class with Email/Password properties there in your ViewModel there is no way of creating a new user class with Login/Password properties out of thin air in XAML and pass that to your ViewModel. Binding is powerful but not allmighty ... ;-)
Since your tbLogin.Text and tbPassword.Text two-way bound to User.Email and User.Password correspondently you can simply bind CommandParameter to the User. Bigger question is: why do you need CommandParameter at all then your view model can access User property directly?
Related
I have a Button bound to an ICommand interface but it isn't being fired when I run the application.
The button should be disabled when the app runs, putting a breakpoint in the ICommand or CanUpdate but it isn't being hit.
The ICommand seems to have been implemented correctly as far I can see - have substituted value in CanUpdate for simplicity...
Scratching my head to workout what is missing?....
XAML
<StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="1">
<RadioButton Width="64" IsChecked="{Binding Passed}" GroupName="T1">Yes</RadioButton>
<RadioButton Width="64" IsChecked="{Binding Passed, Converter={StaticResource InverseBoolRadioConverter}}" GroupName="T1" >No</RadioButton>
</StackPanel >
Button Command="{Binding UpdateHasPassed}" Content="Update"></Button>
Code-Behind:-
private RelayCommand hasPassed;
public bool Passed
{
get
{
return passed;
}
set
{
if (passed !=value )
{
passed = value;
OnPropertyChanged();
}
}
}
public ICommand HasPassed
{
get
{
if (hasPassed == null)
{
haspassed = new RelayCommand( param => CanUpdate());
}
return haspassed;
}
}
private bool CanUpdate()
{
return (1 != 2)
}
You're on the right track looking into INotifyPropertyChanged. I would also recommend reading up on WPF data bindings, the ICommand interface (and specifically creating a RelayCommand, more on that later), and MVVM design.
The benefit of WPF data bindings and ICommand is that you can control when the button gets enabled or disabled, based on your conditional criteria (i.e. name has changed from its original value). With the tips mentioned here, you should be able to do what you want in short time. Just google each of the topics and you'll get what you need.
C#:
public void SetCompetition(Window wT1)
{
//Add all the Copetition
wT1._competition = new List<Competition>();
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test1", IsSelected = false });
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test2", IsSelected = false });
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test3", IsSelected = false });
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test4", IsSelected = false });
wT1.cboSetupCompetition.ItemsSource = wT1._competition;
wT1.cboSetupCompetition.Items.Refresh();
}
Data Template:
<UserControl.Resources>
<System:Double x:Key="Double1">11</System:Double>
<DataTemplate x:Key="cmbCompetition">
<WrapPanel Height="30" >
<Label Content="{Binding Name}" ></Label>
</WrapPanel>
</DataTemplate>
</UserControl.Resources>
<ComboBox x:Name="cboSetupCompetition" ItemTemplate="{DynamicResource cmbCompetition}" HorizontalAlignment="Left" Margin="29,28,0,0" VerticalAlignment="Top" Width="173" RenderTransformOrigin="0.5,0.591" FontSize="12" Height="22" IsEditable="True" Background="#FFD8D8D8" SelectionChanged="UpdateCompetitionSelection"/>
I have a Combobox with a label and an image and when I select an item I would like to see the same format in the Combobox when it is closed. I am not getting any errors I am seeing the name of the application.Competition(this is my object Model) instead of the values of the image and label.
The SetCopetition is invoked when the application loads.
A TextBox is not able to display a Label and an Image or whatever elements that are in your DataTemplate in it.
Set the IsEditable property of the ComboBox to false and it should work as expected, i.e. your DataTemplate will be applied to the selected item when the ComboBox is closed:
<ComboBox x:Name="cboSetupCompetition" IsEditable="False" ItemTemplate="{DynamicResource cmbCompetition}" HorizontalAlignment="Left" Margin="29,28,0,0" VerticalAlignment="Top" Width="173" RenderTransformOrigin="0.5,0.591" FontSize="12" Height="22" Background="#FFD8D8D8" SelectionChanged="UpdateCompetitionSelection"/>
Your issue has nothing to do with MVVM...
the specific problem as Mn8 spotted is that IsEditable=true forces the combo to display a textbox as the selected item
However you are still thinking winforms not WPF, using code behind to link data into the view causes many problems and instability as quite often this breaks the binding connections which is what is suspected was your problem initially, using a proper MVVM approach will eliminate all these problems
the best overveiw of MVVM i know of is
https://msdn.microsoft.com/en-gb/library/hh848246.aspx
Model
this is your data layer, it handle storage and access to data, your model will handle access to files, databases, services, etc
a simple model would be
public class Model
{
public string Text { get; set; }
public Uri Uri { get; set; }
}
ViewModel
on top of your Model you have your View Model
this manages the interaction of your View with the model
for example here because it uses Prism's BindableBase the SetProperty method notifies the View of any changes to the data, the ObservableCollection automatically notifies of changes to the collection, it also uses Prism's DelegateCommand to allow method binding in the view
public class ViewModel:BindableBase
{
public ViewModel()
{
AddItem = new DelegateCommand(() => Collection.Add(new Model()
{
Text = NewText,
Uri = new Uri(NewUri)
}));
}
private string _NewText;
public string NewText
{
get { return _NewText; }
set { SetProperty(ref _NewText, value); }
}
private string _NewUri;
public string NewUri
{
get { return _NewUri; }
set { SetProperty(ref _NewUri, value); }
}
private Model _SelectedItem;
public Model SelectedItem
{
get { return _SelectedItem; }
set
{
if (SetProperty(ref _SelectedItem, value))
{
NewText = value?.Text;
NewUri = value?.Uri.ToString();
}
}
}
public ObservableCollection<Model> Collection { get; } = new ObservableCollection<Model>();
public DelegateCommand AddItem { get; set; }
}
View
the View ideally does nothing but displays and collects data, all formatting / Styling should be done here
firstly you need to define the data source, the usual way is via the data context as this auto inherits down the visual tree, in the example because i set the window's datacontext, i have also set it for everything in the window the only exception is the dataTempplate as this is set to the current item in the collection
i then bind properties to the datasource
Note the code behind file is only the default constructor no other code at all
<Window
x:Class="WpfApplication1.MainWindow"
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:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<StackPanel>
<GroupBox Header="Text">
<TextBox Text="{Binding NewText}"/>
</GroupBox>
<GroupBox Header="URI">
<TextBox Text="{Binding NewUri}"/>
</GroupBox>
<Button Content="Add" Command="{Binding AddItem}"/>
<ComboBox ItemsSource="{Binding Collection}" SelectedItem="{Binding SelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Uri}" />
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Window>
I've got the following ItemsControl that gives me a check box for every database within the available collection. These checkboxes allow the user to select which ones to filter on. The databases to filter on are in a separate collection (FilteredDatabases). How exactly do I do this? I could add an InFilter property to the database item class. But, I don't want to start changing this code yet. The problem I can't get around in my head is the fact that I need to bind to a property that is not on the database item itself. Any ideas?
<ItemsControl ItemsSource="{Binding AvailableDatabases}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding ???}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
// In view model
public IBindingList FilteredDatabases
{
get;
private set;
}
public IBindingList AvailableDatabases
{
get;
private set;
}
Bind CheckBox.Command to routed command instance
Bind routed command to method
Use IBindingList.Add and IBindingList.Remove methods
The following code illustrates what you are trying to do, in order to do this you are better off using ObservableCollection instead of as your collection object, if an ItemsControl is bound to it it will automatically update the UI when viewmodels are added and removed.
XAML:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ItemsControl Grid.Column="0" ItemsSource="{Binding AvailableDatabases}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl Grid.Column="1" ItemsSource="{Binding FilteredDatabases}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
View Models:
public class MainViewModel
{
private ObservableCollection<DBViewModel> _availableDatabases;
private ObservableCollection<DBViewModel> _filteredDatabases;
public ObservableCollection<DBViewModel> AvailableDatabases
{
get
{
if (_availableDatabases == null)
{
_availableDatabases = new ObservableCollection<DBViewModel>(new List<DBViewModel>()
{
new DBViewModel(this) { Name = "DB1" , IsChecked = true},
new DBViewModel(this) { Name = "DB2" },
new DBViewModel(this) { Name = "DB3" },
new DBViewModel(this) { Name = "DB4" },
new DBViewModel(this) { Name = "DB5" },
new DBViewModel(this) { Name = "DB6" },
new DBViewModel(this) { Name = "DB7" , IsChecked = true },
});
}
return this._availableDatabases;
}
}
public ObservableCollection<DBViewModel> FilteredDatabases
{
get
{
if (_filteredDatabases == null)
_filteredDatabases = new ObservableCollection<DBViewModel>(new List<DBViewModel>());
return this._filteredDatabases;
}
}
}
public class DBViewModel
{
private MainViewModel _parentVM;
private bool _isChecked;
public string Name { get; set; }
public DBViewModel(MainViewModel _parentVM)
{
this._parentVM = _parentVM;
}
public bool IsChecked
{
get
{
return this._isChecked;
}
set
{
//This is called when checkbox state is changed
this._isChecked = value;
//Add or remove from collection on parent VM, perform sorting here
if (this.IsChecked)
_parentVM.FilteredDatabases.Add(this);
else
_parentVM.FilteredDatabases.Remove(this);
}
}
}
View models should also implement INotifyPropertyChanged, I omitted it since it was not necessary in this particular case.
I Have two views in MVVM(WPF). First View contains two Text boxes: User Name, Password, second view is having two Buttons: Submit and Clear. Both Views now set on On Form. When I press 'Clear' button both textboxes are cleared and in Submit a message of UserName and Password is displayed. I am using only MVVM+WPF, not prism.
ModelView Of First View:
class LoginView:ViewModelBase
{
string _userName;
public string UserName
{
get {return _userName ; }
set {
if (_userName != value)
{
_userName = value;
}
base.OnPropertyChanged(UserName);
}
}
string _Pwd;
public string PWD
{
get { return _Pwd; }
set
{
_Pwd = value;
base.OnPropertyChanged(_Pwd);
}
}
}
and For Button
class ButtonHandler
{
private DelegateCommand _ClearData;
public ICommand ClearCommand
{
get
{
if (_ClearData == null)
{
_ClearData = new DelegateCommand(ClearText);
}
return _ClearData;
}
}
LoginView lg = new LoginView();
private void ClearText()
{
lg.UserName = "";
lg.PWD = "";
}
}
and View Code of First Control
<Label Content="Login" Grid.Row="0" Grid.Column="1" VerticalAlignment="Top" HorizontalAlignment="Left"
FontFamily="Georgia" FontSize="24" FontWeight="UltraBold" ></Label>
<Label Content="User Name" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Left"></Label>
<Label Content="Password" Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Left"></Label>
<TextBox Name="username" Grid.Row="1" Grid.Column="1" Margin="100,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="{Binding Path=UserName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ></TextBox>
<TextBox Name="pwd" Grid.Row="2" Grid.Column="1" Margin="100,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="{Binding Path=PWD,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
<Separator Grid.Row="0" Grid.Column="1" Height="5" Margin="0,40,0,0" Background="Green"></Separator>
and Button View
<Button x:Name="Submit" Content="Submit" Grid.Column="1"></Button>
<Button x:Name="Clear" Content="Clear" Grid.Column="2"
Command="{Binding Path=ClearCommand, Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" >
</Button>
Why it is not working?
You are not using the MVVM pattern correctly, with this pattern the ViewModel should not have a reference to the View. A command is part of your ViewModel, therefore your reference to LoginView violates the pattern.
So you have two input fields and a button? for this I would have a single ViewModel and a single View. The ViewModel would expose two string properties (username & password) and a command that binds to the clear button. When the command executes it would clear the username and password texts on the ViewModel. The View will then update accordingly.
The basic principle of MVVM is to have a class that the view can bind to that has all the application logic inside of it. One of the main reasons is to have a separation of concerns. So if you want a username you expose a property that the view binds to and then when you want to log in you create a function that uses those bound values to submit to you business logic layer of your application.
This would seem to be one way to utilize MVVM in your example:
public class LoginViewModel
{
public string UserName {get;set;}//Implement INotifyPropertyChanged
public string PWD {get;set;}
private DelegateCommand _ClearData;
public ICommand ClearCommand
{
get
{
if (_ClearData == null)
{
_ClearData = new DelegateCommand(ClearText);
}
return _ClearData;
}
}
private void ClearText()
{
UserName = "";
PWD = "";
}
}
and then in your xaml:
<TextBox Text={Binding UserName} />
<TextBox Text={Binding PWD} />
<Button Command={Binding ClearCommand}/>
I've totally lost in the command binding that is used in MVVM. How should I bind my object to the window and/or its command to the control to get method called on the Button Click?
Here is a CustomerViewModel class:
public class CustomerViewModel : ViewModelBase
{
RelayCommand _saveCommand;
public ICommand SaveCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand(param => this.Save(), param => this.CanSave);
NotifyPropertyChanged("SaveCommand");
}
return _saveCommand;
}
}
public void Save()
{
...
}
public bool CanSave { get { return true; } }
...
ViewModelBase implements the INotifyPropertyChanged interface
Here is how Button is bound to the command:
<Button Content="Save" Margin="3" Command="{Binding DataContext.Save}" />
An instance of the CustomerViewModel is assigned to the DataContext of the window that contains a Button.
The given example is not working: I've put break point into the Save method but execution doesn't pass to the method. I've saw a lot of examples (on the stackoverflow too), but can't figure out how binding should be specified.
Please advise, any help will be appreciated.
Thanks.
P.S. Probably I need to specify RelativeSource in the Button binding... something like this:
Command="{Binding Path=DataContext.Save, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
but which type should be specified for ancestor?
What you are trying to do is to bind directly to the Save method. This is not how to do it.
Assuming that you have set the DataContext of your View to an instance of CustomerViewModel, this is how you bind to the SaveCommand:
<Button Content="Save" Margin="3" Command="{Binding SaveCommand}" />
You do not have to call NotifyPropertyChanged("SaveCommand");.