0

For a Silverlight and WPF app, I have a custom control that includes an ObservableCollection as a dependency property. One element of that control, a Border, needs to change color depending on the composition of items in the ObservableCollection.

For example, let's say the collection is of animals, vegetables, and minerals and called ObjectList. If there is at least one animal, I want the border to be red; if there are no animals but at least one vegetable, it's green; otherwise the collection has only minerals, so will appear as blue.

I created a converter that can take the collection and determine the color, so have a binding like:

<Border Background="{Binding ObjectList, 
                     RelativeSource={RelativeSource Self}, 
                     Converter={StaticResource MyColorConverter}}" />

The challenge is that as items get added/removed from ObjectList I need to trigger reevaluation of the background color; however, ObjectList itself doesn't change. I figure I have three options, but am unsure of which may be the best practice:

  1. Create a new collection each time an object is added or removed. That seems heavy-handed, but will result in ObjectList being changed and so trigger the background update.

  2. Call UpdateTarget for the background property in the CollectionChanged callback for ObjectList. Since UpdateTarget isn't available for Silverlight, I just remove and re-add the binding - again a bit heavy handed.

  3. Implement INotifyPropertyChanged on my custom control and call PropertyChanged on the ObjectList within the implementation of CollectionChanged

I like 3 the best, but the fact I have a DependencyObject that also implements INPC seems odd. Is it? Is there a more elegant approach?

Jim O'Neil
  • 23,344
  • 7
  • 42
  • 67
  • 1
    Point 3 is a light-weight solution and of the three, it's compelling for that reason. For myself, I would bind the background to an additional DP in your control, and then update it on changes to the ObjectList. Since ObservableCollection inherits from IList, the underlying ListCollectionView can be very helpful. – Gayot Fow Jun 09 '14 at 17:21
  • I actually left that one out, I am using an additional DP but it just seemed like another (redundant) moving part that still needed to "change" somehow – Jim O'Neil Jun 09 '14 at 18:12
  • When your collection gets large you may not appreciate the performance hit of updating the list property. Also, if the list is being filtered on Animal and you get a new Mineral added, then you are iterating the entire list for naught. So it's not necessarily redundant, it depends upon how sophisticated you want your control to be. – Gayot Fow Jun 09 '14 at 18:45

1 Answers1

2

There is a way of doing this recommended by the MSDN documentation (scroll down to Best Practices for Working with the VisualStateManager; it's written for full .Net but this section is well suitable for Silverlight too). Whenever your VisualStates depend on properties/state of your custom Control it is recommended to have a ChangedHandler for each VisualState-affecting property and call a private UpdateVisualStates method from there. Evaluate your conditions and set the VisualStates programmatically from within this method.

Even if you are not using VisualStates for the color change I recommend you follow this same pattern.

The following code is left incomplete for brevity:

public ObservableCollection ObjectList {...}
public static readonly DependencyProperty ObjectListProperty =
    DependencyProperty.Register(...OnObjectListChanged...);

private static void OnObjectListChanged(...)
{ObjectList.CollectionChanged += OnObjectListCollectionChanged;}

private void OnObjectListCollectionChanged(...){ UpdateVisualStates(); }

private void UpdateVisualStates()
{
    //actually you have to instatiate a SolidColorBrush here
    if (ContainsAtLeastOneAnimal()) { m_border.Background = Colors.Red; }
    else if (ContainsAtLeastOneVegetable()) {m_border.Background = Colors.Green;}
    else { m_border.Background = Colors.Blue; }
}

Feel free to introduce a DependencyProperty BorderColor and bind to it from your xaml if you don't want to have a reference to the border. It's fine. And there is really no problem with having another moving part. That's way better than simulating that the whole ObjectList instance changed.

Martin
  • 5,714
  • 2
  • 21
  • 41
  • Thanks for the doc pointer and sample. It looks like you've essentially moved my converter to control-specific code (referencing the border), but as you said adding another dependency property could alleviate that. Another property to me still seems unnecessary though and IMHO diminishes maintainability, since I need to ensure the objects are in sync. Kind of like having a "count" variable versus using list.Count. – Jim O'Neil Jun 10 '14 at 18:27
  • @JimO'Neil: I can think of an alternative that may be more to your taste: You could write a `BackgroundColorBehavior` and attach it to the `Border` element. Let the behavior have a `DependencyProperty` of type `INotifyCollectionChanged` (bind the ObjectList) and pull the converter code into your new behavior. So you do not have to litter your control's code. – Martin Jun 11 '14 at 07:27