Dynamic styling in .Net Maui/Xamarin - 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.

Related

.NET Maui styling with DataTrigger

Overview: I have two properties:
IsPast
IsCurrentMonth
If it's past or not current month I want to display label in red.
The following code is a shrink version of default Maui application. If you run it you get red label (expected). After one click it stays red (expected) but following clicks switch the red color on and off. Is it a bug or I don't understand the way DataTrigger/Style/whatever works:
View model:
public class ViewModel : INotifyPropertyChanged
{
private bool _isPast;
public bool IsPast
{
get => _isPast;
set
{
_isPast = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsPast)));
}
}
private bool _isCurrentMonth;
public bool IsCurrentMonth
{
get => _isCurrentMonth;
set
{
_isCurrentMonth = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsCurrentMonth)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:BugMaui"
x:Class="BugMaui.MainPage"
x:DataType="local:ViewModel">
<VerticalStackLayout>
<HorizontalStackLayout>
<Label Text="IsPast: " />
<Label Text="{Binding IsPast}" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="IsCurrentMonth: " />
<Label Text="{Binding IsCurrentMonth}" />
</HorizontalStackLayout>
<Label
Text="Hello, World!"
FontSize="32"
HorizontalOptions="Center">
<Label.Style>
<Style TargetType="Label">
<Style.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding IsPast}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger TargetType="Label" Binding="{Binding IsCurrentMonth}" Value="False">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
<Button
Text="Click me"
Clicked="OnCounterClicked"/>
</VerticalStackLayout>
</ContentPage>
And the code behind:
public partial class MainPage : ContentPage
{
ViewModel viewModel;
public MainPage()
{
InitializeComponent();
viewModel = new ViewModel { IsCurrentMonth = true, IsPast = true };
BindingContext = viewModel;
}
private void OnCounterClicked(object sender, EventArgs e)
{
viewModel.IsPast = !viewModel.IsPast;
viewModel.IsCurrentMonth = !viewModel.IsCurrentMonth;
}
}
My answer for the question is YES, it is a bug if .NET MAUI developers hadn't decided to change the way how DataTrigger/Style worked in XAMARIN (as well as WPF).
According to .NET MAUI Triggers documentation, DataTrigger should "apply property values, or performs actions, when the bound data meets a specified condition". If condition is not met, no action should be done.
Thus the DataTrigger in your part of code is correct to satisfy your requirement to set red background if viewModel.IsCurrentMonth is False OR viewModel.IsPast is True:
<DataTrigger TargetType="Label" Binding="{Binding IsCurrentMonth}" Value="False">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger TargetType="Label" Binding="{Binding IsPast}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
This use case of DataTrigger worked well in both XAMARIN and WPF. The background remained red by repetitive clicking as it was expected.
There is no explanation, why in .NET MAUI DataTrigger works differently. The red background is set only based on the value of the "lastly set" property bound to the DataTrigger. So if the condition is not met the previously fullfilled conditions are forgotten and the default (transparent) background is set.
In your example it means that the background is set based on the viewModel.IsCurrentMonth property:
private void OnCounterClicked(object sender, EventArgs e)
{
viewModel.IsPast = !viewModel.IsPast;
viewModel.IsCurrentMonth = !viewModel.IsCurrentMonth;
}
If you change the order of setting the viewModel properties in the OnCounterClicked event handler, the background is set based on the value of viewModel.IsPast property.
private void OnCounterClicked(object sender, EventArgs e)
{
viewModel.IsCurrentMonth = !viewModel.IsCurrentMonth;
viewModel.IsPast = !viewModel.IsPast;
}
Multitrigger proposed in this answer wouldn't satisfy your requirement, as it is mentioned in Liyun's Zhang commment.
The following workaround for this bug proposed by Liyung Zhang is working, but it still does not answer the original question if described DataTrigger behavior is a bug or not:
<Label.Style>
<Style TargetType="Label">
<Setter Property="Background" Value="Red" />
<Style.Triggers>
<MultiTrigger TargetType="Label">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding IsPast}" Value="False" />
<BindingCondition Binding="{Binding IsCurrentMonth}" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Background" Value="Transparent" />
</MultiTrigger>
</Style.Triggers>
</Style>
</Label.Style>
Conclusion:
Based on my experience the above mentioned new .NET MAUI DataTrigger behaviour can be clasified as a bug and it is unfortunately source of other problems in development not only in DataTrigger styling but also in MultiTrigger usage where I had to fight with similar unpredictable not expected and really weird behavior.
You can express your condition (IsPast is true && IsCurrentMonth is false) better with a MultiTrigger like this:
<Label
Text="Hello, World!"
FontSize="32"
HorizontalOptions="Center">
<Label.Style>
<Style TargetType="Label">
<Style.Triggers>
<MultiTrigger TargetType="Label">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding IsPast}" Value="True" />
<BindingCondition Binding="{Binding IsCurrentMonth}" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="Background" Value="Red" />
</MultiTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
Check the .NET MAUI MultiTrigger documentation for more info.

Trigger BorderBrush based on boolean value in ItemSource

I have a ItemsControl grid with a set of images. ItemSource is bound to a ObservableCollection that look like this:
public class Card
{
(...)
public string Image_url_small { get; set; }
public bool IsStarter = false;
(...)
}
On double click I have an event that changes IsStarter to true/false for every item with the same ID value - it works fine.
Now from the UI side I would like to highlight each of the items with IsStarter = true using a red border. Something like this:
So I wrote it like this:
<ItemsControl Name="DeckBox" ItemsSource="{Binding Lst}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border BorderThickness="2">
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsStarter}" Value="true">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Image Source="{Binding Image_url_small}"
MouseRightButtonUp="Image_MouseRightButtonUp"
MouseLeftButtonDown="Image_MouseLeftButtonDown"
Stretch="Fill"/>
</Border>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="10"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
My plan was to set a transparent border for all IsStarter = false and a red one for all with value = true but unfortunately it does not work as intended and they stay transparent all the time.
Any idea how to fix that? :)
Thx for help!

Xamarin Community Toolkit TabView switching tabs when tapping on entry

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>

Remove borders on custom renderer SearchBar for Xamarin Forms UWP

I am currently working with a UWP project in Xamarin Forms.
When i use the default SearchBar, it comes with a border that i wish to remove as well as add a rounded background.
I have setup the renderer and some code, but the border is still intact.
public class SearchBar_UWP : SearchBarRenderer
{
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
Control.Background = null;
Control.BorderBrush = null;
}
}
What am I missing in my UWP custom renderer code in order to remove the border and add a rounded background?
Remove border
You could copy the default style and modify BorderThickness property (in test i found we need to change the property on TextBox inside AutoSuggestBox ), then place the new style into Application.Resources , at last apply the style in custom renderer .
custom style in App
<Application.Resources>
<Style TargetType="AutoSuggestBox" x:Key="myStyle">
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="TextBoxStyle" Value="{StaticResource AutoSuggestBoxTextBoxStyle}" />
<Setter Property="UseSystemFocusVisuals" Value="{ThemeResource IsApplicationFocusVisualKindReveal}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="AutoSuggestBox">
<Grid x:Name="LayoutRoot">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Orientation">
<VisualState x:Name="Landscape" />
<VisualState x:Name="Portrait" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox x:Name="TextBox"
Style="{TemplateBinding TextBoxStyle}"
PlaceholderText="{TemplateBinding PlaceholderText}"
Header="{TemplateBinding Header}"
Width="{TemplateBinding Width}"
BorderThickness="0"
ScrollViewer.BringIntoViewOnFocusChange="False"
Canvas.ZIndex="0"
Margin="0"
DesiredCandidateWindowAlignment="BottomEdge"
UseSystemFocusVisuals="{TemplateBinding UseSystemFocusVisuals}" />
<Popup x:Name="SuggestionsPopup">
<Border x:Name="SuggestionsContainer">
<ListView x:Name="SuggestionsList"
Background="{ThemeResource AutoSuggestBoxSuggestionsListBackground}"
BorderThickness="{ThemeResource AutoSuggestListBorderThemeThickness}"
BorderBrush="{ThemeResource AutoSuggestBoxSuggestionsListBorderBrush}"
DisplayMemberPath="{TemplateBinding DisplayMemberPath}"
IsItemClickEnabled="True"
ItemTemplate="{TemplateBinding ItemTemplate}"
ItemTemplateSelector="{TemplateBinding ItemTemplateSelector}"
ItemContainerStyle="{TemplateBinding ItemContainerStyle}"
MaxHeight="{ThemeResource AutoSuggestListMaxHeight}"
Margin="{ThemeResource AutoSuggestListMargin}"
Padding="{ThemeResource AutoSuggestListPadding}" />
</Border>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
custom renderer
[assembly: ExportRenderer(typeof(Xamarin.Forms.SearchBar), typeof(MyRenderer))]
namespace App1.UWP
{
class MyRenderer : SearchBarRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
base.OnElementChanged(e);
if(Control != null)
{
Control.Style = App.Current.Resources["myStyle"] as Windows.UI.Xaml.Style;
}
}
}
}
Add Rounded background
Warp SearchBar into Frame , and set CornerRadius on Frame .
<Frame HeightRequest="100" WidthRequest="100" VerticalOptions="Center" HorizontalOptions="Center" BorderColor="Red" CornerRadius="50">
<SearchBar />
</Frame>

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