Implementing textbox lostfocus event in MVVM - c#

I want to accomplish a simple task.
Need to implement textbox lostfocus, As the user puts in data, as soon as one field is filled and he reaches on to the next, it should fire a validation function on the previous field.
Also, I am using MVVM pattern.
So I have this class
public class data : INotifyPropertyChanged
{
public string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public string firstname;
public string FirstName
{
get
{
return firstname;
}
set
{
firstname = value;
OnPropertyChanged("FirstName");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
// Raise the PropertyChanged event
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In the Viewmodel I got this
data1 = new data() { name = "Eddie Vedder", firstname = "Eddie" }; //this line in initialization
public data _data1;
public data data1
{
get { return _data1; }
set
{
_data1 = value;
ValidateThis();
NotifyPropertyChanged(new PropertyChangedEventArgs("data1"));
}
}
In Xaml:
<StackPanel Orientation="Horizontal" >
<Label Width="90" Content="Name" Height="28" HorizontalAlignment="Left" Name="lblName" VerticalAlignment="Top" />
<TextBox Text="{Binding Path=data1.name, UpdateSourceTrigger=LostFocus, Mode=TwoWay}" MaxLength="40" TabIndex="2" Height="25" Margin="0,3,0,0" HorizontalAlignment="Left" Name="txtName" VerticalAlignment="Top" Width="200" />
</StackPanel>
<StackPanel Orientation="Horizontal" >
<Label Width="90" Content="First Name" Height="28" HorizontalAlignment="Left" Name="lblFirstName" VerticalAlignment="Top" />
<TextBox Text="{Binding Path=data1.firstname, UpdateSourceTrigger=LostFocus, Mode=TwoWay}" MaxLength="40" TabIndex="3" Name="txtFirstName" Height="25" Margin="0,3,0,0" VerticalAlignment="Top" Width="200" >
</TextBox>
</StackPanel>
My binding is working as it shoes the default name Eddie Vedder when I execute it.
When I debug it, it doesn't enter the class data.

As you use MVVM pattern I assume that you have some binding to view model property and it looks like:
Xaml:
<StackPanel>
<!--Pay attention on UpdateSourceTrigger-->
<TextBox Text="{Binding Text, UpdateSourceTrigger=LostFocus}" />
<TextBox />
</StackPanel>
c#:
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
Validate(); // Desired validation
OnPropertyChanged();
}
}
If you set UpdateSourceTrigger to LostFocus, property changed will be fired when you lost focus.

There is a very nice article for this: MVVM WPF commands
First create a class: the DelegateCommand.cs
public class DelegateCommand<T> : System.Windows.Input.ICommand where T : class
{
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public DelegateCommand(Action<T> execute)
: this(execute, null)
{
}
public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (_canExecute == null)
return true;
return _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
Add the delegate into your ViewModel:
private readonly DelegateCommand<string> _lostFocusCommand;
public DelegateCommand<string> LostFocusCommand
{
get { return _lostFocusCommand; }
}
private string _input;
public string Input
{
get { return _input; }
set
{
_input = value;
}
}
And initialize it in the constructor of the ViewModel:
// _input will be the property you have with a binding to the textbox control in the view.
// in the canExecute part add the conditions you want to use to check if the lostfocus command will be raised
_lostFocusCommand = new DelegateCommand<string>(
(s) => { /* perform some action */
MessageBox.Show("The lostfocuscommand works!");
}, //Execute
(s) => { return !string.IsNullOrEmpty(_input); } //CanExecute
);
View:
you need to add the following namespace
xmlns:b="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
And the control
<TextBox Grid.Column="0"
Text="{Binding Input, UpdateSourceTrigger=PropertyChanged}">
<b:Interaction.Triggers>
<b:EventTrigger EventName="LostFocus">
<b:InvokeCommandAction Command="{Binding LostFocusCommand}" CommandParameter="{Binding Input}"/>
</b:EventTrigger>
</b:Interaction.Triggers>
</TextBox>

Well this kinda did the trick
public class Validate
{
public static ErrorProperties ep = new ErrorProperties();
public static bool ValidateThis(string PropertyName, string PropertyValue)
{
if (PropertyValue.Length > 10)
{
ep.ErrorPropertyName = PropertyName;
return true;
}
return false;
}
}
public class ErrorProperties
{
public string ErrorPropertyName { get; set; }
public string Error { get; set; }
}
public class data : INotifyPropertyChanged
{
private ObservableCollection<ErrorProperties> _ErrorList = new ObservableCollection<ErrorProperties>();
public ObservableCollection<ErrorProperties> ErrorList
{
get
{
return _ErrorList;
}
set
{
if (_ErrorList != value)
{
_ErrorList = value;
OnPropertyChanged("ErrorList");
}
}
}
private string _Name;
public string Name
{
get
{
return _Name;
}
set
{
if (_Name != value)
{
_Name = value;
if (Validate.ValidateThis("Name", value))
ErrorList.Add(Validate.ep);
OnPropertyChanged("Name");
}
}
}
private string _FirstName;
public string FirstName
{
get
{
return _FirstName;
}
set
{
if (_FirstName != value)
{
_FirstName = value;
if (Validate.ValidateThis("FirstName", value))
ErrorList.Add(Validate.ep);
OnPropertyChanged("FirstName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
// Raise the PropertyChanged event
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Related

Command Binding in WPF cannot read value of property

i have the following code:
XAML Snippet:
<TextBox HorizontalAlignment="Left" Height="23" Margin="10,57,0,0" TextWrapping="Wrap" Text="{Binding Name}" VerticalAlignment="Top" Width="140">
<TextBox.DataContext>
<ViewModels:FilterViewModel/>
</TextBox.DataContext>
</TextBox>
<Button Content="Filtern" HorizontalAlignment="Left" Margin="420,57,0,0" VerticalAlignment="Top" Width="75" Command="{Binding FilterButton}" CommandParameter="{Binding Filter}">
<Button.DataContext>
<ViewModels:FilterViewModel/>
</Button.DataContext>
</Button>
FilterViewModel.cs:
class Button : ICommand
{
public delegate void ICommandOnExecute(object parameter);
public delegate bool ICommandOnCanExecute(object parameter);
private ICommandOnExecute _execute;
private ICommandOnCanExecute _canExecute;
public Button(ICommandOnExecute onExecuteMethod, ICommandOnCanExecute onCanExecuteMethod)
{
_execute = onExecuteMethod;
_canExecute = onCanExecuteMethod;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute.Invoke(parameter);
}
public void Execute(object parameter)
{
_execute.Invoke(parameter);
}
}
class FilterViewModel
{
public ICommand FilterButton { get; set; }
public FilterViewModel()
{
this.FilterButton = new Button(FilterExecute, canExecute);
}
public bool canExecute(object parameter)
{
return true;
}
public void FilterExecute(object paramter)
{
Console.WriteLine("Test: " + name);
}
private String name;
public String Name
{
get
{
return name;
}
set
{
Console.WriteLine(value);
name = value;
}
}
}
So when i click the button i want the content of the textbox printed to the console. e.g. input = "123" -> output should be "Test: 123".
However it doesn't matter what i am writing into the textbox, the result is always only the output: "Test: ". The value of the property "name" is ignored completely.
Thanks for your help!
You seem to be working with two instances of the view model. Move the definition to the MainWindow (or similar top-level parent) containing the TextBox and the Button. The DataContext value is then inherited by all child elements.
<Window.DataContext>
<ViewModels:FilterViewModel />
</Window.DataContext>
Then you can remove the definitions for the TextBox and the Button.

Strange behavior of MVVM binding to property

I am beginner in MVVM. I am writing simple app called Members. This is my member class (model):
class Member: INotifyPropertyChanged
{
public Member(string name)
{
Name = name;
_infoCommand = new InfoCommand(this);
}
string _name;
public string Name
{
get
{
return _name;
}
set
{
_name= value;
notify("Name");
notify("CanShowInfo");
}
}
public override string ToString()
{
return Name;
}
public event PropertyChangedEventHandler PropertyChanged;
void notify(string property_name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property_name));
}
}
private ICommand _infoCommand;
public ICommand InfoCommand
{
get
{
return _infoCommand;
}
set
{
_infoCommand = value;
}
}
public bool CanShowInfo
{
get
{
return _infoCommand.CanExecute(null);
}
}
}
This is my InfoCommand class:
class InfoCommand : ICommand
{
Member _member;
public InfoCommand(Member member)
{
_member = member;
}
public bool CanExecute(object parameter)
{
if (_member.Jmeno.Length > 0)
return true;
else
return false;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show("I am " + _member.Name);
}
}
This is my MemberViewModel class:
class MembersViewModel : INotifyPropertyChanged
{
ObservableCollection<Member> _members = new ObservableCollection<Member>();
public MembersViewModel()
{
Members.Add(new Member("Member1"));
Members.Add(new Member("Member2"));
Members.Add(new Member("Member3"));
Members.Add(new Member("Member4"));
Members.Add(new Member("Member5"));
}
public event PropertyChangedEventHandler PropertyChanged;
protected void notify(string property_name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property_name));
}
Member _selectedMember;
public Member SelectedMember
{
get
{
return _selectedMember;
}
set
{
_selectedMember= value;
notify("SelectedMember");
}
}
public ObservableCollection<Member> Members
{
get
{
return _members;
}
set
{
_members = value;
}
}
AddCommand _addCommand;
public AddCommand AddCommand
{
get
{
if (_addCommand == null)
_addCommand = new AddCommand(this);
return _addCommand;
}
}
}
This is my AddCommand:
class AddCommand : ICommand
{
MembersViewModel _vm;
public AddCommand(MembersViewModel vm)
{
_vm = vm;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_vm.Members.Add(new Member("New Member")); //<-------------------------
}
}
And finally my View:
<Window x:Class="mvvm_gabriel.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModels="clr-namespace:mvvm_gabriel.ViewModel"
Title="MainWindow" Height="482" Width="525">
<Window.Resources>
</Window.Resources>
<Window.DataContext>
<ViewModels:MembersViewModel />
</Window.DataContext>
<Grid>
<ListView ItemsSource="{Binding Members}"
SelectedItem="{Binding SelectedMember, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" />
<Button Grid.Column="1" Content="Info" Width="50" HorizontalAlignment="Left" Command="{Binding InfoCommand}" IsEnabled="{Binding Path=CanShowInfo, Mode=OneWay}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBox Text="{Binding SelectedMember.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Add" Command="{Binding AddCommand}" />
</Grid>
When I click some member in my ListView, his name is shown in TextBox. Now I can edit this name and property of my Member object is updated automatically. When I delete name of some member completely (string.Length == 0), Info button in my member template is disabled.
I can also add new members by clicking Add button. Member is added to my observable collection and automatically shown in ListView.
Everything works perfectly as far as here.
But now: look at line marked like this <---------------------- in my AddCommand.Execute method. When I add new member to my collection, I automatically give him name "New Member" and everything works fine. I can then adit my member's name and my button is disabled automatically as described above. But when I give empty string as the name for new member in constructor on marked line, enabling of my Info button quits working. I can give my new member any name and my Info button is still disabled.
Can anyone explain it and suggest some solution, please?
Your button in the mainwindow is binding the IsEnabled of the button to a property in the model, but the command binding will also cause the button to interrogate the CanExecute() of the command.
<Button Grid.Column="1" Content="Info" Width="50" HorizontalAlignment="Left" Command="{Binding InfoCommand}" IsEnabled="{Binding Path=CanShowInfo, Mode=OneWay}" />
This can lead to confusing behavior, as seen in your case.
You can basically remove the IsEnabled binding of the button, and add the property changed handler to the InfoCommand.
public class InfoCommand : ICommand
{
Member _member;
public InfoCommand(Member member)
{
_member = member;
_member.PropertyChanged += _member_PropertyChanged;
}
private void _member_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Name")
RaiseCanExecuteChanged();
}
private void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
public bool CanExecute(object parameter)
{
if (_member.Name.Length > 0)
return true;
else
return false;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show("I am " + _member.Name);
}
}

Binding a string variable to a label

I am trying to move from WinForms to WPF, and am stuck on binding.
I have a label:
<Label Name="labelState" Content="{Binding state}" HorizontalAlignment="Right" Margin="10,10,10,10" FontSize="12" />
In the cs of the same userControl (named FormInput), I have :
public string state { get; set; }
public FormInput()
{
state = "ok";
InitializeComponent();
}
Why doesn't this work?
Thank you.
When you are binding something in WPF you need to use INotifyPropertyChanged
Implement a class follows,
class TestObject : INotifyPropertyChanged
{
private string _state;
public string State
{
get
{
return _state;
}
set
{
if (_state == value) return;
_state = value;
OnPropertyChanged("State");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
and in your FormInput
public FormInput()
{
InitializeComponent();
TestObject t = new TestObject();
labelState.DataContext = t;
t.State = "ok";
}
and XAML as follows,
<Label Name="labelState" Content="{Binding State}" HorizontalAlignment="Right" >

wpf - One ViewModel that interacts with multiple instances of a Model

I have a WorkspaceViewModel that handles addition and deletion of tab items dynamically through an ObservableCollection. Each time a tab is connected to a PayslipModel, all bindings work fine but one problem I am having is that;
I have a save button in the UserControl who's DataContext is set to WorkspaceViewModel and I would like to save whatever info is being displayed in the selected tab. Now, each time a tab is added, a new instance of PayslipModel is created, which is exactly what I want because I don't want bindings to be shared for all tabs. However, I am unable to save what is being displayed since PayslipModel has multiple instances, therefore nothing is returned (temporarily using MessageBox to test if info is being retrieved) when I hit save.
I created a diagram to better explain my situation:
Is it possible to access the current instance when a tab is selected or cycle through all instances and do something like batch saving?
This is a working example which shows one of the possiblities:
View
<TabControl DataContext="{Binding}" ItemsSource="{Binding Models}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" >
</TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<DockPanel>
<Button DockPanel.Dock="Top" Content="Click Me" Command="{Binding DataContext.PCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabControl}}"
CommandParameter="{Binding Desc}"/>
<TextBlock Text="{Binding Desc}" >
</TextBlock>
</DockPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Model View
public class ModelView
{
public ModelView()
{
_models = new ObservableCollection<Model>();
_pCommand = new Command(DoParameterisedCommand);
}
ObservableCollection<Model> _models;
public ObservableCollection<Model> Models { get { return _models; } }
private void DoParameterisedCommand(object parameter)
{
MessageBox.Show("Parameterised Command; Parameter is '" +
parameter.ToString() + "'.");
}
Command _pCommand;
public Command PCommand
{
get { return _pCommand; }
}
}
Model
public class Model : INotifyPropertyChanged
{
string _desc;
public string Desc { get { return _desc; } set { _desc = value; RaisePropertyChanged("Desc"); } }
string _name;
public string Name { get { return _name; } set { _name = value; RaisePropertyChanged("Name"); } }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
Command
public class Command : ICommand
{
public Command(Action<object> parameterizedAction, bool canExecute = true)
{
_parameterizedAction = parameterizedAction;
_canExecute = canExecute;
}
Action<object> _parameterizedAction = null;
bool _canExecute = false;
public bool CanExecute
{
get { return _canExecute; }
set
{
if (_canExecute != value)
{
_canExecute = value;
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}
public event EventHandler CanExecuteChanged;
bool ICommand.CanExecute(object parameter)
{
return _canExecute;
}
void ICommand.Execute(object parameter)
{
this.DoExecute(parameter);
}
public virtual void DoExecute(object param)
{ if (_parameterizedAction != null)
_parameterizedAction(param);
else
throw new Exception();
}
}
Use this to initialize:
public MainWindow()
{
InitializeComponent();
ModelView mv = new ModelView();
mv.Models.Add(new Model() { Name = "a", Desc = "aaa" });
mv.Models.Add(new Model() { Name = "b" , Desc = "bbb"});
mv.Models.Add(new Model() { Name = "c", Desc = "cccc" });
this.DataContext = mv;
}

I have bind numbers to CombBox. I want change value comboBox and textBox changed value

I want change number from ComboBox and change value in TextBox.
(For example I have number =2 and content at "lblblblb" ofc. this is in ObservableCollection<string>, so I to call ContentWithListView[SelectNumberStep])
ReadPage.xaml
<TextBox HorizontalAlignment="Left" Margin="580,154,0,0"
TextWrapping="Wrap" Text="{Binding ContentWithListView[SelectNumberStep],Mode=TwoWay}"
VerticalAlignment="Top" Width="725" Height="82"/>
<ComboBox HorizontalAlignment="Left" Margin="440,154,0,0"
ItemsSource="{Binding NumberStep,Mode=TwoWay}"
SelectedItem="{Binding SelectNumberStep,Mode=TwoWay}"
VerticalAlignment="Top" Width="95" Height="77" />
How I change content in TextBox from CombBox numbers?
ReadViewModel.cs
private ObservableCollection<string> contentWithListView;
public ObservableCollection<string> ContentWithListView
{
get
{
return this.contentWithListView;
}
set
{
this.contentWithListView = value;
}
}
private ObservableCollection<int> stepNumber;
public ObservableCollection<int> NumberStep
{
get
{
return this.stepNumber;
}
set
{
this.stepNumber = value;
}
}
private int selectNumberStep;
public int SelectNumberStep
{
get
{
return this.selectNumberStep;
}
set
{
this.selectNumberStep = value;
}
}
previous answer doesn't integrate the fact he textbox content have to be in TwoWays so, in such scenario, you could consolidate your properties with the INotifyPropertyChanged interface like that:
Xaml part
<StackPanel d:DataContext="{d:DesignInstance Type=classes:StackOverFlowX }">
<TextBox Text="{Binding Content, Mode=TwoWay}"/>
<ComboBox ItemsSource="{Binding NumberStep, Mode=TwoWay}"
SelectedItem="{Binding SelectNumberStep,Mode=TwoWay}"/>
</StackPanel>
Class
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class StackOverFlowX : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public StackOverFlowX()
{
}
private ObservableCollection<string> contentWithListView;
public ObservableCollection<string> ContentWithListView
{
get
{
return this.contentWithListView;
}
set
{
this.contentWithListView = value;
OnPropertyChanged();
}
}
private ObservableCollection<int> stepNumber;
public ObservableCollection<int> NumberStep
{
get
{
return this.stepNumber;
}
set
{
this.stepNumber = value;
OnPropertyChanged();
}
}
private int selectNumberStep;
public int SelectNumberStep
{
get
{
return this.selectNumberStep;
}
set
{
this.selectNumberStep = value;
OnPropertyChanged();
OnPropertyChanged("Content");
}
}
private string _content;
public string Content
{
get
{
return contentWithListView[this.SelectNumberStep];
}
set
{
this._content = value;
if (contentWithListView.IndexOf(value) > -1)
{
SelectNumberStep = contentWithListView.IndexOf(value);
OnPropertyChanged("SelectNumberStep");
}
OnPropertyChanged();
}
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I would change your view model code around, so that the text box binds to a scalar string value which is updated on the SelectNumberStep change:
public string Content
{
get
{
// bounds checking here..
return contentWithListView[this.SelectNumberStep];
}
}
public int SelectNumberStep
{
get
{
return this.selectNumberStep;
}
set
{
this.selectNumberStep = value;
this.NotifyOfPropertyChange(() => this.SelectNumberStep);
this.NotifyOfPropertyChange(() => this.Content);
}
}
<TextBox Text="{Binding Content}" ... />

Categories