In the MvvmCross N=26 tutorial, dynamic fragments are loaded into a frame via button click event in the View (code snippet below). However, I'm trying to figure out how handle the click event in the ViewModel and not in the View. After the button is clicked, how do I know the button was clicked and in the View, load the correct fragment in the frame?
For instance, I may have 10 fragments and one frame in the FirstView xml. I want to be able to load any of those 10 fragments in that frame based on a property of a object referenced in the FirstViewModel. Can I check that property in the View and load the fragment that I want from the 10 fragments available? (i.e. remove the but1.Click event in the View and still run the transaction based on the value of the object in the ViewModel)
but1.Click += (sender, args) =>
{
var dNew = new DubFrag()
{
ViewModel = ((SecondViewModel) ViewModel).Sub
};
var trans3 = SupportFragmentManager.BeginTransaction();
trans3.Replace(Resource.Id.subframe1, dNew);
trans3.AddToBackStack(null);
trans3.Commit();
};
The approach you suggest of mapping a vm property to which fragment to show should work, yes.
To use this, just subscribe to property changed in your view code (there are some weak reference helper classes and extension methods to assist with this)
Alternatively, this blog post - http://enginecore.blogspot.ca/2013/06/more-dynamic-android-fragments-with.html?m=1 - introduces a mini framework that allows navigating by fragments.
A similar approach is used in the Shakespeare sample in the mvvmcross-tutorials fragments sample.
It should be possible to adapt that code to your needs
Related
After several weeks of trying to improve the performance of my application I'm asking the question here:
Scenario:
A View is created when the user selects a Person out of a DataGrid. The View contains a ListView as menu and a ContentControl where custom UserControls are displayed.
My problem occurs when the DataContext of the view is set - it takes 31 (!!!) seconds to finish the InitializeComponent-Method when set in the constructor (see image below).
However, if I don't set the DataContext to the ViewModel, as shown in the image above, the method is finished in the blink of an eye.
I already tried to optimize my XAML and paid attention to the performance of every control but it did not help.
My class-structur of the important ViewModels for the View is as follows:
PersonViewModel : ViewModelBase (used when a normal person is openend)
InteressentViewModel : PersonViewModel
StudentViewModel : InteressentViewModel
In my view I have bindings on Properties and values of the ViewModel like Collections, simple values like Strings, and so on. For the simple values the binding is set to the Context Property loaded from EF.
My Question:
What could be the reason that setting the DataContext takes so much time?
EDIT 1:
It also takes so long when I set the DataContext after the Control is Loaded, in the "Loaded" Event.
EDIT 2:
Image of Profiler:
This is how it looks, when I set the DataContext in the Loaded event (Time is needed for AppCode).
When I set it in the Constructor, the InitializeComponent takes ~30secs and the Profiler states the time is needed for parsing (see comments). Constructor of ViewModel is finished after 0,5 secs in Debug-Mode.
EDIT 3:
this is how my constructor of the Student-ViewModel looks like:
public StudentViewModel_v2(T_StudentInVertiefung student) : base(student.T_Interessent)
{
// Simple Initialisations
// loading the context object from the DataBase to a T_StudentInVertiefung Object from the viewModel
_Student = student;
... some more code
// List initialisations
... about 20 ObservableCollections
// thesis values
AvailableThesisStates = new ObservableCollection<T_Abschlussarbeitsstatus>();
AvailableGrades = new ObservableCollection<T_Gesamtpraedikat>();
AvailableLecturers = new ObservableCollection<T_Vortragender>();
// set the type of person
Typ = PersonTyp.STUDENT;
// Load data values
// test, if a connection is possible
if (IngeniumEducationEntities.TestConnection())
{
// available options
// loading values for ComboBox Selection
#region available options
AvailableStates = new List<T_Personenstatus>(_dbContext.
T_Personenstatus.OrderBy(s => s.Bezeichnung));
#endregion
}
// commands
... several command initialisations
}
the class itself is derived from another ViewModel where the constructor looks about the same. Calling the constructor is finished after ~0,5s in DebugMode.
The Binding in the View looks like this:
<TextBox Text="{Binding Person.Vorname, UpdateSourceTrigger=LostFocus}" />
For each type of person (normal, student, alumni, e.g.) different views are available. To bind to a column from the database, e.g. the first name, I put the Binding on the Context-Property stored in the ViewModel like in the code above.
What I forgot say is that it doesn't takes much time everytime when opening a new view. Sometimes it's finished right away and anothertime it takes ~30s.
If you moved the datacontext assignment to Loaded eventhandler and the performance hit is moving to Loaded event too, then you have a long running data access in your properties which let the binding process slow down.
Try to load the data from database asynchronously before/in parallel and change your properties (getter) to present only already loaded data, e.g. stored/cached data in a backing field of your view model.
Check your data access to the database too. Maybe you can improve the database access too by adding proper indexes on the involved tables.
I have a question about the Navigation Service introduced in MvvmCross 5.
In Version 4:
I navigate with ShowViewModel<ViewModel>() to a Fragment
then Init method of the ViewModel is called
after that the OnCreateView method of the Fragment is called
There I can manipulate the view based on ViewModel data (for example add specific elements to the view).
In Version 5:
I navigate with await NavigationService.Navigate<ViewModel>()
the OnCreateView of the Fragment is called first
after that the Initialize method from the ViewModel.
This ends in no ViewModel data while creating the Fragment view.
Is this a bug or a feature of async navigation?
If that is so wanted, is there a better way to manipulate the Fragment view based on ViewModel data?
Is this a bug or a feature of async navigation?
It was by design, but has since (v5.0.4) been revised, see below of flow changes.
If that is so wanted, is there a better way to manipulate the Fragment
view based on ViewModel data?
Using v5.0.4+ should yield the desired behaviour you are expecting. Where the navigation service is awaited on Initialize() of your ViewModel to complete before starting the views life cycle events.
MvvmCross v5.0.0 - v5.0.3
The behaviour you are seeing was present in MvvmCross 5.0.0-5.0.3. The flow was as follows:
ViewModel.Ctor
(Selected Navigate calls) Init(parameter) (deprecated, uses reflection, rather use type safe Initialize)
(Selected Navigate calls) ViewModel.ReloadState(savedState)
(Selected Navigate calls) ViewModel.Start()
BeforeNavigate (NavigationService Event)
*ViewDispatcher.ShowViewModel() (Triggers view life cycles)
*ViewModel.Initialize()
AfterNavigate (NavigationService Event)
BeforeClose (NavigationService Event)
ViewDispatcher.ChangePresentation()
AfterClose (NavigationService Event)
MvvmCross v5.0.4+
v5.0.4+ has improved the flow and changed the navigation order:
ViewModel.Ctor
BeforeNavigate (NavigationService Event)
*ViewModel.Initialize()
Init(parameter) (deprecated, uses reflection, rather use type safe Initialize)
ViewModel.ReloadState(savedState)
ViewModel.Start()
*ViewDispatcher.ShowViewModel() (Triggers view life cycles)
AfterNavigate (NavigationService Event)
BeforeClose (NavigationService Event)
ViewDispatcher.ChangePresentation()
AfterClose (NavigationService Event)
Additional Information
You can check out the GitHub issue(#1968) logged around the navigation order. Additionally, you can check out the pull request(#1971) which updated the Initialize order for version 5.0.4.
I have problem how to implement sub-page navigation in UWP. The page is in RootFrame, which I can use on navigation. But i want to use something like this:
<Page>
<Grid>
<Frame x:Name="MyFrame"/>
</Grid>
</Page>
What I want is, use Navigate method of control MyFrame in ViewModel. I can call the method from code-behind, but I'm developing my app using MVVM. I'm not sure, if Template10 can work with sub-frames.
I appreciate any advice.
EDIT:
More details:
I have pivot control which is in page. the pivot has 2 tabs (pivotitems). The content of the pivotitem must be navigable. What I mean: I pivotitem 1, I need to have one Frame and use it for navigation in the pivotitem. My problem is, how to use or how to call the frame in pivotitem from ViewModel, especially I need to call Navigate method. Now I'm using Template10's navigation service and it's working with rootframe. I don't know, how to use it for other let's say sub-frames.
You can always do this.
var nav = Bootstrapper.NavigationServiceFactory(BackButton.Attach, ExistingContent.Exclude, this.Frame);
This will give you a navigation service for the frame in your page. You can then use session state, if you like.
Bootstapper.SessionState["MyNav"] = nav;
From here your view-model can access the service and navigate. You can repeat this for as many frames as you have. And you can then handle navigation in your view-model without consideration of "where" the frame is, just that your logic requires it to nav.
Does this make sense?
I don't know how you are going to trigger the navigation change so I'll assume it will start from a button click. I am also assuming the button's Command property is already bound to an ICommand in the viewmodel (the same concepts can be applied to different kinds of views).
All we have to do now is to make the ICommand implementation call our custom NavigationService to perform the content switch. This NavigationService class will be nothing but a simple proxy to the window global frame. Its main navigation method can be as simples as:
public void Switch()
{
var rootFrame = Window.Current.Content as Frame;
if ((rootFrame.Content as ParentPage) != null)
{
rootFrame.Navigate(typeof(ChildPage));
}
}
So you have tagged this with Template10 but it seems to be a more general question for UWP as a whole. I wonder if you have considered all of the inherent complexities with this approach - specifically related to suspension and resume. For each frame you have, you would need to save and restore navigation state, which isn't straight-forward when you have nested frames. Have you also considered how global navigation would work?
Template 10 does support the concept of multiple NavigationServices and, therefore, multiple frames, but only from the perspective of you can create them. Template10 does not inherently understand how such frames may be related to each other, so cannot perform automatic back propagation where you have something like:
FrameA[Main->Page1->Page1:Pivot1.FrameB[View1->View2->View3]]
Here we have two frames - FrameA and FrameB. FrameA has navigated from
Main to Page1. Page1 has a Pivot that hosts FrameB in PivotItem1 and
FrameB has navigated from View1 to View 2 and from View2 to View 3.
Global navigation (i.e. the shell back, etc.) would be automatically wired to FrameA, so you would need to intercept that action, and then handle you own navigation activity for FrameB.
Take a look at the BackButtonBehavior to see how it is possible to intercept the global back and then put in place your own action.
I don't know if you can do something like that..
One possible workaround is to use a Messenger that sends a message from your viewmodel to the view's code behind.. I'm not a fan of this solution though, because as I said before you have to use the page's code behind..
I'm currently building a universal app but I'm concentrating on the WP8.1 part of it right now. I'm using MVVMLight with this project.
For simplicity sake, we'll just assume that I only have 2 pages in the project.
Page1.xaml contains a list which has various items. The Page1.xaml is binded to its own ViewModel i.e. Page1ViewModel. Each item in the list represents a viewModel i.e. ItemViewModel.
When I tap on an item, I call the following code:
public RelayCommand<ItemViewModel> ItemTapCommand
{
get
{
return this._itemTapCommand ?? (this._itemTapCommand =
new RelayCommand<ItemViewModel>((msg) =>
ExecuteItempTapCommand(msg)));
}
}
When an item in the list is tapped, I call the following code:
private object ExecuteItempTapCommand(ItemViewModel selectedItemViewModel)
{
Page2ViewModel page2ViewModel =
SimpleIoc.Default.GetInstance<ItemViewModel>();
page2ViewModel.SelectedItem = selectedItemViewModel;
_navigationService.Navigate(typeof(Page2),
selectedItemViewModel);
return null;
}
As you can see I'm using my Ioc to create get an instance of my Page2ViewModel and I then set the SelectedItem to the selectedItemViewModel.
Once it is set, I navigate to Page2 which is binded to my Page2ViewModel.
What I want to know is, is the above is ok to do? I've seen plenty of examples when dealing with passing object from one page to another is done by passing an Id for example and then I request the information from Page2, but why request it again when most of the information I need is already in my SelectedItemViewModel since it represents the tapped item in my list in Page1.
If it's not correct, what is the best way to go about this using MVVMLight?
Can you provide a sample? I've seen something about Messaging but I'm not sure how this would work as if I navigate to my page2, the Page2ViewModel will only be initiated when the page is created, so how can it receive a message? The way I have it above seems to initiate the Page2ViewModel and my Pag2 loads, it's re-using it and everything bind correctly but I'm not sure this is the correct way to go about it.
Any help would be appreciated.
Thanks.
In your Page2ViewModel, why not use
protected override void OnNavigatedTo(NavigationEventArgs e)
{
Page2SelectedItem = e.Parameter as ItemViewModel;
base.OnNavigatedTo(e);
}
It looks like you are packing that data in with your _navigationService.Navigate call already.
With that set up, what happens if you just change to:
private object ExecuteItempTapCommand(ItemViewModel selectedItemViewModel)
{
_navigationService.Navigate(typeof(Page2), selectedItemViewModel);
return null;
}
You can use the ViewModel to get it if you do some work before that.
Read this blog post by Marco Minerva called Calling ViewModel methods in response to Page navigation events using MVVM Light in WinRT
which explains how to react to OnNavigatedTo and OnNavigatedFrom in the ViewModel.
It's a very cool solution.
I want to serialize wpf user control xaml and codebehind. XamlWriter.Save() only serialize's xaml so what can I do in this situation? My situtation is I have a usercontrol which include custom methods, subscribed events (ex: button click) When I deserialize usercontrol (for create usercontrol at runtime) I want to run that events and methods. Sorry my english isn't very good.
Just an idea, you can use MEF to built up a plugin structure for your user control. You want to create a user control on the fly but the event handler still should be hardcoded somewhere else in your project; with the plugin structure, you can just collect the plugin and reuse it; the events can be handled by a command something. Maybe giving a scenario and we can figure out more detail.
Plus, ISerializable provides a way for custom binary serialization for field, not for methods or events. Here is a related question:
What is the point of the ISerializable interface?
; on the other hand, you can still try some pattern like how the web control save its view state; for example
two virtual methods:
public virtual byte[] SaveState(); // it saves the states for your custom control
public virtual void LoadState(byte[] state) // it restore your state back to the control.
The custom code should be like:
byte[] state = controlA.SaveState(); // this method saves its own state.
YourControl controlB = new YourControl();
controlB.LoadState(state); // this method load the save state from A to B itself.
For the event, every event has a handler name, you can also serialize its handler name to the state and find it back by the saved name from its naming container alike. I don't have any experience to save the method.
If you still want to save and load state including fields, events and method, maybe serialization is not the proper way you are looking for I think.