Как изменить цвета приложения во время выполнения?

Задача: перемещая ползунки (3 ползунка для Rgb) менять цвета элементов управления и их цветную анимацию. И все было бы очень просто, если бы не анимация и не заморозки. Я решил проблему, но подход сложный.

Структура проекта:

Структура проекта

Ради простоты забудьте о MVVM. Итак, приложение реализует класс AppColors для хранения цветов и кистей:

      class AppColors : DependencyObject
{
    public SolidColorBrush ControlNormalBackgroundBrush
    {
        get { return (SolidColorBrush)GetValue(ControlNormalBackgroundBrushProperty); }
        set { SetValue(ControlNormalBackgroundBrushProperty, value); }
    }
    public static readonly DependencyProperty ControlNormalBackgroundBrushProperty = DependencyProperty.Register(
            nameof(ControlNormalBackgroundBrush), typeof(SolidColorBrush), typeof(AppColors),
            new PropertyMetadata(new SolidColorBrush(Color.FromArgb(0xFF, 0x00, 0xAE, 0xFF))));

    public SolidColorBrush ControlMouseOverBackgroundBrush
    {
        get { return (SolidColorBrush)GetValue(ControlMouseOverBackgroundBrushProperty); }
        set { SetValue(ControlMouseOverBackgroundBrushProperty, value); }
    }
    public static readonly DependencyProperty ControlMouseOverBackgroundBrushProperty = DependencyProperty.Register(
            nameof(ControlMouseOverBackgroundBrush), typeof(SolidColorBrush), typeof(AppColors),
            new PropertyMetadata(new SolidColorBrush(Color.FromArgb(0xFF, 0xAE, 0xAE, 0xFF))));

    public Color ControlNormalBackgroundColor
    {
        get { return (Color)GetValue(ControlNormalBackgroundColorProperty); }
        set { SetValue(ControlNormalBackgroundColorProperty, value); }
    }
    public static readonly DependencyProperty ControlNormalBackgroundColorProperty =
        DependencyProperty.Register(nameof(ControlNormalBackgroundColor), typeof(Color), typeof(AppColors), new PropertyMetadata(default(Color)));

    public Color ControlMouseOverBackgroundColor
    {
        get { return (Color)GetValue(ControlMouseOverBackgroundColorProperty); }
        set { SetValue(ControlMouseOverBackgroundColorProperty, value); }
    }
    public static readonly DependencyProperty ControlMouseOverBackgroundColorProperty =
        DependencyProperty.Register(nameof(ControlMouseOverBackgroundColor), typeof(Color), typeof(AppColors), new PropertyMetadata(default(Color)));

    public static AppColors Instance { get; private set; }
    static AppColors() => Instance = new AppColors();
}

Для модели представления MainWindowViewModel реализован базовый класс ViewModelBase :

      internal abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string PropetryName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropetryName));
    }

    protected virtual bool Set<T>(ref T field, T value, [CallerMemberName] string PropetryName = null)
    {
        if (Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(PropetryName);
        return true;
    }
}

Сама модель представления:

      class MainWindowViewModel : ViewModelBase
{
    private byte _RedNormal;
    public byte RedNormal { get => _RedNormal; set { if (Set(ref _RedNormal, value)) SetAppColorNormal(); } }

    private byte _GreenNormal;
    public byte GreenNormal { get => _GreenNormal; set { if (Set(ref _GreenNormal, value)) SetAppColorNormal(); } }

    private byte _BlueNormal;
    public byte BlueNormal { get => _BlueNormal; set { if (Set(ref _BlueNormal, value)) SetAppColorNormal(); } }


    private byte _RedMouseOver;
    public byte RedMouseOver { get => _RedMouseOver; set { if (Set(ref _RedMouseOver, value)) SetAppColorMouseOver(); } }

    private byte _GreenMouseOver;
    public byte GreenMouseOver { get => _GreenMouseOver; set { if (Set(ref _GreenMouseOver, value)) SetAppColorMouseOver(); } }

    private byte _BlueMouseOver;
    public byte BlueMouseOver { get => _BlueMouseOver; set { if (Set(ref _BlueMouseOver, value)) SetAppColorMouseOver(); } }

    private void SetAppColorNormal()
    {
        AppColors.Instance.ControlNormalBackgroundBrush = new SolidColorBrush(Color.FromArgb(0xFF, _RedNormal, _GreenNormal, _BlueNormal));
        AppColors.Instance.ControlNormalBackgroundColor = Color.FromArgb(0xFF, _RedNormal, _GreenNormal, _BlueNormal);
    }
    private void SetAppColorMouseOver()
    {
        AppColors.Instance.ControlMouseOverBackgroundBrush = new SolidColorBrush(Color.FromArgb(0xFF, _RedMouseOver, _GreenMouseOver, _BlueMouseOver));
        AppColors.Instance.ControlMouseOverBackgroundColor = Color.FromArgb(0xFF, _RedMouseOver, _GreenMouseOver, _BlueMouseOver);
    }

    public MainWindowViewModel()
    {
        _RedMouseOver = AppColors.Instance.ControlMouseOverBackgroundBrush.Color.R;
        _GreenMouseOver = AppColors.Instance.ControlMouseOverBackgroundBrush.Color.G;
        _BlueMouseOver = AppColors.Instance.ControlMouseOverBackgroundBrush.Color.B;
        AppColors.Instance.ControlMouseOverBackgroundColor = AppColors.Instance.ControlMouseOverBackgroundBrush.Color;

        _RedNormal = AppColors.Instance.ControlNormalBackgroundBrush.Color.R;
        _GreenNormal = AppColors.Instance.ControlNormalBackgroundBrush.Color.G;
        _BlueNormal = AppColors.Instance.ControlNormalBackgroundBrush.Color.B;
        AppColors.Instance.ControlNormalBackgroundColor = AppColors.Instance.ControlNormalBackgroundBrush.Color;
    }
}

Вид (окно):

введите описание изображения здесь

      <Window x:Class="WpfCore.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfCore"
    mc:Ignorable="d"
    Title="MainCoreTest" Height="300" Width="300" WindowStartupLocation="CenterScreen">
<Window.DataContext>
    <local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>

    <Grid Grid.Column="0">
        <StackPanel>
            <TextBlock HorizontalAlignment="Center" Text="{Binding Source={x:Static local:AppColors.Instance}, Path=ControlNormalBackgroundBrush}"/>
            <TextBlock HorizontalAlignment="Center" Text="{Binding Source={x:Static local:AppColors.Instance}, Path=ControlNormalBackgroundColor}"/>
            <Slider Maximum="255" Minimum="0" Margin="10" SmallChange="1" Width="110" Value="{Binding RedNormal}"/>
            <Slider Maximum="255" Minimum="0" Margin="10" SmallChange="1" Width="110" Value="{Binding GreenNormal}"/>
            <Slider Maximum="255" Minimum="0" Margin="10" SmallChange="1" Width="110" Value="{Binding BlueNormal}"/>
            <local:CastomButton Width="50" Height="50"/>
        </StackPanel>
    </Grid>

    <Grid Grid.Column="1">
        <StackPanel>
            <TextBlock HorizontalAlignment="Center" Text="{Binding Source={x:Static local:AppColors.Instance}, Path=ControlMouseOverBackgroundBrush}"/>
            <TextBlock HorizontalAlignment="Center" Text="{Binding Source={x:Static local:AppColors.Instance}, Path=ControlMouseOverBackgroundColor}"/>
            <Slider Maximum="255" Minimum="0" Margin="10" SmallChange="1" Width="110" Value="{Binding RedMouseOver}"/>
            <Slider Maximum="255" Minimum="0" Margin="10" SmallChange="1" Width="110" Value="{Binding GreenMouseOver}"/>
            <Slider Maximum="255" Minimum="0" Margin="10" SmallChange="1" Width="110" Value="{Binding BlueMouseOver}"/>
            <local:CastomButton Width="50" Height="50"/>
        </StackPanel>
    </Grid>
</Grid>

Две группы ползунков предназначены для изменения цветов в классе AppColors с помощью статической ссылки через модель представления MainWindowViewModel . Два синих квадрата - это настраиваемые элементы управления CastomButton . Для них цвета изменятся.

CastomButton :

      public partial class CastomButton : UserControl
{
    public CastomButton()
    {
        InitializeComponent();
    }

    public SolidColorBrush BrushTo
    {
        get { return (SolidColorBrush)GetValue(BrushToProperty); }
        set { SetValue(BrushToProperty, value); }
    }

    public static readonly DependencyProperty BrushToProperty =
        DependencyProperty.Register(nameof(BrushTo), typeof(SolidColorBrush), typeof(CastomButton), 
            new PropertyMetadata(AppColors.Instance.ControlNormalBackgroundBrush));

    protected override void OnMouseEnter(MouseEventArgs e)
    {
        base.OnMouseEnter(e);
        if(BrushTo.Color!= AppColors.Instance.ControlNormalBackgroundBrush.Color)
            BrushTo = AppColors.Instance.ControlNormalBackgroundBrush.CloneCurrentValue();
        BrushTo = BrushTo.CloneCurrentValue();
        BrushTo.BeginAnimation(SolidColorBrush.ColorProperty, new ColorAnimation()
        {
            To = AppColors.Instance.ControlMouseOverBackgroundColor,
            Duration = TimeSpan.FromSeconds(1)
        });
    }

    protected override void OnMouseLeave(MouseEventArgs e)
    {
        base.OnMouseEnter(e);
        BrushTo = BrushTo.CloneCurrentValue();
        BrushTo.BeginAnimation(SolidColorBrush.ColorProperty, new ColorAnimation()
        {
            To = AppColors.Instance.ControlNormalBackgroundColor,
            Duration = TimeSpan.FromSeconds(1)
        });
    }
}

xaml:

      <UserControl x:Class="WpfCore.CastomButton"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:WpfCore"
         mc:Ignorable="d" 
         d:DesignHeight="50" d:DesignWidth="50">
<UserControl.DataContext>
    <local:AppColors/>
</UserControl.DataContext>
<UserControl.Resources>
    <local:ConverterBrush x:Key ="ConverterBrush"/>
    <ControlTemplate x:Key="ButtonImageTemplate" TargetType="Button">
        <Grid Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
            <Rectangle x:Name="Back" SnapsToDevicePixels="True" Fill="{TemplateBinding Background}"/>
        </Grid>
    </ControlTemplate>

    <Style x:Key="ButtonImageStyle" TargetType="Button">
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="Background">
            <Setter.Value>
                <MultiBinding Converter="{StaticResource ConverterBrush}" ConverterParameter="{x:Static local:AppColors.Instance}">
                    <Binding Path="BrushTo" RelativeSource="{RelativeSource AncestorType={x:Type local:CastomButton}}"/>
                    <Binding Path="ControlNormalBackgroundBrush" Source="{x:Static local:AppColors.Instance}"/>
                </MultiBinding>
            </Setter.Value>
        </Setter>
        <Setter Property="Template" Value="{StaticResource ButtonImageTemplate}"/>
    </Style>
</UserControl.Resources>
<Grid>
    <Button Width="50" Height="50" Style="{StaticResource ButtonImageStyle}"/>
</Grid>

Цвета приложения можно изменить с помощью ползунков в главном окне и во время анимации, тогда свойство Background элемента управления CastomButton привязывается к двум источникам данных посредством множественной привязки. Для конвертации используйте конвертер ConverterBrush :

      internal class ConverterBrush : IMultiValueConverter
{
    public object Convert(object[] values, Type t, object p, CultureInfo c)
    {
        SolidColorBrush color0 = (SolidColorBrush)values[0];
        SolidColorBrush color1 = (SolidColorBrush)values[1];
        AppColors ap = (AppColors)p;

        if (color0.Color != color1.Color)
            if (ap.ControlNormalBackgroundColor != ap.ControlNormalBackgroundBrush.Color)
                return color1.CloneCurrentValue();
            else
                return color0.CloneCurrentValue();
        else
            return color0;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Изменяя цвет в классе AppColors , преобразователь возвращает кисть из AppColors , а если он изменяется во время анимации, преобразователь возвращает копию свойства BrushTo .

Я попытался реализовать это поведение в разметке xaml, но привязать анимацию как нестатический ресурс не получилось. Если я привязываю цвет как статический ресурс, он анимируется, но не реагирует на изменения, если я привязываю его как x.Static, то я перехватываю исключение. Приведенный выше код работает, но есть проблемы с масштабируемостью и использованием. Возникает вопрос, как по-другому реализовать динамическое изменение цвета (включая анимацию), чтобы решить проблемы использования и масштабирования?

0 ответов

Другие вопросы по тегам