From 00fc319c803fbc36410083272bc67ef09ffd1bbf Mon Sep 17 00:00:00 2001 From: Marco Cavallo Date: Wed, 4 Feb 2026 23:10:48 +0100 Subject: [PATCH 01/12] fix: add ViewModel-to-View DataTemplates in App.xaml - issue #94 --- src/PTRP.App/App.xaml | 55 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/PTRP.App/App.xaml b/src/PTRP.App/App.xaml index 47436be..78225e9 100644 --- a/src/PTRP.App/App.xaml +++ b/src/PTRP.App/App.xaml @@ -1,7 +1,15 @@ + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" + xmlns:vm="clr-namespace:PTRP.ViewModels;assembly=PTRP.ViewModels" + xmlns:vmPatients="clr-namespace:PTRP.ViewModels.Patients;assembly=PTRP.ViewModels" + xmlns:vmEducators="clr-namespace:PTRP.ViewModels.Educators;assembly=PTRP.ViewModels" + xmlns:vmProjects="clr-namespace:PTRP.ViewModels.Projects;assembly=PTRP.ViewModels" + xmlns:views="clr-namespace:PTRP.App.Views" + xmlns:viewsPatients="clr-namespace:PTRP.App.Views.Patients" + xmlns:viewsEducators="clr-namespace:PTRP.App.Views.Educators" + xmlns:viewsProjects="clr-namespace:PTRP.App.Views.Projects"> @@ -17,7 +25,50 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From dd3e35809be6aa1da90292d62f6110b562332d95 Mon Sep 17 00:00:00 2001 From: Marco Cavallo Date: Wed, 4 Feb 2026 23:14:27 +0100 Subject: [PATCH 02/12] fix: remove non-existent ViewModels from DataTemplates - issue #94 --- src/PTRP.App/App.xaml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/PTRP.App/App.xaml b/src/PTRP.App/App.xaml index 78225e9..27c3170 100644 --- a/src/PTRP.App/App.xaml +++ b/src/PTRP.App/App.xaml @@ -6,10 +6,11 @@ xmlns:vmPatients="clr-namespace:PTRP.ViewModels.Patients;assembly=PTRP.ViewModels" xmlns:vmEducators="clr-namespace:PTRP.ViewModels.Educators;assembly=PTRP.ViewModels" xmlns:vmProjects="clr-namespace:PTRP.ViewModels.Projects;assembly=PTRP.ViewModels" - xmlns:views="clr-namespace:PTRP.App.Views" + xmlns:viewsSetup="clr-namespace:PTRP.App.Views.Setup" xmlns:viewsPatients="clr-namespace:PTRP.App.Views.Patients" xmlns:viewsEducators="clr-namespace:PTRP.App.Views.Educators" - xmlns:viewsProjects="clr-namespace:PTRP.App.Views.Projects"> + xmlns:viewsProjects="clr-namespace:PTRP.App.Views.Projects" + xmlns:viewsSync="clr-namespace:PTRP.App.Views.Sync"> @@ -32,7 +33,7 @@ - + @@ -40,10 +41,6 @@ - - - - @@ -60,13 +57,13 @@ - + From b671123d9e1fdb1d1b09b3797c43388abe5e262e Mon Sep 17 00:00:00 2001 From: Marco Cavallo Date: Wed, 4 Feb 2026 23:16:21 +0100 Subject: [PATCH 03/12] feat: add ViewLocator for DI-based View resolution - issue #94 --- src/PTRP.App/Infrastructure/ViewLocator.cs | 120 +++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/PTRP.App/Infrastructure/ViewLocator.cs diff --git a/src/PTRP.App/Infrastructure/ViewLocator.cs b/src/PTRP.App/Infrastructure/ViewLocator.cs new file mode 100644 index 0000000..664067e --- /dev/null +++ b/src/PTRP.App/Infrastructure/ViewLocator.cs @@ -0,0 +1,120 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using Microsoft.Extensions.DependencyInjection; +using PTRP.App.Views.Educators; +using PTRP.App.Views.Patients; +using PTRP.App.Views.Projects; +using PTRP.App.Views.Setup; +using PTRP.App.Views.Sync; +using PTRP.ViewModels; +using PTRP.ViewModels.Educators; +using PTRP.ViewModels.Patients; +using PTRP.ViewModels.Projects; + +namespace PTRP.App.Infrastructure; + +/// +/// Locates and instantiates Views for ViewModels using Dependency Injection. +/// Used by ViewLocatorDataTemplateSelector to resolve Views from DI container. +/// Issue #94: Enables DataTemplate pattern with DI-based View constructors. +/// +public class ViewLocator +{ + private readonly IServiceProvider _serviceProvider; + + public ViewLocator(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + } + + /// + /// Creates a View instance for the given ViewModel, resolving dependencies via DI. + /// + /// The ViewModel instance + /// A UserControl instance with DataContext set to the ViewModel + public UserControl? CreateViewForViewModel(object? viewModel) + { + if (viewModel == null) + return null; + + UserControl? view = viewModel switch + { + // First Run / Setup + FirstRunViewModel => new FirstRunView(), + + // Patients Module + PatientListViewModel => _serviceProvider.GetRequiredService(), + + // Educators Module + EducatorListViewModel => _serviceProvider.GetRequiredService(), + + // Projects Module + ProjectListViewModel => _serviceProvider.GetRequiredService(), + ProjectFormViewModel => new ProjectFormView(), // ProjectFormView is created manually with specific patient context + + // Sync Module + SyncViewModel => _serviceProvider.GetRequiredService(), + + // Unknown ViewModel + _ => null + }; + + if (view != null) + { + view.DataContext = viewModel; + } + + return view; + } +} + +/// +/// DataTemplateSelector that uses ViewLocator to resolve Views from DI container. +/// Replaces static DataTemplates in App.xaml for DI-based View instantiation. +/// +public class ViewLocatorDataTemplateSelector : DataTemplateSelector +{ + private static ViewLocator? _viewLocator; + + /// + /// Sets the ViewLocator instance (called from App.xaml.cs on startup) + /// + public static void Initialize(IServiceProvider serviceProvider) + { + _viewLocator = new ViewLocator(serviceProvider); + } + + /// + /// Selects a DataTemplate by creating a View for the ViewModel via DI. + /// + public override DataTemplate SelectTemplate(object item, DependencyObject container) + { + if (_viewLocator == null) + { + throw new InvalidOperationException( + "ViewLocatorDataTemplateSelector not initialized. Call Initialize() in App.xaml.cs."); + } + + // Create a DataTemplate that instantiates the View via ViewLocator + var dataTemplate = new DataTemplate + { + VisualTree = new FrameworkElementFactory(typeof(ContentPresenter)) + }; + + // We can't use FrameworkElementFactory with DI, so we use a different approach: + // Return a template that creates a ContentControl and set its Content in code-behind + // Actually, we need to create the view directly here + + var view = _viewLocator.CreateViewForViewModel(item); + + if (view != null) + { + // Create a DataTemplate with the view as content + var factory = new FrameworkElementFactory(view.GetType()); + dataTemplate.VisualTree = factory; + } + + return dataTemplate; + } +} From bf3a292e3afcfdb1cc550a3eedc9c21b5d1f4e09 Mon Sep 17 00:00:00 2001 From: Marco Cavallo Date: Wed, 4 Feb 2026 23:16:38 +0100 Subject: [PATCH 04/12] refactor: simplify ViewLocator to direct View resolution - issue #94 --- src/PTRP.App/Infrastructure/ViewLocator.cs | 73 ++++------------------ 1 file changed, 13 insertions(+), 60 deletions(-) diff --git a/src/PTRP.App/Infrastructure/ViewLocator.cs b/src/PTRP.App/Infrastructure/ViewLocator.cs index 664067e..3483f16 100644 --- a/src/PTRP.App/Infrastructure/ViewLocator.cs +++ b/src/PTRP.App/Infrastructure/ViewLocator.cs @@ -1,5 +1,4 @@ using System; -using System.Windows; using System.Windows.Controls; using Microsoft.Extensions.DependencyInjection; using PTRP.App.Views.Educators; @@ -16,8 +15,8 @@ namespace PTRP.App.Infrastructure; /// /// Locates and instantiates Views for ViewModels using Dependency Injection. -/// Used by ViewLocatorDataTemplateSelector to resolve Views from DI container. -/// Issue #94: Enables DataTemplate pattern with DI-based View constructors. +/// Replaces DataTemplate approach when Views require constructor parameters. +/// Issue #94: Enables MVVM navigation with DI-based View constructors. /// public class ViewLocator { @@ -30,9 +29,10 @@ public ViewLocator(IServiceProvider serviceProvider) /// /// Creates a View instance for the given ViewModel, resolving dependencies via DI. + /// Sets the ViewModel as the View's DataContext. /// /// The ViewModel instance - /// A UserControl instance with DataContext set to the ViewModel + /// A UserControl instance with DataContext set to the ViewModel, or null if no matching View found public UserControl? CreateViewForViewModel(object? viewModel) { if (viewModel == null) @@ -43,20 +43,23 @@ public ViewLocator(IServiceProvider serviceProvider) // First Run / Setup FirstRunViewModel => new FirstRunView(), - // Patients Module + // Patients Module (requires IServiceProvider in constructor) PatientListViewModel => _serviceProvider.GetRequiredService(), - // Educators Module + // Educators Module (requires IServiceProvider in constructor) EducatorListViewModel => _serviceProvider.GetRequiredService(), - // Projects Module + // Projects Module (requires IServiceProvider in constructor) ProjectListViewModel => _serviceProvider.GetRequiredService(), - ProjectFormViewModel => new ProjectFormView(), // ProjectFormView is created manually with specific patient context + + // ProjectFormView is created manually with specific patient context, + // so it's not navigated to directly - it's opened in dialogs + ProjectFormViewModel => new ProjectFormView(), - // Sync Module + // Sync Module (requires IServiceProvider in constructor) SyncViewModel => _serviceProvider.GetRequiredService(), - // Unknown ViewModel + // Unknown ViewModel - return null _ => null }; @@ -68,53 +71,3 @@ public ViewLocator(IServiceProvider serviceProvider) return view; } } - -/// -/// DataTemplateSelector that uses ViewLocator to resolve Views from DI container. -/// Replaces static DataTemplates in App.xaml for DI-based View instantiation. -/// -public class ViewLocatorDataTemplateSelector : DataTemplateSelector -{ - private static ViewLocator? _viewLocator; - - /// - /// Sets the ViewLocator instance (called from App.xaml.cs on startup) - /// - public static void Initialize(IServiceProvider serviceProvider) - { - _viewLocator = new ViewLocator(serviceProvider); - } - - /// - /// Selects a DataTemplate by creating a View for the ViewModel via DI. - /// - public override DataTemplate SelectTemplate(object item, DependencyObject container) - { - if (_viewLocator == null) - { - throw new InvalidOperationException( - "ViewLocatorDataTemplateSelector not initialized. Call Initialize() in App.xaml.cs."); - } - - // Create a DataTemplate that instantiates the View via ViewLocator - var dataTemplate = new DataTemplate - { - VisualTree = new FrameworkElementFactory(typeof(ContentPresenter)) - }; - - // We can't use FrameworkElementFactory with DI, so we use a different approach: - // Return a template that creates a ContentControl and set its Content in code-behind - // Actually, we need to create the view directly here - - var view = _viewLocator.CreateViewForViewModel(item); - - if (view != null) - { - // Create a DataTemplate with the view as content - var factory = new FrameworkElementFactory(view.GetType()); - dataTemplate.VisualTree = factory; - } - - return dataTemplate; - } -} From edd4a33e9d9994231892a9b7ecfdae0bb9c18174 Mon Sep 17 00:00:00 2001 From: Marco Cavallo Date: Wed, 4 Feb 2026 23:17:16 +0100 Subject: [PATCH 05/12] feat: integrate ViewLocator in MainWindow for DI-based View resolution - issue #94 --- src/PTRP.App/MainWindow.xaml.cs | 40 ++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/PTRP.App/MainWindow.xaml.cs b/src/PTRP.App/MainWindow.xaml.cs index 7f6cc06..d351d79 100644 --- a/src/PTRP.App/MainWindow.xaml.cs +++ b/src/PTRP.App/MainWindow.xaml.cs @@ -1,6 +1,9 @@ +using System; +using System.ComponentModel; +using System.Windows; using MaterialDesignThemes.Wpf; +using PTRP.App.Infrastructure; using PTRP.ViewModels; -using System.Windows; namespace PTRP.App; @@ -12,19 +15,22 @@ namespace PTRP.App; /// 1. Collegamento del ViewModel (binding) /// 2. Setup MessageQueue per Snackbar /// 3. Gestione eventi notifica dal ViewModel +/// 4. Risoluzione View tramite ViewLocator (Issue #94) /// public partial class MainWindow : Window { private readonly MainViewModel _viewModel; + private readonly ViewLocator _viewLocator; /// - /// Costruttore - riceve il ViewModel via Dependency Injection + /// Costruttore - riceve il ViewModel e ViewLocator via Dependency Injection /// - public MainWindow(MainViewModel viewModel) + public MainWindow(MainViewModel viewModel, ViewLocator viewLocator) { InitializeComponent(); _viewModel = viewModel; + _viewLocator = viewLocator; // Imposta il ViewModel come DataContext DataContext = _viewModel; @@ -34,6 +40,33 @@ public MainWindow(MainViewModel viewModel) // Subscribe to notification events _viewModel.NotificationRequested += OnNotificationRequested; + + // Subscribe to CurrentViewModel changes to resolve Views via ViewLocator + _viewModel.PropertyChanged += OnViewModelPropertyChanged; + } + + /// + /// Intercetta i cambiamenti di CurrentViewModel e risolve la View tramite ViewLocator. + /// Issue #94: Permette Views con costruttori DI invece di DataTemplate statici. + /// + private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(MainViewModel.CurrentViewModel)) + { + // Risolvi la View per il ViewModel corrente tramite ViewLocator + var view = _viewLocator.CreateViewForViewModel(_viewModel.CurrentViewModel); + + // Imposta la View nel ContentControl + if (view != null) + { + ContentArea.Content = view; + } + else + { + // ViewModel sconosciuto o non implementato - mostra placeholder + ContentArea.Content = null; + } + } } /// @@ -77,6 +110,7 @@ private void OnNotificationRequested(object? sender, NotificationEventArgs e) protected override void OnClosed(EventArgs e) { _viewModel.NotificationRequested -= OnNotificationRequested; + _viewModel.PropertyChanged -= OnViewModelPropertyChanged; base.OnClosed(e); } } From 95c56991475b2dcf5500461d441aa08050bfeb36 Mon Sep 17 00:00:00 2001 From: Marco Cavallo Date: Wed, 4 Feb 2026 23:18:58 +0100 Subject: [PATCH 06/12] refactor: use named ContentControl for ViewLocator instead of DataTemplate binding - issue #94 --- src/PTRP.App/MainWindow.xaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PTRP.App/MainWindow.xaml b/src/PTRP.App/MainWindow.xaml index 8687b24..b77579a 100644 --- a/src/PTRP.App/MainWindow.xaml +++ b/src/PTRP.App/MainWindow.xaml @@ -340,9 +340,9 @@ - - + From 06e96531bcfc40ac1fd15ef5811553fbd1b107cb Mon Sep 17 00:00:00 2001 From: Marco Cavallo Date: Wed, 4 Feb 2026 23:19:19 +0100 Subject: [PATCH 07/12] refactor: remove DataTemplates from App.xaml, now using ViewLocator - issue #94 --- src/PTRP.App/App.xaml | 57 +++++++------------------------------------ 1 file changed, 9 insertions(+), 48 deletions(-) diff --git a/src/PTRP.App/App.xaml b/src/PTRP.App/App.xaml index 27c3170..7fc6918 100644 --- a/src/PTRP.App/App.xaml +++ b/src/PTRP.App/App.xaml @@ -1,16 +1,7 @@ + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"> @@ -27,46 +18,16 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 5783a5c8273bb8b8b079658c858eb92944bab612 Mon Sep 17 00:00:00 2001 From: Marco Cavallo Date: Wed, 4 Feb 2026 23:19:57 +0100 Subject: [PATCH 08/12] feat: register ViewLocator and SyncView in DI container - issue #94 --- src/PTRP.App/App.xaml.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/PTRP.App/App.xaml.cs b/src/PTRP.App/App.xaml.cs index d87584a..99a61ed 100644 --- a/src/PTRP.App/App.xaml.cs +++ b/src/PTRP.App/App.xaml.cs @@ -9,9 +9,11 @@ using PTRP.Data; using PTRP.Data.Repositories; using PTRP.Data.Repositories.Interfaces; +using PTRP.App.Infrastructure; using PTRP.App.Views.Patients; using PTRP.App.Views.Educators; using PTRP.App.Views.Projects; +using PTRP.App.Views.Sync; using System.IO; using System.Windows; @@ -112,6 +114,9 @@ private void ConfigureServices(ServiceCollection services) services.AddSingleton(); // Issue #46: Navigation Service services.AddScoped(); // Issue #49: Configuration Service + // Registra ViewLocator (Issue #94: DI-based View resolution) + services.AddSingleton(); + // Registra i ViewModels services.AddSingleton(); // Singleton per condividere stato app // TODO: Issue #49 - Uncomment when implemented @@ -125,11 +130,12 @@ private void ConfigureServices(ServiceCollection services) services.AddTransient(); // Issue #52: Sync ViewModel services.AddTransient(); // Issue #52: Conflict Resolution ViewModel - // Registra le Views + // Registra le Views (Issue #94: Views with DI-based constructors) services.AddScoped(); services.AddScoped(); // Issue #51/#74: Patient List View services.AddScoped(); // Issue #63: Educator List View services.AddScoped(); // Issue #64: Project List View + services.AddScoped(); // Issue #52: Sync View } /// From e5b61854d7aae8b68a49837699c4aee169373621 Mon Sep 17 00:00:00 2001 From: Marco Cavallo Date: Wed, 4 Feb 2026 23:23:09 +0100 Subject: [PATCH 09/12] feat: add ProjectStateToColorConverter for ProjectListView - issue #94 --- .../ProjectStateToColorConverter.cs | 58 ++++++++----------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/src/PTRP.App/Converters/ProjectStateToColorConverter.cs b/src/PTRP.App/Converters/ProjectStateToColorConverter.cs index e529698..c9506ab 100644 --- a/src/PTRP.App/Converters/ProjectStateToColorConverter.cs +++ b/src/PTRP.App/Converters/ProjectStateToColorConverter.cs @@ -2,45 +2,33 @@ using System.Globalization; using System.Windows.Data; using System.Windows.Media; +using PTRP.Models.Enums; -namespace PTRP.App.Converters +namespace PTRP.App.Converters; + +/// +/// Converts ProjectStatus enum to a Color brush for UI display. +/// Used in ProjectListView for status badges. +/// +public class ProjectStateToColorConverter : IValueConverter { - /// - /// Converts project state string to color brush for badge display. - /// Used in PatientListView DataGrid to show colored status badges. - /// - public class ProjectStateToColorConverter : IValueConverter + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - /// - /// Converts project state to color brush. - /// - /// Project state string (Active, Suspended, Completed, Deceased, None) - /// Target type (Brush) - /// Optional parameter - /// Culture info - /// SolidColorBrush for the badge background - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is not string state) - return new SolidColorBrush(Colors.Gray); - - return state switch - { - "Active" => new SolidColorBrush(Color.FromRgb(76, 175, 80)), // Material Green 500 - "Suspended" => new SolidColorBrush(Color.FromRgb(255, 193, 7)), // Material Amber 500 - "Completed" => new SolidColorBrush(Color.FromRgb(158, 158, 158)), // Material Grey 500 - "Deceased" => new SolidColorBrush(Color.FromRgb(244, 67, 54)), // Material Red 500 - "None" => new SolidColorBrush(Color.FromRgb(189, 189, 189)), // Material Grey 400 - _ => new SolidColorBrush(Colors.Gray) - }; - } + if (value is not ProjectStatus status) + return Brushes.Gray; - /// - /// Not implemented (one-way binding only). - /// - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + return status switch { - throw new NotImplementedException("ProjectStateToColorConverter is one-way only."); - } + ProjectStatus.Active => new SolidColorBrush(Color.FromRgb(40, 167, 69)), // Green #28A745 + ProjectStatus.Suspended => new SolidColorBrush(Color.FromRgb(255, 193, 7)), // Yellow #FFC107 + ProjectStatus.Completed => new SolidColorBrush(Color.FromRgb(0, 123, 255)), // Blue #007BFF + ProjectStatus.Deceased => new SolidColorBrush(Color.FromRgb(108, 117, 125)), // Gray #6C757D + _ => Brushes.Gray + }; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException("ProjectStateToColorConverter does not support ConvertBack"); } } From badbeb7fefde8b5bc9acd89cfad3a885a55ad50b Mon Sep 17 00:00:00 2001 From: Marco Cavallo Date: Wed, 4 Feb 2026 23:23:29 +0100 Subject: [PATCH 10/12] feat: add NullToVisibilityConverter for ProjectListView - issue #94 --- .../Converters/NullToVisibilityConverter.cs | 60 +++++++------------ 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/src/PTRP.App/Converters/NullToVisibilityConverter.cs b/src/PTRP.App/Converters/NullToVisibilityConverter.cs index b74c121..cffbf4b 100644 --- a/src/PTRP.App/Converters/NullToVisibilityConverter.cs +++ b/src/PTRP.App/Converters/NullToVisibilityConverter.cs @@ -3,51 +3,35 @@ using System.Windows; using System.Windows.Data; -namespace PTRP.App.Converters +namespace PTRP.App.Converters; + +/// +/// Converts null to Visibility.Collapsed and non-null to Visibility.Visible. +/// Used throughout the app for conditional visibility based on object presence. +/// +public class NullToVisibilityConverter : IValueConverter { /// - /// Converts null/empty values to Visibility enum. - /// Used to hide UI elements when data is not available. + /// When true, inverts the logic: null = Visible, non-null = Collapsed /// - public class NullToVisibilityConverter : IValueConverter - { - /// - /// Converts null/empty value to Visibility. - /// - /// Value to check (object, string, etc.) - /// Target type (Visibility) - /// Optional parameter ("Invert" to reverse logic) - /// Culture info - /// Visibility.Visible if value is not null/empty, Visibility.Collapsed otherwise - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - bool isNull = value == null; - - // Check for empty strings - if (!isNull && value is string str) - { - isNull = string.IsNullOrWhiteSpace(str); - } + public bool Invert { get; set; } = false; - // Check for parameter to invert logic - bool invert = parameter is string param && param.Equals("Invert", StringComparison.OrdinalIgnoreCase); + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + bool isNull = value == null; - if (invert) - { - return isNull ? Visibility.Visible : Visibility.Collapsed; - } - else - { - return isNull ? Visibility.Collapsed : Visibility.Visible; - } + if (Invert) + { + return isNull ? Visibility.Visible : Visibility.Collapsed; } - - /// - /// Not implemented (one-way binding only). - /// - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + else { - throw new NotImplementedException("NullToVisibilityConverter is one-way only."); + return isNull ? Visibility.Collapsed : Visibility.Visible; } } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException("NullToVisibilityConverter does not support ConvertBack"); + } } From 155d662c13cd6ad06527b0305c740295408340e8 Mon Sep 17 00:00:00 2001 From: Marco Cavallo Date: Wed, 4 Feb 2026 23:23:55 +0100 Subject: [PATCH 11/12] feat: register converters in App.xaml resources - issue #94 --- src/PTRP.App/App.xaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/PTRP.App/App.xaml b/src/PTRP.App/App.xaml index 7fc6918..ee5ba55 100644 --- a/src/PTRP.App/App.xaml +++ b/src/PTRP.App/App.xaml @@ -1,7 +1,8 @@ + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" + xmlns:converters="clr-namespace:PTRP.App.Converters"> @@ -14,6 +15,11 @@ + + + + + From 9cbdc7c92f498ba34679d908ebe5a50e7b4c1074 Mon Sep 17 00:00:00 2001 From: Marco Cavallo Date: Wed, 4 Feb 2026 23:27:18 +0100 Subject: [PATCH 12/12] fix: use correct enum name TherapyProjectState instead of ProjectStatus - issue #94 --- .../Converters/ProjectStateToColorConverter.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/PTRP.App/Converters/ProjectStateToColorConverter.cs b/src/PTRP.App/Converters/ProjectStateToColorConverter.cs index c9506ab..a64dcd5 100644 --- a/src/PTRP.App/Converters/ProjectStateToColorConverter.cs +++ b/src/PTRP.App/Converters/ProjectStateToColorConverter.cs @@ -7,22 +7,22 @@ namespace PTRP.App.Converters; /// -/// Converts ProjectStatus enum to a Color brush for UI display. +/// Converts TherapyProjectState enum to a Color brush for UI display. /// Used in ProjectListView for status badges. /// public class ProjectStateToColorConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - if (value is not ProjectStatus status) + if (value is not TherapyProjectState status) return Brushes.Gray; return status switch { - ProjectStatus.Active => new SolidColorBrush(Color.FromRgb(40, 167, 69)), // Green #28A745 - ProjectStatus.Suspended => new SolidColorBrush(Color.FromRgb(255, 193, 7)), // Yellow #FFC107 - ProjectStatus.Completed => new SolidColorBrush(Color.FromRgb(0, 123, 255)), // Blue #007BFF - ProjectStatus.Deceased => new SolidColorBrush(Color.FromRgb(108, 117, 125)), // Gray #6C757D + TherapyProjectState.Active => new SolidColorBrush(Color.FromRgb(40, 167, 69)), // Green #28A745 + TherapyProjectState.Suspended => new SolidColorBrush(Color.FromRgb(255, 193, 7)), // Yellow #FFC107 + TherapyProjectState.Completed => new SolidColorBrush(Color.FromRgb(0, 123, 255)), // Blue #007BFF + TherapyProjectState.Deceased => new SolidColorBrush(Color.FromRgb(108, 117, 125)), // Gray #6C757D _ => Brushes.Gray }; }