views:

1898

answers:

2

Hello!

I'm using WPF within Visual Studio 2008. I have a simple WPF UserControl with the following code:

  public partial class UserControl1 : UserControl
  {
    public UserControl1()
    {
      InitializeComponent();
      Composite = new Composite();
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
      //LayoutRoot is name of default Grid instance
      if (!LayoutRoot.Children.Contains(Composite))
      {
        LayoutRoot.Children.Add(Composite);
      }
    }

    public Composite Composite
    {
      get;
      set;
    }
  }

  public class Composite : ContentControl
  {
    protected override void OnRender(DrawingContext drawingContext)
    {
      drawingContext.DrawRectangle(new SolidColorBrush(Color), new Pen(Brushes.Black, 1.0), new Rect(RenderSize));
    }

    public Color Color
    {
      get;
      set;
    }
  }

I then use this UserControl in a WPF application, the XAML of the page looking like this:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:test="clr-namespace:WpfControlLibrary1;assembly=WpfControlLibrary1"
    Title="Window1" Height="500" Width="700" Background="AliceBlue">
  <test:UserControl1 Name="uControl1">
    <test:UserControl1.Composite>
      <test:Composite Color="Green"/>
    </test:UserControl1.Composite>
  </test:UserControl1>
</Window>

My question is: what code do I have to add to the above so that by changing "Composite Color" to something other than Green and hitting the return button, the UserControl automatically refreshes? The behaviour I'm looking for is what happens when you change the Background of Window1 to a color other than AliceBlue and hit return.

When I run the code the correct color is seen, the problem is with the refresh at designtime via XAML.

Many thanks for any pointers which help me understand what is going on here!

+4  A: 

WPF is optimized to only re-draw items when absolutely necessary, so the behavior you are seeing is partly because WPF doesn't know that when you change Composite.Color, it needs to re-draw the control.

On top of that, the Visual Studio/Blend designer (or XAML controls) won't be able to notice that that property has changed since your control doesn't signal that that property is changed; without that notification, WPF doesn't know that the property is changed (which saves it from checking all of the time to see if things have changed).

While you could implement INotifyPropertyChanged and fix the second problem, since you're inheriting from ContentControl (which, high up in the inheritance hierarchy, implements INotifyPropertyChanged), you can use a DependencyProperty and fix both problems - and get better WPF binding support, to boot!

// DependencyProperty backing store for Color.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty ColorProperty =
    DependencyProperty.Register("Color", typeof(Color), typeof(Composite), 
    new FrameworkPropertyMetadata(new Color(), FrameworkPropertyMetadataOptions.AffectsRender));

public Color Color
{
    get { return (Color)GetValue(ColorProperty); }
    set { SetValue(ColorProperty, value); }
}

By setting FrameworkPropertyMetadataOptions.AffectsRender, WPF knows that when this property is changed, the render pass for this item needs to be performed again, and will re-render your control, calling your OnRender() method and displaying the new color. And, as a DependencyProperty, it's automatically generating the right PropertyChanged event so that anybody watching your class (like WPF's binding and animation system) will get notified of the update as soon as the property changes.

Nicholas Armstrong
Thanks Nicholas, but this code produces a System.Windows.Markup.XamlParseException error when applied to my code example. If I take out the FrameworkPropertyMetadataOptions.AffectsRender parameter from the FrameworkPropertyMetadata constructor then I don't get the error but my problem isn't solved.
Shunyata Kharg
That's what I get for not fully testing my code! I've updated the code and tested that it works - I mistakenly initialized the DependencyProperty to null, which fails with a XamlParseException. By initializing the value to white by using new Color(), the DependencyProperty initializes and the designer updates as soon as you change the color. My apologies.
Nicholas Armstrong
That now works, thank you Nicholas! Setting the defaultValue parameter to null only seems to be a problem if you define a flags parameter, otherwise null works just fine. Anyhow, I can now mark your reply as the answer. Thanks again!
Shunyata Kharg
A: 

You are going about this in a very strange manner (reminiscent of an MFC control); take some time and read through the first half or so of WPF Unleashed (http://www.amazon.com/Windows-Presentation-Foundation-Unleashed-WPF/dp/0672328917/ref=sr_1_1?ie=UTF8&amp;s=books&amp;qid=1243491021&amp;sr=8-1), it's enjoyable to read and everything will make much more sense.

Paul Betts
Thanks for the tip, Paul. The control I work on uses large datasets which generally are too big to type into XAML at designtime. This means that I have to create large numbers of objects dynamically at runtime, something that's perfectly feasible to do in an "MFC" way using WPF. However, I would also like to introduce some more WPF advantages to this model, hence my inheriting these objects from ContentControl and getting each object to render itself, rather than passing the DrawingContext around from one to the other.
Shunyata Kharg