I'm building my first full-scale Xamarin.Forms app and trying to figure out how to keep user input between navigation. After doing some searching online I've read that the default behavior is to completely reload pages each time you navigate, but you can change the default behavior by setting the NavigationCacheMode to true or required, but I've tried to set this attribute in both xaml and C# with no success - it seems like the property is not recognized.
Is there a simple way to make it so that user input does not disappear when navigating between pages? If anyone can show me how to set the NavigationCacheMode that would be great, but I'm also open to any reasonable solution that will keep the user input from disappearing during navigation.
Additional details: My app has a UWP and Android project. I am using a master detail page for navigation. Here is my MenuList_ItemSelected event handler:
private void MenuList_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var item = (MenuItem)e.SelectedItem;
var title = item.Title;
var page = (Page)Activator.CreateInstance(item.TargetType);
Detail = new NavigationPage(page); //TODO: when menu item is clicked and you're already on that page, the menu should just slide back. (currently it does nothing and stays out).
IsPresented = false;
}
Finally was able to solve this! I adapted this code from a related post which implements a Dictionary that keeps track of the navigation stack:
In the constructor for my Master Detail Page:
public partial class MenuPage : MasterDetailPage
{
Dictionary<Type, Page> menuCache = new Dictionary<Type, Page>();
}
Then in the ItemSelected method:
private void MenuList_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (menuCache.Count == 0)
menuCache.Add(typeof(AttendancePage), Detail);
var item = (MenuItem)e.SelectedItem;
if (item != null)
{
if (menuCache.ContainsKey(item.TargetType))
{ Detail = menuCache[item.TargetType]; }
else
{
Detail = new NavigationPage((Page)Activator.CreateInstance(item.TargetType));
menuCache.Add(item.TargetType, Detail);
}
menuList.SelectedItem = null; //solves issue with nav drawer not hiding when same item is selected twice
IsPresented = false;
}
}
Related
I need to hide the hamburger menu on certain pages but still display information in then navbar. I don’t know of any way to accomplish this.
Also, I need the navbar to stay fixed to the top of the screen but it’s getting cut off when the keyboard pops up.
How can I go about this?
FlyoutPage.ShouldShowToolbarButton method is used to determine whether to show/hide hamburger icon , and it is triggered every time when selecting pages.
We can define a bool field ,change its value when directing to specific pages.
FlyoutPage
public override bool ShouldShowToolbarButton()
{
return showIcon;
}
private bool showIcon = true;
private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var item = e.SelectedItem as FlyoutPage1FlyoutMenuItem;
if (item == null)
return;
var page = (Page)Activator.CreateInstance(item.TargetType);
page.Title = item.Title;
Detail = new NavigationPage(page);
IsPresented = false;
FlyoutPage.ListView.SelectedItem = null;
//add this logic
showIcon = (item.Id == 1) ? false : true; //only the second page do not show hamburger icon
}
I hope there is a simple answer to this question because I have been beating my head against the wall for some time now over it, and am ready to move on from trying to achieve what I think should be a relatively easy process.... LOL
What I am trying to do, is populate a templated MasterDetail page from VS2019 with my own views from a project I worked on previously.
After some time I decided to go about loading the views into the master detail using this switch statement
public bool OnNavigationItemSelected(IMenuItem item)
{
int id = item.ItemId;
LinearLayout alt = FindViewById<LinearLayout>(Resource.Id.LLTarget);
LayoutInflater layoutInflater = (LayoutInflater)BaseContext.GetSystemService(Context.LayoutInflaterService);
View addView = null;
if (id == Resource.Id.nav_add)
{
alt.RemoveAllViewsInLayout();
addView = layoutInflater.Inflate(Resource.Layout.addDevice, null);
}
else if (id == Resource.Id.nav_list)
{
alt.RemoveAllViewsInLayout();
addView = layoutInflater.Inflate(Resource.Layout.listDevices, null);
}
... so on and so on...
if (addView != null)
alt.AddView(addView);
DrawerLayout drawer = FindViewById<DrawerLayout>(Resource.Id.drawer_layout);
drawer.CloseDrawer(GravityCompat.Start);
return true;
}
The listDevice and addDevice items are XAML pages, with associated view Classes that manipulate, and post-get data from the view,
Here is my problem,
While the code I have here, works beautifully to show the XAML pages. It will NOT call the onCreate() method for the class.
If I load the pages using SetContentView() or StartActivityForResult() I lose the ability to post the view as a sub page of the MasterDetail layout.
Here's a smple of my class layout
namespace TestApp2
{
[Activity(Label = "#string/add_device")]
public class AddDevice: Activity
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
Spinner spinSubType;
Button btnAdd;
EditText ebModel;
AutoCompleteTextView acMfg;
EditText ebSerial;
ImageButton btnPhoto;
int _imgCount = 0;
LinearLayout fsCon;
List<DB.Photos> PhotoCollection = new List<DB.Photos>();
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
logger.Info("Loading form addDevice");
SetContentView(Resource.Layout.addDevice);
try
{
logger.Info("Capturing controls from addDevice.axml");
spinSubType= FindViewById<Spinner>(Resource.Id.typeSpinner);
acMfg= FindViewById<AutoCompleteTextView>(Resource.Id.acMfg);
btnAdd = FindViewById<Button>(Resource.Id.btnAdd);
ebSerial = FindViewById<EditText>(Resource.Id.edSerial);
btnPhoto = FindViewById<ImageButton>(Resource.Id.imageButton1);
btnPhoto.SetImageResource(Resource.Drawable.ic_camera);
btnPhoto.Click += BtnCamera_Click;
fsCon = FindViewById<LinearLayout>(Resource.Id.fsCon);
}
catch (Exception e)
{ logger.Error(e.Message); }
try
{
logger.Info("Populating spinner for types");
LoadStyles(typeSpinner);
LoadMfg(ebMfg);
}
catch (Exception ex)
{ logger.Error(ex); }
btnAdd.Click += BtnAdd_Click;
typeSpinner.ItemSelected += new EventHandler<AdapterView.ItemSelectedEventArgs>(Spinner_ItemSelected);
}
}
I know this has to be something insanely easy and simple I am overlooking, probably expecting it to be overly complex and not seeing the obvious simple answer. Or it is somewhat complex, and my google kungfu is too weak...
At any rate, any advice, tips, or ideas would be MOST appreciated!
Cheers!
Deciding to abandon this idea....
I was using android views, and nesting them within oneanother using a MasterDetail system, but it appears that while this works great for displaying XAML pages, it does not work so well if you are trying to use those pages for data management.
I'm going to start from scratch and build the whole thing using Xamarin.Forms alone.
I'm navigating from one page to another (using Navigation.PushModalAsync). The first page has a list. The second page has a list view. I want to pass on that list to the second page and then populate a list view with data from that list. How do I go about this?
kind regards
UPDATE:
It appears as though my listview isn't appearing. I tried manually setting the itemsource and the next page is still blank. I have this method:
protected override void OnAppearing()
{
base.OnAppearing();
var listView = new ListView();
//listView.ItemsSource = dataSource;
listView.ItemsSource = new string[]{
"mono",
"monodroid",
"monotouch",
"monorail",
"monodevelop",
"monotone",
"monopoly",
"monomodal",
"mononucleosis"
};
listView.RowHeight = 40;
}
But the next page remains blank with just a red background. To add to this, I don't seem to have the listview.ItemSource.Add() method.
Have a look at the Messaging Center in Xamarin Forms here, which is one option: https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/messaging-center/
Or another option is just passing the list in the constructor of the modal you are pushing. In my opinion, using the messaging center is a much cleaner way.
in Page1
List<string> mydata;
var page2 = new Page2(mydata);
Navigation.PushModalAsync(page2);
in Page2
List<string> Data { get; set; }
public Page2(List<string> data) {
this.Data = data;
}
public override void OnAppearing() {
MyListView.ItemsSource = Data;
}
I am trying to get this search page in my app to work properly. Right now, it searches through the ListView well, but the ListView does not link to the other pages in the app. Instead, when each ListView element is tapped, it just brings up a blank page. I think I made a mistake in how I structure the DetailPage class, but I am not sure how to fix it.
This is the code for Search.cs.
This is a Xamarin forum post where I have been discussing this problem. Someone has helped me and they have been very helpful so far.
I have been searching around for answers in the MSDN, but I have not been able to figure it out yet. How do I change the value of the DetailPage parameter to so that it actually opens a selected page instead of just a blank page?
This is the DetailPage class.
class DetailPage : ContentPage
{
public DetailPage(pageList page_list)
{
this.page_list = page_list;
}
public pageList page_list { private set; get; }
}
This is where it the DetailPage object detailPage gets its parameter itemSelected. It just opens a blank page, which is not what I want.
listView.ItemTapped += async (sender, args) =>
{
var itemSelected = args.Item as pageList;
if (itemSelected != null)
{
DetailPage detailPage = new DetailPage(itemSelected);
await Navigation.PushAsync(detailPage, true);
}
};
since you know the Type of the page, you can use Activator.CreateInstance to instantiate it
var itemSelected = args.Item as pageList;
if (itemSelected != null)
{
var page = (ContentPage) System.Activator.CreateInstance(itemSelected.fileName);
await Navigation.PushAsync(page, true);
}
I am working on a windows phone 8.1 universal app. I am pulling a feed and then displaying it in a listbox. Each item in the feed takes you to a web page. I am using WebView control to show the content of the web page when someone clicks one of the items in the listbox view page. I can show the web page in the WebView control, but when I press the hardware back button, it takes me back to the mainpage(where I started from) instead of the listbox view page. How can I go back to the listbox view page so the user can click on yet another item to view that in the WebView control?
here is my XAML:
<WebView Grid.Row="0" Name="webBrowser1" Visibility="Collapsed" Width="auto" Height="auto" Grid.RowSpan="2" NavigationCompleted="Mywebbrowser_LoadCompleted"/>
Here is my selection changed code on the listbox view page that takes the user to the web page in the webview control:
private void SearchListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox listBox = sender as ListBox;
if (listBox != null && listBox.SelectedItem != null)
{
// Get the item that was tapped.
SearchListItem sItem = (SearchListItem)listBox.SelectedItem;
// Set up the page navigation only if a link actually exists in the feed item.
if (sItem.Url.Length > 0)
{
// Get the associated URI of the feed item.
Uri site = new Uri(sItem.Url.Replace("https", "http"));
//Set up the app bar once the feed items are displayed in the web browser control
//appbar();
// Show the progress bar.....
mycontrols.progressbarShow(pgbar, pgText);
//appbar_eh.appbarNoShow(ApplicationBar);
//mycontrols_eh.progressbarShow(pgbar, pgText);
webBrowser1.Visibility = Windows.UI.Xaml.Visibility.Visible;
webBrowser1.Source = site;
}
}
}
Edit added BackPressed handler:
void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e)
{
Frame frame = Window.Current.Content as Frame;
if (frame == null)
{
return;
}
if (frame.CanGoBack)
{
frame.GoBack();
e.Handled = true;
webBrowser1.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
}
Thanks!
How are you currently handling the HardwareButton.BackPressed event?
Since it goes back to your mainpage rather than exiting the app you must have some handler for it (either directly or via library code). You'll need to modify that code to hide your WebView if it's open instead of performing its normal navigation.
If you're using the NavigationHelper functions you can override the GoBack command to do this.
Edit:
Based on your added sample code, here's one possible way. The basic idea is to close the WebView instead of calling Frame.GoBack rather than doing both. The Frame.GoBack call will go back, so if you don't want to navigate then don't call it.
void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e)
{
Frame frame = Window.Current.Content as Frame;
if (frame == null)
{
return;
}
// Depending on the app there may be higher level state than
// directly checking the Visibility property
if (webBrowser1.Visibility == Windows.UI.Xaml.Visibility.Visible)
{
// If webBrowser is open then close it rather than
// navigating to the previous page
webBrowser1.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
e.Handled = true;
}
else if (frame.CanGoBack)
{
frame.GoBack();
e.Handled = true;
}
}