Shared TitleView in Xamarin.Forms AppShell - mvvm

Is there a way to implement a shared TitleView component in Xamarin.Forms AppShell? I've tried a couple of approaches but it's not working as I intended.
What I'm trying to achieve is something like the following:
App Shell with a few tabs and a common Shell TitleView
The titleView is just a ContentView (let's call it Exposure) with a label to display the result of a calculation and a refresh button to fetch data and recalculate
Changing Tab shouldn't affect the state of the TitleView
What I've tried:
Initialize one instance of the Exposure component (View and ViewModel) and configure it on the DI framework to use that instance only (singleton).
When the Pages are initialized I get the single instance of the Exposure component and assign it to the Shell.TitleView property.
By doing this I was hoping that the Exposure state would be consistent across the tabs (single instance after all...), but what happens is that when I hit the refresh button it only refreshes the calculation on the current tab Exposure component, changing tab will present me with the Exposure component with old state. I'm surprised this is not supported out of the box (configure titleView at Shell level) but I might be missing something.
Might not be relevant but I'm using ReactiveUI (MVVM) and Splat (dependency injection)
The next thing I'm willing to try is to trigger a refresh when each page re-appears but that feels dodgy.

You can create a base ContentPage and set the style of TitleView of it .
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="xxx.BaseContentPage">
<NavigationPage.TitleView>
<StackLayout>
//...
</StackLayout>
</NavigationPage.TitleView>
<ContentPage.Content>
<StackLayout>
//...
</StackLayout>
</ContentPage.Content>
</ContentPage>

Related

Multiple NavigationPages Outside of MainPage

We are mostly a WPF shop currently and are exploring MAUI development for future projects. We have a need to track three separate layers of content (in addition to any necessary modal content), which each navigate and may all be visible on the screen at once.
I am attempting to work through various use-cases in a test app, and am separating cases in a TabBar. I had thought to use three NavigationPages in the VM of one tab and navigate each independently, but I am stumbling over getting even one to work, let alone three.
I have tried several different approaches, including ContentPresenter and ContentView, but here is the core of my most recent approach:
public NavTestVM()
{
NavPage = new NavigationPage(new NavRoot());
}
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiAndUnitTest.Views.NavTest"
x:DataType="viewModels:NavTestVM">
<TemplatedPage ControlTemplate="{Binding NavPage.CurrentPage}" HeightRequest="400" WidthRequest="600"/>
</ContentPage>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiAndUnitTest.Views.NavTestViews.NavRoot"
BackgroundColor="Coral">
</ContentPage>
The NavRoot page (the root page in NavPage) does not appear in the tab. If, in App.xaml.cs, I replace
MainPage = new AppShell();
with
MainPage = new NavigationPage(new NavRoot());
I get a page full of Coral-y goodness on startup. What am I missing to get current NavigationPage content to display, outside of App.xaml.cs.MainPage?
Assuming I can get this working, I would then expect I can just repeat the approach twice more for the other layers and call it good.

FlyoutPage.Detail set from code doesn't show navigation bar

So I'm trying to do dependency injection from the start of my app. I'm using a Flyout as the root page of my app, so I need to assign a page to Flyout.Detail. Maybe I'm missing something, but I can't set the Detail page in XAML because I can't specify the parameter in my Constructor. I can't find anything online about it.
<?xml version="1.0" encoding="utf-8" ?>
<FlyoutPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyApp"
xmlns:view="clr-namespace:MyApp.View"
xmlns:viewmodel="clr-namespace:MyApp.ViewModel"
x:Class="MyApp.View.FlyoutMenuRoot"
Title="FlyoutMenuRoot">
<FlyoutPage.Flyout>
<view:FlyoutMenuPage x:Name="flyoutMenuRoot" />
</FlyoutPage.Flyout>
<<FlyoutPage.Detail>
<NavigationPage>
<x:Arguments>
<view:BudgetViewPage />
</x:Arguments>
</NavigationPage>
</FlyoutPage.Detail>
</FlyoutPage>
So I'm opting to set the Detail page in the code-behind:
public partial class FlyoutMenuRoot : FlyoutPage
{
DataService dataService;
public FlyoutMenuRoot(DataService dataService)
{
InitializeComponent();
this.dataService = dataService;
Detail = new BudgetViewPage(this.dataService);
}
}
Maybe I'm missing something there as the XAML references NavigationPage before the x:Arguments.
At any rate, the Navigation bar doesn't show up at the top of the screen, so no burger icon. However, I can swipe from the left part of the screen to pull out the Flyout page. Am I missing soemthing? When I have a parameterless constructor for the Detail page and call it from the XAML, the Navigation bar and burger icon show up.
I sure do feel like I'm missing something...
you need to wrap it in a NavigationPage
Detail = new NavigationPage(new BudgetViewPage(this.dataService));

dotnet maui how to hide the back button on desktop?

I'm trying to hide the backbutton shown in the image on desktop, but no matter what I tried, then it keept showing up.
I have tried
<Shell.BackButtonBehavior>
<BackButtonBehavior IsVisible="False" IsEnabled="False" />
</Shell.BackButtonBehavior>
And I have tried following this SO post Why Back Button is hidden in Maui?
My navigation is done by saying await Shell.Current.GoToAsync(new ShellNavigationState(location), false);
Am I missing something?
So everything you need to know can be found here:
https://learn.microsoft.com/en-us/dotnet/maui/user-interface/pages/navigationpage
NavigationPage.HasBackButton = true/false
Image (Proof)
https://gyazo.com/afbc744e7b6c5d1caa56960a536390c7
If this was helpful, mark this please as answer :)
This technique (the code in your question), added within the <ContentPage ... > declaration at the top of the xaml:
<Shell.BackButtonBehavior>
<BackButtonBehavior IsVisible="False" IsEnabled="False" />
</Shell.BackButtonBehavior>
seems to work when using the Shell to activate pages in C# codebehind (often in the BindingSource e.g., viewmodel):
await Shell.Current.GoToAsync($"{nameof(MyContentPage)}");
Adding this within the <ContentPage ...> declaration at the top of the xaml:
NavigationPage.HasBackButton="false"
seems to be applicable when using the push/pop within a NavigationPage:
await Navigation.PushAsync(new DetailsPage());
I don't use the Navigation.PushAsync. My app requires very specific navigation based on current data state, so a stack doesn't work for me.
I have verified setting the Shell.BackButtonBehavior (in the very code you provided) works in my case because I am activating pages via
await Shell.Current.GoToAsync
To enable/disable something you can use OnPlatform or OnIdiom
Exemple :
NavigationPage.HasBackButton="{OnIdiom Default='True', Desktop='False'}"

How to get handle of controls in ViewModel (MVVM)

I am new to MVVM and need help on below scenario.
I have a Stack panel added on my view, now I have to add few controls dynamically to this stack panel through viewmodel. For this I need a handle of stack panel in my viewmodel. Can anybody please guide me how I can access stack panel in my viewmodel.
I read in other bloges that it can be done by using Dependency property. but still I am not able to find way to solve this issue.
Couple of things to note first. The intention of the ViewModel in the MVVM pattern is to provide separation from the View. Therefore, your ViewModel should have no knowledge of the View itself nor the controls contained in the View. Secondly, what you should be attempting to do is have your View bind to a property of your ViewModel (with the understanding that your ViewModel serves as the DataContext of your View). Normally, you would bind a control's ItemsSource property to some collection in the ViewModel. However, you will notice the StackPanel does not implement the ItemsSource dependency property. Instead, use ItemsControl in place of your StackPanel. I would suggest some additional reading on the MVVM pattern and the binding mechanics for additional clarification.
Totally agreed with Backlash,
It seems that your ViewModel and your View are too much coupled;
There are a bunch of resources on the Internet, but here are some I preferred:
Jason Dolinger presentation video on MVVM
John Smith article introducing MVVM
I have done this before with a user control. I've had a collection of objects that i needed to dynamically to controls in a StackPanel. You can also do it with any control...I'll use a TextBlock in this example.
Create a data template with the control you want wrapped in a stack panel:
(MyText is a property in the object of the collection... see below)
<DataTemplate x:Key="MyTemplate">
<Grid Margin="0">
<StackPanel>
<TextBlock Text="{Binding Path=MyText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
Then the key is to bind to a collection of objects using an ItemsControl:
(The collection is on your viewModel)
<ItemsControl
ItemsSource="{Binding ACollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemTemplate=" {StaticResource MyTemplate}" Background="Transparent">
</ItemsControl>
So for example if there are 3 items in "ACollection" there will be 3 TextBlocks stacked on top of each other if there are 5 in the collection there will be 5 TextBlocks etc.
Thanks All for your help, Here I solved the problem
In view model I have created ObservableCollection of FrameWorkElement type, which can hold any other controls which will be decided at runtime. Control can be a text box or button.
Public ObservableCollection < FrameWorkElement > Test
{
get{....} set{...}
}
Now I can add/set any other control to Test
Tets.Add(new TextBox()); Or Button , this will be decided at runtime.
Now bind this "Test" to ItemsControl.
<ItemsControl x:Name="itemsControl" ItemsSource={Biding Test}>
</ItemsControl>

how to create a master page or a page which is in common to all the views using gwt mvp

Hi I am new to GWT MVP pattern. I am from asp.net background and currently I am working on GWT and I am asked to create a Master page which has the menu items which should be in common to all the views. Initially I created a sample mvp project using https://developers.google.com/web-toolkit/articles/mvp-architecture-2 and in that there is navigation from one view to another. How do I maintain one view constant and keep changing the other views depending on what menu item we click. Please help
The article you mentioned is from before MVP support added in GWT. It's good at explaining the concept, but the actual implementation is less useful. To continue take a look at the GWT documentation about activities: https://developers.google.com/web-toolkit/doc/latest/DevGuideMvpActivitiesAndPlaces . There you will also find the solution for your problem. In brief, take a look at the ActivityManager. This manages all activities. On the activity manager you set one widget that will be static for all activities. This widget must have a method setWidget (actually it must implement AcceptsOneWidget). In each of your activity implementations you get this widget via the start method. And by calling setWidget with the specific view for that activity in the start method you set the activity specific view. This is all described very briefly, but you should get the concept if you read the mentioned documentation.
If you are working with UiBinder, your ui.xml file should be like this,
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:style>
</ui:style>
<g:DockLayoutPanel unit="EM">
<g:north size="4">
//Add Logo, menus or what you want to be displayed for all the pages
</g:north>
<g:center>
//Add code for your desired UI. In java code you change the UI by this "flowpanel"
For eg: <g:FlowPanel ui:field="flowpanel" />
</g:center>
</g:DockLayoutPanel>
Then everytime you can clear and add the widgets to be displayed in the view in the <g:center> using your java code like this flowpanel.clear();
flowpanel.add(anyWidget you need).
So <g:north> will be static view and <g:center> will be dynamic view. Now you will get the page as you desired. Since you can change everytime your view on <g:center> only.
Like this you can add <g:east>, <g:west> and g:south> if required.
If you are not working with UiBinder then do everything in your java code as follows,
final DockLayoutPanel dockLayoutPanel = new DockLayoutPanel(Style.Unit.EM);
dockLayoutPanel.addNorth(any widget you need, "4"); //NorthPanel
dockLayoutPanel.add(any widget you need); //CenterPanel
Where "4" is the size of the panel, you can change it.
Like this dockLayoutPanel.addWest(any widget you need, "4"); //WestPanel
dockLayoutPanel.addEast(any widget you need, "4");//EastPanel
dockLayoutPanel.addSouth(any widget you need, "4"); //SouthPanel
I hope you got idea now.
You divide your screen into two or more areas, and assign dedicated ActivityMapper and ActivityManager to each zone. For example, one zone can be "menu" with MenuActivityManager, and the other zone "body" with BodyActivityManager.
Here is a good explanation:
http://blog.ltgt.net/gwt-21-activities-nesting-yagni/
Note that there are both pros and cons for using this method. Browsers take milliseconds to render standard html. It may be easier to create a widget mainMenu and include it (best of all, using UiBinder) into every view, rather than deal with two activity managers.