How to access a variable in another page - c#

So I have this page in an UWP Windows 10 app using C#:
public sealed partial class MainPage : Page
This page contains a TextBlock called tbPageTitle.
I'd like to change the text of tbPageTitle to "bla" from another page, so I use the following code:
MainPage.tbPageTitle.text = "bla";
However, I get the following error:
CS0120 An object reference is required for the non-static field, method, or property 'MainPage.tbPageTitle'
I don't know what to do here. I feel like I've read every single Google result.
I found some results to create a new instance of a class, so that would be for example:
MainPage mp = new MainPage();
mp.tbPageTitle.text = "bla";
But wouldn't that create a completely new MainPage? This also doesn't work by the way...

According to the answer to Sandy's comment you have an inner Frame element in the MainPage where you load other pages. So the easiest way to get the current MainPage instance is the following:
MainPage mainPage = (Window.Current.Content as Frame).Content as MainPage;
Note that this will obviously fail if you every navigate outside of the MainPage and call this line there. Additionally note that objects you are creating in XAML are not public, what means that you can't access your tbPageTitle element here anyway, but will need to create any kind of wrapper property in your MainPage like this:
public string PageTitle {
get { return tbPageTitle.Text; }
set { tbPageTitle.Text = value; }
}
However as mentioned by HeySatan, this is not the most beautiful code design you are creating here. Maybe you could create a method to go to a specific frame, something like that:
public enum TabContent { Home, Replies, Messages, Settings }
public void OpenTab(TabContent content) {
// Set Page title and navigate
switch (content) {
case TabContent.Home:
tbPageTitle.Text = "Home";
InnerFrame.Navigate(typeof(HomePage));
break;
case TabContent.Replies:
tbPageTitle.Text = "Replies";
InnerFrame.Navigate(typeof(RepliesPage));
break;
case TabContent.Messages:
tbPageTitle.Text = "Messages";
InnerFrame.Navigate(typeof(MessagesPage));
break;
case TabContent.Settings:
tbPageTitle.Text = "Settings";
InnerFrame.Navigate(typeof(SettingsPage));
break;
}
}
Main goal of this method is that if you have a button Settings in your HomePage you are only calling the following line and all logic to do the navigation stays in the MainPage and HomePage only has logic related to itself:
// In HomePage:
MainPage mainPage = (Window.Current.Content as Frame).Content as MainPage;
mainPage.OpenTab(TabContent.Settings);
If you don't want to access Window.Current.Content all the time, you could also declare a static method in your MainPage class and make access simpler:
// In MainPage:
public static MainPage Instance {
// This will return null when your current page is not a MainPage instance!
get { return (Window.Current.Content as Frame).Content as MainPage; }
}
// Now in HomePage it's only:
MainPage.Instance.OpenTab(TabContent.Settings);

Related

Prism navigation breaks page binding after returning to the page

I have two pages . the first page has an Image with the source bound the second page has a label with text bound.
when I navigate using prism navigation navigatedTo to the second page the label binding works but when I hit the back button to the first page the image disapears then when I navigate again to the second page the label text is empty .
Update added more info and some code
Main page has a listview of Merchants when clicked on an item it trigger a command and Merchant obj as paramter, the First page takes this object and do other operations on it one of the features is a scratch game when clicked on a button it takes you to the second page (scratchgame) also bound with command and a parameter of Merchant Obj.
First Page (Merchant Page) view :
<controls:CircleImage x:Name="logoimg"
TranslationY="-25" WidthRequest="100"
VerticalOptions="End"
BorderColor="#800080"
BorderThickness="2"
Source="{Binding Merchant.MerchantLogo}">
</controls:CircleImage>
First Page ViewModel :
internal class MerchantPageViewModel : AppMapViewModelBase, INavigationAware
{
private NojoomAppManager manager;
private readonly INavigationService _navigationService;
private Merchant _merchant;
public Merchant Merchant
{
get { return _merchant; }
set { _merchant = value; RaisePropertyChanged(nameof(Merchant)); }
}
public MerchantPageViewModel(INavigationService navigationService) : base(navigationService)
{
// Azure Mobile SDK
manager = NojoomAppManager.DefaultManager;
_navigationService = navigationService;
}
public new DelegateCommand<object> ScratchGameNavigateCommand =>
_scratchGameNavigateCommand ?? (_scratchGameNavigateCommand = new DelegateCommand<object>(ExecuteScratchNavigateCommand));
private async void ExecuteScratchNavigateCommand(object obj)
{
var p = new NavigationParameters();
p.Add("merchant", obj);
await NavigationService.NavigateAsync("ScratchGame", p);
}
public void OnNavigatedTo(INavigationParameters parameters)
{
Merchant = parameters.GetValue<Merchant>("merchant");
}
}
Second page ( scratch game ) view
<Label x:Name="Credit" FontSize="Large" FontAttributes="Bold" VerticalOptions="Center"></Label>
this label take its value from an API call
second page code :
private Merchant Merchant = new Merchant();
private Wallet Wallet = new Wallet();
public ScratchGame()
{
InitializeComponent();
manager = NojoomAppManager.DefaultManager;
}
private async Task Getwallet()
{
try
{
var wallets = await manager.GetWalletByIdAsync(Merchant.Id, Settings.UserId, false);
Wallet = wallets.First();
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
public void OnNavigatedTo(INavigationParameters parameters)
{
Merchant = parameters.GetValue<Merchant>("merchant");
Task.Run(async () => { await Getwallet(); }).Wait();
Credit.text = Wallet.SilverStars.ToString();
}
Update 2
after further investigation when I hit the back button on the second page , the first page NavigatedTo is triggerd but without paramter which makes the image source null .
How do I handle this and make the parameter passed again or used again when the back button is hit ?
The OnNavigatedTo method is executed when another page navigates to it. This is called after the ViewModel has been pushed on to the stack, this means that you either need to pass the parameter back to it OR check whether the property already has value, because when you navigate back, the ViewModel is already pushed into the stack, and it keeps values setted before.
The solution will depend on your preference, viability or ease of use when developing, is the page gonna be in the middle of a big stack? if so, maybe passing the parameter on every page after that one is not the smartest way.
Here is an example (remember, this might not be suitable to every situation, but for most cases it is):
public void OnNavigatedTo(INavigationParameters parameters)
{
if(Merchant == null)
Merchant = parameters.GetValue<Merchant>("merchant");
}
I found the solution ,simply I add this line of code in the second page .
I need to pass the paramater back again to the first page otherwise it will be null when navigated back to it.
public void OnNavigatedFrom(INavigationParameters parameters)
{
parameters.Add("merchant", Merchant);
}

How to pass a parameter between UWP pages when navigation is initiated from a parent page?

I am using a navigation model that has a MainPage, which contains a hamburger menu and a MyFrame in a splitview. I have two pages, WorkingPage and SavePage, which are displayed in the MyFrame. So MainPage includes this:
<Page>
<!-- Other stuff -->
<SplitView>
<SplitView.Pane>
<!-- Other stuff -->
<ListBox Name="HamburgerMenuMenuItems"
SelectionChanged="HamburgerMenuMenuItems_SelectionChanged">
<ListBoxItem Name="HamburgerMenuItemSave">
<!-- Content -- >
</ListBoxItem>
</ListBox>
<!-- Other stuff -->
</SplitView.Pane>
<SplitView.Content>
<Frame Name="MyFrame"></Frame>
</SplitView.Content>
</SplitView>
<!-- Other stuff -->
</Page>
The user clicks Save, which is one of the items in the Hamburger menu (set up as a listbox), and which raises the selection changed event, on the MainPage, which results in MainPage initiating a navigation from WorkingPage to SavePage in MyFrame.
public sealed partial class MainPage : Page
{
private void HamburgerMenuMenuItems_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Other options similar ...
else if (HamburgerMenuItemSave.IsSelected)
{
MyFrame.Navigate(typeof(Pages.File.SavePage));
}
// Other options similar ...
}
}
WorkingPage contains a data member that I want SavePage to have access to.
This is what I have:
public sealed partial class WorkingPage : Page
{
public MyClass myClass;
// Other stuff ...
}
I want the value of "myClass" to be passed to SavePage, so it ultimately ends up as:
public sealed partial class SavePage : Page
{
public MyClass myClass;
// Other stuff ...
}
I know from research (and extensively using it myself) that the proper way to pass parameters between two pages is as follows:
Frame.Navigate(typeof(PageClass), someParameter);
But the problem is MainPage is where the navigation is initiated, but the needed parameter value to pass (myClass) only exists in the scope of WorkingPage. This thus requires that either:
A) the navigation be initiated by WorkingPage, so that I can use the line of code above, and just put in "SavePage" as the PageClass and "myClass" as the parameter, or
B) MainPage somehow needs to obtain knowledge of the value of "myClass", so that I can use the same line of code as (A), but with "this.MyFrame" instead of "Frame"
How can getting the value of "myClass" from WorkingPage to SavePage be accomplished, with a navigate event initiated from MainPage? This seems like a common need, but everything I have found only talks about the simple case of one page initiating navigation to another, when a parameter must be passed from the initiating page to the other.
I am pretty sure this can be done in different ways.. but personally I like to use following pattern to achieve this :
Firstly, in your MainPage you have to create sort of an utility method for the navigation :
public void navigateWithParameter(Page yourPage,String yourParameter){
MyFrame.Navigate(typeof(yourPage), yourParameter);
}
Next you can call this method from any page you want to (which in your case is the WorkingPage), by getting the current instance of the MainPage and calling the navigateWithParameter function with the appropriate parameters :
var frame = (Frame)Window.Current.Content;
var mainpage = (MainPage)frame.Content;
mainpage.navigateWithParameter(yourPage,"It works!");
Hope this helps!
Through further research, I found a way to address this issue (other ways may exist). There exists a property of a Frame that allows you to get a reference to the page the frame is currently displaying:
MyFrame.Content
Thus, on MainPage, in the event handler, you can obtain a reference to the page being displayed, which then allows you to access MyClass, since it is a public data member. You can then pass the value of MyClass into the page being navigated to... all without WorkingPage having any knowledge of the event firing.
public sealed partial class MainPage : Page
{
private void HamburgerMenuMenuItems_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Other options similar ...
else if (HamburgerMenuItemSave.IsSelected)
{
// 1. Check if the current page is the correct type
if (MyFrame.Content is WorkingPage workingPage)
{
// 2. Grab the data to pass from the instance of the displayed page
MyClass dataToPass = workingPage.myClass;
// 3. Pass the grabbed data to the page being navigated to
MyFrame.Navigate(typeof(Pages.File.SavePage), dataToPass);
}
}
// Other options similar ...
}
}
I had a similar task. I have a UWP page with a lot of settings, grouped buttons with binded NumberBoxes. To move out of MainPage all button handlers needed a "helper Class". There I passed the main page object, which automaticaly gives me access to all elements in my MainPage.
My XAML looked like is:
<Page> <Grid>
<StackPanel >
<muxc:NumberBox x:Name="Offset" PlaceholderText="0,35" />
<Button Content="GrOffset" Click="buttonClickHandler.Offset_Click" />
</StackPanel>
<TextBox x:Name="Messager_Container" Text="Total" />
<Grid/>
<Page/>
C# is
namespace Eval
{
public sealed partial class MainPage : Page
{ /// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
ButtonClickHandler buttonClickHandler = new ButtonClickHandler(); // create a new instance of our "helper class"
//===================================
public MainPage()
{
this.InitializeComponent();
var mainPageVAR = this; // only for check ot, is it valid assigning
buttonClickHandler.mainPage = this; //assign to, we pass the addres to our MainPage class instance
}
public class ButtonClickHandler // our helping class
{
public Eval.MainPage mainPage;
public void Offset_Click(object sender, RoutedEventArgs e)
{
mainPage.Messager_Container.Text=
mainPage.Offset.Value.ToString();
// our value from numberBox "Offset" passed to TextBox "Messager_Container"
}
}
}

Reserve the loaded page content of WebView when switch away from current page in a Master / Detail layout in Xamarin Forms

I am using Master / Detail layout to build a Navigation Menu and a few pages. One of the page is a WebView control, nothing else. When I switch away from this WebView page from Navigation Menu and then switch back, the content of the WebView is gone, the state is also gone. This happens both on iOS and Android.
But if I navigate away from the WebView page to a completely different page (non-Master/Detail page), after coming back to WebView page, everything is fine. All the content and the state are preserved.
I have to reload the page, but user's operations will be lost. Is there a way to preserve the page's content and state and restore them without reloading the page?
Unfortunately, you would need to modify the source code of Xamarin.Forms.MasterDetailPage to give the desired behavior. The problem is that when the Detail page is removed from the MasterDetail (switching from Webview to another page), all of the native controls are disposed; even if you keep a hard reference to the Xamarin.Forms.Webview or Xamarin.Forms.Page object.
In the source, you can see that if the _detail property is not null, it is removed from the MasterDetailPage.PageController.InternalChildren. You would want to put a check in there for your Xamarin.Forms.Page that contains the Xamarin.Forms.Webview and make sure it isn't removed from the page controller. This will persist the native webview and webview handlers so nothing is re-loaded. A few things to note:
Make sure you also don't re-add your webview page to the MasterDetail PageController.InternalChildren
The webview page will stay loaded into memory. This is important because if your website does a lot of javascript, storing things or more, you should just be aware that it will stay in memory even when it is not displayed
You can verify all of this by creating a new Forms project and replacing the App class code with what's below:
public class App : Application
{
public App()
{
MainPage = new MasterDetail();
}
}
public class MasterDetail : MasterDetailPage
{
static WebViewPage persistentWebPage = new WebViewPage();
public MasterDetail()
{
var masterPage = new MasterPage();
persistentWebPage = new WebViewPage();
Master = masterPage;
Detail = persistentWebPage;
masterPage.listview.ItemSelected += (sender, e) =>
{
var item = e.SelectedItem as string;
if (string.IsNullOrEmpty(item))
return;
switch (item)
{
case "1":
Detail = persistentWebPage;
break;
default:
Detail = new ContentPage { BackgroundColor = Color.Red };
break;
}
masterPage.listview.SelectedItem = null;
IsPresented = false;
};
}
}
public class MasterPage : ContentPage
{
public ListView listview = new ListView { ItemsSource = new string[] { "1", "2", "3" } };
public MasterPage()
{
Title = "Master";
Content = listview;
}
}
public class WebViewPage : ContentPage
{
public static WebView webView = new WebView { Source = "https://www.google.com/" };
public WebViewPage()
{
Content = webView;
}
}
Next create a renderer in the Android project (you could do iOS also, I just did Android) and copy the following code:
public class WebViewRenderer_Droid : WebViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
if (e.NewElement == null)
Console.WriteLine("*********e.NewElement is null");
else
Console.WriteLine("*********e.NewElement is not null");
if (e.OldElement == null)
Console.WriteLine("*********e.OldElement is null");
else
Console.WriteLine("*********e.OldElement is not null");
if (Control == null)
Console.WriteLine("*********Control is null");
else
Console.WriteLine("*********Control is not null");
base.OnElementChanged(e);
}
}
Run the project and switch away from the webview page and back. You'll see that Control and e.OldElement are both null at one point. This is proof that your native handlers are being disposed even with a hard reference. That is because you don't have any control over how Xamarin.Forms handles the native control reference. From the source code of Xamarin.Forms, we can see that if Control is null, than everything gets recreated.
I hope this helps! It was fun digging deep to figure this one out.
To fix this, I created a custom webview view renderer. When I create the new webview in the renderer, (e.g. new WKWebView(Frame, opts)) I replace it with a subclass of WKWebview that overrides Dispose, making it do nothing. Keep track of the original webview in OnElementChanged as desired.
In your App class, create static instance of the ContentPage containing the WebView. Creating the ContentPage as a static variable in the App class will preserve its state and ensure that it opens to the same webpage that the user most recently viewed.
Sample Code
using Xamarin.Forms;
namespace PersistWebViewSample
{
public class StartPage : ContentPage
{
public StartPage()
{
var navigateToWebViewButton = new Button
{
Text = "Open Persistent Web View"
};
navigateToWebViewButton.Clicked += async (sender, e) => await Navigation.PushAsync(App.PersistentWebView);
Title = "Start";
Content = navigateToWebViewButton;
}
}
public class App : Application
{
static readonly string xamarinUrl = "https://www.xamarin.com/";
public App()
{
MainPage = new NavigationPage(new StartPage());
}
public static ContentPage PersistentWebView { get; } = new ContentPage
{
Title = "Persistent Web View",
Content = new WebView
{
Source = xamarinUrl
}
};
}
}
I found a working solution, although more like a hack. That is instead of navigating to a sub-page in Master/Detail when an menu item is touched, I navigate to another page which means down 1 level. When user navigate back from that page, everything will be preserved. But this way, the navigation menu won't always be there anymore. But that's OK.
Navigation controller keeps the existing UIViewController on the Navigation Stack, but when you pop the UIViewController, it removes view controller from the stack, hence its content gets deleted.
To preserve it, you need to have a shared instance of WebViewController, and always use the same WebViewController.
In your WebView controller write the following code to create a shared instance:
private static ViewController vc;
public static WebViewController SharedInstance()
{
if (vc == null)
{
var storyboard = UIStoryboard.FromName("MainStoryboard", null);
vc = storyboard.InstantiateViewController("WevViewController") as WevViewController;
}
return vc;
}
Whenever you want to display web view controller, use following
public void DisplayWebview()
{
WevViewController webvw = WevViewController.SharedInstance();
NavigationController.PushViewController(webvw,true);
}

How to read/write in a parent frame's variable in UWP?

In my MainPage.xaml I've got a SplitView that loads many pages inside a frame created in it's SlplitView.Content.
I've got data in a MainPage's variable that needs to be sent to every page that loads in my SplitView content's frame according to the ListBoxItem clicked.
Also in the current page I may have to update the MainPage's variable before a new page is loaded.
How can I do this? Is there a way to declare a global variable? Can I transport that information from a page to another updating it's value on the parent page?
I think you can declare a public static variable in App Class in App.xaml.cs, and use it in any pages in the app.
In App.xaml.cs:
sealed partial class App : Application
{
...
public static string MyTestVar { get; set; }
...
}
In MainPage.xaml.cs:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
App.MyTestVar = "world";
}
}
And for some other cases like implementing a setting page, you can check Store and retrieve settings and other app data.
Complementing the last answer I solved my problem by declaring an internal static variable in my "App.xaml.cs".
internal static string foo = "";
Then to access it I used:
App.foo = "my string";
There is also a more elegant way to preserve and restore page data when leaving it (which is what I needed) as follows: https://msdn.microsoft.com/pt-br/library/windows/apps/ff967548%28v=vs.105%29.aspx

MonoTouch can't open new window from AccessoryButtonTapped

How do I open an new window from AccessoryButtonTapped button from my BasisViewController. Right now I have this for open, but the problem is the NavigationController which I can't find because I'm inherent the UITableViewSource.
Opskrift ops = new Opskrift(item.ImageName, item.Name, item.optionTxt, item.SubHeading)
this.NavigationController.PushViewController(this.opskrift, true);
If I use ops.NavigationController.PushViewController(this.opskrift, true);
I get an Object reference not set to an instance of an object exception.
Give your UITableViewSource inherited class access to your controller by passing it through its constructor:
public class MyTableSource : UITableViewSource
{
private BasisViewController controller;
public MyTableSource(BasisViewController parentController)
{
this.controller = parentController;
}
//use like this in a method:
//this.controller.NavigationController.PushViewController(opskrift, true);
}
Your Opfskrift controller's NavigationController property returns null because it is not part of a navigation controller's stack when you initialize it (=hasn't been "pushed" in a navigation controller). Of course, BasisViewController must also belong to a navigation controller for its NavigationController property to contain something other than null.

Categories