Xamarin Forms ToolbarItem doesn't change IsEnabled from XAML - xamarin.forms

I'm facing a problem with ToolbarItem and IsEnabled property when trying to turn it on/off from XAML using triggers. ToolbarItem doesn't support triggers, so what I do is to create a Button (a hidden one) which supports triggers and then bind Button.IsEnabled to ToolbarItem.IsEnabled; here is the sample code:
<ContentPage.ToolbarItems>
<ToolbarItem x:Name="tlbSave" Text="Save" Clicked="Handle_Clicked">
<ToolbarItem.IsEnabled>
<Binding Source="{x:Reference btnTest}" Path="IsEnabled" />
</ToolbarItem.IsEnabled>
</ToolbarItem>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout Padding="10" VerticalOptions="CenterAndExpand">
<Entry x:Name="txtTest" HorizontalOptions="FillAndExpand" />
<Button x:Name="btnTest" Text="HIDDEN" IsEnabled="false" HorizontalOptions="FillAndExpand">
<Button.Triggers>
<MultiTrigger TargetType="Button">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding Source={x:Reference txtTest}, Path=Text.Length,
Converter={convert:IsPositiveIntegerConverter}}" Value="true" />
</MultiTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiTrigger>
</Button.Triggers>
</Button>
</StackLayout>
</ContentPage.Content>
If you try this piece of code you will see how btnTest gets enable/disable when txtTest.Text has some value. But it isn't affecting tlbSave.IsEnabled property.
However, this work perfect in code behind when I set tlbSave.IsEnabled into btnText.PropertyChanged EventHandler
btnTest.IsVisible is false, I'm just showing it up for testing purposes.
Any idea about how to deal with this?

This is because of the IsEnabled property of ToolbarItem is read-only.
If you just set IsEnabled property of a toolbar item in your XAML to false or true, you will get the following exception at runtime.
System.InvalidOperationException: The BindableProperty "IsEnabled" is readonly.
And if you take a look at Microsoft's documentation, you will notice that you cannot directly set IsEnabled property of a toolbar item.
For disabling a toolbar item, the suggested way is to use a command and it's CanExecute.

I found out a way to solve this problem, at least a way better than implementing OnPropertyChange for btnTest
<ContentPage.ToolbarItems>
<ToolbarItem x:Name="tlbSave" Text="Save" Clicked="Handle_Clicked" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout Padding="10" VerticalOptions="CenterAndExpand">
<Entry x:Name="txtTest" HorizontalOptions="FillAndExpand" />
<Button x:Name="btnTest" Text="HIDDEN">
<Button.Triggers>
<MultiTrigger TargetType="Button">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding Source={x:Reference txtTest}, Path=Text.Length,
Converter={convert:IsPositiveIntegerConverter}}" Value="true" />
</MultiTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiTrigger>
</Button.Triggers>
<Button.IsEnabled>
<Binding Source="{x:Reference tlbSave}" Path="IsEnabled" Mode="OneWayToSource" />
</Button.IsEnabled>
</Button>
</StackLayout>
</ContentPage.Content>
Then set btnTest.IsEnabled = false; inside constructor and everything will go as smooth as I want.

Related

How to make behavior binding on customer control in Xamarin form or dotnet maui?

I created a customer entry which contain a label for error message
<Grid xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ExampleApp.Contols.CustomerEntry"
Title="Customer"
x:Name="AppEntry">
<Entry x:Name="ControlEntry"
BackgroundColor="{Binding BackgroundColor, Source={x:Reference AppEntry}}"/>
<Label x:Name="ConrolErrorMessage"
Text="{Binding ErrorMessage, Source={x:Reference AppEntry}}"/>
</Grid>
Now using the control, in any page.
<CustomerEntry
x:Name="Password"
BackgroundColor="Blue">
<controls:EntryControl.Behaviors>
<toolkit:TextValidationBehavior
InvalidStyle="{StaticResource InvalidEntry}"
MinimumLength="10"
MaximumLength="100" />
</controls:EntryControl.Behaviors>
</CustomerEntry>
The InvalidEntry style is simple:
<Style
x:Key="InvalidEntry"
TargetType="controls:CustomerEntry">
<Setter Property="BackgroundColor" Value="Red" />
</Style>
Now the behavior is not working. Is it possible to bind pass the behavior to the Entry of the customer control?
I try to make the entry of the customer control as follow:
<Entry x:Name="ControlEntry"
BackgroundColor="{Binding BackgroundColor, Source={x:Reference AppEntry}}"
Behaviors="{Binding Behaviors, Source={x:Reference AppEntry}}"/>
The above code didn't work.

How do I expose MultiValidationBehavior's children in custom control?

I'm creating a custom control for an entry that can be validated.
I did this by creating a ContentView that has a Grid as it's child that contains the entry, error label, etc.
I'd like this to be flexible when it comes to validation, so ideally it would be nice to expose the Entry's MultiValidationBehavior's Children property, or set that property as my control's content property.
As it stands now, I haven't figured out a way to add behaviors to my custom control.
Is this possible?
<ContentView x:Class="MPK.UI.Views.Components.FormEntry"
x:Name="FormEntryControl"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:yummy="clr-namespace:Xamarin.Forms.PancakeView;assembly=Xamarin.Forms.PancakeView"
xmlns:converters="clr-namespace:MPK.UI.Converters;assembly=MPK.UI">
<ContentView.Resources>
<ResourceDictionary>
<converters:IsValidToEntryBorderConverter x:Key="IsValidToEntryBorderConverter"/>
<converters:ErrorsToLabelTextConverter x:Key="ErrorsToLabelTextConverter"/>
<xct:InvertedBoolConverter x:Key="InvertedBoolConverter" />
</ResourceDictionary>
</ContentView.Resources>
<Grid>
<yummy:PancakeView CornerRadius="10"
HeightRequest="50"
HorizontalOptions="FillAndExpand"
BackgroundColor="{StaticResource EntryBackgroundColor}"
Padding="16,0,16,0">
<yummy:PancakeView.Behaviors>
<xct:AnimationBehavior AnimateCommand="{Binding Source={x:Reference FormEntryControl}, Path=ShakeCommand}">
<xct:AnimationBehavior.AnimationType>
<xct:ShakeAnimation />
</xct:AnimationBehavior.AnimationType>
</xct:AnimationBehavior>
</yummy:PancakeView.Behaviors>
<yummy:PancakeView.Border>
<yummy:Border
Color="{Binding IsValid, Source={x:Reference MultiValidationBehavior}, Converter={StaticResource IsValidToEntryBorderConverter}}"
Thickness="1" />
</yummy:PancakeView.Border>
<Entry x:Name="Entry"
Text="{Binding Text, Source={x:Reference FormEntryControl}}"
Placeholder="{Binding Placeholder, Source={x:Reference FormEntryControl}}"
ReturnType="{Binding ReturnType, Source={x:Reference FormEntryControl}}"
ReturnCommand="{Binding ReturnCommand, Source={x:Reference FormEntryControl}}"
PlaceholderColor="{StaticResource EntryPlaceholderTextColor}"
BackgroundColor="Transparent"
IsPassword="{Binding IsPassword, Source={x:Reference FormEntryControl}}"
ClearButtonVisibility="{Binding ClearButtonVisibility, Source={x:Reference FormEntryControl}}">
<Entry.Effects>
<xct:RemoveBorderEffect />
</Entry.Effects>
<Entry.Behaviors>
<xct:MultiValidationBehavior x:Name="MultiValidationBehavior"
IsValid="{Binding IsValid, Source={x:Reference FormEntryControl}, Mode=TwoWay}"
Children="{Binding ValidationBehaviors, Source={x:Reference FormEntryControl}}"/>
<!-- Binding children doesn't work here -->
</Entry.Behaviors>
</Entry>
</yummy:PancakeView>
<xct:Expander Margin="8,4,0,0"
AnimationLength="100"
IsExpanded="{Binding IsValid, Source={x:Reference FormEntryControl}, Mode=OneWay, Converter={StaticResource InvertedBoolConverter}}">
<Label Text="{Binding Errors, Source={x:Reference MultiValidationBehavior}, Converter={StaticResource ErrorsToLabelTextConverter}}"
TextColor="{StaticResource ErrorColor}" />
</xct:Expander>
</Grid>
</ContentView>
The solution was much simpler than I expected.
In my control's code behind I needed to add a property that points to the multivalidationbehavior's children property.
public IList<ValidationBehavior> ValidationBehaviors => TheMultiValidationBehavior.Children;
Using my custom control looks something like this:
<components:FormEntry Placeholder="Name">
<components:FormEntry.ValidationBehaviors>
<xct:TextValidationBehavior MinimumLength="1" xct:MultiValidationBehavior.Error="Min: 1"/>
</components:FormEntry.ValidationBehaviors>
</components:FormEntry>

Xamarin Forms IsEnabled MultiTrigger on Entry is not working

I am trying to get an entry IsEnabled property to set based on 2 different boolean values using a MultiTrigger in Xamarin.Forms 4.6.0.726. I have also tried in the last stable 4.5 version too.
Unfortunately the Entry IsEnabled property seems to remain at whatever the Setter's value is set to (in the case, true).
I have tried the two types of BindingCondition in the below code sample. The first (uncommented) conditions are bound to the IsVisible properties of two other elements on the page. The StackLayout and the Image will toggle their visibility as expected, however the Entry IsEnabled will not changed.
The second snip of code binds directly to the values in the ViewModel, implenenting INotifyPropertyChanged but has the exact same result where the IsEnabled value does not change.
I have run out of ideas and i'm starting to wonder if this is a bug with Xamarin and MultiTriggers. There doesn't seem to be a huge amount of people using them online, and those that do I have mine set out in what seems to be the most common way in the first set of code.
<StackLayout x:Name="ButtonsStack" IsVisible="{Binding Invoice.Editable}">
<!-- Content Here -->
</StackLayout>
<Image x:Name="InvoiceImage" IsVisible="{Binding IsUploadInvoice}" />
<StackLayout Orientation="Horizontal" HorizontalOptions="End">
<Entry Text="{Binding Invoice.TotalAmount}">
<Entry.Triggers>
<MultiTrigger TargetType="Entry">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding Source={x:Reference ButtonsStack}, Path=IsVisible}" Value="True"/>
<BindingCondition Binding="{Binding Source={x:Reference InvoiceImage}, Path=IsVisible}" Value="True"/>
<!--<BindingCondition Binding="{Binding Invoice.Editable}" Value="True"/>
<BindingCondition Binding="{Binding IsUploadInvoice}" Value="True"/>-->
</MultiTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiTrigger>
<Entry.Triggers>
<Entry>
</StackLayout>
As zafar said, you need to set Entry IsEnable="False" by default, when all the conditions are true, the setter makes the Entry's IsEnabled property true.
<StackLayout>
<Entry IsEnabled="False" Text="{Binding Invoice.TotalAmount}">
<Entry.Triggers>
<MultiTrigger TargetType="Entry">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding Source={x:Reference ButtonsStack}, Path=IsVisible}" Value="True" />
<BindingCondition Binding="{Binding Source={x:Reference InvoiceImage}, Path=IsVisible}" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiTrigger>
</Entry.Triggers>
</Entry>
</StackLayout>
About Multi triggers, please take a look:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/triggers

Is it possible to have a tab inside of a page in XamarinForm?

I typed "tab" and the only thing that seems to be relavent in the IntelliSense was "TabbedPage". I searched Google, and it also only showed tabbed page.
But what if I want something else above the tab? Something like this? Is this achievable in Xamarin Form? It is possible in native Android.
--------------------
Text inputs, buttons
---------------------
tabbed or swipe-able
content
--------------------
TabbedPage is a Page, is not a View so you can't include it inside another page.
On GitHub there is some TabView you can use. For example XFControls
<ctrls:TabView.TabTemplate>
<DataTemplate>
<StackLayout>
<ctrls:FontIcon Glyph="{Binding Glyph}"
FontFamily="{StaticResource font}"
FontSize="25"
Color="Gray"
HorizontalOptions="Center"
>
<ctrls:FontIcon.Triggers>
<DataTrigger TargetType="ctrls:FontIcon"
Binding="{Binding IsSelected}"
Value="True"
>
<Setter Property="Color" Value="Red" />
</DataTrigger>
</ctrls:FontIcon.Triggers>
</ctrls:FontIcon>
<Label Text="{Binding Title}"
TextColor="Gray"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
FontSize="10">
<Label.Triggers>
<DataTrigger TargetType="Label"
Binding="{Binding IsSelected}"
Value="True"
>
<Setter Property="TextColor" Value="Red" />
</DataTrigger>
</Label.Triggers>
</Label>
</StackLayout>
</DataTemplate>
</ctrls:TabView.TabTemplate>
<ctrls:TabView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Title}"
HorizontalOptions="Center" />
</DataTemplate>
</ctrls:TabView.ItemTemplate>
</ctrls:TabView>

Picker with custom Items [Xaml]

I am trying to figure out a way how to create a picker with custom items in XAML. I am not sure if it is possible to achieve this task only using xaml. The picker would have to contain items that consist of a label and a switch. The code below illustrates the end goal:
<Picker Title="Picker title here">
<Picker.ItemsSource>
<StackLayout Orientation="Horizontal">
<Label Text="Hello Xamarin.Forms!" />
<Switch />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Hello Xamarin.Forms2!" />
<Switch />
</StackLayout>
</Picker.ItemsSource>
</Picker>
It's not possible. I think you can create a Popup with a ListView, where you have a ViewCell with all controls you need

Resources