It is a problem that I face for a long time.
Let say that we have a POCO class called Person (INotifyPropertyChanged is provided using Foldy and its [AddINotifyPropertyChangedInterface] attribute)
[AddINotifyPropertyChangedInterface]
public class Person
{
public int Id{ get; set; }
[StringLength(20)]
[Required(ErrorMessage = "Field required")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Field required")]
public string LastName{ get; set; }
}
And in ViewModel I reference this class as a Property
public class SomeViewModel
{
public Person Person
{
get => person;
set
{
person= value;
SomeMethod();
}
}
// Rest of the code
}
And question is how can I invoke "SomeMethod" when first name is changed in textbox.
Textbox is bound to property as follows:
<TextBox Text="{Binding Person.FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged)/>
I tried to work around it binding to:
public string FirstName
{
get => firstName;
set
{
firstName= value;
Person.FirstName=value;
SomeMethod();
}
}
But the problem is with validation in the user form because I use data annotation attributes in POCO class for validation.
Thank you in advance for your help and your time!
The way I see it, there are 2 options for you to achieve what you want:
Create a view model for Person and move you method to the setter for FirstName. Bind the Textbox to the FirstName property of PersonViewModel and SomeMethod() will be invoked whenver you change texts in TextBox.
public class PersonViewModel
{
private string _firstName;
public string FirstName
{
get => _firstName;
set
{
_firstName = value;
SomeMethod();
}
}
In some cases you must implement SomeMethod() inside the owner ViewModel, then just use the interactivity to bind to TextChanged event of the Textbox and assign you method as invoke method.
xmlns:i = "http://schemas.microsoft.com/expression/2010/interactivity"
<TextBox Name="your_textBox" Text={Binding ...}>
<i:Interactivity.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Commmand="{Binding YourMethodInViewModel, ElementName=your_textbox}", CommandParameter="{Binding ElementName=your_textbox}"/>
</i:EventTrigger>
</i:Interactivity.Triggers>
</TextBox>
public ICommand YourMethodInViewModel{get;set;}
You would have to listen to property changes of Person:
public class SomeViewModel
{
public ViewModel()
{
this.Person = new Person();
}
public Person Person
{
get => this.person;
set
{
if (this.Person != null)
{
this.Person.PropertyChanged -= OnPersonFirstNameChanged;
}
this.person= value;
if (this.Person != null)
{
this.Person.PropertyChanged += OnPersonFirstNameChanged;
}
}
}
public void OnPersonFirstNameChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Person.FirstName))
{
SomeMethod();
}
}
}
Alternatively implement a Person.FirstNameChanged event that Person can raise. This improves readability. PropertyChanged is intended primarily for the data binding engine. Since you have to switch on PropertyChangedEventArgs.PropertyChanged in order to find out what happened, you can't handle this event gracefully.
Thanks to the #Thế Long who pointed me to the right direction, for those who faced the same problem - I used "SourceUpdated" event in order to be able to set delay in binding
msdn SourceUpdated
xmlns:i = "http://schemas.microsoft.com/expression/2010/interactivity"
<TextBox Text={Binding = " ..., Delay=500"}>
<i:Interactivity.Triggers>
<i:EventTrigger EventName="SourceChanged">
<i:InvokeCommandAction Commmand="{Binding SomeMethodCommand}"/>
</i:EventTrigger>
</i:Interactivity.Triggers>
</TextBox>
Related
MyProduct is the model that has HasError boolean property (with OnPropertyChanged ...) that can change.
MyProductDialogViewModel is:
class ProductDialogViewModel : Notifier
{
public ProductDialogViewModel() { }
public MyProduct Product { get; set; }
public bool HasError
{
get { return Product.HasError; }
}
}
I have assigned MyProductDialogViewModel instance to BaseContentControl.DataContext to inflate a ContentControl.
This View can be inflated with different ViewModels all having HasError property using template binding.
<ContentControl x:Name="BaseContentControl" Content="{Binding}" ... >
Then I try to extract informations directly from its DataContext.
This don't work:
<Label Content="{Binding ElementName=BaseContentControl, Path=DataContext.HasError}"/>
But this works perfectly.
<Label Content="{Binding ElementName=BaseContentControl, Path=DataContext.Product.HasError}"/>
I tought it ca be a notifiy problem in the ViewModel so I have changed to this:
class ProductDialogViewModel : Notifier
{
public ProductDialogViewModel() { }
public MyProduct Product { get; set; }
public bool HasError
{
get { return Product.HasError; }
set
{
if (Product.HasError != value)
{
Product.HasError = value;
OnPropertyChanged("HasError");
}
}
}
}
but to no avail (in fact the set method is never called so it never notifies).
I don't want to directly refer to the specific Model instance cause the View can be inflated with different ViewModels.
How can I do ?
Thanks
You have to propagate the PropertyChanged event of MyProduct, i.e. subscribe to it and invoke OnPropertyChanged(nameof(HasError)) if HasError property of MyProduct being changed:
public class ProductDialogViewModel : Notifier
{
public ProductDialogViewModel() { }
private MyProduct _product = null;
public MyProduct Product
{
get { return _product; }
set
{
if (_product!=null)
{
_product.PropertyChanged -= Product_PropertyChanged;
}
_product = value;
if (_product != null)
{
_product.PropertyChanged += Product_PropertyChanged;
}
}
}
private void Product_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName==nameof(MyProduct.HasError))
{
OnPropertyChanged(nameof(HasError));
}
}
public bool HasError => Product.HasError;
}
I have my Model class which implements INotifyPropertyChanged.
My View has 5 TextBox, 2 Button and a ListView to display grid.
In my ViewModel I was previously adding default values to the ObservableCollection of my Model class and displaying it to a ListView.
The button implementation is done using ICommand and RelayCommand.
Now I want to add data to the ObservableCollection from the user from the UI TextBox. How can I achieve that? The UI TextBox hasbindings with properties of the Model class.
My View
<ListView Name="UserGrid" Grid.Row="1" Margin="4,178,12,13" ItemsSource="{Binding UserDatas}" >
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,7,0,0" Name="txtUserId" VerticalAlignment="Top" Width="178" Text="{Binding UserId}" />
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,35,0,0" Name="txtFirstName" VerticalAlignment="Top" Width="178" Text="{Binding FirstName}" />
Like these there are 5 TextBoxes
The Model class:
public class User : INotifyPropertyChanged
{
private int userId;
private string firstName;
public int UserId
{
get
{
return userId;
}
set
{
userId = value;
RaisePropertyChanged("UserId");
}
}
}
The ViewModel:
public class UsersViewModel:INotifyPropertyChanged
{
private ObservableCollection<User> userDatas;
public ObservableCollection<User> UserDatas
{
get
{
if (userDatas == null)
{
userDatas = new ObservableCollection<User>();
}
return userDatas;
}
set
{
userDatas = value;
RaisePropertyChanged("UserDatas");
}
}
}
private CommandBase _LoadCommand;
public ICommand LoadCommand
{
get
{
if (this._LoadCommand == null)
this._LoadCommand = new CommandBase(LoadData);
return this._LoadCommand;
}
}
private void LoadData(object obj)
{
//What need to be done here to access the textboxes of UI which are binded to User.cs class.
User newUser = new User();
UserDatas.Add(newUser);
}
Now what I need to write in the LoadData method to take inputfrom textboxes from UI and store it in my ObservableCollection
There are several things you can do. Most obvious, is to have a "Add New" Command, which creates a new empty object and stores it in a CurrentUser or SelectedUser property.
This property is bound to the Template's (or Form's) context. You'd have 3 commands (Add New User, Save User, Cancel to cancel addition of a new user creation).
For example
public class UsersViewModel : INotifyPropertyChanged
{
public UsersViewModel()
{
UserDatas = new ObservableCollection<User>();
AddNewUserCommand = new RelayCommand(AddNewUser, param => !this.IsNewUser);
SaveUserCommand = new RelayCommand(SaveUser);
CancelNewUserCommand = new RelayCommand(CancelNewUser, param => this.IsNewUser);
}
private ObservableCollection<User> userDatas;
public ObservableCollection<User> UserDatas
{
get { return userDatas; }
set
{
userDatas = value;
RaisePropertyChanged("UserDatas");
}
}
private User selectedUser;
public User SelectedUser
{
get { return selectedUser; }
set
{
selectedUser = value;
RaisePropertyChanged("SelectedUser");
RaisePropertyChanged("IsNewUser");
}
}
public bool IsNewUser
{
get
{
if(SelectedUser==null)
return false;
return SelectedUser.UserId == 0;
}
}
public ICommand AddNewUserCommand { get; private set; }
public ICommand CancelNewUserCommand { get; private set; }
public ICommand SaveUserCommand { get; private set; }
private void AddNewUser()
{
SelectedUser = new User();
}
private void SaveUser()
{
// Just in case of concurency
var newUser = SelectedUser;
if(newUser == null)
{
return;
}
var isNewUser = newUser.UserId == 0;
// Persist it to the database
this.userRepository.Add(newUser);
this.userRepository.SaveChanges();
// If all worked well, add it to the observable collection
if(isNewUser)
{
// Only add if new, otherwise it should be already in the collection
UserDatas.Add(newUser)
}
}
}
But again, it's very discouraged to work directly on the model and bind it to the View. You should also create a ViewModel for your User and put validation (implement the IDataErrorInfo interface on the UserViewModel) in there and handling of state, for example tracking if the UserViewModel is dirty (i.e. data was changed).
All these are presentation concerns and not business logic, so they belong to a ViewModel and not to the Model itself.
Currently, I'm developing a simple task manager application to exercise my Windows 10 development skills, using the Universal Windows Application (UWP) template and the MVVM design pattern.
In my ViewModel (which implements INotifyPropertyChanged), I've got an ObservableCollection<BasicTask> which is databound to a ListView in XAML, where BasicTask is a simple class I developed.
My commands are instantiated within the ViewModel as properties, and are databound to the 'Command' property of (in this case, ) an AppBarButton UI element. The command's function is to call a method in the ViewModel, which is defined within the constructor of the command like so;
class DeleteTaskCommand : ICommand
{
public DeleteTaskCommand(MainViewModel viewModel)
{
ViewModel = viewModel;
}
public MainViewModel ViewModel { get; private set; }
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => (ViewModel.SelectedTask != BasicTask.Default) ? true : false;
public void Execute(object parameter) => ViewModel.DeleteTask();
}
The DeleteTask() method in the ViewModel affects the ObservableCollection<BasicTask>, which, as I stated before, is databound to a ListView and therefore (to my knowledge) should push the changes immediately to the ListView. And this is exactly what is doesn't do :(
I can confirm that the command calls the method in the ViewModel properly as I replaced the body of the DeleteTask() method with await new MessageDialog("").ShowAsync();, which worked.
I've tried this with many projects with absolutely no success so far. One apparent solution is here, however, Visual Studio notifies that the CommandManager class doesn't exist :/
UPDATE
The XAML of the ListView mentioned above is the following;
<ListView ItemsSource="{Binding TaskLists}"
SelectedIndex="{Binding SelectedTaskListIndex, Mode=TwoWay}"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>[...]</Grid>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
TaskLists is an ObservableCollection<TaskList> TaskLists, and the TaskList class is as follows;
class TaskList
{
public string Title { get; set; }
public string Description { get; set; }
public List<BasicTask> Items { get; set; }
public static TaskList Generate(string title, string description, List<BasicTask> items)
=> new TaskList() { Title = title, Description = description, Items = items };
}
A second ListView binds to the Items property of the selected TaskList within the TaskLists ObservableCollection, and it's the second ListView that does not get updated.
public int SelectedTaskListIndex
{
get { return selectedTaskListIndex; }
set
{
selectedTaskListIndex = value;
SelectedTaskList = (value != -1) ? TaskLists[value] : TaskList.Default;
RaisePropertyChanged(nameof(SelectedTaskListIndex));
}
}
public TaskList SelectedTaskList
{
get { return selectedTaskList; }
set
{
selectedTaskList = value;
RaisePropertyChanged(nameof(SelectedTaskList));
}
}
<ListView ItemsSource="{Binding SelectedTaskList.Items}"
Any and all help with this issue will be appreciated :)
In my ViewModel (which implements INotifyPropertyChanged), I've got an
ObservableCollection which is databound to a ListView in
XAML
class TaskList {
public string Title { get; set; }
public string Description { get; set; }
public List Items { get; set; } ... }
Actually, I noticed that you created a List property in TaskList, if you replace it with ObservableCollection and also notify the UI in the right way, the UI updating should works, here is my DeleteTask() method:
private void DeleteTask()
{
SelectedTaskList.Items.Remove(SelectedTask);
}
Both SelectedTaskList and SelectedTask has notified the UI timely.
By the way, to update/raise CanExecuteChanged, I used this solution provided by Jerry Nixon - MSFT in this case
public TaskList SelectedTaskList
{
get { return selectedTaskList; }
set
{
selectedTaskList = value;
RaisePropertyChanged(nameof(SelectedTaskList));
DeleteTaskCommand.RaiseCanExecuteChanged();
}
}
public BasicTask SelectedTask
{
get { return selectedTask; }
set
{
selectedTask = value;
RaisePropertyChanged(nameof(SelectedTask));
DeleteTaskCommand.RaiseCanExecuteChanged();
}
}
Please check my feasible sample on Github
I am new in WPF I have implemented INotifyPropertyChanged interface. I have one viewmodel containing the property "TeamMemberList". The control executes the setter part, changes the property value but the PropertyChanged event remains null.
Here is code:
ViewModelBase:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ViewModel:(Which inherits the viewmodelbase)
Property is
public List<Employee> TeamMemberList
{
get
{
return _teamMemberList;
}
set
{
_teamMemberList = value;
NotifyPropertyChanged("TeamMemberList");
}
}
Binding
<ListBox Margin="10" ItemsSource="{Binding TeamMemberList, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" >
when new employee added to the DB, model reads it & creates List for all emplyee then the TeamMeberList property gets updated. This is updation method for TeamMemberList
var qryEmp = from employee in ClientModel.EmployeeList
where employee.ReportingManager == UserProfile.EmployeeId
select new Employee
{
EmployeeId = employee.EmployeeId,
EmployeeName = employee.EmployeeName,
Designation = employee.Designation,
ProfilePic = employee.ProfilePic,
};
TeamMemberList = qryEmp.ToList();
And implementation of Employee
public class Employee : ViewModelBase
{
private string _employeeName;
private string _employeeId;
private string _profilePic;
private string _designation;
private string _reportinManager;
public string EmployeeName
{
get
{
return _employeeName;
}
set
{
_employeeName = value;
NotifyPropertyChanged("EmployeeName");
}
}
public string EmployeeId
{
get
{
return _employeeId;
}
set
{
_employeeId = value;
NotifyPropertyChanged("EmployeeId");
}
}
public string ProfilePic
{
get
{
return _profilePic;
}
set
{
_profilePic = value;
NotifyPropertyChanged("ProfilePic");
}
}
public string Designation
{
get
{
return _designation;
}
set
{
_designation = value;
NotifyPropertyChanged("Designation");
}
}
public string ReportingManager
{
get
{
return _reportinManager;
}
set
{
_reportinManager = value;
NotifyPropertyChanged("ReportingManager");
}
}
}
It's hard to say what the problem is when we don't see more code (eg: how you are setting the DataContext etc...).
But there is an easy way to debug your bindings by adding the following attribute to it:
<ListBox Margin="10" ItemsSource="{Binding TeamMemberList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PresentationTraceSources.TraceLevel=High>
Adding this attribute will output the whole binding sequence to the Output window of Visual Studio. That should point out what is going wrong.
If you want to enable this for all bindings, you can also use the Visual Studio options:
Use ObserveableCollection instead of a list
So I'm building a program that pulls Table and Field names out of a Microsoft Access database and puts them in to two different Listbox items. As a test for my binding I had both boxes setup identically (Just a simple copy/paste because I was getting ahead of myself) and I got the Table names to bind successfully but only using lbTables.DataContext = this;(I tried to use lbTables.SetBinding (ListBox.DataContextProperty, new Binding ("MDBtoCSV.MainWindow")); but it doesn't work for some reason). The DataContext doesn't seem to inherit from the window above it which from what I've read is what what it's supposed to do.
When I began trying to pin down the DataContext on lbFields in XAML I found an odd problem. if I define and initialize the collection at the same time, globally(as below), private ObservableCollection<CheckedListItem<Table>> _listTables = new ObservableCollection<CheckedListItem<Label>> (); both ListBox behave how I would expect them too.
But if I instead initialize the collection in my code and use lbTables.DataContext = this;, then only lbTables populates while lbFields remains blank. Is there a preferred method or less fragile method to explicitly defining the DataContext and ItemSource?
Below is my XAML:
<ListBox x:Name="lbFields" HorizontalAlignment="Left" Height="70" Margin="10,113,0,0" VerticalAlignment="Top" Width="240"
DataContext="{Binding ElementName=appMainWindow, Mode=OneWay}" ItemsSource="{Binding Path=ListTables, Mode=OneWay}">
</ListBox>
</Grid>
TL;DR: Why would DataContext not inherit from Window? Why doesn't ListBox.DataContext = this act the same as the XAML version?
Edit:
private ObservableCollection<CheckedListItem<Table>> _listTables;// = new ObservableCollection<CheckedListItem<Table>> ();
public ObservableCollection<CheckedListItem<Table>> ListTables
{
get { return _listTables; }
private set { _listTables = value;}
}
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
private string name;
public string Name { get; set; }
public CheckedListItem () { }
public CheckedListItem (T item, bool isChecked = false)
{ this.item = item;
this.isChecked = isChecked;}
public T Item
{ get { return item; }
set
{ item = value;
NotifyPropertyChanged("Item");}
}
public bool IsChecked
{ get { return isChecked; }
set
{ isChecked = value;
NotifyPropertyChanged ("IsChecked");}
}
public class Table
{ public string name { get; set; }
private ObservableCollection<string> _listFields = new ObservableCollection<string> (
new string[]{"null","null","null","null"});
public ObservableCollection<string> ListFields { get { return _listFields; } set { _listFields = value; } }
}
I highly recommend you take a look at the MVVM pattern example and use that for your WPF and bindings. It makes all of this a lot easier.
I would suggest three ViewModel classes: MainWindowViewModel, DatabaseTableViewModel and DatabaseTableColumnViewModel. As you might imagine, they are nested in each respective one with some properties of their own like so:
class MainWindowViewModel : ViewModelBase
{
// Make sure you implement INotifyPropertyChanged and invoke on each property!
public string Title { get; set; }
public ObservableCollection<DatabaseTableViewModel> Tables { get; set; }
public DatabaseTableViewModel SelectedTable { get; set; }
}
class DatabaseTableViewModel : ViewModelBase
{
public string TableName { get; set; }
public ObservableCollection<DatabaseTableColumnViewModel> Columns { get; set; }
}
class DatabaseTableColumnTableViewModel : ViewModelBase
{
public string ColumnName { get; set; }
public string ColumnType { get; set; }
public DatabaseTableColumnViewModel SelectedColumn { get; set; }
}
So your XAML bindings would look something like this:
<ListBox ItemsSource="{Binding Path=Tables}"
SelectedItem="{Binding Path=SelectedTable}">
</ListBox>
<ListBox ItemsSource="{Binding Path=SelectedTable.Columns}"
SelectedItem="{Binding Path=SelectedTable.SelectedColumn}">
</ListBox>
You will have to work a little bit with this to get multiple selection working but that's the general idea with bindings.
You should be setting your MainWindow.DataContext to an instance of MainWindowViewModel.
Everything I'm doing turned out to work just fine, what I was missing was the INotifyPropertyChanged in the MainWindow. I couldn't figure it out before, but then I found this which is something that I've been looking for for three months.