Переглянути джерело

Refactor: migrate `FileAssociationsViewModel` and `FileAssociationsView` to R3. Replace ReactiveUI with `BindableReactiveProperty`, update bindings, clean up obsolete logic, and handle file type associations more efficiently.

Ruben 4 місяців тому
батько
коміт
e79d494358

+ 0 - 3
src/PicView.Avalonia/StartUp/StartUpHelper.cs

@@ -17,7 +17,6 @@ using PicView.Avalonia.WindowBehavior;
 using PicView.Core.FileAssociations;
 using PicView.Core.FileHistory;
 using PicView.Core.ProcessHandling;
-using PicView.Core.ViewModels;
 
 namespace PicView.Avalonia.StartUp;
 
@@ -159,8 +158,6 @@ public static class StartUpHelper
         }
 
         Application.Current.Name = "PicView";
-        
-        vm.AssociationsViewModel ??= new FileAssociationsViewModel();
 
         if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
         {

+ 5 - 5
src/PicView.Avalonia/Views/FileAssociationsView.axaml

@@ -20,17 +20,17 @@
 
         <uc:SpinWaiter
             HorizontalAlignment="Center"
-            IsVisible="{CompiledBinding AssociationsViewModel.IsProcessing,
+            IsVisible="{CompiledBinding AssociationsViewModel.IsProcessing.Value,
                                         Mode=OneWay}"
             VerticalAlignment="Center" />
 
         <DockPanel
             Focusable="True"
             HorizontalAlignment="Center"
-            IsHitTestVisible="{CompiledBinding !AssociationsViewModel.IsProcessing,
+            IsHitTestVisible="{CompiledBinding !AssociationsViewModel.IsProcessing.Value,
                                                Mode=OneWay}"
             LastChildFill="True"
-            Opacity="{CompiledBinding AssociationsViewModel.Opacity,
+            Opacity="{CompiledBinding AssociationsViewModel.Opacity.Value,
                                       Mode=OneWay}">
 
             <TextBlock
@@ -74,7 +74,7 @@
                         Foreground="{DynamicResource MainTextColor}"
                         HorizontalAlignment="Left"
                         Padding="23,7,4,6"
-                        Text="{CompiledBinding AssociationsViewModel.FilterText,
+                        Text="{CompiledBinding AssociationsViewModel.FilterText.Value,
                                                FallbackValue='',
                                                Mode=TwoWay}"
                         Watermark="{CompiledBinding Translation.Filter.Value,
@@ -91,7 +91,7 @@
                         HorizontalAlignment="Right"
                         IconHeight="12"
                         IconWidth="12"
-                        IsVisible="{CompiledBinding AssociationsViewModel.FilterText,
+                        IsVisible="{CompiledBinding AssociationsViewModel.FilterText.Value,
                                                     Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
                         Padding="10,3,10,3"
                         x:Name="ClearButton" />

+ 12 - 24
src/PicView.Avalonia/Views/FileAssociationsView.axaml.cs

@@ -1,6 +1,4 @@
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
-using Avalonia;
+using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Media;
@@ -9,7 +7,8 @@ using PicView.Avalonia.ViewModels;
 using PicView.Core.FileAssociations;
 using PicView.Core.Localization;
 using PicView.Core.ViewModels;
-using ReactiveUI;
+using R3;
+using Observable = R3.Observable;
 
 namespace PicView.Avalonia.Views;
 
@@ -30,10 +29,8 @@ public partial class FileAssociationsView : UserControl
             _ => 240
         };
 
-        AttachedToVisualTree += delegate
+        Loaded += delegate
         {
-            FilterBox.TextChanged += FilterBox_TextChanged;
-            
             InitializeCheckBoxesCollection();
 
             KeyDown += (_, e) =>
@@ -59,9 +56,9 @@ public partial class FileAssociationsView : UserControl
         vm.AssociationsViewModel ??= new FileAssociationsViewModel();
             
         // Subscribe to changes in the filter text
-        vm.AssociationsViewModel.WhenAnyValue(x => x.FilterText)
+        Observable.EveryValueChanged(vm.AssociationsViewModel, x => x.FilterText.Value, UIHelper.GetFrameProvider)
             .Subscribe(FilterCheckBoxes)
-            .DisposeWith(_disposables);
+            .AddTo(_disposables);
             
         // Create checkboxes for each file type group and item
         foreach (var fileTypeGroup in vm.AssociationsViewModel.FileTypeGroups)
@@ -165,24 +162,23 @@ public partial class FileAssociationsView : UserControl
                 };
                     
                 // Subscribe to changes in the file type's IsSelected property
-                fileType.WhenAnyValue(x => x.IsSelected)
-                    .ObserveOn(RxApp.MainThreadScheduler)
-                    .Subscribe(isSelected =>
+                Observable.EveryValueChanged(fileType, x => x.IsSelected, UIHelper.GetFrameProvider)
+                    .Subscribe( isSelected =>
                     {
                         fileCheckBox.IsChecked = isSelected;
                     })
-                    .DisposeWith(_disposables);
+                    .AddTo(_disposables);
                     
                 // Subscribe to changes in the file type's IsVisible property
-                fileType.WhenAnyValue(x => x.IsVisible)
-                    .ObserveOn(RxApp.MainThreadScheduler)
+                Observable.EveryValueChanged(fileType, x => x.IsVisible, UIHelper.GetFrameProvider)
                     .Subscribe(isVisible =>
                     {
                         fileCheckBox.IsVisible = isVisible;
                     })
-                    .DisposeWith(_disposables);
+                    .AddTo(_disposables);
             }
         }
+        
     }
     
     private void UpdateGroupCheckboxState(FileTypeGroup group)
@@ -306,14 +302,6 @@ public partial class FileAssociationsView : UserControl
         return null;
     }
         
-    private void FilterBox_TextChanged(object? sender, EventArgs e)
-    {
-        if (DataContext is MainViewModel vm)
-        {
-            vm.AssociationsViewModel.FilterText = FilterBox.Text;
-        }
-    }
-        
     private void FilterCheckBoxes(string? filterText)
     {
         if (string.IsNullOrWhiteSpace(filterText))

+ 2 - 2
src/PicView.Avalonia/Views/GeneralSettingsView.axaml

@@ -49,7 +49,7 @@
                 Background="Transparent"
                 BorderThickness="0"
                 Classes="altHover changeColor"
-                IsChecked="{CompiledBinding SettingsViewModel.IsShowingPermanentDeletionDialog}"
+                IsChecked="{CompiledBinding SettingsViewModel.IsShowingPermanentDeletionDialog.Value}"
                 Margin="0,0,0,10"
                 Width="300">
                 <TextBlock
@@ -66,7 +66,7 @@
                 Background="Transparent"
                 BorderThickness="0"
                 Classes="altHover changeColor"
-                IsChecked="{CompiledBinding SettingsViewModel.IsShowingRecycleDialog}"
+                IsChecked="{CompiledBinding SettingsViewModel.IsShowingRecycleDialog.Value}"
                 Margin="0,0,0,10"
                 Width="300">
                 <TextBlock

+ 1 - 1
src/PicView.Avalonia/Views/ImageSettingsView.axaml

@@ -62,7 +62,7 @@
             BorderThickness="0"
             Classes="altHover changeColor"
             Command="{CompiledBinding ShowSideBySideCommand}"
-            IsChecked="{CompiledBinding PicViewer.IsShowingSideBySide}"
+            IsChecked="{CompiledBinding PicViewer.IsShowingSideBySide.Value}"
             Margin="0,0,0,10"
             Width="300">
             <TextBlock

+ 3 - 5
src/PicView.Core/FileAssociations/FileAssociationProcessor.cs

@@ -1,10 +1,8 @@
-using System.Collections.ObjectModel;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
 using System.Security.Principal;
 using System.Text.Json;
 using System.Text.Json.Serialization;
-using PicView.Core.FileHandling;
 using PicView.Core.ProcessHandling;
 
 namespace PicView.Core.FileAssociations;
@@ -29,7 +27,7 @@ public static class FileAssociationProcessor
     /// </summary>
     /// <param name="groups">Collection of file type groups to process</param>
     /// <returns>True if successful, false otherwise</returns>
-    public static async Task<bool> SetFileAssociations(ReadOnlyObservableCollection<FileTypeGroup> groups)
+    public static async Task<bool> SetFileAssociations(List<FileTypeGroup> groups)
     {
         try
         {
@@ -84,7 +82,7 @@ public static class FileAssociationProcessor
 
     #region Private Helper Methods
 
-    private static async Task<bool> HandleNonAdminWindowsAssociations(ReadOnlyObservableCollection<FileTypeGroup> groups)
+    private static async Task<bool> HandleNonAdminWindowsAssociations(List<FileTypeGroup> groups)
     {
         try
         {
@@ -161,7 +159,7 @@ public static class FileAssociationProcessor
         }
     }
 
-    private static async Task<bool> HandleDirectAssociations(ReadOnlyObservableCollection<FileTypeGroup> groups)
+    private static async Task<bool> HandleDirectAssociations(List<FileTypeGroup> groups)
     {
         foreach (var group in groups)
         {

+ 90 - 257
src/PicView.Core/ViewModels/FileAssociationsViewModel.cs

@@ -1,230 +1,121 @@
-using System.Collections.ObjectModel;
-using System.Diagnostics;
-using System.Reactive;
-using System.Reactive.Linq;
-using DynamicData;
-using PicView.Core.FileAssociations;
-using ReactiveUI;
+using PicView.Core.FileAssociations;
+using R3;
+
+// For ToArray, FirstOrDefault, etc
 
 namespace PicView.Core.ViewModels;
 
 /// <summary>
-/// View model for managing file associations in PicView.
+/// View model for managing file associations in PicView, using CySharp/R3.
 /// Handles the binding between UI checkboxes and file type data.
 /// </summary>
-public class FileAssociationsViewModel : ReactiveObject
+public class FileAssociationsViewModel : IDisposable
 {
-    private readonly ReadOnlyObservableCollection<FileTypeGroup> _fileTypeGroups;
-    private readonly SourceList<FileTypeGroup> _fileTypeGroupsList = new();
+    private readonly CompositeDisposable _disposables = new();
+
+    public FileAssociationsViewModel()
+    {
+        // Create file type groups and populate with data
+        FileTypeGroups.AddRange(FileTypeGroupHelper.GetFileTypes());
+
+        // CanExecute as observable
+        var canExecute = IsProcessing
+            .AsObservable()
+            .Select(processing => !processing);
+
+        // Commands
+        ApplyCommand = canExecute
+            .ToReactiveCommand(async _ => await ApplyFileAssociations())
+            .AddTo(_disposables);
+
+        UnassociateCommand = canExecute
+            .ToReactiveCommand(async _ => { await UnassociateFileAssociations(); })
+            .AddTo(_disposables);
+
+        ClearFilterCommand = canExecute
+            .ToReactiveCommand(_ => { FilterText.Value = string.Empty; })
+            .AddTo(_disposables);
+
+        ResetCommand = canExecute
+            .ToReactiveCommand(_ => { ResetFileTypesToDefault(); })
+            .AddTo(_disposables);
+
+        SelectAllCommand = canExecute
+            .ToReactiveCommand(_ => { SelectAllFileTypes(); })
+            .AddTo(_disposables);
+
+        UnselectAllCommand = canExecute
+            .ToReactiveCommand(_ => { UnselectAllFileTypes(); })
+            .AddTo(_disposables);
+
+        // Opacity reacts to IsProcessing
+        IsProcessing
+            .AsObservable()
+            .Subscribe(isProcessing => { Opacity.Value = isProcessing ? 0.3 : 1.0; })
+            .AddTo(_disposables);
+    }
 
     /// <summary>
     /// Gets the read-only collection of file type groups that are available for association.
     /// </summary>
-    public ReadOnlyObservableCollection<FileTypeGroup> FileTypeGroups => _fileTypeGroups;
+    public List<FileTypeGroup> FileTypeGroups { get; } = [];
 
     /// <summary>
     /// Gets or sets the filter text used to search and filter file type groups and items.
     /// </summary>
-    public string? FilterText
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    } = string.Empty;
+    public BindableReactiveProperty<string?> FilterText { get; } = new(string.Empty);
 
     /// <summary>
     /// Gets or sets a value indicating whether the view model is currently processing an operation.
     /// Used to disable UI interaction during long-running tasks.
     /// </summary>
-    public bool IsProcessing
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    }
+    public BindableReactiveProperty<bool> IsProcessing { get; } = new(false);
 
     /// <summary>
     /// Gets or sets the opacity value for the UI, used to visually indicate processing state.
     /// </summary>
-    public double Opacity
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    } = 1.0;
+    public BindableReactiveProperty<double> Opacity { get; } = new(1.0);
 
     /// <summary>
     /// Command to apply the selected file associations.
     /// </summary>
-    public ReactiveCommand<Unit, bool> ApplyCommand { get; }
+    public ReactiveCommand? ApplyCommand { get; }
 
     /// <summary>
     /// Command to clear the current filter text.
     /// </summary>
-    public ReactiveCommand<Unit, string> ClearFilterCommand { get; }
+    public ReactiveCommand? ClearFilterCommand { get; }
 
     /// <summary>
     /// Command to unassociate all file types from the application.
     /// </summary>
-    public ReactiveCommand<Unit, Unit> UnassociateCommand { get; }
+    public ReactiveCommand? UnassociateCommand { get; }
 
     /// <summary>
     /// Command to reset file type selections to their default state.
     /// </summary>
-    public ReactiveCommand<Unit, Unit> ResetCommand { get; }
+    public ReactiveCommand? ResetCommand { get; }
 
     /// <summary>
     /// Command to select all visible file types.
     /// </summary>
-    public ReactiveCommand<Unit, Unit> SelectAllCommand { get; }
+    public ReactiveCommand? SelectAllCommand { get; }
 
     /// <summary>
     /// Command to unselect all visible file types.
     /// </summary>
-    public ReactiveCommand<Unit, Unit> UnselectAllCommand { get; }
-    
-    /// <summary>
-    /// Initializes a new instance of the <see cref="FileAssociationsViewModel"/> class.
-    /// Sets up file type groups, commands, and filtering behavior.
-    /// </summary>
-    public FileAssociationsViewModel()
-    {
-        // Create file type groups and populate with data
-        InitializeFileTypes();
-        
-        // Setup the filtering
-        var filter = this.WhenAnyValue(x => x.FilterText)
-            .Throttle(TimeSpan.FromMilliseconds(200))
-            .Select(BuildFilter);
-            
-        _fileTypeGroupsList.Connect()
-            .AutoRefresh()
-            .Filter(filter)
-            .Bind(out _fileTypeGroups)
-            .Subscribe();
-
-        // CanExecute for commands
-        var canExecute = this.WhenAnyValue(x => x.IsProcessing)
-            .Select(processing => !processing);
-            
-        // Initialize commands with error handling
-        ApplyCommand = ReactiveCommand.CreateFromTask(
-            ApplyFileAssociations, 
-            canExecute);
-            
-        // Handle errors from the Apply command
-        ApplyCommand.ThrownExceptions
-            .Subscribe(ex => 
-            {
-                IsProcessing = false;
-#if DEBUG
-                Debug.WriteLine($"Error in ApplyCommand: {ex}");
-#endif
-            });
-        
-        UnassociateCommand = ReactiveCommand.CreateFromTask(
-            UnassociateFileAssociations, 
-            canExecute);
-        
-        UnassociateCommand.ThrownExceptions
-            .Subscribe(ex => 
-            {
-                IsProcessing = false;
-                Debug.WriteLine($"Error in UnassociateCommand: {ex}");
-            });
-            
-        ClearFilterCommand = ReactiveCommand.Create(() => FilterText = string.Empty);
-        
-        ResetCommand = ReactiveCommand.Create(ResetFileTypesToDefault, canExecute);
-        
-        ResetCommand.ThrownExceptions
-            .Subscribe(ex =>
-            {
-                Debug.WriteLine($"Error in ResetCommand: {ex}");
-            });
-        
-        SelectAllCommand = ReactiveCommand.Create(SelectAllFileTypes, canExecute);
-        
-        SelectAllCommand.ThrownExceptions
-            .Subscribe(ex =>
-            {
-                Debug.WriteLine($"Error in SelectAllCommand: {ex}");
-            });
-        
-        UnselectAllCommand = ReactiveCommand.Create(UnselectAllFileTypes, canExecute);
-        
-        UnselectAllCommand.ThrownExceptions
-            .Subscribe(ex =>
-            {
-                Debug.WriteLine($"Error in UnselectAllCommand: {ex}");
-            });
-        
-        this.WhenAnyValue(x => x.IsProcessing).Subscribe(isProcessing =>
-        {
-            Opacity = isProcessing ? 0.3 : 1.0;
-        });
-    }
+    public ReactiveCommand? UnselectAllCommand { get; }
 
-    #region Initialize and filtering
-    
-    /// <summary>
-    /// Initializes file type groups by loading default file types from <see cref="FileTypeGroupHelper"/>.
-    /// </summary>
-    private void InitializeFileTypes()
+    public void Dispose()
     {
-        var groups = FileTypeGroupHelper.GetFileTypes();
-        
-        _fileTypeGroupsList.Edit(list =>
-        {
-            list.Clear();
-            list.AddRange(groups);
-        });
+        Disposable.Dispose(IsProcessing, Opacity, ApplyCommand, ClearFilterCommand, UnassociateCommand, ResetCommand,
+            SelectAllCommand, UnselectAllCommand);
     }
-    
-    /// <summary>
-    /// Builds a filter function for file type groups based on the provided filter text.
-    /// </summary>
-    /// <param name="filter">The filter text to search for in file type descriptions and extensions.</param>
-    /// <returns>A function that determines if a file type group should be visible based on the filter.</returns>
-    private Func<FileTypeGroup, bool> BuildFilter(string? filter)
-    {
-        if (string.IsNullOrWhiteSpace(filter))
-        {
-            // Reset all items to visible when filter is empty
-            foreach (var group in _fileTypeGroupsList.Items)
-            {
-                foreach (var item in group.FileTypes)
-                {
-                    item.IsVisible = true;
-                }
-            }
-            return _ => true;
-        }
-        
-        return group => {
-            // Update visibility of items based on filter
-            var anyVisible = false;
-            foreach (var item in group.FileTypes)
-            {
-                item.IsVisible = item.Description.Contains(filter, StringComparison.OrdinalIgnoreCase) || 
-                                 item.Extension.Contains(filter, StringComparison.OrdinalIgnoreCase);
-                if (item.IsVisible)
-                    anyVisible = true;
-            }
-        
-            // Only show groups that have at least one visible item
-            return anyVisible;
-        };
-    }
-    
-    #endregion
-    
+
     #region Selection
-    
-    /// <summary>
-    /// Updates all selection states to ensure changes are properly reflected in the UI.
-    /// This method forces property notifications to be sent for all selection states.
-    /// </summary>
+
     private void UpdateSelection()
     {
-        // Force property notifications to ensure all changes are processed
         foreach (var group in FileTypeGroups)
         {
             group.IsSelected = group.IsSelected;
@@ -234,37 +125,28 @@ public class FileAssociationsViewModel : ReactiveObject
             }
         }
     }
-    
-    /// <summary>
-    /// Resets all file type selections to their default state as defined by <see cref="FileTypeGroupHelper.GetFileTypes"/>.
-    /// </summary>
-    /// <remarks>
-    /// This method uses snapshots of collections to avoid enumeration modification exceptions.
-    /// </remarks>
+
     private void ResetFileTypesToDefault()
     {
-        // Get fresh default file types
         var defaultGroups = FileTypeGroupHelper.GetFileTypes();
-        
-        // Use snapshot to get current groups to avoid enumeration issues
         var currentGroups = FileTypeGroups.ToArray();
-        
+
         foreach (var group in currentGroups)
         {
             var defaultGroup = defaultGroups.FirstOrDefault(g => g.Name == group.Name);
             if (defaultGroup == null)
+            {
                 continue;
-                
-            // Update the group selection state
+            }
+
             group.IsSelected = defaultGroup.IsSelected;
-            
-            // Create snapshot of file types to avoid enumeration issues
+
             var fileTypes = group.FileTypes.ToArray();
             foreach (var fileType in fileTypes)
             {
-                var defaultType = defaultGroup.FileTypes.FirstOrDefault(dt => 
+                var defaultType = defaultGroup.FileTypes.FirstOrDefault(dt =>
                     dt.Description == fileType.Description);
-                
+
                 if (defaultType != null)
                 {
                     fileType.IsSelected = defaultType.IsSelected;
@@ -273,24 +155,13 @@ public class FileAssociationsViewModel : ReactiveObject
         }
     }
 
-    /// <summary>
-    /// Unselects all file types by setting their selection state to false.
-    /// Used before unassociating all file types from the application.
-    /// </summary>
-    /// <remarks>
-    /// This method uses snapshots of collections to avoid enumeration modification exceptions.
-    /// </remarks>
     private void UnselectFileTypes()
     {
-        // Make a copy of the current groups to avoid enumeration issues
         var currentGroups = FileTypeGroups.ToArray();
-    
-        // Update selection states to false for all items
+
         foreach (var group in currentGroups)
         {
             group.IsSelected = false;
-            
-            // Use snapshot of file types to avoid enumeration issues
             var fileTypes = group.FileTypes.ToArray();
             foreach (var fileType in fileTypes)
             {
@@ -298,58 +169,37 @@ public class FileAssociationsViewModel : ReactiveObject
             }
         }
     }
-    
-    /// <summary>
-    /// Selects all visible file types except for archive types which are handled specially.
-    /// </summary>
-    /// <remarks>
-    /// This method uses snapshots of collections to avoid enumeration modification exceptions.
-    /// Archive types (.zip, .rar, etc.) are not automatically selected to avoid unwanted associations.
-    /// </remarks>
+
     private void SelectAllFileTypes()
     {
-        // Make a copy of the current groups to avoid enumeration issues
         var currentGroups = FileTypeGroups.ToArray();
-    
-        // Update selection states to true for all items
+
         foreach (var group in currentGroups)
         {
-            // Use snapshot of file types to avoid enumeration issues
             var fileTypes = group.FileTypes.ToArray();
             foreach (var fileType in fileTypes)
             {
                 if (!fileType.IsVisible)
                 {
-                    // We don't want to select hidden items
                     continue;
                 }
-                // Only set archive types explicitly
+
                 if (fileType.Extension.StartsWith(".zip") ||
                     fileType.Extension.StartsWith(".rar") ||
                     fileType.Extension.StartsWith(".7z") ||
-                    fileType.Extension.StartsWith(".gzip")) 
+                    fileType.Extension.StartsWith(".gzip"))
                 {
                     continue;
                 }
+
                 fileType.IsSelected = true;
             }
         }
     }
 
-    /// <summary>
-    /// Toggles selection state of all visible file types between indeterminate and unselected.
-    /// If the number of indeterminate checkboxes equals or is greater than the number of non-indeterminate ones,
-    /// all visible checkboxes will be set to unchecked. Otherwise, all will be set to indeterminate.
-    /// </summary>
-    /// <remarks>
-    /// This method uses snapshots of collections to avoid enumeration modification exceptions.
-    /// </remarks>
     private void UnselectAllFileTypes()
     {
-        // Make a copy of the current groups to avoid enumeration issues
         var currentGroups = FileTypeGroups.ToArray();
-    
-        // Count the total number of visible checkboxes and indeterminate ones
         var totalVisible = 0;
         var indeterminateCount = 0;
 
@@ -365,12 +215,8 @@ public class FileAssociationsViewModel : ReactiveObject
             }
         }
 
-        // Determine which state to set based on the counts
-        // If indeterminate count is equal to or greater than non-indeterminate count, 
-        // set all to unchecked, otherwise set all to indeterminate
         var setToUnchecked = indeterminateCount >= totalVisible - indeterminateCount;
 
-        // Apply the chosen state to all visible checkboxes
         foreach (var group in currentGroups)
         {
             foreach (var fileType in group.FileTypes.Where(ft => ft.IsVisible))
@@ -379,35 +225,22 @@ public class FileAssociationsViewModel : ReactiveObject
             }
         }
     }
-    
+
     #endregion
 
     #region Associations
-    
-    /// <summary>
-    /// Applies the current file type associations based on selection states.
-    /// </summary>
-    /// <returns>A <see cref="Task"/> representing the asynchronous operation, with a boolean result indicating success.</returns>
-    /// <remarks>
-    /// This method sets <see cref="IsProcessing"/> to true during execution, which disables UI interaction.
-    /// </remarks>
-    private async Task<bool> ApplyFileAssociations() => await SetFileAssociations(false);
-    
-    /// <summary>
-    /// Unassociates all file types from the application by setting all selection states to false
-    /// and then applying the associations.
-    /// </summary>
-    /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
-    /// <remarks>
-    /// This method sets <see cref="IsProcessing"/> to true during execution, which disables UI interaction.
-    /// </remarks>
-    private async Task UnassociateFileAssociations() => await SetFileAssociations(true);
-    
+
+    private async Task<bool> ApplyFileAssociations()
+        => await SetFileAssociations(false);
+
+    private async Task UnassociateFileAssociations()
+        => await SetFileAssociations(true);
+
     private async Task<bool> SetFileAssociations(bool unassociate)
     {
         try
         {
-            IsProcessing = true;
+            IsProcessing.Value = true;
 
             return await Task.Run(async () =>
             {
@@ -419,15 +252,15 @@ public class FileAssociationsViewModel : ReactiveObject
                 {
                     UpdateSelection();
                 }
-                
+
                 return await FileAssociationProcessor.SetFileAssociations(FileTypeGroups);
             });
         }
         finally
         {
-            IsProcessing = false;
+            IsProcessing.Value = false;
         }
     }
-    
+
     #endregion
 }