├── .gitattributes ├── .gitignore ├── README.md ├── SolutionColor ├── Commands │ ├── EnableAutoPickColorCommand.cs │ ├── PickColorCommand.cs │ └── ResetColorCommand.cs ├── Key.snk ├── Resources │ ├── icon.png │ └── toolbarIcons.png ├── SettingsStore.cs ├── SolutionColor.args.json ├── SolutionColor.csproj ├── SolutionColorPackage.cs ├── SolutionColorPackage.vsct ├── SolutionListener.cs ├── TitleBarColorController.cs ├── VSPackage.resx ├── VSUtils.cs ├── license.txt ├── screenshot.png └── source.extension.vsixmanifest ├── dependencies ├── RandomColorGenerator.Forms.dll └── Readme.md ├── solutioncolor.sln ├── solutioncolor └── source.extension.vsixmanifest └── toolbarIcons.pdn /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SolutionColor 2 | 3 | Small Visual Studio extension that allows to choose a different title bar color for each solution. Useful if you often work with multiple Visual Studio instances simultaneously. 4 | 5 | ![Screenshot](SolutionColor/screenshot.png?raw=true) 6 | 7 | More information and download on the [Visual Studio Extension Gallery page](https://visualstudiogallery.msdn.microsoft.com/68d6015f-5818-4c06-ba3f-f471513899e5) -------------------------------------------------------------------------------- /SolutionColor/Commands/EnableAutoPickColorCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using Microsoft.VisualStudio.Shell; 4 | 5 | namespace SolutionColor 6 | { 7 | /// 8 | /// Command enable/disable automatic color picking. 9 | /// 10 | internal sealed class EnableAutoPickColorCommand 11 | { 12 | public const int CommandId = 0x0102; 13 | 14 | private SolutionColorPackage package; 15 | private MenuCommand menuItem; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// Adds our command handlers for menu (commands must exist in the command table file) 20 | /// 21 | /// Owner package, not null. 22 | private EnableAutoPickColorCommand(SolutionColorPackage package) 23 | { 24 | ThreadHelper.ThrowIfNotOnUIThread(); 25 | 26 | this.package = package; 27 | if (package == null) 28 | { 29 | throw new ArgumentNullException("package"); 30 | } 31 | 32 | OleMenuCommandService commandService = ((IServiceProvider)package).GetService(typeof(IMenuCommandService)) as OleMenuCommandService; 33 | if (commandService != null) 34 | { 35 | var menuCommandID = new CommandID(SolutionColorPackage.ToolbarCommandSetGuid, CommandId); 36 | menuItem = new MenuCommand(this.Execute, menuCommandID); 37 | commandService.AddCommand(menuItem); 38 | 39 | menuItem.Checked = package.Settings.IsAutomaticColorPickEnabled(); 40 | } 41 | } 42 | 43 | /// 44 | /// Gets the instance of the command. 45 | /// 46 | public static EnableAutoPickColorCommand Instance 47 | { 48 | get; 49 | private set; 50 | } 51 | 52 | /// 53 | /// Initializes the singleton instance of the command. 54 | /// 55 | /// Owner package, not null. 56 | public static void Initialize(SolutionColorPackage package) 57 | { 58 | Instance = new EnableAutoPickColorCommand(package); 59 | } 60 | 61 | private void Execute(object sender, EventArgs e) 62 | { 63 | ThreadHelper.ThrowIfNotOnUIThread(); 64 | menuItem.Checked = !menuItem.Checked; 65 | package.Settings.SetAutomaticColorPickEnabled(menuItem.Checked); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /SolutionColor/Commands/PickColorCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using Microsoft.VisualStudio.Shell; 4 | using System.Windows.Forms; 5 | 6 | namespace SolutionColor 7 | { 8 | /// 9 | /// Command to open color picker in order to choose a color for the titlebar. 10 | /// 11 | internal sealed class PickColorCommand 12 | { 13 | public const int CommandId = 0x0100; 14 | 15 | private SolutionColorPackage package; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// Adds our command handlers for menu (commands must exist in the command table file) 20 | /// 21 | /// Owner package, not null. 22 | private PickColorCommand(SolutionColorPackage package) 23 | { 24 | this.package = package; 25 | if (package == null) 26 | { 27 | throw new ArgumentNullException("package"); 28 | } 29 | 30 | OleMenuCommandService commandService = ((IServiceProvider)package).GetService(typeof(IMenuCommandService)) as OleMenuCommandService; 31 | if (commandService != null) 32 | { 33 | var menuCommandID = new CommandID(SolutionColorPackage.ToolbarCommandSetGuid, CommandId); 34 | var menuItem = new MenuCommand(this.Execute, menuCommandID); 35 | commandService.AddCommand(menuItem); 36 | } 37 | } 38 | 39 | /// 40 | /// Gets the instance of the command. 41 | /// 42 | public static PickColorCommand Instance 43 | { 44 | get; 45 | private set; 46 | } 47 | 48 | /// 49 | /// Initializes the singleton instance of the command. 50 | /// 51 | /// Owner package, not null. 52 | public static void Initialize(SolutionColorPackage package) 53 | { 54 | Instance = new PickColorCommand(package); 55 | } 56 | 57 | private void Execute(object sender, EventArgs e) 58 | { 59 | ThreadHelper.ThrowIfNotOnUIThread(); 60 | 61 | var dialog = new ColorDialog(); 62 | dialog.AllowFullOpen = true; 63 | dialog.Color = package.GetMainTitleBarColor(); 64 | dialog.CustomColors = package.Settings.GetCustomColorList(); 65 | 66 | if (dialog.ShowDialog() == DialogResult.OK) 67 | { 68 | package.SetTitleBarColor(dialog.Color); 69 | package.Settings.SaveOrOverwriteSolutionColor(VSUtils.GetCurrentSolutionPath(), dialog.Color); 70 | } 71 | 72 | package.Settings.SaveCustomColorList(dialog.CustomColors); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /SolutionColor/Commands/ResetColorCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using Microsoft.VisualStudio.Shell; 4 | 5 | namespace SolutionColor 6 | { 7 | /// 8 | /// Command to reset the title bar color. 9 | /// 10 | internal sealed class ResetColorCommand 11 | { 12 | public const int CommandId = 0x0101; 13 | 14 | private SolutionColorPackage package; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// Adds our command handlers for menu (commands must exist in the command table file) 19 | /// 20 | /// Owner package, not null. 21 | private ResetColorCommand(SolutionColorPackage package) 22 | { 23 | this.package = package; 24 | if (package == null) 25 | { 26 | throw new ArgumentNullException("package"); 27 | } 28 | 29 | OleMenuCommandService commandService = ((IServiceProvider)package).GetService(typeof(IMenuCommandService)) as OleMenuCommandService; 30 | if (commandService != null) 31 | { 32 | var menuCommandID = new CommandID(SolutionColorPackage.ToolbarCommandSetGuid, CommandId); 33 | var menuItem = new MenuCommand(this.Execute, menuCommandID); 34 | commandService.AddCommand(menuItem); 35 | } 36 | } 37 | 38 | /// 39 | /// Gets the instance of the command. 40 | /// 41 | public static ResetColorCommand Instance 42 | { 43 | get; 44 | private set; 45 | } 46 | 47 | /// 48 | /// Initializes the singleton instance of the command. 49 | /// 50 | /// Owner package, not null. 51 | public static void Initialize(SolutionColorPackage package) 52 | { 53 | Instance = new ResetColorCommand(package); 54 | } 55 | 56 | private void Execute(object sender, EventArgs e) 57 | { 58 | ThreadHelper.ThrowIfNotOnUIThread(); 59 | package.ResetTitleBarColor(); 60 | package.Settings.RemoveSolutionColorSetting(VSUtils.GetCurrentSolutionPath()); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SolutionColor/Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wumpf/VSSolutionColor/9831b02a7c1cb7b330683ceda06fe8e29079044e/SolutionColor/Key.snk -------------------------------------------------------------------------------- /SolutionColor/Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wumpf/VSSolutionColor/9831b02a7c1cb7b330683ceda06fe8e29079044e/SolutionColor/Resources/icon.png -------------------------------------------------------------------------------- /SolutionColor/Resources/toolbarIcons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wumpf/VSSolutionColor/9831b02a7c1cb7b330683ceda06fe8e29079044e/SolutionColor/Resources/toolbarIcons.png -------------------------------------------------------------------------------- /SolutionColor/SettingsStore.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Settings; 2 | using Microsoft.VisualStudio.Shell; 3 | using Microsoft.VisualStudio.Shell.Settings; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | namespace SolutionColor 8 | { 9 | public class SolutionColorSettingStore 10 | { 11 | private const string CollectionName = "SolutionColorSettings"; 12 | private const string AutomaticColorPickIdentifier = "AutomaticColorPick"; 13 | 14 | public SolutionColorSettingStore() 15 | { 16 | } 17 | 18 | public bool IsAutomaticColorPickEnabled() 19 | { 20 | ThreadHelper.ThrowIfNotOnUIThread(); 21 | 22 | var settingsStore = GetSettingsStore(); 23 | if (settingsStore.PropertyExists(CollectionName, AutomaticColorPickIdentifier)) 24 | return settingsStore.GetBoolean(CollectionName, AutomaticColorPickIdentifier); 25 | else 26 | return false; // off by default. 27 | } 28 | 29 | public void SetAutomaticColorPickEnabled(bool enabled) 30 | { 31 | ThreadHelper.ThrowIfNotOnUIThread(); 32 | 33 | var settingsStore = GetSettingsStore(); 34 | settingsStore.SetBoolean(CollectionName, AutomaticColorPickIdentifier, enabled); 35 | } 36 | 37 | public void SaveOrOverwriteSolutionColor(string solutionPath, System.Drawing.Color color) 38 | { 39 | ThreadHelper.ThrowIfNotOnUIThread(); 40 | 41 | if (string.IsNullOrEmpty(solutionPath)) return; 42 | solutionPath = Path.GetFullPath(solutionPath); 43 | 44 | byte[] colorBytes = { color.A, color.R, color.G, color.B }; 45 | 46 | var settingsStore = GetSettingsStore(); 47 | settingsStore.SetMemoryStream(CollectionName, solutionPath, new MemoryStream(colorBytes)); 48 | } 49 | 50 | public void RemoveSolutionColorSetting(string solutionPath) 51 | { 52 | ThreadHelper.ThrowIfNotOnUIThread(); 53 | 54 | if (string.IsNullOrEmpty(solutionPath)) return; 55 | solutionPath = Path.GetFullPath(solutionPath); 56 | 57 | var settingsStore = GetSettingsStore(); 58 | if (settingsStore.PropertyExists(CollectionName, solutionPath)) 59 | settingsStore.DeleteProperty(CollectionName, solutionPath); 60 | } 61 | 62 | /// 63 | /// Retrieves the color setting for a given solution. 64 | /// 65 | /// Path for the solution to check. 66 | /// Color we saved for the solution 67 | /// true if there was a color saved, false if not. 68 | public bool GetSolutionColorSetting(string solutionPath, out System.Drawing.Color color) 69 | { 70 | ThreadHelper.ThrowIfNotOnUIThread(); 71 | 72 | color = System.Drawing.Color.Black; 73 | 74 | if (string.IsNullOrEmpty(solutionPath)) return false; 75 | solutionPath = Path.GetFullPath(solutionPath); 76 | 77 | var settingsStore = GetSettingsStore(); 78 | if (settingsStore.PropertyExists(CollectionName, solutionPath)) 79 | { 80 | MemoryStream colorBytes = settingsStore.GetMemoryStream(CollectionName, solutionPath); 81 | color = System.Drawing.Color.FromArgb(colorBytes.ReadByte(), colorBytes.ReadByte(), colorBytes.ReadByte(), colorBytes.ReadByte()); 82 | return true; 83 | } 84 | else 85 | return false; 86 | } 87 | 88 | 89 | private const string CustomColorPaletteName = "CustomColorPalette"; 90 | 91 | public int[] GetCustomColorList() 92 | { 93 | ThreadHelper.ThrowIfNotOnUIThread(); 94 | 95 | var settingsStore = GetSettingsStore(); 96 | if (settingsStore.PropertyExists(CollectionName, CustomColorPaletteName)) 97 | { 98 | string customColorPaletteString = settingsStore.GetString(CollectionName, CustomColorPaletteName); 99 | return customColorPaletteString.Split(new char[]{ ' ' }, System.StringSplitOptions.RemoveEmptyEntries).Select(x => 100 | { 101 | // Can't be cautious enough when reading user string. 102 | int color = -1; 103 | if (!int.TryParse(x, out color)) 104 | return -1; 105 | else 106 | return color; 107 | }).ToArray(); 108 | } 109 | else 110 | { 111 | return new int[0]; 112 | } 113 | } 114 | 115 | public void SaveCustomColorList(int[] colorList) 116 | { 117 | ThreadHelper.ThrowIfNotOnUIThread(); 118 | 119 | // Save as string since it is easy and save. 120 | // Alternative would be memorystream like we do with the color per solution path. 121 | // Then however, we'd need to save the count separately [...] 122 | var settingsStore = GetSettingsStore(); 123 | settingsStore.SetString(CollectionName, CustomColorPaletteName, colorList.Aggregate(string.Empty, (s, i) => s + " " + i.ToString())); 124 | } 125 | 126 | private WritableSettingsStore GetSettingsStore() 127 | { 128 | ThreadHelper.ThrowIfNotOnUIThread(); 129 | var settingsManager = new ShellSettingsManager(ServiceProvider.GlobalProvider); 130 | WritableSettingsStore settingsStore = settingsManager.GetWritableSettingsStore(SettingsScope.UserSettings); 131 | 132 | // Ensure our settings collection exists. 133 | if (!settingsStore.CollectionExists(CollectionName)) 134 | settingsStore.CreateCollection(CollectionName); 135 | 136 | return settingsStore; 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /SolutionColor/SolutionColor.args.json: -------------------------------------------------------------------------------- 1 | { 2 | "DataCollection": [ 3 | { 4 | "Id": "535670a5-880d-40db-aa99-208a55d4ecd5", 5 | "Command": "/rootsuffix Exp" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /SolutionColor/SolutionColor.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 16.0 6 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 7 | 8 | 9 | true 10 | Program 11 | $(DevEnvDir)\devenv.exe 12 | /rootsuffix Exp 13 | 14 | 15 | 16 | 17 | 14.0 18 | 19 | 20 | 21 | true 22 | 23 | 24 | Key.snk 25 | 26 | 27 | 28 | Debug 29 | AnyCPU 30 | 2.0 31 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 32 | {43D6AD8C-3B27-4B6F-AF3F-4C9B9E70DDAA} 33 | Library 34 | Properties 35 | SolutionColor 36 | SolutionColor 37 | v4.6 38 | true 39 | true 40 | true 41 | true 42 | true 43 | false 44 | 45 | 46 | true 47 | full 48 | false 49 | bin\Debug\ 50 | DEBUG;TRACE 51 | prompt 52 | 4 53 | x86 54 | 55 | 56 | pdbonly 57 | true 58 | bin\Release\ 59 | TRACE 60 | prompt 61 | 4 62 | x86 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | Designer 78 | 79 | 80 | 81 | 82 | true 83 | 84 | 85 | true 86 | Always 87 | 88 | 89 | 90 | true 91 | 92 | 93 | Menus.ctmenu 94 | 95 | 96 | 97 | 98 | False 99 | 100 | 101 | True 102 | 103 | 104 | 105 | 106 | 107 | 108 | True 109 | 110 | 111 | True 112 | 113 | 114 | 115 | 116 | 117 | 118 | ..\dependencies\RandomColorGenerator.Forms.dll 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 14.3.25407 130 | 131 | 132 | 133 | 134 | 135 | true 136 | VSPackage 137 | Designer 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 156 | -------------------------------------------------------------------------------- /SolutionColor/SolutionColorPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Automation; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Runtime.InteropServices; 6 | using Microsoft.VisualStudio.Shell; 7 | using System.Collections.Generic; 8 | using System.Windows.Interop; 9 | using System.Linq; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using Task = System.Threading.Tasks.Task; 13 | 14 | namespace SolutionColor 15 | { 16 | /// 17 | /// This is the class that implements the package exposed by this assembly. 18 | /// 19 | /// 20 | /// 21 | /// The minimum requirement for a class to be considered a valid package for Visual Studio 22 | /// is to implement the IVsPackage interface and register itself with the shell. 23 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF) 24 | /// to do it: it derives from the Package class that provides the implementation of the 25 | /// IVsPackage interface and uses the registration attributes defined in the framework to 26 | /// register itself and its components with the shell. These attributes tell the pkgdef creation 27 | /// utility what data to put into .pkgdef file. 28 | /// 29 | /// 30 | /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file. 31 | /// 32 | /// 33 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 34 | [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] // Info on this package for Help/About 35 | [ProvideMenuResource("Menus.ctmenu", 1)] 36 | [Guid(SolutionColorPackage.PackageGuidString)] 37 | [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")] 38 | [ProvideAutoLoad(Microsoft.VisualStudio.VSConstants.UICONTEXT.NoSolution_string, PackageAutoLoadFlags.BackgroundLoad)] 39 | public sealed class SolutionColorPackage : AsyncPackage 40 | { 41 | public const string PackageGuidString = "8fa74b3d-8744-465c-b06e-a719e1d63ddf"; 42 | 43 | public static readonly Guid ToolbarCommandSetGuid = new Guid("00d80876-3407-4666-bf62-7262028ea83b"); 44 | 45 | public SolutionColorSettingStore Settings { get; private set; } = new SolutionColorSettingStore(); 46 | private EnvDTE.WindowEvents windowEvents; 47 | 48 | 49 | /// 50 | /// Store process id, since we use this on a very regular basis (whenever any windows opens anywhere...) and we don't want to do GetCurrentProcess every time. 51 | /// 52 | private readonly int currentProcessId = System.Diagnostics.Process.GetCurrentProcess().Id; 53 | 54 | private Dictionary windowTitleBarController = new Dictionary(); 55 | 56 | /// 57 | /// Listener to opened solutions. Sets title bar color settings in effect if any. 58 | /// 59 | private class SolutionOpenListener : SolutionListener 60 | { 61 | private SolutionColorPackage package; 62 | 63 | public SolutionOpenListener(SolutionColorPackage package) : base(package) 64 | { 65 | this.package = package; 66 | } 67 | 68 | /// 69 | /// IVsSolutionEvents3.OnAfterOpenSolution is called BEFORE a solution is fully loaded. 70 | /// This is different from EnvDTE.Events.SolutionEvents.Opened which is loaded after a resolution was loaded due to historical reasons: 71 | /// In earlier Visual Studio versions there was no asynchronous loading, so a solution was either fully loaded or not at all. 72 | /// 73 | public override int OnAfterOpenSolution(object pUnkReserved, int fNewSolution) 74 | { 75 | ThreadHelper.ThrowIfNotOnUIThread(); 76 | 77 | // Check if we already saved something for this solution. 78 | string solutionPath = VSUtils.GetCurrentSolutionPath(); 79 | System.Drawing.Color color; 80 | if (package.Settings.GetSolutionColorSetting(solutionPath, out color)) 81 | package.SetTitleBarColor(color); 82 | else if (package.Settings.IsAutomaticColorPickEnabled()) 83 | { 84 | color = RandomColorGenerator.RandomColor.GetColor(RandomColorGenerator.ColorScheme.Random, VSUtils.IsUsingDarkTheme() ? RandomColorGenerator.Luminosity.Dark : RandomColorGenerator.Luminosity.Light); 85 | package.SetTitleBarColor(color); 86 | package.Settings.SaveOrOverwriteSolutionColor(solutionPath, color); 87 | } 88 | 89 | return 0; 90 | } 91 | 92 | public override int OnAfterCloseSolution(object pUnkReserved) 93 | { 94 | package.ResetTitleBarColor(); 95 | return 0; 96 | } 97 | } 98 | 99 | private SolutionListener listener; 100 | 101 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 102 | { 103 | await base.InitializeAsync(cancellationToken, progress); 104 | 105 | await JoinableTaskFactory.SwitchToMainThreadAsync(); // Command constructors need to be on main thread. 106 | PickColorCommand.Initialize(this); 107 | ResetColorCommand.Initialize(this); 108 | EnableAutoPickColorCommand.Initialize(this); 109 | 110 | listener = new SolutionOpenListener(this); 111 | 112 | await UpdateTitleBarControllerListAsync(); 113 | 114 | // Use window event to find removed/added windows. 115 | // TODO: Doesn't always reliably catch undocking events. 116 | var dte = VSUtils.GetDTE(); 117 | windowEvents = dte.Events.WindowEvents; 118 | windowEvents.WindowCreated += async (Window) => await UpdateTitleBarControllerListAsync(); 119 | windowEvents.WindowClosing += async (Window) => await UpdateTitleBarControllerListAsync(); 120 | windowEvents.WindowActivated += async (a, b) => await UpdateTitleBarControllerListAsync(); 121 | } 122 | 123 | protected override void Dispose(bool disposing) 124 | { 125 | ThreadHelper.ThrowIfNotOnUIThread(); 126 | if (disposing) 127 | listener.Dispose(); 128 | base.Dispose(disposing); 129 | } 130 | 131 | private async Task UpdateTitleBarControllerListAsync() 132 | { 133 | await JoinableTaskFactory.SwitchToMainThreadAsync(); 134 | 135 | Window[] windows = new Window[Application.Current.Windows.Count]; 136 | Application.Current.Windows.CopyTo(windows, 0); 137 | 138 | // Destroy old window controller. 139 | windowTitleBarController = windowTitleBarController.Where(x => windows.Contains(x.Key)) 140 | .ToDictionary(x=>x.Key, x=>x.Value); 141 | 142 | // Go through new windows. 143 | foreach (Window window in windows.Where(w => !windowTitleBarController.ContainsKey(w))) 144 | { 145 | var newController = TitleBarColorController.CreateFromWindow(window); 146 | if (newController != null) 147 | { 148 | windowTitleBarController.Add(window, newController); 149 | 150 | // Check if we already saved something for this solution. 151 | // Do this in here since we call UpdateTitleBarControllerList fairly regularly and in the most cases won't have any new controllers. 152 | if (Settings.GetSolutionColorSetting(VSUtils.GetCurrentSolutionPath(), out var color)) 153 | newController.SetTitleBarColor(color); 154 | } 155 | } 156 | } 157 | 158 | #region Color Manipulation 159 | 160 | public void SetTitleBarColor(System.Drawing.Color color) 161 | { 162 | foreach (var bar in windowTitleBarController) 163 | bar.Value.SetTitleBarColor(color); 164 | } 165 | 166 | public void ResetTitleBarColor() 167 | { 168 | foreach(var bar in windowTitleBarController) 169 | bar.Value.ResetTitleBarColor(); 170 | } 171 | 172 | public System.Drawing.Color GetMainTitleBarColor() 173 | { 174 | TitleBarColorController titleBar; 175 | if (windowTitleBarController.TryGetValue(Application.Current.MainWindow, out titleBar)) 176 | return titleBar.TryGetTitleBarColor(); 177 | else 178 | return System.Drawing.Color.Black; 179 | } 180 | 181 | #endregion 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /SolutionColor/SolutionColorPackage.vsct: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | DefaultDocked 17 | 18 | Solution Color 19 | Solution Color 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 38 | 39 | 46 | 47 | 54 | 55 | 56 | 57 | 58 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /SolutionColor/SolutionListener.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio; 2 | using Microsoft.VisualStudio.Shell; 3 | using Microsoft.VisualStudio.Shell.Interop; 4 | using System; 5 | 6 | namespace SolutionColor 7 | { 8 | /// 9 | /// Abstract listener for solution events. 10 | /// This is much more powerful than EnvDTE.Events.SolutionEvents! 11 | /// 12 | /// More events, intentionally not implemented: IVsSolutionEvents4, IVsSolutionEvents, IVsSolutionEvents2 13 | /// 14 | abstract public class SolutionListener : IVsSolutionEvents3, IDisposable 15 | { 16 | private IVsSolution solutionService; 17 | private uint eventsCookie = (uint)Constants.VSCOOKIE_NIL; 18 | private bool isDisposed; 19 | 20 | protected SolutionListener(IServiceProvider serviceProvider) 21 | { 22 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 23 | 24 | solutionService = serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution; 25 | if (solutionService == null) 26 | { 27 | throw new InvalidOperationException(); 28 | } 29 | 30 | ErrorHandler.ThrowOnFailure(solutionService.AdviseSolutionEvents(this, out eventsCookie)); 31 | } 32 | 33 | public void Dispose() 34 | { 35 | ThreadHelper.ThrowIfNotOnUIThread(); 36 | Dispose(true); 37 | GC.SuppressFinalize(this); 38 | } 39 | 40 | protected virtual void Dispose(bool disposing) 41 | { 42 | ThreadHelper.ThrowIfNotOnUIThread(); 43 | 44 | if (!isDisposed) 45 | { 46 | if (disposing && solutionService != null && eventsCookie != (uint)Constants.VSCOOKIE_NIL) 47 | { 48 | ErrorHandler.ThrowOnFailure(solutionService.UnadviseSolutionEvents(eventsCookie)); 49 | eventsCookie = (uint)Constants.VSCOOKIE_NIL; 50 | } 51 | isDisposed = true; 52 | } 53 | } 54 | 55 | #region Event Impls 56 | 57 | public virtual int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) 58 | { 59 | return 0; 60 | } 61 | 62 | public virtual int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) 63 | { 64 | return 0; 65 | } 66 | 67 | public virtual int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) 68 | { 69 | return 0; 70 | } 71 | 72 | public virtual int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) 73 | { 74 | return 0; 75 | } 76 | 77 | public virtual int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) 78 | { 79 | return 0; 80 | } 81 | 82 | public virtual int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) 83 | { 84 | return 0; 85 | } 86 | 87 | public virtual int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel) 88 | { 89 | return 0; 90 | } 91 | 92 | public virtual int OnBeforeCloseSolution(object pUnkReserved) 93 | { 94 | return 0; 95 | } 96 | 97 | public virtual int OnAfterCloseSolution(object pUnkReserved) 98 | { 99 | return 0; 100 | } 101 | 102 | public virtual int OnAfterMergeSolution(object pUnkReserved) 103 | { 104 | return 0; 105 | } 106 | 107 | public virtual int OnBeforeOpeningChildren(IVsHierarchy pHierarchy) 108 | { 109 | return 0; 110 | } 111 | 112 | public virtual int OnAfterOpeningChildren(IVsHierarchy pHierarchy) 113 | { 114 | return 0; 115 | } 116 | 117 | public virtual int OnBeforeClosingChildren(IVsHierarchy pHierarchy) 118 | { 119 | return 0; 120 | } 121 | 122 | public virtual int OnAfterClosingChildren(IVsHierarchy pHierarchy) 123 | { 124 | return 0; 125 | } 126 | 127 | public virtual int OnAfterOpenSolution(object pUnkReserved, int fNewSolution) 128 | { 129 | return 0; 130 | } 131 | 132 | #endregion 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /SolutionColor/TitleBarColorController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Media; 4 | using System.Windows.Controls; 5 | 6 | namespace SolutionColor 7 | { 8 | /// 9 | /// All the important magic to manipulate the main window's title bar happens here. 10 | /// 11 | public class TitleBarColorController 12 | { 13 | private DependencyObject titleBarContainer = null; 14 | private DependencyObject mainMenuControl = null; 15 | private DependencyObject mainMenuItemsWrapperControl = null; 16 | private TextBlock titleBarTextBox = null; 17 | 18 | private object defaultBackgroundValue = null; 19 | private Brush defaultTextForeground = null; 20 | private object defaultMenuBackgroundValue = null; 21 | private Style defaultMenuItemStyle = null; 22 | private const string ColorPropertyName = "Background"; 23 | private const string ForegroundPropertyName = "Foreground"; 24 | 25 | private TitleBarColorController() 26 | { 27 | } 28 | 29 | /// 30 | /// Uses knowledge of the VS window structure to retrieve pointers to the titlebar and its text element. 31 | /// 32 | static public TitleBarColorController CreateFromWindow(Window window) 33 | { 34 | TitleBarColorController newController = new TitleBarColorController(); 35 | try 36 | { 37 | // Apply knowledge of basic Visual Studio 2015/2017/2019 window structure. 38 | 39 | if (window == Application.Current.MainWindow) 40 | { 41 | var windowContentPresenter = VisualTreeHelper.GetChild(window, 0); 42 | var rootGrid = VisualTreeHelper.GetChild(windowContentPresenter, 0); 43 | 44 | newController.titleBarContainer = VisualTreeHelper.GetChild(rootGrid, 0); 45 | 46 | // Note that this part doesn't work for the VS2019 main windows as there is simply no title text like this. 47 | // However docked-out code windows are just like in previous versions. 48 | try 49 | { 50 | var dockPanel = VisualTreeHelper.GetChild(newController.titleBarContainer, 0); 51 | newController.titleBarTextBox = VisualTreeHelper.GetChild(dockPanel, 3) as TextBlock; 52 | } 53 | catch 54 | { 55 | // We can do without the text box! 56 | } 57 | 58 | // In VS2019+ the main menu has been integrated with the title bar. 59 | // We can set the opacity to 0 and color the text as we did/do with the title text. 60 | newController.mainMenuControl = GetDecendantFirstInLine(newController.titleBarContainer, 6); 61 | if (newController.mainMenuControl != null) 62 | { 63 | newController.mainMenuItemsWrapperControl = GetDecendantFirstInLine(newController.mainMenuControl, 3); 64 | if (newController.mainMenuItemsWrapperControl is MenuItem) // Nestedness of the layout changed a bit over different versions; 65 | newController.mainMenuItemsWrapperControl = GetDecendantFirstInLine(newController.mainMenuControl, 2); 66 | 67 | System.Reflection.PropertyInfo propertyInfo = newController.mainMenuControl.GetType().GetProperty(ColorPropertyName); 68 | newController.defaultMenuBackgroundValue = propertyInfo.GetValue(newController.mainMenuControl); 69 | if (newController.mainMenuItemsWrapperControl != null && VisualTreeHelper.GetChildrenCount(newController.mainMenuItemsWrapperControl) > 0) 70 | { 71 | var child = VisualTreeHelper.GetChild(newController.mainMenuItemsWrapperControl, 0); 72 | newController.defaultMenuItemStyle = child.GetType().GetProperty("Style")?.GetValue(child) as Style; 73 | } 74 | } 75 | } 76 | else 77 | { 78 | var windowContentPresenter = VisualTreeHelper.GetChild(window, 0); 79 | var rootGrid = VisualTreeHelper.GetChild(windowContentPresenter, 0); 80 | var rootDockPanel = VisualTreeHelper.GetChild(rootGrid, 0); 81 | var titleBarContainer = VisualTreeHelper.GetChild(rootDockPanel, 0); 82 | var titleBar = VisualTreeHelper.GetChild(titleBarContainer, 0); 83 | var border = VisualTreeHelper.GetChild(titleBar, 0); 84 | var contentPresenter = VisualTreeHelper.GetChild(border, 0); 85 | var grid = VisualTreeHelper.GetChild(contentPresenter, 0); 86 | 87 | newController.titleBarContainer = grid; 88 | 89 | newController.titleBarTextBox = VisualTreeHelper.GetChild(grid, 1) as TextBlock; 90 | } 91 | 92 | if (newController.titleBarContainer != null) 93 | { 94 | System.Reflection.PropertyInfo propertyInfo = newController.titleBarContainer.GetType().GetProperty(ColorPropertyName); 95 | newController.defaultBackgroundValue = propertyInfo.GetValue(newController.titleBarContainer); 96 | } 97 | 98 | if (newController.titleBarTextBox != null) 99 | newController.defaultTextForeground = newController.titleBarTextBox.Foreground; 100 | } 101 | catch 102 | { 103 | return null; 104 | } 105 | 106 | if (newController.titleBarContainer == null) 107 | return null; 108 | 109 | return newController; 110 | } 111 | 112 | /// 113 | /// Tries to set a given color to the titlebar. Will color the text either black or white dependingon the color's brightness. 114 | /// Opens message box if something goes wrong. 115 | /// 116 | public void SetTitleBarColor(System.Drawing.Color color) 117 | { 118 | try 119 | { 120 | float luminance = 0.299f * color.R + 0.587f * color.G + 0.114f * color.B; 121 | var textColor = (luminance > 128.0f) ? Color.FromRgb(0, 0, 0) : Color.FromRgb(255, 255, 255); 122 | var textBrush = new SolidColorBrush(textColor); 123 | 124 | if (titleBarContainer != null) 125 | { 126 | System.Reflection.PropertyInfo propertyInfo = titleBarContainer.GetType().GetProperty(ColorPropertyName); 127 | propertyInfo.SetValue(titleBarContainer, new SolidColorBrush(Color.FromArgb(color.A, color.R, color.G, color.B)), null); 128 | } 129 | 130 | if (titleBarTextBox != null) 131 | { 132 | titleBarTextBox.Foreground = textBrush; 133 | } 134 | 135 | if (mainMenuControl != null) 136 | { 137 | System.Reflection.PropertyInfo propertyInfo = mainMenuControl.GetType().GetProperty(ColorPropertyName); 138 | propertyInfo.SetValue(mainMenuControl, new SolidColorBrush(Colors.Transparent)); 139 | } 140 | 141 | if (mainMenuItemsWrapperControl != null) 142 | { 143 | var newMenuItemStyle = CreateNewMenuItemStyle(mainMenuItemsWrapperControl, textBrush); 144 | if (newMenuItemStyle != null) 145 | { 146 | ApplyStyleOnAllChildren(mainMenuItemsWrapperControl, newMenuItemStyle); 147 | } 148 | } 149 | } 150 | catch (Exception e) 151 | { 152 | MessageBox.Show("Failed to set the color of the title bar:\n" + e.ToString(), "Failed to set Titlebar Color"); 153 | } 154 | } 155 | 156 | /// 157 | /// Resets titlebar (and text) color to the default that was saved in the first successful UpdateWidgetPointer() call. 158 | /// 159 | public void ResetTitleBarColor() 160 | { 161 | try 162 | { 163 | if (titleBarContainer != null) 164 | { 165 | System.Reflection.PropertyInfo propertyInfo = titleBarContainer.GetType().GetProperty(ColorPropertyName); 166 | propertyInfo.SetValue(titleBarContainer, defaultBackgroundValue, null); 167 | } 168 | 169 | if (titleBarTextBox != null) 170 | { 171 | titleBarTextBox.Foreground = defaultTextForeground; 172 | } 173 | 174 | if (mainMenuControl != null) 175 | { 176 | System.Reflection.PropertyInfo propertyInfo = mainMenuControl.GetType().GetProperty(ColorPropertyName); 177 | propertyInfo.SetValue(mainMenuControl, defaultMenuBackgroundValue); 178 | } 179 | 180 | if (mainMenuItemsWrapperControl != null) 181 | { 182 | ApplyStyleOnAllChildren(mainMenuItemsWrapperControl, defaultMenuItemStyle); 183 | } 184 | } 185 | catch (Exception e) 186 | { 187 | MessageBox.Show("Failed to reset the color of the title bar:\n" + e.ToString(), "Failed to reset Titlebar Color"); 188 | } 189 | } 190 | 191 | /// 192 | /// Tries to retrieve the color of the title bar. Falls back to black if it fails. 193 | /// 194 | public System.Drawing.Color TryGetTitleBarColor() 195 | { 196 | try 197 | { 198 | System.Reflection.PropertyInfo propertyInfo = titleBarContainer.GetType().GetProperty(ColorPropertyName); 199 | var colorBrush = propertyInfo.GetValue(titleBarContainer) as SolidColorBrush; 200 | if (colorBrush != null) 201 | { 202 | return System.Drawing.Color.FromArgb(colorBrush.Color.A, colorBrush.Color.R, colorBrush.Color.G, colorBrush.Color.B); 203 | } 204 | else 205 | return System.Drawing.Color.Black; 206 | } 207 | catch (Exception e) 208 | { 209 | MessageBox.Show("Failed to get the color of the title bar:\n" + e.ToString(), "Failed to get Titlebar Color"); 210 | } 211 | 212 | return System.Drawing.Color.Black; 213 | } 214 | 215 | /// 216 | /// Creates a new Style with the supplied based on the Style of the first Child object of 217 | /// 218 | /// 219 | /// 220 | /// 221 | private Style CreateNewMenuItemStyle(DependencyObject menuItemWrapper, SolidColorBrush newTextBrush) 222 | { 223 | if (defaultMenuItemStyle == null) 224 | return null; 225 | 226 | var newStyle = new Style(defaultMenuItemStyle.TargetType, defaultMenuItemStyle); 227 | foreach (var setter in defaultMenuItemStyle.Setters) 228 | { 229 | if ((setter is Setter) && ((setter as Setter).Property.ToString() == ForegroundPropertyName)) 230 | { 231 | newStyle.Setters.Remove(setter); 232 | newStyle.Setters.Add(new Setter((setter as Setter).Property, newTextBrush)); 233 | } 234 | } 235 | return newStyle; 236 | } 237 | 238 | /// 239 | /// Applies the to all children of 240 | /// 241 | /// The object that has all the relevant MenuItems as children 242 | /// The new style to apply to all children 243 | private void ApplyStyleOnAllChildren(DependencyObject menuItemWrapper, Style styleToApply) 244 | { 245 | if (menuItemWrapper == null) 246 | throw new ArgumentNullException(nameof(menuItemWrapper)); 247 | 248 | for (int i = 0; i < VisualTreeHelper.GetChildrenCount(menuItemWrapper); i++) 249 | { 250 | var menuItem = VisualTreeHelper.GetChild(menuItemWrapper, i) as MenuItem; 251 | if (menuItem != null) 252 | { 253 | menuItem.Style = styleToApply; 254 | } 255 | } 256 | } 257 | 258 | /// 259 | /// Call VisualTreeHelper.GetChild(ref, 0) multiple times, to get a first-in-line decendant x levels deep. 260 | /// 261 | /// The parent visual to get the decendant of 262 | /// The amount of levels deep (if 1 is passed, this method behaves as GetChild). 263 | static private DependencyObject GetDecendantFirstInLine(DependencyObject reference, int levelsDeep) 264 | { 265 | while ((reference != null) && (levelsDeep > 0)) 266 | { 267 | if (VisualTreeHelper.GetChildrenCount(reference) < 1) 268 | return null; 269 | reference = VisualTreeHelper.GetChild(reference, 0); 270 | levelsDeep--; 271 | } 272 | return reference; 273 | } 274 | 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /SolutionColor/VSPackage.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Solution Color Extension 122 | 123 | 124 | Pick a different titlebar color depending on the current sln file you're working on. 125 | 126 | 127 | -------------------------------------------------------------------------------- /SolutionColor/VSUtils.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | 3 | namespace SolutionColor 4 | { 5 | /// 6 | /// A few VS extension utils. Basically shortcuts to commonly functionality. 7 | /// 8 | internal static class VSUtils 9 | { 10 | public static EnvDTE80.DTE2 GetDTE() 11 | { 12 | var dte = Package.GetGlobalService(typeof(EnvDTE.DTE)) as EnvDTE80.DTE2; 13 | if (dte == null) 14 | { 15 | throw new System.Exception("Failed to retrieve DTE2!"); 16 | } 17 | return dte; 18 | } 19 | 20 | public static string GetCurrentSolutionPath() 21 | { 22 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 23 | return GetDTE().Solution.FileName; 24 | } 25 | 26 | public static bool IsUsingDarkTheme() 27 | { 28 | // Probe the current theme. 29 | // Inspired by https://github.com/Irdis/VSTalk/blob/2d49471422a42513ac84179c27472ca6d9112047/trunk/VSTalk/VSTalk.Extension/Integration/ThemeManager.cs#L131 30 | uint colorSample = GetDTE().GetThemeColor(EnvDTE80.vsThemeColors.vsThemeColorToolboxBackground); 31 | int colorSum = (byte)(colorSample >> 16) + (byte)(colorSample >> 8) + (byte)(colorSample >> 0); // rgb 32 | return colorSum < 128 * 3; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SolutionColor/license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2019 Andreas Reich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /SolutionColor/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wumpf/VSSolutionColor/9831b02a7c1cb7b330683ceda06fe8e29079044e/SolutionColor/screenshot.png -------------------------------------------------------------------------------- /SolutionColor/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | SolutionColor 6 | Pick a different titlebar color depending on the current sln file you're working on. 7 | license.txt 8 | Resources\icon.png 9 | screenshot.png 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /dependencies/RandomColorGenerator.Forms.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wumpf/VSSolutionColor/9831b02a7c1cb7b330683ceda06fe8e29079044e/dependencies/RandomColorGenerator.Forms.dll -------------------------------------------------------------------------------- /dependencies/Readme.md: -------------------------------------------------------------------------------- 1 | # RandomColorGeneratorSharped 2 | A small library for pleasing random colors by Nathan P Jones 3 | [Github Link](https://github.com/nathanpjones/randomColorSharped/) 4 | 5 | To use it inside a Visual Studio extension I needed to sign the assembly with my key, which is why I couldn't rely on the NuGet package. 6 | 7 | 8 | 9 | 10 | 11 | The MIT License (MIT) 12 | 13 | Copyright (c) 2014 Nathan P Jones 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. -------------------------------------------------------------------------------- /solutioncolor.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.25920.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SolutionColor", "solutioncolor\SolutionColor.csproj", "{43D6AD8C-3B27-4B6F-AF3F-4C9B9E70DDAA}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {43D6AD8C-3B27-4B6F-AF3F-4C9B9E70DDAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {43D6AD8C-3B27-4B6F-AF3F-4C9B9E70DDAA}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {43D6AD8C-3B27-4B6F-AF3F-4C9B9E70DDAA}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {43D6AD8C-3B27-4B6F-AF3F-4C9B9E70DDAA}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /solutioncolor/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | SolutionColor 6 | Pick a different titlebar color depending on the current sln file you're working on. 7 | license.txt 8 | Resources\icon.png 9 | screenshot.png 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /toolbarIcons.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wumpf/VSSolutionColor/9831b02a7c1cb7b330683ceda06fe8e29079044e/toolbarIcons.pdn --------------------------------------------------------------------------------