Как изменить цвета приложения во время выполнения?
Задача: перемещая ползунки (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, то я перехватываю исключение. Приведенный выше код работает, но есть проблемы с масштабируемостью и использованием. Возникает вопрос, как по-другому реализовать динамическое изменение цвета (включая анимацию), чтобы решить проблемы использования и масштабирования?