Frame.Navigate in LoadState - c#

I'm having trouble trying to navigate automatically between pages in my Windows 8.1 app based on a little check. It just doesn't want to navigate to another page when doing this in LoadState, as if something isn't loaded yet, but it doesn't give an error either. When I insert a delay using (for example) await Task.Delay(2000) before doing Frame.Navigate, then my app will redirect without any problem.
protected async override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
MyData oData = await getData();
if (oData != null)
{
this.Frame.Navigate(typeof(newPage), oData);
}
else
{
// do something else
}
}
Do I have to put this code in another load- or navigated-event? Or how can I make this work?

In LoadState and SaveState you should only save and restore the page state (called when suspending and reactivating the app). Do nothing else (like navigating).
Put your logic into the OnNavigatedTo method instead...

If you want to navigate from method that called when page is loads, you should place your navigation code to OnNavigatedTo(...). But do not forget to wrap your code in Dispatcher.RunAsync(...) - Frame navigation in xaml return false

I tried calling Frame.Navigate(...) from the OnNavigatedTo method but still the navigation didn't occur.
There are other answers which say use Dispatcher.RunAsync, but that feels like it's making assumptions about the threading model of Windows Phone.
Here's what I do: attach a handler to the Loaded event of the page instead, and put my "redirect" logic in there. Loaded fires after OnNavigateTo and after NavigationHelper_LoadState, but before the page has become visible.
public LaunchPadPage() {
this.InitializeComponent();
this.navigationHelper = new NavigationHelper(this);
this.navigationHelper.LoadState += this.NavigationHelper_LoadState;
this.navigationHelper.SaveState += this.NavigationHelper_SaveState;
this.Loaded += LaunchPadPage_Loaded;
this.app = (App)App.Current;
}
private void NavigationHelper_LoadState(object sender, LoadStateEventArgs e) {
// Let's show the root zone items
// NB: In case we don't have this data yet, do nothing
if (app.Hierarchy != null)
{
DefaultViewModel["Items"] = app.Hierarchy.RootItems;
}
}
private void LaunchPadPage_Loaded(object sender, RoutedEventArgs e) {
// No data? Go to the downloads page instead.
if (app.Hierarchy == null)
{
Frame.Navigate(typeof(DownloadingPage));
}
}

Related

Windows phone 8.1 universal app DataTransferManager UI not showing

In my windows phone 8.1 universal app project I am trying to make a share option.
But when I click on the button (ShareCommand) the Share UI is not showing up, I have tried this in the emulator and on a device.
The event is correctly wired up since the DataRequested event gets called, but after this event there is no Share UI showing.
Here is the code I use in my ViewModel (using prism framework).
private DataTransferManager _dataTransferManager;
private DelegateCommand _shareCommand;
// Share button
public DelegateCommand ShareCommand
{
get
{
return _shareCommand ?? (_shareCommand = new DelegateCommand(() =>
{
DataTransferManager.ShowShareUI();
}));
}
}
public override async void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewModelState)
{
base.OnNavigatedTo(navigationParameter, navigationMode, viewModelState);
// get data transfer manager and register events
_dataTransferManager = DataTransferManager.GetForCurrentView();
_dataTransferManager.DataRequested += DataTransferMangerDataRequested;
_dataTransferManager.TargetApplicationChosen += DataTransferMangerTargetApplicationChosen;
}
public override void OnNavigatedFrom(Dictionary<string, object> viewModelState, bool suspending)
{
base.OnNavigatedFrom(viewModelState, suspending);
// clean up events
_dataTransferManager.DataRequested -= DataTransferMangerDataRequested;
_dataTransferManager.TargetApplicationChosen -= DataTransferMangerTargetApplicationChosen;
}
private void DataTransferMangerTargetApplicationChosen(DataTransferManager sender, TargetApplicationChosenEventArgs args)
{
}
private void DataTransferMangerDataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
var request = args.Request;
var deferral = request.GetDeferral();
request.Data.Properties.Title = "title test";
request.Data.Properties.Description = "description test";
request.Data.SetText("test hello");
request.Data.SetUri(new Uri("https://www.google.com"));
request.FailWithDisplayText("fail");
deferral.Complete();
}
I have tried setting different properties in the DataRequested event but still nothing.
Does anyone know what it could be? Do I need to set some permissions?
Edit:
Ok, weird I tried this in a new solution with only this code and it is working fine. But no idea why its not working in my current solution.
Ok I found out what was causing the problem.
I had to remove this, since this will cancel the operation. (I thought this will show if it failed for some reason and not cancel directly).
request.FailWithDisplayText("fail");

Reload Data every time I navigate to Page

I have this Windows Phone Page where I load data through the standard ViewModel scope.
public Profile()
{
InitializeComponent();
App.PersonalizedViewModel.favorites.Clear();
DataContext = App.PersonalizedViewModel;
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (!App.PersonalizedViewModel.IsDataLoaded)
{
App.PersonalizedViewModel.LoadData();
}
}
This works fine. However when I navigate to this page from some other page the data is still the same. I mean the LoadData() method should recheck updated data right? Please suggest.
EDIT:
My PersonalizedViewModelClass:
public class PersonalizationViewModel: INotifyPropertyChanged
{
public PersonalizationViewModel()
{
this.favorites = new ObservableCollection<ItemViewModel>();
this.Bar = new ObservableCollection<Bars>();
}
public ObservableCollection<ItemViewModel> favorites { get; private set; }
public ObservableCollection<Bars> Bar { get; private set; }
private string _sampleProperty = "Sample Runtime Property Value";
public string SampleProperty
{
get
{
return _sampleProperty;
}
set
{
if (value != _sampleProperty)
{
_sampleProperty = value;
NotifyPropertyChanged("SampleProperty");
}
}
}
public bool IsDataLoaded
{
get;
private set;
}
/// <summary>
/// Creates and adds a few ItemViewModel objects into the Items collection.
/// </summary>
public async void LoadData()
{
favorites.Clear();
try
{
var query = ParseObject.GetQuery("Favorite")
.WhereEqualTo("user", ParseUser.CurrentUser.Username);
IEnumerable<ParseObject> results = await query.FindAsync();
this.favorites.Clear();
foreach (ParseObject result in results)
{
string venue = result.Get<string>("venue");
string address = result.Get<string>("address");
string likes = result.Get<string>("likes");
string price = result.Get<string>("price");
string contact = result.Get<string>("contact");
this.favorites.Add(new ItemViewModel { LineOne=venue, LineTwo=address, LineThree=likes, Rating="", Hours="", Contact=contact, Price=price, Latitude="", Longitude="" });
}
if (favorites.Count == 0)
{
// emailPanorama.DefaultItem = emailPanorama.Items[1];
MessageBox.Show("You do not have any saved cafes. Long press a cafe in main menu to save it.");
}
}
catch (Exception exc)
{
MessageBox.Show("Data could not be fetched!", "Error", MessageBoxButton.OK);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Implementation of PersonalizedViewModel:
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
await App.PersonalizedViewModel.LoadData();
user_tb.Text = ParseUser.CurrentUser.Username;
if (NavigationContext.QueryString.ContainsKey("item"))
{
var index = NavigationContext.QueryString["item"];
var indexParsed = int.Parse(index);
mypivot.SelectedIndex = indexParsed;
}
if (NavigationService.BackStack.Any())
{
var length = NavigationService.BackStack.Count() - 1;
var i = 0;
while (i < length)
{
NavigationService.RemoveBackEntry();
i++;
}
}
}
I don't see the problem, however, I think you need to narrow in on the problem.
First off, you are calling LoadData from 2 places. 1 from MainPage_Load and 1 from OnNavigatedTo. In MainPage_Load it is conditional and in OnNavigatedTo it is always being called. I suggest that you get to a single path through the code instead of 2 so that you don't get different experiences. I personally recommend (without knowing all the details) that you call load data from OnNavigatedTo instead of MainPage_Load. If you want to do it conditionally that is fine but if you are loading the data from memory, it really is unnecessary as you won't improve performance anymore than a few milliseconds. Also, if you are not loading from memory, you may not want to load it conditionally because the underlying data may have changed. In either case, the choice to load data or not should be moved out of the view and into the data layer (but that is for another post).
Once you have a single path chosen (i.e. calling LoadData from MainPage_Load or OnNavigatedTo) you should use your debugger. Put a break point in LoadData method and if it is being called appropriately, then your problem is more specific than your posted question. Here are some questions to think about (you may want to start from the last question and work your way backward)
Questions:
Is LoadData being called appropriately?
Does ParseObject have the correct data?
Is the ParseUser...UserName set properly?
Is the foreach being executed the proper # of times (i.e. does the result of your query have the right # of items?)
Couple Code Tips completely unrelated to this problem:
Single Path through code. Don't call LoadData from more than one place.
Don't call favorites.clear() twice in the same method. (it is called twice in LoadData)
Consistent naming. favorites is lowercase but Bar is upper case.
User proper data types. On your ItemViewModel you have Hours, Latitude, and Longitude. You have them as strings. These clearly are not strings. Also, you should not set them to empty. Empty means they have been set to a value. Emtpy is a valid value. Null means not set. To keep your objects clean and accurate you want to be accurate in how you set things and then deal appropriately with the impact. If you really really want them to be initialized to empty strings, then at least do it in the constructor of ItemViewModel so that every caller doesn't have to know how to initialize every property. I guarantee this is leading to buggy code if you continue using this practice.
Please take the comments as constructive criticism not criticism. I know many people don't like to hear these things but the teams I lead write bugs until they start following these types of guidelines.
Good luck,
Tom
Instead of defining this
App.PersonalizedViewModel.favorites.Clear();
DataContext = App.PersonalizedViewModel;
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
into constructor i.e. Profile I would suggest remove this code from Constructor and add it into your OnNavigatedTo. so the data will load after navigation
Your OnNavigatedTo Method looks like follows
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
App.PersonalizedViewModel.favorites.Clear();
DataContext = App.PersonalizedViewModel;
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
Might be your problem will solve.
Edit
Try this query
var results = (from find in ParseObject.GetQuery("Favorite").WhereEqualTo("user", ParseUser.CurrentUser.Username) select find);
Tried this:
var query = from favorite in ParseObject.GetQuery("Favorite")
where favorite.Get<string>("user") == ParseUser.CurrentUser.Username
select favorite;
IEnumerable<ParseObject> results = await query.FindAsync();
I had a similar Problem.All u want to do here is generate a new instance of the Page.U can do this in two Ways.
One Way is by forcing a GUID along with Page Navigation URI that will create a New Instance of the Page and your Load Data() will work.
NavigationService.Navigate(new Uri(String.Format("/MainPage.xaml?item={0}", Guid.NewGuid().ToString()), UriKind.RelativeOrAbsolute));
The Second Way to implement that Part of your Page in a User Control .Like create a User Control for Load Data() and put it in constructor.It will generate a new Instance everytime you load the Page.
If the problem persists in the front end,you can try this.
1.have you mentioned the below attribute in your xaml page?
<UserControl Loaded="MainPage_Loaded">
So that every time the page loads the data will get loaded on to the page.
2.The data must exist, if you have no problem in the code behind as it is a WPF application and not a web page.
Hope you find it useful.
Two changes required..
Remove the this.Loaded from OnNavigatedTo. That may not be required.
Second move the LoadData to OnNavigatedTo method
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
App.PersonalizedViewModel.favorites.Clear();
DataContext = App.PersonalizedViewModel;
// this.Loaded += new RoutedEventHandler(MainPage_Loaded);
if (!App.PersonalizedViewModel.IsDataLoaded)
{
App.PersonalizedViewModel.LoadData();
}
}
For the purpose of debugging, you can remove the line if (!App.PersonalizedViewModel.IsDataLoaded) and try.

Difference between LoadState and navigationHelper_LoadState

I am new to windows store app development. Currently I am looking into passing and receiving parameter between xamls, using c#.
Can someone help explain difference between LoadState() and navigationHelper_LoadState() with some examples? Which should I go for receiving parameter?
So, NavigationHelper.LoadState requires two things:
OnNavigatedTo Invoked when this page is about to be displayed in a Frame.
NavigationMode.New Navigation is to a new instance of a page (not forward or back)
MSDN says:
In addition to providing the implementations described earlier, NavigationHelper also needs to be called from the OnNavigatedTo() and OnNavigatedFrom() event handlers that are implemented on each page. When these events occur, NavigationHelper calls a page-specific implementation of LoadState() and SaveState(). You can customize the implementation of these functions on each page. They should be used in place of OnNavigatedTo() and OnNavigatedFrom() respectively.
The raw code is:
public void OnNavigatedTo(NavigationEventArgs e)
{
var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
this._pageKey = "Page-" + this.Frame.BackStackDepth;
if (e.NavigationMode == NavigationMode.New)
{
var nextPageKey = this._pageKey;
int nextPageIndex = this.Frame.BackStackDepth;
while (frameState.Remove(nextPageKey))
{
nextPageIndex++;
nextPageKey = "Page-" + nextPageIndex;
}
if (this.LoadState != null)
{
this.LoadState(this, new LoadStateEventArgs(e.Parameter, null));
}
}
else
{
if (this.LoadState != null)
{
this.LoadState(this, new LoadStateEventArgs(e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey]));
}
}
}
For the sake of your question, there is no LoadState() override unless you define your own like this blog. He simply does this:
private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
LoadState(e);
}
private void navigationHelper_SaveState(object sender, SaveStateEventArgs e)
{
SaveState(e);
}
protected virtual void LoadState(LoadStateEventArgs e) { }
protected virtual void SaveState(SaveStateEventArgs e) { }
See, they are identical. Nothing different between them except the execution pipeline which might impact timing a little, but not likely. In the end, no real difference. People who need to use one over the other... they have to be mistaken, attributing a cause where something else is the influence.
Best of luck.
There are a lot of example online. You might be better off going through those and then coming back here for more specific questions.
http://marcominerva.wordpress.com/2013/10/10/a-base-page-class-for-windows-8-1-store-apps-with-c-and-xaml/

Windows Phone 8 Long List Selector - scroll to bottom after data loaded async

I'm creating an app similar to the stock Messaging. But i'm very new to wp8 development and also to c# and .net
I'm using the Long List Selector to display messages. Messages are loaded on NavigatedTo event of the page. the handler is async as it is loading the data from a webservice, when there are 0 stored in local db, then it saves them in local database.
I would like to scroll to the last message after the data is loaded.
the page OnNavigated to
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
string contactId = "";
if (NavigationContext.QueryString.TryGetValue("contactId", out contactId))
{
await App.MessagesViewModel.LoadData();
DataContext = App.MessagesViewModel;
//scroll to last message, but it's apparently to soon
var lastMessage = App.MessagesViewModel.Messages.LastOrDefault();
llsMessages.ScrollTo(lastMessage);
}
}
but this throws an exception System.ArgumentException: The provided item doesn't exist in the collection. So i figured the list hasn't yet changed.
So i tried different events of LongListSelector that would indicate that it has already added the data from the view model. After a while of experimetnation i came up with this
private void llsMessages_SizeChanged(object sender, SizeChangedEventArgs e)
{
var lastMessage = App.MessagesViewModel.Messages.LastOrDefault();
if (lastMessage != null)
{
llsMessages.ScrollTo(lastMessage);
}
}
but this works only when the messages are loaded from the database. When loading from webservice the last message is null.
So after load i'm on the first message at top, then i navigate away from the page, then come back, the list scrolls to bottom. i would like to eliminate this, but i have no idea how.
is there any way how to accomplish this?
Maybe this will work:
private async Task DoAndScroll()
{
await App.MessagesViewModel.LoadData();
var lastMessage = App.MessagesViewModel.Messages.LastOrDefault();
llsMessages.ScrollTo(lastMessage);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
string contactId = "";
if (NavigationContext.QueryString.TryGetValue("contactId", out contactId))
{
DataContext = App.MessagesViewModel;
DoAndScroll();
}
}
Try the ItemRealized event of the longlistselector. Check whether the currently processed item in the selector is the last item of your datacontext and if it is so, then scroll to that item. Below code worked for me.
private void longlist_ItemRealized(object sender, ItemRealizationEventArgs e)
{
Search.BindSearch search = e.Container.Content as Search.BindSearch;
if (search != null)
{
int offset = 0;
if (OCollectionBindSearch.Count - OCollectionBindSearch.IndexOf(search) <= offset)
{
longlist.ScrollTo(search);
}
}
}

MediaElement Source cannot be set after SaveState/LoadState

(Note: All code has been severely simplified.)
Problem
MediaElement source not being set after Suspend/Resume. The CurrentState quickly changes to "Closed" after the source is set.
I am handling the MediaFailed event — it doesn't fire. I am also handling the MediaOpened event, which doesn't fire either.
Details
I have the following method which updates the MediaElement's Source. It works really well as long as the app is not trying to resume after having been Suspended.
private async void UpdateMediaElementSource(object sender, EventArgs e)
{
var videoSource = this.DefaultViewModel.CurrentSource; // a string
var file = await StorageFile.GetFileFromPathAsync(videoSource);
var videoStream = await file.OpenAsync(FileAccessMode.Read);
this.videoMediaElement.SetSource(videoStream, file.ContentType);
// The above line works many times as long as the app is not trying to Resume.
}
When the app is Suspended it calls the SaveState method:
protected async override void SaveState(Dictionary<String, Object> pageState)
{
pageState["MediaElementSource"] = this.DefaultViewModel.CurrentSource;
// I also made the videoStream global so I can dispose it — but no dice.
this.videoStream.Dispose();
this.videoStream = null;
}
When the app Resumes, it calls the LoadState method:
protected async override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
string source = string.Empty;
if (pageState != null)
{
if (pageState.ContainsKey("MediaElementSource"))
{
source = (string)pageState["MediaElementSource"];
}
}
var document = PublicationService.GetDocument(this.currentDocumentIdNumber);
this.DefaultViewModel = new DocumentViewModel(document);
this.DefaultViewModel.CurrentMarkerSourceChanged += UpdateMediaElementSource;
if (!string.IsNullOrEmpty(source))
{
// This causes the UpdateMediaElementSource() method to run.
this.DefaultViewModel.CurrentSource = source;
}
}
I appreciate any help on this issue. Please let me know if you need more details.
So, it turns out that the mediaElement's Source was being set before it was added to the visual tree.
Usually, this is not an issue when doing this:
mediaElement.Source = whatever;
but it IS an issue when you do this:
mediaElement.SetSource(stream, MimeType);
Conclusion
Make sure that your MediaElement is part of the VisualTree when you call SetSource(...).
A simple way to get my above code to work is by adding a global bool that is set to true once the mediaElement.Loaded event has fired. Then, inside the code that calls SetSource(), wrap that in an if(_mediaElementLoaded) block.

Categories