. Net Maui/Xamarin中的动态样式

zd287kbt  于 2022-12-07  发布在  其他
关注(0)|答案(2)|浏览(214)

假设我想显示一个日历,每一天(DayViewModel)都可以有这样的属性,这些属性会影响样式:

  • 是当前月份的日期
  • 是周末
  • 可用天数

在此基础上,我想将我的DayContentView设置为如下样式:

  • 如果当前月份的日期-〉不透明度1
  • 如果不是当前月份-〉不透明度0.5
  • 如果周末-〉背景颜色为红色
  • 如果可用-〉背景颜色为绿色

此外,最后一个属性(“当天可用”)可以根据用户的操作(单击按钮“检查可用性”)进行更改。
在其他框架中,我会创建一些样式/样式类,并根据这些标志相应地分配它们。但由于StyleClass是不可绑定的,我不能。我可以吗?也许有一些变通办法。
另一个选择是为所有的组合创建一个单独的样式(如果需要的话,使用继承)。但是首先,组合的数量增加到2^n,第二个问题是,我不知道如何为一个更改名称的视图动态地更改整个样式。这可能吗?
结束我的长问题:我不想在视图模型中存储颜色、字体大小、不透明度等值,然后手动绑定所有值。

wvt8vs2t

wvt8vs2t1#

使用DataTrigger可以实现同样的效果。
结果:

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:**多个控件共享样式

以下是多个控件(在此范例中为三个TextBlock中的两个)如何共用具有Triggers的相同Style

<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>

结果如下所示:

zed5wv10

zed5wv102#

我本打算按照@ToolmakerStave的建议添加一个示例,但同时我发现了一个似乎很不错的解决方案,所以我决定将其分享给大家。
1.声明实现INotifyPropertyChanged的视图模型
1.创建XAML文件并在其中定义所有样式
1.在我的例子中,在代码背后检测绑定上下文(视图模型)的变化及其所有动态变化的属性(IsAvailable
1.检测到更改后,使用XAML文件中定义的样式创建样式,并按所需顺序应用这些样式(进行更新)
1.将最终样式指定给特定元素
所以从我的观点模型开始:

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; }
}

假设只有IsAvailable属性可以在某个用户操作后更改。
然后,我有一个XAML视图,它定义了所有需要的样式,但它们并没有直接在这个文件中使用。我将在后面的代码中使用它们。所有需要设置样式的元素都设置了x:Name属性,以便从代码中获得对它们的引用:

<?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>

现在有一个helper类,允许用另一个样式更新一个样式:

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));
    }
}

最后一部分,后面的代码:

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;
    }
}

最后一个想法在最后的代码之后:)如果没有更简单的解决方案,那么能够在XAML中这样做就很好了(伪代码):

<Label Text="{Binding Message}">
    <Label.Styles>
        <Style ShouldApply="{Binding IsError}" Style="{StaticResource ErrorMessage}" />
        <Style ShouldApply="{Binding IsWarning}" Style="{StaticResource WarningMessage}" />
        ...
    </Label.Styles>
</Label>

然后根据定义的条件,按照定义的顺序依次应用创建所有样式。

相关问题