我遇到了类似的问题。我为工作中的 DSL 开发了一个语法荧光笔。它有两组颜色 - 用于浅色和深色主题。当 VS 主题发生变化时,我需要一种在运行时在这两组颜色之间切换的方法。
经过一番搜索,我在 F# github 中负责与 VS 集成的代码中找到了解决方案:
https://github.com/dotnet/fsharp/blob/main/vsintegration/src/FSharp.Editor/Classification/ClassificationDefinitions.fs#L121
F# repo 中的代码与 Omer Raviv 的答案中的代码非常相似。我将它翻译成 C# 并得到如下内容:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows.Media;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;
using Microsoft.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using DefGuidList = Microsoft.VisualStudio.Editor.DefGuidList;
using VSConstants = Microsoft.VisualStudio.VSConstants;
//...
internal abstract class EditorFormatBase : ClassificationFormatDefinition, IDisposable
{
private const string textCategory = "text";
private readonly string classificationTypeName;
protected EditorFormatBase()
{
VSColorTheme.ThemeChanged += VSColorTheme_ThemeChanged;
//Get string ID which has to be attached with NameAttribute for ClassificationFormatDefinition-derived classes
Type type = this.GetType();
classificationTypeName = type.GetCustomAttribute<NameAttribute>()?.Name;
if (classificationTypeName != null)
{
ForegroundColor = VSColors.GetThemedColor(classificationTypeName); //Call to my class VSColors which returns correct color for the theme
}
}
private void VSColorTheme_ThemeChanged(ThemeChangedEventArgs e)
{
//Here MyPackage.Instance is a singleton of my extension's Package derived class, it contains references to
// IClassificationFormatMapService and
// IClassificationTypeRegistryService objects
if (MyPackage.Instance?.ClassificationFormatMapService == null || MyPackage.Instance.ClassificationRegistry == null || classificationTypeName == null)
{
return;
}
var fontAndColorStorage =
ServiceProvider.GlobalProvider.GetService(typeof(SVsFontAndColorStorage)) as IVsFontAndColorStorage;
var fontAndColorCacheManager =
ServiceProvider.GlobalProvider.GetService(typeof(SVsFontAndColorCacheManager)) as IVsFontAndColorCacheManager;
if (fontAndColorStorage == null || fontAndColorCacheManager == null)
return;
Guid guidTextEditorFontCategory = DefGuidList.guidTextEditorFontCategory;
fontAndColorCacheManager.CheckCache(ref guidTextEditorFontCategory, out int _ );
if (fontAndColorStorage.OpenCategory(ref guidTextEditorFontCategory, (uint) __FCSTORAGEFLAGS.FCSF_READONLY) != VSConstants.S_OK)
{
//Possibly log warning/error, in F# source it’s ignored
}
Color? foregroundColorForTheme = VSColors.GetThemedColor(classificationTypeName); //VSColors is my class which stores colors, GetThemedColor returns color for the theme
if (foregroundColorForTheme == null)
return;
IClassificationFormatMap formatMap = MyPackage.Instance.ClassificationFormatMapService
.GetClassificationFormatMap(category: textCategory);
if (formatMap == null)
return;
try
{
formatMap.BeginBatchUpdate();
ForegroundColor = foregroundColorForTheme;
var myClasType = MyPackage.Instance.ClassificationRegistry
.GetClassificationType(classificationTypeName);
if (myClasType == null)
return;
ColorableItemInfo[] colorInfo = new ColorableItemInfo[1];
if (fontAndColorStorage.GetItem(classificationTypeName, colorInfo) != VSConstants.S_OK) //comment from F# repo: "we don't touch the changes made by the user"
{
var properties = formatMap.GetTextProperties(myClasType);
var newProperties = properties.SetForeground(ForegroundColor.Value);
formatMap.SetTextProperties(myClasType, newProperties);
}
}
catch (Exception)
{
//Log error here, in F# repo there are no catch blocks, only finally block
}
finally
{
formatMap.EndBatchUpdate();
}
}
void IDisposable.Dispose()
{
VSColorTheme.ThemeChanged -= VSColorTheme_ThemeChanged;
}
}
我使用上面的类作为我所有 ClassificationFormatDefinition 类的基类。
编辑:升级到 AsyncPackage 以获取较新版本的 VS 后,以前的代码停止工作。您需要在其他地方声明 MEF 导入,例如,直接在 ClassificationFormatDefinition 的继承者中。此外,正如@Alessandro 所指出的,代码中存在一个微妙的错误。如果您切换 VS 主题,然后立即转到 VS 设置“字体和颜色”部分,您将看到默认颜色值没有改变。它们会在 VS 重新启动后发生变化,但这仍然不理想。幸运的是,有一个解决方案(再次感谢@Alessandro)。您需要使用正确的 guid 75A05685-00A8-4DED-BAE5-E7A50BFA929A 调用 IVsFontAndColorCacheManager 的 ClearCache 或 RefreshCache,它对应于注册表中字体和颜色缓存中的 MefItems 类别。
这是对一篇文章的参考,该文章对此进行了一些描述:
https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.interop.ivsfontandcolorcachemanager?view=visualstudiosdk-2019
很遗憾,我找不到 guid 常量的任何文档。
更新:经过更多研究、调试并将错误代码记录添加到 VS 活动日志中,我发现以下内容:
- 主题更改处理程序被多次调用以进行单个更改
VS 主题的
- ClearCache 在前几次调用中返回 0,但之后开始返回错误代码
- RefreshCache 总是返回 0(至少在我的情况下)
因此,我用对 RefreshCache 的调用替换了对 ClearCache 的调用。
因此,这是一个更新的示例:
internal abstract class EditorFormatBase : ClassificationFormatDefinition, IDisposable
{
private const string TextCategory = "text";
private readonly string _classificationTypeName;
private const string MefItemsGuidString = "75A05685-00A8-4DED-BAE5-E7A50BFA929A";
private Guid _mefItemsGuid = new Guid(MefItemsGuidString);
[Import]
internal IClassificationFormatMapService _classificationFormatMapService = null; //Set via MEF
[Import]
internal IClassificationTypeRegistryService _classificationRegistry = null; // Set via MEF
protected EditorFormatBase()
{
VSColorTheme.ThemeChanged += VSColorTheme_ThemeChanged;
Type type = this.GetType();
_classificationTypeName = type.GetCustomAttribute<NameAttribute>()?.Name;
if (_classificationTypeName != null)
{
ForegroundColor = VSColors.GetThemedColor(_classificationTypeName);
}
}
private void VSColorTheme_ThemeChanged(ThemeChangedEventArgs e)
{
ThreadHelper.ThrowIfNotOnUIThread();
if (_classificationFormatMapService == null || _classificationRegistry == null || _classificationTypeName == null)
return;
var fontAndColorStorage = ServiceProvider.GlobalProvider.GetService<SVsFontAndColorStorage, IVsFontAndColorStorage>();
var fontAndColorCacheManager = ServiceProvider.GlobalProvider.GetService<SVsFontAndColorCacheManager, IVsFontAndColorCacheManager>();
if (fontAndColorStorage == null || fontAndColorCacheManager == null)
return;
fontAndColorCacheManager.CheckCache(ref _mefItemsGuid, out int _);
if (fontAndColorStorage.OpenCategory(ref _mefItemsGuid, (uint)__FCSTORAGEFLAGS.FCSF_READONLY) != VSConstants.S_OK)
{
//TODO Log error
}
Color? foregroundColorForTheme = VSColors.GetThemedColor(_classificationTypeName);
if (foregroundColorForTheme == null)
return;
IClassificationFormatMap formatMap = _classificationFormatMapService.GetClassificationFormatMap(category: TextCategory);
if (formatMap == null)
return;
try
{
formatMap.BeginBatchUpdate();
ForegroundColor = foregroundColorForTheme;
var classificationType = _classificationRegistry.GetClassificationType(_classificationTypeName);
if (classificationType == null)
return;
ColorableItemInfo[] colorInfo = new ColorableItemInfo[1];
if (fontAndColorStorage.GetItem(_classificationTypeName, colorInfo) != VSConstants.S_OK) //comment from F# repo: "we don't touch the changes made by the user"
{
var properties = formatMap.GetTextProperties(classificationType);
var newProperties = properties.SetForeground(ForegroundColor.Value);
formatMap.SetTextProperties(classificationType, newProperties);
}
}
catch (Exception)
{
//TODO Log error here
}
finally
{
formatMap.EndBatchUpdate();
if (fontAndColorCacheManager.RefreshCache(ref _mefItemsGuid) != VSConstants.S_OK)
{
//TODO Log error here
}
fontAndColorStorage.CloseCategory();
}
}
void IDisposable.Dispose()
{
VSColorTheme.ThemeChanged -= VSColorTheme_ThemeChanged;
}
}
您可以通过检查代码编辑器的当前背景来确定是否需要使用适合浅色或深色主题的颜色。这是我使用的代码的链接:
https://github.com/Acumatica/Acuminator/blob/dev/src/Acuminator/Acuminator.Vsix/Coloriser/Constants/VSColors.cs#L82
这是来自@Alessandro 的更简洁的 sn-p(再次感谢!):
var colorBackground = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowBackgroundColorKey);
Color color = (colorBackground != null && colorBackground.B < 64) ? lightcolor : darkcolor;
您还可以创建一个单独的共享 ThemeUpdater 类,该类将订阅ThemeChanged 事件,并且所有ClassificationFormatDefinition 派生类都将订阅它,以对主题更改进行特定更改。这具有性能优势,您可以批量更新所有格式定义,并在主题更改时仅调用一次EndBatchUpdate 和RefreshCache/ClearCache。