I'm using the NavigationContext.QueryString for my Windows Phone 8 app. For example, I set an URI identifier like ItemId in the navigation string and in the OnNavigatedTo, I parse the Id and read the Item via linq.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
try
{
int itemId = int.Parse(NavigationContext.QueryString["itemId"]);
_item = App.MainViewModel.GetItem(itemId);
DataContext = _item;
}
catch (KeyNotFoundException ex)
{
Debug.WriteLine(ex.Message);
throw;
}
}
I've found an interesting alternative and want to hear your opinion:
// in the calling page
PhoneApplicationService.Current.State["Item"] = App.MainViewModel.GetItem(123);
// in the destination page
Item item = PhoneApplicationService.Current.State["Item"] as Item;
Is this really a recommended way?
From MSDN:
The PhoneApplicationService class provides access to various aspects
of the application’s lifetime. This includes management of the
application’s idle behavior and management of the application’s state
when it becomes active or inactive. You can use it like that but the
data has to be serializable.
This link has other ways to share data, but I don't think state is the recommended way of doing it. It's more for tombstoning purpose.
Related
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.
In Windows phone 8 app, instead of always opening the application with same PhoneApplicationPage, I need to switch the initial view. i.e. Home page if settings already exists and settings page if the user opens the app for the first time.
How should I go about it?
Currently the way I adopted is :
Made Default task empty in WMAppManifest.xml
<DefaultTask Name="_default" />
Decided which page to move to in Application_Launching event handler.
private void Application_Launching(object sender, LaunchingEventArgs e)
{
if (SettingFileExists())
RootFrame.Navigate(new Uri("Home.xaml", UriKind.Relative));
else
RootFrame.Navigate(new Uri("Settings.xaml", UriKind.Relative));
}
Is this the best way to approach this scenario? Is there any potential issue with my code?
There a re lots of different ways of doing this and no one "best" way.
My personal preference is to use a custom UriMapper that does the redirect on start up.
E.g.
Set the Navigation startup Uri to something special that doesn't exist. e.g. "StartUp"
Set a custom UriMapper:
RootFrame.UriMapper = new MyUriMapper();
Then in the UriMapper check for the special uri and take appropriate action:
public class MyUriMapper : UriMapperBase
{
public override Uri MapUri(Uri uri)
{
if (uri.OriginalString == "/StartUp")
{
if (!this.dataOperations.IsLoggedIn())
{
return Login.Path;
}
else
{
return Main.Path;
}
}
return uri;
}
}
I'm new to Windows Phone and C#, enjoying the change from Objective-C and Java.
I cant find the way to pass an object from one class to another. I came across some sample code looking on MSDN but I tink that maybe its not applicable for what I need.
private void meetingList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (meetingList.SelectedIndex != -1)
{
Meeting aMeeting = (Meeting)meetingList.SelectedItem;
this.NavigationService.Navigate(new Uri("/MeetDetails.xaml", UriKind.Relative));
ApplicationBar.IsVisible = true;
}
}
How can I pass my Meeting Object 'aMeeting' into my MeetDetails class so that I can display all the details to the user.
I know I can break it down, and pass in all the vars from the 'aMeeting' by using something like this:
this.NavigationService.Navigate(new Uri("/MeetDetails.xaml?Meeting=" +
aMeeting.meetName + "&TheDate=" +
aMeeting.meetDate, UriKind.Relative));
Is there something I've missed? Are there alternative ways you guys would recommend?
Many Thanks,
-Code
What you've posted is a good way of transferring simple data about the place. However it becomes a pain when you have to pass a complex object between pages.
The recommended way is to use the MVVM pattern (from wikipedia and MSDN). This gives you a way to separate the View from everything else by making use of data binding. The best tutorials I have seen is to watch the videos on MSDN.
var t1 = App.Current as App;
t1.SSIDToken = stData1SSID;
t1.CSRFToken = stData1CSRF;
this works real good, just make the members u need in the app.cs file
(here it was :
public string SSIDToken {get; set;}
public string CSRFToken {get; set;}
Then create the top code to create a var to serve as temp buffer.
If you want to get back the values use the same code :
var t1 = App.Current as App;
thisisatextbox.Text = t1.SSIDToken;
thisisalsoatextbox.Text = t1.CSRFToken;
Further info ;
http://www.eugenedotnet.com/2011/07/passing-values-between-windows-phone-7-pages-current-context-of-application/
EDIT: After a couple of months of experience, noticed you can add
public static new App Current
{
get { return Application.Current as App; }
}
In the App.xaml (In the public class App) to be able to call upon App.Current without having to declare it every single time!
Now you can use App.Current.CSRFToken = "" || string CSRFTk = App.Current.CSRFToken;
You might want to consider a manager class with properties which could store your current Meeting object. This would then be set in your SelectionChanged event handler and then accessed in your MeetDetails page. The manager class is defined externally to your pages so that it can be accessed from all your pages.
I'm in the process of developing my first Windows Phone 7 Application. I'm freshfaced to Silverlight, C# and the whole .NET Scene, but I like to think I'm making decent progress.
I know from various code examples, I can set the tile using ShellTile. I know I can pass through params with the URI (Like this example):
ShellTile.Create(new Uri("/MainPage.xaml?DefaultTitle=FromSecondaryTile", UriKind.Relative), tile );
Can anyone point me in the direction (or explain) how I can handle arguments passed from the tile? So, when the tile's open, I'd like to open a certain part of the application.
For the record, I'm aware I could create a separate page for each one to handle it that way, but I can see that getting messy fast :)
Thanks!
Mike
A way I've found that works well for my particular purpose, is the same means of passing values between xaml pages, which is simply to pass them through in the query string:
NavigationContext.QueryString["XXXXX"].ToString();
Where XXXXX is the name in the key/name pair.
You can either set the url to a seperate page (like OtherPage.xaml) or you can use the supplied URI, and change the page/view in the OnNavigatedTo override.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
...
}
Here the NavigationEventArgs will provide you with the navigation args you supplied, as a regular dictionary. From those, you can decide what to do then.
Also, you can make life easier with a simple extension (here specialized with a overload for a integer key, since I personally prefer use them for identifiers)
namespace System.Windows.Navigation
{
public static class NavigationExtensions
{
public static int? TryGetKey(this NavigationContext source, string key)
{
if (source.QueryString.ContainsKey(key))
{
string value = source.QueryString[key];
int result = 0;
if (int.TryParse(value, out result))
{
return result;
}
}
return null;
}
public static string TryGetStringKey(this NavigationContext source, string key)
{
if (source.QueryString.ContainsKey(key))
{
return source.QueryString[key];
}
return null;
}
}
}
I have a custom control which inherits from System.Web.UI.Control and some of its properties can be declaratively set using databinding expressions. e.g.
<foo:Foo runat="server" MyFoo="<%# this.GetFoo() %>" />
Now, when I do that I need to call .DataBind() on the control (or one of its parents) to evaluate these expressions.
What I would like to be able to do is detect if any properties were set this way and just automatically have the custom control call this.DataBind() after OnPreRender or there about.
So the question: how do I detect if databinding expressions are waiting to be executed?
I'm convinced that in some ControlBuilder or DataBindContext class lives the information needed to determine this. I've hunted around with Reflector and cannot seem to find it.
I should add, that I don't want to pay the overhead of executing DataBind() if no direct properties have been assigned this way. This is why I'd like to detect before hand. This class is extremely light but I'd like the ability to declaratively set properties without needing any code behind.
Doing some deeper looking into ControlBuilder, I noticed that the compiled factory for each control instance will attach a DataBinding event handler when there are data binding expressions present. I've found that checking for this seems to be a very reliable method for determining if data binding needs to occur. Here is the basis of my solution to the problem:
using System;
using System.Reflection;
using System.Web.UI;
public class AutoDataBindControl : Control
{
private static readonly object EventDataBinding;
private bool needsDataBinding = false;
static AutoDataBindControl()
{
try
{
FieldInfo field = typeof(Control).GetField(
"EventDataBinding",
BindingFlags.NonPublic|BindingFlags.Static);
if (field != null)
{
AutoDataBindControl.EventDataBinding = field.GetValue(null);
}
}
catch { }
if (AutoDataBindControl.EventDataBinding == null)
{
// effectively disables the auto-binding feature
AutoDataBindControl.EventDataBinding = new object();
}
}
protected override void DataBind(bool raiseOnDataBinding)
{
base.DataBind(raiseOnDataBinding);
// flag that databinding has taken place
this.needsDataBinding = false;
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
// check for the presence of DataBinding event handler
if (this.HasEvents())
{
EventHandler handler = this.Events[AutoDataBindControl.EventDataBinding] as EventHandler;
if (handler != null)
{
// flag that databinding is needed
this.needsDataBinding = true;
this.Page.PreRenderComplete += new EventHandler(this.OnPreRenderComplete);
}
}
}
void OnPreRenderComplete(object sender, EventArgs e)
{
// DataBind only if needed
if (this.needsDataBinding)
{
this.DataBind();
}
}
}
This solution disables itself if no DataBinding event handler is attached or if the control is manually data bound (directly or via a parent).
Note that most of this code is just jumping through hoops to be able to test for the existence of the event. The only reflection needed is a one-time lookup to get the object used as the key for EventDataBinding.
There is an internal ArrayList called SubBuilders on the ControlBuilder class. For each databinding expression TemplateParser enocunters, ProcessCodeBlock() adds a CodeBlockBuilder object with a BlockType property CodeBlockType.DataBinding to SubBuilders.
So if you can get a handle to the ControlBuilder you want, you should be able to reflectively iterate over SubBuilders and look for objects of type CodeBlockBuilder where BlockType == CodeBlockType.DataBinding.
Note of course this is all kinds of nasty and I'm really suspicious this is the best way to solve your core problem. If you take two steps back and look at the original problem, maybe post that on Stackoverflow instead - there's plenty of super-smart people who can help come up with a good solution.