浏览代码

avalonia: Made IsSelected a property of Shell and fixed nullability references in CoreRepository

Kenric Nugteren 3 月之前
父节点
当前提交
064c756722

+ 0 - 18
InABox.Avalonia/Converters/ShellSelectedConverter.cs

@@ -1,18 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace InABox.Avalonia.Converters
-{
-    public class ShellSelectedConverter : AbstractConverter<IShell, bool>
-    {
-        public static ShellSelectedConverter Instance = new ShellSelectedConverter();
-
-        protected override bool Convert(IShell? value, object? parameter = null)
-        {
-            return value?.Parent?.IsSelected(value) ?? false;
-        }
-    }
-}

+ 90 - 68
InABox.Avalonia/DataModels/CoreRepository.cs

@@ -1,6 +1,7 @@
 using System.Collections;
 using System.Collections.ObjectModel;
 using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
 using System.Linq.Expressions;
 using System.Runtime.CompilerServices;
 using Avalonia.Controls.Selection;
@@ -11,6 +12,7 @@ using InABox.Configuration;
 using InABox.Core;
 
 using JetBrains.Annotations;
+using NotNullAttribute = JetBrains.Annotations.NotNullAttribute;
 
 namespace InABox.Avalonia
 {
@@ -34,7 +36,7 @@ namespace InABox.Avalonia
             !String.IsNullOrWhiteSpace(filename) 
             && File.Exists(CacheFileName(filename));
         
-        public static string CacheFileName(string? filename) =>
+        public static string CacheFileName(string filename) =>
             Path.Combine(CacheFolder(), filename);
 
         public static string CacheFolder()
@@ -79,7 +81,7 @@ namespace InABox.Avalonia
         
         public Func<Filter<TEntity>> Filter { get; set; }
 
-        protected virtual Filter<TEntity> BaseFilter() => null;
+        protected virtual Filter<TEntity>? BaseFilter() => null;
         
         public IModelHost Host { get; set; }
         
@@ -109,13 +111,10 @@ namespace InABox.Avalonia
         {
             AllItems = new CoreObservableCollection<TItem>();
             AllItems.CollectionChanged += (sender, args) => ItemsChanged(AllItems);
-            EnableSynchronization(AllItems);
+            // EnableSynchronization(AllItems);
             
             Items = new CoreObservableCollection<TItem>(); 
-            EnableSynchronization(Items);
-
-            SelectedItems = new CoreObservableCollection<TItem>();
-            EnableSynchronization(SelectedItems);
+            // EnableSynchronization(Items);
             
             Reset();
             Host = host;
@@ -128,29 +127,29 @@ namespace InABox.Avalonia
         {
         }
 
-        private void EnableSynchronization(IEnumerable items)
-        {
-            // BindingBase.EnableCollectionSynchronization(items, null, 
-            //     (collection, context, method, access) =>
-            //     {
-            //         lock (collection)
-            //         {
-            //             method?.Invoke();
-            //         }
-            //     }
-            // );
-        }
+        // private void EnableSynchronization(IEnumerable items)
+        // {
+        //     // BindingBase.EnableCollectionSynchronization(items, null, 
+        //     //     (collection, context, method, access) =>
+        //     //     {
+        //     //         lock (collection)
+        //     //         {
+        //     //             method?.Invoke();
+        //     //         }
+        //     //     }
+        //     // );
+        // }
 
         #region INotifyPropertyChanged
         
-        public event PropertyChangedEventHandler PropertyChanged;
+        public event PropertyChangedEventHandler? PropertyChanged;
 
         protected void DoPropertyChanged(object sender, PropertyChangedEventArgs args)
         {
             PropertyChanged?.Invoke(sender, args);
         }
         
-        protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
+        protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
         {
             if (EqualityComparer<T>.Default.Equals(field, value)) 
                 return false;
@@ -159,7 +158,7 @@ namespace InABox.Avalonia
             return true;
         }
 
-        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
+        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
             => DoPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
         
         #endregion
@@ -173,7 +172,7 @@ namespace InABox.Avalonia
             return Images.GetValueOrDefault(id);
         }
         
-        public byte[] GetImage(Guid id) => Images.GetValueOrDefault(id);
+        public byte[]? GetImage(Guid id) => Images.GetValueOrDefault(id);
         
         public bool HasImages() => Images.Any();
 
@@ -197,7 +196,7 @@ namespace InABox.Avalonia
             OnPropertyChanged(nameof(AvailableFilters));
         }
         
-        protected Filter<TEntity> EffectiveFilter()
+        protected Filter<TEntity>? EffectiveFilter()
         {
             var filters = new Filters<TEntity>();
             filters.Add(BaseFilter());
@@ -219,7 +218,6 @@ namespace InABox.Avalonia
             Loaded = false;
             AllItems.Clear();
             Items.Clear();
-            SelectedItems.Clear();
             Images.Clear();
         }
         
@@ -229,9 +227,8 @@ namespace InABox.Avalonia
         
         private void DoRefresh(bool force)
         {
-            var curselected = SelectedItems.ToArray();
+            var selectedIDs = SelectedItems.Select(x => x.ID).ToHashSet();
             Items.Clear();
-            SelectedItems.Clear();
 
             var dataFileName = DataFileName();
             if (!force && !Loaded && CoreRepository.IsCached(dataFileName))
@@ -240,7 +237,10 @@ namespace InABox.Avalonia
                 if (LoadFromStorage())
                 {
                     DoAfterLoad();
-                    SelectedItems.AddRange(Items.Where(x=>curselected.Contains(x)));
+                    foreach(var item in Items)
+                    {
+                        item.IsSelected = selectedIDs.Contains(item.ID);
+                    }
                     return;
                 }
             }
@@ -249,13 +249,16 @@ namespace InABox.Avalonia
             {
                 DoLoad();
                 SaveToStorage();
-                SelectedItems.AddRange(Items.Where(x=>curselected.Contains(x)));
+                foreach(var item in Items)
+                {
+                    item.IsSelected = selectedIDs.Contains(item.ID);
+                }
                 return;
             }
-
-            SelectedItems.AddRange(Items.Where(x => curselected.Contains(x)));
-
-
+            foreach(var item in Items)
+            {
+                item.IsSelected = selectedIDs.Contains(item.ID);
+            }
         }
 
         private void AfterRefresh()
@@ -299,10 +302,10 @@ namespace InABox.Avalonia
             Initialize();
         }
         
-        public event CoreRepositoryChangedEvent Changed;
+        public event CoreRepositoryChangedEvent? Changed;
         protected void NotifyChanged() => Changed?.Invoke(this, new CoreRepositoryChangedEventArgs());
         
-        public virtual SortOrder<TEntity> Sort => LookupFactory.DefineSort<TEntity>();
+        public virtual SortOrder<TEntity>? Sort => LookupFactory.DefineSort<TEntity>();
 
         protected CoreObservableCollection<TItem> AllItems { get; private set; }
         
@@ -313,40 +316,44 @@ namespace InABox.Avalonia
         public int ItemCount => Items.Count;
         
         IEnumerable ICoreRepository.Items => Items;
-        
+
         #region Item Selection
-        
-        public CoreObservableCollection<TItem> SelectedItems { get; private set; }
-        
+
         IEnumerable ICoreRepository.SelectedItems => SelectedItems;
-        
-        public bool IsSelected(TItem item) => (item != null) && SelectedItems.Contains(item);
+
+        IEnumerable<TItem> SelectedItems => Items.Where(x => x.IsSelected);
+
+        public bool IsSelected(TItem? item) => item is not null && item.IsSelected;
         
         public void SetSelectedItems(IEnumerable<TItem> items)
         {
-            SelectedItems.ReplaceRange(items);
+            var selectedItems = items.Select(x => x.ID).ToHashSet();
+            foreach(var item in Items)
+            {
+                item.IsSelected = selectedItems.Contains(item.ID);
+            }
             Search();
         }
         
-        public void SelectItem([CanBeNull] TItem item)
+        public void SelectItem([CanBeNull] TItem? item)
         {
-            if ((item != null) && !SelectedItems.Contains(item))
+            if ((item != null) && !item.IsSelected)
             {
-                SelectedItems.Add(item);
+                item.IsSelected = true;
                 Search();
             }
         }
         
-        public void UnselectItem([CanBeNull] TItem item)
+        public void UnselectItem([CanBeNull] TItem? item)
         {
-            if ((item != null) && SelectedItems.Contains(item))
+            if ((item != null) && item.IsSelected)
             {
-                SelectedItems.Remove(item);
+                item.IsSelected = false;
                 Search();
             }
         }
         
-        public void ToggleSelection(TItem item)
+        public void ToggleSelection(TItem? item)
         {
             if (IsSelected(item))
                 UnselectItem(item);
@@ -356,13 +363,19 @@ namespace InABox.Avalonia
         
         public void SelectNone()
         {
-            SelectedItems.Clear();
+            foreach(var item in Items)
+            {
+                item.IsSelected = false;
+            }
             Search();
         }
 
         public void SelectAll()
         {
-            SelectedItems.ReplaceRange(Items);
+            foreach(var item in Items)
+            {
+                item.IsSelected = true;
+            }
             Search();
         }
         
@@ -376,9 +389,9 @@ namespace InABox.Avalonia
         
         #region Searching
         
-        public Func<TItem, bool> SearchPredicate { get; set; }
+        public Func<TItem, bool>? SearchPredicate { get; set; }
         
-        public Func<List<TItem>,List<TItem>> SortPredicate { get; set; }
+        public Func<List<TItem>,List<TItem>>? SortPredicate { get; set; }
 
         public ICoreRepository Search(Func<TItem, bool> searchpredicate, Func<List<TItem>,List<TItem>> sortpredicate)
         {
@@ -397,7 +410,6 @@ namespace InABox.Avalonia
 
         public ICoreRepository Search()
         {
-            var curselected = SelectedItems.ToArray();
             var items = AllItems == null
                 ? new List<TItem>()
                 : SearchPredicate != null
@@ -407,9 +419,6 @@ namespace InABox.Avalonia
             if (SortPredicate != null)
                 items = SortPredicate(items);
             
-            SelectedItems.ReplaceRange(items.Where(x=>curselected.Contains(x)));
-            OnPropertyChanged(nameof(SelectedItems));
-            
             Items.ReplaceRange(items);
             OnPropertyChanged(nameof(Items));
             OnPropertyChanged(nameof(ItemCount));
@@ -422,7 +431,7 @@ namespace InABox.Avalonia
 
         #endregion
         
-        protected virtual Expression<Func<TEntity, object>> ImageColumn => null;
+        protected virtual Expression<Func<TEntity, object?>>? ImageColumn => null;
         
         #region Loading
         
@@ -437,7 +446,7 @@ namespace InABox.Avalonia
                 Sort
             );
             
-            if (ImageColumn != null)
+            if (ImageColumn is not null)
             {
                 _query.Add(
                     new Filter<Document>(x => x.ID).InQuery(EffectiveFilter(), ImageColumn),
@@ -545,6 +554,11 @@ namespace InABox.Avalonia
             public void Set([NotNull] String key, CoreTable table) => _data[key] = table;
 
             public bool Contains([NotNull] String key) => _data.ContainsKey(key);
+
+            public bool TryGet(string key, [NotNullWhen(true)] out CoreTable? table)
+            {
+                return _data.TryGetValue(key, out table);
+            }
             
             public void SerializeBinary(CoreBinaryWriter writer)
             {
@@ -603,10 +617,13 @@ namespace InABox.Avalonia
                     var defs = _query.Definitions();
                     foreach (var key in defs.Keys)
                     {
-                        var table = storage.Contains(key.ToString())
-                            ? storage.Get(key.ToString())
-                            : InitializeTable(defs[key]);
-                        if (CheckColumns(table, _query.Definitions()[key].Columns))
+                        var keyStr = key.ToString() ?? "";
+                        if(!storage.TryGet(keyStr, out var table))
+                        {
+                            table = InitializeTable(defs[key]);
+                        }
+                        var queryDef = _query.Definitions()[key];
+                        if (CheckColumns(table, queryDef.Type, queryDef.Columns))
                             _query.Set(key, table);
                         else
                             return false;
@@ -619,8 +636,9 @@ namespace InABox.Avalonia
             return true;
         }
 
-        private bool CheckColumns(CoreTable table, IColumns required)
+        private bool CheckColumns(CoreTable table, Type T, IColumns? required)
         {
+            required = CoreUtils.GetColumns(T, required);
             foreach (var column in required.ColumnNames())
             {
                 if (!table.Columns.Any(x => String.Equals(x.ColumnName, column)))
@@ -648,7 +666,7 @@ namespace InABox.Avalonia
             QueryStorage storage = new QueryStorage();
             var results = _query.Results();
             foreach (var key in results.Keys)
-                storage.Set(key.ToString(),results[key]);
+                storage.Set(key.ToString() ?? "", results[key]);
             var data = storage.WriteBinary(BinarySerializationSettings.Latest);
             try
             {
@@ -666,7 +684,7 @@ namespace InABox.Avalonia
 
         #region CRUD Operations
         
-        public event CoreRepositoryItemCreatedEvent<TItem> ItemAdded;
+        public event CoreRepositoryItemCreatedEvent<TItem>? ItemAdded;
         
         private T CreateItem<T>(CoreRow row) 
             where T : Shell<TParent,TEntity>, new()
@@ -693,7 +711,7 @@ namespace InABox.Avalonia
         {
             _table.Rows.Add(item.Row);
             AllItems.Add(item);
-            Search(null);
+            Search();
             NotifyChanged();
         }
         
@@ -708,7 +726,7 @@ namespace InABox.Avalonia
         {
             _table.Rows.Remove(item.Row);
             AllItems.Remove(item);
-            Search(null);
+            Search();
             NotifyChanged();
         }
 
@@ -716,6 +734,10 @@ namespace InABox.Avalonia
         {
             new Client<TEntity>().Save(Items.Select(x=>x.Entity).Where(x=>x.IsChanged()),auditMessage);
         }
+        public virtual Task SaveAsync(string auditMessage)
+        {
+            return Task.Run(() => Save(auditMessage));
+        }
 
         object ICoreRepository.CreateItem() => this.CreateItem();
         

+ 3 - 1
InABox.Avalonia/DataModels/ICoreRepository.cs

@@ -29,6 +29,9 @@ namespace InABox.Avalonia
         ICoreRepository Refresh(bool force);
         Task<ICoreRepository> RefreshAsync(bool force);
         void Refresh(bool force, Action loaded);
+
+        void Save(string auditMessage);
+        Task SaveAsync(string auditMessage);
         
         DateTime LastUpdated { get; }
 
@@ -53,6 +56,5 @@ namespace InABox.Avalonia
         bool IsSelected(object item);
         void SetSelectedItems(IEnumerable<object> items);
         IEnumerable SelectedItems { get; }
-        
     }
 }

+ 2 - 0
InABox.Avalonia/DataModels/IShell.cs

@@ -8,6 +8,8 @@ namespace InABox.Avalonia
         Guid ID { get; }
         ICoreRepository Parent { get; }
 
+        bool IsSelected { get; set; }
+
         bool IsChanged();
         void Save(string auditmessage);
         void Cancel();

+ 19 - 5
InABox.Avalonia/DataModels/Shell.cs

@@ -15,17 +15,31 @@ namespace InABox.Avalonia
             get => _tag;
             set => SetProperty(ref _tag, value);
         }
+
+        private bool _isSelected;
+        public bool IsSelected
+        {
+            get => _isSelected;
+            set
+            {
+                if(_isSelected != value)
+                {
+                    _isSelected = value;
+                    DoPropertyChanged(nameof(IsSelected));
+                }
+            }
+        }
         
         #region INotifyPropertyChanged
         
-        public new event PropertyChangedEventHandler? PropertyChanged;
+        public event PropertyChangedEventHandler? PropertyChanged;
         
         protected void DoPropertyChanged(string propertyName)
         {
             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
         }
         
-        protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
+        protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
         {
             if (EqualityComparer<T>.Default.Equals(field, value)) return false;
             field = value;
@@ -36,7 +50,7 @@ namespace InABox.Avalonia
         
         #endregion
 
-        private TEntity _entity;
+        private TEntity? _entity;
         
         private TEntity CheckEntity()
         {
@@ -64,7 +78,7 @@ namespace InABox.Avalonia
             _entity = null;
         }
         
-        private CoreRow _row;
+        private CoreRow _row = null!;
         public CoreRow Row
         {
             get => _row;
@@ -106,7 +120,7 @@ namespace InABox.Avalonia
         // 3. Using the Get/Set helper functions reduces code complexity when defining 
         // shell properties, and distinguishes between data and calculated properties
 
-        private static ShellColumns<TParent, TEntity> _columns;
+        private static ShellColumns<TParent, TEntity>? _columns;
         
         public ShellColumns<TParent, TEntity> Columns
         {