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".
I got listview as shown below. I am looking for a way to somehow instead of true/false show some other text or place check marks instead. How can i achieve that?
Important remark: I have many pages with diffrent listviews where also true/false appears. I would like to be able to choose for which listview that solution be defined and the best that i could choose specific listview and column (if possible of course)
asdasdasd
Specific part where True/False values are placed (BottleIsFilled):
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label
Grid.Column="0"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center"
Text="{Binding BottleTypeName}" />
<Label
Grid.Column="1"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center"
Text="{Binding BottleIsFilled}" />
<Label
Grid.Column="2"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center"
Text="{Binding Amount}" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
As Jason suggested use BoolToObjectConverter from XamarinCommunityToolkit package
Install the nuget package on all your projects (shared and platform).
Add the xaml namespace for this package xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
Instantiate and consume BoolToObjectConverter
Seems one common instance for each page is appropriate for your use case.
<ContentPage.Resources>
<xct:BoolToObjectConverter x:Name="BoolToObjectConverter" TrueObject="yes" FalseObject="no"/>
</ContentPage.Resources>
...
<Label
Grid.Column="1"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center"
Text="{Binding BottleIsFilled, Converter={StaticResource BoolToObjectConverter}}"/>
Docs
https://learn.microsoft.com/en-us/xamarin/community-toolkit/converters/booltoobjectconverter
Note
If you got error about the namespace, try typing <BoolToObjectConverter /> and put cursor on it, on vs intelisense should highlight it and suggest and apply the correct namespace for you when you click "ctrl+." Or ctrl+enter.
I've added a show password icon to my entry field to allow for password peaks. I have included the entry field and the image in the same StackLayout. It works exactly as it should except the entry field line is shortened to accommodate the image on the same horizontal plane. I want the entry line to extend to the edge of the screen and the password image to appear above it.
This is how it displays currently:
Here is the xaml snippet for the password entry field and image contained in a StackLayout
<StackLayout x:Name="passwordStack" Orientation="Horizontal">
<Entry
x:Name="passwordEntry"
HorizontalOptions="FillAndExpand"
IsPassword="True"
VerticalOptions="Center">
</Entry>
<Image
x:Name="passwordEye"
Source="show_password_icon"
HorizontalOptions="End"
VerticalOptions="Center"
WidthRequest="30"
HeightRequest="30">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="ShowPassword" NumberOfTapsRequired="1" />
</Image.GestureRecognizers>
</Image>
</StackLayout>
Use a Grid instead of an StackLayout:
<Grid>
<Entry
Grid.Row="0"
Grid.Column="0"
x:Name="passwordEntry"
HorizontalOptions="FillAndExpand"
IsPassword="True"
VerticalOptions="Center">
</Entry>
<Image
Grid.Row="0"
Grid.Column="0"
x:Name="passwordEye"
Source="show_password_icon"
HorizontalOptions="End"
VerticalOptions="Center"
WidthRequest="30"
HeightRequest="30">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="ShowPassword" NumberOfTapsRequired="1" />
</Image.GestureRecognizers>
</Image>
</Grid>
This way, the Image will overlay the Entry.
A problem about this is that Image will be displayed on top of text if the text is too long. So a better solution would be to build a custom control.
I am working on a Xamarin.Forms app for a warehouse, we use the Zebra TC25, which has a build in barcode scanner. The scanner works fine and delivers codes.
I have a grid, as shown below, and I want to move the focus to the next text-field (Entry) after a scan has occured. Now I have a callback method which gets triggered after a scan has happened.
My question: How can I have an elegant way of knowing which element has focus and which should be the next?
I have tried using TabIndex - it was ignored.
I have tried giving them names just like below, and then use a dictionary, which maps each Entry-element to the next one to be focused. There I had to iterate over the Keys, and then call something like _focusOrder[entry].Focus();, which is also not the most pretty solution. Specially considering that there are going to be a few forms with more that just 3 codes to be scanned.
Note: The scanner is implemented as a keyboard, and I don't have to set the value from the scanner in my callback - it just works out-of-the-box. Therefore I don't keep track on the focused item, as it is not necessary (for the input).
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Text="Von" />
<Entry Grid.Row="0" Grid.Column="1" Text="{Binding Path=VonLagerplatz}" x:Name="_vonEntry" />
<Label Grid.Row="1" Grid.Column="0" Text="Nach" />
<Entry Grid.Row="1" Grid.Column="1" Text="{Binding Path=NachLagerplatz}" x:Name="_nachEntry" />
<Label Grid.Row="2" Grid.Column="0" Text="Artikel" />
<Entry Grid.Row="2" Grid.Column="1" Text="{Binding Path=Artikel}" x:Name="_artikelEntry" />
</Grid>
Optimal solutions: (which I could not make work, cause it is my first Xamarin app)
Make the TabIndex-property work.
Use an attached property, where I can reference the next element in XAML. So have something like <Entry .... x:Name="_vonEntry" NextElement="{Reference _nachEntry}" />
One way to approach this would be to write a custom behaviour that you attach to your entries. Something like this:
<Entry x:Name="VonEntry" Grid.Row="0" Grid.Column="1">
<Entry.Behaviors>
<custom:NextElementOnReturn Element="{x:Reference NachEntry}" />
</Entry.Behaviors>
</Entry>
<Entry x:Name="NachEntry" Grid.Row="1" Grid.Column="1" />
You would have to implement the behaviour NextElementOnReturn by yourself. As far as I know there's nothing builtin.
There's a nice tutorial about behaviours here to get you startet https://blog.xamarin.com/extend-xamarin-forms-controls-functionality-with-behaviors/
I would like to add drop shadow to a Label. This label is overlapped in the Page, like an always visible control that opens a filtering page.
Please find a gif attached with my screen:
Here's my XAML:
<!-- **** Filter button **** -->
<Label
Margin="0,0,10,10"
WidthRequest="50"
HeightRequest="50"
HorizontalOptions="End"
VerticalOptions="End"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
FontSize="30"
Style="{DynamicResource FilterAction}"
Text="ï‚°"
BackgroundColor="{StaticResource ComplementColor}"
FontFamily="{x:Static artina:FontAwesome.FontName}"
TextColor="White">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding btn_open_filter_businesses_click}" />
</Label.GestureRecognizers>
</Label>
<templates:Badge
BadgeText="{Binding number_of_filters_selected}"
BadgeTextColor="White"
BadgeBackgroundColor="#1DBDFF"
HorizontalOptions="End"
VerticalOptions="End"
TranslationX="-4"
TranslationY="-4"
IsVisible="{Binding number_of_filters_selected, Converter={StaticResource filterVis}"
x:Name="filtersCountBagde">
<templates:Badge.GestureRecognizers>
<TapGestureRecognizer Command="{Binding btn_open_filter_businesses_click}" />
</templates:Badge.GestureRecognizers>
</templates:Badge>
I would like something like Gmail, find the example below:
Any help would be appreciated.
You can use Xamarin Effects to achieve a shadow on your button. There is a code sample that you can download here which should get you started:
Shadow Effect
It will involve creating platform-specific implementations for your shadow.
You could also try the idea put forward in this similar question.