Im working on a MVVM Windows phone app that displays weather info.
When the app loads up it opens MainPage.xaml. It makes a call the the service to get weather info and binds that data to the UI. Both Fahrenheit and Celcius info are returned but only one is displayed.
On the setting page, the user can select to view the temp in either Fahrenheit or Celcius.
The user can change this setting at any time and its stored in IsolatedStorageSettings.
The issue Im having is this:
when the user navigates to the Settings page and changes their preference for either Fahrenheit or Celcius, this change is not reflected on the main page.
This issue started me thinking about this in a broader context. I can see this being an issue in ANY MVVM app where the display depends on some setting in IsolatedStorage. Any time any setting in the IsoStore is updated, how does the ViewModels know this? When I navigate back in the NavigationStack from the settings page back to MainPage how can I force a rebind of the page?
The data in my model hasnt changed, only the data that I want to display has changed.
Am I missing something simple here?
Thanks in advance.
Alex
Probably you have code like this:
public double DisplayTemperature
{
get { return (IsCelsium) ? Celsium : Fahrenheit; }
}
And IsCelsium is:
public double IsCelsium
{
get { return (bool)settings["IsCelsium"]; }
set { settings["IsCelsium"] = value; }
}
So you need to add NotifyPropertyChanged event to notify UI to get new values from DisplayTemperature property:
public double IsCelsium
{
get { return (bool)settings["IsCelsium"]; }
set
{
settings["IsCelsium"] = value;
NotifyPropertyChanged("DisplayTemperature");
}
}
Take a look at Caliburn Micro. You could implement something similar or use CM itself. When using CM I don't even think about this stuff, CM makes it so simple.
When your ViewModel inherits from Screen there are life-cycle events that fire that you can override. For example, OnInitialize fires the very first time the ViewModel is Activated and OnActivate fires every time the VM is activated. There's also OnViewAttached and OnViewLoaded.
These methods are the perfect place to put logic to populate or re-populate data.
CM also has some special built in features for allowing one to easily tombstone a single property or an entire object graph into Iso or phone state.
ok, so Ive come up with a solution. Before I get to it, let me provide some background. The app that Im working on uses both MVVM Light and WP7Contrib. That being the case, I am using Funq for DI and the MVVMLight Toolkit. After I posted my initial question, I gave the question a bit more thought. I remembered a video that I watched a while back from MIX2011 called Deep Dive MVVM with Laurent Bugnion
http://channel9.msdn.com/Events/MIX/MIX11/OPN03
In it, he talks about just this problem (view models not living at the same time) on Windows Phone. The part in question starts around the 19 minute mark.
Anyway, after I remembered that and realized that the ViewModel locator is exposed in App.xaml, this became a trivial problem to solve. When the user changes the Fahrenheit/Celcius option on the setting page, I simply get a reference to the MainViewModel via the ViewModelLocator and reset the collection that is bound to the UI thus causing the bindings to update.
public bool AddOrUpdateValue(string Key, Object value)
{
bool valueChanged = false;
// If the key exists
if (settings.Contains(Key))
{
// If the value has changed
if (settings[Key] != value)
{
// Store the new value
settings[Key] = value;
valueChanged = true;
}
}
// Otherwise create the key.
else
{
settings.Add(Key, value);
valueChanged = true;
}
return valueChanged;
}
public bool ImperialSetting
{
get
{
return GetValueOrDefault<bool>(ImperialSettingKeyName, ImperialSettingDefault);
}
set
{
if (AddOrUpdateValue(ImperialSettingKeyName, value))
{
Save();
RaisePropertyChanged("ImperialSettingText");
var vml = new ViewModelLocator();
vml.MainViewModel.Cities = (App.Current as App).Cities;
}
}
}
It was a mistake on my part not to realize that I could get access to the viewModel via the ViewModelLocator. Hopefully this post saves someone else the time I burned on this issue.
Related
I am using a CellTable to display results of a query and I need these results to be shown as (html) links. I would like to react to a click on these links. So far, I had implemented it like this:
// ClickHandler on CellTable
cellTable.addCellPreviewHandler(new Handler<OperationalItemMultipleSearchRowData>() {
#Override
public void onCellPreview(CellPreviewEvent<OperationalItemMultipleSearchRowData> event) {
boolean isClick = "click".equals(event.getNativeEvent().getType());
if (isClick)
AdminUtils.EVENT_BUS.fireEvent(new SimpleSearchEvent(event.getValue().getName()));
}
});
The Problem is that this reacted to a Click on the whole row instead of the link. Due to architectural restrictions, the link itself is not a real html link, but a SafeHtml link that leads nowhere. I just needed the look&feel of a Link:
Column<OperationalItemMultipleSearchRowData, SafeHtml> nameColumn = new Column<OperationalItemMultipleSearchRowData, SafeHtml>(new SafeHtmlCell()) {
#Override
public SafeHtml getValue(final OperationalItemMultipleSearchRowData object) {
return new SafeHtml() {
#Override
public String asString() {
return "" + object.getName() + "";
}
};
}
};
How can I react to a click on this link only ? (instead of the whoel row)
Is there a more elegant way to implement this ?
Cheers
As with any other use of event delegation, the basic idea is to find walk up the hierarchy starting from the target of the event up until you find the link element you're looking for, or some other element that signals the search is over and the click was targetted outside the link (e.g. you reached the cell, the row or the table).
That being said, I think you should merge your behavior inside a specific Cell implementation rather than using a CellPreviewHandler (copy/paste the ActionCell or TextButtonCell as a starting point).
As a side note, I also believe you should not use a link when you're not actually linking anywhere, or try to provide a target for the link if the behavior is the one of a link (that way, right-clicking, middle-clicking or ctrl-clicking will Just Workâ˘). If you want the look of a link (without the "feel"), then just use an ActionCell or TextButtonCell and style it accordingly.
I am facing the following problem,
I have an object called "data". It has three properties, one of it being itemRendererData. The "itemRendererData" is an ArrayCollection of objects having many properties one of which is the property "imageURL" (datatype:String).
I am working in flex. I have defined the view and the item renderer properly. The view has the data. I am supposed to get the images from the url specified by imageURL property.
In the itemRenderer, I have declared, source
source = {data.itemRendererData.imageURL}
But the images are not being displayed.
Use a the FlexEvent.DATA_CHANGE handler rather than binding, which is actually the proper way to handle this and gives you far more control.
public function CustomItemRenderer() {
this.addEventListener(FlexEvent.DATA_CHANGE, this.dataChangeHandler);
this.addEventListener(FlexEvent.CREATION_COMPLETE, this.creationCompleteHandler);
}
private function creationCompleteHandler(e:FlexEvent) {
if (this.data) {
this.image.source = this.data.itemRendererData.imageURL;
}
}
private function dataChangeHandler(e:FlexEvent) {
if (this.data && this.initialized) {
this.image.source = this.data.itemRendererData.imageURL;
}
}
You will notice that I have a handler for FlexEvent.CREATION_COMPLETE as well. This is because the data is actually set before the components are created. So the first time a renderer is loaded, this.image is null and this.image.source will error out.
If that doesn't work, you also need to make sure that the Image/BitmapImage is not a direct child of the renderer. I never did figure out why this was, but adding it as a child of Group fixed that issue where the image was being set but not rendering. Again, I have no idea why this was and I tested for a few hours trying to figure it out.
As an added tip, avoid MXML-based ItemRenderers in mobile applications. They are noticeably slower than pure-AS3 renderers.
Right now i am developing a Windows phone app, yes this is my first windows app. Right now i am facing an issue, don't know what's the silly mistake i made. Yes of course i have done debugging.
Now, what exactly i am doing?
Passing data from 1st page to 2nd page,
On the page, catching data inside onNavigateTo() method, yes i am receiving it correctly.
Based on the parameter/data (i.e. ID) i got, i am making web service call.
Problem:
If i move to the 3rd page from 2page and again came back to the 2nd page, its again making web call. i.e. calling DownloadStringAsync again in below code.
i.e. If 2nd page is having ListBox with 5 data, now clicking on particular item i am moving to 3rd page, if i came back to 2nd page from page 3, items get doubled i.e. 10 items (just because its making call again)
Here is the possible code for the reference:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
type = Convert.ToInt32(NavigationContext.QueryString["Type"]);
if (type != 0)
{
if (NetworkInterface.GetIsNetworkAvailable())
{
client.DownloadStringAsync(new Uri(Utils.Constant.WebService_URL));
}
else
{
MessageBox.Show("Please check internet connection!!");
}
}
}
Can't you use the following code to test if the user used the back button?
e.NavigationMode == System.Windows.Navigation.NavigationMode.Back
sometimes this method(QueryString) create problems.
In back event it create problems
its betters to store the id(parameter) in isolatedstorage application key
IsolatedStorageSettings.ApplicationSettings["id"] = "your data";
OnNavigatedTo is called whenever you navigate to the page, either by a forward navigation, or a back navigation. That's why it's retriggered when you navigate back from page #3 to page #2.
You can avoid this by only triggering the network call on a forward navigation.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (this.isInitialized) return;
type = Convert.ToInt32(NavigationContext.QueryString["Type"]);
if (type != 0)
{
if (NetworkInterface.GetIsNetworkAvailable())
{
client.DownloadStringAsync(new Uri(Utils.Constant.WebService_URL));
}
else
{
MessageBox.Show("Please check internet connection!!");
}
}
this.isInitialized = true;
}
Beware that NetworkInterface.GetIsNetworkAvailable can block (in this case the UI thread) for a long time in some scenarios, to work around this you can use network detection events.
I'm developing my first app and I'm trying to make it multilanguage.
Using AppHub example and some other link I created my resource files, fixed binding strings on my components and set a settings page.
First problem I had was that menu items and appbar buttons couldn't use localization strings (project complained when launched) so I have:
TextBlocks and other components binded with localized strings
Appbar buttons and items localized manually with a procedure loading localized strings
Now that I have my settings page, one item user can change is language.
Well, correct CultureInfo is selected according to user selection and then I use
Thread.CurrentThread.CurrentUICulture = Settings.Language;
When I press back button and return to main page, appbar items are localized correctly, while everything else is not.
The only workaround (that I really don't like, it's just to understand) is this:
public MainPage()
{
Thread.CurrentThread.CurrentUICulture = Settings.Language;
InitializeComponent();
// Everything else I need here
}
so I have to set language before components are created to make it work.
What's wrong? Which is the correct way to make a page refresh after changing language using binded strings?
I did not put a lot of code because I used basically the one provided in the link, but if you need more info I will edit my question.
I finally found a solution to automatically update my application components reacting to language change.
A good tutorial can be found here; briefly you must find a way to notify your app that localized resource is changed.
public class LocalizedStrings : ViewModelBase
{
private static AppResources localizedresources = new AppResources();
public AppResources LocalizedResources
{
get { return localizedresources; }
}
public void UpdateLanguage()
{
localizedresources = new AppResources();
RaisePropertyChanged(() => LocalizedResources);
}
public static LocalizedStrings LocalizedStringsResource
{
get
{
return Application.Current.Resources["LocalizedStrings"]
as LocalizedStrings;
}
}
}
With this when user change language, you should simply run
LocalizedStrings.LocalizedStringsResource.UpdateLanguage();
and the job is done.
I am trying to serialize my game data. In case the user presses the Windows button, everything should be saved. I know that we should override the OnExiting event in the game class. but am using the Game State Management , I want to serialize game data in my GamePlayScreen class. I did override the Serialize and DeSerialize methods, but they didnt work.
hers my code:
public override void Serialize(Stream stream)
{
gameState.HumanPlayer = HumanPlayer;
gameState.Player1 = AIPlayer1;
gameState.Player2 = AIPlayer2;
gameState.Player3 = AIPlayer3;
XmlSerializer serializer = new XmlSerializer(typeof(GameState));
serializer.Serialize(stream, gameState);
base.Serialize(stream);
}
public override void Deserialize(Stream stream)
{
XmlSerializer serializer = new XmlSerializer(typeof(GameState));
gameState = (GameState)serializer.Deserialize(stream);
if (gameState.HumanPlayer != null)
HumanPlayer = gameState.HumanPlayer;
if (gameState.Player1 != null)
AIPlayer1 = gameState.Player1;
if (gameState.Player2 != null)
AIPlayer2 = gameState.Player2;
if (gameState.Player3 != null)
AIPlayer3 = gameState.Player3;
base.Deserialize(stream);
}
I tried to create my own IsolatedStorageFile instead of the provided stream object, but it didnt work.
I tried to write the same code in the Load and Unload event. it works fine there, but in case of pressing the back button. i need to serialize if the user pressed the windows button or the search button.
It looks like you need to handle the OnDeactivated and OnActivated events. Just do the same thing as is done in the OnExiting event and the Constructor. I would have thought the sample would do this as proper handling of tombstone/rehydrate is such a big thing for WP7, however it seems it has been neglected. Note that OnActivated is NOT called when the app is launched and OnDeactivated is NOT called when the app is closed manually or exited using the Back button.
Note that Activated and Deactivated area also available as events on PhonApplicationServices.Current, along with Launching and Closing, which are ONLY called on actual open and exit situations.
EDIT
Ok, I take it back. OnDeactivated and OnActivated are not required. It seems that OnExiting is fired for both Deactivate and Exit scenarios. I downloaded the sample you linked (XNA4 WP7, not Mango version) and put this code into the GameplayScreen:
public override void Serialize(System.IO.Stream stream)
{
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(string));
serializer.Serialize(stream, "Blah de blah blah");
base.Serialize(stream);
}
public override void Deserialize(System.IO.Stream stream)
{
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(string));
string testStr = (string)serializer.Deserialize(stream);
base.Deserialize(stream);
}
A break point shows that the Deserialize method is being hit functioning correctly, so your problem must be in how you apply your loaded data, or perhaps you've edited other code that has broken it.