I'm using prism to develop an android app.
I'm trying to make a Base ViewModel. Inside this ViewModel I would like to set common properties to all my ViewModels.
public class BaseViewModel : BindableBase
{
protected INavigationService _navigationService;
protected IPageDialogService _dialogService;
public BaseViewModel(INavigationService navigationService, IPageDialogService dialogService)
{
_navigationService = navigationService;
_dialogService = dialogService;
}
private string _common;
/// <summary>
/// Common property
/// </summary>
public string CommonProperty
{
get { return _common; }
set
{
_common = value;
SetProperty(ref _common, value);
}
}
}
My problem is: when I set the common property in the constructor, works fine.
But when I´m setting the common property in OnNavigatingTo and using async, doesn´t work. The SetProperty is triggered when calling the OnNavigatingTo, but my binded label with this common property doesn´t refresh the value.
namespace TaskMobile.ViewModels.Tasks
{
/// <summary>
/// Specific view model
/// </summary>
public class AssignedViewModel : BaseViewModel, INavigatingAware
{
public AssignedViewModel(INavigationService navigationService, IPageDialogService dialogService) : base(navigationService,dialogService)
{
CommonProperty= "Jorge Tinoco"; // This works
}
public async void OnNavigatingTo(NavigationParameters parameters)
{
try
{
Models.Vehicle Current = await App.SettingsInDb.CurrentVehicle();
CommonProperty= Current.NameToShow; //This doesn´t works
}
catch (Exception e)
{
App.LogToDb.Error(e);
}
}
}
when you use SetProperty, you should not set value for the backfield.
so you should remove this line:
_common = value;
Because you are doing asynchronous invocation on a separate thread the UI is not being notified of the change.
The async void of OnNavigatingTo, which is not an event handler, means it is a fire and forget function running in a separate thread.
Reference Async/Await - Best Practices in Asynchronous Programming
Create a proper event and asynchronous event handler to perform your asynchronous operations there
For example
public class AssignedViewModel : BaseViewModel, INavigatingAware {
public AssignedViewModel(INavigationService navigationService, IPageDialogService dialogService)
: base(navigationService, dialogService) {
//Subscribe to event
this.navigatedTo += onNavigated;
}
public void OnNavigatingTo(NavigationParameters parameters) {
navigatedTo(this, EventArgs.Empty); //Raise event
}
private event EventHandler navigatedTo = degelate { };
private async void onNavigated(object sender, EventArgs args) {
try {
Models.Vehicle Current = await App.SettingsInDb.CurrentVehicle();
CommonProperty = Current.NameToShow; //On UI Thread
} catch (Exception e) {
App.LogToDb.Error(e);
}
}
}
That way when the awaited operation is completed the code will continue on the UI thread and it will get the property changed notification.
Related
I have achieved desired result with MessagingCenter, but I have got an information from reading Xamarin articles that MessagingCenter is not the preferred way to trigger 30+ events. Additional to that I have to unsubscribe from MessagingCenter after action has been done. I want to have Settings page where I would have 30+ settings that have to be changed across whole application in different views. How I can inject SettingsViewModel into other ViewModels in Xamarin.Forms application?
SettingsViewModel.cs:
namespace MessagingCenterApp.ViewModels
{
public class SettingsViewModel : BaseViewModel, ISettingsViewModel
{
public ICommand ChangeCommand { get; set; }
public SettingsViewModel()
{
Title = "Settings";
this.BoxColor = Color.Red;
this.ChangeCommand = new Command(this.ChangeColor);
}
private void ChangeColor()
{
this.BoxColor = Color.FromHex(this.BoxColorS);
MessagingCenter.Send<Object, Color>(this, "boxColor", this.BoxColor);
}
private Color _boxColor;
public Color BoxColor
{
get => _boxColor;
set
{
_boxColor = value;
this.OnPropertyChanged();
}
}
private string _boxColorS;
public string BoxColorS
{
get => Preferences.Get("BoxColor", "#17805d");
set
{
Preferences.Set("BoxColor", value);
this.ChangeColor();
this.OnSettingsChanged();
this.OnPropertyChanged();
}
}
public event EventHandler<SettingsChangedEventArgs> SettingsChanged;
private void OnSettingsChanged() => this.SettingsChanged?.Invoke(this, new SettingsChangedEventArgs(this.Settings));
public Settings Settings { get; private set; }
}
}
HomeViewModel.cs:
namespace MessagingCenterApp.ViewModels
{
public class HomeViewModel : BaseViewModel
{
public HomeViewModel()
{
this.Title = "Home";
MessagingCenter.Subscribe<Object, Color>(this, "boxColor", (sender, arg) =>
{
System.Diagnostics.Debug.WriteLine("received color = " + arg);
this.BoxColor = arg;
});
this.BoxColor = Color.Red;
this.SettingsViewModel = new SettingsViewModel();
this.SettingsViewModel.SettingsChanged += OnSettingsChanged;
}
private void OnSettingsChanged(object sender, SettingsChangedEventArgs e)
{
throw new NotImplementedException();
}
private Color _boxColor;
public Color BoxColor
{
get => _boxColor;
set
{
_boxColor = value;
OnPropertyChanged();
}
}
private ISettingsViewModel SettingsViewModel { get; }
}
}
Should I somehow do all in MainViewModel? I mean:
namespace MessagingCenterApp.ViewModels
{
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
this.SettingsViewModel = new SettingsViewModel();
this.HomeViewModel = new HomeViewModel(this.SettingsViewModel);
}
public SettingsViewModel SettingsViewModel { get; set; }
public HomeViewModel HomeViewModel { get; }
}
}
Then initialized it in AppShell? I could not get this approach working.
Important! I don't want to use any MVVM framework! Only native behaviour.
mvvmcross' Messenger is alleged to be "lighter weight" than X-Form's built-in Messaging Center.
I use mvvmcross Messenger by defining some helper methods in a "BasePage". Then each page inherits from "BasePage" rather than "ContentPage".
This automatically handles "unsubscribe" of each method. And makes it easier to manage mvvmcross' "subscription tokens".
BasePage.xaml.cs:
// If not using mvvmcross, this could inherit from ContentPage instead.
public class BasePage : MvxContentPage
{
protected readonly IMvxMessenger Messenger;
public BasePage()
{
this.Messenger = Mvx.IoCProvider.Resolve<IMvxMessenger>();
}
protected override void OnAppearing()
{
base.OnAppearing();
// Examples of subscribing to messages. Your subclasses of BasePage can also do this.
this.Subscribe<MyMessage1>(OnMyMessage1);
this.SubscribeOnMainThread<MyMessage2>(OnMyMessage2);
}
protected override void OnDisappearing()
{
UnsubscribeAll();
base.OnDisappearing();
}
#region Messenger Subscriptions
protected List<MvxSubscriptionToken> _subscriptions = new List<MvxSubscriptionToken>();
/// <summary>
/// Create subscription and add to "_subscriptions".
/// Call this from subclass' OnAppearing, once per subscription.
/// Automatically unsubscribed in OnDisappearing.
/// </summary>
/// <param name="token"></param>
/// <param name="msgType"></param>
protected void Subscribe<T>(Action<T> onMessage) where T : MvxMessage
{
var token = this.Messenger.Subscribe<T>(onMessage);
// Hold token to avoid GC of the subscription.
_subscriptions.Add(token);
}
protected void SubscribeOnMainThread<T>(Action<T> onMessage) where T : MvxMessage
{
var token = this.Messenger.SubscribeOnMainThread<T>(onMessage);
// Hold token to avoid GC of the subscription.
_subscriptions.Add(token);
}
/// <summary>
/// OnDisappearing calls this.
/// </summary>
private void UnsubscribeAll()
{
if (_subscriptions.Count > 0)
{
foreach (MvxSubscriptionToken token in _subscriptions)
{
// Per "https://www.mvvmcross.com/documentation/plugins/messenger", this is sufficient to Unsubscribe:
// "Subscriptions can be cancelled at any time using the Unsubscribe method on the IMvxMessenger or by calling Dispose() on the subscription token."
token.Dispose();
}
_subscriptions.Clear();
}
}
#endregion
}
For view models, class would be "BaseViewModel", that your view models inherit from. Contents similar to above, but different method names for Appearing/Disappearing.
BaseViewModel.cs:
public class BaseViewModel : MvxViewModel
{
...
// mvvmcross' MvxViewModel provides these.
protected override void ViewAppearing()
{
...
}
protected override void ViewDisappearing()
{
...
}
... Messenger Subscriptions methods ...
}
I have a MVVM WPF application in C#, NET 3.5 and Visual Studio 2008.
From the app main xaml I import a user control.
This user control has some public methods, there are two I am interested in.
One method to start an animation and another to stop it.
From my view's constructor in code-behind (xaml.cs), I call the user control public method to start the animation to show it to user while I am loading some data into my gridview within listview. The method to load the data is called form my view model.
So now, when the loading task is finished, I need to call the another user control public method to stop animation but I do not know how to do this from my view model.
Any ideas? I cannot touch the user control as this is not mine.
Below some piece of code.
XAML:
xmlns:controlProgress="clr-namespace:Common.XAML.Controls.Progress;assembly=Common.XAML"
<controlProgress:Progress x:Name="Progress"
Grid.ZIndex="3"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="150"
CustomText="Loading...">
Code-behind (xaml.cs):
public MyView(ViewModelSession vm)
: base(vm)
{
InitializeComponent();
Progress.StartAnimation();
}
View Model:
public MyViewModel(Session session)
: base(session)
{
this.LoadDataIntoGridView();
}
You can use the INotifyPropertyChanged Interface e.g. create an ViewModelBase
public class ViewModelBase
: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then you use this for your ViewModel and add a Property IsLoading
public class MyViewModel : ViewModelBase
{
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set
{
if(_isLoading == value) return;
_isLoading = value;
OnPropertyChanged();
}
}
Then in your View Codebehind use the PropertyChanged event of the ViewModel to Start/Stop Animation.
Then you can set the bool in your ViewModel to start stop closing animation
in your view
UPDATE
public class MyView
{
private readonly MyViewModel _viewModel;
public MyView(MyViewModel viewModel)
: base(viewModel)
{
InitializeComponent();
_viewModel = viewModel;
_viewModel.PropertyChanged +=OnPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(MyViewModel.IsLoading))
{
if (_viewModel.IsLoading)
{
Progress.StartAnimation();
}
else
{
Progress.StopAnimation();
}
}
}
}
You could put a boolean property in your view model to track if the loading has been completed, after that the property will be set to true.
public class MyViewModel
{
public bool IsLoadComplete { get; set; }
public MyViewModel()
{
this.LoadDataIntoGridView();
}
}
Then in your codebehind you can start a Task to track changes in that property of the DataContext:
public MyView(MyViewModel vm)
{
InitializeComponent();
Progress.StartAnimation();
Task.Run(() =>
{
var dataContext = DataContext as MyViewModel;
while (true)
{
if (dataContext.IsLoadComplete)
break;
Task.Delay(100);
}
Dispatcher.BeginInvoke(new Action(() => { Progress.StopAnimation(); }));
});
}
You have to use Dispatcher.BeginInvoke to queue the call in the UI thread. Of course this is not a ready-to-production solution. You may provide Datacontext until View has been constructed in which case you must refactor, also you may keep track of the task you have just started and may be support cancellation with a CancellationToken. This is only a sample
In my WPF MVVM app, using Caliburn.Micro, I have a ViewModel, CreateServiceViewModel that, on a button click, opens a GridView in a seperate window for the User to chose a Row from.
I created another ViewModel for this, MemberSearchViewModel which has two properties:
private Member selectedMember;
public Member SelectedMember
{
get { return selectedMember; }
set { selectedMember = value; }
}
private IList<Member> members;
public IList<Member> Members
{
get { return members; }
set { members = value; }
}
How do I get that SelectedMember value back to the calling ViewModel? That ViewModel has a property of Service.SelectedMember.
EventAggregator is what you could use... One of many solutions I am sure.
public class MessageNotifier{
public object Content{get;set;}
public string Message {get;set;}
}
//MEF bits here
public class HelloWorldViewModel: Screen, IHandle<MessageNotifier>{
private readonly IEventAggregator _eventAggregator
//MEF constructor bits
public YourViewModel(IEventAggregator eventAggregator){
_eventAggregator = eventAggregator;
}
public override OnActivate(){
_eventAggregator.Subscribe(this);
}
public override OnDeactivate(){
_eventAggregator.UnSubscribe(this);
}
//I Handle all messages with this signature and if the message applies to me do something
//
public void Handle(MesssageNotifier _notifier){
if(_notifier.Message == "NewSelectedItem"){
//do something with the content of the selectedItem
var x = _notifier.Content
}
}
}
//MEF attrs
public class HelloWorld2ViewModel: Screen{
private readonly IEventAggregator _eventAggregator
//MEF attrs
public HelloWorld2ViewModel(IEventAggregator eventAggregator){
_eventAggregator = eventAggregator;
}
public someobject SelectedItem{
get{ return _someobject ;}
set{ _someobject = value;
NotifyOfPropertyChange(()=>SelectedItem);
_eventAggregator.Publish(new MessageNotifier(){ Content = SelectedItem, Message="NewSelectedItem"});
}
}
One option is to utilize NotifyPropertyChanged. Since you are working with ViewModels, they most likely implement INotifyPropertyChanged, which you can make use of just as the framework does.
When your CreateServiceViewModel creates the MemberSearchViewModel, it would just subscribe to the PropertyChanged event:
//This goes wherever you create your child view model
var memberSearchViewModel = new MemberSearchViewModel(); //Or using a service locator, if applicable
memberSearchViewModel.PropertyChanged += OnMemberSearchPropertyChanged;
private void OnMemberSearchPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "SelectedMember")
{
//Code to respond to a change in the Member
}
}
And then in your MemberSearchViewModel, you simply raise the NotifyPropertyChanged event when the user has selected a member from the grid.
EDIT:
As #DNH correctly notes in the comments, using event handlers like this can lead to memory leaks if not properly cleaned up. So when you are finished with the MemberSearchViewModel, make sure to unsubscribe to the PropertyChanged event. So for example, if you only need it until the user selects a member, you could put it inside the Property Changed Handler itself (I've switched it to use a class-level variable to hold the ViewModel):
private void OnMemberSearchPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "SelectedMember")
{
//Code to respond to a change in the Member
//Unsubscribe so the view model can be garbage collected
_memberSearchViewModel.PropertyChanged -= OnMemberSearchPropertyChanged;
_memberSearchViewModel = null;
}
}
One option would be to store MemberSearchViewModel as a field of CreateServiceViewModel and define CreateServiceViewModel.SelectedMember property as follows:
public Member SelectedMember
{
get
{
return _memberSearchViewModel.SelectedMember;
}
set
{
_memberSearchViewModel.SelectedMember = value;
}
}
How about?
public interface INotifyMe<T>
{
T ResultToNotify { get; set; }
}
public class CreateServiceViewModel : ViewModelBase, INotifyMe<Member>
{
// implement the interface as you like...
}
public class MemberSearchViewModel : ViewModelBase
{
public MemberSearchViewModel(INotifyMe<Member> toBeNotified)
{
// initialize field and so on...
}
}
Now you could let listen CreateServiceViewModel to its own property and you won't have to think about the removal of the event listener.
Well of course to do the more classical way you could alternatively use an interface like this.
public interface INotifyMe<T>
{
void Notify(T result);
}
As a follow-up to my comment, here's an example using Prism - I've never used Caliburn.
Create an event - the event's payload will be your SelectedMember:
public class YourEvent:CompositePresentationEvent<YourEventPayload>{}
Publish the event:
EventAggregator.GetEvent<YourEvent>().Publish(YourEventPayload);
Subscribe to the event:
EventAggregator.GetEvent<YourEvent>().Subscribe((i) => ...);
If I have a member, constructor and method like so
//The injected object exposes a public property of type bool which will raise
//a NotifyPropertyChangedEvent
IInjectedObject _injectedObject;
public someClass(IInjectedObject injectedObject)
{
_injectedObject = injectedObject;
}
public void DoSomething()
{
}
Is there a way to call the method in my class when the property on the injected object changes?
You state that your interface inherits from INotifyPropertyChanged, so it can reasonably be expected to notify listeners of property changes through the "PropertyChanged" event. Assuming something like this:
public interface IInjectedObject : INotifyPropertyChanged
{
bool MyImportantProperty { get; }
}
Then, your dependant object must listen for the INotifyPropertyChanged.PropertyChanged event:
public class MyDependantClass
{
public MyDependantClass(IInjectedObject injectedObject)
{
MyInjectedObject = injectedObject;
}
// We wrap the private field in a protected property,
// to capture the event subscriptions
private IInjectedObject _myInjectedObject;
protected IInjectedObject MyInjectedObject
{
get { return _myInjectedObject; }
set
{
// unsubscribe from the old property's event
if(_myInjectedObject!= null)
_myInjectedObject.PropertyChanged -= OnPropertyChanged;
_myInjectedObject= value;
// subscribe to the new property's event
if(_myInjectedObject!= null)
_myInjectedObject.PropertyChanged += OnPropertyChanged;
}
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if(args.PropertyName == "MyImportantProperty")
{
//react to the changed property here!
}
}
}
Basically I've got a command binding for the command itself assigned to Window.CommandBindings:
<CommandBinding Command="local:TimerViewModel.AddTimer"
CanExecute="local:TimerViewModel.AddTimer_CanExecute"
Executed="local:TimerViewModel.AddTimer_Executed" />
local is a namespace generated by default pointing to the namespace of the application. What I'm trying to achieve here is to have the command handling inside the TimerViewModel but I keep getting the following error:
CanExecute="local:TimerViewModel.AddTimer_CanExecute" is not valid. 'local:TimerViewModel.AddTimer_CanExecute' is not a valid event handler method name. Only instance methods on the generated or code-behind class are valid.
The TimerViewModel is pretty simple though but I believe I am missing something:
public class TimerViewModel : ViewModelBase
{
public TimerViewModel()
{
_timers = new ObservableCollection<TimerModel>();
_addTimer = new RoutedUICommand("Add Timer", "AddTimer", GetType());
}
private ObservableCollection<TimerModel> _timers;
public ObservableCollection<TimerModel> Timers
{
get { return _timers; }
}
private static RoutedUICommand _addTimer;
public static RoutedUICommand AddTimer
{
get { return _addTimer; }
}
public void AddTimer_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
public void AddTimer_Executed(object sender, ExecutedRoutedEventArgs e)
{
_timers.Add(new TimerModel(TimeSpan.FromSeconds((new Random()).Next())));
}
}
Can anyone point out the mistakes I'm making?
Also take a look at Josh Smith's RelayCommand. Using it would enable you to write the above like this:
public class TimerViewModel : ViewModelBase {
public TimerViewModel() {
Timers = new ObservableCollection<TimerModel>();
AddTimerCommand = new RelayCommand(() => AddTimer());
}
public ObservableCollection<TimerModel> Timers {
get;
private set;
}
public ICommand AddTimerCommand {
get;
private set;
}
private void AddTimer() {
Timers.Add(new TimerModel(TimeSpan.FromSeconds((new Random()).Next())));
}
}
Take a look at http://www.wpftutorial.net/DelegateCommand.html for an example of how to implement the delegate command for WPF. It allows you to hook up Execute and CanExecute as event handlers. If you're using RoutedUICommand directly you need to derive a custom command from it and override Execute and CanExecute with your functions.