Xamarin Community Toolkit TabView switching tabs when tapping on entry - xamarin

I have a strange behavior with the TabView.
View:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PLUTester.Test.View.Home"
xmlns:view="clr-namespace:PLUTester.Test.View"
xmlns:controls2="clr-namespace:MarcTron.Plugin.Controls;assembly=Plugin.MtAdmob"
xmlns:local="clr-namespace:PLUTester;assembly=PLUTester"
xmlns:xtc="http://xamarin.com/schemas/2020/toolkit"
xmlns:localfonts="clr-namespace:PLUTester.AppFonts"
BackgroundColor="White">
<ContentPage.Resources>
<Style TargetType="xtc:TabViewItem">
<Setter Property="VerticalOptions" Value="Center"/>
<Setter Property="FontSize" Value="30" />
<Setter Property="FontSizeSelected" Value="35"/>
<Setter Property="FontFamily" Value="MyIcon"/>
<Setter Property="VerticalOptions" Value="FillAndExpand" />
<Setter Property="TextColor" Value="Gray" />
<Setter Property="TextColorSelected" Value="Black" />
<Setter Property="FontAttributesSelected" Value="Bold" />
<Setter Property="BadgeTextColor" Value="Black"/>
</Style>
<Style x:Key="CustomTabStyle" TargetType="xtc:TabView">
<!--<Setter Property="IsTabTransitionEnabled" Value="True" />-->
<Setter Property="TabStripHeight" Value="50" />
<Setter Property="TabContentBackgroundColor" Value="White" />
<Setter Property="TabIndicatorPlacement" Value="Bottom"/>
<Setter Property="TabIndicatorColor" Value="Black"/>
<Setter Property="TabStripPlacement" Value="Bottom" />
</Style>
</ContentPage.Resources>
<ContentPage.Content>
<Grid>
<xtc:TabView Style="{StaticResource CustomTabStyle}" IsSwipeEnabled="False">
<xtc:TabView.TabStripBackgroundView>
<Frame BackgroundColor="White" BorderColor="Gray" CornerRadius="0" Margin="-1,0,-1,0"/>
</xtc:TabView.TabStripBackgroundView>
<xtc:TabViewItem Text="{x:Static localfonts:FontIcon.SchoolOutline}">
<Grid>
<ContentView Content="{Binding TestView}"/>
</Grid>
</xtc:TabViewItem>
<xtc:TabViewItem Text="{x:Static localfonts:FontIcon.CardsOutline}">
<Grid>
<ContentView Content="{Binding LernView}"/>
</Grid>
</xtc:TabViewItem>
<xtc:TabViewItem Text="{x:Static localfonts:FontIcon.ChartLine}">
<Grid>
<ContentView Content="{Binding StatisticsView}"/>
</Grid>
</xtc:TabViewItem>
<xtc:TabViewItem Text="{x:Static localfonts:FontIcon.Podium}">
<Grid>
<ContentView Content="{Binding HighscoreView}"/>
</Grid>
</xtc:TabViewItem>
<xtc:TabViewItem Text="{x:Static localfonts:FontIcon.Tune}">
<Grid>
<ContentView>
<view:Settings/>
</ContentView>
</Grid>
</xtc:TabViewItem>
</xtc:TabView>
</Grid>
</ContentPage.Content>
</ContentPage>
Viewmodel:
private ContentView _TestView;
public ContentView TestView
{
get { return _TestView; }
set { _TestView = value;
OnPropertyChanged(nameof(TestView));
}
}
private ContentView _LernView;
public ContentView LernView
{
get { return _LernView; }
set { _LernView = value;
OnPropertyChanged(nameof(LernView));
}
}
private ContentView _StatisticsView;
public ContentView StatisticsView
{
get { return _StatisticsView; }
set { _StatisticsView = value;
OnPropertyChanged(nameof(StatisticsView));
}
}
private ContentView _HighscoreView;
public ContentView HighscoreView
{
get { return _HighscoreView; }
set { _HighscoreView = value;
OnPropertyChanged(nameof(HighscoreView));
}
}
The second page (LernView) contains some entries, when I tap on a entry the tabview switches back to the first page and so its not possible to enter some data.
The strange thing is, when I change the position of the pages, so that "LernView" is before "TestView" in the TabView, then the entries in "LernView" works as expected, but the entries in "TestView" kicks me back to the first site.
Same behavior with picker.
Here is the output from debug window, when tapping a entry:
[ViewRootImpl#b8ac428[MainActivity]] ViewPostIme pointer 0
[ViewRootImpl#b8ac428[MainActivity]] ViewPostIme pointer 1
[InputMethodManager] prepareNavigationBarInfo() DecorView#add6a19[MainActivity]
[InputMethodManager] getNavigationBarColor() -16711423
[InputMethodManager] Starting input: tba=com.modev.plutester ic=com.android.internal.widget.EditableInputConnection#4a24c5d mNaviBarColor -16711423 mIsGetNaviBarColorSuccess true , NavVisible : true , NavTrans : false
[InputMethodManager] startInputInner - Id : 0
[InputMethodManager] startInputInner - mService.startInputOrWindowGainedFocus
[InputTransport] Input channel constructed: 'ClientS', fd=231
[InputTransport] Input channel destroyed: 'ClientS', fd=226
[InputMethodManager] SSI - flag : 0 Pid : 28689 view : com.modev.plutester
[InputMethodManager] prepareNavigationBarInfo() DecorView#add6a19[MainActivity]
[InputMethodManager] getNavigationBarColor() -16711423
[InputMethodManager] prepareNavigationBarInfo() DecorView#add6a19[MainActivity]
[InputMethodManager] getNavigationBarColor() -16711423
[InputMethodManager] Starting input: tba=com.modev.plutester ic=null mNaviBarColor -16711423 mIsGetNaviBarColorSuccess true , NavVisible : true , NavTrans : false
[InputMethodManager] startInputInner - Id : 0
[InputMethodManager] startInputInner - mService.startInputOrWindowGainedFocus
[InputTransport] Input channel constructed: 'ClientS', fd=230
[InputTransport] Input channel destroyed: 'ClientS', fd=231
[IInputConnectionWrapper] getExtractedText on inactive InputConnection
[IInputConnectionWrapper] requestCursorAnchorInfo on inactive InputConnection
[InsetsSourceConsumer] setRequestedVisible: visible=true, type=13, host=com.modev.plutester/crc64abfcbdcb04412aea.MainActivity, from=android.view.InsetsSourceConsumer.show:229 android.view.InsetsController.showDirectly:1437 android.view.InsetsController.controlAnimationUnchecked:1110 android.view.InsetsController.applyAnimation:1417 android.view.InsetsController.show:962 android.view.ViewRootImpl$ViewRootHandler.handleMessage:6098 android.os.Handler.dispatchMessage:106 android.os.Looper.loop:246 android.app.ActivityThread.main:8506 java.lang.reflect.Method.invoke:-2
[SurfaceControl] hide : mNativeObject = 480531777920 - sc.mNativeObject = 480531879648 - Surface(name=Surface(name=5fb0fa1 InputMethod)/#0x49804dd - animation-leash)/#0x1b84285
[SurfaceControl] nativeSetFlags Done : Surface(name=Surface(name=5fb0fa1 InputMethod)/#0x49804dd - animation-leash)/#0x1b84285
[SurfaceControl] hide : mNativeObject = 480531777920 - sc.mNativeObject = 480531879648 - Surface(name=Surface(name=5fb0fa1 InputMethod)/#0x49804dd - animation-leash)/#0x1b84285
[SurfaceControl] nativeSetFlags Done : Surface(name=Surface(name=5fb0fa1 InputMethod)/#0x49804dd - animation-leash)/#0x1b84285
[SurfaceControl] release : mNativeObject = 480531879648 - Surface(name=Surface(name=5fb0fa1 InputMethod)/#0x49804dd - animation-leash)/#0x1b84285 / android.view.-$$Lambda$Rl1VZmNJ0VZDLK0BAbaVGis0rrA.accept:2 android.view.InsetsSourceControl.release:170 android.view.InsetsAnimationThreadControlRunner.releaseControls:119 android.view.InsetsAnimationThreadControlRunner.access$200:40
[SurfaceControl] nativeRelease nativeObject s[480531879648]
[SurfaceControl] nativeRelease nativeObject e[480531879648]
The mentioned Entry:
<Entry Text="{Binding MaxPLU}" Keyboard="Numeric" FontSize="Medium" VerticalOptions="EndAndExpand" WidthRequest="80" HorizontalTextAlignment="Center" HorizontalOptions="Start">
<Entry.Behaviors>
<controls:NumberValidationBehavior/>
</Entry.Behaviors>
</Entry>
NumberValidationBehavior:
public class NumberValidationBehavior : Behavior<Entry>
{
protected override void OnAttachedTo(Entry entry)
{
entry.TextChanged += OnEntryTextChanged;
base.OnAttachedTo(entry);
}
protected override void OnDetachingFrom(Entry entry)
{
entry.TextChanged -= OnEntryTextChanged;
base.OnDetachingFrom(entry);
}
private static void OnEntryTextChanged(object sender, TextChangedEventArgs args)
{
if (!string.IsNullOrWhiteSpace(args.NewTextValue))
{
bool isValid = args.NewTextValue.ToCharArray().All(x => char.IsDigit(x)); //Make sure all characters are numbers
((Entry)sender).Text = isValid ? args.NewTextValue : args.NewTextValue.Remove(args.NewTextValue.Length - 1);
}
}
}
Some clerification:
TabView
MyPage1 -> Everything works as expected
MyPage2 -> Tapping in an entry kicks me back to MyPage1
Now I switch the pages:
TabView
MyPage2 -> Everything works as expected
MyPage1 -> Tapping in an entry kicks me back to MyPage2
What am I doing wrong?
Update: This strange behavior just occurs, if I set the HorizontalTextAlignment of the entry. If its not set, the entry works as expected.

Another work around is to disable all controls (Entry/Editor) that opens the virtual keyboard when leaving the Tab and Enable when it is on Active Tab.
weirdly when you focused on one Entry/Editor and navigated through Tabs and when you are navigating back one page after the control focused, it will fire the Focused Event and the Tab will adjust to at least show the cursor on the Entry or Editor.
private bool _isLernViewActive;
public bool IsLernViewActive
{
get { return _isLernViewActive; }
set { _isLernViewActive = value;
OnPropertyChanged(nameof(IsLernViewActive));
}
}
Xaml:
<Entry Text="{Binding MaxPLU}" Keyboard="Numeric" FontSize="Medium" VerticalOptions="EndAndExpand" WidthRequest="80"
IsEnabled={Binding IsLernViewActive} HorizontalTextAlignment="Center" HorizontalOptions="Start">
<Entry.Behaviors>
<controls:NumberValidationBehavior/>
</Entry.Behaviors>
</Entry>

Related

Dynamic styling in .Net Maui/Xamarin

Let's say I want to display a calendar. Each day (DayViewModel) can have such attributes that should influence styling:
is day of current month
is weekend
is day available
And based on that I'd like to style my DayContentView like follows:
if day of current month -> opacity 1
if not current month -> opacity 0.5
if weekend -> background color is red
if available -> background color is green
Moreover the last one property ("is day available") can change based on user's action (clicked button "Check availability").
In other frameworks I'd create a few styles/style classes and assign them accordingly base on those flags. But since StyleClass is not bindable I cannot. Can I? Maybe there is some workaround.
Another option could be to create a separate style (using inheritance if needed) for all the combinations. But first of all the number of combinations raises like 2^n and the second issue is that I have no idea how to dynamically change whole styl for a view changing its name. Is it possible?
Finishing my quite long question: How to do it right/in a smart way? I don't want to store values of colors, font sizes, opacities etc. in a view model and bind all the values manually.
The same thing can be achieved using DataTriggers.
Result:
MainPage.xaml
<Window
x:Class="WpfApp6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp6"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<ItemsControl Margin="5" ItemsSource="{Binding Days}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:DayModel}">
<Border
Padding="10"
Width="200"
Height="100"
Margin="5">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Gray" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsDayOfCurrentMonth}" Value="False">
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
<DataTrigger Binding="{Binding IsAvailable}" Value="True">
<Setter Property="Background" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding IsWeekend}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel>
<TextBlock Text="{Binding IsDayOfCurrentMonth, StringFormat='IsDayOfCurrentMonth: {0}'}" />
<TextBlock Text="{Binding IsAvailable, StringFormat='IsAvailable: {0}'}" />
<TextBlock Text="{Binding IsWeekend, StringFormat='IsWeekend: {0}'}" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
DayModel.cs
public record DayModel(bool IsDayOfCurrentMonth, bool IsWeekend, bool IsAvailable);
MainViewModel.cs
public class MainViewModel
{
public ObservableCollection<DayModel> Days { get; } = new();
public MainViewModel()
{
//Create all possible cominations of days
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 2; j++)
{
for (int k = 0; k < 2; k++)
{
Days.Add(new DayModel(i == 0, j == 0, k == 0));
}
}
}
}
}
EDIT: sharing style by multiple controls
Here's how the same Style with Triggers might be shared by multiple controls (two of the three TextBlocks in this example):
<DataTemplate DataType="{x:Type local:DayModel}">
<Border
Width="200"
Height="100"
Margin="5"
Padding="10"
Background="#eee">
<StackPanel>
<StackPanel.Resources>
<Style x:Key="ColoredTextBlock" TargetType="TextBlock">
<Setter Property="Foreground" Value="Gray" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsDayOfCurrentMonth}" Value="False">
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
<DataTrigger Binding="{Binding IsAvailable}" Value="True">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding IsWeekend}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock Style="{StaticResource ColoredTextBlock}" Text="{Binding IsDayOfCurrentMonth, StringFormat='IsDayOfCurrentMonth: {0}'}" />
<TextBlock Style="{StaticResource ColoredTextBlock}" Text="{Binding IsAvailable, StringFormat='IsAvailable: {0}'}" />
<TextBlock Text="{Binding IsWeekend, StringFormat='IsWeekend: {0}'}" />
</StackPanel>
</Border>
</DataTemplate>
The result looks like this:
I planned to add an example as #ToolmakerStave suggested but I found a solution in the meantime that seems to be very nice so I decided to share it instead. The idea is as follows:
Declare view model that implements INotifyPropertyChanged
Create a XAML file and define there all the styles
In the code behind detect change of binding context (view model) and all its dynamically changing properties (IsAvailable) in my case
When a change is detected then create a style using styles defined in the XAML file applying them in the desired order (making updates)
Assign the final style to particular elements
So starting from my view model:
public class DayViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// To style
public bool IsCurrentMonth { get; set; }
public bool IsWeekend { get; set; }
public bool IsHoliday { get; set; }
private bool _isAvailable;
public bool IsAvailable
{
get => _isAvailable;
set
{
_isAvailable = value;
PropertyChanged?.Invoke(
this,
new PropertyChangedEventArgs(nameof(IsAvailable))
);
}
}
// To display
public string DayOfWeek { get; set; }
public int DayOfMonth { get; set; }
}
Assuming that only IsAvailable property can change after some user action.
Then I have a XAML view that defines all the needed styles but they are not used directly in this file. I'll use them in the code behind. All the elements that I need to style have x:Name property set to get a reference to them from the code:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:MyProject.Calendar.ViewModels"
x:Class="MyProject.Calendar.ContentViews.DayView"
x:DataType="vm:DayViewModel">
<ContentView.Resources>
<ResourceDictionary>
<!-- By default Border has opacity 0.3 -->
<Style TargetType="Border">
<Setter Property="Opacity" Value="0.3" />
</Style>
<!-- These styles are used from the code behind -->
<Style x:Key="CurrentMonthBorder" TargetType="Border">
<Setter Property="Opacity" Value="1.0" />
</Style>
<Style x:Key="WeekendBorder" TargetType="Border">
<Setter Property="BackgroundColor" Value="LightGray" />
</Style>
<Style x:Key="AvailableBorder" TargetType="Border">
<Setter Property="BackgroundColor" Value="Green" />
</Style>
<Style x:Key="HolidayLabel" TargetType="Label">
<Setter Property="TextColor" Value="DarkRed" />
</Style>
<!-- // These styles are used from the code behind -->
</ResourceDictionary>
</ContentView.Resources>
<Border x:Name="DayBorder">
<VerticalStackLayout HorizontalOptions="FillAndExpand">
<Label x:Name="DayOfWeekLabel" Text="{Binding DayOfWeek}" />
<Label x:Name="DayOfMonthLabel" Text="{Binding DayOfMonth}" />
</VerticalStackLayout>
</Border>
</ContentView>
Now a helper class that allows to update a style with another style:
public static class StyleExtension
{
public static Style Update(this Style style, Style otherStyle)
{
var result = new Style(style.TargetType);
var allSetters = style.Setters.Concat(otherStyle.Setters);
foreach (var setter in allSetters)
{
result.Setters.Add(setter);
}
return result;
}
public static Style UpdateIf(this Style style, bool condition, Style otherStyle)
{
return style.Update(condition ? otherStyle : new Style(style.TargetType));
}
}
Final part, the code behind:
public partial class DayView : ContentView
{
private DayViewModel _viewModel;
public DayView()
{
InitializeComponent();
BindingContextChanged += OnBindingContextChanged;
}
private void OnBindingContextChanged(object sender, EventArgs e)
{
_viewModel = BindingContext as DayViewModel;
_viewModel.PropertyChanged += OnAvailabilityChanged;
StyleDayBorder();
StyleDayOfWeekLabel();
StyleDayOfMonthLabel();
}
private void OnAvailabilityChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(DayViewModel.IsAvailable))
{
StyleDayBorder();
}
}
private void StyleDayBorder()
{
DayBorder.Style = new Style(typeof(Border))
.UpdateIf(_viewModel.IsCurrentMonth, GetStyle("CurrentMonthBorder"))
.UpdateIf(_viewModel.IsWeekend, GetStyle("WeekendBorder"))
.UpdateIf(_viewModel.IsAvailable, GetStyle("AvailableBorder"));
}
private void StyleDayOfWeekLabel()
{
DayOfWeekLabel.Style = new Style(typeof(Label))
.UpdateIf(_viewModel.IsHoliday, GetStyle("HolidayLabel"));
}
private void StyleDayOfMonthLabel()
{
DayOfMonthLabel.Style = new Style(typeof(Label))
.UpdateIf(_viewModel.IsHoliday, GetStyle("HolidayLabel"));
}
private Style GetStyle(string name)
{
return Resources[name] as Style;
}
}
And final thought after the final code:) If there is no simpler solution then it would be nice to be able to do it in the XAML like this (pseudocode):
<Label Text="{Binding Message}">
<Label.Styles>
<Style ShouldApply="{Binding IsError}" Style="{StaticResource ErrorMessage}" />
<Style ShouldApply="{Binding IsWarning}" Style="{StaticResource WarningMessage}" />
...
</Label.Styles>
</Label>
And then all the styles would be created applying one after another in the defined order based on the defined conditions.

Custom Button renderer and unexpected properties change

I'm currently trying to create a Button with custom color text when disabled. For this point i successfully did the job with a custom renderer.
But an unexpected side effect as came. Some button style are disapplying when passing on this specific state.
As you can see on the image above the button icon position is different and the cornerRadius property isn't applied anymore.
In the enable state with the custom button renderer i recover the cornerRadius property (actually i can resolve this problem setting each time the radius in the renderer, so isn't really a problem). The icon remain at the top and doesn't respect the ContentLayout margin property. The renderer i expect is the left button who is did with normal button component.
Here is the declaration of my control (i put the left button too to demonstrate the settings are the same:
<StackLayout Orientation="Horizontal" Margin="0,50,0,0">
<Button Text="Disponibilités de l'élève" BackgroundColor="{StaticResource Primary}" BorderColor="LightGray" BorderWidth="1" TextColor="White" FontAttributes="Bold" CornerRadius="10" ContentLayout="Top,25" WidthRequest="{Binding ButtonWidth}" HorizontalOptions="CenterAndExpand" Command="{Binding OpenMapCommand}">
<Button.ImageSource>
<FontImageSource FontFamily="FASolid" Color="White" Glyph="{x:Static fa:FontAwesomeIcons.CalendarDays}"/>
</Button.ImageSource>
</Button>
<!--<Button Text="Poser une option" BackgroundColor="{StaticResource Primary}" TextColor="White" BorderColor="LightGray" BorderWidth="1" FontAttributes="Bold" CornerRadius="10" ContentLayout="Top,25" WidthRequest="{Binding ButtonWidth}" HorizontalOptions="CenterAndExpand" Command="{Binding SubmitOptionCommand}">
<Button.ImageSource>
<FontImageSource FontFamily="FARegular" Color="White" Glyph="{x:Static fa:FontAwesomeIcons.CalendarPlus}"/>
</Button.ImageSource>
</Button>-->
<controls:DisableExtentButton Text="Poser une option" BackgroundColor="{StaticResource Primary}" DisabledBackgroundColor="LightGreen" TextColor="White" DisabledTextColor="Green" BorderColor="LightGray" BorderWidth="1" FontAttributes="Bold" CornerRadius="10" ContentLayout="Top,25" WidthRequest="{Binding ButtonWidth}" HorizontalOptions="CenterAndExpand" Command="{Binding SubmitOptionCommand}">
<Button.Triggers>
<DataTrigger TargetType="Button" Binding="{Binding OptionTaken}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="ImageSource">
<Setter.Value>
<FontImageSource FontFamily="FARegular" Color="Green" Glyph="{x:Static fa:FontAwesomeIcons.CalendarCheck}"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Button.Triggers>
<Button.ImageSource>
<FontImageSource FontFamily="FARegular" Color="White" Glyph="{x:Static fa:FontAwesomeIcons.CalendarPlus}"/>
</Button.ImageSource>
</controls:DisableExtentButton>
</StackLayout>
Here is the declaration of my custom Control:
public class DisableExtentButton : Button
{
public Color DisabledTextColor
{
get { return (Color)GetValue(DisabledTextColorProperty); }
set { SetValue(DisabledTextColorProperty, value); }
}
public Color DisabledBackgroundColor
{
get { return (Color)GetValue(DisabledBackgroundColorProperty); }
set { SetValue(DisabledBackgroundColorProperty, value); }
}
public static readonly BindableProperty DisabledTextColorProperty = BindableProperty.Create(nameof(DisabledTextColor), typeof(Color), typeof(DisableExtentButton), Color.Gray);
public static readonly BindableProperty DisabledBackgroundColorProperty = BindableProperty.Create(nameof(DisabledBackgroundColor), typeof(Color), typeof(DisableExtentButton), Color.DarkGray);
}
The android renderer:
public class DisableExtentButtonRenderer : ButtonRenderer
{
public DisableExtentButtonRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
if (Control != null)
SetColors();
base.OnElementChanged(e);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Button.IsEnabled))
SetColors();
base.OnElementPropertyChanged(sender, e);
}
private void SetColors()
{
var element = (DisableExtentButton)this.Element;
Control.SetTextColor(Element.IsEnabled ? Element.TextColor.ToAndroid() : element.DisabledTextColor.ToAndroid());
Control.SetBackgroundColor(Element.IsEnabled ? Element.BackgroundColor.ToAndroid() : element.DisabledBackgroundColor.ToAndroid());
}
}
I didn't successfully found why the ContentLayout property isn't applied correctly anymore and more for informational purpose why the corner radius is reseted when passing into OnElementPropertyChanged. If i redo the button without custom renderer it work as expected like the left button.

Visual state manager does not work on a button in a collection view

I'm trying to change the background color or text color of a button in a collection view when clicked, this is what I have so far, but it is not working. I even tried to have the button as the root but the state still does not change
<CollectionView
ItemsSource="{Binding Suburbs}"
SelectionMode="None"
VerticalScrollBarVisibility="Never"
HorizontalScrollBarVisibility="Never">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal" ItemSpacing="5" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout
VerticalOptions="Start">
<Button
Padding="15"
CornerRadius="10"
BorderColor="Black"
BorderWidth="1"
TextColor="Black"
CommandParameter="{Binding .}"
Command="{Binding Source={x:Reference browse}, Path=BindingContext.CitySelectedCommand}"
BackgroundColor="Transparent"
Text="{Binding name}"
HeightRequest="30"
VerticalOptions="Start"
HorizontalOptions="FillAndExpand">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ColorStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Property="TextColor" Value="Red"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Button>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
``
Actually VisualStateGroup does work .
However the color only changes when you're pressing on the button , the color will change to origin color after you release the button .
Refer to Visual states in Xamarin.Forms.
If you want the button stays another color after clicking on it , you need to create a property in the model and bind it to the TextColor/BackgroundColor.
Xaml
TextColor="{Binding color}"
Command="{Binding command}"
Model
public class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string name { get; set; }
public Color _color;
public Color color {
get { return _color; }
set {
_color = value;
NotifyPropertyChanged();
}
}
public ICommand command { get; set; }
bool isClick = false;
public Model()
{
color = Color.Black;
command = new Command((obj)=> {
isClick = !isClick;
color = isClick ? Color.Red : Color.Black;
});
}
}

Can I set multiple properties on a template at the same time with one parameter?

I have this template:
<?xml version="1.0" encoding="utf-8"?>
<Frame xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:local="clr-namespace:Japanese;assembly=Japanese"
       x:Class="Japanese.Templates.ButtonTemplate"
       x:Name="this" CornerRadius="5"
       BackgroundColor="{Binding FrameBackgroundColor, Source={x:Reference this}}"
       BorderColor="{Binding FrameBorderColor, Source={x:Reference this}}"
       HorizontalOptions="FillAndExpand" HasShadow="false"
       HeightRequest="35" Padding="0">
<StackLayout Orientation="Horizontal" Padding="10,5" HorizontalOptions="FillAndExpand">
     <Label Text="{Binding Text,  Source={x:Reference this}}" FontSize="16"
         TextColor="{Binding LabelTextColor, Source={x:Reference this}}"
               x:Name="label1"
               HorizontalOptions="CenterAndExpand"
               VerticalOptions="Center" HorizontalTextAlignment="Center" />
       <Frame.GestureRecognizers>
         <TapGestureRecognizer Command="{Binding TapButtonPressed, Source={x:Reference this}}" CommandParameter="{Binding Param, Source={x:Reference this}}" NumberOfTapsRequired="1" />
        </Frame.GestureRecognizers>
    </StackLayout>
</Frame>
and this backing CS:
    
public partial class ButtonTemplate : Frame
{
public event EventHandler Action;
    public ButtonTemplate()
    {
     InitializeComponent();
    }
    public ICommand TapButtonPressed
    {
     get
        {
         return new Command((object componentIdentifier) =>
            {
             this.Action?.Invoke(this, new EventArgs());
            });
         }
     }
     public static readonly BindableProperty EnabledProperty =
            BindableProperty.Create(
                nameof(Enabled),
                typeof(bool),
                typeof(ButtonTemplate),
                default(bool));
                        
     public bool Enabled { get; set; }
     public static readonly BindableProperty FrameBackgroundColorProperty =
            BindableProperty.Create(
                nameof(FrameBackgroundColor),
                typeof(Color),
                typeof(ButtonTemplate),
                default(Color));
        
     public static readonly BindableProperty FrameBorderColorProperty =
            BindableProperty.Create(
                nameof(FrameBorderColor),
                typeof(Color),
                typeof(ButtonTemplate),
                default(Color));
        
     public static readonly BindableProperty ParamProperty =
            BindableProperty.Create(
                nameof(Param),
                typeof(string),
                typeof(ButtonTemplate),
                default(string));
        
     public static readonly BindableProperty LabelTextColorProperty =
            BindableProperty.Create(
                nameof(LabelTextColor),
                typeof(Color),
                typeof(ButtonTemplate),
                default(Color));
        
     public static readonly BindableProperty TextProperty =
            BindableProperty.Create(
                nameof(Text),
                typeof(string),
                typeof(ButtonTemplate),
                default(string));
        
     public Color FrameBackgroundColor
     {
      get { return (Color)GetValue(FrameBackgroundColorProperty); }
         set { SetValue(FrameBackgroundColorProperty, value); }
     }
     public Color FrameBorderColor
     {
         get { return (Color)GetValue(FrameBorderColorProperty); }
         set { SetValue(FrameBorderColorProperty, value); }
     }
     public Color LabelTextColor
     {
         get { return (Color)GetValue(LabelTextColorProperty); }
         set { SetValue(LabelTextColorProperty, value); }
     }
     public string Param
     {
         get { return (string)GetValue(ParamProperty); }
         set { SetValue(ParamProperty, value); }
     }
     public string Text
     {
         get { return (string)GetValue(TextProperty); }
         set { SetValue(TextProperty, value); }
     }
 }
Currently I use bindings to set TextColor, BorderColor and BackgroundColor. But all I need is two states enabled and disabled.  Is there a way I can set the three binding values all at the same time to one or other color set with just one binding parameter?
Edit:
So what I need is to just have one parameter so for example:
<template:button enabled="true">
BackgroundColor will be Blue
BorderColor will be Gray
TextColor will be White
Or:
<template:button enabled="false">
BackgroundColor will be White
BorderColor will be Black
TextColor will be Gray
So basically, you are trying to create a reusable control.
The easiest thing to do is just add an Enabled property on this in the code-behind. This also allows you to set it from XAML.
Just add: public bool Enabled { get; set; }.
You could then in the setter, reference the controls in your template by a name and set the properties like that. You would have to add an x:Key attribute to each of the controls.
Seeing you already have data-binding in place, you should be able to just update the properties that you are binding to from the setter.
If you also want to be able to bind to the new Enabled property, you will have to create it as a BindableProperty (docs). Add this:
public static readonly BindableProperty EnabledProperty =
BindableProperty.Create (nameof(Enabled), typeof(bool), typeof(ButtonTemplate), null, propertyChanged: OnEnabledChanged);
public bool Enabled { get; set; }
private static void OnEnabledChanged (BindableObject bindable, object oldValue, object newValue)
{
// Set the color properties here
}
The BindableProperty has an on property changed method where you can set the properties for the colors. By implementing it like this, you can also bind to the Enabled property: <template:button enabled="{Binding IsValid}">
Edit:
What I mean by setting the properties is this. But from your new code, I see you don't have data-binding in place here. You do have named your controls, so you can just refer to them and set their properties like this:
private static void OnEnabledChanged (BindableObject bindable, object oldValue, object newValue)
{
// Referencing controls
if ((bool)newValue)
{
BorderColor = Color.Red;
label1.BackgroundColor = Color.Red;
}
else
{
BorderColor = Color.Green;
label1.BackgroundColor = Color.Green;
}
// Using bindings
if ((bool)newValue)
{
FrameBackgroundColor = Color.Red;
FrameBorderColor = Color.Red;
}
else
{
FrameBackgroundColor = Color.Green;
FrameBorderColor = Color.Green;
}
}
I see that you have named your Frame this. That could cause problems since this is a reserved keyword in .NET. You might want to change that.
Would recommend using VisualStateManager for this particular use-case.
For e.g.
<Frame xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SampleApp.ButtonView"
x:Name="this"
HorizontalOptions="FillAndExpand"
CornerRadius="5"
HasShadow="false"
HeightRequest="35" Padding="0">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Blue" />
<Setter Property="BorderColor" Value="Gray" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Focused" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="White" />
<Setter Property="BorderColor" Value="Black" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<StackLayout Orientation="Horizontal" Padding="10,5" HorizontalOptions="FillAndExpand">
<Label Text="{Binding Text, Source={x:Reference this}}"
IsEnabled="{Binding IsEnabled, Source={x:Reference this}}"
FontSize="16"
HorizontalOptions="CenterAndExpand"
VerticalOptions="Center" HorizontalTextAlignment="Center">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="TextColor" Value="White" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Focused" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="Gray" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Label>
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding TapButtonPressed,Source={x:Reference this}}"
CommandParameter="{Binding Param, Source={x:Reference this}}" NumberOfTapsRequired="1" />
</Frame.GestureRecognizers>
</StackLayout>
</Frame>
Code behind:
public partial class ButtonView : Frame
{
public ButtonView()
{
InitializeComponent();
VisualStateManager.GoToState(this, "Normal");
}
public static readonly BindableProperty TextProperty =
BindableProperty.Create(
"Text", typeof(string), typeof(ButtonView),
defaultValue: default(string));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}

Make sure an item is picked with Picker Xamarin Forms

I'm currently working on a cross-platform app build with Xamarin Forms and I've several picker. I need to be sure all the pickers have a selected item, and if not I'd like to set their background to red.
I've already tried to loop to each element of the stacklayout to pick all Pickers and check their selected items but it's not working (it seems that my layout have only 2 children and no pickers). I cant't see how to do this with behavior too.
My loop (in code behind)
public void checkChampsVides()
{
for (int i = 0; i < DiagHabitat.Children.Count(); i++)
{
DisplayAlert("e", DiagHabitat.Children.GetHashCode().ToString(), "ok");
if (DiagHabitat.Children.ElementAt(i).GetType() == typeof(Picker))
{
Picker p = DiagHabitat.Children.ElementAt(i) as Picker;
if (p.SelectedIndex == 0)
p.BackgroundColor = Color.Red;
}
}
}
Xaml
<ContentPage
Title="Diagnostic Habitat"
Padding="20"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XXX.DiagnosticHabitatPage">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key= "BoutonSauvegarde" TargetType="Button" >
<Setter Property="BackgroundColor" Value="#6AD0C6"/>
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout x:Name="DiagHabitat">
<ProgressBar Progress="1"/>
<TableView x:Name ="DiagnosticHabitat" Intent="Form" HasUnevenRows="True">
<TableRoot Title="Diagnostic habitat">
<TableSection Title="Title1">
<ViewCell>
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand" >
<Label VerticalOptions="Center" Text="text1"/>
<Picker x:Name="accesPorteEntree" HorizontalOptions="FillAndExpand" SelectedIndex="{Binding DiagHabitatAjoute.AccesPorteEntreeEPC, Mode=OneWayToSource}" >
<Picker.Items>
<x:String>Seuil haut</x:String>
<x:String>Seuil bas</x:String>
<x:String>Sans seuil</x:String>
</Picker.Items>
</Picker>
</StackLayout>
</ViewCell>
<ViewCell>
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand" >
<Label VerticalOptions="Center" Text="text2"/>
<Picker x:Name="niveauSecuAcces" HorizontalOptions="FillAndExpand" SelectedIndex="{Binding DiagHabitatAjoute.SecuAccesEPC, Mode=OneWayToSource}">
<Picker.Items>
<x:String>Bas</x:String>
<x:String>Moyen</x:String>
<x:String>Haut</x:String>
</Picker.Items>
</Picker>
</StackLayout>
</ViewCell>
<ViewCell>
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand" >
<Label VerticalOptions="Center" Text="Largeur circulation"/>
<Picker x:Name="largeurCirculation" HorizontalOptions="FillAndExpand" SelectedIndex="{Binding DiagHabitatAjoute.LargeurCircuEPC, Mode=OneWayToSource}" >
<Picker.Items>
<x:String>Inf à 75 cm</x:String>
<x:String>75 - 90 cm</x:String>
<x:String>Sup à 90 cm</x:String>
</Picker.Items>
</Picker>
</StackLayout>
</ViewCell>
...
You can use triggers, and apply them using implicit Style.
<TableView.Resources>
<ResourceDictionary>
<Style TargetType="Picker">
<Setter Property="BackgroundColor" Value="Green" />
<Style.Triggers>
<Trigger TargetType="Picker" Property="SelectedItem" Value="{x:Null}">
<Setter Property="BackgroundColor" Value="Red" />
</Trigger>
<Trigger TargetType="Picker" Property="SelectedIndex" Value="0">
<Setter Property="BackgroundColor" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</TableView.Resources>
You can recursively descend through your top most Layout container that holds the Pickers and use some switch pattern matching to determine when to recurse into the next container.
public void CheckPickers(Layout layout)
{
foreach (var child in layout.Children)
{
switch (child)
{
case Picker picker:
if (picker.SelectedIndex <= 0)
picker.BackgroundColor = Color.Red;
else
picker.BackgroundColor = Color.Green;
break;
case Layout l:
CheckPickers(l);
break;
default:
break;
}
}
}

Resources