#1,175 – Custom Panel, part VII (Using Attached Property to Arrange)

Here’s an example of a custom panel that uses an attached property (weight) in determining both size and position of child elements.

    public class WeightedPanel : Panel
    {
        private static FrameworkPropertyMetadata weightMetadata =
            new FrameworkPropertyMetadata(1.0,
                FrameworkPropertyMetadataOptions.AffectsParentArrange);

        public static readonly DependencyProperty WeightProperty =
            DependencyProperty.RegisterAttached("Weight", typeof(double),
                typeof(WeightedPanel), weightMetadata);

        public static void SetWeight(DependencyObject depObj, double value)
        {
            depObj.SetValue(WeightProperty, value);
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            double totalWeight = totalChildWeight();

            foreach (UIElement elem in InternalChildren)
            {
                double childWeight = (double)elem.GetValue(WeightProperty);
                double childHeight = (childWeight / totalWeight) * availableSize.Height;
                elem.Measure(new Size(availableSize.Width, childHeight));
            }

            return availableSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            double totalWeight = totalChildWeight();
            double top = 0.0;

            foreach (UIElement elem in InternalChildren)
            {
                double childWeight = (double)elem.GetValue(WeightProperty);
                double childHeight = (childWeight / totalWeight) * finalSize.Height;
                Rect r = new Rect(new Point(0.0, top),
                                  new Size(elem.DesiredSize.Width, childHeight));

                elem.Arrange(r);

                top += childHeight;
            }

            return finalSize;
        }

        private double totalChildWeight()
        {
            double weightSum = 0;
            foreach (UIElement elem in InternalChildren)
                weightSum += (double)elem.GetValue(WeightProperty);

            return weightSum;
        }
    }

Below, we use this panel, specifying that 2nd label is 2x bigger (more weight) than the first label.

    <loc:WeightedPanel>
        <Label Content="I'm child #1" loc:WeightedPanel.Weight="1"
               Background="Thistle" />
        <Label Content="I'm child #2" loc:WeightedPanel.Weight="2"
               Background="Lavender" />
        <!-- Weight defaults to 1 -->
        <Label Content="Third kid"
               Background="Honeydew" />
    </loc:WeightedPanel>

1175-001

#1,142 – Setting Attached Property Value from Code

You can change the value of an attached property for a given control from code by using the SetValue or SetCurrentValue methods.  You call these methods on the control that the property is attached to, passing in a reference to the property and the new value.  (SetCurrentValue is preferred, to avoid overwriting a local value).

Below, we set a value for MyAttProps.Important in XAML, but also wire up a Click event to allow changing the value from code.

        <Label x:Name="lblHi" Content="Hi there"
               loc:MyAttProps.Important="True"
               Background="AliceBlue"/>
        <Button Content="Change Content"
                Click="Button_Click"/>

In the code-behind, we use SetCurrentValue to change the value.

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            bool impValue = (bool)lblHi.GetValue(MyAttProps.ImportantProperty);

            lblHi.SetCurrentValue(MyAttProps.ImportantProperty, !impValue);
        }

#1,141 – Attached Properties Allow Customization of Existing Controls

An attached property is a dependency property defined in one class and then attached (used) on an instance of some other class.

You can define your own attached properties as a mechanism for extending the appearance or behavior of a control.

Below, we register a dependency property of type bool, named Important.  When Important is set to true, we set the foreground brush of the control to red.

    public class MyAttProps
    {
        // Surround property definition
        static PropertyMetadata ImportantMetadata =
            new PropertyMetadata(
                false,                // Default value
                OnImportantChanged,   // Changed callback
                null);                // Coerce value callback

        public static readonly DependencyProperty ImportantProperty =
            DependencyProperty.RegisterAttached(
                "Important",          // Property name
                typeof(bool),         // Property type
                typeof(MyAttProps),   // Defining class type
                ImportantMetadata);   // Metadata described above

        // Allow setting value from XAML
        public static void SetImportant(DependencyObject depObj, bool value)
        {
            depObj.SetValue(ImportantProperty, value);
        }

        public static bool GetImportant(DependencyObject depObj)
        {
            return (bool)depObj.GetValue(ImportantProperty);
        }

        // Important property has changed
        public static void OnImportantChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            Control ctrl = d as Control;
            bool important = (bool)e.NewValue;
            if ((ctrl != null) && important)
                ctrl.SetValue(Control.ForegroundProperty, Brushes.Red);
            else
                ctrl.ClearValue(Control.ForegroundProperty);
        }
    }

We can now use this new property from XAML:

        <Label x:Name="lblHi" Content="Hi there" 
               loc:MyAttProps.Important="True"
               Background="AliceBlue"/>

1141-001