I am using Xamarin Forms with Prism, based on this GitHub sample..
Desired Behavior
Deep link is clicked, showing the detail view:
User presses back button. Scroll and highlight the linked selection (not happening).
None of the OnNavigation events are firing. Is this a bug? How do I accomplish this?
App.Xaml
public partial class App : PrismApplication
{
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override async void OnInitialized()
{
InitializeComponent();
await NavigationService.NavigateAsync("MainTabbedPage/NavigationPage/ShowsListPage/DetailPage?show=279121");
//await NavigationService.NavigateAsync("MainTabbedPage/NavigationPage/ShowsListPage");
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<UpcomingShowsPage>();
Container.RegisterTypeForNavigation<ShowsListPage>(); // <-- Problematic ListView
Container.RegisterTypeForNavigation<DetailPage>();
Container.RegisterTypeForNavigation<MainTabbedPage>();
Container.RegisterTypeForNavigation<NavigationPage>();
Container.RegisterType<ITsApiService, TsApiService>();
}
ShowsListPage.xaml
ContentPage is using the Prism directive: prism:ViewModelLocator.AutowireViewModel="True". (nothing special)
ShowsListPageViewModel.cs
using System.Collections.ObjectModel;
using InfoSeries.Core.Models;
using InfoSeries.Core.Services;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using Xamarin.Forms;
namespace DeepNavigation.ViewModels
{
public class ShowsListPageViewModel : BindableBase, INavigationAware
{
private readonly ITsApiService _tsApiService;
private readonly INavigationService _navigationService;
private ObservableCollection<SerieFollowersVM> _highlightSeries;
public ObservableCollection<SerieFollowersVM> HighlightSeries
{
get { return _highlightSeries; }
set { SetProperty(ref _highlightSeries, value); }
}
public ShowsListPageViewModel(ITsApiService tsApiService, INavigationService navigationService)
{
_tsApiService = tsApiService;
_navigationService = navigationService;
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public async void OnNavigatedTo(NavigationParameters parameters)
{
var series = await _tsApiService.GetStatsTopSeries();
HighlightSeries = new ObservableCollection<SerieFollowersVM>(series);
}
public void OnNavigatingTo(NavigationParameters parameters)
{
}
private DelegateCommand<ItemTappedEventArgs> _goToDetailPage;
public DelegateCommand<ItemTappedEventArgs> GoToDetailPage
{
get
{
if (_goToDetailPage == null)
{
_goToDetailPage = new DelegateCommand<ItemTappedEventArgs>(async selected =>
{
NavigationParameters param = new NavigationParameters();
var serie = selected.Item as SerieFollowersVM;
param.Add("show", serie.Id);
await _navigationService.NavigateAsync("DetailPage", param);
});
}
return _goToDetailPage;
}
}
}
}
Question
How can I get the back button to select the list view?
Is there any platform guidance saying that the back button after a deep link must go to the source calling application.. rendering this question useless? (e.g. pop the navigation back to Chrome/Safari)
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;
}
This works (the graph is properly loaded):
var oxyPlotView = new OxyPlotView{ Model = GetPlotModelSynch() };
This doesn't (the graph remains empty):
var oxyPlotView = new OxyPlotView();
// Here PlotModel will be loaded asynchronously from the BindingContext:
oxyPlotView.SetBinding(OxyPlotView.ModelProperty, new Binding(nameof(GraphViewModel.PlotModel)));
I have made proper isolated tests to ensure that INotifyPropertyChanged is working properly with my ViewModel. So the problem seems to be that OxyPlotView is built properly only if it has al the info from its inception (?). Is that even possible?
Here is the full ViewModel. INotifyPropertyChanged works because Title is behaving as intended (Title is binded to a Label in the same view).
class GraphViewModel : INotifyPropertyChanged
{
IGraphSeriesGroupRepository _graphSeriesGroupRepository;
private GraphSeriesGroup _graphSeriesGroup;
private ulong _sensorId;
public event PropertyChangedEventHandler PropertyChanged;
private PlotModel _plotModel;
public PlotModel PlotModel
{
get { return _plotModel; }
set
{
if (_plotModel != value)
{
_plotModel = value;
OnPropertyChanged(nameof(PlotModel));
}
}
}
private string _title;
public string Title
{
get { return _title; }
set
{
if (_title != value)
{
_title = value;
OnPropertyChanged(nameof(Title));
}
}
}
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set
{
_isLoading = value;
OnPropertyChanged(nameof(IsLoading));
}
}
public GraphViewModel(IGraphSeriesGroupRepository graphSeriesGroupRepository, ulong sensorId)
{
_graphSeriesGroupRepository = graphSeriesGroupRepository;
_sensorId = sensorId;
Load();
}
public PlotModel GetPlotModelSynch()
{
_graphSeriesGroup = _graphSeriesGroupRepository.GetGraphSeriesGroup(_sensorId);
return GetPlotModel(_graphSeriesGroup);
}
private async void Load()
{
IsLoading = true;
await Task.Delay(5000);
_graphSeriesGroup = await _graphSeriesGroupRepository.GetGraphSeriesGroupAsync(_sensorId);
ApplyChanges();
IsLoading = false;
}
private void ApplyChanges()
{
// ---
Title = _graphSeriesGroup.Title;
PlotModel = GetPlotModel(_graphSeriesGroup);
}
private PlotModel GetPlotModel(GraphSeriesGroup graphSeriesGroup)
{
...
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Update: The only way I've found to make it work is:
private void chatter_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == nameof(GraphViewModel.PlotModel))
{
_oxyPlotView = new OxyPlotView
{
Model = _graphViewModel.PlotModel
};
_stackLayout.Children.Add(_oxyPlotView);
}
}
...even updating an _oxyPlotView (which was already added to the StackLayout) and calling _oxyPlotView.InvalidateDisplay() didn't work.
I have a C# WPF application built with Visual Studio 2015. I'm using MVVM and the Observer Pattern.
My Provider is a user control called 'ucClientFilter1ViewModel' that contains two text box controls where the user can search for a client(s):
namespace NSUCClientControls
{
public class ucClientFilter1ViewModel : ViewModelBase, IObservable<ClientFilterParameter>
{
private string filterLocation;
private string whereSearch1;
private string whereSearch2;
private List<IObserver<ClientFilterParameter>> observers;
public ucClientFilter1ViewModel()
{
observers = new List<IObserver<ClientFilterParameter>>();
}
public string FilterLocation
{
get { return filterLocation; }
set { filterLocation = value; }
}
public string WhereSearch1
{
get { return whereSearch1; }
set
{
whereSearch1 = value;
TestUpdateGrid(filterLocation);
}
}
public string WhereSearch2
{
get { return whereSearch2; }
set
{
whereSearch2 = value;
TestUpdateGrid(filterLocation);
}
}
private void TestUpdateGrid(string _filterLocation)
{
var filterInfo = new ClientFilterParameter(this);
foreach (var observer in observers)
{
observer.OnNext(filterInfo);
}
}
public IDisposable Subscribe(IObserver<ClientFilterParameter> observer)
{
// Check whether observer is already registered. If not, add it
if (!observers.Contains(observer))
{
observers.Add(observer);
// Provide observer with existing data
var filterInfo = new ClientFilterParameter(this);
observer.OnNext(filterInfo);
}
return new Unsubscriber<ClientFilterParameter>(observers, observer);
}
internal class Unsubscriber<ClientFilterParameter> : IDisposable
{
private IObserver<ClientFilterParameter> observer;
private List<IObserver<ClientFilterParameter>> observers;
public Unsubscriber(List<IObserver<ClientFilterParameter>> _observers, IObserver<ClientFilterParameter> _observer)
{
observers = _observers;
observer = _observer;
}
public void Dispose()
{
if (observers.Contains(observer))
{
observers.Remove(observer);
}
}
}
}
}
My Observer is a user control called 'ucClientGrid1ViewModel' that contains a datagrid where the search results are displayed.
namespace NSUCClientControls
{
public class ucClientGrid1ViewModel : ViewModelBase, IObserver<ClientFilterParameter>
{
private IDisposable cancellation;
private ObservableCollection<Client> clientsMultiple;
public ucClientGrid1ViewModel()
{
}
public ObservableCollection<Client> ClientsMultiple
{
get
{
var myClientDataAccess = new ClientDataAccess();
clientsMultiple = myClientDataAccess.GetClientListFromSQL_Test2();
return clientsMultiple;
}
set
{
}
}
public virtual void Subscribe(ucClientFilter1ViewModel provider)
{
cancellation = provider.Subscribe(this);
}
public void OnNext(ClientFilterParameter myFilter)
{
OnPropertyChanged("ClientsMultiple");
var myDummyWindow = new dummyWindow();
myDummyWindow.Show();
myDummyWindow.Close();
}
public void OnError(Exception error)
{
throw new NotImplementedException();
}
public void OnCompleted()
{
throw new NotImplementedException();
}
}
}
This all works and I get the search results that I am expecting. But what I don't understand is why the inclusion of the following lines actually speed things up!
var myDummyWindow = new dummyWindow();
myDummyWindow.Show();
myDummyWindow.Close();
I'm new to MVVM and the observer pattern, so as I was writing the code I had included message boxes at various points to help me to follow the flow of it. It was all working as expected. Then I removed the message boxes and it still worked but the application was pausing at the end before you could continue to keep searching.
Putting a message box back in at the end prevented this pause. Replacing the message box with a "DummyWindow" that just opens and closes has the same affect and prevents the pause at the end. This is what I currently have but I'd rather not leave this in there.
Presumably opening the window causes something else to happen which stops some redundant process, and this then prevents the pause? What else could I do to prevent the pause at the end, without using this DummyWindow?
I've tried searching on here and with Bing with no luck.
Thanks in advance!
Edit:
ViewModelBase...
namespace NSCommon
{
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
{
protected ViewModelBase()
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public void Dispose()
{
OnDispose();
}
protected virtual void OnDispose()
{
}
}
}
ClientFilterParameter...
namespace NSCommon
{
public class ClientFilterParameter
{
public ClientFilterParameter(ucClientFilter1ViewModel myFilter)
{
FilterLocation = myFilter.FilterLocation;
WhereSearch1 = myFilter.WhereSearch1;
WhereSearch2 = myFilter.WhereSearch2;
}
private string filterLocation;
private string whereSearch1;
private string whereSearch2;
public string FilterLocation
{
get { return filterLocation; }
set { filterLocation = value; }
}
public string WhereSearch1
{
get { return whereSearch1; }
set { whereSearch1 = value; }
}
public string WhereSearch2
{
get { return whereSearch2; }
set { whereSearch2 = value; }
}
}
}
I have created a custom binding for the FocusChange event on an EditText using MvvmCross. I can get the event bound and firing, but I can't figure out how to pass the event args. My Custom Binding is this
using Android.Views;
using Android.Widget;
using Cirrious.MvvmCross.Binding;
using Cirrious.MvvmCross.Binding.Droid.Target;
using Cirrious.MvvmCross.Binding.Droid.Views;
using Cirrious.MvvmCross.ViewModels;
using System;
namespace MPS_Mobile_Driver.Droid.Bindings
{
public class MvxEditTextFocusChangeBinding
: MvxAndroidTargetBinding
{
private readonly EditText _editText;
private IMvxCommand _command;
public MvxEditTextFocusChangeBinding(EditText editText) : base(editText)
{
_editText = editText;
_editText.FocusChange += editTextOnFocusChange;
}
private void editTextOnFocusChange(object sender, EditText.FocusChangeEventArgs eventArgs)
{
if (_command != null)
{
_command.Execute( eventArgs );
}
}
public override void SetValue(object value)
{
_command = (IMvxCommand)value;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_editText.FocusChange -= editTextOnFocusChange;
}
base.Dispose(isDisposing);
}
public override Type TargetType
{
get { return typeof(IMvxCommand); }
}
protected override void SetValueImpl(object target, object value)
{
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
}
}
I wire it up in my ViewModel as this:
public IMvxCommand FocusChange
{
get
{
return new MvxCommand(() =>
OnFocusChange()
);
}
}
private void OnFocusChange()
{
//Do Something
}
Is there a way to do something like
public IMvxCommand FocusChange
{
get
{
return new MvxCommand((e) =>
OnFocusChange(e)
);
}
}
private void OnFocusChange(EditText.FocusChangeEventArgs e)
{
//Do Something
}
What I tried to do there doesn't work, but I was hoping that there was something similar that might work. I am able to pass the eventargs when the command fires in the custom binding with this line
_command.Execute( eventArgs );
I just can't figure a way to catch them in the ViewModel. Can anyone help me with this?
Jim
After trying many different arrangements, I found that the correct syntax to wire up your MvxCommand is
public IMvxCommand FocusChange
{
get
{
return new MvxCommand<EditText.FocusChangeEventArgs>(e => OnFocusChange(e));
}
}
private void OnFocusChange(EditText.FocusChangeEventArgs e)
{
if (!e.HasFocus)
{
//Do Something
}
}
Hope this helps!