CommandViewModel.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using Microsoft.CmdPal.Core.ViewModels.Models; 6 using Microsoft.CommandPalette.Extensions; 7 8 namespace Microsoft.CmdPal.Core.ViewModels; 9 10 public partial class CommandViewModel : ExtensionObjectViewModel 11 { 12 public ExtensionObject<ICommand> Model { get; private set; } = new(null); 13 14 protected bool IsInitialized { get; private set; } 15 16 protected bool IsFastInitialized { get; private set; } 17 18 public bool HasIcon => Icon.IsSet; 19 20 // These are properties that are "observable" from the extension object 21 // itself, in the sense that they get raised by PropChanged events from the 22 // extension. However, we don't want to actually make them 23 // [ObservableProperty]s, because PropChanged comes in off the UI thread, 24 // and ObservableProperty is not smart enough to raise the PropertyChanged 25 // on the UI thread. 26 public string Id { get; private set; } = string.Empty; 27 28 public string Name { get; private set; } = string.Empty; 29 30 public IconInfoViewModel Icon { get; private set; } 31 32 // UNDER NO CIRCUMSTANCES MAY SOMEONE WRITE TO THIS DICTIONARY. 33 // This is our copy of the data from the extension. 34 // Adding values to it does not add to the extension. 35 // Modifying it will not modify the extension 36 // (except it might, if the dictionary was passed by ref) 37 private Dictionary<string, ExtensionObject<object>>? _properties; 38 39 public IReadOnlyDictionary<string, ExtensionObject<object>>? Properties => _properties?.AsReadOnly(); 40 41 public CommandViewModel(ICommand? command, WeakReference<IPageContext> pageContext) 42 : base(pageContext) 43 { 44 Model = new(command); 45 Icon = new(null); 46 } 47 48 public void FastInitializeProperties() 49 { 50 if (IsFastInitialized) 51 { 52 return; 53 } 54 55 var model = Model.Unsafe; 56 if (model is null) 57 { 58 return; 59 } 60 61 Id = model.Id ?? string.Empty; 62 Name = model.Name ?? string.Empty; 63 IsFastInitialized = true; 64 } 65 66 public override void InitializeProperties() 67 { 68 if (IsInitialized) 69 { 70 return; 71 } 72 73 if (!IsFastInitialized) 74 { 75 FastInitializeProperties(); 76 } 77 78 var model = Model.Unsafe; 79 if (model is null) 80 { 81 return; 82 } 83 84 var ico = model.Icon; 85 if (ico is not null) 86 { 87 Icon = new(ico); 88 Icon.InitializeProperties(); 89 UpdateProperty(nameof(Icon)); 90 } 91 92 if (model is IExtendedAttributesProvider command2) 93 { 94 UpdatePropertiesFromExtension(command2); 95 } 96 97 model.PropChanged += Model_PropChanged; 98 } 99 100 private void Model_PropChanged(object sender, IPropChangedEventArgs args) 101 { 102 try 103 { 104 FetchProperty(args.PropertyName); 105 } 106 catch (Exception ex) 107 { 108 ShowException(ex, Name); 109 } 110 } 111 112 protected void FetchProperty(string propertyName) 113 { 114 var model = Model.Unsafe; 115 if (model is null) 116 { 117 return; // throw? 118 } 119 120 switch (propertyName) 121 { 122 case nameof(Name): 123 Name = model.Name; 124 break; 125 case nameof(Icon): 126 var iconInfo = model.Icon; 127 Icon = new(iconInfo); 128 Icon.InitializeProperties(); 129 break; 130 } 131 132 UpdateProperty(propertyName); 133 } 134 135 protected override void UnsafeCleanup() 136 { 137 base.UnsafeCleanup(); 138 139 Icon = new(null); // necessary? 140 141 var model = Model.Unsafe; 142 if (model is not null) 143 { 144 model.PropChanged -= Model_PropChanged; 145 } 146 } 147 148 private void UpdatePropertiesFromExtension(IExtendedAttributesProvider? model) 149 { 150 var propertiesFromExtension = model?.GetProperties(); 151 if (propertiesFromExtension == null) 152 { 153 _properties = null; 154 return; 155 } 156 157 _properties = []; 158 159 // COPY the properties into us. 160 // The IDictionary that was passed to us may be marshalled by-ref or by-value, we _don't know_. 161 // 162 // If it's by-ref, the values are arbitrary objects that are out-of-proc. 163 // If it's bu-value, then everything is in-proc, and we can't mutate the data. 164 foreach (var property in propertiesFromExtension) 165 { 166 _properties.Add(property.Key, new(property.Value)); 167 } 168 } 169 }