IanG on Tap

Ian Griffiths in Weblog Form (RSS 2.0)

Blog Navigation

April (2018)

(1 item)

August (2014)

(1 item)

July (2014)

(5 items)

April (2014)

(1 item)

March (2014)

(1 item)

January (2014)

(2 items)

November (2013)

(2 items)

July (2013)

(4 items)

April (2013)

(1 item)

February (2013)

(6 items)

September (2011)

(2 items)

November (2010)

(4 items)

September (2010)

(1 item)

August (2010)

(4 items)

July (2010)

(2 items)

September (2009)

(1 item)

June (2009)

(1 item)

April (2009)

(1 item)

November (2008)

(1 item)

October (2008)

(1 item)

September (2008)

(1 item)

July (2008)

(1 item)

June (2008)

(1 item)

May (2008)

(2 items)

April (2008)

(2 items)

March (2008)

(5 items)

January (2008)

(3 items)

December (2007)

(1 item)

November (2007)

(1 item)

October (2007)

(1 item)

September (2007)

(3 items)

August (2007)

(1 item)

July (2007)

(1 item)

June (2007)

(2 items)

May (2007)

(8 items)

April (2007)

(2 items)

March (2007)

(7 items)

February (2007)

(2 items)

January (2007)

(2 items)

November (2006)

(1 item)

October (2006)

(2 items)

September (2006)

(1 item)

June (2006)

(2 items)

May (2006)

(4 items)

April (2006)

(1 item)

March (2006)

(5 items)

January (2006)

(1 item)

December (2005)

(3 items)

November (2005)

(2 items)

October (2005)

(2 items)

September (2005)

(8 items)

August (2005)

(7 items)

June (2005)

(3 items)

May (2005)

(7 items)

April (2005)

(6 items)

March (2005)

(1 item)

February (2005)

(2 items)

January (2005)

(5 items)

December (2004)

(5 items)

November (2004)

(7 items)

October (2004)

(3 items)

September (2004)

(7 items)

August (2004)

(16 items)

July (2004)

(10 items)

June (2004)

(27 items)

May (2004)

(15 items)

April (2004)

(15 items)

March (2004)

(13 items)

February (2004)

(16 items)

January (2004)

(15 items)

Blog Home

RSS 2.0

Writing

Programming C# 5.0

Programming WPF

Other Sites

Interact Software

XAML Dynamic Property Updates

Thursday 8 January, 2004, 04:36 PM

Chris Sells put an interesting XAML conundrum to me yesterday. It was one of those problems to which the intuitive reaction is "That must be dead simple" and which then takes about a day to figure out... The problem was essentially this:

How do I modify the appearance of all the buttons on a XAML page at once, programmatically?

This seems like it should be straightforward because, on the face of it, XAML’s styling mechanism does precisely this kind of thing. Consider this example:

<FlowPanel xmlns="http://schemas.microsoft.com/2003/xaml" xmlns:def="Definition">
    <FlowPanel.Resources>
        <Style>
            <Button Background="Red" ID="Button1"/>
            <Style.VisualTriggers>
                <PropertyTrigger Property="IsMouseOver" Value="true">
                    <Set PropertyPath="Background" Value="limegreen"/>
                </PropertyTrigger>
            </Style.VisualTriggers>
        </Style>
    </FlowPanel.Resources>

    <Button ID="Button2">Hello</Button>
    <Button ID="Button3">there</Button>
    <Button ID="Button4">world!</Button>
</FlowPanel>

The first child of that <Style> element tells Avalon that buttons should have a red background by default. The second child, <Style.VisualTriggers>, indicates that the background should be set to lime green if the button’s IsMouseOver property becomes true, i.e., it causes the button to go green when you move the mouse over it.

So we appear to have everything we need – not only do styles provide a mechanism for applying properties consistently across the entire page, that mechanism appears to support dynamic property changes.

Unfortunately, the extent of the support for dynamic behaviour appears to be surprisingly limited: as far as I can tell, styles and their associated triggers are essentially fixed at page load time. If you attempt to modify a style at runtime, you will get an InvalidOperationException, which turns out to be because the Style object has set an internal flag indicating that it is ‘sealed’. (This is not the same thing as the C# sealed keyword, by the way. It is just an internal flag that the Style maintains, and once it is set, it refuses to allow further updates.)

It seems like the Style and its associated triggers are read by Avalon when it builds its internal data structures to represent the UI, and then not used again. Styles do not remain ‘wired up’ which is presumably why they refuse to let you modify them. (It would be more irritating if they didn’t throw an exception, and instead just silently failed.) This is evidently not the mechanism we are looking for. Move along.

Fortunately, XAML is designed to support dynamic user interfaces which can directly reflect changes made to objects. It’s just that styles aren’t the mechanism we're supposed to use in order to do that. Instead, data binding seems to be the way to go here. We can use this syntax:

<ButtonBackground="*Bind(Path=Background;BindType=OneWay)">Bound</Button>

This indicates that the button’s Background should be bound to the Background property of the data source. But what is the data source here? We specify that by adding the following child to the FlowPanel:

<FlowPanel.DataContext>
    <Bind>
        <Bind.DataSource>
            <ObjectDataSource TypeName="MyStyleSource,MyAssembly"/>
        </Bind.DataSource>
    </Bind>
</FlowPanel.DataContext>

This indicates that an instance of a class called MyStyleSource, defined in a component called MyAssembly, should be created and used as a data source. All we have to do is write that class and give it a property called Background of the appropriate type. (MSAvalon.Windows.Media.Brush, in this case.) Recall for a moment the original goal here: to be able to modify the appearance of the buttons dynamically. For this to work, Avalon will need some way of detecting when our custom data source object changes. It will expect us to implement the IPropertyChange interface in order to indicate when such change occur. (This performs the same role as property change events in Windows Forms binding, except a single event is used to notify about any property changes, rather than having one event per property.) The data source will look like this:

using MSAvalon.ComponentModel;
using System.ComponentModel;

public class MyStyleSource : IPropertyChange
{
  private Brush bg = Brushes.Blue;
  public Brush Background
  {
    get { return bg; }
    set
    {
      if (!bg.Equals(value))
      {
        bg = value;
        if (PropertyChanged != null)
        {
          PropertyChanged(this,
            new PropertyChangedEventArgs("Background"));
                        
        }
      }
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;
}

We are now where we want to be. We have told Avalon to create an instance of our custom data source, and to bind its Background property to our button’s Background. All we have to do now is retrieve the data source and modify its Background property in some code on our panel’s codebehind:

StyleSource ss = (MyStyleSource) this.DataContext;
ss.Background = Brushes.Yellow;

This reads the panel’s DataContext property. We set this earlier (in the XAML) to be our custom data source. It then updates the Background property, which will raise the PropertyChanged event. This event will be handled by Avalon, which will update any components bound to the property, thus causing our button’s background colour to change.

However, I’m not really happy with this as it stands. It forces me to declare all my buttons like this:

<Button Background="*Bind(Path=Background;BindType=OneWay)">Click me</Button>

If I forget to put that property binding in there, the button won’t change when it should. Worse, if I want to change anything (e.g. bind to a different source object, or bind extra properties), I need to change every single button in my XAML! You’d think that this would be a job for styles. The obvious solution would be to put this at the top of the file:

<FlowPanel.Resources>
  <Style>
    <ButtonBackground="*Bind(Path=Background;BindType=OneWay)"/>
  </Style>
</FlowPanel.Resources>

Unfortunately that turns out not to work, and nor does the more verbose complex property syntax for specifying a bind here. The reason seems to be that bindings can only be applied to DependencyObjects, and although Button is a dependency object, we’re not really creating a Button here, despite how the syntax looks. It seems that the contents of a Style element are parsed using a completely different mechanism from the one used for the rest of the XAML file. As far as I can tell, this syntax populates a Style with a list of things it will do to any Button it is applied to, and that list doesn’t seem to have a provision for storing the fact that it should apply a binding. (Which you may or may not regard as a problem. I have to admit that it never occurred to me to ask if this was a useful thing to do. I like a challenge...)

This is as far as I have got. I can’t see a way of automatically assigning a binding to a particular property on every instance of a given type. And while this isn’t a show stopper, it’s a bit disappointing. But I may well have missed something – if I have, please let me know. (The fact that I haven’t written a comment facility for my blog engine yet makes this harder, admittedly, but feel free to email me.)

Copyright © 2002-2024, Interact Software Ltd. Content by Ian Griffiths. Please direct all Web site inquiries to webmaster@interact-sw.co.uk