WPF Tips n' Tricks – Another way to declare read-only dependency properties

Sorry for the hiatus this week-end, I spent a lovely time out of London, disconnected from the online world. Back online (in the train, with WIFI, fantastic, check it out).

Read-only dependency properties (attached or not) are declared by a call to RegisterReadOnly, which returns a DependencyPropertyKey type, which you have to use when setting values. From that object you get a reference to the DependencyPropert object that is used to read values. Let's look at a typical dependency property registration.

public class Div : Control

{

private static DependencyProperty LeftProperty;

private static DependencyPropertyKey LeftPropertyKey;

static Div()

{

Div.LeftPropertyKey = DependencyProperty.RegisterReadOnly(

"Left",

typeof(double),

typeof(Div),

new FrameworkPropertyMetadata(0d));

Div.LeftProperty = Div.LeftPropertyKey.DependencyProperty;

}

Traditionally, you would then define a property with only a getter. But one of the not so known new features of C# 2.0 is the ability to declare different access modifiers for the getter and the setter of a property. We'll use it to our advantage to declare a property getter but have a private setter using the Key, keeping our object model clean and nifty.

public double Left

{

get { return (double)GetValue(Div.LeftProperty); }

private set { SetValue(Div.LeftPropertyKey, value); }

}

And voila, a nice and clean way to set your read-only properties without calls to SetValue all over the place.

[Edit: Added the private as I forgot it. Thanks for correcting me! ]

Ads

WPF Bug, TextBlock with empty element

A note for me as much as for everybody else (while waiting for the Connect website to be updated to let us fill RTM bugs).

Having a TextBlock containing an element that has no size ends up with an ArgumentOutOfRange exception. Tsk tsk tsk.

<Page

  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <TextBlock>

        <Hyperlink />

    </TextBlock>

</Page>

Ads

WPF Tips n' Tricks – Reusing the content of a Popup control

That one is far from obvious, and is what I'd classify as a bug. As soon as Microsoft fix the Connect web-site to let us report RTM bugs, I'll more than happily fill one up.

In WPF, controls can only have one parent at a time. Because rendering is done top down and every control has a sequence of Measure/Arrange calls to define the layout of the windows, it makes perfect sense.

It also makes perfect sense to be able to remove a control from somewhere (let's say a Button from a Panel) and re-add it somewhere else. Quick and dirty example, a button switching between two panels.

<Window x:Class="PopupExample.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:my="clr-namespace:IsDirtyExample"

    Title="IsDirtyExample" SizeToContent="WidthAndHeight">

    <DockPanel LastChildFill="False">

        <DockPanel DockPanel.Dock="Top" Name="FirstGrid" Background="Orange" Width="250" Height="200">

            <Button Name="OneButtonToRuleThemAll">Ding</Button>

        </DockPanel>

        <DockPanel DockPanel.Dock="Top" Name="SecondGrid" Background="OrangeRed" Width="250" Height="200"></DockPanel>

        <Button DockPanel.Dock="Top" Click="HandleMoveClick">Move the Ding!</Button>

    </DockPanel>

</Window>

namespace PopupExample

{

    public partial class Window1 : System.Windows.Window

    {

        public Window1()

        {

            InitializeComponent();

        }

        public void HandleMoveClick(object source, RoutedEventArgs e)

        {

            FirstGrid.Children.Remove(OneButtonToRuleThemAll);

            SecondGrid.Children.Add(OneButtonToRuleThemAll);

        }

    }

}

When you create a Popup control however, it only has a Child property. So you'd assume you would just change the Child property to a new control, and that new control would then be showing next time you open the Popup, and the old control wouldn't be in any visual tree anymore. Well, it doesn't always happen that way at all.

If your Popup is visible, then you'll see the change. If the Popup is not visible anymore, and you set it's Child property to null, and reattach your control to a new Popup, you'll be greeted by a very useful message.

Must disconnect specified child from current parent Visual before attaching to new parent Visual.

But wait, the control is not assigned to the Child property anymore? It is and it is not. Whenever the popup gets created, it keeps a separate object deep within called a PopupRoot. That one gets created once and has as a child the control pointed by the Popup's Child property. But whenever a popup is closed (IsOpened=false), changes to its Child property will not impact the PopupRoot that still has a reference to the previous value. Hence why when you try to add your control somewhere else, you have an exception

The solution? Put as the first child of your popup a neutral panel (Grid for example), and move your controls around only as a child of that grid. Full code sample shown below.

namespace PopupExample

{

    public partial class Window1 : System.Windows.Window

    {

        public Window1()

        {

            InitializeComponent();

        }

        private void button_Click(object sender, RoutedEventArgs e)

        {

            if (popupButton == null)

            {

                popupButton = new Button();

                popupButton.Content = "This button on a popup";

            }

 

            Popup popup = new Popup();

            Grid grid = new Grid();

            grid.Children.Add(popupButton);

            popup.Child = grid;

            popup.PlacementTarget = this;

            popup.PlacementRectangle = new Rect(0, 0, this.ActualWidth, this.ActualHeight - 18);

            popup.Placement = PlacementMode.Bottom;

            popup.StaysOpen = false;

            popup.IsOpen = true// Second call used to trigger an exception

            popup.Closed += new EventHandler(popup_Closed);

 

        }

 

        private void popup_Closed(object sender, EventArgs e)

        {

            Popup popup = (Popup)sender;

            ((Grid)popup.Child).Children.Clear(); // will finally clear the control from being in a visual tree

            popup.Closed -= new EventHandler(popup_Closed);

 

        }

    }

}

 

Ads