Storing a view in Xamarin.Forms MvvmCross - xamarin.forms

I have an app with four main pages, switched through a tab bar (no "back" button).
One page has a lot of content (ScrollView) and takes quite a few seconds until it's rendered. I handle that by showing a "loading" overlay while the work is done. But for that specific page I'd like to keep the view alive, so that when the user switches to another page and comes back later, the page is ready without loading everything again.
I'm not sure how to do that in MvvmCross, though.
I did read the documentation and from what I understood the View Presenter would be the right way to do it, since the docs say:
"Another kind of presentation changes your app can request through
hints includes clearing / modifying the BackStack, changing a root
while maintaining the existent views, … possibilities are really
endless. Once again your app is king here!"
I guess I would need to create a custom MvxPresentationHint for that, but I don't quite get it :(
How or rather where would I access and store/load the View?
I'm generally still quite unfamiliar with MvvmCross (how it works under the hood) and especially customization of Mvx classes, even though I've been using it for a while.
Any explanation and preferably code examples beyond what's written in the documentation would be extremely appreciated!

It isn't meaningful to attempt to "store" a view in MVVM. The XF view is a representation of what will be created with native (e.g. "Android" or "iOS") widgets. Creating and measuring/laying out those native widgets is what is slow. MVVM View Presenter won't speed up that logic.
Instead of "store", you need "keep alive":
For a ContentPage called MyPage, when you create it, store it in a static variable. Then re-use that variable. If you never need more than one of these, you can store it in the class itself.
Modify the "code behind", MyPage.xaml.cs:
public partial class MyPage : ContentPage
{
// Singleton Pattern.
private static MyPage _it;
public static MyPage It {
get {
if (_it == null)
_it = new MyPage();
return _it;
}
}
// "private", because calling this directly defeats the purpose. Instead, use `MyPage.It`.
private MyPage()
{
InitializeComponent();
}
}
To create it, whereever you would put:
new MyPage()
instead put this:
MyPage.It
For instance, you might do PushAsync(MyPage.It);
This will always return the SAME INSTANCE of MyPage. So once it has been created, it keeps its state.
IMPORTANT: Note that the constructor is only called ONCE. Any code that needs to be done each time the page appears, put in override .. OnAppearing() method.
LIMITATION: Views "expect" to be part of the visual hierarchy when they are manipulated. If you attempt to alter the page or its view model while it is not on the screen, you may encounter problems. Those are beyond the scope of this answer - create a new StackOverflow question with the details of any problem you encounter.

Related

How do I run code at client start (Not at page open) in Blazor

I need to run some code to get a user's username and department when they first connect to my Blazor Server Side application. I could just do this using OnInitialized() but that appears to only work on the one page in which it was placed. Users will likely be sent separate links to different pages though and I don't want to have to place this code on every page. I discovered that I can place code in my main layout and it will run no matter what page I start on but it runs on every page change and it doesn't allow me to run things asynchronously so that's not ideal. I'm looking for something like a Global.asax but in Blazor if that makes sense.
Edit: Turns out I can run things asynchronously in my layout! I just needed to create a code block like any other razor page. Makes sense. Though It's still weird that we have to put this type of code in the layout. It just doesn't feel right.
This is what I do:
Create a state object (class) that can be injected where needed. This is somewhat like session, but can also have global events. See here.
Add it to IoC in Startup.cs. Background info here
services.AddScoped<MyState>();
Initialize it in MainLayout.razor or elsewhere:
if (MyState.User == null)
{
MyState.User = authService.User;
}
Instantiate in pages/components as needed:
[Inject]
public MyState myState { get; set; }
...
myObj.CreatedBy = myState.User.UserName;

Issues with wrapping PrismForms NavigationService

In PrismForms we got the problem, that the NavigationStack is empty after navigating to a new page. That means after using the hardware back-button on the SecondPage, the app is closed. Although the back-arrow in the header on Android isnt shown. If looking closely you can see the back-arrow for a short moment after the page is switched. I guess thats before the NavigationStack gets cleared.
To the first page we navigate with the following command in OnInitialized() in our App.xaml.cs which derives from PrismApplication.
NavigationService.NavigateAsync("NavigationPage/StartPage");
(If only Navigating to „StartPage“ here, the Stack doesnt get cleared.)
That has do to with PageNavigationService.ProcessNavigationForNavigationPage(...) calling
bool clearNavStack = GetClearNavigationPageNavigationStack(currentPage); and PageNavigationService.ProcessNavigationForContentPage(...) not.
From the StartPage to the next we navigate with NavigateAsync("SecondPage")“. Here the described behaviour appears.
For navigation we use a class which wraps the Prism NavigationService. We hold him as a property and get him via Unity in our constructor:
this.PrismNavigation = prismNavigation ?? throw new ArgumentNullException(nameof(prismNavigation));
The methods „NavigateAsync“ and „GoBackAsync“, etc. we just pass through.
This way we want to seperate our ViewModel-Project from references to XamarinForms to later be able to use the same ViewModels for for example a WPF-GUI.
Why is the stack beeing cleared by our own NavigationService? If we register the original Prism NavigationService in App.xaml.cs instead, navigating back works as expected again. We found the point in the framework and could avoid the clearing with a drity hack, but that’s against the navigation-logic implemented in PrismForms, but we don’t understand how to do it the correct way.
Every help appreciated!
We edited a few things to get it working after finding some interesting information by Brian Lagunas in the forlast-post here:
https://github.com/PrismLibrary/Prism/issues/591
Although the topic was about something else, it led to improvements for overwriting the Navigation Service.
Remember that in your viewModels the Navigation Service must be named "navigationService" by convention. Also we switched from just holding the Prism Navigation Service as a parameter to deriving from it as suggested in the link above.
public class MyNavigationService : UnityPageNavigationService

How navigate to next page using AJAX in MVC4?

I don't have so much experience using AJAX in a MVC application, in fact is my first facing. Please check the below image and note the rectangles.
The image is just an example that I took from internet.
The biggest rectangle is a partial view in my application and I have to render it when the user press Continue or Continuar button. The application should replace the current view for another without refresh the page.
This is the code which I'm testing, note first that I'm passing the first element of a list, but when the user press the button, render the view with the next element index = 2.
public ActionResult DoTest()
{
if (!Request.IsAjaxRequest())
{ }
List<Worksheet> worksheets = new List<Worksheet>()
{
new Worksheet("Hoja 1", ...),
new Worksheet("Hoja 2", ...)
};
return View(worksheets[0]);
}
Can orient me a little bit to know how to implement this feature? I just know that I need to use Ajax.
Have a look through the tutorials and examples here. There's plenty of other material around on the web with information on this subject.
There are many different ways you can achieve this. One way would be to write a custom paging Helper (HtmlHelper) that accepts new content upon the post event. You can view all about Helpers here : Custom HTML Helpers
Another way could be to use partial page rendering to achieve the partial page update upon post event.
If I was you I would combine a partial view with a jquery function to update the content. You can view some help on that here: Change dive content with Jquery

returnedObject property

I'm programming in Flex Builder Burrito for an mobile application.
I'm trying to get a variable from navigator.PopView()
and i found the following site: adobe View and ViewNavigator
On that page is written that you can get to an returnedObject:
The ViewNavigator will save this object internally, and the new view can access it from with the navigator.returnedObject property.
The problem is when I want to acces the returnedObject flash builder doesn't seem to find that even the package isn't found.
I've found my problem on an other site.
There it's plain simple explained.
Do the override public function createReturnObject():Object.
On the page where it needs to be send back.
Then on the popped view, you can acces it by returnedObject.
On same page next lines are
The property is a ViewReturnObject
which contains the object that was
returned
and the context in which the removed view was pushed (See Setting
the View Context).
ViewNavigator.poppedViewReturnedObject
is guaranteed to be set by the time
the new view
receives the add event and will be destroyed after the view receives its
viewActivate
event.
and also a NOTE
Note, the return object is only stored when a view is popped of
the navigation stack
or replaced through the use of the pop and replace navigation
operations (e.g.,
replaceView, popView, etc...). It will be cleared after the new view
receives its
ViewNavigatorEvent.VIEW_ACTIVATE event.
I think should try function to get popuped view poppedViewReturnedObject of ViewNavigator
Its description is also on same page
public function get poppedViewReturnedObject():ViewReturnObject
Hopes that help

MVVM Light + Blend designer view error: Cannot find resource named 'Locator'.

The application runs fine but i could not see my design in the designer view.
It says Cannot find resource named 'Locator'. Obviously, i did not change anything in the code, i just did the data binding using the data binding dialog...
anyone facing the same problem?
There are two known occurrences where this can happen.
If you change to Blend before you built the application, the DLLs are not available yet and this error can be seen. Building the application solves the issue.
There is a bug in Expression Blend where, if you are placing a user control in another user control (or Window in WPF), and the inner user control uses a global resource, the global resource cannot be found. In that case you will get the error too.
Unfortunately I do not have a workaround for the second point, as it is a Blend bug. I hope we will see a resolution for that soon, but it seems to be still there in Blend 4.
What you can do is
Ignore the error when working on the outer user control. When you work on the inner user control, you should see the design time data fine (not very satisfying I know).
Use the d:DataContext to set the design time data context in Blend temporarily.
Hopefully this helps,
Laurent
I've come up with a reasonably acceptable workaround to this problem since it doesn't appear to have been fixed in Blend 4:
In the constructor for your XAML UserControl just add the resources it needs, provided you're in design mode within Blend. This may be just the Locator, or also Styles and Converters as appropriate.
public partial class OrdersControl : UserControl
{
public OrdersControl()
{
// MUST do this BEFORE InitializeComponent()
if (DesignerProperties.GetIsInDesignMode(this))
{
if (AppDomain.CurrentDomain.BaseDirectory.Contains("Blend 4"))
{
// load styles resources
ResourceDictionary rd = new ResourceDictionary();
rd.Source = new Uri(System.IO.Path.Combine(Environment.CurrentDirectory, "Resources/Styles.xaml"), UriKind.Absolute);
Resources.MergedDictionaries.Add(rd);
// load any other resources this control needs such as Converters
Resources.Add("booleanNOTConverter", new BooleanNOTConverter());
}
}
// initialize component
this.InitializeComponent();
}
There may be some edge cases, but its working OK for me in the simple cases where before I'd get a big red error symbol. I'd LOVE to see suggestions on how to better solve this problem, but this at least allows me to animate user controls that otherwise are appearing as errors.
You could also extract out the creation of resources to App.xaml.cs:
internal static void CreateStaticResourcesForDesigner(Control element)
{
if (AppDomain.CurrentDomain.BaseDirectory.Contains("Blend 4"))
{
// load styles resources
ResourceDictionary rd = new ResourceDictionary();
rd.Source = new Uri(System.IO.Path.Combine(Environment.CurrentDirectory, "Resources/Styles.xaml"), UriKind.Absolute);
element.Resources.MergedDictionaries.Add(rd);
// load any other resources this control needs
element.Resources.Add("booleanNOTConverter", new BooleanNOTConverter());
}
}
and then in the control do this BEFORE InitializeComponent():
// create local resources
if (DesignerProperties.GetIsInDesignMode(this))
{
App.CreateStaticResourcesForDesigner(this);
}
Note: At some point in time this stopped working for me and I ended up hardcoding the path to the Styles.xaml because I got frustrated trying to figure out which directory I was in.
rd.Source = new Uri(#"R:\TFS-PROJECTS\ProjectWPF\Resources\Styles.xaml", UriKind.Absolute);
I'm sure I could find the right path with 5 minutes work, but try this if you're at your wits end like I was!
In MyUserControl.xaml, instead of:
DataContext="{Binding Main, Source={StaticResource Locator}
use:
d:DataContext="{Binding Main, Source={StaticResource Locator}
where "d" has been previously defined as:
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
The reason and workaround explained here
http://blogs.msdn.com/b/unnir/archive/2009/03/31/blend-wpf-and-resource-references.aspx
Look at (b) part of the post.
I had a similar problem with a user control resource.
I added this in my usercontrol xaml code:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/GinaControls;component/Resources/GinaControlsColors.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
Where GinaControls is the namespace where the control class is declared and /Resources/GinaControlsColors.xaml is the project folder and xaml resource file name.
Hope this helps.
Just add this in your App.xaml.cs at the very beginning
here's my piece of code
[STATThread()]
static void main(){
App.Current.Resources.Add("Locator", new yournamespace.ViewModel.ViewModelLocator());
}
public App(){
main();
}
Make sure the Blend has opened the entire solution and NOT just the single project containing the views. I was right-clicking in Visual Studio and selecting Open In Expression Blend. To my surprize, Blend could not find the solution file, so it only opened the single project.
When I realized this, I launched Blend directly, pointed it to the solution file, and then Blend was able to find the ViewModelLocator in my view.

Resources