如何更改本机 WPF 控件主题的颜色?
How do I Change colors of the native WPF control theme?
如何在 Windows10 下更改本机 WPF 控件主题使用的基础颜色?我知道有像 MahApps.Metro 和 MUI 这样的库,但我想做的就是让我的应用程序中的元素以一致的颜色绘制(MenuItem 和 Toolbar,我在看你和你的不那么和谐颜色)。我也想提供各种颜色主题。
我该怎么做?
我不得不承认我只是不明白我的要求是否可行。一些调查表明默认的 WPF 主题使用静态资源来处理按钮背景等内容:
<Setter Property="Background" Value="{StaticResource Button.Static.Background}"/>
如果那个资源是静态的,我想我不能改变它?简单地复制所有原始 WPF 模板并以某种方式用动态资源替换静态资源是否有意义?
要对所有控件和颜色“正确”执行此操作比您想象的要复杂得多。
有些画笔使用 windows 主题颜色,有些使用硬编码值。
您可以over-ride windows 主题颜色。
例如,在 app.xaml 中合并的资源字典中,您可以对所有颜色和画笔设置自己的首选项。
这是一个:
<SolidColorBrush Color="LimeGreen" x:Key="{x:Static SystemColors.HighlightBrushKey}"/>
https://docs.microsoft.com/en-us/dotnet/api/system.windows.systemcolors?view=netcore-3.1
你会发现这只会改变一些事情。
您必须 re-template 控件来替换硬编码值。
这是什么意思?
看看单选按钮模板:
在那里你会看到像这样的东西:
<Ellipse x:Name="Border"
StrokeThickness="1">
<Ellipse.Stroke>
<LinearGradientBrush EndPoint="0.5,1"
StartPoint="0.5,0">
<GradientStop Color="{DynamicResource BorderLightColor}"
Offset="0" />
<GradientStop Color="{DynamicResource BorderDarkColor}"
Offset="1" />
</LinearGradientBrush>
</Ellipse.Stroke>
<Ellipse.Fill>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource ControlLightColor}" />
<GradientStop Color="{DynamicResource ControlMediumColor}"
Offset="1.0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
这些资源在主模板下方的一长串大列表中定义。你想要替换所有这些。
无论您想让您的控件全部使用 windows 主题还是您自己的主题,如果您从头开始,您将有很多工作要做。
您可能想看看可用的各种 pre-rolled 主题。 Material 设计很受欢迎,因为很多人都熟悉 android。
我最近为我正在开发的应用程序创建了一些自定义主题。首先要注意的是 WPF 中的控件是 class SolidColorBrush。这些可以通过在不同构造函数中采用 RGBA 的各种方法构造。
这是一个很大的努力,所以如果它太多了,至少跳转到我在 App.xaml 中的样式 xaml 示例,并注意您可以绑定到 [=] 中的 ViewModel 中的变量79=]s 从而生成 StaticResource return 动态颜色值。这就是绑定的美妙之处。您仍然通过 {StaticResource} 分配样式,但它提供的值由代码中的绑定变量决定。
我创建了三个 classes 来处理选项和主题。 OptionsMenu.xaml 文件作为对话框让用户选择,OptionsMenuVM.cs 设置绑定,Options.cs 保存数据。这些分别是视图、视图模型和模型。
Options.cs 有
public class Options
{
//white theme color values
public SolidColorBrush whiteThemeLightPanelColor = new SolidColorBrush(Color.FromRgb(255, 255, 255));
public SolidColorBrush whiteThemeDarkPanelColor = new SolidColorBrush(Color.FromRgb(230, 230, 230));
public SolidColorBrush whiteThemeTextBoxBackgroundColor = new SolidColorBrush(Color.FromRgb(255, 255, 255));
public SolidColorBrush whiteThemeTextBoxForegroundColor = new SolidColorBrush(Color.FromRgb(0, 0, 0));
//light theme color values
public SolidColorBrush lightThemeLightPanelColor = new SolidColorBrush(Color.FromRgb(200, 200, 200));
public SolidColorBrush lightThemeDarkPanelColor = new SolidColorBrush(Color.FromRgb(225, 225, 225));
public SolidColorBrush lightThemeTextBoxBackgroundColor = new SolidColorBrush(Color.FromRgb(180, 180, 180));
public SolidColorBrush lightThemeTextBoxForegroundColor = new SolidColorBrush(Color.FromRgb(0, 0, 0));
//dark theme color values
public SolidColorBrush darkThemeLightPanelColor = new SolidColorBrush(Color.FromRgb(100, 100, 100));
public SolidColorBrush darkThemeDarkPanelColor = new SolidColorBrush(Color.FromRgb(70, 70, 70));
public SolidColorBrush darkThemeTextBoxBackgroundColor = new SolidColorBrush(Color.FromRgb(50, 50, 50));
public SolidColorBrush darkThemeTextBoxForegroundColor = new SolidColorBrush(Color.FromRgb(255, 255, 255));
//blue theme color values
public SolidColorBrush blueThemeLightPanelColor = new SolidColorBrush(Color.FromRgb(105, 175, 209));
public SolidColorBrush blueThemeDarkPanelColor = new SolidColorBrush(Color.FromRgb(34, 103, 140));
public SolidColorBrush blueThemeTextBoxBackgroundColor = new SolidColorBrush(Color.FromRgb(211, 211, 211));
public SolidColorBrush blueThemeTextBoxForegroundColor = new SolidColorBrush(Color.FromRgb(0, 0, 0));
//most recent custom color values
public SolidColorBrush lastCustomLightPanelColor = new SolidColorBrush(Color.FromRgb(200, 200, 200));
public SolidColorBrush lastCustomDarkPanelColor = new SolidColorBrush(Color.FromRgb(225, 225, 225));
public SolidColorBrush lastCustomTextBoxBackgroundColor = new SolidColorBrush(Color.FromRgb(180, 180, 180));
public SolidColorBrush lastCustomTextBoxForegroundColor = new SolidColorBrush(Color.FromRgb(0, 0, 0));
//runtime color values
public SolidColorBrush lightPanelColor;
public SolidColorBrush darkPanelColor;
public SolidColorBrush textBoxBackgroundColor;
public SolidColorBrush textBoxForegroundColor;
public string chosenTheme = "Light";
}
视图模型有
private Options userOptions;
public Options UserOptions
{
get => userOptions;
set
{
userOptions = value;
OnPropertyChanged("UserOptions");
OnPropertyChanged("ChosenTheme");
UpdateColors();
}
}
public SolidColorBrush LightPanelColor
{
get => userOptions.lightPanelColor;
set
{
userOptions.lightPanelColor = value;
OnPropertyChanged("LightPanelColor");
}
}
public SolidColorBrush DarkPanelColor
{
get => userOptions.darkPanelColor;
set
{
userOptions.darkPanelColor = value;
OnPropertyChanged("DarkPanelColor");
}
}
public SolidColorBrush TextBoxBackgroundColor
{
get => userOptions.textBoxBackgroundColor;
set
{
userOptions.textBoxBackgroundColor = value;
OnPropertyChanged("TextBoxBackgroundColor");
}
}
public ObservableCollection<string> ThemeOptions { get; } = new ObservableCollection<string>() { "White", "Light", "Blue", "Dark", "Custom" };
public string ChosenTheme
{
get => userOptions.chosenTheme;
set
{
userOptions.chosenTheme = value;
OnPropertyChanged("ChosenTheme");
OnPropertyChanged("EnableColorSelection");
UpdateColors();
}
}
public void UpdateColors()
{
if(userOptions.chosenTheme.Equals("White"))
{
LightPanelColor = userOptions.whiteThemeLightPanelColor;
DarkPanelColor = userOptions.whiteThemeDarkPanelColor;
TextBoxBackgroundColor = userOptions.whiteThemeTextBoxBackgroundColor;
TextBoxForegroundColor = userOptions.whiteThemeTextBoxForegroundColor;
}
else if (userOptions.chosenTheme.Equals("Light"))
{
LightPanelColor = userOptions.lightThemeLightPanelColor;
DarkPanelColor = userOptions.lightThemeDarkPanelColor;
TextBoxBackgroundColor = userOptions.lightThemeTextBoxBackgroundColor;
TextBoxForegroundColor = userOptions.lightThemeTextBoxForegroundColor;
}
else if (userOptions.chosenTheme.Equals("Dark"))
{
LightPanelColor = userOptions.darkThemeLightPanelColor;
DarkPanelColor = userOptions.darkThemeDarkPanelColor;
TextBoxBackgroundColor = userOptions.darkThemeTextBoxBackgroundColor;
TextBoxForegroundColor = userOptions.darkThemeTextBoxForegroundColor;
}
else if (userOptions.chosenTheme.Equals("Blue"))
{
LightPanelColor = userOptions.blueThemeLightPanelColor;
DarkPanelColor = userOptions.blueThemeDarkPanelColor;
TextBoxBackgroundColor = userOptions.blueThemeTextBoxBackgroundColor;
TextBoxForegroundColor = userOptions.blueThemeTextBoxForegroundColor;
}
else if(userOptions.chosenTheme.Equals("Custom"))
{
LightPanelColor = userOptions.lastCustomLightPanelColor;
DarkPanelColor = userOptions.lastCustomDarkPanelColor;
TextBoxBackgroundColor = userOptions.lastCustomTextBoxBackgroundColor;
TextBoxForegroundColor = userOptions.lastCustomTextBoxForegroundColor;
}
}
Options.xaml 有
<StackPanel Orientation="Horizontal">
<Label Content="Theme: " />
<ComboBox Width="150" ItemsSource="{Binding ThemeOptions}" SelectedItem="{Binding ChosenTheme}" />
</StackPanel>
<GroupBox Header="Custom Theme Color Selections" IsEnabled="{Binding EnableColorSelection}" Style="{StaticResource GroupBoxStyle}">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Label Content="Light Panel Background: " Style="{StaticResource OptionsLabel}"/>
<Button Foreground="{Binding Path=LightPanelColor, UpdateSourceTrigger=PropertyChanged}" ToolTip="Click to Change Color" Width="28" Height="18" Command="{Binding ChooseColorCmd}" CommandParameter="LightPanelColor">
<Rectangle Width="40" Height="15" Fill="{Binding Path=LightPanelColor, UpdateSourceTrigger=PropertyChanged}"/>
</Button>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Dark Panel Background: " Style="{StaticResource OptionsLabel}"/>
<Button Foreground="{Binding Path=DarkPanelColor, UpdateSourceTrigger=PropertyChanged}" ToolTip="Click to Change Color" Width="28" Height="18" Command="{Binding ChooseColorCmd}" CommandParameter="DarkPanelColor">
<Rectangle Width="40" Height="15" Fill="{Binding Path=DarkPanelColor, UpdateSourceTrigger=PropertyChanged}"/>
</Button>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Text Box Background: " Style="{StaticResource OptionsLabel}"/>
<Button Foreground="{Binding Path=TextBoxBackgroundColor, UpdateSourceTrigger=PropertyChanged}" ToolTip="Click to Change Color" Width="28" Height="18" Command="{Binding ChooseColorCmd}" CommandParameter="TextBoxBackgroundColor">
<Rectangle Width="40" Height="15" Fill="{Binding Path=TextBoxBackgroundColor, UpdateSourceTrigger=PropertyChanged}"/>
</Button>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Text Box Foreground: " Style="{StaticResource OptionsLabel}"/>
<Button Foreground="{Binding Path=TextBoxForegroundColor, UpdateSourceTrigger=PropertyChanged}" ToolTip="Click to Change Color" Width="28" Height="18" Command="{Binding ChooseColorCmd}" CommandParameter="TextBoxForegroundColor">
<Rectangle Width="40" Height="15" Fill="{Binding Path=TextBoxForegroundColor, UpdateSourceTrigger=PropertyChanged}"/>
</Button>
</StackPanel>
</StackPanel>
</GroupBox>
这会为您提供一个 select 主题的组合框,如果他们选择自定义,我会启用一些按钮让他们从颜色选择器中进行选择。您可以从颜色选择器中获取 RGB 并以此方式设置颜色。如果他们选择普通主题,UpdateColors() 将查看所选主题并将颜色设置为 option.cs.
中的默认颜色
这是我的 viewModel 中按钮调用以选择自定义颜色的函数。顺便说一句,标准主题更容易添加自定义颜色使其变得更加复杂。
private void ChooseColor(object cp)
{
string caller = cp.ToString();
System.Windows.Forms.ColorDialog cd = new System.Windows.Forms.ColorDialog();
Color startColor = Color.FromRgb(0,0,0);
if (caller.Equals("LightPanelColor"))
{
startColor = LightPanelColor.Color;
}
else if (caller.Equals("DarkPanelColor"))
{
startColor = DarkPanelColor.Color;
}
else if (caller.Equals("TextBoxBackgroundColor"))
{
startColor = TextBoxBackgroundColor.Color;
}
else if (caller.Equals("TextBoxForegroundColor"))
{
startColor = TextBoxForegroundColor.Color;
}
else
{
return;
}
cd.Color = System.Drawing.Color.FromArgb(startColor.A, startColor.R, startColor.G, startColor.B);
if (cd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
if(caller.Equals("LightPanelColor"))
{
LightPanelColor.Color = Color.FromArgb(cd.Color.A, cd.Color.R, cd.Color.G, cd.Color.B);
OnPropertyChanged("LightPanelColor");
}
else if(caller.Equals("DarkPanelColor"))
{
DarkPanelColor.Color = Color.FromArgb(cd.Color.A, cd.Color.R, cd.Color.G, cd.Color.B);
OnPropertyChanged("DarkPanelColor");
}
else if(caller.Equals("TextBoxBackgroundColor"))
{
TextBoxBackgroundColor.Color = Color.FromArgb(cd.Color.A, cd.Color.R, cd.Color.G, cd.Color.B);
OnPropertyChanged("TextBoxBackgroundColor");
}
else if(caller.Equals("TextBoxForegroundColor"))
{
TextBoxForegroundColor.Color = Color.FromArgb(cd.Color.A, cd.Color.R, cd.Color.G, cd.Color.B);
OnPropertyChanged("TextBoxForegroundColor");
}
else
{
return;
}
}
}
为了让这些颜色在整个应用程序中可用,我输入了 App.xaml
<vm:OptionsMenuVM x:Key="optionsMenuVM"/>
OptionsMenu.xaml 通过
获取它的数据上下文
<Window Height="360" Width="564.225" DataContext="{StaticResource optionsMenuVM}" Background="{Binding DarkPanelColor}" Name="OptionsWindow">
现在,您可以在应用程序的任何位置通过
设置控件的背景
Background="{Binding Path=DarkPanelColor, Source={StaticResource optionsMenuVM}}">
然后你可以选择你想要的颜色主题。我只有四种颜色,所以你必须决定有多少种颜色。最好是一种类型或一组控件的颜色。所以每个 TextBox 都使用 TextBoxColor 或任何你称之为的东西。
这是我在我的 App.xaml 文本框样式中放置的示例
<Style x:Key="InputTextbox" TargetType="TextBox">
<Style.Setters>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Width" Value="90"/>
<Setter Property="Margin" Value="0,2,2,2"/>
<Setter Property="Background" Value="{Binding Path=TextBoxBackgroundColor, Source={StaticResource optionsMenuVM}}"/>
<Setter Property="Foreground" Value="{Binding Path=TextBoxForegroundColor, Source={StaticResource optionsMenuVM}}"/>
<EventSetter Event="Loaded" Handler="TextBox_Loaded"/>
</Style.Setters>
</Style>
那么对于像这样的每个 TextBox,您都可以编写
<TextBox Style="{StaticResource InputTextbox}"/>
您也可以对其他控件执行相同的操作。只要在设置颜色时调用 PropertyChanged 事件,整个应用程序就会立即更新。背景颜色绑定将从您的视图模型中提取。
我不得不省略一些部分,因为我有一个很大的选项菜单和很多东西,但我希望我是如何做到这一点的。一旦你做了这样的事情,你想做的一件事就是保存选项数据。
由于视图模型仅包含 Options.cs 的实例,因此用已加载的实例替换此 class 实例将带您回到您离开的地方。我认为将这些数据保存到 AppData 文件夹中是个好主意,您可以通过
Environment.SpecialFolder.ApplicationData
然后将您的程序的新文件夹附加到该路径(Path.Combine 是您的朋友)并在那里保存一个文件供您选择。我尝试序列化,但 Solid Color Brushes 没有序列化,所以我使用 BinaryWriter 来保存 RGB 值并读回它们。这是我的保存和加载功能
private void SaveUserOptionsFile()
{
try
{
using (BinaryWriter binaryWriter = new BinaryWriter(File.Open(optionsSavePath, FileMode.Create)))
{
Color lastCustomLightPanelColor = userOptions.lastCustomLightPanelColor.Color;
Color lastCustomDarkPanelColor = userOptions.lastCustomDarkPanelColor.Color;
Color lastCustomTextBoxBackgroundColor = userOptions.lastCustomTextBoxBackgroundColor.Color;
Color lastCustomTextBoxForegroundColor = userOptions.lastCustomTextBoxForegroundColor.Color;
binaryWriter.Write(lastCustomLightPanelColor.R);
binaryWriter.Write(lastCustomLightPanelColor.G);
binaryWriter.Write(lastCustomLightPanelColor.B);
binaryWriter.Write(lastCustomDarkPanelColor.R);
binaryWriter.Write(lastCustomDarkPanelColor.G);
binaryWriter.Write(lastCustomDarkPanelColor.B);
binaryWriter.Write(lastCustomTextBoxBackgroundColor.R);
binaryWriter.Write(lastCustomTextBoxBackgroundColor.G);
binaryWriter.Write(lastCustomTextBoxBackgroundColor.B);
binaryWriter.Write(lastCustomTextBoxForegroundColor.R);
binaryWriter.Write(lastCustomTextBoxForegroundColor.G);
binaryWriter.Write(lastCustomTextBoxForegroundColor.B);
binaryWriter.Write(userOptions.chosenTheme);
}
}
catch(IOException e)
{
if(File.Exists(optionsSavePath))
{
File.Delete(optionsSavePath);
}
}
}
public void LoadUserOptionsFile()
{
if (File.Exists(optionsSavePath))
{
try
{
using (BinaryReader binaryReader = new BinaryReader(File.Open(optionsSavePath, FileMode.Open)))
{
UserOptions.lastCustomLightPanelColor.Color = Color.FromRgb(binaryReader.ReadByte(), binaryReader.ReadByte(), binaryReader.ReadByte());
UserOptions.lastCustomDarkPanelColor.Color = Color.FromRgb(binaryReader.ReadByte(), binaryReader.ReadByte(), binaryReader.ReadByte());
UserOptions.lastCustomTextBoxBackgroundColor.Color = Color.FromRgb(binaryReader.ReadByte(), binaryReader.ReadByte(), binaryReader.ReadByte());
UserOptions.lastCustomTextBoxForegroundColor.Color = Color.FromRgb(binaryReader.ReadByte(), binaryReader.ReadByte(), binaryReader.ReadByte());
ChosenTheme = binaryReader.ReadString();
originalUserOptions = new Options(UserOptions);
}
}
catch (IOException e)
{
UserOptions = new Options();
originalUserOptions = new Options();
if (File.Exists(optionsSavePath))
{
File.Delete(optionsSavePath);
}
}
}
}
我通过捕获主 window 上的关闭事件并调用保存函数来保存此文件。程序打开时调用加载,如果没有任何内容,它将转到默认起始值。
我有变量 originalUserOptions 在那里让用户取消。它被构造为用户打开菜单时存在的选项的副本。实际选项实例由他们的输入编辑,他们看到颜色发生变化。如果他们点击确定,选项将保留,如果他们点击取消,我将选项设置回原始实例,它会回到开始时的状态。在我的 setter 选项中,我必须为所有相关数据调用 PropertyChanged 才能更新视图。
我知道这里有很多,但这是一个应用程序范围的数据争论练习,它分布在许多文件中。如果您尝试其中的任何一个,请不要复制粘贴整个块我有很多事情要做,我不得不尝试提取相关位。以此作为思路的例子,利用其中的一些思路自己搭建一个主题系统。对不起,如果实际使用太多了。祝你好运。
如何在 Windows10 下更改本机 WPF 控件主题使用的基础颜色?我知道有像 MahApps.Metro 和 MUI 这样的库,但我想做的就是让我的应用程序中的元素以一致的颜色绘制(MenuItem 和 Toolbar,我在看你和你的不那么和谐颜色)。我也想提供各种颜色主题。
我该怎么做?
我不得不承认我只是不明白我的要求是否可行。一些调查表明默认的 WPF 主题使用静态资源来处理按钮背景等内容:
<Setter Property="Background" Value="{StaticResource Button.Static.Background}"/>
如果那个资源是静态的,我想我不能改变它?简单地复制所有原始 WPF 模板并以某种方式用动态资源替换静态资源是否有意义?
要对所有控件和颜色“正确”执行此操作比您想象的要复杂得多。
有些画笔使用 windows 主题颜色,有些使用硬编码值。
您可以over-ride windows 主题颜色。
例如,在 app.xaml 中合并的资源字典中,您可以对所有颜色和画笔设置自己的首选项。
这是一个:
<SolidColorBrush Color="LimeGreen" x:Key="{x:Static SystemColors.HighlightBrushKey}"/>
https://docs.microsoft.com/en-us/dotnet/api/system.windows.systemcolors?view=netcore-3.1
你会发现这只会改变一些事情。
您必须 re-template 控件来替换硬编码值。
这是什么意思?
看看单选按钮模板:
在那里你会看到像这样的东西:
<Ellipse x:Name="Border"
StrokeThickness="1">
<Ellipse.Stroke>
<LinearGradientBrush EndPoint="0.5,1"
StartPoint="0.5,0">
<GradientStop Color="{DynamicResource BorderLightColor}"
Offset="0" />
<GradientStop Color="{DynamicResource BorderDarkColor}"
Offset="1" />
</LinearGradientBrush>
</Ellipse.Stroke>
<Ellipse.Fill>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource ControlLightColor}" />
<GradientStop Color="{DynamicResource ControlMediumColor}"
Offset="1.0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
这些资源在主模板下方的一长串大列表中定义。你想要替换所有这些。
无论您想让您的控件全部使用 windows 主题还是您自己的主题,如果您从头开始,您将有很多工作要做。
您可能想看看可用的各种 pre-rolled 主题。 Material 设计很受欢迎,因为很多人都熟悉 android。
我最近为我正在开发的应用程序创建了一些自定义主题。首先要注意的是 WPF 中的控件是 class SolidColorBrush。这些可以通过在不同构造函数中采用 RGBA 的各种方法构造。
这是一个很大的努力,所以如果它太多了,至少跳转到我在 App.xaml 中的样式 xaml 示例,并注意您可以绑定到 [=] 中的 ViewModel 中的变量79=]s 从而生成 StaticResource return 动态颜色值。这就是绑定的美妙之处。您仍然通过 {StaticResource} 分配样式,但它提供的值由代码中的绑定变量决定。
我创建了三个 classes 来处理选项和主题。 OptionsMenu.xaml 文件作为对话框让用户选择,OptionsMenuVM.cs 设置绑定,Options.cs 保存数据。这些分别是视图、视图模型和模型。
Options.cs 有
public class Options
{
//white theme color values
public SolidColorBrush whiteThemeLightPanelColor = new SolidColorBrush(Color.FromRgb(255, 255, 255));
public SolidColorBrush whiteThemeDarkPanelColor = new SolidColorBrush(Color.FromRgb(230, 230, 230));
public SolidColorBrush whiteThemeTextBoxBackgroundColor = new SolidColorBrush(Color.FromRgb(255, 255, 255));
public SolidColorBrush whiteThemeTextBoxForegroundColor = new SolidColorBrush(Color.FromRgb(0, 0, 0));
//light theme color values
public SolidColorBrush lightThemeLightPanelColor = new SolidColorBrush(Color.FromRgb(200, 200, 200));
public SolidColorBrush lightThemeDarkPanelColor = new SolidColorBrush(Color.FromRgb(225, 225, 225));
public SolidColorBrush lightThemeTextBoxBackgroundColor = new SolidColorBrush(Color.FromRgb(180, 180, 180));
public SolidColorBrush lightThemeTextBoxForegroundColor = new SolidColorBrush(Color.FromRgb(0, 0, 0));
//dark theme color values
public SolidColorBrush darkThemeLightPanelColor = new SolidColorBrush(Color.FromRgb(100, 100, 100));
public SolidColorBrush darkThemeDarkPanelColor = new SolidColorBrush(Color.FromRgb(70, 70, 70));
public SolidColorBrush darkThemeTextBoxBackgroundColor = new SolidColorBrush(Color.FromRgb(50, 50, 50));
public SolidColorBrush darkThemeTextBoxForegroundColor = new SolidColorBrush(Color.FromRgb(255, 255, 255));
//blue theme color values
public SolidColorBrush blueThemeLightPanelColor = new SolidColorBrush(Color.FromRgb(105, 175, 209));
public SolidColorBrush blueThemeDarkPanelColor = new SolidColorBrush(Color.FromRgb(34, 103, 140));
public SolidColorBrush blueThemeTextBoxBackgroundColor = new SolidColorBrush(Color.FromRgb(211, 211, 211));
public SolidColorBrush blueThemeTextBoxForegroundColor = new SolidColorBrush(Color.FromRgb(0, 0, 0));
//most recent custom color values
public SolidColorBrush lastCustomLightPanelColor = new SolidColorBrush(Color.FromRgb(200, 200, 200));
public SolidColorBrush lastCustomDarkPanelColor = new SolidColorBrush(Color.FromRgb(225, 225, 225));
public SolidColorBrush lastCustomTextBoxBackgroundColor = new SolidColorBrush(Color.FromRgb(180, 180, 180));
public SolidColorBrush lastCustomTextBoxForegroundColor = new SolidColorBrush(Color.FromRgb(0, 0, 0));
//runtime color values
public SolidColorBrush lightPanelColor;
public SolidColorBrush darkPanelColor;
public SolidColorBrush textBoxBackgroundColor;
public SolidColorBrush textBoxForegroundColor;
public string chosenTheme = "Light";
}
视图模型有
private Options userOptions;
public Options UserOptions
{
get => userOptions;
set
{
userOptions = value;
OnPropertyChanged("UserOptions");
OnPropertyChanged("ChosenTheme");
UpdateColors();
}
}
public SolidColorBrush LightPanelColor
{
get => userOptions.lightPanelColor;
set
{
userOptions.lightPanelColor = value;
OnPropertyChanged("LightPanelColor");
}
}
public SolidColorBrush DarkPanelColor
{
get => userOptions.darkPanelColor;
set
{
userOptions.darkPanelColor = value;
OnPropertyChanged("DarkPanelColor");
}
}
public SolidColorBrush TextBoxBackgroundColor
{
get => userOptions.textBoxBackgroundColor;
set
{
userOptions.textBoxBackgroundColor = value;
OnPropertyChanged("TextBoxBackgroundColor");
}
}
public ObservableCollection<string> ThemeOptions { get; } = new ObservableCollection<string>() { "White", "Light", "Blue", "Dark", "Custom" };
public string ChosenTheme
{
get => userOptions.chosenTheme;
set
{
userOptions.chosenTheme = value;
OnPropertyChanged("ChosenTheme");
OnPropertyChanged("EnableColorSelection");
UpdateColors();
}
}
public void UpdateColors()
{
if(userOptions.chosenTheme.Equals("White"))
{
LightPanelColor = userOptions.whiteThemeLightPanelColor;
DarkPanelColor = userOptions.whiteThemeDarkPanelColor;
TextBoxBackgroundColor = userOptions.whiteThemeTextBoxBackgroundColor;
TextBoxForegroundColor = userOptions.whiteThemeTextBoxForegroundColor;
}
else if (userOptions.chosenTheme.Equals("Light"))
{
LightPanelColor = userOptions.lightThemeLightPanelColor;
DarkPanelColor = userOptions.lightThemeDarkPanelColor;
TextBoxBackgroundColor = userOptions.lightThemeTextBoxBackgroundColor;
TextBoxForegroundColor = userOptions.lightThemeTextBoxForegroundColor;
}
else if (userOptions.chosenTheme.Equals("Dark"))
{
LightPanelColor = userOptions.darkThemeLightPanelColor;
DarkPanelColor = userOptions.darkThemeDarkPanelColor;
TextBoxBackgroundColor = userOptions.darkThemeTextBoxBackgroundColor;
TextBoxForegroundColor = userOptions.darkThemeTextBoxForegroundColor;
}
else if (userOptions.chosenTheme.Equals("Blue"))
{
LightPanelColor = userOptions.blueThemeLightPanelColor;
DarkPanelColor = userOptions.blueThemeDarkPanelColor;
TextBoxBackgroundColor = userOptions.blueThemeTextBoxBackgroundColor;
TextBoxForegroundColor = userOptions.blueThemeTextBoxForegroundColor;
}
else if(userOptions.chosenTheme.Equals("Custom"))
{
LightPanelColor = userOptions.lastCustomLightPanelColor;
DarkPanelColor = userOptions.lastCustomDarkPanelColor;
TextBoxBackgroundColor = userOptions.lastCustomTextBoxBackgroundColor;
TextBoxForegroundColor = userOptions.lastCustomTextBoxForegroundColor;
}
}
Options.xaml 有
<StackPanel Orientation="Horizontal">
<Label Content="Theme: " />
<ComboBox Width="150" ItemsSource="{Binding ThemeOptions}" SelectedItem="{Binding ChosenTheme}" />
</StackPanel>
<GroupBox Header="Custom Theme Color Selections" IsEnabled="{Binding EnableColorSelection}" Style="{StaticResource GroupBoxStyle}">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Label Content="Light Panel Background: " Style="{StaticResource OptionsLabel}"/>
<Button Foreground="{Binding Path=LightPanelColor, UpdateSourceTrigger=PropertyChanged}" ToolTip="Click to Change Color" Width="28" Height="18" Command="{Binding ChooseColorCmd}" CommandParameter="LightPanelColor">
<Rectangle Width="40" Height="15" Fill="{Binding Path=LightPanelColor, UpdateSourceTrigger=PropertyChanged}"/>
</Button>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Dark Panel Background: " Style="{StaticResource OptionsLabel}"/>
<Button Foreground="{Binding Path=DarkPanelColor, UpdateSourceTrigger=PropertyChanged}" ToolTip="Click to Change Color" Width="28" Height="18" Command="{Binding ChooseColorCmd}" CommandParameter="DarkPanelColor">
<Rectangle Width="40" Height="15" Fill="{Binding Path=DarkPanelColor, UpdateSourceTrigger=PropertyChanged}"/>
</Button>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Text Box Background: " Style="{StaticResource OptionsLabel}"/>
<Button Foreground="{Binding Path=TextBoxBackgroundColor, UpdateSourceTrigger=PropertyChanged}" ToolTip="Click to Change Color" Width="28" Height="18" Command="{Binding ChooseColorCmd}" CommandParameter="TextBoxBackgroundColor">
<Rectangle Width="40" Height="15" Fill="{Binding Path=TextBoxBackgroundColor, UpdateSourceTrigger=PropertyChanged}"/>
</Button>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Text Box Foreground: " Style="{StaticResource OptionsLabel}"/>
<Button Foreground="{Binding Path=TextBoxForegroundColor, UpdateSourceTrigger=PropertyChanged}" ToolTip="Click to Change Color" Width="28" Height="18" Command="{Binding ChooseColorCmd}" CommandParameter="TextBoxForegroundColor">
<Rectangle Width="40" Height="15" Fill="{Binding Path=TextBoxForegroundColor, UpdateSourceTrigger=PropertyChanged}"/>
</Button>
</StackPanel>
</StackPanel>
</GroupBox>
这会为您提供一个 select 主题的组合框,如果他们选择自定义,我会启用一些按钮让他们从颜色选择器中进行选择。您可以从颜色选择器中获取 RGB 并以此方式设置颜色。如果他们选择普通主题,UpdateColors() 将查看所选主题并将颜色设置为 option.cs.
中的默认颜色这是我的 viewModel 中按钮调用以选择自定义颜色的函数。顺便说一句,标准主题更容易添加自定义颜色使其变得更加复杂。
private void ChooseColor(object cp)
{
string caller = cp.ToString();
System.Windows.Forms.ColorDialog cd = new System.Windows.Forms.ColorDialog();
Color startColor = Color.FromRgb(0,0,0);
if (caller.Equals("LightPanelColor"))
{
startColor = LightPanelColor.Color;
}
else if (caller.Equals("DarkPanelColor"))
{
startColor = DarkPanelColor.Color;
}
else if (caller.Equals("TextBoxBackgroundColor"))
{
startColor = TextBoxBackgroundColor.Color;
}
else if (caller.Equals("TextBoxForegroundColor"))
{
startColor = TextBoxForegroundColor.Color;
}
else
{
return;
}
cd.Color = System.Drawing.Color.FromArgb(startColor.A, startColor.R, startColor.G, startColor.B);
if (cd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
if(caller.Equals("LightPanelColor"))
{
LightPanelColor.Color = Color.FromArgb(cd.Color.A, cd.Color.R, cd.Color.G, cd.Color.B);
OnPropertyChanged("LightPanelColor");
}
else if(caller.Equals("DarkPanelColor"))
{
DarkPanelColor.Color = Color.FromArgb(cd.Color.A, cd.Color.R, cd.Color.G, cd.Color.B);
OnPropertyChanged("DarkPanelColor");
}
else if(caller.Equals("TextBoxBackgroundColor"))
{
TextBoxBackgroundColor.Color = Color.FromArgb(cd.Color.A, cd.Color.R, cd.Color.G, cd.Color.B);
OnPropertyChanged("TextBoxBackgroundColor");
}
else if(caller.Equals("TextBoxForegroundColor"))
{
TextBoxForegroundColor.Color = Color.FromArgb(cd.Color.A, cd.Color.R, cd.Color.G, cd.Color.B);
OnPropertyChanged("TextBoxForegroundColor");
}
else
{
return;
}
}
}
为了让这些颜色在整个应用程序中可用,我输入了 App.xaml
<vm:OptionsMenuVM x:Key="optionsMenuVM"/>
OptionsMenu.xaml 通过
获取它的数据上下文 <Window Height="360" Width="564.225" DataContext="{StaticResource optionsMenuVM}" Background="{Binding DarkPanelColor}" Name="OptionsWindow">
现在,您可以在应用程序的任何位置通过
设置控件的背景 Background="{Binding Path=DarkPanelColor, Source={StaticResource optionsMenuVM}}">
然后你可以选择你想要的颜色主题。我只有四种颜色,所以你必须决定有多少种颜色。最好是一种类型或一组控件的颜色。所以每个 TextBox 都使用 TextBoxColor 或任何你称之为的东西。
这是我在我的 App.xaml 文本框样式中放置的示例
<Style x:Key="InputTextbox" TargetType="TextBox">
<Style.Setters>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Width" Value="90"/>
<Setter Property="Margin" Value="0,2,2,2"/>
<Setter Property="Background" Value="{Binding Path=TextBoxBackgroundColor, Source={StaticResource optionsMenuVM}}"/>
<Setter Property="Foreground" Value="{Binding Path=TextBoxForegroundColor, Source={StaticResource optionsMenuVM}}"/>
<EventSetter Event="Loaded" Handler="TextBox_Loaded"/>
</Style.Setters>
</Style>
那么对于像这样的每个 TextBox,您都可以编写
<TextBox Style="{StaticResource InputTextbox}"/>
您也可以对其他控件执行相同的操作。只要在设置颜色时调用 PropertyChanged 事件,整个应用程序就会立即更新。背景颜色绑定将从您的视图模型中提取。
我不得不省略一些部分,因为我有一个很大的选项菜单和很多东西,但我希望我是如何做到这一点的。一旦你做了这样的事情,你想做的一件事就是保存选项数据。
由于视图模型仅包含 Options.cs 的实例,因此用已加载的实例替换此 class 实例将带您回到您离开的地方。我认为将这些数据保存到 AppData 文件夹中是个好主意,您可以通过
Environment.SpecialFolder.ApplicationData
然后将您的程序的新文件夹附加到该路径(Path.Combine 是您的朋友)并在那里保存一个文件供您选择。我尝试序列化,但 Solid Color Brushes 没有序列化,所以我使用 BinaryWriter 来保存 RGB 值并读回它们。这是我的保存和加载功能
private void SaveUserOptionsFile()
{
try
{
using (BinaryWriter binaryWriter = new BinaryWriter(File.Open(optionsSavePath, FileMode.Create)))
{
Color lastCustomLightPanelColor = userOptions.lastCustomLightPanelColor.Color;
Color lastCustomDarkPanelColor = userOptions.lastCustomDarkPanelColor.Color;
Color lastCustomTextBoxBackgroundColor = userOptions.lastCustomTextBoxBackgroundColor.Color;
Color lastCustomTextBoxForegroundColor = userOptions.lastCustomTextBoxForegroundColor.Color;
binaryWriter.Write(lastCustomLightPanelColor.R);
binaryWriter.Write(lastCustomLightPanelColor.G);
binaryWriter.Write(lastCustomLightPanelColor.B);
binaryWriter.Write(lastCustomDarkPanelColor.R);
binaryWriter.Write(lastCustomDarkPanelColor.G);
binaryWriter.Write(lastCustomDarkPanelColor.B);
binaryWriter.Write(lastCustomTextBoxBackgroundColor.R);
binaryWriter.Write(lastCustomTextBoxBackgroundColor.G);
binaryWriter.Write(lastCustomTextBoxBackgroundColor.B);
binaryWriter.Write(lastCustomTextBoxForegroundColor.R);
binaryWriter.Write(lastCustomTextBoxForegroundColor.G);
binaryWriter.Write(lastCustomTextBoxForegroundColor.B);
binaryWriter.Write(userOptions.chosenTheme);
}
}
catch(IOException e)
{
if(File.Exists(optionsSavePath))
{
File.Delete(optionsSavePath);
}
}
}
public void LoadUserOptionsFile()
{
if (File.Exists(optionsSavePath))
{
try
{
using (BinaryReader binaryReader = new BinaryReader(File.Open(optionsSavePath, FileMode.Open)))
{
UserOptions.lastCustomLightPanelColor.Color = Color.FromRgb(binaryReader.ReadByte(), binaryReader.ReadByte(), binaryReader.ReadByte());
UserOptions.lastCustomDarkPanelColor.Color = Color.FromRgb(binaryReader.ReadByte(), binaryReader.ReadByte(), binaryReader.ReadByte());
UserOptions.lastCustomTextBoxBackgroundColor.Color = Color.FromRgb(binaryReader.ReadByte(), binaryReader.ReadByte(), binaryReader.ReadByte());
UserOptions.lastCustomTextBoxForegroundColor.Color = Color.FromRgb(binaryReader.ReadByte(), binaryReader.ReadByte(), binaryReader.ReadByte());
ChosenTheme = binaryReader.ReadString();
originalUserOptions = new Options(UserOptions);
}
}
catch (IOException e)
{
UserOptions = new Options();
originalUserOptions = new Options();
if (File.Exists(optionsSavePath))
{
File.Delete(optionsSavePath);
}
}
}
}
我通过捕获主 window 上的关闭事件并调用保存函数来保存此文件。程序打开时调用加载,如果没有任何内容,它将转到默认起始值。
我有变量 originalUserOptions 在那里让用户取消。它被构造为用户打开菜单时存在的选项的副本。实际选项实例由他们的输入编辑,他们看到颜色发生变化。如果他们点击确定,选项将保留,如果他们点击取消,我将选项设置回原始实例,它会回到开始时的状态。在我的 setter 选项中,我必须为所有相关数据调用 PropertyChanged 才能更新视图。
我知道这里有很多,但这是一个应用程序范围的数据争论练习,它分布在许多文件中。如果您尝试其中的任何一个,请不要复制粘贴整个块我有很多事情要做,我不得不尝试提取相关位。以此作为思路的例子,利用其中的一些思路自己搭建一个主题系统。对不起,如果实际使用太多了。祝你好运。