In version 5 of MvvmCross, there has been added an asynchronous Initialize override where you can do you heavy data loading.
public override async Task Initialize()
{
MyObject = await GetObject();
}
Is there a way to determine in the View that the Initialize has completed? Say in the View I want to set the Toolbar Title to a display a field in MyObject
MyViewModel vm;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your application here
this.SetContentView(Resource.Layout.MyView);
var toolbar = (Toolbar)FindViewById(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
vm = (MyViewModel)this.ViewModel;
SupportActionBar.Title = vm.MyObject.Name;
}
On the line that sets the SupportActionBar.Title, is there a way to know for sure whether the Initialize task has completed and if not, get notified when it does?
UPDATE:
I tried set two correct answers because #nmilcoff answered my actual question and #Trevor Balcom showed me a better way to do what I wanted.
Yes, you can subscribe to InitializeTask's property changes.
Something like this will work:
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// your code
ViewModel.PropertyChanged += MyViewModel_PropertyChanged;
}
private void MyViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == nameof(ViewModel.InitializeTask) && ViewModel.InitializeTask != null)
{
ViewModel.InitializeTask.PropertyChanged += ViewModel_InitializeTask_PropertyChanged;
}
}
private void ViewModel_InitializeTask_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == nameof(ViewModel.InitializeTask.IsSuccessfullyCompleted))
SupportActionBar.Title = ViewModel.MyObject.Name;
}
Of course, it could be the case that it might be easier to just listen to ViewModel.MyObject.Name property changes. But the above is a generic way to listen to InitializeTask property changes.
You can learn more about InitializeTask and MvxNotifyTask in the official documentation.
On Xamarin Forms:
I wanted to add Property Changed event login in the VM to be able to test it, so:
View.xaml.cs
protected override void OnViewModelSet()
{
base.OnViewModelSet();
var vm = this.DataContext as SearchMovieViewModel;
if (vm is null)
{
return;
}
vm.OnViewModelSet();
}
On your ViewModel:
/// <summary>
/// This method should be called in every View Code Behind when you
/// need to subscribe to InitializeTask changes.
/// </summary>
public void OnViewModelSet()
{
if (this.InitializeTask is null)
{
return;
}
this.InitializeTask.PropertyChanged += this.InitializeTask_PropertyChanged;
}
Finally on your View Model implement whatever check you need to do for MvvmCross InitializeTask, in my case I used IsCompleted Property, but you can use whichever you need:
private void InitializeTask_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(this.InitializeTask.IsCompleted))
{
// do something
}
}
DonĀ“t forget to unsubscribe, for example when the view is destroyed. You can override this method in your View Model:
public override void ViewDestroy(bool viewFinishing = true)
{
base.ViewDestroy(viewFinishing);
this.InitializeTask.PropertyChanged -= this.InitializeTask_PropertyChanged;
}
The Toolbar also supports data binding the Title property like so:
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="#style/AppTheme.PopupOverlay"
app:MvxBind="Title MyObject.Name" />
Related
What I am trying to do: pass the data from ViewModel to Activity
ViewModel.cs
public event EventHandler RecommendedScents;
private void _recommendedScents()
{
var handler = RecommendedScents;
if (handler != null)
handler(this, new System.EventArgs());
}
Activity.cs
I register the Event in view
ViewModel.RecommendedScents -= SetRecommendedScents;
ViewModel.RecommendedScents += SetRecommendedScents;
Get the Control here:
private void SetRecommendedScents(object sender, EventArgs e)
{
}
You don't need to use such trick to pass data from ViewModel to View. Just declare your View as MvxActivity<MyViewModel>
And you will have a property ViewModel in your View.
[Activity(ScreenOrientation = ScreenOrientation.Portrait)]
public class MyView : MvxActivity<MyViewModel>
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
var myValue = ViewModel.SomeProperty; // here you access your VM
}
}
If you need to send data from ViewModel to View, you need to use a a Event aggregator system like MvvmCross.Plugin.Messenger
https://www.mvvmcross.com/documentation/plugins/messenger?scroll=1524
I have Portable project in Visual Studio 2015 with a ListView that gets populated with some data via an API call through the following function refreshData:
async Task refreshData()
{
myListView.BeginRefresh();
var apiCallResult = await App.Api.myApiGetCall();
myListView.ItemsSource = apiCallResult;
myListView.EndRefresh();
}
refreshData() is called in
protected override void OnAppearing()
{
base.OnAppearing();
refreshData();
}
Everything is working fine except on Android where the refresh indicator is not stopping or disappearing on EndRefresh() when the page is initially loaded. The page is in a TabbedPage so I can go to a different tab and then return to this page and the refresh indicator properly starts and stops with completion of my API call.
Why is refresh is not stopping when the page initially loads on Android? Any help would be appreciated.
Note: This works perfectly fine when I run on iOS.
So far I've tried:
replacing myListView.BeginRefresh() with myListView.IsRefreshing = true and myListView.EndRefresh() with myListView.IsRefreshing = false
Using Device.BeginInvokeOnMainThread(() => {//update list and endRefresh}).
Using async void refreshData() instead of async Task refreshData().
Personally I can get this problem when I start ListView refreshing in the Page Contructor and stop it after the data is loaded. Sometimes (quite often) Xamarin.Forms ListView doesn't cancel refreshing animation.
I believe you faced with a quite common issue with Android SwipeRefreshLayout: it may not stop refreshing animation after setRefreshing(false) called. Native Android developers use the following approach:
swipeRefreshLayout.post(new Runnable() {
#Override
public void run() {
mSwipeRefreshLayout.setRefreshing(refreshing);
}
});
Interestingly, Xamarin.Forms uses this approach when it sets initial refreshing status (code); however, it is not enough. You need a custom renderer:
public class ExtendedListViewRenderer : ListViewRenderer
{
/// <summary>
/// The refresh layout that wraps the native ListView.
/// </summary>
private SwipeRefreshLayout _refreshLayout;
public ExtendedListViewRenderer(Android.Content.Context context) : base(context)
{
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_refreshLayout = null;
}
base.Dispose(disposing);
}
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
_refreshLayout = (SwipeRefreshLayout)Control.Parent;
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == ListView.IsRefreshingProperty.PropertyName)
{
// Do not call base method: we are handling it manually
UpdateIsRefreshing();
return;
}
base.OnElementPropertyChanged(sender, e);
}
/// <summary>
/// Updates SwipeRefreshLayout animation status depending on the IsRefreshing Element
/// property.
/// </summary>
protected void UpdateIsRefreshing()
{
// I'm afraid this method can be called after the ListViewRenderer is disposed
// So let's create a new reference to the SwipeRefreshLayout instance
SwipeRefreshLayout refreshLayoutInstance = _refreshLayout;
if (refreshLayoutInstance == null)
{
return;
}
bool isRefreshing = Element.IsRefreshing;
refreshLayoutInstance.Post(() =>
{
refreshLayoutInstance.Refreshing = isRefreshing;
});
}
}
Try this:
async void refreshData()
{
Device.BeginInvokeOnMainThread(() => {
myListView.BeginRefresh();
var apiCallResult = await App.Api.myApiGetCall();
myListView.ItemsSource = apiCallResult;
myListView.EndRefresh();
});
}
Apparently, there is no need for "Task" anymore.
If the error occurs only in Android, it's possible that it's just the way Android handles threads. It does not allow threads to change visual elements directly. Sometimes when we try to do that it throws a silent exception and the code action have no practical effect.
You need to follow MVVM Pattern
On your ViewModel you need to:
Implement INotifyPropertyChanged
Define properties like:
private bool _IsRefreshing;
public bool IsRefreshing
{
get { return _IsRefreshing; }
set { SetProperty(ref _IsRefreshing, value; }
/*So every time the property changes the view is notified*/
}
Define the method that fetch your data, in your case refreshData()
Toggle the IsRefreshing true/false when needed
On your Page you need to:
Bind the listview itemSource to a VM property with the SetPropertyValue
Bind ListView.IsRefreshing to ViewModel's IsRefreshing:
MyListView.SetBinding<MyViewModel>(ListView.IsRefreshing, vm => vm.IsRefreshing);
Here is a great article talking about INotifyPropertyChanged
I have 'BasePage' class which is base class for all other pages in my project.
In initialization, I'm adding EventHandler for 'SystemNavigationManager' for event 'BackRequest'. For some reason that line id causing 'AccessViolationException' when XAML designer is trying to render XAML of class that extends 'BasePage'
I'm not familiar with UWP, so I'll be very grateful for tips.
BasePage
public class BasePage: Page {
internal string title = "";
internal HeaderView headerView;
public BasePage() {
this.Loaded += BasePage_Loaded;
// FIXME: For some reason if this line is uncommented then xaml designer fails.
SystemNavigationManager.GetForCurrentView().BackRequested += BasePage_BackRequested;
}
private void BasePage_BackRequested(object sender, BackRequestedEventArgs e) {
bool handled = e.Handled;
this.BackRequested(ref handled);
e.Handled = handled;
}
private void BackRequested(ref bool handled) {
//Get a hold of the current frame so that we can inspect the app back stack.
if (this.Frame == null)
return;
// Check to see if this is the top-most page on the app back stack.
if (this.Frame.CanGoBack && !handled) {
// If not, set the event to handled and go back to the previous page in the app.
handled = true;
this.Frame.GoBack();
}
}
private void setupPageAnimation() {
TransitionCollection collection = new TransitionCollection();
NavigationThemeTransition theme = new NavigationThemeTransition();
var info = new ContinuumNavigationTransitionInfo();
theme.DefaultNavigationTransitionInfo = info;
collection.Add(theme);
this.Transitions = collection;
}
private void BasePage_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e) {
setupPageAnimation();
}
}
SOLUTION
Just like Ivan said, final code looks like this. Without of a trace of bug.
BasePage
public BasePage() {
this.Loaded += BasePage_Loaded;
}
protected override void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
SystemNavigationManager.GetForCurrentView().BackRequested += BasePage_BackRequested;
}
protected override void OnNavigatedFrom(NavigationEventArgs e) {
base.OnNavigatedFrom(e);
SystemNavigationManager.GetForCurrentView().BackRequested -= BasePage_BackRequested;
}
You shouldn't subscribe to back events on constructor but OnNavigatedTo and unsubscribe in OnNavigatedFrom. Even if it didn't crash it would cause a lot of problems because your back logic would be activated on all previous pages when you press the back button.
This is just a simplified example, but I'm trying to set this up so that when I open up this page in my Application, the first thing that happens is the keyboard pops up ready for the user to type in their response to an Entry field.
var namelabel = new Label { Text = "What is your name?" };
var nameentry = new Entry { Placeholder = "Type here..." };
var colorlabel = new Label { Text = "What's your fav color?" };
var colorentry = new Entry { Placeholder = "Type here..." };
Content = new StackLayout {
Spacing = 15,
Children = { namelabel, nameentry, colorlabel, colorentry }
};
How can I set the focus of the page to the first entry? And additionally, after the user has put in their first entry, how could I set this up so that the user could press a "Next" Button on the Keyboard (or something along those lines) and the app will take them to fill in the second entry?
Use the Focus method
nameentry.Focus();
If you want the focus to be set when your page appears, you should probably do this in the OnAppearing method
protected override void OnAppearing ()
{
base.OnAppearing ();
nameentry.Focus();
}
In one of my projects I did something like this. Please try the following example:
public class EntryFocusBehavior : Behavior<Entry>
{
public string NextFocusElementName { get; set; }
protected override void OnAttachedTo(Entry bindable)
{
base.OnAttachedTo(bindable);
bindable.Completed += Bindable_Completed;
}
protected override void OnDetachingFrom(Entry bindable)
{
bindable.Completed -= Bindable_Completed;
base.OnDetachingFrom(bindable);
}
private void Bindable_Completed(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(NextFocusElementName))
return;
var parent = ((Entry)sender).Parent;
while (parent != null)
{
var nextFocusElement = parent.FindByName<Entry>(NextFocusElementName);
if (nextFocusElement != null)
{
nextFocusElement.Focus();
break;
}
else
{
parent = parent.Parent;
}
}
}
}
And then XAML:
!!! Please let me know if I made a mistake in the code.
Just inside OnAppearing(), add the following code,
protected async override void OnAppearing()
{
await Task.Run(async () =>
{
await Task.Delay(100);
Device.BeginInvokeOnMainThread(async () =>
{
txtName.Focus();
});
});
}
Note: txtName is the reference name of your Entry Field.
Focus() needs to have a visible page and visible control to focus on.
The issue in my case was that is is necessary that OnAppearing has to exit before a page is shown / visible. What helped in my case is to wait for page visibility in a different thread and then set the focus in the main (UI) thread:
protected override void OnAppearing()
{
base.OnAppearing();
Task.Run(() =>
{
while (!IsVisible)
{
Debug.WriteLine("Page not visible, waiting...");
Task.Delay(50).Wait();
}
Device.BeginInvokeOnMainThread(() =>
{
bool gotFocus = Entry.Focus();
if (!gotFocus)
Debug.WriteLine("Could not set focus.");
});
});
}
So long as the element to focus on is the topmost element, this should work. I place this in the OnAppearing method.
base.OnAppearing();
Entry entry = this.FindByName<Entry>("NameOfEntryElement");
entry.Focus();
The source of this info is here:
https://forums.xamarin.com/discussion/100354/entry-focus-not-working-for-android
There is further discussion in the article about timing issues.
I know this is an old thread but this might work for someone as it worked for me.
protected override async void OnAppearing()
{
base.OnAppearing();
await Task.Delay(500);
await Task.Run(() =>
{
entryname.Focus();
});
}
It's possible that Focus or RequestFocus because your control didn't load again. You can override onAppearing but even if you use that, it may possible that didn't work because the control is not set again.
So you can use onAppearing, but for the fist apparing you may use Xamarin community toolkit. And LifeCycleEffect
<Entry x:Name="myEntry">
<Entry.Effects>
<xct:LifecycleEffect Loaded="LifeCycleEffect_Loaded" />
</Entry.Effects>
</Entry>
Here in C#
void LifeCycleEffect_Loaded(object? sender, EventArgs e)
{
if ( sender is Entry && (sender as Entry ).Name != null && (sender as Entry).Name.Equals("myEntry") )
// myEntry.Focus() or myEntry.RequestFocus() or (sender as Entry).Focus()
}
I suggest you to take notes from this link
https://learn.microsoft.com/en-us/xamarin/community-toolkit/effects/lifecycleeffect
Can anyone explain to me why this doesn't work
protected override void OnAppearing()
{
base.OnAppearing();
CustomerNameEntry.Focus();
}
But this does (adding async and Task.Delay(1))
protected override async void OnAppearing()
{
base.OnAppearing();
await Task.Delay(1);
CustomerNameEntry.Focus();
}
I'd rather not add this clutter, it seems hacky but it's the only way I've been able to get it to work (I've also tried invoking on main thread, to no avail).
The simple one did not work for me:
protected override void OnAppearing()
{
MyEntry.Focus();
}
I had to wait an unspecified amount of time, but did not want to wait longer than necessary. Also, I didn't want to pollute OnAppearing() with delay code, so implemented a helper with a Focus methdo that works:
using System.Threading.Tasks;
using Xamarin.Forms;
namespace MyApp.Xmrn.Views.Helpers
{
internal static class ViewHelper
{
// Disable the warning about a non-awaited async, as we use it as
// fire and forget and return from the method immediately.
#pragma warning disable 1998, 4014
/// <summary>
/// Entry.Focus replacement, that tries to focus a control
/// multiple times until it succeeds.
/// </summary>
/// <param name="entry">Entry control to be focused.</param>
internal static async Task Focus(Entry entry)
{
Task.Run(async () =>
{
int threshold = 20;
while (!entry.Focus() && threshold-- > 0)
await Task.Delay(50);
});
}
#pragma warning restore 1998, 4014
}
}
And then to use it
protected override async void OnAppearing()
{
base.OnAppearing();
await ViewHelper.Focus(MyEntry);
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == "Renderer")
{
myEntry.Focus();
}
}
This worked perfectly for me :D
protected override async void OnAppearing() {
base.OnAppearing();
while(!passwordInput.Focus()) { await Task.Delay(50); }
}
I'm searching for the appropiate way to handle the back button pressed event on Windows Phone 8.1 WinRT using the NavigationService available on MVVM light 5.
So far I think the best place to do it is inside the ViewModelLocator by registering the GoBack method of the NavigationService while creating it following the approach outlined in NavigationService in MVVM Light V5
This is an effective approach. However, I can't handle validation before navigating back so I was wondering if there is a more suitable way to handle this event.
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
// Register NavigationService
SimpleIoc.Default.Register(CreateNavigationService);
// Register ViewModels here
}
private INavigationService CreateNavigationService()
{
var navigationService = new NavigationService();
// Register pages here
navigationService.Configure("Details", typeof(DetailsPage));
// Handle back button
HardwareButtons.BackPressed += (sender, args) => {
navigationService.GoBack();
args.Handled = true;
};
return navigationService;
}
}
If you take a look at how Marco is enabling OnNavigatedTo and OnNavigatedFrom calls to propagate to ViewModel in the blog post
Calling ViewModel methods in response to Page navigation events using MVVM Light in WinRT
you'll notice he uses INavigable interface and Activate and Deactivate methods. You could extend that INavigable interface with AllowGoingBack method, like this:
public interface INavigable
{
void Activate(object parameter);
void Deactivate(object parameter);
bool AllowGoingBack();
}
Each page-related ViewModel can then have its own implementation of AllowGoingBack method depending on the context. Then, in the code behind of the View (which is OK, because View can know about the ViewModel) you can override OnNavigatingFrom and check if going back should be allowed:
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
var navigableViewModel = this.DataContext as INavigable;
if (navigableViewModel != null)
{
if (e.NavigationMode == NavigationMode.Back && !navigableViewModel.AllowGoBack())
{
e.Cancel = true;
}
}
}
Your ViewModel would then implement INavigable, so you would define the validation code inside AllowGoingBack(), and return true if going back is OK, and false if it's not.
Based on the answer by igrali, and following the instructions in Calling ViewModel methods in response to Page navigation events using MVVM Light in WinRT, what I did in the "BindablePage.cs" class, in the OnNavigatedTo method was to add add the following:
HardwareButtons.BackPressed += HardwareButtons_BackPressed;
and in the OnNavigatedFrom:
HardwareButtons.BackPressed -= HardwareButtons_BackPressed;
Then add the event handler:
void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)
{
var navigableViewModel = this.DataContext as INavigable;
if (navigableViewModel != null)
navigableViewModel.BackButonPressed(e);
}
Next, in the INavigable interface add
void BackButonPressed(Windows.Phone.UI.Input.BackPressedEventArgs e);
And finally, on each view model:
public void BackButonPressed(Windows.Phone.UI.Input.BackPressedEventArgs e)
{
// You can modify this code to show a confirmation dialog, etc...
e.Handled = true;
navigationService.GoBack();
}
If this is a universal app, then don't forget to surround these new pieces of code with #if WINDOWS_PHONE_APP ... #endif
I found an interesting article about your question: http://blog.falafel.com/windows-phone-and-mvvm-light-navigationservice-and-cangoback/
This is the idea:
public AboutPage()
{
this.InitializeComponent();
HardwareButtons.BackPressed += HardwareButtons_BackPressed;
}
private void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)
{
var frame = Window.Current.Content as Frame;
if (frame.CanGoBack)
{
var navigation = ServiceLocator.Current.GetInstance<INavigationService>();
navigation.GoBack();
e.Handled = true;
}
}