diff options
author | Eric Maupin <ermaup@microsoft.com> | 2018-04-30 22:07:52 +0300 |
---|---|---|
committer | Eric Maupin <ermaup@microsoft.com> | 2018-05-01 01:00:32 +0300 |
commit | 70c58f024cfb570d761105480126673db631ee5f (patch) | |
tree | 674526b7b9f8dd99b9bc341d4476a2d0aa0773d9 /Xamarin.PropertyEditing | |
parent | 31f4af0b1babf93cfad66617a84c1b85b8a3681b (diff) |
[Core] Rework CollectionPropertyViewModel
In order to track rows for display purposes and in order to provide a
clean way to get at the "real" name of the child, we've moved to using a
VM per child. Since the main way to get a real name of an editable
target is to request an object editor, we introduce a cached provider so
the PanelViewModel doesn't have to get its own copy when the targets are
selected.
This collections tend to be small, but if we run into issues with the
amount of things we're keeping alive we can easily move to on-demand
requests.
Diffstat (limited to 'Xamarin.PropertyEditing')
-rw-r--r-- | Xamarin.PropertyEditing/TargetPlatform.cs | 14 | ||||
-rw-r--r-- | Xamarin.PropertyEditing/ViewModels/CollectionPropertyViewModel.cs | 180 |
2 files changed, 181 insertions, 13 deletions
diff --git a/Xamarin.PropertyEditing/TargetPlatform.cs b/Xamarin.PropertyEditing/TargetPlatform.cs index a7f00f2..6296149 100644 --- a/Xamarin.PropertyEditing/TargetPlatform.cs +++ b/Xamarin.PropertyEditing/TargetPlatform.cs @@ -67,5 +67,19 @@ namespace Xamarin.PropertyEditing get; set; } + + internal TargetPlatform WithProvider (IEditorProvider provider) + { + if (provider == null) + throw new ArgumentNullException (nameof(provider)); + + return new TargetPlatform (provider) { + SupportsMaterialDesign = SupportsMaterialDesign, + SupportsCustomExpressions = SupportsCustomExpressions, + SupportsBrushOpacity = SupportsBrushOpacity, + GroupedTypes = GroupedTypes, + AutoExpandGroups = AutoExpandGroups + }; + } } } diff --git a/Xamarin.PropertyEditing/ViewModels/CollectionPropertyViewModel.cs b/Xamarin.PropertyEditing/ViewModels/CollectionPropertyViewModel.cs index 94f68a5..4c26ef2 100644 --- a/Xamarin.PropertyEditing/ViewModels/CollectionPropertyViewModel.cs +++ b/Xamarin.PropertyEditing/ViewModels/CollectionPropertyViewModel.cs @@ -9,15 +9,60 @@ using Xamarin.PropertyEditing.Properties; namespace Xamarin.PropertyEditing.ViewModels { + internal class CollectionPropertyItemViewModel + : NotifyingObject + { + public CollectionPropertyItemViewModel (object item, string typeName) + { + if (item == null) + throw new ArgumentNullException (nameof(item)); + if (typeName == null) + throw new ArgumentNullException (nameof(typeName)); + + Item = item; + TypeName = typeName; + } + + public object Item + { + get; + } + + public string TypeName + { + get; + } + + public int Row + { + get { return this.row; } + set + { + if (this.row == value) + return; + + this.row = value; + OnPropertyChanged(); + } + } + + private int row; + } + + // TODO: One thing this doesn't support currently is a way of previewing the changes + internal class CollectionPropertyViewModel : PropertyViewModel<IList> { public CollectionPropertyViewModel (TargetPlatform platform, IPropertyInfo property, IEnumerable<IObjectEditor> editors) : base (platform, property, editors) { + if (this.cachedProvider == null) + this.cachedProvider = new CachedEditorProvider (platform.EditorProvider); + RequestTypes (); - Panel = new PanelViewModel (platform) { + Panel = new PanelViewModel (platform.WithProvider (this.cachedProvider)) { ArrangeMode = PropertyArrangeMode.Category, AutoExpand = true }; @@ -27,13 +72,14 @@ namespace Xamarin.PropertyEditing.ViewModels MoveUpCommand = new RelayCommand (() => MoveTarget (up: true), () => CanMoveTarget (up: true)); MoveDownCommand = new RelayCommand (() => MoveTarget (up: false), () => CanMoveTarget (up: false)); CommitCommand = new RelayCommand (OnCommitCommand); + CancelCommand = new RelayCommand(RequestCurrentValueUpdate); this.collectionView.CollectionChanged += OnCollectionViewContentsChanged; } public event EventHandler<TypeRequestedEventArgs> TypeRequested; - public IReadOnlyList<object> Targets => this.collectionView; + public IReadOnlyList<CollectionPropertyItemViewModel> Targets => this.collectionView; public IReadOnlyList<ITypeInfo> SuggestedTypes { @@ -61,7 +107,7 @@ namespace Xamarin.PropertyEditing.ViewModels } } - public object SelectedTarget + public CollectionPropertyItemViewModel SelectedTarget { get { return this.selectedTarget; } set @@ -69,12 +115,15 @@ namespace Xamarin.PropertyEditing.ViewModels if (this.selectedTarget == value) return; - object old = this.selectedTarget; + CollectionPropertyItemViewModel old = this.selectedTarget; this.selectedTarget = value; - if (value != null) - Panel.SelectedObjects.ReplaceOrAdd (old, value); - else + if (value != null) { + if (old == null) + Panel.SelectedObjects.Add (value.Item); + else + Panel.SelectedObjects.ReplaceOrAdd (old.Item, value.Item); + } else Panel.SelectedObjects.Clear(); OnPropertyChanged(); @@ -128,12 +177,21 @@ namespace Xamarin.PropertyEditing.ViewModels get; } + public ICommand CancelCommand + { + get; + } + protected override async Task UpdateCurrentValueAsync () { await base.UpdateCurrentValueAsync (); + if (this.cachedProvider == null) + this.cachedProvider = new CachedEditorProvider (TargetPlatform.EditorProvider); + + this.cachedProvider.Clear(); if (Value != null) - this.collectionView.Reset (Value.Cast<object>()); + this.collectionView.Reset (await GetViewsFromValueAsync()); else this.collectionView.Clear(); } @@ -144,11 +202,70 @@ namespace Xamarin.PropertyEditing.ViewModels RequestTypes (); } - private object selectedTarget; + // We need to grab the object editor for the children to get their "real" type name, but we don't want + // the panel view model to have query the editor again each time it's selected, so we'll give it this + // caching provider. Lists of this kind tend to be relatively small, but if becomes a problem we can + // add a window to the cache. + + private class CachedEditorProvider + : IEditorProvider + { + public CachedEditorProvider (IEditorProvider realProvider) + { + if (realProvider == null) + throw new ArgumentNullException (nameof(realProvider)); + + this.realProvider = realProvider; + } + + public void Add (IObjectEditor editor) + { + this.editors.Add (editor.Target, editor); + } + + public void Remove (object target) + { + this.editors.Remove (target); + } + + public void Clear () + { + this.editors.Clear(); + } + + public Task<IObjectEditor> GetObjectEditorAsync (object item) + { + if (this.editors.TryGetValue (item, out IObjectEditor editor)) + return Task.FromResult (editor); + + return this.realProvider.GetObjectEditorAsync (item); + } + + public async Task<IObjectEditor> GetAndCacheEditorAsync (object item) + { + if (!this.editors.TryGetValue (item, out IObjectEditor editor)) { + editor = await GetObjectEditorAsync (item); + this.editors[item] = editor; + } + + return editor; + } + + public Task<object> CreateObjectAsync (ITypeInfo type) + { + return this.realProvider.CreateObjectAsync (type); + } + + private readonly Dictionary<object, IObjectEditor> editors = new Dictionary<object, IObjectEditor> (); + private readonly IEditorProvider realProvider; + } + + private CollectionPropertyItemViewModel selectedTarget; private ITypeInfo selectedType; private AsyncValue<IReadOnlyDictionary<IAssemblyInfo, ILookup<string, ITypeInfo>>> assignableTypes; private ObservableCollectionEx<ITypeInfo> suggestedTypes; - private readonly ObservableCollectionEx<object> collectionView = new ObservableCollectionEx<object> (); + private readonly ObservableCollectionEx<CollectionPropertyItemViewModel> collectionView = new ObservableCollectionEx<CollectionPropertyItemViewModel> (); + private CachedEditorProvider cachedProvider; private static readonly ITypeInfo OtherType = new OtherTypeFake(); @@ -160,6 +277,20 @@ namespace Xamarin.PropertyEditing.ViewModels public string Name => Resources.OtherTypeAction; } + private async Task<IReadOnlyList<CollectionPropertyItemViewModel>> GetViewsFromValueAsync () + { + var items = new List<CollectionPropertyItemViewModel> (Value.Count); + for (int i = 0; i < Value.Count; i++) { + object target = Value[i]; + IObjectEditor editor = await this.cachedProvider.GetAndCacheEditorAsync (target); + items.Add (new CollectionPropertyItemViewModel (target, editor.TypeName) { + Row = i + }); + } + + return items; + } + private async void RequestTypes () { if (Property == null) @@ -180,7 +311,7 @@ namespace Xamarin.PropertyEditing.ViewModels private Task PushValueAsync () { - object[] snapshot = this.collectionView.ToArray (); + object[] snapshot = this.collectionView.Select (vm => vm.Item).ToArray (); return SetValueAsync (new ValueInfo<IList> { Value = snapshot, Source = ValueSource.Local @@ -229,24 +360,47 @@ namespace Xamarin.PropertyEditing.ViewModels return (up) ? (index > 0) : (index < this.collectionView.Count - 1); } + private void ReIndex () + { + for (int i = 0; i < this.collectionView.Count; i++) { + this.collectionView[i].Row = i; + } + } + private void MoveTarget (bool up) { int index = this.collectionView.IndexOf (SelectedTarget); this.collectionView.Move (index, index + ((up) ? -1 : 1)); + + ReIndex(); } private async void OnAddTarget () { object target = await TargetPlatform.EditorProvider.CreateObjectAsync (SelectedType); - this.collectionView.Add (target); - SelectedTarget = target; + IObjectEditor editor = await TargetPlatform.EditorProvider.GetObjectEditorAsync (target); + this.cachedProvider.Add (editor); + + var vm = new CollectionPropertyItemViewModel (target, editor.TypeName); + + if (SelectedTarget != null) { + this.collectionView.Insert (SelectedTarget.Row + 1, vm); + } else { + this.collectionView.Add (vm); + } + + ReIndex(); + SelectedTarget = vm; } private void OnRemoveTarget () { int index = Math.Max (0, this.collectionView.IndexOf (SelectedTarget) - 1); + this.cachedProvider.Remove (SelectedTarget.Item); this.collectionView.Remove (SelectedTarget); + ReIndex(); + SelectedTarget = (this.collectionView.Count > 0) ? this.collectionView[index] : null; } |