diff --git a/Directory.Build.props b/Directory.Build.props index a93d14d..a716e92 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 2.7.0 + 2.8.0 net48 enable latest diff --git a/source/ProjectFilter/Commands/FilterProjectsCommand.cs b/source/ProjectFilter/Commands/FilterProjectsCommand.cs index aef685a..3007d9a 100644 --- a/source/ProjectFilter/Commands/FilterProjectsCommand.cs +++ b/source/ProjectFilter/Commands/FilterProjectsCommand.cs @@ -3,7 +3,6 @@ using Microsoft.VisualStudio.Text.PatternMatching; using ProjectFilter.Services; using ProjectFilter.UI; -using ProjectFilter.UI.Utilities; using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -31,20 +30,23 @@ protected async override Task ExecuteAsync(OleMenuCmdEventArgs e) { } - private async Task GetOptionsAsync() { + private static async Task GetOptionsAsync() { IHierarchyProvider hierarchyProvider; TextFilterFactory textFilterFactory; Func>> hierarchyFactory; IExtensionSettings globalSettings; ISolutionSettingsManager solutionSettingsManager; SolutionSettings? solutionSettings; + FilterDialogViewModel vm; + FilterDialog dialog; + bool result; await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); hierarchyProvider = await VS.GetRequiredServiceAsync(); textFilterFactory = await CreateTextFilterFactoryAsync(); - hierarchyFactory = async () => await hierarchyProvider.GetHierarchyAsync(); + hierarchyFactory = async () => await hierarchyProvider.GetHierarchyAsync(true); globalSettings = await VS.GetRequiredServiceAsync(); await globalSettings.LoadAsync(); @@ -52,49 +54,55 @@ protected async override Task ExecuteAsync(OleMenuCmdEventArgs e) { solutionSettingsManager = await VS.GetRequiredServiceAsync(); solutionSettings = await solutionSettingsManager.GetSettingsAsync(); - using (var vm = new FilterDialogViewModel(hierarchyFactory, Debouncer.Create, textFilterFactory, solutionSettings?.Nodes, Package.JoinableTaskFactory)) { - FilterDialog dialog; - bool result; - - + vm = new FilterDialogViewModel(hierarchyFactory, textFilterFactory, solutionSettings?.Nodes) { // Initialize the settings with the settings that were last // used for this solution. If this solution hasn't been // filtered before, then we'll use the global settings. - vm.LoadProjectDependencies = solutionSettings?.LoadProjectDependencies ?? globalSettings.LoadProjectDependencies; - vm.UseRegularExpressions = solutionSettings?.UseRegularExpressions ?? globalSettings.UseRegularExpressions; - vm.ExpandLoadedProjects = solutionSettings?.ExpandLoadedProjects ?? globalSettings.ExpandLoadedProjects; - vm.HideSolutionFoldersWithoutLoadedProjects = solutionSettings?.HideSolutionFoldersWithoutLoadedProjects ?? globalSettings.HideSolutionFoldersWithoutLoadedProjects; - - dialog = new FilterDialog { - DataContext = vm - }; - - result = dialog.ShowModal().GetValueOrDefault(); - - // Save the settings for this solution, even if filtering - // was cancelled. This saves the user from having to re-apply - // the settings the next time they open the dialog. - solutionSettings = new SolutionSettings { - LoadProjectDependencies = vm.LoadProjectDependencies, - UseRegularExpressions = vm.UseRegularExpressions, - ExpandLoadedProjects = vm.ExpandLoadedProjects, - HideSolutionFoldersWithoutLoadedProjects = vm.HideSolutionFoldersWithoutLoadedProjects - }; - PopulateNodeSettings(vm.Items, solutionSettings.Nodes); - - solutionSettingsManager.SetSettings(solutionSettings); - - // Also save the settings to the global settings so that the same settings - // can be used for solutions that don't have their own settings yet. - globalSettings.LoadProjectDependencies = vm.LoadProjectDependencies; - globalSettings.UseRegularExpressions = vm.UseRegularExpressions; - globalSettings.ExpandLoadedProjects = vm.ExpandLoadedProjects; - globalSettings.HideSolutionFoldersWithoutLoadedProjects = vm.HideSolutionFoldersWithoutLoadedProjects; - await globalSettings.SaveAsync(); - - if (result) { - return vm.Result; - } + LoadProjectDependencies = solutionSettings?.LoadProjectDependencies ?? globalSettings.LoadProjectDependencies, + UseRegularExpressions = solutionSettings?.UseRegularExpressions ?? globalSettings.UseRegularExpressions, + ExpandLoadedProjects = solutionSettings?.ExpandLoadedProjects ?? globalSettings.ExpandLoadedProjects, + HideSolutionFoldersWithoutLoadedProjects = + solutionSettings?.HideSolutionFoldersWithoutLoadedProjects ?? + globalSettings.HideSolutionFoldersWithoutLoadedProjects, + HideSolutionFoldersWithOnlyFiles = + solutionSettings?.HideSolutionFoldersWithOnlyFiles ?? + globalSettings.HideSolutionFoldersWithOnlyFiles + }; + + dialog = new FilterDialog { + DataContext = vm + }; + + result = dialog.ShowModal().GetValueOrDefault(); + + // Save the settings for this solution, even if filtering + // was cancelled. This saves the user from having to re-apply + // the settings the next time they open the dialog. + solutionSettings = new SolutionSettings { + LoadProjectDependencies = vm.LoadProjectDependencies, + UseRegularExpressions = vm.UseRegularExpressions, + ExpandLoadedProjects = vm.ExpandLoadedProjects, + HideSolutionFoldersWithoutLoadedProjects = vm.HideSolutionFoldersWithoutLoadedProjects, + HideSolutionFoldersWithOnlyFiles = vm.HideSolutionFoldersWithOnlyFiles + }; + PopulateNodeSettings(vm.Items, solutionSettings.Nodes); + + solutionSettingsManager.SetSettings(solutionSettings); + + // Also save the settings to the global settings so that the same settings + // can be used for solutions that don't have their own settings yet. + globalSettings.LoadProjectDependencies = solutionSettings.LoadProjectDependencies; + globalSettings.UseRegularExpressions = solutionSettings.UseRegularExpressions; + globalSettings.ExpandLoadedProjects = solutionSettings.ExpandLoadedProjects; + globalSettings.HideSolutionFoldersWithoutLoadedProjects = + solutionSettings.HideSolutionFoldersWithoutLoadedProjects ?? false; + globalSettings.HideSolutionFoldersWithOnlyFiles = + solutionSettings.HideSolutionFoldersWithOnlyFiles ?? false; + + await globalSettings.SaveAsync(); + + if (result) { + return vm.Result; } return null; diff --git a/source/ProjectFilter/Services/ExtensionSettings.cs b/source/ProjectFilter/Services/ExtensionSettings.cs index 2477d2d..0e8ce26 100644 --- a/source/ProjectFilter/Services/ExtensionSettings.cs +++ b/source/ProjectFilter/Services/ExtensionSettings.cs @@ -14,6 +14,7 @@ public ExtensionSettings() { UseRegularExpressions = false; ExpandLoadedProjects = true; HideSolutionFoldersWithoutLoadedProjects = false; + HideSolutionFoldersWithOnlyFiles = false; } @@ -28,4 +29,7 @@ public ExtensionSettings() { public bool HideSolutionFoldersWithoutLoadedProjects { get; set; } + + public bool HideSolutionFoldersWithOnlyFiles { get; set; } + } diff --git a/source/ProjectFilter/Services/FilterOptions.cs b/source/ProjectFilter/Services/FilterOptions.cs index 7a484c0..e31774b 100644 --- a/source/ProjectFilter/Services/FilterOptions.cs +++ b/source/ProjectFilter/Services/FilterOptions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; namespace ProjectFilter.Services; @@ -13,13 +12,15 @@ public FilterOptions( IEnumerable projectsToUnload, bool loadProjectDependencies, bool expandLoadedProjects, - bool hideSolutionFoldersWithoutLoadedProjects + bool hideSolutionFoldersWithoutLoadedProjects, + bool hideSolutionFoldersWithOnlyFiles ) { ProjectsToLoad = [.. projectsToLoad]; ProjectsToUnload = [.. projectsToUnload]; LoadProjectDependencies = loadProjectDependencies; ExpandLoadedProjects = expandLoadedProjects; HideSolutionFoldersWithoutLoadedProjects = hideSolutionFoldersWithoutLoadedProjects; + HideSolutionFoldersWithOnlyFiles = hideSolutionFoldersWithOnlyFiles; } @@ -32,9 +33,12 @@ bool hideSolutionFoldersWithoutLoadedProjects public bool LoadProjectDependencies { get; } - public bool ExpandLoadedProjects{ get; } + public bool ExpandLoadedProjects { get; } public bool HideSolutionFoldersWithoutLoadedProjects { get; } + + public bool HideSolutionFoldersWithOnlyFiles { get; } + } diff --git a/source/ProjectFilter/Services/FilterService.cs b/source/ProjectFilter/Services/FilterService.cs index 9d3bfc8..551d817 100644 --- a/source/ProjectFilter/Services/FilterService.cs +++ b/source/ProjectFilter/Services/FilterService.cs @@ -121,13 +121,17 @@ public async Task ApplyAsync(FilterOptions options) { // remain hidden because the user cancelled half way through. await solutionExplorer.HideUnloadedProjectsAsync(); - // Hiding unloaded projects will hide the solution folders that those hidden - // projects are in as long as there is nothing else in those folders. If all - // of the projects in a solution folder have been unloaded, but the folder contains - // other solution item (like a readme file or editorconfig file), the folder - // will not be hidden. We have an option that allows those folders to be hidden. - if (options.HideSolutionFoldersWithoutLoadedProjects) { - await solutionExplorer.HideSolutionFoldersWithoutLoadedProjectsAsync(); + if (options.HideSolutionFoldersWithoutLoadedProjects || options.HideSolutionFoldersWithOnlyFiles) { + // Hiding unloaded projects will hide the solution folders that those hidden projects + // are in as long as there is nothing else in those folders. If all of the projects + // in a solution folder have been unloaded, but the folder contains other solution + // item (like a readme file or editorconfig file), the folder will not be hidden. + // So when we want to hide solution files without loaded projects, or hide solution + // folders that only contain files, then we need to hide those folders ourselves. + await solutionExplorer.HideSolutionFoldersAsync( + options.HideSolutionFoldersWithoutLoadedProjects, + options.HideSolutionFoldersWithOnlyFiles + ); } // Expand the projects if we are supposed to. For some reason, Visual Studio seems to expand diff --git a/source/ProjectFilter/Services/HierarchyProvider.cs b/source/ProjectFilter/Services/HierarchyProvider.cs index 817ebd8..e57a619 100644 --- a/source/ProjectFilter/Services/HierarchyProvider.cs +++ b/source/ProjectFilter/Services/HierarchyProvider.cs @@ -16,7 +16,7 @@ namespace ProjectFilter.Services; public class HierarchyProvider : IHierarchyProvider { - public async Task> GetHierarchyAsync() { + public async Task> GetHierarchyAsync(bool omitEmptyFolders) { List<(HierarchyNode Hierarchy, Guid Parent)> nodes; Dictionary mapping; List roots; @@ -45,7 +45,9 @@ public async Task> GetHierarchyAsync() { } } - RemoveEmptyFolders(roots); + if (omitEmptyFolders) { + RemoveEmptyFolders(roots); + } return roots; } @@ -182,7 +184,7 @@ private static int CompareNodes(HierarchyNode x, HierarchyNode y) { } - private void RemoveEmptyFolders(IList nodes) { + private static void RemoveEmptyFolders(IList nodes) { for (int i = nodes.Count - 1; i >= 0; i--) { HierarchyNode node; diff --git a/source/ProjectFilter/Services/IExtensionSettings.cs b/source/ProjectFilter/Services/IExtensionSettings.cs index b3706c0..fa9b632 100644 --- a/source/ProjectFilter/Services/IExtensionSettings.cs +++ b/source/ProjectFilter/Services/IExtensionSettings.cs @@ -20,6 +20,9 @@ public interface IExtensionSettings { bool HideSolutionFoldersWithoutLoadedProjects { get; set; } + bool HideSolutionFoldersWithOnlyFiles { get; set; } + + Task LoadAsync(); diff --git a/source/ProjectFilter/Services/IHierarchyProvider.cs b/source/ProjectFilter/Services/IHierarchyProvider.cs index 21c23a5..0d6ffb0 100644 --- a/source/ProjectFilter/Services/IHierarchyProvider.cs +++ b/source/ProjectFilter/Services/IHierarchyProvider.cs @@ -9,6 +9,6 @@ namespace ProjectFilter.Services; [Guid("7eaa8e03-e6fe-35c7-a27d-5415b28d58df")] public interface IHierarchyProvider { - Task> GetHierarchyAsync(); + Task> GetHierarchyAsync(bool omitEmptyFolders); } diff --git a/source/ProjectFilter/Services/ISolutionExplorer.cs b/source/ProjectFilter/Services/ISolutionExplorer.cs index aa6ce0c..39ef04b 100644 --- a/source/ProjectFilter/Services/ISolutionExplorer.cs +++ b/source/ProjectFilter/Services/ISolutionExplorer.cs @@ -16,7 +16,10 @@ public interface ISolutionExplorer { Task HideUnloadedProjectsAsync(); - Task HideSolutionFoldersWithoutLoadedProjectsAsync(); + Task HideSolutionFoldersAsync( + bool hideSolutionFoldersWithoutLoadedProjects, + bool hideSolutionFoldersWithOnlyFiles + ); Task ExpandAsync(IEnumerable projects); diff --git a/source/ProjectFilter/Services/SolutionExplorer.cs b/source/ProjectFilter/Services/SolutionExplorer.cs index af279f5..e47174c 100644 --- a/source/ProjectFilter/Services/SolutionExplorer.cs +++ b/source/ProjectFilter/Services/SolutionExplorer.cs @@ -122,9 +122,16 @@ public async Task HideUnloadedProjectsAsync() { } - public async Task HideSolutionFoldersWithoutLoadedProjectsAsync() { + public async Task HideSolutionFoldersAsync( + bool hideSolutionFoldersWithoutLoadedProjects, + bool hideSolutionFoldersWithOnlyFiles + ) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + if (!hideSolutionFoldersWithoutLoadedProjects && !hideSolutionFoldersWithOnlyFiles) { + return; + } + SolutionExplorerWindow? solutionExplorer; @@ -133,31 +140,37 @@ public async Task HideSolutionFoldersWithoutLoadedProjectsAsync() { if (solutionExplorer is not null) { IVsSolution solution; IHierarchyProvider provider; - IEnumerable nodes; IEnumerable selection; + IEnumerable nodes; + List foldersToHide; WindowFrame? window; bool focused; solution = await VS.Services.GetSolutionAsync(); provider = await VS.GetRequiredServiceAsync(); - nodes = await provider.GetHierarchyAsync(); - - window = null; - focused = false; // We need to execute some commands that operate on the solution folders, // which means we may need to change the selected item solution explorer. // Remember the current selection so that we can restore it. selection = await solutionExplorer.GetSelectionAsync(); - // We only want to hide folders that contain projects where all of the projects are - // unloaded. We don't want to hide folders that only contain miscellaneous files like - // the "Solution Items" folder. The nodes we get from the `IHierarchyProvider` only - // include nodes that contain projects either within them directly or somewhere within - // a descendant folder. That means if we encounter a folder that doesn't have loaded - // projects within it, then we know it must contain only unloaded projects. - foreach (IHierarchyNode node in GetSolutionFoldersWithoutLoadedProjects(nodes)) { + // The hierarchy we can retrieve only includes projects and not files. We can choose to + // get the hierarchy with or without empty folders. An empty folder in this scenario is + // a folder without any projects (either loaded or unloaded). We'll get the hierarchy + // with the empty folders (don't omit empty folders) so that we are able to include + // folders with only files (they would be empty folders) in the folders to hide. + nodes = await provider.GetHierarchyAsync(false); + foldersToHide = GetSolutionFoldersToHide( + nodes, + hideSolutionFoldersWithoutLoadedProjects, + hideSolutionFoldersWithOnlyFiles + ); + + window = null; + focused = false; + + foreach (IHierarchyNode node in foldersToHide) { if (ErrorHandler.Succeeded(solution.GetProjectOfGuid(node.Identifier, out IVsHierarchy hierarchy))) { SolutionItem? folder; @@ -195,7 +208,11 @@ public async Task HideSolutionFoldersWithoutLoadedProjectsAsync() { } - public static List GetSolutionFoldersWithoutLoadedProjects(IEnumerable nodes) { + public static List GetSolutionFoldersToHide( + IEnumerable nodes, + bool hideSolutionFoldersWithoutLoadedProjects, + bool hideSolutionFoldersWithOnlyFiles + ) { List folders; @@ -203,41 +220,58 @@ public static List GetSolutionFoldersWithoutLoadedProjects(IEnum foreach (IHierarchyNode child in nodes) { if (child.IsFolder) { - Visit(child, folders, out _); + Visit(child); } } return folders; - static void Visit( - IHierarchyNode folder, - List foldersWithoutLoadedProjects, - out bool hasLoadedProjectsWithin - ) { - bool hasLoadedProjectsDirectly; - bool hasLoadedProjectsInDescendants; - + bool Visit(IHierarchyNode folder) { + if (folder.Children.Count == 0) { + // An empty folder has no projects, so we'll assume that it must therefore + // contain files which are not included in the hierarchy that we traverse. + if (hideSolutionFoldersWithOnlyFiles) { + folders.Add(folder); + return true; + } else { + return false; + } - hasLoadedProjectsDirectly = false; - hasLoadedProjectsInDescendants = false; + } else { + bool canHide = true; + bool hasLoadedProjects = false; - foreach (IHierarchyNode child in folder.Children) { - if (child.IsFolder) { - Visit(child, foldersWithoutLoadedProjects, out bool hasLoadedProjectsWithinFolder); + foreach (IHierarchyNode child in folder.Children) { + if (child.IsFolder) { + if (!Visit(child)) { + // We can't hide this child folder, which means we + // also can't hide the folder we are visiting. + canHide = false; + } - if (hasLoadedProjectsWithinFolder) { - hasLoadedProjectsInDescendants = true; + } else { + // The node is not a folder, so it must be a project. + // If the project is loaded, then we can't hide the folder. + if (child.IsLoaded) { + canHide = false; + hasLoadedProjects = true; + } } + } - } else if (child.IsLoaded) { - hasLoadedProjectsDirectly = true; + // If the folder does not contain any loaded projects, but we + // are not allowed to hide folders without loaded projects, + // then we can't actually hide this folder. We can only determine + // this once we have looked at all children in the given folder. + if (canHide && !hasLoadedProjects && !hideSolutionFoldersWithoutLoadedProjects) { + canHide = false; } - } - hasLoadedProjectsWithin = hasLoadedProjectsDirectly || hasLoadedProjectsInDescendants; + if (canHide) { + folders.Add(folder); + } - if (!hasLoadedProjectsWithin) { - foldersWithoutLoadedProjects.Add(folder); + return canHide; } } } diff --git a/source/ProjectFilter/Services/SolutionSettings.cs b/source/ProjectFilter/Services/SolutionSettings.cs index be44f3c..5295af5 100644 --- a/source/ProjectFilter/Services/SolutionSettings.cs +++ b/source/ProjectFilter/Services/SolutionSettings.cs @@ -11,6 +11,7 @@ public SolutionSettings() { UseRegularExpressions = false; ExpandLoadedProjects = true; HideSolutionFoldersWithoutLoadedProjects = null; + HideSolutionFoldersWithOnlyFiles = null; Nodes = []; } @@ -27,6 +28,9 @@ public SolutionSettings() { public bool? HideSolutionFoldersWithoutLoadedProjects { get; set; } + public bool? HideSolutionFoldersWithOnlyFiles { get; set; } + + public Dictionary Nodes { get; } } diff --git a/source/ProjectFilter/UI/AttachedProperties/ClearOnEscape.cs b/source/ProjectFilter/UI/AttachedProperties/ClearOnEscape.cs deleted file mode 100644 index cfc344c..0000000 --- a/source/ProjectFilter/UI/AttachedProperties/ClearOnEscape.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; - - -namespace ProjectFilter.UI.AttachedProperties; - - -public static class ClearOnEscape { - - public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached( - "Enabled", - typeof(bool), - typeof(ClearOnEscape), - new PropertyMetadata(false, OnEnabledChanged) - ); - - - - public static bool GetEnabled(DependencyObject obj) { - if (obj is null) { - throw new ArgumentNullException(nameof(obj)); - } - - return (bool)obj.GetValue(EnabledProperty); - } - - - public static void SetEnabled(DependencyObject obj, bool value) { - if (obj is null) { - throw new ArgumentNullException(nameof(obj)); - } - - obj.SetValue(EnabledProperty, value); - } - - - private static void OnEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - TextBox? box; - - - box = d as TextBox; - - if (box is not null) { - if ((bool)e.OldValue) { - box.KeyDown -= OnTextBoxKeyDown; - } - - if ((bool)e.NewValue) { - box.KeyDown += OnTextBoxKeyDown; - } - } - } - - - private static void OnTextBoxKeyDown(object sender, KeyEventArgs e) { - if (e.OriginalSource == sender) { - if (e.Key == Key.Escape) { - TextBox box; - - - box = (TextBox)sender; - - if (!string.IsNullOrEmpty(box.Text)) { - box.Text = ""; - e.Handled = true; - } - } - } - } - -} diff --git a/source/ProjectFilter/UI/AttachedProperties/FocusWhenRequested.cs b/source/ProjectFilter/UI/AttachedProperties/FocusWhenRequested.cs deleted file mode 100644 index e219228..0000000 --- a/source/ProjectFilter/UI/AttachedProperties/FocusWhenRequested.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Microsoft.VisualStudio; -using ProjectFilter.UI.Utilities; -using System; -using System.Diagnostics; -using System.Windows; - - -namespace ProjectFilter.UI.AttachedProperties; - - -public static class FocusWhenRequested { - - public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached( - "Source", - typeof(FocusSource), - typeof(FocusWhenRequested), - new PropertyMetadata(null, OnSourceChanged) - ); - - - private static readonly DependencyProperty EventHandlerProperty = DependencyProperty.RegisterAttached( - "EventHandler", - typeof(EventHandler), - typeof(FocusWhenRequested), - new PropertyMetadata(null) - ); - - - public static FocusSource GetSource(DependencyObject obj) { - if (obj is null) { - throw new ArgumentNullException(nameof(obj)); - } - - return (FocusSource)obj.GetValue(SourceProperty); - } - - - public static void SetSource(DependencyObject obj, FocusSource value) { - if (obj is null) { - throw new ArgumentNullException(nameof(obj)); - } - - obj.SetValue(SourceProperty, value); - } - - - private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - UIElement? target; - - - target = d as UIElement; - - if (target is not null) { - EventHandler? handler; - - - if (e.OldValue is FocusSource oldSource) { - // Remove the event handler from the old source. - // The event handler is saved against the target object. - handler = target.GetValue(EventHandlerProperty) as EventHandler; - target.SetValue(EventHandlerProperty, null); - - if (handler is not null) { - oldSource.FocusRequested -= handler; - } - } - - if (e.NewValue is FocusSource newSource) { - handler = (_, _) => OnFocusRequested(target); - newSource.FocusRequested += handler; - - // Save the event handler against the target so - // that we can remove it of the source changes. - target.SetValue(EventHandlerProperty, handler); - } - } - } - - - private static void OnFocusRequested(UIElement target) { - try { - target.Focus(); - } catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) { - Debug.WriteLine(ex); - } - } - -} diff --git a/source/ProjectFilter/UI/AttachedProperties/FocusWhenVisible.cs b/source/ProjectFilter/UI/AttachedProperties/FocusWhenVisible.cs deleted file mode 100644 index d019e10..0000000 --- a/source/ProjectFilter/UI/AttachedProperties/FocusWhenVisible.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Microsoft.VisualStudio; -using System; -using System.Windows; - - -namespace ProjectFilter.UI.AttachedProperties; - - -public static class FocusWhenVisible { - - public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached( - "Enabled", - typeof(bool), - typeof(FocusWhenVisible), - new PropertyMetadata(false, OnEnabledChanged) - ); - - - public static bool GetEnabled(DependencyObject obj) { - if (obj is null) { - throw new ArgumentNullException(nameof(obj)); - } - - return (bool)obj.GetValue(EnabledProperty); - } - - - public static void SetEnabled(DependencyObject obj, bool value) { - if (obj is null) { - throw new ArgumentNullException(nameof(obj)); - } - - obj.SetValue(EnabledProperty, value); - } - - - private static void OnEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - UIElement? target; - - - target = d as UIElement; - - if (target is not null) { - if ((bool)e.OldValue) { - target.IsVisibleChanged -= OnIsVisibleChanged; - } - - if ((bool)e.NewValue) { - target.IsVisibleChanged += OnIsVisibleChanged; - } - } - } - - - private static void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) { - UIElement? target; - - - target = sender as UIElement; - - if (target is not null && target.IsVisible) { - try { - target.Focus(); - } catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) { - System.Diagnostics.Debug.WriteLine(ex); - } - } - } - -} diff --git a/source/ProjectFilter/UI/AttachedProperties/Watermark.cs b/source/ProjectFilter/UI/AttachedProperties/Watermark.cs deleted file mode 100644 index 8dc4e90..0000000 --- a/source/ProjectFilter/UI/AttachedProperties/Watermark.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Controls; - - -namespace ProjectFilter.UI.AttachedProperties; - - -public static class Watermark { - - public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached( - "Text", - typeof(string), - typeof(Watermark), - new PropertyMetadata(null, OnTextChanged) - ); - - - private static readonly DependencyPropertyKey HidePropertyKey = DependencyProperty.RegisterAttachedReadOnly( - "Hide", - typeof(bool), - typeof(Watermark), - new PropertyMetadata(false) - ); - - - public static readonly DependencyProperty HideProperty = HidePropertyKey.DependencyProperty; - - - public static string GetText(DependencyObject obj) { - if (obj is null) { - throw new ArgumentNullException(nameof(obj)); - } - - return (string)obj.GetValue(TextProperty); - } - - - public static void SetText(DependencyObject obj, string value) { - if (obj is null) { - throw new ArgumentNullException(nameof(obj)); - } - - obj.SetValue(TextProperty, value); - } - - - public static bool GetHide(DependencyObject obj) { - if (obj is null) { - throw new ArgumentNullException(nameof(obj)); - } - - return (bool)obj.GetValue(HideProperty); - } - - - private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - TextBox? box; - - - box = d as TextBox; - - if (box is not null) { - if (e.OldValue is not null) { - box.TextChanged -= OnTextBoxTextChanged; - box.ClearValue(HidePropertyKey); - } - - if (e.NewValue is not null) { - box.TextChanged += OnTextBoxTextChanged; - UpdateHideProperty(box); - } - } - } - - - private static void OnTextBoxTextChanged(object sender, TextChangedEventArgs e) { - UpdateHideProperty((TextBox)sender); - } - - - private static void UpdateHideProperty(TextBox box) { - box.SetValue(HidePropertyKey, !string.IsNullOrEmpty(box.Text)); - } - -} diff --git a/source/ProjectFilter/UI/FilterDialog.xaml b/source/ProjectFilter/UI/FilterDialog.xaml index 23ef096..5d99beb 100644 --- a/source/ProjectFilter/UI/FilterDialog.xaml +++ b/source/ProjectFilter/UI/FilterDialog.xaml @@ -18,7 +18,7 @@ Width="350" ShowInTaskbar="False" WindowStartupLocation="CenterOwner" - Background="{DynamicResource {x:Static platform:ThemedDialogColors.WindowPanelBrushKey}}" + Background="{DynamicResource {x:Static platform:EnvironmentColors.ToolWindowBackgroundBrushKey}}" platform:ThemedDialogStyleLoader.UseDefaultThemedDialogStyles="True" platformImaging:ImageThemingUtilities.ThemeScrollBars="True" properties:CloseDialog.Trigger="{Binding Result}" @@ -48,7 +48,8 @@ - + + @@ -71,10 +72,10 @@ - @@ -96,23 +97,27 @@ IsChecked="{Binding ExpandLoadedProjects}" > - + - + + - - + + + + + + +