I am developing a UWP application where i am following MVVM pattern.
I have a property in the View Model which is bind to the view. I have one function in the service which process multiple tasks.
After each execution of activity i need to update the property which is in the View Model.
ViewModel.cs
public Brush CurrentGetExecutionColor
{
get { return _currentGetExecutionColor; }
set { Set(ref _currentGetExecutionColor, value); }
}
public DelegateCommand DelegateCommandProcess
=> _delegateCommandProcess ?? (_delegateCommandProcess = new DelegateCommand(async () =>
{
await _service.ProcessMethod();
}));
Service.cs
private async Task<bool> ProcessMethod()
{
While(condition)
{
Process();
//UpdateViewModel property
CurrentGetExecutionColor = Color.Red;
}
}
How i can achieve this functionality so that i can update View Model property from service.
Thanks in Advance.
Try to implement in your property OnPropertyChanged, like this:
private Type _yourProperty;
public Type YourProperty
{
get { return _yourProperty; }
set
{
_yourProperty = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Related
I'm binding an activity indicator to a property called IsLoading to show that the page is busy (e.g. processing API calls). I need to implement this in all my MAUI app pages, so my question, how can I re-use this code? The property is implemented as follows:
public event PropertyChangedEventHandler PropertyChanged;
private bool isLoading;
public bool IsLoading
{
get => isLoading;
set
{
isLoading = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
You probably want to put this in a base class that you inherit from. For example.
Create a class/file BaseViewModel.cs, of course the name can be whatever you want. It might look like this:
public class BaseViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isLoading;
public bool IsLoading
{
get => isLoading;
set
{
isLoading = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now, whenever you create a new view model, you need to inherit from this. Let's say that you create a EditPersonViewModel, that would look like this:
public class EditPersonViewModel : BaseViewModel // This is where you inherit from the BaseViewModel
{
private bool isSaved;
public bool IsSaved
{
get => isSaved;
set
{
isSaved = value;
OnPropertyChanged();
}
}
}
Now you have access to all the things that are also in BaseViewModel. So you can set IsLoading to true or false, or you can implement a new property, IsSaved in my above example, and you can still call OnPropertyChanged to make the UI aware of the value change.
When I searched,I found,how to bind values from viewmodel to view but not viewmodel to viewmodel.can anyone help me to do that. what i need is to pass Authentication to other viewmodel.I am new in the MVVM world so please give me more detail.
my ViewModel look like this
public class ModelView_Authentication : INotifyPropertyChanged
{
//Binding authentication
private Authentication _authentication;
public Authentication authentication
{
get { return _authentication; }
set
{
_authentication = value;
NotifayPropertyChanged("_authentication");
}
}
//Command Button
public ModelView_Authentication()
{
authentication = new Authentication();
ButtonCommand = new ViewModdelCommand(exeMethode, canexeMethode);
}
public ICommand ButtonCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private bool canexeMethode(Object param)
{
return true;
}
//run this Command Onclick Button
private void exeMethode(Object param)
{
}
protected void NotifayPropertyChanged(string s)
{
PropertyChangedEventHandler pc = PropertyChanged;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(s));
}
}
//Run Assync Login
public static async Task<string> main(Authentication authentication)
{
var tocken = await Login.GetConnection(authentication);
return tocken.ToString();
}
}
need is to pass Authentication to other viewmodel
Your main ViewModel adheres to INotifyPropertyChanged, you can have your other VMs subscribe to the notification process of the main VM and acquire changes to specific properties as needed.
Just have a reference to the main VM, it is as easy as that. Where the VMs get their references, that process is up to you.
A good place is on App class. Since the App class is known throughout each of the namespaces, setup up a static property on it, set it after the main VM is created, and then access the it as needed.
public static ModelView_Authentication AuthVM { get; set; }
the access such as
var mainVM = App.AuthVM;
I have Label databound with BindingSource property. Label.Text property get updated only once.
this is how is property bound to label
this.lblWorkPlace.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.appStateBindingSource, "ResourceName", true));
i also tried to bind same property to textbox and textbox updates properly
this.lTextEdit1.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.appStateBindingSource, "ResourceName", true));
what could be wrong?
UPDATE
this is my "state" class
public class AppState: INotifyPropertyChanged
{
private static Operation _activeTask;
private static AppState _instance;
public static AppState Instance
{
get => _instance ?? (_instance = new AppState());
}
public Operation ActiveTask
{
get => _activeTask;
set
{
if (value != _activeTask)
{
_activeTask = value;
RaisePropertyChanged("ResourceName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string prop)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
public string ResourceName => ActiveTask?.Operacija?.acResursName.Trim() ?? "";
}
}
Problem was cross thread call. Application did not update UI and did not throw any exceptions
I've a problem with DataGrid in Prism MVVM.
When I edited entity in other window, then I create new window with DataGrid is not updated. Only run application again help. It's my code:
<DataGrid Name="ClientsTable" IsReadOnly="True" ItemsSource="{Binding Path=ListOfClients, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AutoGeneratingColumn="DataGrid_AutoGeneratingColumn" Margin="22,10,22,55" Width="800"/>
Part of ViewModel for this window:
public ListOfClientsViewModel(IClientService clientService, IEventAggregator eventAggregator)
{
this.clientService = clientService;
this.eventAggregator = eventAggregator;
ListOfClients.AddRange(clientService.GetAllClientsForList());
}
private ObservableCollection<ClientForList> listOfClients = new ObservableCollection<ClientForList>();
public ObservableCollection<ClientForList> ListOfClients
{
get { return listOfClients; }
set { SetProperty(ref listOfClients, value); }
}
And part of model from collection. It's in other project.
public class ClientForList : INotifyPropertyChanged
{
private string id;
private string name;
private string firstname;
private string lastname;
private string city;
private DateTime createdDate;
[DisplayName("Numer klienta")]
public string Id
{
get { return id; }
set
{
if (value != id)
{
id = value;
OnPropertyChanged();
}
}
}
[DisplayName("Nazwa")]
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
OnPropertyChanged();
}
}
}
[DisplayName("ImiÄ™")]
public string Firstname
{
get { return firstname; }
set
{
if (value != firstname)
{
firstname = value;
OnPropertyChanged();
}
}
}
[.....]
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And, when I edit entity in other window, then I open window with DataGrid by:
void ExecuteListOfClients()
{
ListOfClients listOfClientsWindow = new ListOfClients();
listOfClientsWindow.DataContext = new ListOfClientsViewModel(IClientService, EventAggregator);
listOfClientsWindow.ShowDialog();
}
And data in DataGrid is old. When I restart application data is actual. Help!
Instead of
set { SetProperty(ref listOfClients, value); }
do
set { SetProperty(listOfClients, value); }
and instead of
ListOfClients.AddRange(clientService.GetAllClientsForList());
do
ListOfClients = clientService.GetAllClientsForList().ToObservableCollection();
You may need to implement the extension ToObservableCollection().
Another solution is to change the property ListOfClients as a normal IList and then implment INotifyPropertyChanged inside your view model
Ok, I solved part of the problem.
I have cut the window creation without the manual creation of the ViewModel on:
void ExecuteListOfClients()
{
ListOfClients listOfClientsWindow = new ListOfClients();
listOfClientsWindow.ShowDialog();
}
And in xaml ListOfClients.xaml I changed to:
prism:ViewModelLocator.AutoWireViewModel="True"
And now, after reopening the window, it shows refreshed data;)
But there is still a problem. I've done the LoadData method, which is done in the constructor and assigns data to the ObservableCollection. It works. I have set a button for it and after editing the client I click to load new data and unfortunately nothing happens. In the future I want this method to be called from the EventAggregator after editing the client, but for now I have made a button to test and unfortunately it does not work.
It works. The problem was not in the WPF itself, nor was the view strangely enough. The problem lay in EntityFramework. I had to modify the command choosing data from the database to the collection:
var clients = db.Clients.AsNoTracking().ToList();
I am using Xamarin.forms (PCL) and I need to refresh/update Content Page with its data every few seconds. The data is retrieved from API in the viewmodel.
Is there any method or handler that can be used periodically to call the Get Api periodically inside the page.xaml.cs, something like:
methodRunPeriodically()
{
userdata = await UserService.GetUserasync(_UserViewModel.EmployeeId);
}
Xamarin.Forms has an API for starting a timer that you might find useful for this, documented here.
Device.StartTimer (TimeSpan.FromSeconds(10), () => {
// If you want to update UI, make sure its on the on the main thread.
// Otherwise, you can remove the BeginInvokeOnMainThread
Device.BeginInvokeOnMainThread(() => methodRunPeriodically());
return shouldRunAgain;
});
Based on the code in the above question, you would ensure that:
Your userdata object implements IPropertyChange as follows:
//Other usings skipped for brevity
...
...
using System.ComponentModel;
using System.Runtime.CompilerServices;
// This is a simple user class that
// implements the IPropertyChange interface.
public class DemoUser : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private string userName = string.Empty;
private string phoneNumber = string.Empty;
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 DemoUser()
{
}
public string Id { get; set; }
public string UserName
{
get
{
return this.userName;
}
set
{
if (value != this.userName)
{
this.userName = value;
NotifyPropertyChanged();
}
}
}
public string PhoneNumber
{
get
{
return this.phoneNumber;
}
set
{
if (value != this.phoneNumber)
{
this.phoneNumber = value;
NotifyPropertyChanged();
}
}
}
}
In your ContentPage, you then try the following, (I slightly modified the code by others above):
public class UserPage : ContentPage
{
private DemoUser demoUser;
private int intervalInSeconds;
public UserPage()
{
//Assuming this is a XAML Page....
InitializeComponent();
}
public UserPage(DemoUser demoUser, int intervalInSeconds = 10) : this()
{
this.demoUser = demoUser;
this.intervalInSeconds = intervalInSeconds;
this.BindingContext = this.demoUser;
Device.StartTimer(TimeSpan.FromSeconds(this.intervalInSeconds), () =>
{
Device.BeginInvokeOnMainThread(() => refreshDemoUser());
return true;
});
}
private async void refreshDemoUser()
{
this.demoUser = await getDemoUserById(this.demoUser.Id);
}
}
You can do as follows to run a Task when 10 seconds has passed. Returning true in Device.StartTimer will ensure that the Timer keeps running. Also, you want to ensure that you invoke the method on the main thread to update the UI:
public MyConstructor()
{
StartTimer();
}
private void StartTimer()
{
Device.StartTimer(System.TimeSpan.FromSeconds(10), () =>
{
Device.BeginInvokeOnMainThread(UpdateUserDataAsync);
return true;
});
}
private async void UpdateUserDataAsync()
{
userdata = await UserService.GetUserasync(_UserViewModel.EmployeeId);
}
If your API doesn't expose an EventHandler that you can subscribe to, then you need to do as mentioned in my example above.
You should just bind the UI to properties in your ViewModel and then set those properties appropriately. Calling OnPropertyChanged() will trigger Xamarin.Forms to update the UI based on the bound properties. Something like below:
//Code in Page
public class MyPage : ContentPage
{
public MyPage()
{
var entry = new Entry();
BindingContext = new MyViewModel();
entry.SetBinding<MyViewModel>(Entry.TextProperty, vm=>vm.EntryText);
Content = entry;
}
}
//Code in ViewModel
public class MyViewModel() : INotifyPropertyChanged
{
public MyViewModel()
{
Task.Factory.StartNew(()=> methodRunPeriodically());
}
string entryText;
public string EntryText
{
get { return entryText; }
set
{
if(entryText == value)
return;
entryText = value;
OnPropertyChanged();
}
}
bool shouldRun = true;
async Task methodRunPeriodically()
{
while(shouldRun)
{
userdata = await UserService.GetUserasync(_UserViewModel.EmployeeId);
EntryText = userdata.FirstName;
await Task.Delay(5000); //Run this every 5 seconds
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In this pattern, we are kicking off a long-running task that will run in a loop. It is reaching out to refresh the userData every 5 seconds and then setting the EntryText property. In the setter of the EntryText property in our ViewModel, we are calling OnPropertyChanged() which will cause Xamarin.Forms to update the UI. Calling OnPropertyChanged() triggers Xamarin.Forms to switch thread context from the background task to the UI thread and then back to the background task.
I didn't write this in XAML, but the binding would be pretty much the same except the entry would be like below:
<Entry Text={Binding EntryText}/>
EDIT
#therealjohn's answer is good also. You could use that instead of my while loop like below:
bool shouldRun = true;
methodRunPeriodically()
{
Device.StartTimer(TimeSpan.FromSeconds(5), () =>
{
userdata = await UserService.GetUserasync(_UserViewModel.EmployeeId);
EntryText = userdata.FirstName;
return shouldRun;
});
}
You can review what the Forms source code is doing with the Device.StartTimer on the native iOS and Android.
Update UI every one second:
Device.StartTimer(TimeSpan.FromMilliseconds(1000), loop2);
bool loop2()
{
Device.BeginInvokeOnMainThread(() => updateUI());
return true;
}
or:
Device.StartTimer(TimeSpan.FromMilliseconds(1000), loop2);
bool loop2()
{
Device.BeginInvokeOnMainThread(() => {
updateUI();
//more stuff;
});
return true;
}