I have a Model called FieldModel. In ViewModel I am setting its properties through a json file parsing like this:
foreach (var field in innerClass.Item2.Properties)
{
FieldView fieldView = new FieldView(field);
fieldView.ClassName = classView.ClassName;
fieldView.IsAbstract = classView.IsAbstract;
FieldViewItems.Add(fieldView);
}
My question is: how to make the binding properly with the reload button in order to reload the content of json file when it's being modified ?
First implement a Command class, I prefer something like this :
public class GeneralCommand : ICommand
{
private Action ToBeExecutedAction;
private Func<bool> ExecutionValidatorFunc;
public GeneralCommand(Action toBeExecutedAction, Func<bool> executionValidatorFunc)
{
ToBeExecutedAction = toBeExecutedAction;
ExecutionValidatorFunc = executionValidatorFunc;
}
public bool CanExecute(object parameter)
{
return ExecutionValidatorFunc();
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
ToBeExecutedAction();
}
}
Now inside your ViewModel class, implement something like the following :
public class FieldModel : INotifyPropertyChanged
{
private GeneralCommand _generalCommand;
public FieldModel()
{
Action action = new Action(ChangeValue);
_generalCommand = new GeneralCommand(action, new Func<bool>(() => true));
}
public ICommand ReloadValues
{
get
{
return _generalCommand;
}
}
string _jsonText;
public string JsonText
{
get
{
return _jsonText;
}
}
private void ChangeValue()
{
//Change JsonText here
//Then raise event change to be updated
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("TextJson"));//Here fill property name
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then from the Xaml bind your Reload button into command property ReloadValues inside your ViewModel object, and bind the JsonText property with a WPF control for example a Textbox.
Hope this is useful.
Related
I have an ObservableCollection of items in which one of the property is bool.
When i set the itemsSource of the datagrid as the ObservableCollection, it auto-generates the column with checkbox column for the bool property.
I would like to know how we can tick the checkbox in code, lets say if we have the mark all option?
I tried updating the ObservableCollection records property value with true, but it doesnt help updating the UI.
Please help.
[EDIT: Below code works as suggested in the answer]
My Class is as follows
public class InvoiceDoc : INotifyPropertyChanged
{
private bool _Selected;
[DisplayName("Selected")]
public bool Selected
{
get { return _Selected; }
set { _Selected = value; this.OnPropertyChanged(); }
}
[DisplayName("Date")]
public DateTime DocDate { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged !=null)
this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The datagrid is as follows
<DataGrid x:Name="dgInvoices" Margin="32,110,32,59" AutoGeneratingColumn="dgInvoices_AutoGeneratingColumn"/>
setting the ItemsSource is as follows
docs = new ObservableCollection<InvoiceDoc>(); ;
dgInvoices.ItemsSource = docs;
I am expecting the grid to auto check the check box once is set the value in the collection.
Binding to an ObservableCollection is only reactive if an Item is added or removed.
Your elements inside your Collection have to implement INotifyPropertyChanged so the UI recognises the changes
EDIT:
Lets say you have the following objects in your Collection:
public class MyClass {
public string Name { get; set; }
public bool IsActive { get; set; }
}
This class has now to be modified to the following:
public class MyClass : INotifyPropertyChanged{
private string _name;
private bool _isActive;
public string Name
{
get { return this._name; }
set { this._name = value; this.OnPropertyChanged();}
}
public bool IsActive
{
get { return this._isActive; }
set { this._isActive = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
If there are any Errors, remove the CallerMemberNameAttribute and invoke the this.OnPropertyChanged(); with the Propertyname.
Loads the dataGrid and populates the Datagrid a row of 1'
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
update();
//this.DataContext = this;
}
CricketEvent events = new CricketEvent();
private void update()
{
events.updateList(new CricketEvent[1] { new CricketEvent(){Runs="1"} });
DG1.ItemsSource = events.RunsList;
}
private void DG1_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
Window1 windowToOpen = new Window1();
var selectedUser = this.DG1.SelectedItem;
windowToOpen.Show();
}
}
Main class that loads the OnPropertyChanged I have a List property and string property that calls the OnPropertyChanged but I want the individual "Runs" property to be updated on its own rather than the whole collection.
class CricketEvent : INotifyPropertyChanged
{
private ObservableCollection<CricketEvent> runsList;
public string runs { get; set; }
public CricketEvent(string numofRuns) {
this.Runs = numofRuns;
}
public CricketEvent() { }
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<CricketEvent> RunsList
{
get { return this.runsList; }
set
{
if (value != this.runsList)
{
this.runsList = value;
OnPropertyChanged("RunsList");
}
}
}
public string Runs
{
get { return runs; }
set
{
runs = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Runs");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public ObservableCollection<CricketEvent> updateList(CricketEvent []events)
{
runsList = new ObservableCollection<CricketEvent>(events.ToList());
return runsList;
}
}
This is the update window that brings up a text box and should change the "1s" In the previous window to whatever is typed into the textbox
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
CricketEvent events = new CricketEvent();
MainWindow main = new MainWindow();
private void Button_Click(object sender, RoutedEventArgs e)
{
events.updateList(new CricketEvent[1] { new CricketEvent(txt1.Text.ToString()) });
main.DG1.ItemsSource = events.RunsList;
}
The Button_Click event in Window1 does not use the instance of MainWindow that is show - it creates a new Window instance (that is not shown) and adds the updated list to the DG1.ItemsSource property. To solve that, pass the original instance of Window to the created Window1 in constructor and use that.
However, you should review your update strategy (and code style) because there is potential for improvments:
It is not a good idea to create a new collection if you want to update just one property of one item. Observable collections provide change notification, so you dont have to recreate the collection at all.
Instead of assinging the collection in code behind, use databinding to bind the collection to the ItemsSource. DataBinding results in automatic update of GUI elements if the collection or one item of you collection changed.
I am using WPF and MVVM for a project. I have a view with a GridView control. User can Insert/Update/Delete In Grid View. when any of the action happen changes reflect in ViewModel. This part is working Ok. But when I want to save the changes in Database I need to loop through each Item in ItemSource one by one. which takes the extra time to complete. I want to process only those Items which are changes.
To accomplish this , I add a boolean property in my Model to indicate whether the Item is changed or note. But problem is that I can not see any way to set this boolean property whenever any other property is changed.
Can any body help me how to do it?
EDIT
I have a SelectedItem Property , and I am assuming that whenever an Item is selected in GridView , User will update or insert the row. so on SelectedItem property I have set boolean property of SelectedItem to True. and while looping to save records I am saving all those records who have True in their boolean property. I know its not the perfact way, but right now I do not have any other way to do it. Your thoughts?
You could subscribe to the PropertyChanged event on your Model and set the Flag to True.
But keep in mind that you have to set the Flag to false after you loaded the data from the database, because the initialization of the model will also call the propertychanged event.
Example for class with IsDirty-Flag:
public class Sample : INotifyPropertyChanged
{
private int id;
private string name;
private bool isDirty;
public event PropertyChangedEventHandler PropertyChanged;
public int Id
{
get { return id; }
set
{
if(id != value)
{
id = value;
RaisePropertyChanged("Id");
}
}
}
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
RaisePropertyChanged("Name");
}
}
}
public bool IsDirty
{
get { return isDirty; }
set
{
if (isDirty != value)
{
isDirty = value;
RaisePropertyChanged("IsDirty");
}
}
}
protected virtual void RaisePropertyChanged(string propertyName)
{
if (propertyName != "IsDirty")
{
IsDirty = true;
}
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
if you are using an ObservableCollection, you can also add an eventhandler to track the rows that are newly added or deleted
If you use MVVM you should have implementation of INotifyPropertyChanged. And you can add some logic to set up yours boolean property in OnPropertyChanged handler.
If you are willing to take a dependency on a build time tool you can do this with ILWeaving.
So if you combine Fody with the PropertyChanged addin then IsDirty functionality is supported out of the box.
Then Martins example can be simplified to this
public class Sample : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Id { get; set; }
public string Name { get; set; }
public bool IsChanged { get; set; }
}
Note the use if IsChanged instead of IsDirty.
And then this will exist in the compiled assembly
public class Sample : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(string propertyName)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
int id;
public int Id
{
get { return id; }
set
{
if (id != value)
{
id = value;
IsChanged = true;
OnPropertyChanged("Id");
}
}
}
bool isChanged;
public bool IsChanged
{
get { return isChanged; }
set
{
if (isChanged != value)
{
isChanged = value;
OnPropertyChanged("IsChanged");
}
}
}
string name;
public string Name
{
get { return name; }
set
{
if (!string.Equals(name, value, StringComparison.Ordinal))
{
name = value;
IsChanged = true;
OnPropertyChanged("Name");
}
}
}
}
I've been struggling with this problem for a couple of days, but somewhere I obviously on a wrong track. Situation is as follows: I have a window with 3 buttons (Add New Task, Show Inbox, Show Today) and a Listview. My TaskViewModel class is has a ObservableCollection of TaskModel, with pretty simple Filter functionality. My class looks as follows:
public class TaskViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ObservableCollection<TaskModel> TaskCollection { get; private set; }
public TaskViewModel()
{
TaskDataAccess ac = new TaskDataAccess();
this.TaskCollection = ac.GetAllTasks();
}
public ICommand AddTaskCommand
{
get { return new DelegateCommand(this.AddTask); }
}
public ICommand FilterInboxCommand
{
get { return new DelegateCommand(this.FilterInbox); }
}
public void AddTask()
{
this.TaskCollection.Add(new TaskModel(9, "I", "New Item for testing"));
this.GetListCollectionView().Filter = this.IsInbox; ;
}
private void FilterInbox()
{
this.GetListCollectionView().Filter = this.IsInbox;
}
....
}
The filter functionality works fine, but when I call the new window "Add new task" it does not update the listview (here: this.TaskCollection.Add(new TaskModel(9, "I", "New Item for testing"));
I'd appreciate if someone could give me a hint...
Try to do this...
create a private field (say _taskCollection) to backup your property TaskCollection.
private readonly ObservableCollection<TaskModel> _taskCollection;
Then remove the private setter from TaskCollection property. Also remove the constructor code that loads the collection.
Instead write your getter this way...
public ObservableCollection<TaskModel> TaskCollection {
get {
if (this._taskCollection == null)
{
TaskDataAccess ac = new TaskDataAccess();
this._taskCollection = ac.GetAllTasks();
}
return this._taskCollection;
}
}
Let me know if this way works ....
Hi I make validation on error in my model class.
public class CurrentUser:IDataErrorInfo, INotifyPropertyChanged
{
//...
private string _validationResult;
private string _nick;
public string Nick
{
get { return _nick; }
set
{
_nick = value;
NotifyPropertyChanged("Nick");
}
}
public string ValidationResult
{
get { return _validationResult; }
private set
{
_validationResult = value;
NotifyPropertyChanged("ValidationResult");
}
}
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
#region Implementation of IDataErrorInfo
private string NickValid()
{
if (string.IsNullOrEmpty(Nick))
{
return NickNull;
}
if (Regex.IsMatch(Nick, "[^a-zA-Z0-9-_.]"))
{
return NickInvalidCharacters;
}
return string.Empty;
}
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string propertyName]
{
get
{
ValidationResult = string.Empty;
switch (propertyName)
{
case "Nick":
ValidationResult = NickValid();
break;
default:
break;
}
return ValidationResult;
}
}
#endregion
}
This model class I use in view model and I bind Nick property of model class to the Text property of comboBox control.
Also I bind method LogOn from view model class on button click event in view. I would like disabale button if validation in model class has error:
View model:
[Export(typeof(ILogOnViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class LogOnViewModel : Screen, ILogOnViewModel,
IPartImportsSatisfiedNotification
{
public CurrentUser CurrentUser { get; set; }
public bool CanLogOn
{
get
{
return string.IsNullOrWhiteSpace(CurrentUser.ValidationResult);
}
}
//bind on button click event
public void LogOn()
{}
}
Solution is simple set CanLogOn property on false if validation in CurrentUser (object) property has error.
But I don’t how notify property CanLogOn that in model class is not error. I run app and button is still disabled.
I need achive this behavior in model:
public string ValidationResult
{
get { return _validationResult; }
private set
{
_validationResult = value;
NotifyPropertyChanged("ValidationResult");
//notify property CanLogOn in view model class
}
}
Any advice? Thank.
Attach an event handler to the PropertyChanged event of the user in your viewmodel:
CurrentUser.PropertyChanged += new PropertyChangedEventHandler(CurrentUser_PropertyChanged);
Add send a notification if the ValidationResult changes:
void CurrentUser_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "ValidationResult") NotifyPropertyChanged("CanLogOn");
}
Note: If your reference CurrentUser is overwritten you need to add the event handler to the new object. You could do this by placing the attachment code in the setter of CurrentUser.