WPF Attached Property Content Control

1.3k Views Asked by At

I have an attached property to provide context help for my varying uielements in my WPF application.

The attached property is set to the child property of a border control that is the Child property of a popup control that is used to provide context sensitive help.

Problem is the color foreground color defaults to random things depending on what UIEelment to which the attached property is attached.

As guess I think this is happening because the content property is using the visual tree of the attached control.

How can I force the content property to reevaluate the default properties fore foreground, font etc?

This is based on this article: http://www.codeproject.com/Articles/59535/A-Simple-Integrated-WPF-Help-System

Ultimately it is probably my derived popup class that needs fixing. I've been looking for a better way to implement my popup but haven't found it yet.

All the fun code is found below. starting with the popup class.

Thanks for any assistance.

public class HelpPopup : Popup
{
    private readonly ContentPresenter mContentPresenter = new ContentPresenter();
    private readonly Border mBorder = new Border();
    private readonly ContentControl mControl = new ContentControl();

    public HelpPopup()
    {
        mBorder.Child = mContentPresenter;
        mControl.Content = mBorder;
        Child = mControl;

        // no background for content control...
        mControl.Background = null;
        mControl.Foreground = SystemColors.ControlTextBrush;

        AllowsTransparency = true;
    }

    #region Content Dependency Property

    public static readonly DependencyProperty ContentProperty = ContentControl.ContentProperty.AddOwner(typeof (HelpPopup), new PropertyMetadata(default(object), ContentChanged));

    private static void ContentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        HelpPopup src = obj as HelpPopup;

        if (src != null)
        {
            // simply update content to match that what's being set..
            src.mContentPresenter.Content = e.NewValue;
        }
    }

    public object Content
    {
        get
        {
            object res = default(object);

            if (CheckAccess() != false)
            {
                res = (object) GetValue(ContentProperty);
            }
            else
            {
                Dispatcher.Invoke(() => res = (object) GetValue(ContentProperty));
            }

            return res;
        }
        set
        {
            if (CheckAccess() != false)
            {
                SetValue(ContentProperty, value);
            }
            else
            {
                Dispatcher.Invoke(() => SetValue(ContentProperty, value));
            }
        }
    }

    #endregion

    #region Background Dependency Property

    public static readonly DependencyProperty BackgroundProperty = 
                    Border.BackgroundProperty.AddOwner(typeof (HelpPopup), new PropertyMetadata(default(Brush), BackgroundChanged));

    private static void BackgroundChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        HelpPopup src = obj as HelpPopup;

        if (src != null)
        {
            src.mBorder.Background = e.NewValue as Brush;
        }
    }

    public Brush Background
    {
        get
        {
            Brush res = default(Brush);

            if (CheckAccess() != false)
            {
                res = (Brush) GetValue(BackgroundProperty);
            }
            else
            {
                Dispatcher.Invoke(() => res = (Brush) GetValue(BackgroundProperty));
            }

            return res;
        }
        set
        {
            if (CheckAccess() != false)
            {
                SetValue(BackgroundProperty, value);
            }
            else
            {
                Dispatcher.Invoke(() => SetValue(BackgroundProperty, value));
            }
        }
    }

    #endregion

    #region Foreground Dependency Property

    public static readonly DependencyProperty ForegroundProperty = Control.ForegroundProperty.AddOwner(
        typeof (HelpPopup), new FrameworkPropertyMetadata(Brushes.Black, ForegroundChanged));

    private static void ForegroundChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        HelpPopup src = obj as HelpPopup;

        if (src != null)
        {
            src.mControl.Foreground = e.NewValue as Brush;
        }
    }

    public Brush Foreground
    {
        get
        {
            Brush res = default(Brush);

            if (CheckAccess() != false)
            {
                res = (Brush) GetValue(ForegroundProperty);
            }
            else
            {
                Dispatcher.Invoke(() => res = (Brush) GetValue(ForegroundProperty));
            }

            return res;
        }
        set
        {
            if (CheckAccess() != false)
            {
                SetValue(ForegroundProperty, value);
            }
            else
            {
                Dispatcher.Invoke(() => SetValue(ForegroundProperty, value));
            }
        }
    }

    #endregion

    #region BorderBrush Dependency Property

    public static readonly DependencyProperty BorderBrushProperty = Border.BorderBrushProperty.AddOwner(
        typeof (HelpPopup),
        new PropertyMetadata(default(Brush), BorderBrushChanged));

    private static void BorderBrushChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        HelpPopup src = obj as HelpPopup;

        if (src != null)
        {
            src.mBorder.BorderBrush = e.NewValue as Brush;
        }
    }

    public Brush BorderBrush
    {
        get
        {
            Brush res = default(Brush);

            if (CheckAccess() != false)
            {
                res = (Brush) GetValue(BorderBrushProperty);
            }
            else
            {
                Dispatcher.Invoke(() => res = (Brush) GetValue(BorderBrushProperty));
            }

            return res;
        }
        set
        {
            if (CheckAccess() != false)
            {
                SetValue(BorderBrushProperty, value);
            }
            else
            {
                Dispatcher.Invoke(() => SetValue(BorderBrushProperty, value));
            }
        }
    }

    #endregion

    #region BorderThickness Dependency Property

    public static readonly DependencyProperty BorderThicknessProperty = Border.BorderThicknessProperty.AddOwner(
        typeof (HelpPopup),
        new PropertyMetadata(default(Thickness), BorderThicknessChanged));

    private static void BorderThicknessChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        HelpPopup src = obj as HelpPopup;

        if (src != null)
        {
            src.mBorder.BorderThickness = (Thickness) e.NewValue;
        }
    }

    public Thickness BorderThickness
    {
        get
        {
            Thickness res = default(Thickness);

            if (CheckAccess() != false)
            {
                res = (Thickness) GetValue(BorderThicknessProperty);
            }
            else
            {
                Dispatcher.Invoke(() => res = (Thickness) GetValue(BorderThicknessProperty));
            }

            return res;
        }
        set
        {
            if (CheckAccess() != false)
            {
                SetValue(BorderThicknessProperty, value);
            }
            else
            {
                Dispatcher.Invoke(() => SetValue(BorderThicknessProperty, value));
            }
        }
    }

    #endregion

    #region CornerRadius Dependency Property

    public static readonly DependencyProperty CornerRadiusProperty = Border.CornerRadiusProperty.AddOwner(
        typeof (HelpPopup),
        new PropertyMetadata(default(CornerRadius), CornerRadiusChanged));

    private static void CornerRadiusChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        HelpPopup src = obj as HelpPopup;

        if (src != null)
        {
            src.mBorder.CornerRadius = (CornerRadius) e.NewValue;
        }
    }

    public CornerRadius CornerRadius
    {
        get
        {
            CornerRadius res = default(CornerRadius);

            if (CheckAccess() != false)
            {
                res = (CornerRadius) GetValue(CornerRadiusProperty);
            }
            else
            {
                Dispatcher.Invoke(() => res = (CornerRadius) GetValue(CornerRadiusProperty));
            }

            return res;
        }
        set
        {
            if (CheckAccess() != false)
            {
                SetValue(CornerRadiusProperty, value);
            }
            else
            {
                Dispatcher.Invoke(() => SetValue(CornerRadiusProperty, value));
            }
        }
    }

    #endregion

    #region Padding Dependency Property

    public static readonly DependencyProperty PaddingProperty = Border.PaddingProperty.AddOwner(
        typeof (HelpPopup),
        new PropertyMetadata(default(Thickness), PaddingChanged));

    private static void PaddingChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        HelpPopup src = obj as HelpPopup;

        if (src != null)
        {
            src.mBorder.Padding = (Thickness) e.NewValue;
        }
    }

    public Thickness Padding
    {
        get
        {
            Thickness res = default(Thickness);

            if (CheckAccess() != false)
            {
                res = (Thickness) GetValue(PaddingProperty);
            }
            else
            {
                Dispatcher.Invoke(() => res = (Thickness) GetValue(PaddingProperty));
            }

            return res;
        }
        set
        {
            if (CheckAccess() != false)
            {
                SetValue(PaddingProperty, value);
            }
            else
            {
                Dispatcher.Invoke(() => SetValue(PaddingProperty, value));
            }
        }
    }

    #endregion

    static HelpPopup()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(HelpPopup), new FrameworkPropertyMetadata(typeof(HelpPopup)));
    }

The attached property is defined here:

class Help
{
    struct HelpActiveInfo
    {
        private EventHandler Handler { get; set; }
        private UIElement Element { get; set; }
        private DependencyPropertyDescriptor Descriptor { get; set; }

        public HelpActiveInfo(UIElement element, DependencyProperty property, EventHandler handler) : this()
        {
            if (element == null)
                throw new InvalidArgumentValue("element", "element cannot be null");

            Element = element;

            if (handler == null)
                throw new InvalidArgumentValue("handler", "handler cannot be null");

            Handler = handler;

            if (property == null)
                throw new InvalidArgumentValue("property", "property cannot be null");

            Descriptor = DependencyPropertyDescriptor.FromProperty(property, typeof(UIElement));
        }

        public void AddHandler()
        {
            if (Handler != null)
            {
                if (Descriptor != null)
                {
                    if (Element != null)
                        Descriptor.AddValueChanged(Element, Handler);
                }
            }
        }

        public void RemoveHandler()
        {
            if (Handler != null)
            {
                if (Descriptor != null)
                {
                    if (Element != null)
                        Descriptor.RemoveValueChanged(Element, Handler);
                }
            }
        }
    }

    private readonly static Dictionary<UIElement, HelpActiveInfo> smChangeMap = new Dictionary<UIElement, HelpActiveInfo>(); 

    #region Help Attached Property

    public static readonly DependencyProperty HelpProperty = DependencyProperty.RegisterAttached("Help", typeof(UIElement), typeof(Help), new PropertyMetadata(default(UIElement), HelpChanged));

    private static void HelpChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        UIElement ui = obj as UIElement;

        if (ui != null)
        {
            HelpActiveInfo hi;
            HelpWindow h = GetParentWindow(ui) as HelpWindow;

            if (smChangeMap.TryGetValue(ui, out hi) != false)
            {
                hi.RemoveHandler();
                smChangeMap.Remove(ui);
            }

            // if have help window, and the new help is not null
            // then monitor help active property to decide when to 
            // add adorner to the element....
            if ((h != null) && (e.NewValue != null))
            {
                hi = new HelpActiveInfo(h, HelpWindow.HelpActiveProperty,
                    (sender, args) =>
                    {
                        AdornerLayer al = AdornerLayer.GetAdornerLayer(ui);

                        if (al != null)
                        {
                            Adorner[] adorners = al.GetAdorners(ui);

                            // remove and existing help adorners
                            if (adorners != null)
                            {
                                List<Adorner> rl = adorners.Where(a => a.GetType() == typeof (HelpAdorner)).ToList();

                                foreach (var r in rl)
                                    al.Remove(r);
                            }

                            // no adorners, add new one if needed
                            if (h.HelpActive != false)
                            {
                                HelpAdorner ha = new HelpAdorner(ui);

                                al.Add(ha);
                            }
                        }
                    });

                smChangeMap[ui] = hi;
                smChangeMap[ui].AddHandler();
            }
        }
    }

    public static void SetHelp(DependencyObject element, UIElement value)
    {
        element.SetValue(HelpProperty, value);
    }

    public static UIElement GetHelp(DependencyObject element)
    {
        return (UIElement)element.GetValue(HelpProperty);
    }
    #endregion

    #region Glow Brush Attached Property
    public static readonly DependencyProperty GlowBrushProperty = DependencyProperty.RegisterAttached(
        "GlowBrush", typeof (Brush), typeof (Help), new PropertyMetadata(new SolidColorBrush(Colors.Red) { Opacity = 0.3 }));

    public static void SetGlowBrush(DependencyObject element, Brush value)
    {
        element.SetValue(GlowBrushProperty, value);
    }

    public static Brush GetGlowBrush(DependencyObject element)
    {
        return (Brush) element.GetValue(GlowBrushProperty);
    }
    #endregion

    #region helpers
    public static Window GetParentWindow(DependencyObject child)
    {
        Window res = null;

        if (child != null)
        {
            DependencyObject parentObject = VisualTreeHelper.GetParent(child);

            res = parentObject as Window ?? GetParentWindow(parentObject);
        }

        return res;
    }
    #endregion
}

A help window class manages the individual help items:

public class HelpWindow : Window
{
    private DependencyObject CurrentHelpObject { get; set; }
    private HelpPopup CurrentHelpPopup { get; set; }

    public HelpWindow()
    {
        CommandBindings.Add(new CommandBinding(ApplicationCommands.Help,
            (x, y) =>
            {
                HelpActive = (HelpActive == false);
            },
            (x, y) =>
            {
                y.CanExecute = true;
            }));

        Loaded += OnLoaded;
    }

    private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
    {
        InitHelpSystem(this);
    }

    private void InitHelpSystem(DependencyObject obj)
    {
        // Continue recursive toggle. Using the VisualTreeHelper works nicely.
        for (int x = 0; x < VisualTreeHelper.GetChildrenCount(obj); x++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(obj, x);
            InitHelpSystem(child);
        }

        UIElement help = obj.GetValue(Help.HelpProperty) as UIElement;

        if (help != null)
        {
            // force a reset of the property because window is up and running
            obj.SetValue(Help.HelpProperty, null);
            obj.SetValue(Help.HelpProperty, help);
        }
    }

    // Return the result of the hit test to the callback.
    private readonly Stack<DependencyObject> mHitTestCollection = new Stack<DependencyObject>();

    public HitTestResultBehavior HitTestResult(HitTestResult result)
    {
        DependencyObject d = result.VisualHit;

        if (d != null)
            mHitTestCollection.Push(d);

        // Set the behavior to return visuals at all z-order levels. 
        return HitTestResultBehavior.Continue;
    }

    private void OnMouseMove(object sender, MouseEventArgs e)
    {
        object content = null;
        DependencyObject checkHelpObject = null;
        List<UIElement> dl = new List<UIElement>();

        mHitTestCollection.Clear();

        // You can check the HelpActive property if desired, however 
        // the listener should not be hooked up so this should not be firing
        VisualTreeHelper.HitTest(sender as Visual, null, new HitTestResultCallback(HitTestResult), new PointHitTestParameters(e.GetPosition(this)));

        // walk the list find the objects (or the parents that have 
        while (mHitTestCollection.Count > 0)
        {
            DependencyObject d = mHitTestCollection.Pop();

            if (d != null)
            {
                object c = null;
                UIElement oc = null;

                // find the content property for this one...
                while ((c == null) && (Equals(d, this) == false))
                {
                    oc = d as UIElement;

                    if (oc != null)
                        c = oc.GetValue(Help.HelpProperty) as DependencyObject;

                    d = VisualTreeHelper.GetParent(d);
                }

                if (c != null)
                {
                    // remove the containing control if it already is n the list
                    if (dl.Contains(oc) != false)
                        dl.Remove(oc);

                    // then add it to the list
                    // this ensures that top of the list is always the lowest seen 
                    // object that implements the attached property
                    dl.Add(oc);
                }
            }
        }

        // go get the object to use
        checkHelpObject = dl.FirstOrDefault();

        // fetch content
        content = (checkHelpObject != null) ? checkHelpObject.GetValue(Help.HelpProperty) : null;

        // and process fetched content...
        if ((content == null) && (CurrentHelpPopup != null))
        {
            CurrentHelpPopup.IsOpen = false;
            CurrentHelpPopup = null;
            CurrentHelpObject = null;
        }
        else
        {
            if ((content != null) && (Equals(CurrentHelpObject, checkHelpObject) == false))
            {
                CurrentHelpObject = checkHelpObject;

                // New visual "stack" hit, close old popup, if any
                if (CurrentHelpPopup != null)
                    CurrentHelpPopup.IsOpen = false;

                CurrentHelpPopup = new HelpPopup()
                                   {
                                       IsOpen = true,
                                       Content = content,
                                       AllowsTransparency = true,
                                       PopupAnimation = PopupAnimation.Fade,
                                       PlacementTarget = (UIElement)checkHelpObject
                                   };
            }
        }
    }

    #region HelpActive Dependency Property

    public static readonly DependencyProperty HelpActiveProperty =
        DependencyProperty.Register("HelpActive", typeof (bool), typeof (HelpWindow),
            new PropertyMetadata(default(bool), HelpActiveChanged));

    private static void HelpActiveChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        HelpWindow src = obj as HelpWindow;

        if (src != null)
        {
            if (src.CurrentHelpPopup != null)
                src.CurrentHelpPopup.IsOpen = false;

            src.CurrentHelpObject = null;
            src.CurrentHelpPopup = null;

            // enable/disable mouse move handlers...
            if (((bool) e.OldValue) != false)
                src.MouseMove -= src.OnMouseMove;

            if (((bool)e.NewValue) != false)
                src.MouseMove += src.OnMouseMove;
        }
    }

    public bool HelpActive
    {
        get
        {
            bool res = default(bool);

            if (CheckAccess() != false)
            {
                res = (bool) GetValue(HelpActiveProperty);
            }
            else
            {
                Dispatcher.Invoke(() => res = (bool) GetValue(HelpActiveProperty));
            }

            return res;
        }
        set
        {
            if (CheckAccess() != false)
            {
                SetValue(HelpActiveProperty, value);
            }
            else
            {
                Dispatcher.Invoke(() => SetValue(HelpActiveProperty, value));
            }
        }
    }

    #endregion
}
0

There are 0 best solutions below