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

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

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.

Calling one of child pages of TabbedPage to reach entry text

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.

Control the view in MVVM

I created a project with the MVVM model, and done so with the view-first approach.
I have a TextBox in my XAML code, along with a Button to pass the data from the TextBox:
<!-- View - XAML code -->
<TextBox
MinWidth="30"
Name="TagId"/>
<Button
Command="{Binding AddTagCommand}"
CommandParameter="{Binding Text, ElementName=TagId}"
Content="Add"/>
When I click the button, I want the TextBox cleared.
According to the Prism manual:
In some cases, the code-behind may contain UI logic code that implements visual behavior that is difficult or inefficient to express in Extensible Application Markup Language (XAML), such as complex animations, or when the code needs to directly manipulate visual elements that are part of the view.
Here's the code behind, and the viewmodel.
//View - code behind
public partial class ApplicationStarterView : UserControl
{
public ApplicationStarterView()
{
}
public ApplicationStarterView(ApplicationStarterViewModel viewModel) : this()
{
DataContext = viewModel;
InitializeComponent();
}
}
//View model
public class ApplicationStarterViewModel : BindableBase
{
private readonly IUnityContainer _container;
public ApplicationStarterViewModel(IUnityContainer container)
{
_container = container;
AddTagCommand = new DelegateCommand<object>(AddTag);
}
public ICommand AddTagCommand { get; private set; }
private void AddTag(object input)
{
//Forward stuff
//Clear TextBox
}
}
Can I in any way squeeze in some code to do a TagId.Clear()?
I'd bind the text to another property on the view model.
That way, you can skip the command parameter and the AddTagCommand can directly read the new Text property, do his adding stuff and then clear it, thus updating TagId.
Completely unrelated piece of advice: it is almost never a good idea to inject the IUnityContainer... if you need to create stuff, use factories.

MvvmCross Binding your DataModel to your ViewModel

I have written a Windows Store App that I need to port to Android. I am attempting to use MvvmCross and Xamarin in Visual Studio to achieve this. In my Windows App, I would create a screen using XAML and in the textbox etc. set the binding to the field in my datamodel object. I would get my datamodel objects from a WCF service reference. In the code behind for the screen, I would just set the datacontext of the root layout grid to the datamodel object generated by the Service Reference. It was pretty simple.
In MvvmCross, it seems that you basically run the viewmodel in order to load a page. The syntax for the fields in the viewmodel are really identical to the ones generated in the datamodel by the service reference. I know that Mvvm needs the viewmodel as the shim between the datamodel and the view. Is there an efficient way to pass the properties from the datamodel, through the viewmodel to the view? I have the service reference working and generating objects and data from the WCF. I could hard code each field that exists in the datamodel into the viewmodel and have the get set act on fields from the datamodel object. I was just hoping there was a less manual way to do it. Any suggestions?
#Stuart had an excellent suggestion. Here is what I did. Here is my ViewModel:
public class InventoryViewModel
: MvxViewModel
{
public async void Init(Guid ID)
{
await MPS_Mobile_Driver.Droid.DataModel.ShipmentDataSource.GetShipmentInventory(ID);
ShipmentInventory = ShipmentDataSource.CurrInventory;
Shipment = await MPS_Mobile_Driver.Droid.DataModel.ShipmentDataSource.GetShipment((int)ShipmentInventory.idno, (short)ShipmentInventory.idsub);
}
private Shipment _Shipment;
public Shipment Shipment
{
get { return _Shipment; }
set { _Shipment = value; RaisePropertyChanged(() => Shipment); }
}
private ShipmentInventory _ShipmentInventory;
public ShipmentInventory ShipmentInventory
{
get { return _ShipmentInventory; }
set { _ShipmentInventory = value; RaisePropertyChanged(() => ShipmentInventory); }
}
}
I pass it a Guid ID and in the Init method, it gets the Shipment Inventory and the Associated Shipment. When I bind the fields, I just bind to Shipment. as follows:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="#style/InputEditText"
local:MvxBind="Text Shipment.OrgEmail" />
</LinearLayout>
That's all there was to it!
Hope this helps someone.
Jim

Accessing main Page UI in user Control

I have mainPage.xaml and userControl.xaml.I called userControl in mainPage. mainPage has a Grid named "grd" and userControl has a button named "btn". Now when I will click on button then click event of userControl will be raised. In this event, I want to hide the Grid(that in mainPage.xaml). How can I access mainPage controls in userControls ?
For windows Phone 8 try this:
(((Application.Current as App).RootVisual as PhoneApplicationFrame).Content as Page)
You can access currently displayed Page using this code :
var mainPage = (PhoneApplicationPage)((App)Application.Current).RootFrame.Content;
Check out my answer, in that OP wanted to set visibility of appbar, my answer will work for Grid also.
how to programatically open and close bottomappbar using a eventhandler that exist in a usercontrol back code?
If you are using MVVM (as you stated in your comment to #har07's answer) you should not hide the grid in the main page using an event handler in your user control page. Instead you should bind a command to the button in the control page. This command should change the view model of the main page and this change should be notified by that view model to the main page.
In the next example I use MVVM Light but other MVVM libraries probably work almost the same.
Add a property to the view model of your main page:
public class MainViewModel : BaseViewModel
{
…your code here
private bool _isValid;
public bool IsValid
{
get
{
return _isValid;
}
set
{
_isValid = value;
RaisePropertyChange("IsValid");
}
}
}
In the main page bind Visibility to IsValid.
<Grid Visibility="{Binding IsValid, Converter={StaticResource converter}}">
…content here
</Grid>
Now, the grid is visible if IsValid is true.
Add a command to the view model of the user control:
public class UserControlViewModel : BaseViewModel
{
…your code here
public RelayCommand InvalidateGridCommand { get; private set; }
public UserControlViewModel()
{
InvalidateGridCommand = new RelayCommand( () => InvalidateGrid() );
}
private void InvalidateGrid()
{
var mainvm = SimpleIoc.Default.GetInstance(MainViewModel);
mainvm.IsValid = false;
}
}
In the user control page bind the Button to the Command:
<Button Command="{Binding InvalidateGridCommand}">
Invalidate
</Button>
Now, clicking the button will set IsValid on the MainViewModel to false, which will, in turn, hide the Grid.
I came here looking for an answer for this question and after some tries I could access the main page of my Windows Phone 8.1 app using:
var mainPage = (MainPage)(Window.Current.Content as Frame).Content.
To access the controls declared in the MainPage, you need also to give them a x:Name and change their x:FieldModifier to "Internal" or "Public".

Categories