Updating ObservableCollection bound to Pivot crashes for SelectedIndex >= 2 (wp7) - windows-phone-7

I have an observableCollection bound to a pivot control in my UI. When I try to update the collection (clear() items and recreate) everything works fine unless the selectedIndex of the pivot control is bigger or equal to 2.
In that case I get an ArgumentOutOfRange exception when I try to call Add() to the observable collection. It is very strange.
I tried creating a new observable collection and then Add() items there, this seems to work but I doesn't refresh the UI unless I call my update function twice.
What can be wrong? Is this a bug?

This is a known issue.
Unhandled Exception When Setting Pivot Control SelectedItem/SelectedIndex Property to 3rd Pivot Item (WP7)
Not deferring navigation/(binding?) to the loaded event is a workaround.

You have probably alreade solved it but here is what I did.
As mentioned before this is a known "bug"/limitation.
You can however set the SelectedIndex in the Loaded event for the page.
See here:
http://christian-helle.blogspot.com/2011/02/working-around-pivot-selectedindex.html
This helped me and it works just fine now =)

To try and save load and performacne overhead, the framework only loads the currently displayed pivot and the ones either side. The other items are delay loaded when the neighbouring item is displayed. As a consequence of this you can experience issues when trying to set the SelectedItem to an item which hasn't been loaded or the page hasn't finished loading fully.
If you can share some code to demonstrate what you're trying to do we may be able to provide some more specific help.

As mentioned. The Pivot control is optimised to not load all the panels. If you are trying what I think you're trying, then I would suggest you switch to a Panorama Control which initalises all the PanoramaItems.

I'm going to try the fix suggested #Jimmy Engtröm. However, I've also been able to work around this by waiting until the Load has occurred.
<controls:Pivot x:Name="pivotCtrl" Title="{Binding ApplicationTitle}"
Loaded="OnPivotControlLoaded" Opacity="1">
And in the page's code behind:
private void OnPivotControlLoaded(object sender, RoutedEventArgs e)
{
// Restore the Pivot control's SelectedIndex
if (State.ContainsKey(SelectedPivotIndexKey))
{
pivotCtrl.SelectedIndex = State.Get<int>(SelectedPivotIndexKey);
}
myStoryboard.Begin();
}
Now, why the Storyboard? Well, when you wait until Load, you will see the first pivot and that's lame. So the Storyboard does a quick fade-in...just enough to disguise the fix. I tried just setting Visibility, but that would crash the app. Also note that, for design purposes, I leave Opacity set to 1 in the pivot control's XAML. Here's the Storyboard:
<Storyboard x:Name="myStoryboard">
<DoubleAnimation
Storyboard.TargetName="pivotCtrl"
Storyboard.TargetProperty="Opacity"
From="0.0" To="1.0" Duration="0:0:01"
/>
</Storyboard>
Here are the helper functions (placed in a separate class file and referenced, e.g. using MyApp.Helpers and the class file needs to reference System.Collections.Generic)
public static T Get<T>(this IDictionary<string, object> dictionary, string key)
{
return (T)dictionary[key];
}
public static void AddOrReplace<T>(this IDictionary<string, T> dictionary, string key, T item)
{
if (dictionary.ContainsKey(key))
dictionary.Remove(key);
dictionary.Add(key, item);
}
Again, it's not the greatest fix but it works alright and the fade-in is actually something I might employ elsewhere.

Related

Xamarin custom control in a datatemplate created with CreateContent()

I have implemented a custom DataTemplateSelector according to: GitHub Xamarin Forms.
This allows for a datatemplate to be selected based on an item, which is received through a data binding. This works fine to select a proper datatemplate and render it. However, I am now at a point where I want to add custom controls to this datatemplate (custom buttons). This works on any other page, but for some reason not in this datatemplate.
The relevant lines in the datatemplate:
xmlns:controls="clr-namespace:Universal_ONE.Views.Controls"
<controls:IconButton Command="{Binding RobotLocationSave}"
Image="{StaticResource BoltBlack}"/>
The part the datatemplate is selected and created:
var templateToUse = templateSelector.SelectTemplate(item, null);
View view = (View)templateToUse.CreateContent();
view.BindingContext = bindingContext;
In the catch block I get the error (after calling CreateContent()):
Xamarin.Forms.Xaml.XamlParseException: Position 371:26. Type controls:IconButton not found in xmlns clr-namespace:Universal_ONE.Views.Controls
The problem has to be with the CreateContent(), since using the control outside of this datatemplate works.
EDIT 1
I've created a Minimal Working Example: GitHub.
The datatemplate is now hardcoded, so I'm sure that one is selected.
The same parseexception gets thrown.
I am trying to compile Xamarin.Forms myself so I can debug the framework itself. However, compiling is not going smooth; thus might take a bit longer.
EDIT 2
A bit more info on the MWE:
The MainPage.xaml has the default Xamarin.Forms app code. Below I've added a custom control, which is simply a frame with a label. The text of this label is set via a bindableproperty (not really relevant). Below this control is the datatemplatecontrol added, which in turn calls the datatemplateselector, which returns the datatemplate. This datatemplate contains the same control as the mainpage. Thus the rendered app should have 2 controls. However, in de datatemplatecontrol you can breakpoint on the catch statement (look for my comment). This is where the parseexception will show, which is caused after calling CreateContent() on the datatemplate.
EDIT 3
I've not been clear enough I think. But you have to put a breakpoint on line 41 of datatemplatecontroler.cs. Since the content of the datatemplate is set to null if the createcontent() fails, thus fails gracefully. When hitting the breakpoint you can read the parseexception.
EDIT 4
I've made an issue and a pull request on the Xamarin repository: GitHub. The problem resides with the XamlParser, which has a small bug where it selects the wrong assembly. Inside the issue I've mentioned a workaround which can be used for now.
I try to download your example and run it. When I run to Content = CreateTemplateForItem(ItemTemplate); the program does not report an error, so the program skips the catch method. Here is a screenshot of the runtime:
Then I tried to actively throw an exception in the try statement (throw new Exception();) to make the program enter the catch method, Here is a screenshot of the runtime:

Load a Xaml inside a stacklayout in Xamarin forms

I have a Content View in Xamarin forms with 2 StackLayout horizontally aligned in it. I want to change the Content of the 2nd Stacklayout dynamically, but I don't want to use Master details page in that case. Please find an attachment to see what my UI looks like. I want to load different pages in StackLayout 2 on button clicks in StackLayout 1.
Update: I want to achieve above using MVVM.
You specifically said you want to load different pages inside Stacklayout2.
Out of the box, this is not possible. A Page can not be nested inside another view in Xamarin.Forms*
However, you most likely don't need to nest a whole page either, which is good news.
You can create custom xaml views as separate xaml files, and then reference them like regular controls. For example you would create a xaml file MyDataView, inside you could use a and fill it out with your different labels, entries and what nots, then instantiate and add that MyDataView inside your page like you would any other control.
For your host page, I would recommend you change your StackLayout2 to a ContentView, as it will only ever contain one view, which in case of something like your custom "MyDataView" from above, would actually contain the stack layout and all the details.
From the point of view of your page, it has the left layout with all the buttons, and it has the "container" on the right to host different complex views. So it does not need a stacklayout there.
There is also an important decision that you need to make on when and how you want to instantiate all the views that you will host inside the right pane.
You might choose to instantiate them all at once, when loading the page, if there aren't too many. Then switchig to each one during page use should be quite quick. Something like this:
public partial class MainPage
{
private MyDataView myDataView = new myDataView();
private OtherView otherView = new OtherView();
private ThirdView thirdView = new ThirdView();
public void OnSomeButtonClick(object sender, EventArgs e)
{
Container.View = myDataView; //Container would be the x:Name you gave to your ContentView control on the right side of the page that will host the different views
}
}
Or you might prefer to "lazy" instantiate the views, which would only instantiate the view the first time it is navigated to. This is useful if some views will never actually be accessed, and you can save some cpu cycles and ram by not loading the view until it's needed. The downside of course is when you do load it for the first time, it will load slower. Like this:
public partial class MainPage
{
private MyDataView myDataView;
private OtherView otherView;
private ThirdView thirdView;
public void OnSomeButtonClick(object sender, EventArgs e)
{
if (myDataView == null) myDataView = new MyDataView();
Container.View = myDataView; //Container would be the x:Name you gave to your ContentView control on the right side of the page that will host the different views
}
}
And finally you can instantiate the view everytime it is needed. I would not go with this route in most cases. Unless you really specifically need to recreate the entire view each time (like if you are dynamically changing it during use, and you need to reset it when it's shown again)
*I have seen a custom renderer implementation that nested a whole page inside a view on github. I did not test it as it was not completely implemented at the time.
Cant you do something like this:
button1.OnClick += (sender, args) =>{
StackLayout2.Children.Remove(currentview);
StackLayout2.Children.Add(newview);
}
Not sure if its called onclick or clicked.

MvxSpinner Initial Value

I have an MvvmCross MvxSpinner binding in Android. The user selects a value and that gets reflected in my property MealTypeSelected.
<MvxSpinner
android:layout_width="match_parent"
android:layout_height="wrap_content"
local:MvxBind="ItemsSource MealTypeList;SelectedItem MealTypeSelected, Mode=TwoWay" />
The spinner is to allow the user to select the meal type (breakfast, lunch, dinner, etc). The meal type is represented by an Enum called MealType.
public enum MealType {Unspecified, Breakfast, Lunch, Dinner, Snack};
I want to make it easier on the user by initializing the spinner to a value based on the time of day when the ViewModel is shown. So if the page is loaded at noon, I'll guess that the selection should be "Lunch" for example.
The problem is, I have tried setting the MealTypeSelected property in the ViewModel at different locations of the life cycle: constructor, Init, and Start. But regardless of what I do, when the View loads, it changes the selection back to the default value of the Enum, which is the value "Unspecified".
Is there a way around this behavior and have the MvxSpinner initialized to specific value?
Try to add this to your "Setup.cs" file. I had the same struggle, and this worked for me. Cant remember where i found the solution the first time.
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
MvxAppCompatSetupHelper.FillTargetFactories(registry);
base.FillTargetFactories(registry);
}
I had a similar problem when binding the SelectedItem of an MvxSpinner to my ViewModel, while also setting it in the ViewModel.
Overriding the Equals of the type that I was binding to fixed this problem.
public override bool Equals(object obj)
{
return this.Id == (obj as MyType)?.Id;
}
Since you seem to be binding directly to an enum, you might have to try and wrap it in another object.
Stack Overflow won't let me accept pnavk's comment as an answer but it is what I ended up doing. Each time the user selected something, I would change the first item to the list to what was selected. An ugly solution but it worked. If that's not acceptable to others, it looks like you might have to do some custom binding.

No warning or error when using (unsupported) event handler inside DataTemplate

Scenario
Consider the following XAML inside a view of a Silverlight application:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" MouseLeftButtonDown="Item_Clicked" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The event handler is not called. Apparently, event handlers do not work inside DataTemplate.
There are no warnings or errors, neither at compile-time nor at runtime. Also, the MSDN DataTemplate reference does not specify any restrictions regarding event handlers.
IntelliSense even "helps" me with <New Event Handler> and Navigate to Event Handler. The handler is placed in the code behind of the view.
Questions
Is the behavior specified by Microsoft?
Is it a known bug in VS2010?
Why doesn't the compiler emit an error or
at least a warning?
Remark
I know how to solve the issue since the problem is reported quite often on this site. I want to know why this fails silently.
"Silent error" never gets you very far when your write .NET code, errors are never silent. I'll describe a general way you tackle issues like this, getting you to the "why" automatically and giving you a chance to google a solution.
An important implementation detail you need to know is how XAML is processed when your app is built. It is interpreted by a code generator, it auto-generates C# code which is stored in the obj\Debug directory of your project. After you build, you'll find *.g.cs files there. Take a look at them, you shouldn't have too much trouble making sense of them, the code that was auto-generated from the markup is annotated with #line directives.
Looking at the Silverlight version, MainPage.g.cs, shows little code (edited to fit):
public partial class MainPage : System.Windows.Controls.UserControl {
internal System.Windows.Controls.Grid LayoutRoot;
private bool _contentLoaded;
public void InitializeComponent() {
// etc..
}
}
No sign of the MouseLeftButtonDown event anywhere, this is not encouraging of course.
Do the exact same thing with a sample WPF app. Now take a look at MainWindow.g.cs (again edited to fit and remove the boring parts):
public partial class MainWindow : System.Windows.Window,
System.Windows.Markup.IComponentConnector,
System.Windows.Markup.IStyleConnector {
private bool _contentLoaded;
public void InitializeComponent() {
//...
}
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
//...
}
void System.Windows.Markup.IStyleConnector.Connect(int connectionId, object target) {
switch (connectionId)
{
case 1:
#line 9 "..\..\MainWindow.xaml"
((System.Windows.Controls.TextBlock)(target)).MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(this.Item_Clicked);
//...
break;
}
}
Big difference, now you do see the MouseLeftButtonDown event back. Clearly the IStyleConnector interface is instrumental to get that event subscribed.
So go have a look at the MSDN article for that interface. It is rather brief, this is internal plumbing that you shouldn't have to know about, description is:
Provides methods used internally by the WPF XAML parser to attach events and event setters in compiled XAML.
A complete match for what you are trying to accomplish of course. Most important in that article is the Version Information section. Supported in the .NET Framework, but Silverlight is not listed.
This gives you the "why", the necessary plumbing is simply not available in Silverlight. Otherwise not surprising, the most important attribute of Silverlight is that it is small, allowing for a quick download that doesn't slow down the user too much when he browses to a web page that uses it. It can only get small by removing stuff.
So, apparently you are supposed to provide that plumbing yourself. How do you do this? The inspired google query is silverlight datatemplate event. The very first hit is a winner, a question that tries to do exactly what you are doing. Lots more relevant hits available if you need more help.

Passing data from page to page

I'm looking for the best practice on how to pass data from page to page.
In Page A I have a button that fires off Page B.
On Page B I have 6 textboxes that allow the user to enter information.
When the user is done, the click on a button that brings them back to Page A.
I want to pass that data back to Page A.
I've seen suggestions to:
build XML documents and save to Isolated Storage
use the App class to store information in properties
pass it like a query string
I'm looking for the Best practice. Is there one that Microsoft recommends or one that is generally accepted as the best way?
Thanks
PhoneApplicationService.Current.State["yourparam"] = param
NavigationService.Navigate(new Uri("/view/Page.xaml", UriKind.Relative));
then in other page simply
var k = PhoneApplicationService.Current.State["yourparam"];
Personally I'd store the values entered on Page B in a model(object) that is also accessible to Page A.
Depending on how you're navigating to Page A the second time, one or more of the following may be usful to help understand passing values between pages:
How to pass the image value in one xaml page to another xaml page in windows phone 7?
Passing a complex object to a page while navigating in a WP7 Silverlight application
How to pass an object from a xaml page to another?
How to pass a value between Silverlight pages for WP7?
How do I navigate from one xaml page to another, and pass values?
One thing you can consider is to use MVC: let your App be the controller, store all data in the model, and the pages are just views that contains pure UI logic. In this case your pages are painters and you pass your model object around. This gives nice isolation of business logic and the UI so that you can rev them easily.
BTW, Silverlight and XAML are great tools for MVC so it's a natural match.
There's a couple of things at play here. First of all, if/when the user uses the Back button to return to page A instead of your button, is the information in the text boxes exchanged or not (is Back = Cancel, or is Back = OK?)
That said, if you're using NavigationService.GoBack (which you should be instead of NavigationService.Navigate, because if you use the Navigate call, repeated hits of the back key will cause all kinds of bad UX for your users), then QueryStrings are not an option. Because pages really have no way to reference each other in the WP7 Silverlight nav system, you need to use a 3rd party to hold your data. For that, you can turn to (a) Isolated Storage (slow & heavy, but fail-safe), (b) Use the PhoneApplicationService.State dictionary, or (c) use Global properties of some kind, either hung off of the application object, or using Statics/Singletons...
Remember to watch for Tombstoning behavior when you do this - your page will process the OnNavigatedTo method when (a) you navigate into it in your application (b) you navigate back to it when you complete your work on Page B, or (c) you tombstone your app from that page and return to your application using the Back key.
Sorry I didn't give a more direct answer there - a lot depends on your specific circumstances. In the most general case, I'd strongly consider using the App State Dictionary on the PhoneApplicationService...it is lightweight, easy to use, and survives tombstoning. Just be sure that your keys are as unique as they need to be.
If you create a new Windows Phone project and use the Windows Phone Databound Template you will have most of the work done for you.
What you will want to do is set up the ViewModel to contain all the data for your app. You can serialize and deserialize this data using IsolatedStorage so that it's saved across application sessions and when Tombstoning.
In the template you will notice MailViewModel and ItemViewModel. MainViewModel stores all the data your application needs including an ObservableCollection of ItemViewModel, and ItemViewModel represents the individual data type for your application.
On the DetailsPage.xaml page you'll want to DataBind each textbox to the App.MainViewModel Items. Set the binding to TwoWay if you want the ViewModel to get updated as soon as the user manipulates the data on DetailsPage.xaml. You can optionally set the Binding to OneWay and then have an OK button that writes the changes back to the ViewModel and saves to IsolatedStorage.
Here is an example of what a Binding looks like:
<TextBlock x:Name="ListTitle" Text="{Binding LineOne}" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
In this case LineOne is a property in ItemViewModel and the page gets this data from the query string when the user selects an item from the MainPage.xaml. The DataContext for the page determs where the databound information comes from.
Here is the snippet where the MainPage passes the selected item from the ViewModel to the DetailsPage.
// Handle selection changed on ListBox
private void MainListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// If selected index is -1 (no selection) do nothing
if (MainListBox.SelectedIndex == -1)
return;
// Navigate to the new page
NavigationService.Navigate(new Uri("/DetailsPage.xaml?selectedItem=" + MainListBox.SelectedIndex, UriKind.Relative));
// Reset selected index to -1 (no selection)
MainListBox.SelectedIndex = -1;
}
Here is how the DetailsPage gets the selected item.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
string selectedIndex = "";
if (NavigationContext.QueryString.TryGetValue("selectedItem", out selectedIndex))
{
int index = int.Parse(selectedIndex);
DataContext = App.ViewModel.Items[index];
}
}
Play around with the default template above and ask any additional questions.
The beauty of databinding and the ObservableCollection is that you can just update the data and the UX will reflect those changes immediatley. This is because any changes to the data fires off an event:
public string LineOne
{
get
{
return _lineOne;
}
set
{
if (value != _lineOne)
{
_lineOne = value;
NotifyPropertyChanged("LineOne");
}
}
}
NotifyPropertyChanged() that broadcasts this information to the View.
You can also keep it simple and use PhoneApplicationService.Current.State which is basically a hashtable. You will need to implement your own marshalling to and from isolated storage if you want anything to outlive the app.
Omar's suggestion to use the Windows Phone Databound Template is probably the best idea on this page. It amounts to the same as my suggestion but you will get a better result (more maintainable code) at the cost of a longer steeper learning curve.
I suggest you do it my way and then do it again Omar's way.
as i implemented like this.. Whether its correct or not i dont know..
When u click news list page it should open the news detail page.
I want to pass the selected news item contents from news List-Page to news-details Page.
the News list page contains following method.
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
NewsDetailsPage newsDetailPage = (e.Content as NewsDetailsPage);
if (newsDetailPage != null)
newsDetailPage.SelectedNewsItem = SelectedNewsItem; //Contains the news details
base.OnNavigatedFrom(e);
}
In the News details Page. U can access that(SelectedNewsItem) object.
This may or may not be correct.
One option is to use Application.Resources:
Store data:
Application.Current.Resources.Add("NavigationParam", customers);
NavigationService.Navigate(new Uri("/Page2.xaml", UriKind.Relative));
Retrieve data:
var customers = (List<Customer>) Application.Current.Resources["NavigationParam"];
Here's a blog post with describes this in more detail: http://mikaelkoskinen.net/windows-phone-pass-data-between-pages-application-resources/ (author: me)

Resources