#1,198 – Selectively Enabling Child Elements in a Disabled Panel

When you set IsEnabled to false in a panel, all child elements in that panel are disabled. You cannot selectively enabled child elements in the panel.

You may, however, want to selectively enable child elements in a panel. (E.g. Disable entire panel, then set IsEnabled=True, IsReadOnly=True on TextBox controls so that you can copy text).

One possible solution is to define a new control that inherits from TextBox and does not coerce the value of IsEnabled.

    public class CanEnableTextBox : TextBox
    {
        static CanEnableTextBox()
        {
            CanEnableTextBox.IsEnabledProperty.OverrideMetadata(typeof(CanEnableTextBox),
                new System.Windows.UIPropertyMetadata(true,
                    new PropertyChangedCallback(IsEnabledPropertyChanged),
                    new CoerceValueCallback(CoerceIsEnabled)));

        }

        private static void IsEnabledPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            // Overriding PropertyChanged results in merged metadata, which is what we want--
            // the PropertyChanged logic in UIElement.IsEnabled will still get invoked.
        }

        private static object CoerceIsEnabled(DependencyObject source, object value)
        {
            return value;
        }
    }

You can now use this control in a panel that has IsEnabled set to false and you’ll be able to set IsEnabled on the child TextBox.

#1,194 – DesiredSize of Child Elements Includes Margins

After the measure phase, during which a custom element calls the Measure method on each of its child elements, each child element will set its DesiredSize property to indicate how much space it wants.

The DesiredSize of a child element accounts for any margins that have been set on that child.  Assume that we have the following XAML (MyElement is a custom element with a single child).

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <loc:MyElement x:Name="Left">
            <loc:MyElement.Child>
                <Label Content="Doowahditty"/>
            </loc:MyElement.Child>
        </loc:MyElement>

        <loc:MyElement Grid.Column="1" x:Name="Right">
            <loc:MyElement.Child>
                <Label Content="Doowahditty" Margin="10"/>
            </loc:MyElement.Child>
        </loc:MyElement>
    </Grid>

We can see that the child element uses a margin.
1194-001

We can look at the value of the DesiredSize property after Measure has been called on the child element.  We can see that in the second case, the desired size is larger, indicating that the child wants to add a margin.

1194-002

 

 

#1,193 – MeasureOverride and Margins

During the measure phase, the MeasureOverride method is called on an element, indicating the size available to the control.  If a Margin has been set on the control, the available size passed in to MeasureOverride will have already been adjusted for that margin.

Below, we include two instances of MyElement in a Grid, setting a margin of 15 on the second element.

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <loc:MyElement/>
        <loc:MyElement Grid.Column="1" Margin="15"/>
    </Grid>

At run-time, we see that the second element is smaller.
1193-001
If we instrument MyElement to report the value of the Size parameter that is passed to it, we see that MeasureOverride gets a smaller size passed in for the second instance.  The Size has been adjusted for a uniform margin of 15, subtracting 30 from both the width and the height.  (We see the same values passed in to ArrangeOverride).

1193-002

 

#1,192 – Calling Arrange on Child Elements

When you author a custom control that contains child elements, you should call the Arrange method of each child object within your ArrangeOverride method.

Below is an example, from a custom element with one child.  The parent control has a dependency property, ChildProperty, representing the single child element.

        protected override Size ArrangeOverride(Size finalSize)
        {
            UIElement childElement = (UIElement)GetValue(ChildProperty);
            if (childElement != null)
                childElement.Arrange(new Rect(new Point(0.0, 0.0), finalSize));

            return finalSize;
        }

Below is a second example, from a custom panel that renders a treemap-like element.  The ChildrenTreemapOrder method returns an enumerable of ChildAndRect objects.

protected override Size ArrangeOverride(Size finalSize)
{
    foreach (ChildAndRect child in ChildrenTreemapOrder(InternalChildren.Cast<UIElement>(), finalSize))
        child.Element.Arrange(child.Rectangle);

    return finalSize;
}

#1,191 – Custom Element with a Single Child Element

Below is an example of a simple custom element that derives from FrameworkElement and includes a single child element (UIElement), set using the Child dependency property.

Here’s the full code for the custom element.  We do the following:

  • Override both Measure and Layout
  • Override VisualChildrenCount and GetVisualChild to respond indicating that we have a single child
  • Override Render to render the graphical portion of the control
  • Use AddVisualChildAddLogicalChild to indicate that the child element belongs to the parent element
    public class MyElement : FrameworkElement
    {
        private static FrameworkPropertyMetadata childMetadata =
                   new FrameworkPropertyMetadata(null,
                            FrameworkPropertyMetadataOptions.AffectsParentArrange,
                            new PropertyChangedCallback(OnChildChanged));

        public static void OnChildChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            MyElement thisElement = obj as MyElement;
            if (thisElement == null)
                throw new Exception("Child property must be attached to MyElement");

            // Remove old one
            Visual oldChild = e.OldValue as Visual;
            if (oldChild != null)
            {
                thisElement.RemoveVisualChild(oldChild);
                thisElement.RemoveLogicalChild(oldChild);
            }

            // Attach new one
            Visual newChild = e.NewValue as Visual;
            if (newChild != null)
            {
                thisElement.AddVisualChild(newChild);
                thisElement.AddLogicalChild(newChild);
            }
        }

        public static readonly DependencyProperty ChildProperty =
            DependencyProperty.RegisterAttached("Child", typeof(UIElement),
                typeof(MyElement), childMetadata);

        public static void SetChild(DependencyObject depObj, UIElement value)
        {
            depObj.SetValue(ChildProperty, value);
        }

        protected override int VisualChildrenCount
        {
            get
            {
                UIElement childElement = (UIElement)GetValue(ChildProperty);
                return childElement != null ? 1 : 0;
            }
        }

        protected override Visual GetVisualChild(int index)
        {
            // (ignoring index)
            return (UIElement)GetValue(ChildProperty);
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            UIElement childElement = (UIElement)GetValue(ChildProperty);
            if (childElement != null)
                childElement.Measure(availableSize);

            // "X" and child both use all of the available space
            return availableSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            UIElement childElement = (UIElement)GetValue(ChildProperty);
            if (childElement != null)
                childElement.Arrange(new Rect(new Point(0.0, 0.0), finalSize));

            return finalSize;
        }

        // Render a big "X"
        protected override void OnRender(DrawingContext dc)
        {
            dc.DrawLine(new Pen(Brushes.Blue, 2.0),
                new Point(0.0, 0.0),
                new Point(ActualWidth, ActualHeight));
            dc.DrawLine(new Pen(Brushes.Green, 2.0),
                new Point(ActualWidth, 0.0),
                new Point(0.0, ActualHeight));
        }
    }

We can now use this element as follows:

    <Grid>
        <loc:MyElement>
            <loc:MyElement.Child>
                <Label Content="I'm the child" HorizontalAlignment="Center"/>
            </loc:MyElement.Child>
        </loc:MyElement>
    </Grid>

When rendered, it looks like this:

1191-001

#1,190 – VisualChildrenCount and GetVisualChild

FrameworkElement provides the VisualChildrenCount property and the GetVisualChild method, typically used during layout to walk through the child elements of the element participating in layout.  When you create a custom control that derives from FrameworkElement, you typically override both members, providing values that make sense for your control.

Below, we use these members to get a report of the visual children of a custom StackPanel.  (Because neither member is public, we override StackPanel to allow access).

XAML is:

    <StackPanel>
        <loc:MyStackPanel x:Name="aStackPanel">
            <Label Content="Hi there"/>
            <Button Content="Click me"/>
        </loc:MyStackPanel>

        <Button Content="Visual Children" Click="Button_Click"/>
    </StackPanel>

Code-behind is:

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            StringBuilder sbMsg = new StringBuilder();

            sbMsg.Append("Children of MyStackPanel:\n");
            int childCount = aStackPanel.MyVisualChildrenCount;
            for (int i = 0; i < childCount; i++)
            {
                Visual child = aStackPanel.MyGetVisualChild(i);
                sbMsg.AppendFormat(" - {0}\n", child.GetType().ToString());
            }

            MessageBox.Show(sbMsg.ToString());
        }

Results at run-time:
1190-001

#1,189 – MeasureOverride Input and Output

The MeasureOverride method in FrameworkElement takes as input a Size and then returns a Size.  These parameters are intended to be used as follows:

  • Input Size – during layout, this is how much room is available for the element to be rendered
  • Output Size – this is how much room the element says that it needs

For example, below we have a custom element that draws a big “X” using all of the available space.  (When rendering, it uses ActualWidth and ActualHeight).  Because it will use as much room as it is given, MeasureOverrides return value is all of the available space.

    public class MyElement : FrameworkElement
    {
        protected override Size MeasureOverride(Size availableSize)
        {
            Size sizeIWillNeed = availableSize;

            return sizeIWillNeed;
        }

        protected override void OnRender(DrawingContext dc)
        {
            dc.DrawLine(new Pen(Brushes.Blue, 2.0),
                new Point(0.0, 0.0),
                new Point(ActualWidth, ActualHeight));
            dc.DrawLine(new Pen(Brushes.Green, 2.0),
                new Point(ActualWidth, 0.0),
                new Point(0.0, ActualHeight));
        }
    }