Jetpack Compose - Room database not able to read from flow on release build - android-room

I'm performing an action on an item and using the copy function to update its value in room database. It works as intended in debug version (the text changes color/value) but in release build it doesn't. It still works, I just don't see it reflects right away. If I navigate away from tab and back I can see the state updates. Why it doesn't behave the same on release build? How would you go by to debug this?
//updating in ViewModel
repository.updateIsDisconnected(devices.copy(isDisconnected = true))
//getting * from db
val getItems = vm.getItems.collectAsState(initial = emptyList())
//Presenting Lazycolumn
LazyColumn() {
items(getItems.value) { item ->
Row(){
//Text that is suppose to be updated
if (item.isDisconnected) Text(text = "Disconnected", color = Color.Green)
else Text(text = "Found", color = Color.Gray)
}
}
}
I'm suspecting it has to do with ProGuard and I've added the following but still the same:
-keep class * extends androidx.room.RoomDatabase
-keep #androidx.room.Entity class *
-dontwarn androidx.room.paging.**

Related

How to tell if the active document is a text document?

I'm developing a Visual Studio extension in which one of the implemented commands needs to be available only when the active document is a text document (like e.g. the Visual Studio's "Toggle Bookmark" does). The problem is that I can't figure out how to tell when that's the case.
Right now I have a half working solution. In the package's Initialize method I subscribe to DTE's WindowActivated event, and then whenever a window is activated I check if the window DocumentData property is of type TextDocument:
protected override void Initialize()
{
base.Initialize();
var dte = Package.GetGlobalService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
dte.Events.WindowEvents.WindowActivated += WindowEventsOnWindowActivated;
//More initialization here...
}
//This is checked from command's BeforeQueryStatus
public bool ActiveDocumentIsText { get; private set; } = false;
private void WindowEventsOnWindowActivated(Window gotFocus, Window lostFocus)
{
if (gotFocus.Kind != "Document")
return; //It's not a document (e.g. it's a tool window)
TextDocument textDoc = gotFocus.DocumentData as TextDocument;
ActiveDocumentIsText = textDoc != null;
}
The problem with this approach is that 1) Window.DocumentData is documented as ".NET Framework internal use only", and 2) this gives a false positive when a document that has both a code view and a design view (e.g. a .visxmanifest file) is open in design mode.
I have tried to use IVsTextManager.GetActiveView as well, but this is returning the last active text view opened - so if I open a .txt file and then a .png file, it returns data for the .txt file even if it's not the active document anymore.
So, how do I check if the active document is a text document, or the code view of a document that can have a designer... and if possible, not using "undocumented" classes/members?
UPDATE: I found a slightly better solution. Inside the window activated handler:
ActiveDocumentIsText = gotFocus.Document.Object("TextDocument") != null;
At least this one is properly documented, but I still have the problem of false positives with designers.
I finally got it. It's somewhat tricky, but it works and is 100% "legal". Here's the recipe:
1- Make the package class implement IVsRunningDocTableEvents. Make all the methods just return VSConstants.S_OK;
2- Add the following field and the following auxiliary method to the package class:
private IVsRunningDocumentTable runningDocumentTable;
private bool DocIsOpenInLogicalView(string path, Guid logicalView, out IVsWindowFrame windowFrame)
{
return VsShellUtilities.IsDocumentOpen(
this,
path,
VSConstants.LOGVIEWID_TextView,
out var dummyHierarchy2, out var dummyItemId2,
out windowFrame);
}
3- Add the following to the Initialize method of the package class:
runningDocumentTable = GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
runningDocumentTable.AdviseRunningDocTableEvents(this, out var dummyCookie);
4- Don't blink, here comes the magic! Implement the IVsRunningDocTableEvents.OnBeforeDocumentWindowShow method as follows:
public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame)
{
runningDocumentTable.GetDocumentInfo(docCookie,
out var dummyFlags, out var dummyReadLocks, out var dummyEditLocks,
out string path,
out var dummyHierarchy, out var dummyItemId, out var dummyData);
IVsWindowFrame windowFrameForTextView;
var docIsOpenInTextView =
DocIsOpenInLogicalView(path, VSConstants.LOGVIEWID_Code, out windowFrameForTextView) ||
DocIsOpenInLogicalView(path, VSConstants.LOGVIEWID_TextView, out windowFrameForTextView);
//Is the document open in the code/text view,
//AND the window for that view is the one that has been just activated?
ActiveDocumentIsText = docIsOpenInTextView && pFrame == logicalViewWindowFrame;
return VSConstants.S_OK;
}

Create Visual Studio Theme Specific Syntax Highlighting

I would like to create a Syntax Highlighter in Visual Studio 2012 (and above) that supports different themes (Dark, Light, Blue).
Visual Studio's Editor Classifier project template explains how to create your own colors in the environment using Microsoft.VisualStudio.Text.Classification.ClassificationFormatDefinition. It works fine...
... until you realize that there are different themes in Visual Studio 2012 (and above) and you don't really support them. Your pretty dark blue colored identifiers on the light theme becomes unreadable in a dark themed environment.
To my understanding if you change your ClassificationFormatDefinition in the Tools/Options/Fonts & Colors in a given theme (e.g.: Light) it won't affect the same ClassificationFormatDefinition in a different theme (e.g.: Dark). The colors seem to be independent across different themes.
That is good. But how do I achieve defining the same ClassificationFormatDefinition (e.g.: MyKeywords) that has the same name in all the themes, but provides different colors for them? Just like Visual Studio's own "Identifier", which is default black on the Light theme and default while on the Black theme.
I know about the Microsoft.VisualStudio.PlatformUI.VSColorTheme.ThemeChanged event that allows me to get notified when the color themes are changed. Do I have to use this and somehow get hold of my existing ClassificationFormatDefinition and assign new colors to them based on the new theme? But that also pops a question: will these modified colors be persisted to the environment, i.e. if I restart Visual Studio, will my changes be there the next time in all the different themes.
I haven't found any attribute that would state which theme the ClassificationFormatDefinition supports nor found much helpful article on the subject.
Any help appreciated.
Ok, here's a workaround I've found. It is far from perfect, but it is as good as it gets.
The trick is to use another base definition when you define your own classification type. This will use their default color for the different themes. The important thing is that you must not define your own color in MyKeywordsFormatDefinition because that disables the default behavior when switching between themes. So try to find a base definition that matches your color. Look for predefined Classificatoin Types here: Microsoft.VisualStudio.Language.StandardClassification.PredefinedClassificationTypeNames
internal static class Classifications
{
// ...
public const string MyKeyword = "MyKeyword";
// ...
}
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = Classifications.MyKeyword)]
[Name("MyKeywords")]
[DisplayName("My Keywords")]
[UserVisible(true)]
internal sealed class MyKeywordsFormatDefinition: ClassificationFormatDefinition
{
// Don't set the color here, as it will disable the default color supporting themes
}
[Export(typeof(ClassificationTypeDefinition))]
[Name(Classifications.MyKeyword)]
[BaseDefinition(PredefinedClassificationTypeNames.Keyword)]
internal static ClassificationTypeDefinition MyKeywordsTypeDefinition;
I hope it will be useful for some of you. Even maybe help to refine a proper solution when you can actually set your own color without reusing existing color definitions.
This might help you, code from F# Power Tools, seems to be listening to the ThemeChanged event and updating the classifiers - https://github.com/fsprojects/VisualFSharpPowerTools/blob/a7d7aa9dd3d2a90f21c6947867ac7d7163b9f99a/src/FSharpVSPowerTools/SyntaxConstructClassifierProvider.cs
There's another, cleaner way using the VsixColorCompiler that ships with the VS SDK.
First, create a ClassificationTypeDefinition and ClassificationFormatDefinition as usual. This will define the default colour in all themes:
public static class MyClassifications
{
public const string CustomThing = "MyClassifications/CustomThing";
[Export]
[Name(CustomThing)]
public static ClassificationTypeDefinition CustomThingType = null;
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = CustomThing)]
[UserVisible(true)] // Note: must be user-visible to be themed!
[Name(CustomThing)]
public sealed class CustomThingFormatDefinition : ClassificationFormatDefinition
{
public CustomThingFormatDefinition()
{
ForegroundColor = Color.FromRgb(0xFF, 0x22, 0x22); // default colour in all themes
DisplayName = "Custom Thing"; // appears in Fonts and Colors options
}
}
}
Next, create a colours.xml file. This will allow us to override the colour for specific themes:
<!-- Syntax described here: https://learn.microsoft.com/en-us/visualstudio/extensibility/internals/vsix-color-compiler -->
<Themes>
<Theme Name="Light" GUID="{de3dbbcd-f642-433c-8353-8f1df4370aba}">
</Theme>
<Theme Name="Dark" GUID="{1ded0138-47ce-435e-84ef-9ec1f439b749}">
<!-- MEF colour overrides for dark theme -->
<Category Name="MEFColours" GUID="{75A05685-00A8-4DED-BAE5-E7A50BFA929A}">
<Color Name="MyClassifications/CustomThing">
<Foreground Type="CT_RAW" Source="FF2222FF" />
</Color>
</Category>
</Theme>
</Themes>
Now edit your .csproj to include a post-build command to compile the XML to a .pkgdef next to your normal package's .pkgdef (VS2015 SDK shown here):
<Target Name="AfterBuild">
<Message Text="Compiling themed colours..." Importance="high" />
<Exec Command=""$(VSSDK140Install)\VisualStudioIntegration\Tools\Bin\VsixColorCompiler.exe" /noLogo "$(ProjectDir)colours.xml" "$(OutputPath)\MyPackage.Colours.pkgdef"" />
</Target>
Whenever you make a change, be sure to clear the MEF cache between builds to force it to update. Additionally, the following registry keys may need to be deleted as well:
HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\14.0\FontAndColors\Cache\{75A05685-00A8-4DED-BAE5-E7A50BFA929A}
HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\14.0Exp\FontAndColors\Cache\{75A05685-00A8-4DED-BAE5-E7A50BFA929A}
I had a similar problem. I've developed a syntax highlighter for the DSL at work. It has two sets of colors - for light and dark themes. I needed a way to switch between these two sets of colors at runtime when VS theme changes.
After some search I found a solution in the F# github in the code responsible for the integration with VS:
https://github.com/dotnet/fsharp/blob/main/vsintegration/src/FSharp.Editor/Classification/ClassificationDefinitions.fs#L121
The code in F# repo is quite similar to the code from Omer Raviv’s answer. I translated it into C# and get something like this:
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;
}
}
I’ve used the class above as the base class for all my ClassificationFormatDefinition classes.
EDIT: After upgrade to AsyncPackage for newer versions of VS the previous code stopped working. You need to declare MEF imports somewhere else, for example, directly in the inheritor of ClassificationFormatDefinition. Moreover, as was pointed out by #Alessandro there is a subtle bug in the code. If you switch the VS theme and then immediately go to the VS settings "Fonts and colors" section you will see that the default colors values didn't change. They will change after the restart of VS but that's still not ideal. Fortunately, there is a solution (thanks again #Alessandro). You need to call IVsFontAndColorCacheManager's either ClearCache or RefreshCache with a correct guid 75A05685-00A8-4DED-BAE5-E7A50BFA929A which corresponds to MefItems category in Fonts and Colors cache in the registry.
Here is a reference to an article which describes this a bit:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.interop.ivsfontandcolorcachemanager?view=visualstudiosdk-2019
Unfortunately I can't find any documentation for the guid constant.
UPDATE: After some more research, debugging and adding logging of bad error codes to VS Activity Log I found out the following:
Theme changed handler is called multiple times for a single change
of VS theme
ClearCache returns 0 for the first few calls but after that starts returning bad error codes
RefreshCache always return 0 (at least in my case)
Therefore I replaced the call to ClearCache with the call to RefreshCache.
So, here is an updated example:
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;
}
}
You can determine if you need to use a color suited for the light or dark theme by checking the current background of the code editor. Here is the link to the code I use:
https://github.com/Acumatica/Acuminator/blob/dev/src/Acuminator/Acuminator.Vsix/Coloriser/Constants/VSColors.cs#L82
And here is a more concise snippet from #Alessandro (thanks again!):
var colorBackground = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowBackgroundColorKey);
Color color = (colorBackground != null && colorBackground.B < 64) ? lightcolor : darkcolor;
You may also create a separate shared ThemeUpdater class which will subscribe to the ThemeChanged event and all ClassificationFormatDefinition derived classes will subscribe to it to make their specific changes on the theme change. This has a performance benefit that you can update all format definitions in a batch and call EndBatchUpdate and RefreshCache/ClearCache only once on theme change.
For Visual Studio 2022, the answer by SENya only works partially or "sometimes": Changing the VS theme will not properly change the colors immediately like 10% of the time. Also, changing the theme from dark to light will quite often appear to work, but after restarting Visual Studio the dark instead of the light colors get used quite often (more than half the time). All of this is non-deterministic.
After some debugging, I understood the problem as follows: Calling IVsFontAndColorCacheManager.ClearCache() deletes the registry key
"Software\Microsoft\VisualStudio\17.0_4d51a943Exp\FontAndColors\Cache\{75A05685-00A8-4DED-BAE5-E7A50BFA929A}\ItemAndFontInfo"
which is the cache of the font and colors. After the custom theme-change function finishes, some other Visual Studio component
sometimes (but not always) immediately updates the font and color cache. I.e. it calls something like fontAndColorStorage.OpenCategory(ref mFontAndColorCategoryGUID, (uint)__FCSTORAGEFLAGS.FCSF_READONLY | (uint)__FCSTORAGEFLAGS.FCSF_LOADDEFAULTS).
Note the FCSF_LOADDEFAULTS. This causes Visual Studio to re-create the registry key. However, apparently it does not use the colors from the updated IClassificationFormatMap, but instead the colors set on the ClassificationFormatDefinition itself,
which were not updated. Thus, changing the theme, changes the displayed colors immediately (because the IClassificationFormatMap got updated), but the registry cache ends up with the wrong colors. After a restart of VS, it uses the cached values and therefore ends up with the wrong colors.
By changing the colors also on the ClassificationFormatDefinition instances, the issue appears to be fixed.
Details
In my VSDoxyHighlighter (Github) I adapted the answer by SENya as follows:
First, some helper class to store the default text format:
public class TextProperties
{
public readonly Color? Foreground;
public readonly Color? Background;
public readonly bool IsBold;
public readonly bool IsItalic;
public TextProperties(Color? foreground, Color? background, bool isBold, bool isItalic)
{
Foreground = foreground;
Background = background;
IsBold = isBold;
IsItalic = isItalic;
}
}
Then the main class handling the theme stuff, called DefaultColors:
/// <summary>
/// Manages the default colors and formatting of our classifications, suitable for the current Visual Studio's color theme.
/// Thus, it provides access to the default formatting for the current theme, and also updates them if the theme
/// of Visual Studio is changed by the user.
///
/// Note that the user settings are stored per theme in the registry.
///
/// An instance should be created via MEF.
/// </summary>
[Export]
public class DefaultColors : IDisposable
{
DefaultColors()
{
VSColorTheme.ThemeChanged += VSThemeChanged;
mCurrentTheme = GetCurrentTheme();
}
public void Dispose()
{
if (mDisposed) {
return;
}
mDisposed = true;
VSColorTheme.ThemeChanged -= VSThemeChanged;
}
/// <summary>
/// Returns the default colors for our extension's classifications, as suitable for the current color theme.
/// </summary>
public Dictionary<string, TextProperties> GetDefaultFormattingForCurrentTheme()
{
return GetDefaultFormattingForTheme(mCurrentTheme);
}
public void RegisterFormatDefinition(IFormatDefinition f)
{
mFormatDefinitions.Add(f);
}
private enum Theme
{
Light,
Dark
}
static private Dictionary<string, TextProperties> GetDefaultFormattingForTheme(Theme theme)
{
switch (theme) {
case Theme.Light:
return cLightColors;
case Theme.Dark:
return cDarkColors;
default:
throw new System.Exception("Unknown Theme");
}
}
// Event called by Visual Studio multiple times (!) when the user changes the color theme of Visual Studio.
private void VSThemeChanged(ThemeChangedEventArgs e)
{
ThreadHelper.ThrowIfNotOnUIThread();
var newTheme = GetCurrentTheme();
if (newTheme != mCurrentTheme) {
mCurrentTheme = newTheme; // Important: We indirectly access mCurrentTheme during the update, so set it before.
ThemeChangedImpl();
}
}
// Called when the Visual Studio theme changes. Responsible for switching out the default colors
// of the classifications.
//
// Based on:
// - https://stackoverflow.com/a/48993958/3740047
// - https://github.com/dotnet/fsharp/blob/main/vsintegration/src/FSharp.Editor/Classification/ClassificationDefinitions.fs#L133
// - https://github.com/fsprojects-archive/zzarchive-VisualFSharpPowerTools/blob/master/src/FSharpVSPowerTools/Commands/SymbolClassifiersProvider.cs
private void ThemeChangedImpl()
{
ThreadHelper.ThrowIfNotOnUIThread();
var fontAndColorStorage = ServiceProvider.GlobalProvider.GetService<SVsFontAndColorStorage, IVsFontAndColorStorage>();
var fontAndColorCacheManager = ServiceProvider.GlobalProvider.GetService<SVsFontAndColorCacheManager, IVsFontAndColorCacheManager>();
fontAndColorCacheManager.CheckCache(ref mFontAndColorCategoryGUID, out int _);
if (fontAndColorStorage.OpenCategory(ref mFontAndColorCategoryGUID, (uint)__FCSTORAGEFLAGS.FCSF_READONLY) != VSConstants.S_OK) {
throw new System.Exception("Failed to open font and color registry.");
}
IClassificationFormatMap formatMap = mClassificationFormatMapService.GetClassificationFormatMap(category: "text");
try {
formatMap.BeginBatchUpdate();
ColorableItemInfo[] colorInfo = new ColorableItemInfo[1];
foreach (var p in GetDefaultFormattingForTheme(mCurrentTheme)) {
string classificationTypeId = p.Key;
TextProperties newColor = p.Value;
if (fontAndColorStorage.GetItem(classificationTypeId, colorInfo) != VSConstants.S_OK) { //comment from F# repo: "we don't touch the changes made by the user"
IClassificationType classificationType = mClassificationTypeRegistryService.GetClassificationType(classificationTypeId);
var oldProp = formatMap.GetTextProperties(classificationType);
var oldTypeface = oldProp.Typeface;
var foregroundBrush = newColor.Foreground == null ? null : new SolidColorBrush(newColor.Foreground.Value);
var backgroundBrush = newColor.Background == null ? null : new SolidColorBrush(newColor.Background.Value);
var newFontStyle = newColor.IsItalic ? FontStyles.Italic : FontStyles.Normal;
var newWeight = newColor.IsBold ? FontWeights.Bold : FontWeights.Normal;
var newTypeface = new Typeface(oldTypeface.FontFamily, newFontStyle, newWeight, oldTypeface.Stretch);
var newProp = TextFormattingRunProperties.CreateTextFormattingRunProperties(
foregroundBrush, backgroundBrush, newTypeface, null, null,
oldProp.TextDecorations, oldProp.TextEffects, oldProp.CultureInfo);
formatMap.SetTextProperties(classificationType, newProp);
}
}
// Also update all of our ClassificationFormatDefinition values with the new values.
// Without this, changing the theme does not reliably update the colors: Sometimes after restarting VS, we get
// the wrong colors. For example, when switching from the dark to the light theme, we often end up with the colors
// of the dark theme after a VS restart.
// From what I could understand: The call fontAndColorCacheManager.ClearCache() below deletes the registry key
// "Software\Microsoft\VisualStudio\17.0_4d51a943Exp\FontAndColors\Cache\{75A05685-00A8-4DED-BAE5-E7A50BFA929A}\ItemAndFontInfo"
// which is the cache of the font and colors. After our function here finishes, some Visual Studio component
// sometimes (but not always) immediately updates the font and color cache. I.e. it calls something like
// fontAndColorStorage.OpenCategory(ref mFontAndColorCategoryGUID, (uint)__FCSTORAGEFLAGS.FCSF_READONLY | (uint)__FCSTORAGEFLAGS.FCSF_LOADDEFAULTS).
// Note the "FCSF_LOADDEFAULTS". This causes Visual Studio to re-create the registry key. However, apparently
// it does not use the colors from the updated formatMap, but instead the colors set on the ClassificationFormatDefinition,
// which were not yet updated so far. Thus, changing the theme, changes the displayed colors immediately (because we update
// the formatMap), but the registry cache ends up with the wrong colors. After a restart of VS, it uses the cached values
// and therefore we get the wrong colors.
// By changing the colors also on the ClassificationFormatDefinition, the issue appears to be fixed.
foreach (IFormatDefinition f in mFormatDefinitions) {
f.Reinitialize();
}
}
finally {
formatMap.EndBatchUpdate();
fontAndColorStorage.CloseCategory();
if (fontAndColorCacheManager.ClearCache(ref mFontAndColorCategoryGUID) != VSConstants.S_OK) {
throw new System.Exception("Failed to clear cache of FontAndColorCacheManager.");
}
}
}
private Theme GetCurrentTheme()
{
// We need to figure out if our extension should choose the default colors suitable for light or dark themes.
// In principle we could explicitly retrieve the color theme currently active in Visual Studio. However, that
// approach is fundamentally flawed: We could check if the theme is one of the default ones (dark, light, blue,
// etc.), but Visual Studio supports installing additional themes. It is impossible to know all themes existing
// out there. So, what we really want is to check if the dark or the light defaults are more suitable given the
// text editor's background color.
// However, the EnvironmentColors does not seem to contain an element for the text editor's background. So we
// simply use the tool windows' background, as suggested also here: https://stackoverflow.com/a/48993958/3740047
// The simplistic heuristic of just checking the blue color seems to work reasonably well. The magic threshold
// was chosen to (hopefully) select the better value for the themes shown at https://devblogs.microsoft.com/visualstudio/custom-themes/
var referenceColor = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowBackgroundColorKey);
return (referenceColor != null && referenceColor.B < 100) ? Theme.Dark : Theme.Light;
}
// Default colors for light color themes.
static readonly Dictionary<string, TextProperties> cLightColors = new Dictionary<string, TextProperties> {
{ IDs.ID_command, new TextProperties(foreground: Color.FromRgb(0, 75, 0), background: null, isBold: true, isItalic: false) },
{ IDs.ID_parameter1, new TextProperties(foreground: Color.FromRgb(0, 80, 218), background: null, isBold: true, isItalic: false) },
// ... further custom classifications
};
// Default colors for dark color themes.
static readonly Dictionary<string, TextProperties> cDarkColors = new Dictionary<string, TextProperties> {
{ IDs.ID_command, new TextProperties(foreground: Color.FromRgb(140, 203, 128), background: null, isBold: true, isItalic: false) },
{ IDs.ID_parameter1, new TextProperties(foreground: Color.FromRgb(86, 156, 214), background: null, isBold: true, isItalic: false) },
// ... further custom classifications
};
private Theme mCurrentTheme;
// GUID of the category in which our classification items are placed (i.e. of the elements in the
// fonts and colors settings of Visual Studio). Not just ours but all sorts of other items exist
// in this category, too.
// Can be found by installing our extension, modifying some of the colors of the classifications in
// the Visual Studio's settings dialog, then exporting the settings and checking the resulting file.
// The section about the modified entries contains the proper GUID.
private const string cFontAndColorCategory = "75A05685-00A8-4DED-BAE5-E7A50BFA929A";
Guid mFontAndColorCategoryGUID = new Guid(cFontAndColorCategory);
[Import]
private IClassificationFormatMapService mClassificationFormatMapService = null;
[Import]
private IClassificationTypeRegistryService mClassificationTypeRegistryService = null;
private List<IFormatDefinition> mFormatDefinitions = new List<IFormatDefinition>();
private bool mDisposed = false;
}
A few points to notice here:
An instance of DefaultColors should not be created by hand, but rather only a single instance should get created by MEF (e.g. via an Import attribute). See below.
The current VS theme is identified by checking some currently active background color, as in this answer. It is in principle possible to check whether the light, blue, blue (high contrast), dark, etc. VS themes are active. However, since the user can install additional themes, the list is endless. Thus, simply checking the background color is more generic and robust.
The ClassificationFormatDefinition definitions (which represent the text format used by Visual Studio for the various classifications) are expected to register themselves on the DefaultColors instance via RegisterFormatDefinition().
To get notified about a theme change of Visual Studio, we subscribe to VSColorTheme.ThemeChanged. Also note that the event gets fired multiple times per theme change. Since it unnecessary to execute all the update code in ThemeChangedImpl() multiple times, we check whether the new and old themes are different.
The reaction to the theme change is in ThemeChangedImpl(). This is the code that is mainly based on the answer by SENya, but with the addition that the ClassificationFormatDefinitions registered previously via RegisterFormatDefinition() get a call to Reinitialize().
For completeness sake, ID_command and ID_parameter1 are some self defined identifiers that are used to identify the ClassificationFormatDefinitions (see below):
/// <summary>
/// Identifiers for the classifications. E.g., Visual Studio will use these strings as keys
/// to store the classification's configuration in the registry.
/// </summary>
public static class IDs
{
public const string ID_command = "VSDoxyHighlighter_Command";
public const string ID_parameter1 = "VSDoxyHighlighter_Parameter1";
// ... further IDs for further classifications
}
Now, the actual ClassificationFormatDefinitions are defined like this:
They inherit from an interface IFormatDefinition (which can be passed to the DefaultColors.RegisterFormatDefinition() function)
public interface IFormatDefinition
{
void Reinitialize();
}
All ClassificationFormatDefinitions are mostly the same: They set the text properties (color, bold, italic, etc) that are appropriate for the current color theme upon construction. This is done by querying the DefaultColors.GetDefaultFormattingForCurrentTheme() function. Moreover, they register themselves on the DefaultColors and implement the Reinitialize() method (which is called by DefaultColors). Since it is always the same, I define a base class FormatDefinitionBase for them:
internal abstract class FormatDefinitionBase : ClassificationFormatDefinition, IFormatDefinition
{
protected FormatDefinitionBase(DefaultColors defaultColors, string ID, string displayName)
{
if (defaultColors == null) {
throw new System.ArgumentNullException("VSDoxyHighlighter: The 'DefaultColors' to a FormatDefinition is null");
}
mID = ID;
mDefaultColors = defaultColors;
mDefaultColors.RegisterFormatDefinition(this);
DisplayName = displayName;
Reinitialize();
}
public virtual void Reinitialize()
{
TextProperties color = mDefaultColors.GetDefaultFormattingForCurrentTheme()[mID];
ForegroundColor = color.Foreground;
BackgroundColor = color.Background;
IsBold = color.IsBold;
IsItalic = color.IsItalic;
}
protected readonly DefaultColors mDefaultColors;
protected readonly string mID;
}
Finally, the actual definitions look like this:
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = IDs.ID_command)]
[Name(IDs.ID_command)]
[UserVisible(true)]
[Order(After = /*Whatever is appropriate for your extension*/)]
internal sealed class CommandFormat : FormatDefinitionBase
{
[ImportingConstructor]
public CommandFormat(DefaultColors defaultColors)
: base(defaultColors, IDs.ID_command, "VSDoxyHighlighter - Command")
{
}
}
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = IDs.ID_parameter1)]
[Name(IDs.ID_parameter1)]
[UserVisible(true)]
[Order(After = /*Whatever is appropriate for your extension*/)]
internal sealed class ParameterFormat1 : FormatDefinitionBase
{
[ImportingConstructor]
public ParameterFormat1(DefaultColors defaultColors)
: base(defaultColors, IDs.ID_parameter1, "VSDoxyHighlighter - Parameter 1")
{
}
}
//... Further format definitions
Notice that the constructor is marked as ImportingConstructor, so that MEF automatically create a single instance of the DefaultColors class and passes it to the constructors.
So, to summarize:
The ClassificationFormatDefinition gets created by MEF. At the same time, MEF also creates an instance of DefaultColors and passes it to the ClassificationFormatDefinition. The ClassificationFormatDefinition sets the default colors and provides a function to allow it to get reinitialized upon theme change. To make this possible, it also registers itself on the DefaultColors instance.
DefaultColors figures out the current theme and contains the default colors for each theme.
DefaultColors listens for the VSColorTheme.ThemeChanged event, and if fired, clears the Visual Studio's font and color cache, updates the current classification format map (to display the new colors) and also updates all custom ClassificationFormatDefinition instances with the new colors (so that upon recreation of the font and color cache by VS the correct colors are used for the cache).

JavaFX: Prevent selection of a different tab if the data validation of the selected tab fails

I'm creating a CRUD application that store data in a local h2 DB. I'm pretty new to JavaFX. I've created a TabPane to with 3 Tab using an jfxml created with Scene Builder 2.0. Each Tab contains an AncorPane that wrap all the controls: Label, EditText, and more. Both the TabPane and the Tabs are managed using one controller. This function is used to create and to update the data. It's called from a grid that display all the data. A pretty basic CRUD app.
I'm stuck in the validation phase: when the user change the tab, by selecting another tab, it's called a validation method of the corresponding tab. If the validation of the Tab fails, I want that the selection remains on this tab.
To achieve this I've implemented the following ChangeListener on the SelectionModel of my TabPane:
boolean processingTabValidationOnChange = false;
tabPane.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) {
if (processingTabValidationOnChange == false) {
boolean success;
switch (t.intValue()) {
case 0: success = validationTab1Passed();
break;
case 1: success = validationTab2Passed();
break;
case 1: success = validationTab3Passed();
break;
default: success = false;
}
if (success == false) {
processingTabValidationOnChange = true;
// select the previous tab
tabPane.getSelectionModel().select(t.intValue());
processingTabValidationOnChange = false;
}
}
}
});
I'm not sure that this is the right approach because:
The event changed is fired two times, one for the user selection and one for the .select(t.intValue()). To avoid this I've used a global field boolean processingTabValidationOnChange... pretty dirty I know.
After the .select(t.intValue()) the TabPane displays the correctly Tab as selected but the content of the tab is empty as if the AnchorPane was hidden. I cannot select again the tab that contains the errors because it's already selected.
Any help would be appreciated.
Elvis
I would approach this very differently. Instead of waiting for the user to select a different tab, and reverting if the contents of the current tab are invalid, prevent the user from changing tabs in the first place.
The Tab class has a disableProperty. If it is set to true, the tab cannot be selected.
Define a BooleanProperty or BooleanBinding representing whether or not the data in the first tab is invalid. You can create such bindings based on the state of the controls in the tab. Then bind the second tab's disableProperty to it. That way the second tab automatically becomes disabled or enabled as the data in the first tab becomes valid or invalid.
You can extend this to as many tabs as you need, binding their properties as the logic dictates.
Here's a simple example.
Update: The example linked above is a bit less simple now. It will dynamically change the colors of the text fields depending on whether the field is valid or not, with validation rules defined by bindings in the controller. Additionally, there are titled panes at the top of each page, with a title showing the number of validation errors on the page, and a list of messages when the titled pane is expanded. All this is dynamically bound to the values in the controls, so it gives constant, clear, yet unobtrusive feedback to the user.
As I commented to the James's answer, I was looking for a clean solution to the approach that I've asked. In short, to prevent the user to change to a different tab when the validation of the current tab fails. I proposed a solution implementing the ChangeListener but, as I explained: it's not very "clean" and (small detail) it doesn't work!
Ok, the problem was that the code used to switch back the previous tab:
tabPane.getSelectionModel().select(t.intValue());
is called before the process of switching of the tab itself it's completed, so it ends up selected... but hidden.
To prevent this I've used Platform.runLater(). The code .select() is executed after the change of tab. The full code becomes:
//global field, to prevent validation on .select(t.intValue());
boolean skipValidationOnTabChange = false;
tabPane.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) {
if (skipValidationOnTabChange == false) {
boolean success;
switch (t.intValue()) {
case 0:
success = validationTab1Passed();
break;
case 1:
success = validationTab2Passed();
break;
case 1:
success = validationTab3Passed();
break;
default:
success = false;
}
if (success == false) {
Platform.runLater(new Runnable() {
#Override
public void run() {
skipValidationOnTabChange = true;
tabPane.getSelectionModel().select(t.intValue());
skipValidationOnTabChange = false;
}
});
}
}
}
});
Anyway, if anyone has a better solution to accomplish this, you're welcome. In example using a method like consume() to prevent the tab to be selected two times. This way I can eliminated the global field skipValidationOnTabChange.
Elvis
I needed to achieve the similar thing. I've done this by changing the com.sun.javafx.scene.control.behavior.TabPaneBehaviour class by overriding selectTab method:
class ValidatingTabPaneBehavior extends TabPaneBehavior {
//constructors etc...
#Override
public void selectTab(Tab tab) {
try {
Tab current = getControl().getSelectionModel().getSelectedItem();
if (current instanceof ValidatingTab) {
((ValidatingTab) current).validate();
}
//this is the method we want to prevent from running in case of error in validation
super.selectTab(tab);
}catch (ValidationException ex) {
//show alert or do nothing tab won't be changed
}
}
});
The ValidatingTab is my own extension to Tab:
public class ValidatingTab extends Tab {
public void validate() throws ValidationException {
//validation
}
}
This is the "clean part" of the trick. Now we need to place ValidatingTabPaneBehavior into TabPane.
First you need to copy (!) the whole com.sun.javafx.scene.control.skin.TabPaneSkin to the new class in order to change its constructor. It is quite long class, so here is only the part when I switch the Behavior class:
public class ValidationTabPaneSkin extends BehaviorSkinBase<TabPane, TabPaneBehavior> {
//copied private fields
public ValidationTabPaneSkin(TabPane tabPane) {
super(tabPane, new ValidationTabPaneBehavior(tabPane));
//the rest of the copied constructor
}
The last thing is to change the skin in your tabPane instance:
tabPane.setSkin(new ValidationTabPaneSkin(tabPane));

how to disable T4 template auto-run in visual studio (2012)?

I have some T4 templates in my project. Whenever I make changes and save the tt file, it auto update the generated files. This is a template that loops all tables in a database and generates about 100+ files. So visual studio hangs for a few seconds every time I save my template and this is annoying. Is there a way to disable to "auto-refresh" function and I can manually run the template through the context menu.
Thanks!
You could delete TextTemplatingFileGenerator under "Custom Tool" in the file's Properties while you are editing it, and then put it back when you are finished.
I had a similiar issue. I found a quick work around by creating a ttinclude file (actually this was already a standard include file containing utility functions for my templates) and including it in all of my T4 templates. Then I simply created a compiler error in the include file. Thus when the generator attempted to run it would simply fail on the compile. Then when I'm ready to actually generate, I get rid of the offending code and then generate.
e.g. To cause a failure:
<#+
#
#>
To disable the failure:
<#+
//#
#>
You can also use this trick in the T4 template itself if you just want to disable the one you're working on.
Hopefully future VS versions will allow you to simply disable the auto-transform.
Since the TT is always executed (still), I found a different way to control the output when the TT is executed.
/********SET THIS TO REGENERATE THE FILE (OR NOT) ********/
var _RegenerateFile = true;
/********COS VS ALWAYS REGENERATES ON SAVE ***************/
// Also, T4VSHostProcess.exe may lock files.
// Kill it from task manager if you get "cannot copy file in use by another process"
var _CurrentFolder = new FileInfo(Host.ResolvePath(Host.TemplateFile)).DirectoryName;
var _AssemblyLoadFolder = Path.Combine(_CurrentFolder, "bin\\Debug");
Directory.SetCurrentDirectory(_CurrentFolder);
Debug.WriteLine($"Using working folder {_CurrentFolder}");
if (_RegenerateFile == false)
{
Debug.WriteLine($"Not Regenerating File");
var existingFileName = Path.ChangeExtension(Host.TemplateFile, "cs");
var fileContent = File.ReadAllText(existingFileName);
return fileContent;
}
Debug.WriteLine($"Regenerating File"); //put the rest of your usual template
Another way (what I eventually settled on) is based on reading a conditional compilation symbol that sets a property on one of the the classes that is providing the data for the T4. This gives the benefit of skipping all that preparation (and IDE lag) unless you add the REGEN_CODE_FILES conditional compilation symbol. (I guess this could also be made into a new solution configuration too. yes, this does work and removes the need for the class change below)
An example of the class i am calling in the same assembly..
public class MetadataProvider
{
public bool RegenCodeFile { get; set; }
public MetadataProvider()
{
#if REGEN_CODE_FILES
RegenCodeFile = true; //try to get this to set the property
#endif
if (RegenCodeFile == false)
{
return;
}
//code that does some degree of preparation and c...
}
}
In the TT file...
var _MetaProvider = new MetadataProvider();
var _RegenerateFile = _MetaProvider.RegenCodeFile;
// T4VSHostProcess.exe may lock files.
// Kill it from task manager if you get "cannot copy file in use by another process"
var _CurrentFolder = new FileInfo(Host.ResolvePath(Host.TemplateFile)).DirectoryName;
var _AssemblyLoadFolder = Path.Combine(_CurrentFolder, "bin\\Debug");
Directory.SetCurrentDirectory(_CurrentFolder);
Debug.WriteLine($"Using working folder {_CurrentFolder}");
if (_RegenerateFile == false)
{
Debug.WriteLine($"Not Regenerating File");
var existingFileName = Path.ChangeExtension(Host.TemplateFile, "cs");
var fileContent = File.ReadAllText(existingFileName);
return fileContent;
}
Debug.WriteLine($"Regenerating File");

Is there a standard way to check for updates in a Dashboard widget?

I'm writing a Dashboard widget in Dashcode, and I'd like to add some sort of check-for-updates functionality. I already looked into Sparkle, but AFAICT it's not applicable to widgets like this. Is there a commonly-used library to do update checking, or will I have to develop my own system?
I only need a very simple setup... automatically checking for new versions would be a plus, but if the user had to click a button in order to check that would be OK with me.
Inasmuch as "is there a function that will..." then i have not come across it.
What i did was as follows
In the plist there is the version of the widget and you put the number in there, lets say 1.0. Which you should be able to access and use. (see code) For reason i didn't and added this global var widget_version = "1.4"; and then updated that when the widget updated.
Then on a server accessible by the web you create a php (or whatever) file that has the number of current version of the widget. Again lets say 1.1.
Then you write a javascript function than will check this current widget version against the server version and display a graphic or message to tell the user. It is best to let the user decide if they want to upgrade rather than making it automatic.
Following is the code i used. Please copy and or hack as you wish.
function getSoftwareUpdate() {
// so use the built in CURL to do a REST call n.b. in widget preference you will need to check 'allow network access'
var softwareUpdate = widget.system("/usr/bin/curl 'http://api.yourserver.com/widget/wfccupdate.php'", null).outputString;
//alert(softwareUpdate); // tells you the function has been called
//alert("the update number from the REST " + softwareUpdate); // for debugging will show the key
// in main.js add this line
// var widget_version = "1.4"; // this is changed when you update the widget code for new release
// yes it's a global variable and bad but i was in a hurry
// the following line should get the widget number but for some reason i didn't do it
// localVersion = widget.preferenceForKey(preferenceForKey);
//alert("the internal preference key " + widget_version);
// then check to see if they match
if(softwareUpdate == widget_version)
{ hide_update('softwareupdate')
}
else
{show_update('softwareupdate')
}
}
function hide_update(el) { // hide the update graphic
if(document.getElementById(el))
{
if(document.getElementById(el).style.display != "none")
document.getElementById(el).style.display = "none";
}
}
function show_update(el) { // show the update graphic
if(document.getElementById(el)) {
if(document.getElementById(el).style.display == "none")
document.getElementById(el).style.display = "block";
}
}
// this is the php that is called by curl and acts as REST
<?php
// data
$UPDATE_database = <<<_UPDATE_
<?xml version="1.0" encoding="utf-8" ?>
<update>
<widgetversion>1.1</widgetversion>
</update>
_UPDATE_;
// load data
$xml = simplexml_load_string($UPDATE_database);
$result = $xml->xpath("widgetversion");
print $result[0];
?>
Hope this helps

Resources