1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //----------------------------------------------------------------
4 namespace System.Activities.Presentation.Internal.PropertyEditing.Model
7 using System.Collections.Generic;
8 using System.Collections.ObjectModel;
9 using System.Collections.Specialized;
10 using System.Diagnostics;
11 using System.Activities.Presentation.PropertyEditing;
12 using System.Activities.Presentation.Internal.PropertyEditing.FromExpression.Framework.Data;
13 using System.Activities.Presentation.Internal.PropertyEditing.FromExpression.Framework.PropertyInspector;
14 using System.Activities.Presentation;
17 // Cider's concrete implementation of CategoryEntry (CategoryBase comes from Sparkle
18 // and it has a few extra goodies that we want to reuse). This class implements
19 // INotifyCollectionChanged. We need to push this implementation to the base class
22 internal class ModelCategoryEntry : CategoryBase, INotifyCollectionChanged
25 private ObservableCollectionWorkaround<PropertyEntry> _basicProperties;
26 private ObservableCollectionWorkaround<PropertyEntry> _advancedProperties;
31 // <param name="categoryName">Localized name for this category</param>
32 public ModelCategoryEntry(string categoryName) : base(categoryName)
34 _basicProperties = new ObservableCollectionWorkaround<PropertyEntry>();
35 _advancedProperties = new ObservableCollectionWorkaround<PropertyEntry>();
38 public event NotifyCollectionChangedEventHandler CollectionChanged;
41 // Gets the advanced properties contained in this category
43 public override ObservableCollection<PropertyEntry> AdvancedProperties
46 return _advancedProperties;
51 // Gets the basic properties contained in this category
53 public override ObservableCollection<PropertyEntry> BasicProperties
56 return _basicProperties;
61 // Gets a flag indicating whether this category contains any properties
66 return _advancedProperties.Count + _basicProperties.Count == 0;
71 // Returns either the basic or the advanced bucket based on the IsAdvanced flag
72 // set in the PropertyEntry itself
74 // <param name="property">Property to examine</param>
75 // <returns>The corresponding basic or advanced bucket</returns>
76 internal ObservableCollectionWorkaround<PropertyEntry> GetBucket(PropertyEntry property)
80 throw FxTrace.Exception.ArgumentNull("property");
82 return property.IsAdvanced ? _advancedProperties : _basicProperties;
86 // Adds the given property to the specified property bucket (use
87 // ModelCategoryEntry.BasicProperties, ModelCategoryEntry.AdvancedProperties, or
88 // ModelCategoryEntry.GetBucket()) sorted using the specified comparer.
90 // <param name="property">Property to add</param>
91 // <param name="bucket">Property bucket to populate</param>
92 // <param name="comparer">Sort algorithm to use</param>
93 // <param name="fireCollectionChangedEvent">If set to true, NotifyCollectionChanged event is fired</param>
95 PropertyEntry property,
96 ObservableCollection<PropertyEntry> bucket,
97 IComparer<PropertyEntry> comparer)
99 Add(property, bucket, comparer, true);
103 // Adds the given property to the specified property bucket (use
104 // ModelCategoryEntry.BasicProperties, ModelCategoryEntry.AdvancedProperties, or
105 // ModelCategoryEntry.GetBucket()) sorted using the specified comparer.
108 PropertyEntry property,
109 ObservableCollection<PropertyEntry> bucket,
110 IComparer<PropertyEntry> comparer,
111 bool fireCollectionChangedEvent)
114 if (property == null)
116 throw FxTrace.Exception.ArgumentNull("property");
120 throw FxTrace.Exception.ArgumentNull("bucket");
122 if (comparer == null)
124 throw FxTrace.Exception.ArgumentNull("comparer");
127 ObservableCollectionWorkaround<PropertyEntry> castBucket = bucket as ObservableCollectionWorkaround<PropertyEntry>;
128 int insertionIndex = 0;
130 if (castBucket == null)
132 Debug.Fail("Invalid property bucket. The property sort order will be broken.");
136 insertionIndex = castBucket.BinarySearch(property, comparer);
137 if (insertionIndex < 0)
139 insertionIndex = ~insertionIndex;
143 bucket.Insert(insertionIndex, property);
145 if (fireCollectionChangedEvent)
147 FirePropertiesChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property));
152 // Removes and re-adds the specified property from this category, if it existed
153 // there to begin with. Noop otherwise.
155 // Use this method to refresh the cate----zation of a property if it suddenly
156 // becomes Advanced if it was Basic before, or if its IsBrowsable status changes.
158 // <param name="property">Property to refresh</param>
159 // <param name="bucket">Property bucket to repopulate</param>
160 // <param name="sortComparer">Comparer to use to reinsert the given property in its new place</param>
161 internal void Refresh(ModelPropertyEntry property, ObservableCollection<PropertyEntry> bucket, IComparer<PropertyEntry> sortComparer)
163 if (property == null)
165 throw FxTrace.Exception.ArgumentNull("property");
167 if (bucket != _basicProperties && bucket != _advancedProperties)
169 Debug.Fail("Invalid bucket specified. Property was not refreshed.");
173 // Let's see if we know about this property
174 ObservableCollectionWorkaround<PropertyEntry> collection;
175 collection = _advancedProperties;
177 int index = collection.BinarySearch(property, null);
180 collection = _basicProperties;
181 index = collection.BinarySearch(property, null);
190 // We know about this property, so refresh it. It may have changed
191 // somehow (eg. switched from basic to advanced, become hidden, etc.)
192 // so make sure it's thrown into the right bucket.
193 collection.RemoveAt(index);
194 Add(property, bucket, sortComparer, false);
198 // This is a work-around fix because Blend's CategoryBase does not handle null filters (valid value)
199 // correctly. We need to ask Blend to eventually fix this issue.
201 // <param name="filter">Filter to apply, can be null</param>
202 public override void ApplyFilter(PropertyFilter filter)
206 this.MatchesFilter = true;
207 this.BasicPropertyMatchesFilter = true;
208 this.AdvancedPropertyMatchesFilter = true;
210 foreach (PropertyEntry property in this.BasicProperties)
212 property.ApplyFilter(filter);
215 foreach (PropertyEntry property in this.AdvancedProperties)
217 property.ApplyFilter(filter);
222 base.ApplyFilter(filter);
226 // Another Blend work-around - we expose all properties through the OM, not just the
227 // Browsable ones. However, as a result, we need to cull the non-browsable ones from
228 // consideration. Otherwise, empty categories may appear.
229 protected override bool DoesPropertyMatchFilter(PropertyFilter filter, PropertyEntry property)
231 property.ApplyFilter(filter);
233 bool isBrowsable = true;
234 ModelPropertyEntry modelPropertyEntry = property as ModelPropertyEntry;
235 if (modelPropertyEntry != null)
237 //display given property if it is browsable or
238 isBrowsable = modelPropertyEntry.IsBrowsable ||
239 // it may not be browsable, but if there is a category editor associated - display it anyway
240 (this.CategoryEditors != null && this.CategoryEditors.Count != 0);
243 return isBrowsable && property.MatchesFilter;
247 // Sets the Disassociated flag on all contained properties to True
249 internal void MarkAllPropertiesDisassociated()
251 MarkAllPropertiesDisassociated(_basicProperties);
252 MarkAllPropertiesDisassociated(_advancedProperties);
256 // Sets the Disassociated flag on all contained attached properties to True
258 internal void MarkAttachedPropertiesDisassociated()
260 MarkAttachedPropertiesDisassociated(_basicProperties);
261 MarkAttachedPropertiesDisassociated(_advancedProperties);
265 // Removes all properties from this category whose Disassociated flag is set to True
267 internal void CullDisassociatedProperties()
269 bool propertiesCulled = false;
270 propertiesCulled |= CullDisassociatedProperties(_basicProperties);
271 propertiesCulled |= CullDisassociatedProperties(_advancedProperties);
273 if (propertiesCulled)
275 FirePropertiesChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
279 private static void MarkAllPropertiesDisassociated(ObservableCollectionWorkaround<PropertyEntry> propertyList)
281 foreach (ModelPropertyEntry property in propertyList)
283 property.Disassociated = true;
287 private static void MarkAttachedPropertiesDisassociated(ObservableCollectionWorkaround<PropertyEntry> propertyList)
289 foreach (ModelPropertyEntry property in propertyList)
291 if (property.IsAttached)
293 property.Disassociated = true;
298 private static bool CullDisassociatedProperties(ObservableCollectionWorkaround<PropertyEntry> propertyList)
300 bool propertiesCulled = false;
301 for (int i = propertyList.Count - 1; i >= 0; i--)
303 ModelPropertyEntry property = (ModelPropertyEntry)propertyList[i];
304 if (property.Disassociated)
306 property.Disconnect();
307 propertyList.RemoveAt(i);
308 propertiesCulled = true;
312 return propertiesCulled;
315 // INotifyCollectionChanged Members
317 private void FirePropertiesChanged(NotifyCollectionChangedEventArgs collectionChangedEventArgs)
319 // Fire both "Properties" changed events
320 OnPropertyChanged("Properties");
321 OnPropertyChanged("Item[]");
323 // as well as the appropriate collection-changed event
324 if (CollectionChanged != null)
326 CollectionChanged(this, collectionChangedEventArgs);