Expander direction error InvalidOperationException: Header not initialized - xaml

When set Expander direction an error message is displayed
System.InvalidOperationException: 'Header not initialized'
<xct:Expander Direction="Left">
<xct:Expander.Header>
<Label Text="H" TextColor="Black"/>
</xct:Expander.Header>
<StackLayout Orientation="Horizontal" >
<Label Text="!" TextColor="Black" />
<Label Text="D" TextColor="Red" />
</StackLayout>
</xct:Expander>
The code works fine if there is no direction attribute.

This is a known bug https://github.com/xamarin/XamarinCommunityToolkit/issues/1144 which has been fixed in this pr and will be available on a next release.

Setting the direction after the header definition made it works for me.
<xct:Expander>
<xct:Expander.Header>
<Label Text="H" TextColor="Black"/>
</xct:Expander.Header>
<xct:Expander.Direction>
<xct:ExpandDirection>Left</xct:ExpandDirection>
</xct:Expander.Direction>
<StackLayout Orientation="Horizontal" >
<Label Text="!" TextColor="Black" />
<Label Text="D" TextColor="Red" />
</StackLayout>
</xct:Expander>

Related

First Label within StackLayout wraps to second line when second label is longer than the remaining space

Within a grid I want to display 2 labels next to each other. A static label of "Reason:" and a bound label that displays the reason description, like so;
<StackLayout
Grid.Row="4" Grid.Column="3"
Spacing="0" Orientation="Horizontal" HorizontalOptions="StartAndExpand">
<Label
Text="{i18n:Translate reason}" HorizontalOptions="Start"
HorizontalTextAlignment="Start"
Style="{StaticResource ListSubItemStyle}" Margin="0,0,10,0" />
<Label
Text="{Binding ReasonCodeDescription}" HorizontalOptions="StartAndExpand"
HorizontalTextAlignment="Start" LineBreakMode="TailTruncation"
Style="{StaticResource ListSubItemStyle}" Margin="0,0,0,0" />
</StackLayout>
When the ReasonCodeDescription is longer than the space left on the line it causes the "reason" label to wrap to a new line;
I have tried altering the "spacing", "margin", "padding", "horizontal*" attributes and different "linebreakmode"s to no avail.
I want it to look like the first line albeit with the extra wording truncated;
You have "StartAndExpand" on the wrong label. You have told second label to be "greedy" when there is a conflict. Move that to first label:
<Label Text="{i18n:Translate reason}" HorizontalOptions="StartAndExpand"
<Label Text=... HorizontOptions="Start"
If that doesn't fix, use nested Grid:
<Grid Grid.Row="4" Grid.Column="3" ColumnDefinitions="Auto,*" />
<Label Grid.Column="0" Text="{i18n:Translate reason}" .. />
<Label Grid.Column="1" Text.. />
</Grid>

XAML, Binding, Source and Path

I am learning to understand, how the binding mechanism works in XAML for .NET MAUI. I am assuming this is the same for all XAML projects, WPF, MAUI etc.
At the end is the whole XAML.
This XAML works fine:
<Button WidthRequest="150" Text="Add Activity"
Command="{Binding AddActivityEntityCommand}"
IsEnabled="{Binding IsNotBusy}"
Grid.Row="2"
Margin="8"/>
Is the reason why this works because the Button is part of the ContentPage, which has it's x:DataType set to MainPageViewModel, which is where the command lives?
The Binding is set to AddActivityEntityCommand, while the actual method signature is
async Task AddActivityEntityAsync(). How is this resolved? Since it obviously doesn't match the name, but it works. And what are the method signature requirements for this to work/being recognized?
This on the other hand, doesn't just work as easy out of the box:
<Label HorizontalOptions="End" TextColor="Red" Padding="0,0,10,0" Text="🗑"
IsVisible="{Binding IsSynchronized}">
<Label.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={x:Type viewmodel:MainPageViewModel},
Path=DeleteActivityCommand}" />
</Label.GestureRecognizers>
</Label>
In this context, adding Command="{Binding DeleteActivityCommand} doesn't work, because it derives its Path from <DataTemplate x:DataType="model:ActivityEntity">, I am assuming, which is the data object and not the ViewModel, where the command actually is.
The problem here is, that as soon as I enter this XAML Command="{Binding Source={x:Type viewmodel:MainPageViewModel}, Path=DeleteActivityCommand}", the CollectionView shows empty and there is an unhandled exception thrown when the view is loaded:
System.Reflection.TargetException: Object does not match target type.
The method signature for this command is this async Task DeleteActivityAsync()
What am I missing?
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="OnesieMobile.View.MainPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:model="clr-namespace:OnesieMobile.Model"
xmlns:viewmodel="clr-namespace:OnesieMobile.ViewModel"
x:DataType="viewmodel:MainPageViewModel"
Title="{Binding Title}">
<Grid
ColumnDefinitions="*"
RowDefinitions="20,50,50,*"
RowSpacing="0">
<Label HorizontalOptions="End" Margin="10,0,10,0" Text="{Binding CurrentDateTime}" Grid.Row="0"/>
<Entry Margin="10,0,10,0"
Grid.Row="1" x:Name="entryNewActivity"
Placeholder="New Activityssss" HeightRequest="30" Text="{Binding NewActivityTitle}" />
<Button WidthRequest="150" Text="Add Activity"
Command="{Binding AddActivityEntityCommand}"
IsEnabled="{Binding IsNotBusy}"
Grid.Row="2"
Margin="8"/>
<CollectionView
Grid.Row="3"
ItemsSource="{Binding ActivityEntities}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:ActivityEntity">
<Grid Padding="10,0,10,0">
<Frame Style="{StaticResource CardView}">
<Grid ColumnDefinitions="*,30,50">
<StackLayout Padding="10,5,0,0" Grid.Column="0">
<Label Text="{Binding Title}" />
</StackLayout>
<StackLayout Padding="10,5,0,0" Grid.Column="1">
<Label HorizontalOptions="End" TextColor="Red"
Padding="0,0,10,0" Text="🗑" IsVisible="{Binding IsSynchronized}" >
<Label.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={x:Type viewmodel:MainPageViewModel},
Path=DeleteActivityCommand}" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
<StackLayout Padding="10,5,0,0" Grid.Column="2">
<Label HorizontalOptions="End"
Padding="0,0,10,0" Text="✔" IsVisible="{Binding IsSynchronized}" />
</StackLayout>
</Grid>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<ActivityIndicator IsVisible="{Binding IsBusy}"
IsRunning="{Binding IsBusy}"
HorizontalOptions="FillAndExpand"
VerticalOptions="CenterAndExpand"
Grid.RowSpan="3"
Grid.ColumnSpan="2"/>
</Grid>
</ContentPage>
Update:
MainPageViewModel.cs contains these Commands
[ICommand]
async Task DeleteActivityAsync()
{
}
[ICommand]
async Task AddActivityEntityAsync()
{
}
Inside an item template, there are several ways to refer to the original BindingContext. I like to do it by getting it from the collection itself:
<CollectionView x:Name="myCollection" ...>
...
<CollectionView.ItemTemplate>
...
Command="{Binding Source={x:Reference myCollection},
Path=BindingContext.DeleteActivityCommand}" />
FUTURE TBD:
I don't like the command name not existing anywhere in the implementing code. Hopefully there will eventually be a way to specify the command name in the ICommand attribute, to make it obvious:
// This won't work today.
[ICommand Name="DeleteActivityCommand"]
...
For now, we have to learn [ICommand]'s magic naming rules, which seem to be "Remove Async from end (if present); Add Command to end".

Styling Label inside another control

In Xamarin Forms, having a XAML code: (an example below) is it possible to move the style of the Label and the Image to appplication level style and make it default for all Expander headers (or other containers)?
<xct:Expander>
<xct:Expander.Header>
<StackLayout>
<Label
Margin="20,10,0,10"
FontAttributes="Bold"
HorizontalOptions="StartAndExpand"
Text="ABCD"
TextTransform="Uppercase"
VerticalOptions="Center" />
<Image
Margin="0,0,20,0"
HorizontalOptions="End"
VerticalOptions="Center">
<Image.Source>
<FontImageSource
FontFamily="{StaticResource Font_FASolid}"
Glyph="{x:Static fonts:FASolid.ChevronDown}"
Size="{StaticResource MediumFontSize}" />
</Image.Source>
</Image>
</StackLayout>
</xct:Expander.Header>
</xct:Expander>
What I would like to have in my xaml finally is just:
<xct:Expander>
<xct:Expander.Header>
<StackLayout>
<Label Text="ABCD"/>
<Image>
<Image.Source>
<FontImageSource/>
</Image.Source>
</Image>
</StackLayout>
</xct:Expander.Header></xct:Expander>

How to remove the navigation bar of a ContentPage when using Shell

I have a registration/login page set up and I use shell to navigate between them but i want to remove the header (I am not sure if that's it's name)
I have tried adding
NavigationPage.HasNavigationBar="false"
this in my login page but with no effect. Maybe because my xaml pages are not defined as navigation pages.
Here is my Login.xaml
<?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:viewmodel="clr-namespace:Appointments.ViewModels"
x:DataType="viewmodel:RegLogViewModel"
x:Class="Appointments.Views.RegisterPage"
NavigationPage.HasNavigationBar="false">
<ContentPage.BindingContext>
<viewmodel:RegLogViewModel/>
</ContentPage.BindingContext>
<Grid
RowDefinitions="130,300,*,70">
<Label
Grid.Row="0"
Text="REGISTRATION"
HorizontalOptions="Center"
VerticalOptions="Center"
FontSize="40"
/>
<StackLayout
Grid.Row="1">
<Entry Placeholder="username" Text="{Binding Name}"/>
<Entry Placeholder="email" Text="{Binding Email}"/>
<Entry Placeholder="password" Text="{Binding Password}"/>
<Entry Placeholder="confirm password" Text="{Binding PasswordConfirmation}"/>
<Entry Placeholder="phone number" Text="{Binding Phone}"/>
<StackLayout Orientation="Horizontal">
<Label
Text="This account is for a hairstylist"
FontSize="20"/>
<Switch/>
</StackLayout>
</StackLayout>
<Button
Grid.Row="2"
Text="Register"
FontSize="Large"
VerticalOptions="Center"
HorizontalOptions="CenterAndExpand"
CornerRadius="10"
Padding="20,0"
BackgroundColor="Aquamarine"
Command="{Binding LoginCommand}"
/>
<Label
Grid.Row="3"
Text="Already a user? Login"
FontSize="20"
TextDecorations="Underline"
HorizontalOptions="Center"
VerticalOptions="Center"
>
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding GoToLoginCommand}"/>
</Label.GestureRecognizers>
</Label>
</Grid>
</ContentPage>
I guess you only need the ContentPage definition
And i have register a route to this page in my AppShell.xaml.cs and in my AppShell.xaml i've set ShellContent to the actual page and that's it.
Since you are using Shell, instead of setting the attached proeprty NavigationPage.HasNavigationBar you need to set Shell.NavBarIsVisible:
<ContentPage ...
Shell.NavBarIsVisible="False"
Related question
How can you restrict/control the navigation routes the user can visit based on login status/role?

Xamarin Forms Flex Layout sizing issue

In my Xamarin Forms app I need to have a flex layout inside another flex layout. This is because:
I need to have two columns in my app - one that takes up 80% of the screen and the other that takes up 20% of the screen. I use a FlexLayout with two children having the FlexLayout.Basis property set for this.
In one of the columns I need to display a series of views so they wrap to fill the available space. I use a FlexLayout set to Wrap to achieve this.
Below this layout I need to display some other controls.
My issue is that the FlexLayout containing the wrapped controls does not adjust its height accurately and the 'bottom' controls encroach on the wrapped layout. This is an example of the problem (in Android):
Label11 has been obscured by the button. The red border is the border of the FlexLayout in the Android UI Automator Viewer. The height does not appear to adjust in accordance with the controls that have been added.
If I remove the 'Right Button' column and therefore don't need the flex basis properties it sizes accurately. I think the FlexLayout containing the wrapped controls does not take account of the fact it is in a column that has been set to 80% width - it appears to base its height as if it was taking up the entire width of the screen. Does anyone know a way to get around this?
Here is the xaml to reproduce the problem:
<?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:local="clr-namespace:FlexLayoutProb"
x:Class="FlexLayoutProb.MainPage">
<StackLayout HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
AutomationId="stackLayoutTop">
<FlexLayout
AutomationId="FlexLayoutTop"
HorizontalOptions="FillAndExpand"
VerticalOptions="Start"
>
<StackLayout AutomationId="stackLayoutLeft" FlexLayout.Basis="80%" HorizontalOptions="FillAndExpand" >
<FlexLayout
AutomationId="FlexLayoutCtrls"
HorizontalOptions="FillAndExpand"
VerticalOptions="Start"
Wrap="Wrap" BackgroundColor="LightGreen"
>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label1"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label2"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label3"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label4"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label5"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label6"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label7"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label8"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label9"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label10"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label11"></Label>
</FlexLayout>
</StackLayout>
<StackLayout AutomationId="stackLayoutRight" FlexLayout.Basis="20%" HorizontalOptions="FillAndExpand">
<Button Text="Right Button" HorizontalOptions="FillAndExpand"></Button>
</StackLayout>
</FlexLayout>
<Button Text="Bottom Button"></Button>
</StackLayout>
</ContentPage>
Thanks to Harold Harvey who helped me solve this. This is what he suggested:
It looks like this issue is related to an existing bug posted to the Xamarin forms team. It is currently open and being investigated.
https://github.com/xamarin/Xamarin.Forms/issues/4875
The bug basically states that when a layout dynamically adds/removes children then the parent isn't being notified by the the height change. So, as a workaround I would offer this solution
<?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:local="clr-namespace:FlexLayoutProb"
x:Class="FlexLayoutProb.MainPage">
<StackLayout HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
AutomationId="stackLayoutTop">
<FlexLayout
AutomationId="FlexLayoutTop"
HorizontalOptions="FillAndExpand"
VerticalOptions="Start"
>
<StackLayout
AutomationId="stackLayoutLeft"
FlexLayout.Basis="80%"
HorizontalOptions="FillAndExpand"
HeightRequest="{Binding Height, Source={x:Reference FlexLayoutCtrls}}">
<FlexLayout
x:Name="FlexLayoutCtrls"
AutomationId="FlexLayoutCtrls"
HorizontalOptions="FillAndExpand"
VerticalOptions="Start"
Wrap="Wrap" BackgroundColor="LightGreen"
>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label1"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label2"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label3"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label4"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label5"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label6"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label7"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label8"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label9"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label10"></Label>
<Label HeightRequest="50" WidthRequest="100" Margin="5" BackgroundColor="LightCyan" Text="Label11"></Label>
</FlexLayout>
</StackLayout>
<StackLayout AutomationId="stackLayoutRight" FlexLayout.Basis="20%" HorizontalOptions="FillAndExpand">
<Button Text="Right Button" HorizontalOptions="FillAndExpand"></Button>
</StackLayout>
</FlexLayout>
<Button Text="Bottom Button"></Button>
</StackLayout>
</ContentPage>
The new lines are:
HeightRequest="{Binding Height, Source={x:Reference FlexLayoutCtrls}}"
x:Name="FlexLayoutCtrls"
I have tested this on UWP,iOS, and Android and it behaves as you would expect.
Since the FlexLayout is being resized correctly, this will pass the correctly height to the parent StackLayout.