I have a question, I am building small login system, basically its ready and working, but still having some problems with UI, si if I take such button click action
private void LoadButton_Click(object sender, RoutedEventArgs e)
{
Nullable<bool> creditencialFile = _controls.CredencialsFileDialog.ShowDialog();
if (creditencialFile == true)
{
ContextStatic.Filename = _controls.CredencialsFileDialog.FileName;
FileInfo creditencialsFileInfo = new FileInfo(ContextStatic.Filename);
ContextStatic.RootFolder = creditencialsFileInfo.DirectoryName;
model.LeapCreditencials = CredentialHelper.LoadCredentials(ContextStatic.Filename);
}
}
It loads credentials from file, and they are saved in object attribute:
model.LeapCreditencials = CredentialHelper.LoadCredentials(ContextStatic.Filename);
Now i want to refresh or reload UI so I all information windows would be set up to with new info. Question is should I need to reload per one control, or there is a smart way to reload Ui with new object values?
Yes, you should implement INotifyPropertyChanged in your model
msdn description to implement INotify Interface
The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
When value of model is changed it will reflect in the UI.
Xaml
<TextBox Text="{Binding Mymodel.CustomerName,
UpdateSourceTrigger=PropertyChanged}" />
Model
public class DemoCustomer : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
this.customerNameValue = value;
NotifyPropertyChanged();
}
}
Yeah, there's a smart way. It's called MVVM (a.k.a. Model View View Model). It is not so hard to understand. You just bind your view to values in ViewModel, and when a value is changed UI is automatically updated.
Related
I have an application in MAUI.NET with MVVM architecture.
In ViewModels I am setting PropertyChanged as most of examples through the web.
In my application the user opens lots of views (that are of type ContentView).
Each time the ContentView is assigned to main area of application and drawn on the monitor, the setters of ViewModels are fired (when they have already binded value).
What I need is to limit this behaviour (firing setters in ViewModels) only to the moment when the user himself/herself click on the checkbox, omitting the moments when the framework just draw the checkbox which have binded value set to true.
In this situation call stack says that external code is firing this.
Anyone have any idea how to deal with this?
edit:
viewmodel:
internal class CheckboxViewModel : BaseViewModel
{
public bool Value
{
get
{
...
}
set
{
//here I compute value and set status to be changed
//this is fired when the user click on checkbox = ok
//but also when checkbox (with binded true value) is drawn on the monitor = problem
}
}
public CheckboxViewModel(XElement item, Registry registry) : base(item, registry)
{
...
}
}
view:
<DataTemplate x:DataType="viewModels:CheckboxViewModel" x:Key="CheckboxDataTemplate">
<CheckBox IsChecked="{Binding Value}" ... />
</DataTemplate>
and my sligthly changed version of INotifyProperty:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new(propertyName));
}
protected virtual void SetPropertyAndNotify<T>(ref T backedProperty, T newValue, [CallerMemberName] string propertyName = null)
{
if (object.Equals(backedProperty, newValue))
return;
backedProperty = newValue;
PropertyChanged?.Invoke(this, new(propertyName));
}
}
situation:
I put a ContentView that in BindedContext has (in some hierarchy) the CheckBoxViewModel and it is drawn. But the setter is fired.
Suppose we have a Model (class Model) with the following property.
public string InputFileName
{
get { return m_InputFileName; }
set
{
m_InputFileName = value;
RaiseNotifyPropertyChanged("InputFileName");
}
}
The above model implements the INotifyPropertyChanged interface, so we have also the following method and the following event. The RaiseNotifyPropertyChanged method below is used to update the ViewModel.
#region INotifyPropertyChanged Implementation
private void RaiseNotifyPropertyChanged(string property)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
The following are the main sections of the class that implements the ViewModel.
public class ViewModel : INotifyPropertyChanged
{
#region Members
private Model m_Model;
private string m_InputFileStr;
private readonly ICommand m_SubmitCommand;
#endregion
#region Constructors
public ViewModel()
{
m_Model = new Model();
m_Model.PropertyChanged += new PropertyChangedEventHandler(this.Model_PropertyChanged);
m_InputFileStr = string.Empty;
// ...
// initialize m_SubmitCommand
}
#endregion
// ...
#region Properties
public string InputFileStr
{
get { return m_InputFileStr; }
set
{
if (value == m_InputFileStr) return;
m_InputFileStr = value;
OnPropertyChanged("InputFileStr");
m_SubmitCommand.RaiseCanExecuteChanged();
}
}
#endregion
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
// This method is called when the model changes, so the Model notified the ViewModel.
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == "InputFileName")
{
InputFileStr = m_Model.InputFileName;
}
else if (args.PropertyName == "OutputFileName")
{
OutputFileStr = m_Model.OutputFileName;
}
else if (args.PropertyName == "ReportText")
{
ReportTextStr = m_Model.ReportText;
}
}
}
The following are the main sections of the class that implements the View:
MainWindow.xaml
<TextBox Name="inputfileTextBox"
Text="{Binding Path=InputFileStr, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
<Button Name="submitButton"
Content="Submit"
Command="{Binding SubmitCommand}"/>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
The above implementation works correctly:
the View and the ViewModel correctly update each other;
the Model correctly updates the ViewModel.
With the aim of enabling the ViewModel to update the Model, I thought I would add the following call inside the set property InputFileStr of ViewModel:
m_Model.InputFileName = value;
However, this solution of updating the Model causes an obvious unintended effect:
The user modified the View.
The ViewModel is automatically modified.
The ViewModel updates the Model (m_Model.InputFileName = value;).
The Model is updated...
... so it notifies the ViewModel about the changes
Is the above behavior a correct behavior? I expect that if the ViewModel updates the Model, then the Model does not have to re-notify the ViewModel about the same change... As an alternative solution I thought I'd add an Update method to the Model: this method should update the Model without using the Model Properties.
public void Update(string inputFileName) // this method does not notifies the ViewModel
{
m_InputFileName = inputFileName;
}
Is this alternative solution a correct solution or are there better solutions?
Depending on what your model is, you will usually just invoke a "Save" method or similar. Most models (say, a database) don't need/want to have every change given to them in real-time.
So in general, the flow would be:
User invokes "save" operation
View model receives this as a command
View model invokes "save" operation on the model with the new data
If your DTO objects are shared between the model and view model, you don't even need to worry about synchronization. Otherwise, this is a good time to sync them.
On a similar note, using PropertyChanged in a model class is usually a bad idea. For starters, its no fun at all to listen to. Instead, if the model receives new data, raise a more semantically clear event to the VM with the new data.
tldr; Basically, don't worry so much about keeping your model and view model in sync. Very often, the model won't be keeping a copy of the current state at all! Even when it is, just update it when the view model is ready to "commit" changes, and notify the View Model of external changes to the model via normal events.
I've added an observable data and bound it to my data grid as follows.
private ObservableCollection<Order> _allOrders;
public ObservableCollection<Order> AllOrders
{
get { return _allOrders;}
set { _allOrders = value; OnPropertyChanged(); }
}
public Presenter() { _allOrders = new ObservableCollection<Order>(...); }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] String propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
When I set breakpoint on the event that is supposed to filter the data, I set the property AllOrder to null. I can verify using the watch that it's set to that. However, the view isn't updated, so I'm guessing that I forgot something. The view model class Presenter implements INotifyPropertyChanged interface, of course.
What's missing?
Edit
The XAML code for the grid looks as follows.
<DataGrid x:Name="dataGrid"
ItemsSource="{Binding AllOrders}"
AutoGeneratingColumn="DataGrid_OnAutoGeneratingColumn" ...>
Assuming that you set DataContext accordingly and AllOrders binding works initially if you want to filter items in the UI, without change collection, it's much easier when you use ListCollectionView with a Filter. WPF does not bind directly to collection but to a view - MSDN.
private readonly ObservableCollection<Order> _allOrders;
private readonly ListCollectionView _filteredOrders;
public ICollectionView FilteredOrders
{
get { return _filteredOrders; }
}
public Presenter()
{
_allOrders = new ObservableCollection<Order>(...);
_filteredOrders = new ListCollectionView(_allOrders);
_filteredOrders.Filter = o => ((Order)o).Active;
}
and in XAML
<DataGrid ... ItemsSource="{Binding FilteredOrders}">
when you want to manually refresh UI just call Refresh
_filteredOrders.Refresh();
Apart from that nothing changes in the view model. You still add/remove items to _allItems and changes should be picked up automatically by UI
Do you set the property AllOrders only in the constructor? If so, then do not set the field _allOrders but the property AllOrders. If you set the field then notification is never raised.
I have two DataGrid, each binding in a dataSource like this :
ItemsSource="{Binding Data, ElementName=EmpSource, Mode=TwoWay}"
The first DataGrid(dgJob), contains Job and the second(dgEmp), the employee linked to the job.
I want to keep all the employees in the EmpSource, and display in the dataGrid, only those who are linked to the selected job in my first datagrid.
So I am doing this in the dgJob selectionChanged event :
dgEmp.ItemsSource = null;
var lstEmp = EmpSource.DataView.OfType<Emp>().Where(ores => ores.IdJob == itmJobSelect.IdJob).ToList();
dgEmp.ItemsSource = lstEmp;
The problem is, the dataGrid is not clearing when I change the selected line in my datagrid with the jobs, so for every job, I display every Employees in the dgEmp, while I should only display those who are connected to the job.
I can delete the line in the xaml, that determine the dataSource, but if I do this, I must refresh the dataGrid when there is a change in the dataSource.
But I don't found how to refresh it(at least for the first time) unless I write the 3 lines each time after a change in dataSource.
Can somebody help me find a solution to my problem?
Thank you.
I recommend you to use MVVM design pattern. You should load your data in view model class and store it in collection which implements INotifyCollectionChanged interface. View model should also implement INotifyPropertyChanged interface.
When your employee collection changes, you should filter second collection as in following code:
Jobs.CollectionChanged += (sender, args) =>
{
Employees = AllEmployees.Where(c=> c.IdJob == SelectedJob.IdJob);
}
You should also do same thing when SelectedJob changes and DataGrid will be refreshed.
This will work only when you will have implemented property changed notifications and correct binding was specified.
Here's example of property changed implementation which you should write:
public class ViewModel : INotifyPropertyChanged
{
public IEnumerable<Emp> Employees
{
get { return _employees; }
set
{
if (_employees != value)
{
_employees = value;
OnPropertyChanged("Employees");
}
}
}
/* ... */
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You should also assign your view model instance to DataContext for make binding works. For example in code behind file constructor:
public void Page()
{
DataContext = new ViewModel();
InitializeComponent();
}
Relativly new to the MVVM stuff, i have Trouble with the following:
I have an object "User", this object exposes some properties, like Username, Email, etc..
In the mvvm model i have a property:
private IUser currentUser;
public IUser CurrentUser
{
get
{
return this.currentUser;
}
set
{
this.currentUser = value;
this.OnPropertyChanged("CurrentUser");
}
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In XAML a TextBox is bound as follows:
Text="{Binding CurrentUser.Email, Mode=TwoWay}"
When changing the Email Address the OnPropertyChanged is not fired and thus other code (as the ICommands) are not "working".
Is there a way that when the user changes the Text in the TextBox the OnPropertyChanged fires??
TIA,
Paul
You are firing PropertyChanged when CurrentUser changes, but current user is not changing you are just changing the Email property on it. A quick fix could be to have the Email property propagate the OnChange event for CurrentUser.
public string Email
{
//get
set
{
this.email = value;
this.OnPropertyChanged("CurrentUser");
}
}
Actually I think this.OnPropertyChanged("Email") would work too, but the setter of CurrentUser is definitely not getting called when you change a property of it.
Where is your INotifyPropertyChanged Interface?
I think it is necessary.
Property Change Notification does not watch the properties of your IUser class.
It is only watching for changes to the ViewModel Property CurrentUser (the reference).
You need
a) make the IUser implement INotifyPropertyChanged
or
b) pull out EmailAddress and add it to the ViewModel like so:
public string Email
{
get
{
return this.CurrentUser.Email;
}
set
{
this.CurrentUser.Email = value;
this.OnPropertyChanged("Email");
}
}