public CombinablePropertyViewModel(TargetPlatform platform, IPropertyInfo property, IEnumerable <IObjectEditor> editors) : base(platform, property, editors) { this.predefinedValues = property as IHavePredefinedValues <TValue>; if (this.predefinedValues == null) { throw new ArgumentException("Property does not have predefined values", nameof(property)); } if (this.predefinedValues.IsValueCombinable && !this.predefinedValues.IsConstrainedToPredefined) { throw new NotSupportedException("Properties with combinable values can not be unconstrained currently"); } this.coerce = property as ICoerce <IReadOnlyList <TValue> >; this.validator = property as IValidator <IReadOnlyList <TValue> >; var choices = new List <FlaggableChoiceViewModel <TValue> > (this.predefinedValues.PredefinedValues.Count); foreach (var kvp in this.predefinedValues.PredefinedValues) { var choiceVm = new FlaggableChoiceViewModel <TValue> (kvp.Key, kvp.Value); choiceVm.PropertyChanged += OnChoiceVmPropertyChanged; choices.Add(choiceVm); } Choices = choices; RequestCurrentValueUpdate(); }
private async Task PushValuesAsync(FlaggableChoiceViewModel <TValue> changedChoice) { SetError(null); using (await AsyncWork.RequestAsyncWork(this)) { try { // Snapshot current choices so we don't catch updates mid-push for multi-editors var currentChoices = Choices.ToDictionary(c => c, c => c.IsFlagged); foreach (IObjectEditor editor in Editors) { ValueInfo <IReadOnlyList <TValue> > value = await editor.GetValueAsync <IReadOnlyList <TValue> > (Property, Variation); HashSet <TValue> current; if (value.Value == null || value.Source == ValueSource.Unset) { current = new HashSet <TValue> (); } else { current = new HashSet <TValue> (value.Value); } foreach (var choice in currentChoices) { if (!choice.Value.HasValue) { continue; } if (choice.Value.Value) { current.Add(choice.Key.Value); } else { current.Remove(choice.Key.Value); } } IReadOnlyList <TValue> values = current.ToArray(); if (this.validator != null) { if (!this.validator.IsValid(values)) { // Some combinables simply don't have a valid "none", but if we're going from indeterminate we still need to // update the value, so we'll flip the changed value to true in that case so we don't go right back to indeterminate if (values.Count == 0) { changedChoice.IsFlagged = true; // We're explicitly triggering a change and need the update here so we need to update our snapshot. currentChoices = Choices.ToDictionary(c => c, c => c.IsFlagged); } continue; } } if (this.coerce != null) { values = this.coerce.CoerceValue(values); } await editor.SetValueAsync(Property, new ValueInfo <IReadOnlyList <TValue> > { Source = ValueSource.Local, Value = values }); } } catch (Exception ex) { if (ex is AggregateException aggregate) { aggregate = aggregate.Flatten(); ex = aggregate.InnerExceptions[0]; } SetError(ex.ToString()); } } }