Windows Phone 7: How to notify data is updated in ListBox? - windows-phone-7

I have above scenario: If user click on ListBox, it will either have sub items (again ListBox) or detail view.
So what i did currently is: Whenever user clicks any item, made a web call and filled up the same ListBox if clicked item is having further sub items.
Now, issue comes in picture:
Suppose i am in 4th screen (detail view),
Moved to the 3rd and 2nd screen with data maintained as stack (Its working fine, yes i am maintaining data in ObservableCollection<ObservableCollection<MyObjects>> so while moving back, i am fetching data from there)
Now, if i click any item in screen 2, it will open detail view for the screen 3 ListBox data.
Means that ListBox is not getting notified that we have filled data inside OnBackKeyPress()
FYI, i am filling up ListBox and WebBrowser in the same page., so my problem is that how do i notify ListBox once i filled up data from stack which i have maintained?
Yes i have also implemented INotifyPropertyChanged but don't know why its not working.
Please check my code:
ListBox and WebView screen: http://pastebin.com/K1G27Yji
RootPageItem class file with the implementation of INotifyPropertyChanged: http://pastebin.com/E0uqLtVG
sorry for pasting code in above way, i did as question is being long.
Problem:
How do i notify ListBox that data is changed from OnBackKeyPress?

And what is the behavior if you set:
listBox1.ItemsSource = null;
before
listBox1.ItemsSource = listRootPageItems;

This is just wrong architecture. Instead of reloading the same listbox, please add a single page for each screen. Share data between them inside the App class (internal static) and use the built in navigation stack for handling "going back". Don't override OnBackKeyPress for this purpose.
You will get your desired functionality for "free" with easier to maintain and use codebase.

Oops it was a silly mistake i made.
I forgot to set items[] array inside OnBackKeyPress() but was accessing while clicking item, hence its having items[] data of last step we moved in forward direction, it was executing the same data.
Now, i have just included a single line and it has solved my problem.
items = listRootPageItems.ToArray(); // resolution point
So final code of onBackKeyPress() is:
/**
* While moving back, taking data from stack and displayed inside the same ListBox
* */
protected override void OnBackKeyPress(CancelEventArgs e)
{
listBox1.Visibility = Visibility.Visible;
webBrowser1.Visibility = Visibility.Collapsed;
listBox1.SelectedIndex = -1;
if (dataStack.Count != 0)
{
listBox1.ItemsSource = null;
listRootPageItems = dataStack[dataStack.Count-1];
listBox1.ItemsSource = listRootPageItems;
items = listRootPageItems.ToArray(); // resolution point
dataStack.Remove(listRootPageItems);
e.Cancel = true;
}
}

Related

How to detect if user requested a tab change on TabbedPage in Xamarin.Forms

I have a Xamarin.Forms application which uses a TabbedPage, let's call it T, T consists of 3 ContentPage children A, B and C. Since the usere has the possibility to edit some data on tab B, I want to notify user before leaving tab in order to allow him to cancel the navigation change and save changes first or to discard changes and leave. So far I have managed to override OnBackButtonPressed() method and the navigation bar back button (which would exit TabbedPage). However I quickly noticed that I am still loosing changes when switching between tabs. I would like to override the click on new tab, so I could first present user with the leaving dialog and the skip the change or continue with it. What would be the best way to do this? I am currently working only on Android platform, so solutions on the platform level are also acceptible.
Thank you for your suggestions and feedback :)
I do not think there is an easy way to do this ,
you can use OnDissappearing and OnAppearing for the pages, that is as easy as it gets .
However I think you are using the wrong design.
Having tabs are ment to make it easier to navigate between pages, if you are going to notify the user when changing the tabs then it would be annoying . If I were you i would save the data for each page locally. so when you get back to the page you will have the data anyway.
So in the end I followed the advice of Ahmad and implemented the persisting of data on individual tabs so they are not lost when tabs are switched. (I no longer refresh input fields from data from model when OnAppearing is called).
But in order to know if there are some unsaved changes on my ChildB page, I had to implement the following procedures:
I created the method HandleExit on my ChildB page, which checks for unsaved changes in fields (at least one value in input fields is different from the ones in stored model) and the either prompts the user that there are unsaved changes (if there are some) or pops the navigation stack if there are no changes.
private async Task HandleExit()
{
if(HasUnsavedChanges())
{
var action = await DisplayAlert("Alert", "There are unsaved changes, do you want to discard them?", "Discard changes", "Cancel");
if(!action)
{
return;
}
}
await Navigation.PopAsync();
}
Since there are two ways on how user can return from Tabbed page (pressing the back button on device or pressing the back button in navigation bar, I had to:
A: override the back button method on my ChildB page, so it calls the HandleExit method. But since Navigation.PopAsync() needs to be called on UI thread, I had to explicitly execute the method on UI thread as written below:
protected override bool OnBackButtonPressed()
{
Device.BeginInvokeOnMainThread(new Action(async () =>
{
await HandleExit();
}));
return true;
}
B: Since there is no way to intercept the navigation bar back button on the ContentPage, I had to intercept the event on the platform level (Android) and then pass the event to the ContentPage if necessary via MessagingCenter. So first we need to intercept the event, when navigation bar button is pressed in one of the child pages and send the event via MessagingCenter. We can do that but adding the following method in our MainActivity.cs class:
public override bool OnOptionsItemSelected(IMenuItem item)
{
// check if the current item id
// is equals to the back button id
if (item.ItemId == 16908332)
{
// retrieve the current xamarin forms page instance
var currentpage = Xamarin.Forms.Application.Current.MainPage.Navigation.NavigationStack.LastOrDefault();
var name = currentpage.GetType().Name;
if(name == "ChildA" || name == "ChildB" || name == "ChildC")
{
MessagingCenter.Send("1", "NavigationBack");
return false;
}
}
return base.OnOptionsItemSelected(item);
}
Now whenever we will press the navigation bar back button in one of the child pages (ChildA, ChildB, ChildC) nothing will happen. But the button will work as before on the rest of the pages. For the second part of solution we need to handle the message from MessagingCenter, so we need to subscribe to it in our ChildB page. We can subsribe to the message topic in OnAppearing method as follows:
MessagingCenter.Subscribe<string>(this, "NavigationBack", async (arg) => {
await HandleExit();
});
Be careful to unsubscribe to the topic in OnDisappearing() otherwise strange things could happen, since there will be references left to your ContentPage even if you pop it from your navigation stack.
Now that we have handled both requests for back navigation in our ChildB page, we also need to handle them in all of remaining child pages (ChildA, ChildC), so they will know if there are unsaved changes in ChildB page, even if it is currently not selected. So the solution is again compraised of handling the device back button, and navigation bar back button, but first we heed a way to check if ChildB has unsaved changes when we are on one of the remaining pages, so we again write HandleExit method but this time it is as follows:
private async Task HandleExit()
{
var root = (TabbedPage)this.Parent;
var editPage = root.Children.Where(x => x.GetType() == typeof(ChildB)).FirstOrDefault();
if(editPage != null)
{
var casted = editPage as ChildB;
if (casted.HasUnsavedChanges())
{
var action = await DisplayAlert("Alert", "There are unsaved changes, do you want to discard them?", "Discard changes", "Cancel");
if (!action)
{
return;
}
}
}
await Navigation.PopAsync();
}
The only thing that remains now is to handle both navigation back events inside remaing child pages. The code for them is the same as in the actual ChildB page.
A: Handling the device back button.
protected override bool OnBackButtonPressed()
{
Device.BeginInvokeOnMainThread(new Action(async () =>
{
await HandleExit();
}));
return true;
}
B: Subscribing to topic from MessagingCenter
MessagingCenter.Subscribe<string>(this, "NavigationBack", async (arg) => {
await HandleExit();
});
If everthing has been done correctly, we should now be prompted with a dialog on any of the child pages if there are unsaved changes on the ChildB page. I hope this will help somebody in the future :)

Windows Form Listview is not visually showing selected items

We have a tool that is being integrated into our application. We have some strict borders around us too in that we cannot modify the application except for our extensions. I have searched here, I've searched the internet, but cannot find any postings about this problem.
I have a Windows Form that contains a ListView and our user requires we create a checkbox to Select/Deselect all. I have the event handler for when the check box state changes and call the routine to set everything to Selected.
private void SelectAllEventHandler(object sender, EventArgs e)
{
ChangeState(RadCapListView, SelectAllRadcap.Checked);
}
private void ChangeState(SWF.ListView control, bool state)
{
if (control.CheckBoxes)
{
control.Items.OfType<SWF.ListViewItem>().ToList()
.ForEach(item => item.Checked = state);
}
else
{
control.Items.OfType<SWF.ListViewItem>().ToList()
.ForEach(item => item.Selected = state);
}
control.Refresh();
}
Going into debug mode all items are marked as selected.
Also at the control level SelectedItems is properly updated.
The issue is that visually the control just will not highlight the selected items like we have our WPF forms doing. As you can see in the code I also tried to refresh the control hoping that would show items selected, but no joy.
Has anyone solved this problem in getting selected items to display properly?
Thank!
Instead of using control.Refresh(), try control.Focus().

Page navigation - keeping data on previous page

In a windows phone app,a page is navigated to another page and on pressing back button, goes back to previous page.Now in previous page the previous data has to be displayed. But its not displaying immediately and takes some time to load. How to solve this issue?
Overall the question is how to maintain the content of page(containing dynamic data) displayed in back navigation?
Use PhoneApplicationService class to keep the data while you navigate between the pages. Here is some samples. Actually, it's very easy:
protected override void OnNavigatedFrom(NavigationEventArgs args)
{
if (ContentPanel.Background is SolidColorBrush)
{
Color clr = (ContentPanel.Background as SolidColorBrush).Color;
if (args.Content is MainPage) (args.Content as MainPage).ReturnedColor = clr;
// save color
PhoneApplicationService.Current.State["Color"] = clr;
}
base.OnNavigatedFrom(args);
}
protected override void OnNavigatedTo(NavigationEventArgs args)
{
// restore color
if (PhoneApplicationService.Current.State.ContainsKey("Color"))
{
Color clr = (Color)PhoneApplicationService.Current.State["Color"];
ContentPanel.Background = new SolidColorBrush(clr);
}
base.OnNavigatedTo(args);
}
How to preserve and restore page state for Windows Phone
You can use single ton object of View Model to store data in a page provided all the controls are bind to a property in View Model.
Then if you do not clear the values of the controls when navigating away from the page, the data will be displayed in the page when you navigate back to that screen provided all the controls

Windows Phone 7 Selection_Changed automatically

currently I'm developing an app for WP7 but came across a little problem with a Listbox event call Selection_Change. The problem is that when i return to the page that contains the listbox the selection_change event triggers without being changed at all or without any user input. The listbox code is similar to this:
private void lsbHistory_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
int index = lsbHistory.SelectedIndex;
NavigationService.Navigate(new Uri("/Views/NextPage, UriKind.Relative));
}
On the page I navigate to, the only way out of the navigated page is by pressing back button or start button meaning that it will return to the page that contains the listbox. When I Navigate back the selection change triggers leading me sometimes to a exception. Has anyone been through this before?
Consider always checking if it's -1 (the default value).
private void lsbHistory_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
int index = lsbHistory.SelectedIndex;
if (index != -1)
{
NavigationService.Navigate(new Uri("/Views/NextPage, UriKind.Relative));
lsbHistory.SelectedIndex = -1; // Set it to -1, to enable re-selection.
}
}
Also, you should consider wrapping the Navigate call in Dispatcher.BeginInvoke to have a better, more smooth, page transition.
The event will be fired when the list is populated.
The simplest solution for you will probably be to add a check that there is nothing selected before triggering your navigation:
if (lsbHistory.SelectedIndex > -1)
{
// do navigation
}
One thing to notice is that when you navigate back to the page which containt the ListBox, the ListBox still has the SelectedItem set to the value it had when the user navigated away. This means that lsbHistory.SelectedIndex will get the index of the item which was selected when the user navigated forward.
Maybe there's something in your code which presumes that the ListBox's SelectedItem is null when the user navigates to the page?

Programmatically slide to next Panorama item

Is it possible to programmatically move from one panorama page/item to the next and get the same kind of animated sliding effect you get when sliding with a finger?
I can use the PanoramaControl.DefaultItem property to move to the expected item/page, but you won't get the animated sliding effect. Any ideas here?
Its possible, just put the setting of the DefaultItem between a SlideTransition Completed event and you are done:
public static class PanoramaExtensions
{
public static void SlideToPage(this Panorama self, int item)
{
var slide_transition = new SlideTransition() { };
slide_transition.Mode = SlideTransitionMode.SlideLeftFadeIn;
ITransition transition = slide_transition.GetTransition(self);
transition.Completed += delegate
{
self.DefaultItem = self.Items[item];
transition.Stop();
};
transition.Begin();
}
}
Use my_panorama.SlideToPage(1) to slide to the second page.
You can use below code :
panoramaRoot.DefaultItem = (PanoramaItem)panoramaRoot.Items[1];
it is not programatically possible to change the selected index of a panorama control. As you mention the only way of setting the index is using the DefaultItem property which is only useful when navigationg to the page which contains the panorama.
Here is another post that discusses it.
I think the easiest way to achieve this would be to create separate visual states for each item and create animated slide transitions for transitioning to each state. Then you can use VisualStateManager.GoToState(<page>, <state>, true); to initiate the state change.
No - the panorama control doesn't support programmatic manipulation like this.
If you want an experience like this, then you could try a hand-written panorama control - e.g. http://phone.codeplex.com/

Resources