Kaynağa Gözat

FilteredSolutionsExtension: fixes, working

Dalibor Votruba 1 yıl önce
ebeveyn
işleme
7d259c4c94

+ 1 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/FilteredSolutionsExtension.csproj

@@ -62,6 +62,7 @@
     <Compile Include="SolutionEventsHandler.cs" />
     <Compile Include="SolutionOpeningInterceptor.cs" />
     <Compile Include="SolutionParser.cs" />
+    <Compile Include="StringToVisibilityConverter.cs" />
   </ItemGroup>
   <ItemGroup>
     <None Include="readme.md" />

+ 26 - 14
FilteredSolutionsExtension/FilteredSolutionsExtension/ProjectFilterDialog.xaml

@@ -1,9 +1,9 @@
-<Window x:Class="Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension.ProjectFilterDialog"
+<Window x:Class="Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension.ProjectFilterDialog"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-        Title="Select Projects for Solution" Height="700" Width="900" 
+        Title="Select Projects for Solution" Height="800" Width="1100" 
         WindowStartupLocation="CenterScreen" ResizeMode="CanResize"
-        MinHeight="500" MinWidth="600">
+        MinHeight="600" MinWidth="900">
     <Grid Margin="15">
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
@@ -17,8 +17,10 @@
         <!-- Solution Info -->
         <GroupBox Grid.Row="0" Header="Solution Information" Padding="10" Margin="0,0,0,10">
             <StackPanel>
-                <TextBlock x:Name="SolutionNameTextBlock" FontWeight="Bold" Margin="0,0,0,5"/>
+                <TextBlock x:Name="SolutionNameTextBlock" FontWeight="Bold" Margin="0,0,0,5" FontSize="14"/>
                 <TextBlock x:Name="ProjectCountTextBlock" Foreground="Gray"/>
+                <TextBlock Text="💡 Dependencies are automatically selected/deselected when you check/uncheck projects" 
+                           Foreground="DarkBlue" FontStyle="Italic" Margin="0,5,0,0" TextWrapping="Wrap"/>
             </StackPanel>
         </GroupBox>
         
@@ -35,7 +37,8 @@
                 <Label Grid.Column="0" Content="Filter pattern:" VerticalAlignment="Center"/>
                 <TextBox Grid.Column="1" x:Name="FilterTextBox" Margin="5,0" 
                          VerticalAlignment="Center" Height="25"
-                         ToolTip="Use * for wildcards (e.g., Test*, *Core*, *.Data)"/>
+                         ToolTip="Use * for wildcards (e.g., Test*, *Core*, *.Data) - filters as you type"
+                         TextChanged="FilterTextBox_TextChanged"/>
                 <Button Grid.Column="2" x:Name="FilterButton" Content="Apply Filter" 
                         Width="90" Click="FilterButton_Click" Margin="5,0,0,0"/>
                 <Button Grid.Column="3" x:Name="ClearFilterButton" Content="Clear" 
@@ -49,6 +52,9 @@
                     Click="SelectAllButton_Click" Margin="0,0,8,0"/>
             <Button x:Name="ClearAllButton" Content="Clear All" Width="90" Height="32" 
                     Click="ClearAllButton_Click" Margin="0,0,8,0"/>
+            <Button x:Name="SelectTestsButton" Content="Select Tests" Width="90" Height="32" 
+                    Click="SelectTestsButton_Click" Margin="0,0,8,0" 
+                    ToolTip="Select projects with 'Test' in name and their dependencies"/>
             <Separator Width="1" Margin="8,0"/>
             <TextBlock x:Name="SelectionCountTextBlock" VerticalAlignment="Center" 
                        Margin="8,0,0,0" Foreground="DarkBlue" FontWeight="Medium"/>
@@ -57,21 +63,27 @@
         <!-- Project Tree -->
         <Border Grid.Row="3" BorderBrush="LightGray" BorderThickness="1" Margin="0,0,0,15">
             <TreeView x:Name="ProjectTreeView" ScrollViewer.HorizontalScrollBarVisibility="Auto"
-                      ScrollViewer.VerticalScrollBarVisibility="Auto" Padding="5">
+                      ScrollViewer.VerticalScrollBarVisibility="Auto" Padding="5"
+                      VirtualizingPanel.IsVirtualizing="True"
+                      VirtualizingPanel.VirtualizationMode="Recycling">
                 <TreeView.ItemTemplate>
                     <DataTemplate>
-                        <StackPanel Orientation="Horizontal" Margin="0,2">
+                        <StackPanel Orientation="Horizontal" Margin="0,4">
                             <CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" 
                                       Checked="ProjectTreeItem_Checked"
                                       Unchecked="ProjectTreeItem_Unchecked"
                                       Margin="0,0,8,0"
-                                      VerticalAlignment="Center"/>
+                                      VerticalAlignment="Top"/>
                             <Image Source="{Binding ProjectTypeIcon}" Width="16" Height="16" 
-                                   Margin="0,0,5,0" VerticalAlignment="Center"/>
-                            <TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center"
-                                       ToolTip="{Binding ProjectInfo.Path}"/>
-                            <TextBlock Text="{Binding DependencyText}" Margin="8,0,0,0" 
-                                       Foreground="Gray" FontStyle="Italic" VerticalAlignment="Center"/>
+                                   Margin="0,0,6,0" VerticalAlignment="Top"/>
+                            <StackPanel Orientation="Vertical" MinWidth="200">
+                                <TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center"
+                                           FontWeight="Medium" 
+                                           ToolTip="{Binding ToolTipText}"/>
+                                <TextBlock Text="{Binding DependencyText}" Margin="0,2,0,0" 
+                                           Foreground="Gray" FontStyle="Italic" VerticalAlignment="Center"
+                                           TextWrapping="Wrap" MaxWidth="600" FontSize="11"/>
+                            </StackPanel>
                         </StackPanel>
                     </DataTemplate>
                 </TreeView.ItemTemplate>
@@ -87,7 +99,7 @@
         
         <!-- Action Buttons -->
         <StackPanel Grid.Row="5" Orientation="Horizontal" HorizontalAlignment="Right">
-            <Button x:Name="OkButton" Content="Create Filtered Solution" Width="150" Height="35" 
+            <Button x:Name="OkButton" Content="Create Filtered Solution" Width="160" Height="35" 
                     Click="OkButton_Click" IsDefault="True" Margin="0,0,10,0"
                     Background="DodgerBlue" Foreground="White" FontWeight="Medium"/>
             <Button x:Name="CancelButton" Content="Open All Projects" Width="130" Height="35" 

+ 187 - 34
FilteredSolutionsExtension/FilteredSolutionsExtension/ProjectFilterDialog.xaml.cs

@@ -8,6 +8,7 @@ using System.Runtime.CompilerServices;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Threading;
 
 namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
 {
@@ -15,7 +16,10 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
     {
         private readonly SolutionInfo _solutionInfo;
         private readonly SolutionParser _solutionParser;
+        private List<ProjectNode> _allProjectNodes;
         private string _filterPattern = "";
+        private bool _updatingSelection = false;
+        private DispatcherTimer _filterTimer;
         
         public string FilteredSolutionPath { get; private set; }
         public ObservableCollection<ProjectTreeItem> Projects { get; set; }
@@ -54,12 +58,12 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
                 ProjectTreeView.Visibility = Visibility.Hidden;
                 StatusTextBlock.Text = "Loading projects...";
 
-                var projectNodes = await _solutionParser.ParseSolutionAsync(_solutionInfo.FullPath);
+                _allProjectNodes = await _solutionParser.ParseSolutionAsync(_solutionInfo.FullPath);
                 
                 System.Windows.Application.Current.Dispatcher.Invoke(() =>
                 {
                     Projects.Clear();
-                    foreach (var projectNode in projectNodes)
+                    foreach (var projectNode in _allProjectNodes)
                     {
                         var projectInfo = new ProjectInfo
                         {
@@ -68,7 +72,11 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
                             ProjectGuid = projectNode.ProjectGuid,
                             TypeGuid = projectNode.TypeGuid
                         };
-                        Projects.Add(new ProjectTreeItem(projectInfo));
+                        
+                        var treeItem = new ProjectTreeItem(projectInfo);
+                        // Store reference to original ProjectNode for dependency tracking
+                        treeItem.Tag = projectNode;
+                        Projects.Add(treeItem);
                     }
                     
                     ProjectCountTextBlock.Text = $"{Projects.Count} projects found";
@@ -80,6 +88,8 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
                     
                     // Set the ItemsSource for the TreeView
                     ProjectTreeView.ItemsSource = Projects;
+                    
+                    ErrorHandler.LogInfo($"Loaded {Projects.Count} projects in filter dialog");
                 });
             }
             catch (Exception ex)
@@ -91,20 +101,65 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
 
         private void SelectAllButton_Click(object sender, RoutedEventArgs e)
         {
-            foreach (var project in Projects)
+            _updatingSelection = true;
+            try
             {
-                project.IsChecked = true;
+                foreach (var project in Projects)
+                {
+                    project.IsChecked = true;
+                }
+                UpdateSelectionCount();
+            }
+            finally
+            {
+                _updatingSelection = false;
             }
-            UpdateSelectionCount();
         }
 
         private void ClearAllButton_Click(object sender, RoutedEventArgs e)
         {
-            foreach (var project in Projects)
+            _updatingSelection = true;
+            try
             {
-                project.IsChecked = false;
+                foreach (var project in Projects)
+                {
+                    project.IsChecked = false;
+                }
+                UpdateSelectionCount();
+            }
+            finally
+            {
+                _updatingSelection = false;
+            }
+        }
+
+        private void SelectTestsButton_Click(object sender, RoutedEventArgs e)
+        {
+            FilterTextBox.Text = "*Test*";
+            ApplyFilter();
+            
+            // Select all visible test projects
+            _updatingSelection = true;
+            try
+            {
+                foreach (var project in Projects)
+                {
+                    if (project.DisplayName.IndexOf("Test", StringComparison.OrdinalIgnoreCase) >= 0)
+                    {
+                        project.IsChecked = true;
+                        // Auto-select dependencies for test projects
+                        if (project.Tag is ProjectNode projectNode)
+                        {
+                            SelectDependencies(projectNode, true);
+                        }
+                    }
+                }
+                UpdateSelectionCount();
+            }
+            finally
+            {
+                _updatingSelection = false;
             }
-            UpdateSelectionCount();
         }
 
         private void FilterButton_Click(object sender, RoutedEventArgs e)
@@ -119,6 +174,19 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
             ApplyFilter();
         }
 
+        private void FilterTextBox_TextChanged(object sender, TextChangedEventArgs e)
+        {
+            // Debounce the filter to avoid too many updates
+            _filterTimer?.Stop();
+            _filterTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(300) };
+            _filterTimer.Tick += (s, args) =>
+            {
+                _filterTimer.Stop();
+                ApplyFilter();
+            };
+            _filterTimer.Start();
+        }
+
         private void ApplyFilter()
         {
             var pattern = FilterTextBox.Text?.Trim() ?? "";
@@ -128,48 +196,131 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
                 // Show all projects
                 foreach (var project in Projects)
                 {
-                    ((FrameworkElement)ProjectTreeView.ItemContainerGenerator.ContainerFromItem(project))?.SetValue(FrameworkElement.VisibilityProperty, Visibility.Visible);
+                    var container = ProjectTreeView.ItemContainerGenerator.ContainerFromItem(project) as FrameworkElement;
+                    if (container != null)
+                    {
+                        container.Visibility = Visibility.Visible;
+                    }
                 }
                 return;
             }
 
-            var wildcardPattern = pattern.Replace("*", ".*");
-            var regex = new System.Text.RegularExpressions.Regex(wildcardPattern, 
-                System.Text.RegularExpressions.RegexOptions.IgnoreCase);
-
-            foreach (var project in Projects)
+            try
             {
-                var isMatch = regex.IsMatch(project.ProjectInfo.Name);
-                var container = ProjectTreeView.ItemContainerGenerator.ContainerFromItem(project) as FrameworkElement;
-                if (container != null)
+                var wildcardPattern = pattern.Replace("*", ".*");
+                var regex = new System.Text.RegularExpressions.Regex(wildcardPattern, 
+                    System.Text.RegularExpressions.RegexOptions.IgnoreCase);
+
+                foreach (var project in Projects)
                 {
-                    container.Visibility = isMatch ? Visibility.Visible : Visibility.Collapsed;
+                    var isMatch = regex.IsMatch(project.ProjectInfo.Name);
+                    var container = ProjectTreeView.ItemContainerGenerator.ContainerFromItem(project) as FrameworkElement;
+                    if (container != null)
+                    {
+                        container.Visibility = isMatch ? Visibility.Visible : Visibility.Collapsed;
+                    }
                 }
             }
+            catch (Exception ex)
+            {
+                ErrorHandler.LogError("Error applying filter pattern", ex);
+                StatusTextBlock.Text = "Invalid filter pattern";
+            }
         }
 
         private void ProjectTreeItem_Checked(object sender, RoutedEventArgs e)
         {
-            UpdateSelectionCount();
+            if (_updatingSelection) return;
+
+            var checkBox = sender as CheckBox;
+            var treeItem = checkBox?.DataContext as ProjectTreeItem;
+            if (treeItem?.Tag is ProjectNode projectNode)
+            {
+                _updatingSelection = true;
+                try
+                {
+                    // Auto-select dependencies
+                    SelectDependencies(projectNode, true);
+                    UpdateSelectionCount();
+                }
+                finally
+                {
+                    _updatingSelection = false;
+                }
+            }
         }
 
         private void ProjectTreeItem_Unchecked(object sender, RoutedEventArgs e)
         {
-            UpdateSelectionCount();
+            if (_updatingSelection) return;
+
+            var checkBox = sender as CheckBox;
+            var treeItem = checkBox?.DataContext as ProjectTreeItem;
+            if (treeItem?.Tag is ProjectNode projectNode)
+            {
+                _updatingSelection = true;
+                try
+                {
+                    // Auto-deselect dependents
+                    DeselectDependents(projectNode);
+                    UpdateSelectionCount();
+                }
+                finally
+                {
+                    _updatingSelection = false;
+                }
+            }
+        }
+
+        private void SelectDependencies(ProjectNode projectNode, bool isChecked)
+        {
+            // Find corresponding tree item and update it
+            var treeItem = Projects.FirstOrDefault(p => 
+                string.Equals(((ProjectNode)p.Tag)?.ProjectGuid, projectNode.ProjectGuid, StringComparison.OrdinalIgnoreCase));
+            
+            if (treeItem != null && treeItem.IsChecked != isChecked)
+            {
+                treeItem.IsChecked = isChecked;
+            }
+
+            // Recursively select dependencies
+            foreach (var dependency in projectNode.Dependencies)
+            {
+                SelectDependencies(dependency, isChecked);
+            }
+        }
+
+        private void DeselectDependents(ProjectNode projectNode)
+        {
+            // Recursively deselect dependents
+            foreach (var dependent in projectNode.Dependents)
+            {
+                var treeItem = Projects.FirstOrDefault(p => 
+                    string.Equals(((ProjectNode)p.Tag)?.ProjectGuid, dependent.ProjectGuid, StringComparison.OrdinalIgnoreCase));
+                
+                if (treeItem != null && treeItem.IsChecked)
+                {
+                    treeItem.IsChecked = false;
+                    DeselectDependents(dependent);
+                }
+            }
         }
 
         private void UpdateSelectionCount()
         {
             var selectedCount = Projects.Count(p => p.IsChecked);
             SelectionCountTextBlock.Text = $"{selectedCount} of {Projects.Count} projects selected";
+            
+            // Enable/disable OK button based on selection
+            OkButton.IsEnabled = selectedCount > 0;
         }
 
         private async void OkButton_Click(object sender, RoutedEventArgs e)
         {
             try
             {
-                var selectedProjects = Projects.Where(p => p.IsChecked).Select(p => p.ProjectInfo).ToList();
-                if (!selectedProjects.Any())
+                var selectedTreeItems = Projects.Where(p => p.IsChecked).ToList();
+                if (!selectedTreeItems.Any())
                 {
                     ErrorHandler.ShowUserInfo("Warning", "Please select at least one project.");
                     return;
@@ -177,18 +328,18 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
 
                 StatusTextBlock.Text = "Creating filtered solution...";
                 OkButton.IsEnabled = false;
+                CancelButton.IsEnabled = false;
 
-                // Convert ProjectInfo to ProjectNode for the generator
-                var projectNodes = selectedProjects.Select(pi => new ProjectNode
-                {
-                    Name = pi.Name,
-                    Path = pi.FullPath,
-                    ProjectGuid = pi.ProjectGuid,
-                    TypeGuid = pi.TypeGuid
-                }).ToList();
-
-                // Include dependencies automatically
-                var projectsWithDependencies = _solutionParser.GetProjectsWithDependencies(projectNodes);
+                // Get corresponding ProjectNodes
+                var selectedProjectNodes = selectedTreeItems
+                    .Select(ti => ti.Tag as ProjectNode)
+                    .Where(pn => pn != null)
+                    .ToList();
+
+                ErrorHandler.LogInfo($"User selected {selectedProjectNodes.Count} projects for filtering");
+
+                // The parser already includes dependencies, but double-check
+                var projectsWithDependencies = _solutionParser.GetProjectsWithDependencies(selectedProjectNodes);
                 
                 // Create filtered solution
                 FilteredSolutionPath = await _solutionParser.CreateFilteredSolutionAsync(
@@ -203,11 +354,13 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
                 ErrorHandler.ShowUserError("Error", "Error creating filtered solution", ex);
                 StatusTextBlock.Text = "Error creating filtered solution";
                 OkButton.IsEnabled = true;
+                CancelButton.IsEnabled = true;
             }
         }
 
         private void CancelButton_Click(object sender, RoutedEventArgs e)
         {
+            ErrorHandler.LogInfo("User chose to open all projects");
             DialogResult = false;
         }
 

+ 205 - 6
FilteredSolutionsExtension/FilteredSolutionsExtension/ProjectTreeItem.cs

@@ -1,11 +1,18 @@
+using System;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
 namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
 {
-
-    public class ProjectTreeItem : System.ComponentModel.INotifyPropertyChanged
+    public class ProjectTreeItem : INotifyPropertyChanged
     {
         private bool _isChecked = true;
 
         public ProjectInfo ProjectInfo { get; }
+        public object Tag { get; set; } // Reference to ProjectNode for dependency tracking
 
         public bool IsChecked
         {
@@ -16,22 +23,214 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
                 {
                     _isChecked = value;
                     OnPropertyChanged();
+                    OnPropertyChanged(nameof(DependencyText)); // Update dependency text when selection changes
                 }
             }
         }
 
         public string DisplayName => ProjectInfo.Name;
 
+        public string DependencyText
+        {
+            get
+            {
+                if (Tag is ProjectNode projectNode)
+                {
+                    var depCount = projectNode.Dependencies.Count;
+                    var dependentCount = projectNode.Dependents.Count;
+                    
+                    if (depCount == 0 && dependentCount == 0)
+                        return "";
+
+                    var parts = new System.Collections.Generic.List<string>();
+                    
+                    if (depCount > 0)
+                    {
+                        var depNames = projectNode.Dependencies.Take(2).Select(d => d.Name);
+                        if (depCount > 2)
+                        {
+                            depNames = depNames.Concat(new[] { $"+{depCount - 2} more" });
+                        }
+                        parts.Add($"depends on: {string.Join(", ", depNames)}");
+                    }
+                    
+                    if (dependentCount > 0)
+                    {
+                        var dependentNames = projectNode.Dependents.Take(2).Select(d => d.Name);
+                        if (dependentCount > 2)
+                        {
+                            dependentNames = dependentNames.Concat(new[] { $"+{dependentCount - 2} more" });
+                        }
+                        parts.Add($"used by: {string.Join(", ", dependentNames)}");
+                    }
+                    
+                    return string.Join(" | ", parts);
+                }
+                return "";
+            }
+        }
+
+        public ImageSource ProjectTypeIcon
+        {
+            get
+            {
+                try
+                {
+                    // Get project type icon based on TypeGuid or file extension
+                    var iconName = GetProjectTypeIconName();
+                    if (!string.IsNullOrEmpty(iconName))
+                    {
+                        // Try to load from pack URI (if you have embedded icons)
+                        var uri = new Uri($"pack://application:,,,/FilteredSolutionsExtension;component/Resources/{iconName}");
+                        return new BitmapImage(uri);
+                    }
+                }
+                catch
+                {
+                    // Fall back to default or no icon
+                }
+                
+                return null; // Return null for default icon or no icon
+            }
+        }
+
+        private string GetProjectTypeIconName()
+        {
+            if (ProjectInfo?.TypeGuid == null)
+                return "project.png"; // Default project icon
+
+            // Map common project type GUIDs to icon names
+            switch (ProjectInfo.TypeGuid.ToUpperInvariant())
+            {
+                case "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}": // C# Project
+                    return IsTestProject() ? "test.png" : "csharp.png";
+                    
+                case "{F184B08F-C81C-45F6-A57F-5ABD9991F28F}": // VB.NET Project
+                    return "vbnet.png";
+                    
+                case "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}": // C++ Project
+                    return "cpp.png";
+                    
+                case "{349C5851-65DF-11DA-9384-00065B846F21}": // Web Application
+                    return "web.png";
+                    
+                case "{E24C65DC-7377-472B-9ABA-BC803B73C61A}": // Web Site
+                    return "website.png";
+                    
+                case "{3AC096D0-A1C2-E12C-1390-A8335801FDAB}": // Test Project
+                    return "test.png";
+                    
+                default:
+                    return IsTestProject() ? "test.png" : "project.png";
+            }
+        }
+
+        private bool IsTestProject()
+        {
+            if (string.IsNullOrEmpty(ProjectInfo?.Name))
+                return false;
+                
+            var name = ProjectInfo.Name.ToLowerInvariant();
+            return name.Contains("test") || 
+                   name.Contains("spec") || 
+                   name.EndsWith(".tests") ||
+                   name.EndsWith(".test") ||
+                   name.EndsWith(".specs") ||
+                   name.EndsWith(".spec");
+        }
+
+        public string ProjectPath => ProjectInfo?.FullPath ?? "";
+
+        public string ProjectSize
+        {
+            get
+            {
+                try
+                {
+                    if (!string.IsNullOrEmpty(ProjectPath) && System.IO.File.Exists(ProjectPath))
+                    {
+                        var fileInfo = new System.IO.FileInfo(ProjectPath);
+                        if (fileInfo.Length < 1024)
+                            return $"{fileInfo.Length} B";
+                        else if (fileInfo.Length < 1024 * 1024)
+                            return $"{fileInfo.Length / 1024:F1} KB";
+                        else
+                            return $"{fileInfo.Length / (1024 * 1024):F1} MB";
+                    }
+                }
+                catch
+                {
+                    // Ignore file access errors
+                }
+                return "";
+            }
+        }
+
+        public string ToolTipText
+        {
+            get
+            {
+                var lines = new System.Collections.Generic.List<string>();
+                
+                if (!string.IsNullOrEmpty(ProjectPath))
+                {
+                    lines.Add($"Path: {ProjectPath}");
+                }
+                
+                if (!string.IsNullOrEmpty(ProjectInfo?.ProjectGuid))
+                {
+                    lines.Add($"GUID: {ProjectInfo.ProjectGuid}");
+                }
+                
+                var size = ProjectSize;
+                if (!string.IsNullOrEmpty(size))
+                {
+                    lines.Add($"Size: {size}");
+                }
+                
+                if (Tag is ProjectNode projectNode)
+                {
+                    if (projectNode.Dependencies.Count > 0)
+                    {
+                        lines.Add($"Dependencies ({projectNode.Dependencies.Count}):");
+                        foreach (var dep in projectNode.Dependencies.Take(5))
+                        {
+                            lines.Add($"  • {dep.Name}");
+                        }
+                        if (projectNode.Dependencies.Count > 5)
+                        {
+                            lines.Add($"  ... and {projectNode.Dependencies.Count - 5} more");
+                        }
+                    }
+                    
+                    if (projectNode.Dependents.Count > 0)
+                    {
+                        lines.Add($"Used by ({projectNode.Dependents.Count}):");
+                        foreach (var dependent in projectNode.Dependents.Take(5))
+                        {
+                            lines.Add($"  • {dependent.Name}");
+                        }
+                        if (projectNode.Dependents.Count > 5)
+                        {
+                            lines.Add($"  ... and {projectNode.Dependents.Count - 5} more");
+                        }
+                    }
+                }
+                
+                return string.Join(Environment.NewLine, lines);
+            }
+        }
+
         public ProjectTreeItem(ProjectInfo projectInfo)
         {
-            ProjectInfo = projectInfo;
+            ProjectInfo = projectInfo ?? throw new ArgumentNullException(nameof(projectInfo));
         }
 
-        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
+        public event PropertyChangedEventHandler PropertyChanged;
 
-        protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
+        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
         {
-            PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
         }
     }
 }

+ 216 - 72
FilteredSolutionsExtension/FilteredSolutionsExtension/SolutionEventsHandler.cs

@@ -2,20 +2,22 @@ using Microsoft.VisualStudio;
 using Microsoft.VisualStudio.Shell;
 using Microsoft.VisualStudio.Shell.Interop;
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
 
 namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
 {
-     public class SolutionEventsHandler : IVsSolutionEvents3, IVsSolutionLoadEvents
+    public class SolutionEventsHandler : IVsSolutionEvents3, IVsSolutionLoadEvents
     {
         private readonly FilteredSolutionsExtensionPackage _package;
         private bool _isProcessingSolution = false;
+        private readonly HashSet<string> _processedSolutions = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 
         public SolutionEventsHandler(FilteredSolutionsExtensionPackage package)
         {
-            _package = package;
+            _package = package ?? throw new ArgumentNullException(nameof(package));
         }
 
         public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution)
@@ -26,7 +28,7 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
 
             try
             {
-                // Use async pattern with JoinableTaskFactory
+                // Use async pattern with JoinableTaskFactory to avoid blocking the UI thread
                 ThreadHelper.JoinableTaskFactory.RunAsync(async () =>
                 {
                     await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
@@ -36,27 +38,43 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
                     {
                         var solutionPath = dte.Solution.FullName;
 
-                        // Check if this is already a filtered solution (temp file)
-                        if (solutionPath.Contains("_filtered.sln") || Path.GetTempPath().Contains(Path.GetDirectoryName(solutionPath)))
+                        // Check if already processed or is a filtered solution
+                        if (_processedSolutions.Contains(solutionPath) || 
+                            IsFilteredSolution(solutionPath))
                         {
+                            ErrorHandler.LogInfo($"Skipping solution dialog - already processed or filtered: {Path.GetFileName(solutionPath)}");
                             return;
                         }
 
-                        // Count projects to see if we should show the dialog
-                        var projectCount = dte.Solution.Projects?.Count ?? 0;
+                        // Count real projects - only show dialog for multi-project solutions
+                        var projectCount = CountRealProjects(dte.Solution);
+                        ErrorHandler.LogInfo($"Solution opened: {Path.GetFileName(solutionPath)} with {projectCount} projects");
+
                         if (projectCount > 1)
                         {
-                            // Delay to ensure solution is fully loaded
-                            await Task.Delay(500);
+                            // Small delay to ensure solution is fully loaded
+                            await Task.Delay(750);
                             
+                            // Check again if we're not already processing (race condition protection)
                             if (!_isProcessingSolution)
                             {
                                 _isProcessingSolution = true;
-                                var solutionInfo = new SolutionInfo(solutionPath);
-                                _package.ShowFilterDialog(solutionInfo);
-                                _isProcessingSolution = false;
+                                _processedSolutions.Add(solutionPath);
+
+                                try
+                                {
+                                    await ShowFilterDialogAsync(solutionPath, dte);
+                                }
+                                finally
+                                {
+                                    _isProcessingSolution = false;
+                                }
                             }
                         }
+                        else
+                        {
+                            ErrorHandler.LogInfo("Skipping solution dialog - only one project found");
+                        }
                     }
                 });
             }
@@ -69,68 +87,142 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
             return VSConstants.S_OK;
         }
 
-        // Alternative approach - try to catch before solution fully opens
-        public int OnBeforeOpenSolution(string pszSolutionFilename)
+        private async Task ShowFilterDialogAsync(string solutionPath, EnvDTE.DTE dte)
         {
-            ThreadHelper.ThrowIfNotOnUIThread();
-
-            if (_isProcessingSolution) return VSConstants.S_OK;
-
-            if (!string.IsNullOrEmpty(pszSolutionFilename) && File.Exists(pszSolutionFilename))
+            await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
+            
+            try
             {
-                // Check if this is already a filtered solution
-                if (pszSolutionFilename.Contains("_filtered.sln") || Path.GetTempPath().Contains(Path.GetDirectoryName(pszSolutionFilename)))
+                ErrorHandler.LogInfo($"Showing filter dialog for: {Path.GetFileName(solutionPath)}");
+                
+                var solutionInfo = new SolutionInfo(solutionPath);
+                var dialog = new ProjectFilterDialog(solutionInfo);
+                var result = dialog.ShowDialog();
+
+                if (result == true && !string.IsNullOrEmpty(dialog.FilteredSolutionPath))
                 {
-                    return VSConstants.S_OK;
+                    ErrorHandler.LogInfo($"User selected filtered solution: {Path.GetFileName(dialog.FilteredSolutionPath)}");
+                    
+                    // Mark filtered solution as processed to prevent dialog on next open
+                    _processedSolutions.Add(dialog.FilteredSolutionPath);
+                    
+                    // Close current solution and open filtered one
+                    dte.Solution.Close(false);
+                    dte.Solution.Open(dialog.FilteredSolutionPath);
                 }
-
-                try
+                else
                 {
-                    // Quick check of project count by parsing solution file
-                    var content = File.ReadAllText(pszSolutionFilename);
-                    var projectLines = content.Split('\n').Where(line => line.Trim().StartsWith("Project(")).ToList();
+                    ErrorHandler.LogInfo("User chose to open all projects or cancelled dialog");
+                }
+            }
+            catch (Exception ex)
+            {
+                ErrorHandler.LogError("Error showing filter dialog", ex);
+                ErrorHandler.ShowUserError("Filter Dialog Error", 
+                    "Failed to show project filter dialog. Opening solution with all projects.", ex);
+            }
+        }
 
-                    if (projectLines.Count > 1)
-                    {
-                        _isProcessingSolution = true;
+        private bool IsFilteredSolution(string solutionPath)
+        {
+            try
+            {
+                if (string.IsNullOrEmpty(solutionPath))
+                    return false;
 
-                        // Show dialog immediately
-                        var solutionInfo = new SolutionInfo(pszSolutionFilename);
-                        var dialog = new ProjectFilterDialog(solutionInfo);
-                        var result = dialog.ShowDialog();
+                // Check if it's a filtered solution by name
+                if (solutionPath.IndexOf("_filtered.sln", StringComparison.OrdinalIgnoreCase) >= 0)
+                    return true;
 
-                        if (result == true && !string.IsNullOrEmpty(dialog.FilteredSolutionPath))
-                        {
-                            // We need to cancel the current opening and open the filtered one instead
-                            Task.Delay(100).ContinueWith(_ =>
-                            {
-                                ThreadHelper.JoinableTaskFactory.RunAsync(async () =>
-                                {
-                                    await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
-                                    var dte = await _package.GetServiceAsync(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
-                                    dte?.Solution?.Close();
-                                    dte?.Solution?.Open(dialog.FilteredSolutionPath);
-                                    _isProcessingSolution = false;
-                                });
-                            });
+                // Check if it's in the temp directory
+                var tempPath = Path.GetTempPath();
+                var solutionDir = Path.GetDirectoryName(solutionPath);
+                
+                return !string.IsNullOrEmpty(solutionDir) && 
+                       solutionDir.StartsWith(tempPath, StringComparison.OrdinalIgnoreCase);
+            }
+            catch
+            {
+                return false;
+            }
+        }
 
-                            return VSConstants.S_FALSE; // Cancel original opening
-                        }
+        private int CountRealProjects(EnvDTE.Solution solution)
+        {
+            if (solution?.Projects == null)
+                return 0;
 
-                        _isProcessingSolution = false;
-                    }
-                }
-                catch (Exception ex)
+            int count = 0;
+            try
+            {
+                foreach (EnvDTE.Project project in solution.Projects)
                 {
-                    ErrorHandler.LogError("Error in OnBeforeOpenSolution", ex);
-                    _isProcessingSolution = false;
+                    try
+                    {
+                        // Skip solution folders and other non-project items
+                        if (!string.IsNullOrEmpty(project.FullName) &&
+                            !string.Equals(project.Kind, EnvDTE.Constants.vsProjectKindSolutionItems, StringComparison.OrdinalIgnoreCase) &&
+                            !string.Equals(project.Kind, "{2150E333-8FDC-42A3-9474-1A3956D46DE8}", StringComparison.OrdinalIgnoreCase)) // Solution Folder GUID
+                        {
+                            count++;
+                        }
+                    }
+                    catch
+                    {
+                        // Skip projects that can't be accessed (unloaded, etc.)
+                    }
                 }
             }
+            catch (Exception ex)
+            {
+                ErrorHandler.LogError("Error counting projects", ex);
+            }
+
+            return count;
+        }
+
+        public int OnBeforeOpenSolution(string pszSolutionFilename)
+        {
+            return VSConstants.S_OK;
+        }
+
+        public int OnAfterCloseSolution(object pUnkReserved)
+        {
+            ThreadHelper.ThrowIfNotOnUIThread();
+            
+            try
+            {
+                // Clear processed solutions when closing to allow fresh dialog on next open
+                _processedSolutions.Clear();
+                _isProcessingSolution = false;
+                ErrorHandler.LogInfo("Solution closed - cleared processed solutions cache");
+            }
+            catch (Exception ex)
+            {
+                ErrorHandler.LogError("Error in OnAfterCloseSolution", ex);
+            }
+
+            return VSConstants.S_OK;
+        }
+
+        public int OnBeforeCloseSolution(object pUnkReserved)
+        {
+            ThreadHelper.ThrowIfNotOnUIThread();
+            
+            try
+            {
+                // Reset processing flag when solution is about to close
+                _isProcessingSolution = false;
+            }
+            catch (Exception ex)
+            {
+                ErrorHandler.LogError("Error in OnBeforeCloseSolution", ex);
+            }
 
             return VSConstants.S_OK;
         }
 
-        // IVsSolutionLoadEvents implementation
+        // IVsSolutionLoadEvents implementation for more granular control
         public int OnBeforeOpenProject(ref Guid guidProjectID, ref Guid guidProjectType, string pszFileName, IVsSolutionLoadEvents pSolutionLoadEvents)
         {
             return VSConstants.S_OK;
@@ -158,24 +250,76 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
         }
 
         public int OnAfterBackgroundSolutionLoadComplete()
+        {
+            ThreadHelper.ThrowIfNotOnUIThread();
+            
+            // This is called when background loading is complete
+            // We could potentially move our dialog logic here for better timing
+            // but OnAfterOpenSolution should be sufficient for most cases
+            
+            return VSConstants.S_OK;
+        }
+
+        // Standard IVsSolutionEvents methods - minimal implementation
+        public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded)
+        {
+            return VSConstants.S_OK;
+        }
+
+        public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel)
+        {
+            return VSConstants.S_OK;
+        }
+
+        public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved)
+        {
+            return VSConstants.S_OK;
+        }
+
+        public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy)
+        {
+            return VSConstants.S_OK;
+        }
+
+        public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel)
+        {
+            return VSConstants.S_OK;
+        }
+
+        public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy)
+        {
+            return VSConstants.S_OK;
+        }
+
+        public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel)
+        {
+            return VSConstants.S_OK;
+        }
+
+        public int OnAfterMergeSolution(object pUnkReserved)
+        {
+            return VSConstants.S_OK;
+        }
+
+        // IVsSolutionEvents3 additional methods
+        public int OnBeforeOpeningChildren(IVsHierarchy pHierarchy)
+        {
+            return VSConstants.S_OK;
+        }
+
+        public int OnAfterOpeningChildren(IVsHierarchy pHierarchy)
+        {
+            return VSConstants.S_OK;
+        }
+
+        public int OnBeforeClosingChildren(IVsHierarchy pHierarchy)
         {
             return VSConstants.S_OK;
         }
 
-        // Other IVsSolutionEvents methods
-        public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) => VSConstants.S_OK;
-        public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) => VSConstants.S_OK;
-        public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) => VSConstants.S_OK;
-        public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) => VSConstants.S_OK;
-        public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) => VSConstants.S_OK;
-        public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) => VSConstants.S_OK;
-        public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel) => VSConstants.S_OK;
-        public int OnBeforeCloseSolution(object pUnkReserved) => VSConstants.S_OK;
-        public int OnAfterCloseSolution(object pUnkReserved) => VSConstants.S_OK;
-        public int OnAfterMergeSolution(object pUnkReserved) => VSConstants.S_OK;
-        public int OnBeforeOpeningChildren(IVsHierarchy pHierarchy) => VSConstants.S_OK;
-        public int OnAfterOpeningChildren(IVsHierarchy pHierarchy) => VSConstants.S_OK;
-        public int OnBeforeClosingChildren(IVsHierarchy pHierarchy) => VSConstants.S_OK;
-        public int OnAfterClosingChildren(IVsHierarchy pHierarchy) => VSConstants.S_OK;
+        public int OnAfterClosingChildren(IVsHierarchy pHierarchy)
+        {
+            return VSConstants.S_OK;
+        }
     }
 }

+ 89 - 25
FilteredSolutionsExtension/FilteredSolutionsExtension/SolutionParser.cs

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 
 namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
 {
-   public class SolutionParser
+    public class SolutionParser
     {
         private static readonly Regex ProjectRegex = new Regex(
             @"Project\(""{([^}]+)}""\)\s*=\s*""([^""]+)""\s*,\s*""([^""]+)""\s*,\s*""{([^}]+)}""",
@@ -18,7 +18,7 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
             RegexOptions.Compiled | RegexOptions.IgnoreCase);
 
         private static readonly Regex ProjectReferenceRegex = new Regex(
-            @"<ProjectReference\s+Include=""([^""]+)""\s*>.*?<Project>{([^}]+)}</Project>",
+            @"<ProjectReference\s+Include=""([^""]+)""\s*>.*?(?:<Project>{([^}]+)}</Project>)?",
             RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
 
         // Known project type GUIDs
@@ -27,31 +27,40 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
             "{2150E333-8FDC-42A3-9474-1A3956D46DE8}" // Solution Folder
         };
 
+        private Dictionary<string, List<string>> _allProjectDependencies = new Dictionary<string, List<string>>();
+
         public async Task<List<ProjectNode>> ParseSolutionAsync(string solutionPath)
         {
             if (!File.Exists(solutionPath))
                 throw new FileNotFoundException($"Solution file not found: {solutionPath}");
 
             var projects = new List<ProjectNode>();
-            var projectDependencies = new Dictionary<string, List<string>>();
+            _allProjectDependencies.Clear();
             
             try
             {
+                ErrorHandler.LogInfo($"Parsing solution: {Path.GetFileName(solutionPath)}");
+                
                 var content = await FileUtils.ReadAllTextAsync(solutionPath);
                 
                 // Parse projects first
                 ParseProjects(content, projects, solutionPath);
+                ErrorHandler.LogInfo($"Found {projects.Count} projects in solution");
                 
                 // Parse solution-level dependencies
-                ParseSolutionDependencies(content, projectDependencies);
+                ParseSolutionDependencies(content, _allProjectDependencies);
                 
                 // Parse project file dependencies for more accurate dependency graph
-                await ParseProjectFileDependencies(projects, projectDependencies);
+                await ParseProjectFileDependencies(projects, _allProjectDependencies);
+                
+                // Build the dependency tree
+                BuildDependencyTree(projects);
                 
                 return projects;
             }
             catch (Exception ex)
             {
+                ErrorHandler.LogError($"Failed to parse solution file: {solutionPath}", ex);
                 throw new InvalidOperationException($"Failed to parse solution file: {ex.Message}", ex);
             }
         }
@@ -60,30 +69,41 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
         {
             var projectLookup = projects.ToDictionary(p => p.ProjectGuid, StringComparer.OrdinalIgnoreCase);
 
+            // Clear existing dependencies
             foreach (var project in projects)
             {
-                // Build dependency relationships
-                var projectInfo = project; // Assuming ProjectNode has ProjectInfo property
-                if (projectInfo != null)
+                project.Dependencies.Clear();
+                project.Dependents.Clear();
+            }
+
+            // Build dependency relationships from parsed data
+            foreach (var kvp in _allProjectDependencies)
+            {
+                var projectGuid = kvp.Key;
+                var dependencyGuids = kvp.Value;
+
+                if (projectLookup.TryGetValue(projectGuid, out var project))
                 {
-                    foreach (var depGuid in GetProjectDependencies(project.ProjectGuid))
+                    foreach (var depGuid in dependencyGuids)
                     {
                         if (projectLookup.TryGetValue(depGuid, out var dependency))
                         {
-                            project.Dependencies.Add(dependency);
-                            dependency.Dependents.Add(project);
+                            if (!project.Dependencies.Contains(dependency))
+                            {
+                                project.Dependencies.Add(dependency);
+                            }
+                            if (!dependency.Dependents.Contains(project))
+                            {
+                                dependency.Dependents.Add(project);
+                            }
                         }
                     }
                 }
             }
-        }
 
-        private List<string> _projectDependencies = new List<string>();
-        
-        private List<string> GetProjectDependencies(string projectGuid)
-        {
-            // This would be populated during parsing
-            return _projectDependencies;
+            // Log dependency information
+            var totalDependencies = _allProjectDependencies.Sum(kvp => kvp.Value.Count);
+            ErrorHandler.LogInfo($"Built dependency tree with {totalDependencies} total dependencies");
         }
 
         public List<ProjectNode> GetProjectsWithDependencies(List<ProjectNode> selectedProjects)
@@ -96,7 +116,7 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
                 var current = toProcess.Dequeue();
                 if (result.Add(current))
                 {
-                    // Add dependencies
+                    // Add all dependencies recursively
                     foreach (var dependency in current.Dependencies)
                     {
                         if (!result.Contains(dependency))
@@ -107,6 +127,7 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
                 }
             }
 
+            ErrorHandler.LogInfo($"Expanded {selectedProjects.Count} selected projects to {result.Count} projects with dependencies");
             return result.ToList();
         }
 
@@ -118,6 +139,8 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
             
             var filteredSolutionPath = Path.Combine(tempDir, $"{solutionName}_filtered.sln");
             
+            ErrorHandler.LogInfo($"Creating filtered solution: {Path.GetFileName(filteredSolutionPath)} with {selectedProjects.Count} projects");
+            
             // Convert ProjectNode to ProjectInfo for generator
             var projectInfos = selectedProjects.Select(pn => new ProjectInfo
             {
@@ -130,6 +153,7 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
             var generator = new FilteredSolutionGenerator();
             await generator.GenerateFilteredSolutionAsync(originalSolutionPath, filteredSolutionPath, projectInfos);
             
+            ErrorHandler.LogInfo($"Successfully created filtered solution: {filteredSolutionPath}");
             return filteredSolutionPath;
         }
 
@@ -138,6 +162,8 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
             var projectMatches = ProjectRegex.Matches(content);
             var solutionDir = Path.GetDirectoryName(solutionPath);
 
+            ErrorHandler.LogInfo($"Parsing {projectMatches.Count} project entries from solution file");
+
             foreach (Match match in projectMatches)
             {
                 var typeGuid = match.Groups[1].Value;
@@ -147,7 +173,10 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
 
                 // Skip solution folders and other non-project items
                 if (SolutionFolderTypes.Contains(typeGuid))
+                {
+                    ErrorHandler.LogInfo($"Skipping solution folder: {name}");
                     continue;
+                }
 
                 // Resolve full path
                 var fullPath = Path.IsPathRooted(relativePath) 
@@ -157,7 +186,7 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
                 // Verify project file exists
                 if (!File.Exists(fullPath))
                 {
-                    System.Diagnostics.Debug.WriteLine($"Warning: Project file not found: {fullPath}");
+                    ErrorHandler.LogError($"Warning: Project file not found: {fullPath}", null);
                     continue;
                 }
 
@@ -176,6 +205,7 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
             var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
             string currentProjectGuid = null;
             bool inProjectDependencies = false;
+            int dependencyCount = 0;
 
             for (int i = 0; i < lines.Length; i++)
             {
@@ -213,14 +243,20 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
                     {
                         var dependentGuid = depMatch.Groups[2].Value;
                         AddDependency(projectDependencies, currentProjectGuid, dependentGuid);
+                        dependencyCount++;
                     }
                 }
             }
+
+            ErrorHandler.LogInfo($"Parsed {dependencyCount} solution-level dependencies");
         }
 
         private async Task ParseProjectFileDependencies(List<ProjectNode> projects, Dictionary<string, List<string>> projectDependencies)
         {
-            foreach (var project in projects.ToList())
+            int projectRefCount = 0;
+            int processedProjects = 0;
+
+            foreach (var project in projects)
             {
                 try
                 {
@@ -231,26 +267,54 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
 
                         foreach (Match match in referenceMatches)
                         {
-                            var referencedProjectGuid = match.Groups[2].Value;
-                            AddDependency(projectDependencies, project.ProjectGuid, referencedProjectGuid);
+                            var referencedProjectPath = match.Groups[1].Value;
+                            var referencedProjectGuid = match.Groups[2].Success ? match.Groups[2].Value : null;
+
+                            // If no GUID in reference, try to find it by path
+                            if (string.IsNullOrEmpty(referencedProjectGuid))
+                            {
+                                var fullRefPath = Path.IsPathRooted(referencedProjectPath) 
+                                    ? referencedProjectPath 
+                                    : Path.GetFullPath(Path.Combine(Path.GetDirectoryName(project.Path), referencedProjectPath));
+
+                                var referencedProject = projects.FirstOrDefault(p => 
+                                    string.Equals(p.Path, fullRefPath, StringComparison.OrdinalIgnoreCase));
+                                
+                                if (referencedProject != null)
+                                {
+                                    referencedProjectGuid = referencedProject.ProjectGuid;
+                                }
+                            }
+
+                            if (!string.IsNullOrEmpty(referencedProjectGuid))
+                            {
+                                AddDependency(projectDependencies, project.ProjectGuid, referencedProjectGuid);
+                                projectRefCount++;
+                            }
                         }
+                        processedProjects++;
                     }
                 }
                 catch (Exception ex)
                 {
-                    System.Diagnostics.Debug.WriteLine($"Warning: Could not parse project file {project.Path}: {ex.Message}");
+                    ErrorHandler.LogError($"Warning: Could not parse project file {project.Path}", ex);
                 }
             }
+
+            ErrorHandler.LogInfo($"Parsed {projectRefCount} project reference dependencies from {processedProjects} project files");
         }
 
         private void AddDependency(Dictionary<string, List<string>> projectDependencies, string projectGuid, string dependentGuid)
         {
+            if (string.IsNullOrEmpty(projectGuid) || string.IsNullOrEmpty(dependentGuid))
+                return;
+
             if (!projectDependencies.ContainsKey(projectGuid))
             {
                 projectDependencies[projectGuid] = new List<string>();
             }
             
-            if (!projectDependencies[projectGuid].Contains(dependentGuid))
+            if (!projectDependencies[projectGuid].Contains(dependentGuid, StringComparer.OrdinalIgnoreCase))
             {
                 projectDependencies[projectGuid].Add(dependentGuid);
             }

+ 27 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/StringToVisibilityConverter.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
+{
+    public class StringToVisibilityConverter : IValueConverter
+    {
+        public static readonly StringToVisibilityConverter Instance = new StringToVisibilityConverter();
+
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is string stringValue)
+            {
+                return string.IsNullOrWhiteSpace(stringValue) ? Visibility.Collapsed : Visibility.Visible;
+            }
+            
+            return value == null ? Visibility.Collapsed : Visibility.Visible;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}