I have an issue with the tombstoning mechanism of Caliburn.Micro.
For example, I have three Views/ViewModels:
MainPageViewModel: displays each item in a list
DisplayPageViewModel: displays detailed item information
MaintainPageViewModel: add or edit an item;
I navigate through MainPageVM, DisplayPageVM, and MaintainPageVM. Then, in MaintainPageVM I navigate back to DisplayPageVM via navigationService.GoBack();. In DisplayPageVM, I pin the item to start and I think at this time the MaintainPageVM gets tombstoned although it is not active anymore.
After that, I navigate back to MainPageVM and then navigate forward to MaintainPageVM to create a new item. A new instance of MaintainPageVM is created but the values from the instance before are restored. This does only occurre if I leave the app by pinning an item to start in the DisplayPageVM (tombstone).
This is my StorageHandler for the MaintainPageViewModel:
public class MaintainPageStorage : StorageHandler<MaintainPageViewModel>
{
public override void Configure()
{
Property(x => x.Message).
InPhoneState().
RestoreAfterActivation();
}
}
Could it be possible that I don't close my MaintainPageVM correctly so that the StorageHandler for this ViewModel is still active although the ViewModel was deactivated?
EDIT
I checked the documentation of Caliburn.Micro:
public class MaintainPageStorage : StorageHandler<MaintainPageViewModel>
{
public override void Configure()
{
Id(x => x.Name);
Property(x => x.Duration).
InPhoneState().
RestoreAfterActivation();
Property(x => x.Message).
InPhoneState().
RestoreAfterActivation();
}
}
I have to specify an Id. In this case, the tombstoned MaintainPageVM will not be restored if I naviagte to this ViewModel again to add a new item.
In another case, the problem is stil present:
I navigate to DisplayPageVM to display an item, then navigate to MaintainPageVM to edit this item and go back without saving. In DisplayPageVM, I pin the item to start (MaintainPageVM gets tombstoned) and go to MaintainPageVm again. The tombstoned version is restored again.
So I have still the problem that the MaintainPageVM is in memory although I've already navigated back from it.
Related
I do want to ensure that in case an app is navigated to a certain page, the app is on another (in my case the previous) page after it was suspended or terminated. In my case the page is for taking photos. I do not want the user to return to this page after the app was in the background since it has no context information. The context information is on the previuos page.
How could I achieve this with Prism.StoreApps?
Background: If an app was just suspended the state of the app remains after it was resumed, hence the last active page is active again. I have no real idea how to set another page active in this case. If an app was terminated Prim.StoreApps restores the navigation state and navigates to the last active view model (hence to the last active page). I do not know either how to alter the navigation state in this case so that another page is navigated to.
In the meantime I fond a working solution myself. Might not be the best and there might be better solutions, but it works.
For resuming the app I handle the Resuming event:
private void OnResuming(object sender, object o)
{
// Check if the current root frame contains the page we do not want to
// be activated after a resume
var rootFrame = Window.Current.Content as Frame;
if (rootFrame != null && rootFrame.CurrentSourcePageType == typeof (NotToBeResumedOnPage))
{
// In case the page we don't want to be activated after a resume would be activated:
// Go back to the previous page (or optionally to another page)
this.NavigationService.GoBack();
}
}
For the page restore after termination I firstly use a property in the App class:
public bool MustPreventNavigationToPageNotToBeResumedOn { get; set; }
public App()
{
this.InitializeComponent();
// We assume that the app was restored from termination and therefore we must prevent navigation
// to the page that should not be navigated to after suspension and termination.
// In OnLaunchApplicationAsync MustPreventNavigationToPageNotToBeResumedOn is set to false since
// OnLaunchApplicationAsync is not invoked when the app was restored from termination.
this.MustPreventNavigationToPageNotToBeResumedOn = true;
this.Resuming += this.OnResuming;
}
protected override Task OnLaunchApplicationAsync(LaunchActivatedEventArgs args)
{
// If the application is launched normally we do not prevent navigation to the
// page that should not be navigated to.
this.MustPreventNavigationToPageNotToBeResumedOn = false;
this.NavigationService.Navigate("Main", null);
return Task.FromResult<object>(null);
}
In OnNavigatedTo of the page I do not want to be activated on a resume I check this property and simply navigate back if it is true (and set the property to false to allow subsequent navigation):
public override void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode,
Dictionary<string, object> viewModelState)
{
if (((App)Application.Current).MustPreventNavigationToPageNotToBeResumedOn)
{
// If must prevent navigation to this page (that should not be navigated to after
// suspension and termination) we reset the marker and just go back.
((App)Application.Current).MustPreventNavigationToPageNotToBeResumedOn = false;
this.navigationService.GoBack();
}
else
{
base.OnNavigatedTo(navigationParameter, navigationMode, viewModelState);
}
}
I am new to automation and want to create an automation test which can do following:
Open one tab --- click and get some info from that tab
Switch to another tab --- click and get some info from this tab now.
Compare the infos.
We use Page Object Model to get info from one page. However the moment, I switch to another tab -- it switches the tab successfully but does not locate any element on it.
Any idea ?
Questions I would ask is,
Is the element locator correct?
Is this a unique element locator?
Is this a synchronization issue? Are you waiting enough for the page to load before finding the element?
Is this problem particular to a browser? Is it consistent across?
Also make sure you pass on the driver object from one page object to the other. Like,
public class PageOne {
public PageOne(WebDriver driver) {
//do something in constructor
}
public void someMethodInPage1() {
driver.findElement(By.id("button1")).click();
PageTwo pageTwo = new PageTwo(driver);
pageTwo.someMethodInPage2();
}
}
public class PageTwo {
private WebDriver driver;
public PageTwo(WebDriver driver) {
//do something in constructor
this.driver = driver;
}
public void someMethodInPage2() {
driver.findElement(By.id("button2")).click();
}
}
I am developing a windows phone 8 app in which i have to use listPicker control. I need to save the selectedIndex from selected item in listPicker, in isolatedStorageSettings to be able to use it when the app opens. I want the saved index to be the selected index in my listPicker when the apps runs again. I have tried to do this with the onnavigatedto and onnavigatedfrom methods in the page in which i have the control. The problem is when i change se selected item and return back from full mode, the selected item does not change. I had searched this problem heare again and i didn't found the solution yet. How can i solve it?
Sorry for my English
I followed this settings_sample for the general setup by modifying the ListBox example. I ran into several problems trying to use a ListPicker with isolated storage like this.
http://msdn.microsoft.com/en-us/library/windowsphone/develop/ff769510(v=vs.105).aspx
I removed the databindings for the ListPicker, set the SelectedIndex after initializing, and stored the SelectedIndex in isolated storage on SelectionChanged after the first occurrence of loading the page. It's a roundabout solution, but my searches came up empty.
public List<string> daysOfWeek = new List<string>() { "Sunday", "Monday", "etc" };
public int listPickerCounter = 0;
public Settings()
{
InitializeComponent();
BuildLocalizedApplicationBar();
// Fill listPicker with string items
this.listPicker.ItemsSource = daysOfWeek;
// Set SelectedIndex = IsolatedStorage Variable
if (IsolatedStorageSettings.ApplicationSettings.Contains("ListPickerSetting"))
{
this.listPicker.SelectedIndex = (int)IsolatedStorageSettings.ApplicationSettings["ListPickerSetting"];
}
}
On SelectionChanged update the isolated storage after the first occurrence of loading page.
private void listPicker_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (listPickerCounter > 0 && IsolatedStorageSettings.ApplicationSettings.Contains("ListPickerSetting"))
{
IsolatedStorageSettings.ApplicationSettings["ListPickerSetting"] = (int)this.listPicker.SelectedIndex;
}
listPickerCounter++;
}
Edit: forgot to add another reference that really helped understand isolated storage.
http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj714090(v=vs.105).aspx
In the N+1 video #34 (Progress), there was an example of using CreateBindingSet() for the Android version, which is not typical. But the narrator also mentioned briefly that the same can be done on the Windows platform.
As much as I tried, however, I am unable to get a View's property to be bound to its ModelView on the Windows Phone. I always get a NullReferenceException.
The closest I came was the code below, including suggestions from ReSharper. Here's my FirstView.xaml.cs:
using Cirrious.MvvmCross.Binding.BindingContext;
using Whatever.ViewModels;
namespace Whatever {
// inheriting from IMvxBindingContextOwner was suggested by ReSharper also
public partial class FirstView : BaseView, IMvxBindingContextOwner {
public class MyBindableMediaElement
{
private string _theMediaSource = "whatever";
public string TheMediaSource
{
get
{
return _theMediaSource;
}
set
{
_theMediaSource = value;
}
}
}
public FirstView()
{
InitializeComponent();
_mediaElement = new MyBindableMediaElement(this.theMediaElement);
var set = this.CreateBindingSet<FirstView, FirstViewModel>();
// the corresponding view model has a .SongToPlay property with get/set defined
set.Bind(_mediaElement).For(v => v.TheMediaSource).To(vm => vm.SongToPlay);
set.Apply();
}
public IMvxBindingContext BindingContext { get; set; } // this was suggested by ReSharper
}
I get a NullReferenceException in MvxBaseFluentBindingDescription.cs as soon as the view is created. The exact location is below:
protected static string TargetPropertyName(Expression<Func<TTarget, object>> targetPropertyPath)
{
var parser = MvxBindingSingletonCache.Instance.PropertyExpressionParser; // <----- exception here**
var targetPropertyName = parser.Parse(targetPropertyPath).Print();
return targetPropertyName;
}
I have not seen a working example of creating a binding set on a Windows Phone emulator. Has anyone gotten this to work? Thanks.
I can confirm that the narrator said that remark a little too flippantly without actually thinking about how he might do it...
However, with a little effort, you definitely can get the CreateBindingSet to work in Windows if you want to.
Before you start, do consider some alternatives - in particular, I suspect most people will use either Windows DependencyProperty binding or some hand-crafted code-behind with a PropertyChanged event subscription.
If you do want to add CreateBindingSet code to a Windows project then:
Add the Binding and BindingEx assemblies to your Ui project - the easiest way to do this is using nuget to add the BindingEx package.
In your Setup class, override InitializeLastChance and use this opportunity to create a MvxWindowsBindingBuilder instance and to call DoRegistration on that builder. Both these first two steps are covered in the n=35 Tibet binding video - and it's this second step that will initialise the binding framework and help you get past your current 'NullReferenceException' (for the code, see BindMe.Store/Setup.cs)
In your view, you'll need to implement the IMvxBindingContextOwner interface and you'll need to ensure the binding context gets created. You should be able to do this as simply as BindingContext = new MvxBindingContext();
In your view, you'll need to make sure the binding context is given the same DataContext (view model) as the windows DataContext. For a Phone Page, the easiest way to do this is probably just to add BindingContext.DataContext = this.ViewModel; to the end of your phone page's OnNavigatedTo method. Both steps 3 and 4 could go in your BaseView if you intend to use Mvx Binding in other classes too.
With this done, you should be able to use the CreateBindingSet code - although do make sure that all binding is done after the new MvxBindingContext() has been created.
I've not got a windows machine with me right now so I'm afraid this answer code comes untested - please do post again if it does or doesn't work.
I can confirm it works almost perfectly; the only problem is, there are no defaults register, so one has to do the full binding like:
set.Bind(PageText).For(c => c.Text).To(vm => vm.Contents.PageText).OneTime();
to fix this, instead of registering MvxWindowsBindingBuilder, I am registering the following class. Note: I have just created this class, and needs testing.
public class UpdatedMvxWindowsBindingBuilder : MvxWindowsBindingBuilder
{
protected override void FillDefaultBindingNames(IMvxBindingNameRegistry registry)
{
base.FillDefaultBindingNames(registry);
registry.AddOrOverwrite(typeof(Button), "Command");
registry.AddOrOverwrite(typeof(HyperlinkButton), "Command");
//registry.AddOrOverwrite(typeof(UIBarButtonItem), "Clicked");
//registry.AddOrOverwrite(typeof(UISearchBar), "Text");
//registry.AddOrOverwrite(typeof(UITextField), "Text");
registry.AddOrOverwrite(typeof(TextBlock), "Text");
//registry.AddOrOverwrite(typeof(UILabel), "Text");
//registry.AddOrOverwrite(typeof(MvxCollectionViewSource), "ItemsSource");
//registry.AddOrOverwrite(typeof(MvxTableViewSource), "ItemsSource");
//registry.AddOrOverwrite(typeof(MvxImageView), "ImageUrl");
//registry.AddOrOverwrite(typeof(UIImageView), "Image");
//registry.AddOrOverwrite(typeof(UIDatePicker), "Date");
//registry.AddOrOverwrite(typeof(UISlider), "Value");
//registry.AddOrOverwrite(typeof(UISwitch), "On");
//registry.AddOrOverwrite(typeof(UIProgressView), "Progress");
//registry.AddOrOverwrite(typeof(IMvxImageHelper<UIImage>), "ImageUrl");
//registry.AddOrOverwrite(typeof(MvxImageViewLoader), "ImageUrl");
//if (_fillBindingNamesAction != null)
// _fillBindingNamesAction(registry);
}
}
This is a skeleton from Touch binding, and so far I have only updated three controls to test out (Button, HyperButton and TextBlock)
How do I modify the SuspensionManager object in a Metro App, to SaveState for the MainPage only and discard State and Navigation for any child pages?
E.g. I have a MainPage that allows you to Navigate to a ChildPage. If the Metro App is Closed or Suspended, I want the ChildPage to override the MainPage state values.
Now, the next time the user opens the app, the MainPage should open and not the ChildPage. Also the MainPage should show State that the ChildPage updated before the app was Closed or Suspended.
Any ideas on how the SuspensionManager object can be safely modified to accomplish this?
Why not just add your data from the ChildPage to the SessionState dictionatry and fill it into the MainPage when the app resumes ?
You don't even need to modify the SuspensionManager for doing that!
public BasicPage1() {
this.InitializeComponent();
Application.Current.Suspending += Current_Suspending;
Application.Current.Resuming += Current_Resuming;
}
void Current_Resuming( object sender, object e ) {
var name = SuspensionManager.SessionState["name"].ToString();
}
void Current_Suspending( object sender, Windows.ApplicationModel.SuspendingEventArgs e ) {
SuspensionManager.SessionState.Add( "name", "danielovich" );
}
Using the suggestion from Danielovich (which is NOT complete), the solution is 2 parts:
Firstly:
No longer save state in the pageState object, instead save state
using the SessionState object. Why? This is so that all Pages can share the same State information.
Secondly:
To ensure, that NavigationState is not saved so that MainPage is always the default page, we need to change the SaveFrameNavigationState in SuspensionManager as follows:
private static void SaveFrameNavigationState(Frame frame)
{
var frameState = SessionStateForFrame(frame);
frame.GetNavigationState();
frameState["Navigation"] = "1,1,0,15,Skycap.MainPage,12,0";
}