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.
Related
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.
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!
The simplest example would be a page with two checkboxes and a button. The button has two datatriggers, one bound to each checkbox's "IsChecked" property, and the setters both point to the "IsEnabled" property of the button. In my searching I found recommendations to set the "IsEnabled" property to false, and write a DataTrigger for each of your "or" conditions to set it to true. This is because the MultiTrigger uses the "AND" operator.
The binding is working, just not with the functionality I'm looking for. If CheckBox1 is checked then the button is enabled. If CheckBox2 is then checked, the button stays enabled. If CheckBox1 is then unchecked, the button is disabled. What are the approaches to keep the button enabled in this scenario since CheckBox2 is still checked?
It seems like this should be doable in XAML as it is only handling boolean properties on controls existing in the XAML and setting boolean values, but it appears we get the DataTrigger (if and only if) and the MultiTrigger (All conditions, so "AND"). Is there a way to write a trigger for "ANY of these conditions"?
Quick example XAML:
<CheckBox x:Name="CheckBox1" />
<CheckBox x:Name="CheckBox2" />
<Button Text="Submit" IsEnabled="False">
<Button.Triggers>
<DataTrigger TargetType="Button" Binding="{Binding Source={x:Reference CheckBox1}, Path=IsChecked}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
<DataTrigger TargetType="Button" Binding="{Binding Source={x:Reference CheckBox2}, Path=IsChecked}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Button.Triggers>
</Button>
The "ANY" condition might be met with StateTriggers and CompareStateTrigger. See also VisualStateManager Target for the use of TargetName.
Assuming the layout is StackLayout:
<StackLayout>
<CheckBox x:Name="CheckBox1" />
<CheckBox x:Name="CheckBox2" />
<Button x:Name="button" Text="Submit" IsEnabled="False"/>
</StackLayout>
In Resources:
<Style TargetType="StackLayout">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup>
<VisualState x:Name="Checked">
<VisualState.StateTriggers>
<CompareStateTrigger Property="{Binding Source={x:Reference CheckBox1}, Path=IsChecked}"
Value="True" />
<CompareStateTrigger Property="{Binding Source={x:Reference CheckBox2}, Path=IsChecked}"
Value="True" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter TargetName="button"
Property="Button.IsEnabled"
Value="True" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Unchecked">
<VisualState.StateTriggers>
<CompareStateTrigger Property="{Binding Source={x:Reference CheckBox1}, Path=IsChecked}"
Value="False" />
<CompareStateTrigger Property="{Binding Source={x:Reference CheckBox2}, Path=IsChecked}"
Value="False" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter TargetName="button"
Property="Button.IsEnabled"
Value="False" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
Note: In Xamarin.Forms 4.5.0.356 StateTriggers and CompareStateTrigger is experimental so the following must be added in App.xaml.cs (in the Xamarin.Forms project):
public App()
{
Device.SetFlags(new string[] { "StateTriggers_Experimental" });
InitializeComponent();
...
You could use MultiTrigger .
MultiTrigger behaves similarly to Trigger or DataTrigger but it has multiple conditions. All the conditions must be true for a Setters to fire
<Button Text="Submit" IsEnabled="True">
<Button.Triggers>
<MultiTrigger TargetType="Button">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding Source={x:Reference CheckBox1}, Path=IsChecked}" Value="False" />
<BindingCondition Binding="{Binding Source={x:Reference CheckBox2}, Path=IsChecked}" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="IsEnabled" Value="False" />
</MultiTrigger>
</Button.Triggers>
</Button>
For now I've moved on by setting VM properties for IsChecked on the checkboxes, and setting Command.CanExecute and calling Command.CanExecuteChanged() for the button's command.
I'm happy to continue the discussion though if anyone has any further ideas.
I'm trying to style Navigation page on xamarin forms app with Mvvmcross.
before "adding" mmvcross I've defined style in App.xaml as follows.
<Style TargetType="NavigationPage">
<Setter Property="BarBackgroundColor" Value="Black" />
<Setter Property="BarTextColor" Value="White" />
</Style>
and it worked. I got background color and text color as i defined. Then we "moved" to Mvvmcross - we added all needed backend code, changed all pages from ContentPage to MvxContentPage etc and... background color of navigation page stopped working on Android (I haven't tried on iOS). If i change BarTextColor in App.xaml changes apply, color is changed. if i change BackgroundColor it also works - all application background color is changed. but no matter what value i apply to BarBackgroundColor it is still white.
my App.xaml is as follows:
<?xml version="1.0" encoding="utf-8" ?>
<core:MvxFormsApplication xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:viewModels="clr-namespace:MvvmCross.ViewModels;assembly=MvvmCross"
xmlns:core="clr-namespace:MvvmCross.Forms.Core;assembly=MvvmCross.Forms"
xmlns:attributes="clr-namespace:MvvmCross.Forms.Presenters.Attributes;assembly=MvvmCross.Forms"
x:Class="ShowMeTheLogs.Core.FormsApp">
<core:MvxFormsApplication.Resources>
<!--Global Styles-->
<Color x:Key="ColorPrimary">#98C340</Color>
<Color x:Key="ColorError">#CF1212</Color>
<Color x:Key="ColorWarning">#E4AD17</Color>
<Color x:Key="ColorInfo">#1283CF</Color>
<Color x:Key="ColorDebug">#989898</Color>
<Style TargetType="NavigationPage">
<Setter Property="BarBackgroundColor" Value="{StaticResource ColorPrimary}" />
<Setter Property="BarTextColor" Value="White" />
</Style>
<Style x:Key="SubmitButton" TargetType="Button">
<Setter Property="BackgroundColor" Value="{StaticResource ColorPrimary}" />
<Setter Property="TextColor" Value="White" />
</Style>
<Style x:Key="circleButton" TargetType="Button">
<Setter Property="BackgroundColor" Value="{StaticResource ColorPrimary}" />
<Setter Property="BorderRadius" Value="50" />
<Setter Property="WidthRequest" Value="100" />
<Setter Property="HeightRequest" Value="100" />
<Setter Property="TextColor" Value="White" />
<Setter Property="FontSize" Value="50" />
</Style>
<Style x:Key="smallSquareButton" TargetType="Button">
<Setter Property="BackgroundColor" Value="{StaticResource ColorPrimary}" />
<Setter Property="WidthRequest" Value="100" />
<Setter Property="HeightRequest" Value="100" />
<Setter Property="TextColor" Value="White" />
<Setter Property="FontSize" Value="Micro" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="VerticalOptions" Value="Center" />
</Style>
</core:MvxFormsApplication.Resources>
</core:MvxFormsApplication>
my App.xalm.cs is the simpliest as it can be:
public partial class FormsApp: MvxFormsApplication
{
public App()
{
InitializeComponent();
}
}
and the simpliest view:
<?xml version="1.0" encoding="utf-8" ?>
<views:MvxContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
x:TypeArguments="viewModels:WebAppListViewModel"
x:Class="ShowMeTheLogs.Core.Views.WebAppListPage"
xmlns:bindings="clr-namespace:MvvmCross.Forms.Bindings;assembly=MvvmCross.Forms"
xmlns:viewModels="clr-namespace:ShowMeTheLogs.Core.ViewModels;assembly=ShowMeTheLogs.Core"
Title="Sample title"
NavigationPage.HasBackButton="False">
<ContentPage.ToolbarItems>
<ToolbarItem x:Name="AddNewApp" Order="Secondary" Text="Dodaj aplikacjÄ™" Priority="0" Command="{bindings:MvxBind NewWebAppCommand}"/>
</ContentPage.ToolbarItems>
<RelativeLayout Padding="0" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<Non relevant="Code" />
</RelativeLayout>
</views:MvxContentPage>
I have literally no idea why it is not working. the only change in androud code was changing class RootActivity: CompatActivity to class RootActivity: MvxFormsAppCompatActivity
have enyone fought this problem before?
packages were obtained from NuGet
- MvvmCross (and MvvmCross.Forms) ver 6.2.2
- Xamarin ver 28.0.0
- Xamarin.Forms ver 3.4.0
In your MainActivity OnCreate method, make sure the two lines setting the Toolbar resource are before the base.OnCreate() call:
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
}
https://github.com/MvvmCross/MvvmCross/issues/2301
I am attempting to build a scaled buttons response for a series of questions using Xamarin Form v2.3.2.127. I am implementing it as ListView and have a number of buttons representing possible scaled response.
We are experiencing an issue where iOS platform does not set BackgroundColor property on a button inside the ListView but code does work on the Android platform.
Inside the XAML Xamarin Forms here is the definition of the button itself (i omitted ListView cell definition for brevity):
<ListView x:Name="Questions"
AutomationId="ListViewQuestions"
SeparatorVisibility="None"
ItemsSource="{Binding Questions}"
ListView.HasUnevenRows="True"
BackgroundColor="Transparent" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" VerticalOptions="FillAndExpand">
<StackLayout Orientation="Horizontal">
<Label Text="{Binding KeyAndQuestion}" />
</StackLayout>
<StackLayout Orientation="Horizontal"
HorizontalOptions="Center">
<controls:ScaleButton
Text="0"
AutomationId="ButtonWeightedZero"
HeightRequest="37"
WidthRequest="37"
TextColor="White"
FontAttributes="Bold"
BorderRadius="5"
BackgroundColor="#919191"
Command="{Binding WeightedQuestionAnswered}"
CommandParameter="0">
<controls:ScaleButton.Triggers>
<DataTrigger TargetType="controls:ScaleButton"
Binding="{Binding SelectedWeight}"
Value="{x:Null}">
<Setter Property="BackgroundColor"
Value="#919191"/>
</DataTrigger>
<DataTrigger TargetType="controls:ScaleButton"
Binding="{Binding SelectedWeight}"
Value="0">
<Setter Property="BackgroundColor"
Value="#007AC3"/>
</DataTrigger>
</controls:ScaleButton.Triggers>
</controls:ScaleButton>
.... // other scale buttons
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Custom ScaledButton definition is straight forward as well.
public class ScaleButton : Button
{
}
I figured before I follow the Xamarin Bugzilla route, i would see if anyone experienced this.
Update
The the bound property is an integer and thus DataTrigger with Binding where x:Nil seems to be the culprit. Thus, I ended up refactoring the Scale button as follows.
<controls:ScaleButton
Text="0"
AutomationId="ButtonWeightedZero"
Style="{StaticResource ScaleButton}"
Command="{Binding WeightedQuestionAnswered}"
CommandParameter="0">
<controls:ScaleButton.Triggers>
<DataTrigger TargetType="controls:ScaleButton"
Binding="{Binding SelectedWeight}"
Value="0">
<Setter Property="Style"
Value="{StaticResource ScaleButtonSelected}"/>
</DataTrigger>
</controls:ScaleButton.Triggers>
</controls:ScaleButton>
Where Global resoure dictionary for the styles was set to this
<Style TargetType="{x:Type Button}" x:Key="ScaleButton" ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor" Value="#656665" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="TextColor" Value="White" />
<Setter Property="VerticalOptions" Value="Center" />
<Setter Property="WidthRequest" Value="37" />
<Setter Property="HeightRequest" Value="37" />
<Setter Property="BorderRadius" Value="5" />
</Style>
<Style TargetType="{x:Type Button}" x:Key="ScaleButtonSelected" ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor" Value="#007AC3" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="TextColor" Value="White" />
<Setter Property="VerticalOptions" Value="Center" />
<Setter Property="WidthRequest" Value="37" />
<Setter Property="HeightRequest" Value="37" />
<Setter Property="BorderRadius" Value="5" />
</Style>
This works on both platforms but the funny part is that trying to use the Setter on just the BackgroundColor only works on Android platform.
Here is what I ended up doing which seems to work a lot better.
I built a custom Weight value to Style converter and bound to Style property.
public class ScaleToStyleConverter : IValueConverter
{
static ScaleToStyleConverter Instance = new ScaleToStyleConverter();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int paramValue;
if (parameter == null)
throw new ArgumentNullException("parameter");
if (!(parameter is string))
throw new ArgumentException("the parameter is not a string", "parameter");
if (!int.TryParse((string)parameter, out paramValue))
{
throw new ArgumentException("The parameter cannot be converted to an integer.", "parameter");
}
return paramValue != (int)value ?
Application.Current.Resources["ScaleButton"] :
Application.Current.Resources["ScaleButtonSelected"];
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
So the Xaml definition for the ScaleButton is as follows.
<controls:ScaleButton
Text="0"
AutomationId="ButtonWeightedZero"
Style="{Binding SelectedWeight, Converter= {x:Static converters:ScaleToStyleConverter.Instance }, ConverterParameter=0 }"
Command="{Binding WeightedQuestionAnswered}"
CommandParameter="0">
</controls:ScaleButton>
That seems to work quite well.