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.