I am new in the development of WP8. I have been following a online course for a couple of weeks and the second task of the course was to develop a app to show the weather, some news and photos related to the city.
So far, I have develop the app following the MVVM pattern using the Panorama control as the conteiner for the differents contents I need to show.
To no longer this, the problem I facing is at the moment to display the xml data that is retrieve from the webservices.
The XAML is:
<phone:panorama x:Name="myPanorama"
DataContext = {Binding Source="WeatherViewModel"}>
<PanoramaItem header="MyWeather">
<Textblock x:name="txtCity"
Text = {Binding Weather.City}
</Textblock>
</PanoramaItem>
<panoramaItem header="Config">
<Text x:Name="txtGetCity"/>
<Button x:Name="btnGetCity"
Command={Binding GetWeatherCommand}/>
</panoramaItem>
</phone:panorama>
My ViewModel:
public class WeaterViewModel : NotificationEnableObject
{
private Weather _currentWeather;
public Weather GetCurrentWeather
{
get
{
if (_currentWeather == null)
_currentWeather = new Weather();
return _currentWeather;
}
set { _currentWeather = value;
OnPropertyChanged("GetCurrentWeather");
}
}
//Constructor ServiceModel serviceModel = new ServiceModel();
public WeatherViewModel()
{
serviceModel.GetWeatherCompleted += (s, a) =>
{
_currentWeather = new Clima();
_currentWeather.City= a.Results[0].City;
_currentWeather.tempC = a.Results[0].tempC;
};
getWeatherCommand = new ActionCommand(null);
}
ActionCommand getWeatherCommand; // ActionCommand derivied from ICommand
public ActionCommand GetWeatherCommand
{
get
{
if (getWeatherCommand!= null)
{
getWeatherCommand = new ActionCommand(() =>
{
//Call the Service who retrieved the data
});
}
return getWeatherCommand;
}
}
}
The Weather specified is a public class which contain the City property. I have tried using an IObservableCollention as well howerver, the result is the same :-(
As you can see in the panorama control I have 2 sections. The one where I write the city I wanna see and the section where I show the information I get from the web services.
Any clue, or help would be very appreciate
Regards!
Ok, I think that is an easy fix.
You're setting GetCurrentWeather this way:
_currentWeather = new Clima();
_currentWeather.City= a.Results[0].City;
_currentWeather.tempC = a.Results[0].tempC;
This is not firing the PropertyChanged event. Change it to:
GetCurrentWeather= new Clima();
GetCurrentWeather.City= a.Results[0].City;
GetCurrentWeather.tempC = a.Results[0].tempC;
and you should be fine.
Related
We are using Xam.Plugin.HtmlLabel plugin in our xamarin forms application. When we set the html string in constructor, the html label is loading correctly in ios. But we assign the same label in method and called the method in constructor it is not loading in iOS and in android it's working fine. The issue reproduced code snippet is mentioned below. This issue is occurred only in iOS.
HTMLPage.xaml
<ContentPage.Content>
<Grid>
<ScrollView
Padding="24"
HorizontalScrollBarVisibility="Never"
VerticalScrollBarVisibility="Never">
<htmlLabel:HtmlLabel
Text="{Binding HTMLDescription}"
LinkColor="{StaticResource LinkColor}"
TextColor="{StaticResource PriTextColor}" />
</ScrollView>
</Grid>
</ContentPage.Content>
HTMLPage.xaml.cs
private string htmlDescription;
public string HTMLDescription
{
get { return htmlDescription; }
set { htmlDescription = value; OnPropertyChanged(); }
}
public HTMLPage(string Description)
{
InitializeComponent();
GetHTML(Description);
BindingContext = this;
}
private async void GetHTML(string description)
{
await Task.Delay(2000); //This delay is for getting the data from server.
HTMLDescription = description;
}
In whatever code does new HTMLPage("this is some html"), are you on the UI thread?
If not on UI thread, then that's your problem - dealing with UI elements off the UI thread is problematic.
If running on UI thread, then you have a different problem: a constructor is a "blocking" operation - it does no good to have async/await on code called inside a constructor; UI thread is blocked until the constructor returns! In general, its a bad idea to do anything lengthy there. Worst case, web query might delay until timeout.
Instead, try setting HTMLDescription AFTER page has appeared:
// Hold it until used.
string Description;
public HTMLPage(string description)
{
this.Description = description;
...
}
protected override void OnAppearing()
{
base.OnAppearing();
// Move to background, so OnAppearing can return.
Task.Run(() => {
// Potentially long operation.
var html = GetHTML(Description);
// Move to UI thread, before touching any UI element.
Device.BeginInvokeOnMainThread(() => {
HTMLDescription = html;
}
}
}
private string GetHTML(string description)
{
Task.Delay(2000); //This delay is for getting the data from server.
return description;
}
NOTE: I've removed async/await from this version of GetHTML, because it is only called on a background thread. You can put those back in, if desired.
Of course the downside of this, is that the page appears at first without that label. If you don't want that, then you need to instead GetHTML(...) BEFORE calling the constructor.
So your code (in a place you don't show) would be something like this:
var html = GetHTML(...);
new HTMLPage(html);
Thus, you are back to your original case that works, where in the constructor you already have the html string, so can simply do
HTMLDescription = description;
Which begs the question: why didn't you do that in the first place? Why did you put the call to GetHtML inside the constructor?
I am using Cimbalino navigation but the query param never gets set for me.
Main View Model
private readonly INavigationService navigationService = null;
public MainViewModel(INavigationService navigationService)
{
this.navigationService = navigationService;
NavigateToPg2Cmd = new RelayCommand(() => NaviagateToPg2());
NavigateToPg2WithParmsCmd = new RelayCommand(() => NaviagateToPg2WithParms());
}
private void NaviagateToPg2WithParms()
{
navigationService.NavigateTo(new Uri("/Views/SecondPg.xaml?parameter=1&parm2=2", UriKind.Relative));
}
When I look into NavigationService the Query Param dictionary is always 0.
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
}
else
{
}
SimpleIoc.Default.Register<INavigationService, NavigationService>();
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<SecondVM>();
}
Edit
Ok, I figured it out. When NavigateTo runs it still has not split the query string out yet so that's why it is zero.
I was also trying to do
private readonly INavigationService navigationService = null;
public SecondVM(INavigationService navigationService)
{
this.navigationService = navigationService;
if (IsInDesignMode)
{
Message = "Design Mode";
}
else
{
if (navigationService.QueryString.ContainsKey("paramter"))
{
Message = navigationService.QueryString["parameter"];
}
}
}
what did not work either as I guess it was too early as well. I really would like to pull it out at constructor time though, is there a way to do this?
I know it's not 100% the solution you are looking for, but you are true... You'll need to wait until the view is loaded before accessing the QueryString params in the ViewModel!
To do this, hook into the Loaded event of the view and pass it to a Command on the viewmodel!
If created a demo of this on my github to get you started: https://github.com/Depechie/NavigationParams
We are writing a Windows 8.1 Store App that uses the new SearchBox XAML control. It looks like the only way to get suggestions into the dropdown list as the user types is to use the SearchBoxSuggestionsRequestedEventArgs and get the SearchSuggestionCollection from the event then append the suggestions to that.
We're using Prism for WinRT and want to separate the SearchBox and it's events from the ViewModel that is getting the list of suggestion strings.
I can't find anyway of binding a list of strings to the SearchSuggestionCollection or any way of adding them programatically that doesn't involve using the event args, which is making out unit testing very complex.
Is there a way of binding/adding the suggestions that doesn't involve the event args?
Okay, so I got obsessed with this question, and here is a solution for when using the SearchBox. I've uploaded a full sample on MSDN and GitHub
In short, use the Behavior SDK and and the InvokeCommand, and then use a converter to grab whatever data you need by using the new attributes InputConvert and InputConverterParameter.
XAML:
<SearchBox SearchHistoryEnabled="False" x:Name="SearchBox" Width="500" Height="50">
<SearchBox.Resources>
<local:SearchArgsConverter x:Name="ArgsConverter"/>
</SearchBox.Resources>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SuggestionsRequested">
<core:InvokeCommandAction
Command="{Binding SuggestionRequest}"
InputConverter="{StaticResource ArgsConverter}"
InputConverterLanguage="en-US"
InputConverterParameter="{Binding ElementName=SearchBox, Path=SearchHistoryEnabled}"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</SearchBox>
Converter:
public sealed class SearchArgsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var args = (SearchBoxSuggestionsRequestedEventArgs)value;
var displayHistory = (bool)parameter;
if (args == null) return value;
ISuggestionQuery item = new SuggestionQuery(args.Request, args.QueryText)
{
DisplayHistory = displayHistory
};
return item;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}
Mainpade codebehind - of course you want this in a VM :)
public sealed partial class MainPage
{
public DelegateCommand<string> Search { get; set; }
public DelegateCommand<ISuggestionQuery> SuggestionRequest { get; set; }
public MainPage()
{
InitializeComponent();
Search = new DelegateCommand<string>(SearchedFor, o => true);
SuggestionRequest = new DelegateCommand<ISuggestionQuery>(SuggestionRequestFor, o => true);
DataContext = this;
}
private void SuggestionRequestFor(ISuggestionQuery query)
{
IEnumerable<string> filteredQuery = _data
.Where(suggestion => suggestion.StartsWith(query.QueryText,
StringComparison.CurrentCultureIgnoreCase));
query.Request.SearchSuggestionCollection.AppendQuerySuggestions(filteredQuery);
}
private readonly string[] _data = { "Banana", "Apple", "Meat", "Ham" };
private void SearchedFor(string queryText)
{
}
}
I wrote up a full walk through on my blog, but the above is all you really need :)
Is it possible to use a simple action method - just like with Caliburn.Micro - instead of a command with MvvmCross bindings?
Example:
public void Action()
{
Tip = 11;
}
<Button
android:text="Button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/button1"
local:MvxBind="Click Action" />
It doesn't work out of the box, I tested that.
While I found a lot of samples about adding new target bindings, I didn't find a single one about adding a new source binding.
UPDATE:
This works now out of the box with the Rio binding. To use it, add the MvvmCross MethodBinding NuGet package to the Android project.
Up until now, much of the emphasis for MvvmCross has been on allowing multi-platform target binding with the source remaining mainly 'vanilla' INotifyPropertyChanged.
There have been some deviation in terms of ViewModel structure - e.g.:
the MvxCommandCollection - http://slodge.blogspot.co.uk/2013/03/fixing-mvvm-commands-making-hot-tuna.html
some users using Fody - http://twincoders.com/blog/codigo-limpio-con-fody/
Recently, several new feature requests have also been logged in this area:
AutoCommands - I think this is what you are asking about here - https://github.com/slodge/MvvmCross/issues/301
Rio binding sources - https://github.com/slodge/MvvmCross/issues/299
Tibet binding - https://github.com/slodge/MvvmCross/issues/298
Because of these, I do expect more functionality to be exposed in this area in the future...
With that said, if you wanted to get this working today, then MvvmCross Binding is overrideable so you could fairly easily do it:
1. Implement an ICommand that invokes a MethodInfo using reflection (for completeness this should probably also use a parameter if available) - some kind of InvokeMethodCommand (code for this left to the reader!)
.
2. Implement an MyMethodSourceBinding class which wraps the InvokeMethodCommand - something like:
public class MyMethodSourceBinding : MvxSourceBinding
{
private readonly MethodInfo _methodInfo;
protected MyMethodSourceBinding(object source, MethodInfo methodInfo)
: base(source)
{
_methodInfo = _methodInfo;
}
public override void SetValue(object value)
{
// do nothing - not allowed
}
public override Type SourceType
{
get { return typeof(ICommand); }
}
public override bool TryGetValue(out object value)
{
value = new InvokeMethodCommand(source, _methodInfo);
return true;
}
}
3. Override MvvmCross's registered IMvxSourceBindingFactory with your own implementation that can detect when a method is present - sadly most of this is cut and paste coding today - it would be something like
public class MySourceBindingFactory
: IMvxSourceBindingFactory
{
private IMvxSourcePropertyPathParser _propertyPathParser;
private IMvxSourcePropertyPathParser SourcePropertyPathParser
{
get
{
if (_propertyPathParser == null)
{
_propertyPathParser = Mvx.Resolve<IMvxSourcePropertyPathParser>();
}
return _propertyPathParser;
}
}
public IMvxSourceBinding CreateBinding(object source, string combinedPropertyName)
{
var tokens = SourcePropertyPathParser.Parse(combinedPropertyName);
return CreateBinding(source, tokens);
}
public IMvxSourceBinding CreateBinding(object source, IList<MvxPropertyToken> tokens)
{
if (tokens == null || tokens.Count == 0)
{
throw new MvxException("empty token list passed to CreateBinding");
}
var currentToken = tokens[0];
if (tokens.Count == 1)
{
return CreateLeafBinding(source, currentToken);
}
else
{
var remainingTokens = tokens.Skip(1).ToList();
return CreateChainedBinding(source, currentToken, remainingTokens);
}
}
private static MvxChainedSourceBinding CreateChainedBinding(object source, MvxPropertyToken propertyToken,
List<MvxPropertyToken> remainingTokens)
{
if (propertyToken is MvxIndexerPropertyToken)
{
return new MvxIndexerChainedSourceBinding(source, (MvxIndexerPropertyToken) propertyToken,
remainingTokens);
}
else if (propertyToken is MvxPropertyNamePropertyToken)
{
return new MvxSimpleChainedSourceBinding(source, (MvxPropertyNamePropertyToken) propertyToken,
remainingTokens);
}
throw new MvxException("Unexpected property chaining - seen token type {0}",
propertyToken.GetType().FullName);
}
private static IMvxSourceBinding CreateLeafBinding(object source, MvxPropertyToken propertyToken)
{
if (propertyToken is MvxIndexerPropertyToken)
{
return new MvxIndexerLeafPropertyInfoSourceBinding(source, (MvxIndexerPropertyToken) propertyToken);
}
else if (propertyToken is MvxPropertyNamePropertyToken)
{
//**************************
// Special code is here
var propertyToken = (MvxPropertyNamePropertyToken) propertyToken;
if (source != null)
{
var method = source.GetType().GetMethod(propertyToken.PropertyName, BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance);
if (method != null)
{
return new MyMethodSourceBinding(source, method);
}
}
return new MvxSimpleLeafPropertyInfoSourceBinding(source,
(MvxPropertyNamePropertyToken) propertyToken);
// Special code ends here
//**************************
}
else if (propertyToken is MvxEmptyPropertyToken)
{
return new MvxDirectToSourceBinding(source);
}
throw new MvxException("Unexpected property source - seen token type {0}", propertyToken.GetType().FullName);
}
}
4. Supply this source binding factory in your own custom binding builder - e.g.:
public class MyAndroidBindingBuilder
: MvxAndroidBindingBuilder
{
protected override IMvxSourceBindingFactory CreateSourceBindingFactory()
{
return new MvxSourceBindingFactory();
}
}
5. Supply this binding builder during your setup
public class Setup : MvxAndroidSetup
{
// ....
protected override MvxAndroidBindingBuilder CreateBindingBuilder()
{
return new MyAndroidBindingBuilder();
}
}
Note: This approach is only for advanced users right now... As suggested in the first part of this question, I do expect the code in this area to change quite a lot so you might also encounter some issues maintaining a fork in this area. (Indeed the code in this area has already changed quite significantly on the Tibet Binding branch within the GitHub repo!)
I have a Flex Application that has a Advanced Data Grid that contains a Grouping Collection. I want the screen to initially have all the nodes closed. From there the user can choose to open one or two and view the information within. If the click on one of the Nodes Children it changes the View using a view stack to a screen containing More info on that child. However when we return to the initial screen it returns with all nodes closed again.
I would like the App to remember what nodes were left open and what the last clicked item was and have it highlighted.
I have tried using IHierarchicalCollectionView(dataProviderName).openNodes and assigning it to an Object when the view changes and when it returns Assisning this object to IHierarchicalCollectionView(dataProviderName).openNodes. But the app seems to go into a loop and IE stops responding.
This is the Code for my DataGrid as it stands. Any help would be appreciated.
public class SummaryGridBase extends AdvancedDataGrid
{
[Bindable]
protected var _modelLocator:ModelLocator = ModelLocator.getInstance();
[Bindable]
override public function set dataProvider(value:Object):void
{
super.dataProvider = value;
}
override protected function collectionChangeHandler(event:Event):void
{
super.collectionChangeHandler(event);
trace("Summary Grid Trace 1");
if( event is CollectionEvent && (event as CollectionEvent).kind == CollectionEventKind.REFRESH )
{
trace("Summary Grid Trace 2");
this.validateGridAndExpand();
}
}
private function validateGridAndExpand():void
{
this.validateNow();
var rootLevel:ArrayCollection = ModelLocator.getInstance().groupingCollection.getRoot() as ArrayCollection;
for each( var item:Object in rootLevel )
{
this.expandItem( item, true, false );
}
}
protected function changeHandler(event:ListEvent):void
{
trace("Change in Summary Data Selection" +(this.selectedItem.Business));
if( this.selectedItem.Business == null )
{
trace("Im Null");
Alert.show( "Please Expand a Vendor Using The Arrow Beside it \nand Select a Polymer From the List", 'Warning', mx.controls.Alert.OK);
}
else
{
var summaryEvent:SummaryEvent = new SummaryEvent( SummaryEvent.SELECT_SUMMARY, (this.selectedItem.Business as String), (this.selectedItem.Op_Site as String),(this.selectedItem.Vendor as String),(this.selectedItem.Item_Desc as String) );
summaryEvent.dispatch();
}
}
public function SummaryGridBase()
{
super();
}
}
I just stumbled across this question and had encountered the same problem recently. I know its late but for the sake of the internet this is what worked for me.
I (eventually) found the answer here
So there's was two parts, first where the data is updated you first get the hierarchical view of what nodes are open and restore this after the grouping collection has been updated:
var hierarchical:IHierarchicalCollectionView = advDatagrid.dataProvider as IHierarchicalCollectionView;
var openNodes:Object = hierarchical.openNodes;
grpCollection.refresh();
advDatagrid.dataProvider = grpCollection;
advDatagrid.validateNow();
IHierarchicalCollectionView(advDatagrid.dataProvider).openNodes = openNodes;
But the other part was to set a grouping object function.
So in your grouping definition:
<mx:GroupingCollection2 id="grpCollection" source="{data}">
<mx:Grouping groupingObjectFunction="grpObjFunc">
<mx:GroupingField name="name"/>
</mx:Grouping>
And a simple function to return a unique id:
private function grpObjFunc(value:String):Object
{
return {uid:value};
}