Click here to Skip to main content
15,892,927 members
Articles / Desktop Programming / WPF

Use any DataTemplate as an Adorner in WPF

Rate me:
Please Sign up or sign in to vote.
4.72/5 (9 votes)
18 Nov 2011CPOL2 min read 51.4K   2.2K   33  
This article explains a simpler way of attaching adorners by using a WPF behavior class.
  • AdornerBehaviorSample.zip
    • AdornerBehaviorSample
      • .svn
        • all-wcprops
        • entries
        • prop-base
          • AdornerBehaviorSample.suo.svn-base
          • EventMapping.png.svn-base
        • props
          • BlendSelection.png.svn-work
          • CodeProject.docx.svn-work
          • FinalScreen.png.svn-work
        • text-base
          • AdornerBehaviorSample.sln.svn-base
          • AdornerBehaviorSample.suo.svn-base
          • EventMapping.png.svn-base
        • tmp
          • prop-base
          • props
          • text-base
      • ~$deProject.docx
      • AdornerBehaviorSample.sln
      • AdornerBehaviorSample
        • .svn
          • all-wcprops
          • entries
          • prop-base
            • Application.ico.svn-base
            • Exclamation.png.svn-base
            • Mail-add.png.svn-base
            • Paper-pencil.png.svn-base
            • Pencil.png.svn-base
          • props
          • text-base
            • AdornerBehaviorSample.csproj.svn-base
            • app.config.svn-base
            • App.xaml.cs.svn-base
            • App.xaml.svn-base
            • Application.ico.svn-base
            • CaAdornerBehavior.cs.svn-base
            • Exclamation.png.svn-base
            • Mail-add.png.svn-base
            • MainWindow.xaml.cs.svn-base
            • MainWindow.xaml.svn-base
            • Paper-pencil.png.svn-base
            • Pencil.png.svn-base
            • SampleViewModel.cs.svn-base
            • Simple Styles.xaml.svn-base
          • tmp
            • prop-base
            • props
            • text-base
        • AdornerBehaviorSample.csproj
        • app.config
        • App.xaml
        • App.xaml.cs
        • Application.ico
        • CaAdornerBehavior.cs
        • Exclamation.png
        • Mail-add.png
        • MainWindow.xaml
        • MainWindow.xaml.cs
        • Paper-pencil.png
        • Pencil.png
        • Properties
          • .svn
            • all-wcprops
            • entries
            • prop-base
            • props
            • text-base
              • AssemblyInfo.cs.svn-base
            • tmp
              • prop-base
              • props
              • text-base
          • AssemblyInfo.cs
        • SampleViewModel.cs
        • Simple Styles.xaml
      • BlendSelection.png
      • CodeProject.docx
      • EventMapping.png
      • FinalScreen.png
  • AdornerBehaviorSample2.zip
    • all-wcprops
    • entries
    • AdornerBehaviorSample.suo.svn-base
    • EventMapping.png.svn-base
    • BlendSelection.png.svn-work
    • CodeProject.docx.svn-work
    • FinalScreen.png.svn-work
    • AdornerBehaviorSample.sln.svn-base
    • AdornerBehaviorSample.suo.svn-base
    • EventMapping.png.svn-base
    • ~$deProject.docx
    • AdornerBehaviorSample.sln
    • all-wcprops
    • entries
    • Application.ico.svn-base
    • Exclamation.png.svn-base
    • Mail-add.png.svn-base
    • Paper-pencil.png.svn-base
    • Pencil.png.svn-base
    • AdornerBehaviorSample.csproj.svn-base
    • app.config.svn-base
    • App.xaml.cs.svn-base
    • App.xaml.svn-base
    • Application.ico.svn-base
    • CaAdornerBehavior.cs.svn-base
    • Exclamation.png.svn-base
    • Mail-add.png.svn-base
    • MainWindow.xaml.cs.svn-base
    • MainWindow.xaml.svn-base
    • Paper-pencil.png.svn-base
    • Pencil.png.svn-base
    • SampleViewModel.cs.svn-base
    • Simple Styles.xaml.svn-base
    • AdornerBehaviorSample.csproj
    • app.config
    • App.xaml
    • App.xaml.cs
    • Application.ico
    • CaAdornerBehavior.cs
    • Exclamation.png
    • Mail-add.png
    • MainWindow.xaml
    • MainWindow.xaml.cs
    • Paper-pencil.png
    • Pencil.png
    • all-wcprops
    • entries
    • AssemblyInfo.cs.svn-base
    • AssemblyInfo.cs
    • SampleViewModel.cs
    • Simple Styles.xaml
    • BlendSelection.png
    • CodeProject.docx
    • EventMapping.png
    • FinalScreen.png
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Interactivity;
using System.Diagnostics;
using Microsoft.Expression.Interactivity.Core;

// -------------------------------------------------------------------
// CodeProject.com
// Wednesday, October 19, 2011
// Carlos Alvarez  (ctarmor-code@yahoo.com)
// -------------------------------------------------------------------


namespace CaControlTestApp
{
    /// <summary>
    /// Custom Adorner hosting a ContentControl with a ContentTemplate
    /// </summary>
    class CaTemplatedAdorner : Adorner
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="adornedElement"></param>
        public CaTemplatedAdorner(UIElement adornedElement, FrameworkElement frameworkElementAdorner)
            : base(adornedElement)
        {
            // Assure we get mouse hits
            _frameworkElementAdorner = frameworkElementAdorner;
            AddVisualChild(_frameworkElementAdorner);
            AddLogicalChild(_frameworkElementAdorner);
        }

        /// <summary>
        /// 
        /// </summary>
        protected override Visual GetVisualChild(int index)
        {
            return _frameworkElementAdorner;
        }

        /// <summary>
        /// 
        /// </summary>
        protected override int VisualChildrenCount
        {
            get
            {
                return 1; 
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="constraint"></param>
        /// <returns></returns>
        protected override Size MeasureOverride(Size constraint)
        {
            _frameworkElementAdorner.Width = constraint.Width;
            _frameworkElementAdorner.Height = constraint.Height;

            return constraint;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="finalSize"></param>
        /// <returns></returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            _frameworkElementAdorner.Arrange(new Rect(new Point(0, 0), finalSize));
            return finalSize;
        }


        /// <summary>
        /// 
        /// </summary>
        FrameworkElement _frameworkElementAdorner;
    }




    /// <summary>
    /// Behavior managing an adorner datatemplate
    /// </summary>
	public class CaAdornerBehavior : Behavior<DependencyObject>
	{
        /// <summary>
        /// Custom Adorner class
        /// </summary>
        CaTemplatedAdorner _caTemplatedAdorner = null;

        /// <summary>
        /// Adorner control holder. This object is passed to the Adorner
        /// </summary>
        ContentControl _adornerControl;

        /// <summary>
        /// Preset actions to handle delayed contruction/destruction
        /// </summary>
        Func<bool> _delayedFactory;
        Func<bool> _delayedDestruction;
        Func<bool> _nonDelayedFactory;
        Func<bool> _nonDelayedDestruction;

        /// <summary>
        /// Preset actions
        /// </summary>
        readonly Func<bool> _factor;
        readonly Func<bool> _dispose;
        readonly Func<bool> _emptyAction;

        /// <summary>
        /// 
        /// </summary>
		public CaAdornerBehavior()
		{
            //
            // create three static Actions to work with delayed, or not, construction
            //
            _emptyAction = () => false;

            //
            // Delay factory action
            //
            _factor = () =>
                {
                    if (AdornerTemplate != null)
                    {
                        AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(AssociatedObject as UIElement);

                        if (null == adornerLayer) throw new NullReferenceException(string.Format("No adorner found in attached object: {0}", AssociatedObject));

                        // Create adorner
                        _adornerControl = new ContentControl();

                        // Add to adorner
                        adornerLayer.Add(_caTemplatedAdorner = new CaTemplatedAdorner(AssociatedObject as UIElement, _adornerControl));

                        // set realted bindings
                        _adornerControl.Content = AdornerTemplate.LoadContent();
                        _adornerControl.Visibility = AdornerVisible;

                        // Bind internal dependency to external 
                        Binding bindingMargin = new Binding("AdornerMargin");
                        bindingMargin.Source = this;
                        BindingOperations.SetBinding(_caTemplatedAdorner, ContentControl.MarginProperty, bindingMargin);
                    }

                    return true;
                };

            //
            // proper dispose
            //
            _dispose = () =>
                {
                    if (null != _caTemplatedAdorner)
                    {
                        AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(AssociatedObject as UIElement);
                        adornerLayer.Remove(_caTemplatedAdorner);
                        BindingOperations.ClearBinding(_caTemplatedAdorner, ContentControl.MarginProperty);
                        _caTemplatedAdorner = null;
                        _adornerControl = null;
                    }
                    return true;
                };

            // set intial actions 
            SetDelayedState(DelayConstruction);

            // Behavior events
            ShowAdornerCommand = new ActionCommand(ShowAdorner);
            HideAdornerCommand = new ActionCommand(HideAdorner);
        }

        /// <summary>
        /// Standard behavior OnAttached() override
        /// </summary>
		protected override void OnAttached()
		{
            base.OnAttached();
            _nonDelayedFactory();
        }


        /// <summary>
        /// Standard behavior OnDetaching() override
        /// </summary>
        protected override void OnDetaching()
        {
            base.OnDetaching();
            _nonDelayedDestruction();
        }


        /// <summary>
        /// ShowAdorner
        /// </summary>
        private void ShowAdorner(object parameter)
        {
            _delayedFactory();

            // Set Data context here because default template assigment is  not setting the context
            var dtContext = (this.AssociatedObject as FrameworkElement).DataContext;
            if ( null == _adornerControl.DataContext )
                _adornerControl.DataContext = dtContext;

            _adornerControl.Visibility = Visibility.Visible;
        }


        /// <summary>
        /// HideAdorner
        /// </summary>
        private void HideAdorner(object parameter)
        {
            if (!_delayedDestruction())
            {
                if (_adornerControl.IsMouseOver)
                {
                    _adornerControl.MouseLeave -= (s, e) => _adornerControl.Visibility = AdornerVisible;
                    _adornerControl.MouseLeave += (s, e) => _adornerControl.Visibility = AdornerVisible;
                }
                else
                {
                    _adornerControl.Visibility = AdornerVisible;
                }
            }

        }


        /// <summary>
        /// ShowAdornerCommand
        /// </summary>
        public ICommand ShowAdornerCommand
        {
            get;
            private set;
        }


        /// <summary>
        /// HideAdornerCommand
        /// </summary>
        public ICommand HideAdornerCommand
        {
            get;
            private set;
        }


        /// <summary>
        /// Template to use in adorner. The template is hosted as a content control template with full 
        /// HitTest.
        /// </summary>
        public static readonly DependencyProperty AdornerTemplateProperty = DependencyProperty.Register(
            "AdornerTemplate",
            typeof(DataTemplate),
            typeof(CaAdornerBehavior), new PropertyMetadata(new PropertyChangedCallback((d, o) => 
            {
                if (null != ((CaAdornerBehavior)d)._adornerControl)
                    ((CaAdornerBehavior)d)._adornerControl.ContentTemplate = (DataTemplate)o.NewValue;
            }
          )));

        /// <summary>
        /// Data template for the adroner. Used inside a ContentControl. 
        /// </summary>
        public DataTemplate AdornerTemplate
        {
            get { return (DataTemplate)GetValue(AdornerTemplateProperty); }
            set { SetValue(AdornerTemplateProperty, value); }
        }




        /// <summary>
        /// Adorner Margin
        /// </summary>
        public static readonly DependencyProperty AdornerMarginProperty = DependencyProperty.Register(
            "AdornerMargin",
            typeof(Thickness),
            typeof(CaAdornerBehavior)
          );

        /// <summary>
        /// Adorner Margin
        /// </summary>
        public Thickness AdornerMargin
        {
            get { return (Thickness)GetValue(AdornerMarginProperty); }
            set { SetValue(AdornerMarginProperty, value); }
        }



        /// <summary>
        /// AdornerVisibleProperty
        /// </summary>
        public static readonly DependencyProperty AdornerVisibleProperty = DependencyProperty.Register(
            "AdornerVisible",
            typeof(Visibility),
            typeof(CaAdornerBehavior),
            new PropertyMetadata((object)Visibility.Hidden, new PropertyChangedCallback((d, o) => 
                { 
                    if (null != ((CaAdornerBehavior)d)._adornerControl)
                        ((CaAdornerBehavior)d)._adornerControl.Visibility = (Visibility)o.NewValue;
                }
          )));

        /// <summary>
        /// Data template for the adroner. Used inside a ContentControl. 
        /// </summary>
        public Visibility AdornerVisible
        {
            get { return (Visibility)GetValue(AdornerVisibleProperty); }
            set { SetValue(AdornerVisibleProperty, value); }
        }

        /// <summary>
        /// True = Construct and dispose adorner on demand. Slower user experience.
        /// False = (Default) Create aodrner when attached. Faster user experience.
        /// </summary>
        public static readonly DependencyProperty DelayConstructionProperty = DependencyProperty.Register(
            "DelayConstruction",
            typeof(bool),
            typeof(CaAdornerBehavior),
            new PropertyMetadata((object)false, new PropertyChangedCallback((d, o) => 
                {
                    ((CaAdornerBehavior)d).SetDelayedState((bool) o.NewValue);
                })
          ));

        /// <summary>
        /// Data template for the adroner. Used inside a ContentControl. 
        /// </summary>
        public bool DelayConstruction
        {
            get { return (bool)GetValue(DelayConstructionProperty); }
        }

        /// <summary>
        /// Data template for the adroner. Used inside a ContentControl. 
        /// </summary>
        /// <param name="delayed"></param>
        private void SetDelayedState(bool delayed)
        {
            _delayedFactory = delayed ? _factor : _emptyAction;
            _delayedDestruction = delayed ? _dispose : _emptyAction;
            _nonDelayedFactory = !delayed ? _factor : _emptyAction;
            _nonDelayedDestruction = !delayed ? _dispose : _emptyAction;
        }
    
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Windows Consultant
United States United States
20+ yrs Leading and Developing Microsoft products in the Financial Industry.

My main background is VC++, server and client development.

Currently focused in WPF/XAML, Windows 8 and Windows Azure Server technologies.

Comments and Discussions