Calling one of child pages of TabbedPage to reach entry text - c#

I know, "It is highly recommended to use MVVM" but I am just trying to understand and learn xamarin.forms structure. So here is the question:
My application is based on TabbedPage which consists of two NavigationPage:
<TabbedPage.Children >
<NavigationPage Title="Search">
<NavigationPage.Icon>
<OnPlatform x:TypeArguments="FileImageSource">
<On Platform="iOS" Value="tab_feed.png"/>
</OnPlatform>
</NavigationPage.Icon>
<x:Arguments>
<views:SearchPage />
</x:Arguments>
</NavigationPage>
<NavigationPage Title="Study">
<NavigationPage.Icon>
<OnPlatform x:TypeArguments="FileImageSource">
<On Platform="iOS" Value="tab_about.png"/>
</OnPlatform>
</NavigationPage.Icon>
<x:Arguments> <!---->
<views:AboutPage />
</x:Arguments>
</NavigationPage>
</TabbedPage.Children>
I am trying to call a child of this tabbedpage from a method created in a different class/view model to reach SearchBar text:
public async void AddToList()
{
var mp = (MainPage)App.Current.MainPage;
var sp = (SearchPage)mp.Children[0]; /// exception related with casting is thrown.
var Word = sp.WordSearchBar.Text;
...
}
SearchPage is defined as below. So I what is the issue creating casting exception. And how I can reach the searcbar text (other than bindings and MVVM)
public partial class SearchPage : ContentPage
{....}

mp.Children[0]; is kind of NavigationPage not SearchPage, so you get the casting exception.
One way to achieve that is create a static property in the App class:
public partial class `App` : Application
{
public static AboutPage aboutPageInstance { get; set; }
public App()
{
InitializeComponent();
DependencyService.Register<MockDataStore>();
MainPage = new MainPage();
}
}
Then assign the value in the AboutPage and let's say you have a label called currentLabel in the AboutPage:
public partial class AboutPage : ContentPage
{
public Label currentLabel { get; set; }
public AboutPage()
{
InitializeComponent();
currentLabel = myLabel;
App.aboutPageInstance = this;
}
}
Then you can access the label in other ContentPage by:
async void AddToList(object sender, EventArgs e)
{
if (App.aboutPageInstance != null)
{
AboutPage about = App.aboutPageInstance;
about.currentLabel.Text = "kkk";
}
}
MessagingCenter is also a good option to communicate between two different ContentPages.

In the AddToList method you are trying to cast the first child of MainPage to SearchPage. Anyway, in your XAML, the first child in <TabbedPage.Children> is a NavigationPage which does - of course - not derive from your SearchPage, hence cannot be casted and a InvalidCastException is thrown.
If you really want to do it this way (learning MVVM right away could be helpful, but if you really want to learn it the hard way, it's fine for me), you would have to access the NavigationPage and then access its CurrentPage
public async void AddToList()
{
var mainPage = (MainPage)App.Current.MainPage;
var navigationPage = (NavigationPage)mainPage.Children[0];
if (navigationPage.CurrentPage is SearchPage searchPage)
{
var Word = searchPage.WordSearchBar.Text;
...
}
}
I've introduced safe casting with pattern matching because the NavigationPage.CurrentPage is subject to change (as opposed to the MainPage and the NavigationPage) and we don't want our app to crash just because the wrong page is active in the navigation page.
Please note that this way, your pages are very tightly coupled, which is usually considered not a very good thing. Even without MVVM you should think about decoupling your pages, e.g. with MessagingCenter, see the Xamarin.Forms tutorial on loosely coupled components. Anyway, you should consider introducing interfaces for your pages in that case, since MessagingCenter requires the type of the sender, which would introduce some kind of tight coupling again. Sending the search text to another component could be achieved by calling
MessagingCenter.Send<ISearchPage, string>(this, MessengerKeys.SearchTextChanged, searchText);
But what exactly would be sensible, strongly depends on your app and there might be a better way.

Related

How to pass object through pages that swtich via Tabs?

I'm programming an app with MAUI where I have an object called Company that is initialized in the MainPage
public partial class MainPage : ContentPage
{
Company company { get; set; } = new Company();
and I want that object to be shared across two pages that switch between one another through a tabs system run on the AppShell.
AppShell.xaml
<Shell
x:Class="Work_Tasks.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Work_Tasks"
xmlns:views="clr-namespace:Work_Tasks.Pages">
<TabBar>
<ShellContent
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage"
Icon="home.png"/>
<ShellContent
ContentTemplate="{DataTemplate views:AddPersonel}"
Route="AddPersonel"
Icon="add_contact.png"/>
</TabBar>
I want to avoid making the object static. Is there any way of passing the object through both or more pages? How should I go about it?
If I had this problem, I would probably go for something like this. Create class:
public class CompanyContainer
{
public Company Company { get; set; } = new Company();
}
Now register it in MauiProgramm.cs as a singleton
builder.Services.AddSingleton<CompanyContainer>();
Now you can inject this instance through constructor to your page:
public partial class MainPage : ContentPage
{
private readonly CompanyContainer _companyContainer;
public MainPage(CompanyContainer container)
{
_companyContainer = container;
}
}
This should solve your issue. You can also make it as a property with public getter in MainPage if you need. And one more thing. In c# by convention we usually write property names with capital letter.

Pass file between pages - UWP C#

I'm wondering what is the best way to pass a file between pages in a UWP app?
I have a UWP app with two pages. In the first page, I have the user open a file with filepicker and load that file into a media player.
I want to pass that same file onto the second page when the user navigates there. I am passing the file over currently as a string which I then am attempting load as a storagefile using GetFileFromPathAsync.
This currently works as I'm able to load the file on the second page but it requires that the user provide broad file system access.
Code on Page 1 (FileLoaded is file path string):
private async void TranscodeMedia_Click(object sender, RoutedEventArgs e)
{
AppWindow appWindow = await AppWindow.TryCreateAsync();
Frame appWindowContentFrame = new Frame();
appWindowContentFrame.Navigate(typeof(TranscodeMedia), FileLoaded);
Code on Page 2:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
var fileTransfer = e.Parameter.ToString();
FileName.Text = fileTransfer;
StorageFile PassedFile = await StorageFile.GetFileFromPathAsync(fileTransfer);
I'm wondering if this is the best way to pass the file between pages? I'd rather not require the user to provide broad system access to the app if possible. Any help you can provide is most appreciated!
The best and most standard way in C#/WPF/UWP way is to use a standard pattern that consist of a general ViewModel class (which contains all the common app data that you want to use in the logic layer), put as a field in the static MainPage (or even in the App.xaml.cs class).
I always do it like this:
1) I use the MainPage automatically created as the "shell" of the app, with a property that is the AppViewModel.
The MainPage (and thus the AppViewModel) can be accessed from everywhere in the app, by setting itself as a static field in its own class (the "Current" static field can be called from everywhere in the app... even in a MessageDialog class!).
This is the code for the MainPage (or a shell Page that you wish, but I suggest doing like this, it is a pretty standard way used even by Microsoft), simpler than you think:
public sealed partial class MainPage : Page
{
public AppViewModel ViewModel { get; set; } = new AppViewModel();
public static MainPage Current { get; set; }
public MainPage()
{
this.InitializeComponent();
Current = this;
}
}
THIS is the trick: to make the page static in one field in its
own class, so that that static field will be UNIQUE in the entire app
(this is one of the main features of the "static" word) and, thus, by calling
MainPage.Current.ViewModel you can immediately get any data (in your
specific case, a StorageFile) stored there.
2) The AppViewModel itself is a class that must implement the INotifyPropertyChanged interface, in order to enable bindable properties and functions.
It is common, among Windows developers, to create a base class that implements it and then derive all the classes that needs bindable (i.e. observable) properties from it.
Here it is, exactly how Microsoft itself creates it:
public class BaseBind : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetProperty<T>(ref T storage, T value,
[CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
}
Then you derive AppViewModel class (and all the other model and viewmodel classes) from it… populating it with all the common properties that you need to share across pages.
I have even added a derived property, in order to show how you can share even multiple data types at once, and a function:
public class AppViewModel : BaseBind
{
public AppViewModel()
{
// Usually we initialize all the starting data here, in the viewmodel constructor...
}
// All common app data
private string sampleCommonString;
public String SampleCommonString
{
get { return sampleCommonString; }
set { SetProperty(ref sampleCommonString, value); OnPropertyChanged(nameof(SampleDerivedProperty1)); OnPropertyChanged(nameof(SampleDerivedProperty2)); }
}
public String SampleDerivedProperty1 => "return something based on SampleCommonString";
public String SampleDerivedProperty2
{
get
{
// evaluate in some way SampleCommonString...
return "Same thing as SampleDerivedProperty1, but it allows to add more than just one istruction";
}
}
// This is a property that you can use for functions and internal logic… but it CAN'T be binded to the UI directly
public String SampleNOTBindableProperty { get; set; }
public void SampleFunction()
{
// Insert code, that needs to interact with all the data contained in the viewmodel itself, here...
// The function has to be with NO parameters, in order to work with simple {x:Bind} markup.
// If your function has to access some specific data, you can create a new bindable (or non) property, just as the ones above, and memorize the data there.
}
}
3) Then, in order to access all this from another Page, just create an AppViewModel field in that page, referencing the viewmodel contained in the static mainpage:
public sealed partial class SecondPage : Page
{
public AppViewModel ViewModel => MainPage.Current.ViewModel;
public SecondPage()
{
this.InitializeComponent();
}
}
...and you can easily bind XAML controls properties to the AppViewModel itself:
<TextBlock Text="{x:Bind ViewModel.SampleCommonString, Mode=OneWay}"/>
<TextBox Text="{x:Bind ViewModel.SampleCommonString, Mode=TwoWay}"/>
<Button Content="Sample content" Click="{x:Bind ViewModel.SampleFunction}"/>
(Mode=OneWay is for real-time binding, in order that the property is immediately updated even in the UI, while Mode=TwoWay is used for those properties that can be edited from the control itself, by the user, in order to interact with app logic).
In this mode you will be able to display data and all its changes in real-time!
So... this is the way to keep all the app data at run-time in a
correct and flexible way... by learning it and practicing, in the
future you will use this pattern even in a smarter way, by creating
viewmodels for every object of your application (for example: if
your app need to store your company's customers data, you will have a
"CustomerViewModel" class derived from the BaseBind class, with all
the data of a customer in it) and creating lists like
ObservableCollection<SampleViewModel> to store all of them (ObservableCollection<t> is a collection type that has built-in mechanism to handle list changes, like adding, removing and reordering list items).
Then you will link every observable collection to the ItemsSource property of a control that inherits from ListBase class (tipically: ListView or GridView), creating a DataTemplate to display each list item, like in this example:
<Page
xmlns:vm="using:SampleApp.ViewModelsPath"
<Grid>
<ListView ItemsSource="{x:Bind ViewModel.SampleListOfObjectViewModel, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:SampleObjectViewModel">
<StackPanel>
<TextBlock Text="{x:Bind SampleObjectProperty1, Mode=OneWay}"/>
<TextBlock Text="{x:Bind SampleObjectProperty2, Mode=OneWay}"/>
<Button Click="{x:Bind SampleObjectFunction}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
...and all the data displayed will be updated in real-time whenever you change it!
Hope this all will help you boost your knowledge about how preparing a WPF/UWP logic layer, because all of this works pretty in the same way even for the WPF apps (i.e. the old desktop programs).
Best regards
There are some other ways to implement your requirement about accessing the same file on different pages. But for your scenario, you could use Future-access list in your UWP app.
By picking files and folders, your user grants your app permission to access items that might not be accessible otherwise. If you add these items to your future-access list then you'll retain that permission when your app wants to access those items again later.
Here is the sample code I made
In the first page:
FileOpenPicker picker = new FileOpenPicker();
picker.FileTypeFilter.Add("*");
StorageFile file = await picker.PickSingleFileAsync();
if (file != null)
{
// add file to the Future Access list
var storageItemAccessList = Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList;
// this token is the key to get the file.
string FALToken = storageItemAccessList.Add(file, "mediaFile");
// in your real scenario, you need to save the token and pass it when you nee
this.Frame.Navigate(typeof(TestPage), FALToken);
}
In the second page:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
string token = (string)e.Parameter;
var storageItemAccessList = StorageApplicationPermissions.FutureAccessList;
StorageFile retrievedFile = await storageItemAccessList.GetFileAsync(token);
}
So you don't need the broad file system access if you use Future-access list to keep the permission of files.
For more detailed information, please refer to this document: Track recently used files and folders

How to make a page template which can be initialized in Main Page C# code?

I want to make a page template in my UWP project, which can be initialized in main page in C# code.
For example, the page is like an introduction of painting work: a painting picture(could be an url representing the image), text introduction below. And I need to initialize these two elements in main page and dynamically create the new page in C# code. Because for each painting work, I need to use a new page for navigation and tracking.
How to do it? Thanks!
I want to make a page template in my UWP project, which can be initialized in the main page in C# code.
For your requirement, you could create ImagePage that contains ImageModel class like the following. When the page navigated to the image and text label will be initialized with the parameter that comes from the main page.
Xaml
<StackPanel>
<Image Name="MyImage"/>
<TextBlock Name="MyDescription"/>
</StackPanel>
Code Behind
public sealed partial class ImagePage : Page
{
public ImagePage()
{
this.InitializeComponent();
}
public class ImageModel
{
public string ImageUrl { get; set; }
public string Description { get; set; }
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.Parameter != null && e.Parameter is ImageModel)
{
var data = e.Parameter as ImageModel;
MyImage.Source = new BitmapImage(new Uri(data.ImageUrl));
MyDescription.Text = data.Description;
}
}
}
Then add Frame that use to navigate On the Main page.
<Grid>
<Frame Name="ImageFrame"/>
</Grid>
Usage
public MainPage()
{
this.InitializeComponent();
var TestData=new ImageModel
{
ImageUrl = "https://via.placeholder.com/150",
Description = "This is a test image"
}
ImageFrame.Navigate( typeof(ImagePage), TestData );
}
Now, when you start the application, your new image page will load in the frame.
This is a short example, the main purpose is to express how to jump to the page and give parameters to the page.
When the page receives a parameter, you can adjust the UI according to the parameter.
So, you can use the GridView to display all the images. When you click on one of them, use this principle to jump to the pre-designed page.
I hope this can help you.

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"
}
}
}

Injecting a Master page into a Master-Detail page using XAML & Xamarin

I'm trying to create a master detail page in XAML where both the Master and Detail pages without any concretions. I've got something like...
<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<MasterDetailPage.Master>
MasterPage
</MasterDetailPage.Master>
<MasterDetailPage.Detail>
DetailPage
</MasterDetailPage.Detail>
</MasterDetailPage>
I'm using MVVM architecture and want it such that the 'MasterPage' is obtained from from ViewModel where I can switch it.
My view model is similar to this
public class MainPageViewModel : NavigableViewModel, IMainPageViewModel
{
public MainPageModel MainPageViews { get; set; }
public MainPageViewModel() : base()
{
MainPageViews = new MainPageModel()
{
NavigationBarPage = ViewFactory.Instance.Resolve<IMasterPageViewModel>()
};
}
}`
Is there anyway to bind the content page to a page in the ViewModel?
Changing comment above to answer since there has been no activity on this since:
It seems that neither the Master property or Detail property of a MasterDetailPage are bindable properties. See: developer.xamarin.com/api/type/Xamarin.Forms.MasterDetailPag‌​e

Categories