DragDropListBox

WPF ListBox subclass with added drag/drop support including an Adorner.

Consumers should use the ItemDropOn and ItemDragOver events, which provide the item in the collection the data was dropped on, as well as its index in the collection. There’s also built-in support for reordering items in the ListBox itself by drag and drop. All you have to do is set AllowDragReorder=”True” in the XAML.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace HollowEarthIndustries
{
    public class DragDropListBox : ListBox
    {
        public DragDropListBox()
        {
            AllowDrop = true;
            DragEnter += DragDropListBox_DragEnter;
            DragOver += DragDropListBox_DragOver;
            DragLeave += DragDropListBox_DragLeave;
            Drop += DragDropListBox_Drop;
        }

        /// <summary>
        /// If ItemsSource is in use, you can't insert into Items.
        /// 
        /// If ItemsSource is in use but it's IEnumerable, as far as I know you can't insert into anything,
        /// but maybe some consumer is doing something clever, so we still allow drop in that case. 
        /// </summary>
        /// <returns></returns>
        public System.Collections.IList GetInsertableCollection()
        {
            if (ItemsSource == null)
                return Items;

            return ItemsSource as System.Collections.IList;
        }

        protected override DependencyObject GetContainerForItemOverride()
        {
            //  We configure the ListBoxItems here rather than with a Style because this is the 
            //  simplest way to allow consumers to use ItemContainerStyle without stepping on 
            //  our internals. 

            var itemContainer = new ListBoxItem
            {
                //  TODO: We need to update the ListBoxItems when the ListBox's AllowDrop changes
                //  TODO: What if consumer provides literal ListBoxItem elements in the XAML? Derp.
                AllowDrop = true,
            };

            //  Events for items as object of drag/drop
            itemContainer.DragEnter += ItemContainer_DragEnter;
            itemContainer.DragOver += ItemContainer_DragOver;
            itemContainer.DragLeave += ItemContainer_DragLeave;
            itemContainer.Drop += ItemContainer_Drop;

            //  Events for items as subject of drag/drop
            itemContainer.PreviewMouseLeftButtonDown += ItemContainer_PreviewMouseLeftButtonDown;
            itemContainer.PreviewMouseMove += ItemContainer_PreviewMouseMove;

            return itemContainer;
        }

        #region Events
        #region ItemDropOn event
        public event EventHandler<ItemDragDropEventArgs> ItemDropOn;

        private string ReorderFormat { get; } = typeof(ItemDragData).FullName;

        protected void OnItemDropOn(DragEventArgs e, object targetItem, int targetIndex)
        {
            if (IsDragReorderingSelf(e))
            {
                var data = (ItemDragData)e.Data.GetData(ReorderFormat);

                System.Collections.IList list = GetInsertableCollection();

                if (list != null)
                {
                    if (!e.KeyStates.HasFlag(DragDropKeyStates.ControlKey) || !AllowDragReorderCopy)
                    {
                        if (data.SourceItemIndex < targetIndex)
                        {
                            //  If we're removing an item that precedes the target item,
                            //  adjust targetIndex down to account for the target item's 
                            //  changed position. 
                            --targetIndex;
                        }
                        list.RemoveAt(data.SourceItemIndex);
                    }

                    list.Insert(targetIndex, data.DragItem);

                    return;
                }
            }
            ItemDropOn?.Invoke(this, new ItemDragDropEventArgs(e, targetItem, targetIndex));
        }
        #endregion ItemDropOn event

        #region ItemDragOver event
        public event EventHandler<ItemDragDropEventArgs> ItemDragOver;

        protected bool IsDragReorderingSelf(DragEventArgs e)
        {
            if (AllowDragReorder && e.Data.GetDataPresent(ReorderFormat))
            {
                var data = (ItemDragData)e.Data.GetData(ReorderFormat);

                if (data.Source == this)
                {
                    return true;
                }
            }
            return false;
        }

        protected void OnItemDragOver(DragEventArgs e, object targetItem, int targetIndex)
        {
            if (IsDragReorderingSelf(e))
            {
                if (e.KeyStates.HasFlag(DragDropKeyStates.ControlKey) && AllowDragReorderCopy)
                {
                    e.Effects = DragDropEffects.Copy;
                }
                else
                {
                    e.Effects = DragDropEffects.Move;
                }
            }

            ItemDragOver?.Invoke(this, new ItemDragDropEventArgs(e, targetItem, targetIndex));
        }
        #endregion ItemDropOn event
        #endregion Events

        #region Drag Over Helpers
        protected void SetDragAfterLastItem(bool isDragging)
        {
            if (HasItems)
            {
                UpdateAdorner(GetLastListBoxItem(), isDragging, false);
            }
        }

        protected void UpdateAdorner(ListBoxItem lbi, bool isDragging, bool insertingBefore)
        {
            var adornerLayer = AdornerLayer.GetAdornerLayer(lbi);
            adornerLayer.IsHitTestVisible = false;

            if (isDragging)
            {
                //  It's a nullable bool; adornerLayer.GetAdorners() is old fashioned and may return null. 
                if (adornerLayer.GetAdorners(lbi)?.OfType<DropTargetAdorner>().Any() != true)
                {
                    adornerLayer.Add(/*_adorner = */new DropTargetAdorner(lbi, insertingBefore, TargetMarkerBrush));
                }
            }
            else
            {
                adornerLayer.GetAdorners(lbi)?.OfType<DropTargetAdorner>().ToList().ForEach(a => adornerLayer.Remove(a));
            }
        }

        protected ListBoxItem GetLastListBoxItem()
        {
            if (HasItems)
            {
                return (ListBoxItem)ItemContainerGenerator.ContainerFromIndex(Items.Count - 1);
            }
            return null;
        }

        #endregion Drag Over Helpers

        #region ListBox Event Handlers
        private void DragDropListBox_DragEnter(object sender, DragEventArgs e)
        {
            SetDragAfterLastItem(true);

            OnItemDragOver(e, null, Items.Count);

            e.Handled = true;
        }

        private void DragDropListBox_DragOver(object sender, DragEventArgs e)
        {
            OnItemDragOver(e, null, Items.Count);

            e.Handled = true;
        }

        private void DragDropListBox_DragLeave(object sender, DragEventArgs e)
        {
            SetDragAfterLastItem(false);

            e.Handled = true;
        }

        private void DragDropListBox_Drop(object sender, DragEventArgs e)
        {
            SetDragAfterLastItem(false);

            if (e.Effects != DragDropEffects.None)
            {
                OnItemDropOn(e, null, Items.Count);
            }

            e.Handled = true;
        }
        #endregion ListBox Event Handlers

        #region ListBoxItem event handlers

        #region Drag source event handlers
        Point? _mouseDownPoint = null;
        private void ItemContainer_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (AllowItemDrag)
            {
                _mouseDownPoint = Mouse.GetPosition((Control)sender);
            }
        }

        private void ItemContainer_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            //System.Diagnostics.Trace.WriteLine($"ItemContainer_PreviewMouseMove e.LeftButton {e.LeftButton}");

            if (AllowItemDrag && e.LeftButton == MouseButtonState.Pressed && _mouseDownPoint.HasValue)
            {
                var pos = Mouse.GetPosition((Control)sender);

                //System.Diagnostics.Trace.WriteLine($"_mouseDownPoint {_mouseDownPoint}  pos {pos}");

                if (Distance(pos, _mouseDownPoint.Value) > 4)
                {
                    var lbi = (ListBoxItem)sender;

                    var dragData = new ItemDragData
                    {
                        DragItem = lbi.DataContext ?? lbi,
                        SourceItemIndex = ItemContainerGenerator.IndexFromContainer(lbi),
                        Source = this
                    };

                    var effect = DragDrop.DoDragDrop(this, dragData, DragDropEffects.Move | DragDropEffects.Copy);

                    _mouseDownPoint = null;

                    e.Handled = true;
                }
            }
            else
            {
                _mouseDownPoint = null;
            }
        }

        protected static double Distance(Point a, Point b)
        {
            var xdist = Math.Abs(a.X - b.X);
            var ydist = Math.Abs(a.Y - b.Y);

            return Math.Sqrt((xdist * xdist) + (ydist * ydist));
        }

        #endregion Drag source event handlers

        #region Drag target event handlers
        private void ItemContainer_DragEnter(object sender, DragEventArgs e)
        {
            var lbi = (ListBoxItem)sender;

            UpdateAdorner(lbi, true, true);

            int index = ItemContainerGenerator.IndexFromContainer(lbi);

            OnItemDragOver(e, lbi.DataContext ?? lbi, index);

            e.Handled = true;
        }

        private void ItemContainer_DragOver(object sender, DragEventArgs e)
        {
            var lbi = (ListBoxItem)sender;

            int index = ItemContainerGenerator.IndexFromContainer(lbi);

            OnItemDragOver(e, lbi.DataContext ?? lbi, index);

            e.Handled = true;
        }

        private void ItemContainer_DragLeave(object sender, DragEventArgs e)
        {
            UpdateAdorner((ListBoxItem)sender, false, true);

            e.Handled = true;
        }

        private void ItemContainer_Drop(object sender, DragEventArgs e)
        {
            System.Diagnostics.Trace.WriteLine($"ItemContainer_Drop e.Effects {e.Effects}");

            var lbi = (ListBoxItem)sender;

            UpdateAdorner(lbi, false, true);

            if (e.Effects != DragDropEffects.None)
            {
                int index = ItemContainerGenerator.IndexFromContainer(lbi);
                OnItemDropOn(e, lbi.DataContext ?? lbi, index);
            }

            e.Handled = true;
        }
        #endregion Drag target event handlers
        #endregion ListBoxItem event handlers

        #region Dependency Properties
        #region TargetMarkerBrush Property
        public Brush TargetMarkerBrush
        {
            get { return (Brush)GetValue(TargetMarkerBrushProperty); }
            set { SetValue(TargetMarkerBrushProperty, value); }
        }

        public static readonly DependencyProperty TargetMarkerBrushProperty =
            DependencyProperty.Register(nameof(TargetMarkerBrush), typeof(Brush), typeof(DragDropListBox),
                new PropertyMetadata(Brushes.Black));
        #endregion TargetMarkerBrush Property

        #region AllowItemDrag Property
        public bool AllowItemDrag
        {
            get { return (bool)GetValue(AllowItemDragProperty); }
            set { SetValue(AllowItemDragProperty, value); }
        }

        public static readonly DependencyProperty AllowItemDragProperty =
            DependencyProperty.Register(nameof(AllowItemDrag), typeof(bool), typeof(DragDropListBox),
                new PropertyMetadata(false));
        #endregion AllowItemDrag Property

        #region AllowDragReorderCopy Property
        /// <summary>
        /// If true, drag-reorder operation will copy when control key is pressed
        /// </summary>
        public bool AllowDragReorderCopy
        {
            get { return (bool)GetValue(AllowDragReorderCopyProperty); }
            set { SetValue(AllowDragReorderCopyProperty, value); }
        }

        public static readonly DependencyProperty AllowDragReorderCopyProperty =
            DependencyProperty.Register(nameof(AllowDragReorderCopy), typeof(bool), typeof(DragDropListBox),
                new PropertyMetadata(false));
        #endregion AllowDragReorderCopy Property


        #region AllowDragReorder Property
        public bool AllowDragReorder
        {
            get { return (bool)GetValue(AllowDragReorderProperty); }
            set { SetValue(AllowDragReorderProperty, value); }
        }

        public static readonly DependencyProperty AllowDragReorderProperty =
            DependencyProperty.Register(nameof(AllowDragReorder), typeof(bool), typeof(DragDropListBox),
                new FrameworkPropertyMetadata(false,
                                              FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                                              AllowDragReorder_PropertyChanged)
                { DefaultUpdateSourceTrigger = System.Windows.Data.UpdateSourceTrigger.PropertyChanged });

        protected static void AllowDragReorder_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as DragDropListBox).OnAllowDragReorderChanged(e.OldValue);
        }

        private void OnAllowDragReorderChanged(object oldValue)
        {
            if (AllowDragReorder)
            {
                AllowItemDrag = true;
            }
        }
        #endregion AllowDragReorder Property


        #endregion Dependency Properties

        #region ItemDragData class
        public class ItemDragData
        {
            public object DragItem { get; set; }
            public int SourceItemIndex { get; set; }

            public DragDropListBox Source { get; set; }
        }
        #endregion ItemDragData class
    }

    #region ItemDragDropEventArgs class
    public class ItemDragDropEventArgs : EventArgs
    {
        public ItemDragDropEventArgs(DragEventArgs e, object targetItem, int targetIndex)
        {
            _e = e;
            TargetItem = targetItem;
            TargetIndex = targetIndex;
        }

        private DragEventArgs _e;

        public int TargetIndex { get; private set; }

        public IDataObject Data => _e.Data;

        public DragDropEffects Effects
        {
            get => _e.Effects;
            set => _e.Effects = value;
        }
        public DragDropKeyStates KeyStates => _e.KeyStates;
        public DragDropEffects AllowedEffects => _e.AllowedEffects;

        /// <summary>
        /// Either ListBoxItem or DataContext of ListBoxItem
        /// If null, user is dragging over the empty space after the last item in the listbox
        /// </summary>
        public Object TargetItem { get; private set; }
    }
    #endregion ItemDragDropEventArgs class

    #region DropTargetAdorner class
    public class DropTargetAdorner : Adorner
    {
        /*
         * AdornerContentPresenter with a DataTemplate would be preferable:
         * 
         * https://stackoverflow.com/a/10034274/424129
         * 
         * */
        public DropTargetAdorner(UIElement adornedElement, bool insertingBefore, Brush targetHighlightBrush = null)
            : base(adornedElement)
        {
            IsHitTestVisible = false;
            _insertingBefore = insertingBefore;
            _targetHighlightBrush = targetHighlightBrush ?? Brushes.Black;

            Border border = new Border();
        }

        protected double _targetBarHeight = 2;
        protected bool _insertingBefore;
        protected Brush _targetHighlightBrush;

        protected override void OnRender(DrawingContext drawingContext)
        {
            var lbi = AdornedElement as ListBoxItem;

            Rect adornedElementRect = new Rect(0, 0, lbi.ActualWidth, lbi.ActualHeight);
            Size targetBarSize = new Size(adornedElementRect.Width, _targetBarHeight);
            Rect targetBarRect = new Rect(adornedElementRect.Location, targetBarSize);

            if (!_insertingBefore)
            {
                targetBarRect.Location = new Point(targetBarRect.Left, adornedElementRect.BottomLeft.Y - _targetBarHeight);
            }

            drawingContext.DrawRectangle(_targetHighlightBrush, null, targetBarRect);
        }
    }
    #endregion DropTargetAdorner class
}

Improved PanelBehaviors

Improved PanelBehaviors. This adds a ChildIndex property.

using System.Windows;
using System.Windows.Controls;

namespace HollowEarth.Behaviors
{
    public static class PanelBehaviors
    {
        public static void UpdateChildFirstLastProperties(Panel panel)
        {
            for (int i = 0; i < panel.Children.Count; ++i)
            {
                var child = panel.Children[i];
                SetIsFirstChild(child, i == 0);
                SetIsLastChild(child, i == panel.Children.Count - 1);
                SetChildIndex(child, i);
            }
        }

        #region PanelBehaviors.IsChildPositionIndicated Attached Property
        public static bool GetIsChildPositionIndicated(Panel panel)
        {
            return (bool)panel.GetValue(IsChildPositionIndicatedProperty);
        }
        public static void SetIsChildPositionIndicated(Panel panel, bool value)
        {
            panel.SetValue(IsChildPositionIndicatedProperty, value);
        }
    
        /// <summary>
        /// Behavior which causes the Panel to identify its first and last children with attached properties. 
        /// </summary>
        public static readonly DependencyProperty IsChildPositionIndicatedProperty =
            DependencyProperty.RegisterAttached("IsChildPositionIndicated", typeof(bool), typeof(PanelBehaviors),
                new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsArrange, IsChildPositionIndicated_PropertyChanged));
        private static void IsChildPositionIndicated_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Panel panel = (Panel)d;
            ((Panel)d).LayoutUpdated += (s, e2) => UpdateChildFirstLastProperties(panel);
        }
        #endregion PanelBehaviors.IsChildPositionIndicated Attached Property
    
        #region PanelBehaviors.IsFirstChild Attached Property
        public static bool GetIsFirstChild(UIElement obj)
        {
            return (bool)obj.GetValue(IsFirstChildProperty);
        }
        public static void SetIsFirstChild(UIElement obj, bool value)
        {
            obj.SetValue(IsFirstChildProperty, value);
        }
    
        /// <summary>
        /// True if UIElement is first child of a Panel
        /// </summary>
        public static readonly DependencyProperty IsFirstChildProperty =
            DependencyProperty.RegisterAttached("IsFirstChild", typeof(bool), typeof(PanelBehaviors),
                new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsParentArrange));
        #endregion PanelBehaviors.IsFirstChild Attached Property

        #region PanelBehaviors.IsLastChild Attached Property
        public static bool GetIsLastChild(UIElement obj)
        {
            return (bool)obj.GetValue(IsLastChildProperty);
        }
        public static void SetIsLastChild(UIElement obj, bool value)
        {
            obj.SetValue(IsLastChildProperty, value);
        }
    
        /// <summary>
        /// True if UIElement is last child of a Panel
        /// </summary>
        public static readonly DependencyProperty IsLastChildProperty =
            DependencyProperty.RegisterAttached("IsLastChild", typeof(bool), typeof(PanelBehaviors),
                new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsParentArrange));
        #endregion PanelBehaviors.IsLastChild Attached Property

        #region PanelBehaviors.ChildIndex Attached Property
        public static int GetChildIndex(UIElement obj)
        {
            return (int)obj.GetValue(ChildIndexProperty);
        }

        public static void SetChildIndex(UIElement obj, int value)
        {
            obj.SetValue(ChildIndexProperty, value);
        }

        public static readonly DependencyProperty ChildIndexProperty =
            DependencyProperty.RegisterAttached("ChildIndex", typeof(int), typeof(PanelBehaviors),
                new FrameworkPropertyMetadata(-1, 
                    FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsParentArrange));
        #endregion PanelBehaviors.ChildIndex Attached Property
    }
}

Dynamic Visitor

The dynamic keyword in C# has a fun property: When you pass a dynamic to a method, method overload resolution is performed at runtime using the runtime binder. I learned that here.

The runtime binder is limited: It ignores inherited methods.

But you can write a quick and dirty visitor pattern without the visited objects needing to support it with a Visit method. I’m sure it’s not the fastest thing at runtime, but one of these days I’ll find a use for it.

Full code below, and here’s a fiddle.

using System;
using System.Collections;

namespace DynamicOverloadResolution
{
    public class DynamicVisitor
    {
        public void Visit(IEnumerable value)
        {
            Console.WriteLine($"IEnumerable {value}");
            foreach (dynamic d in value)
                Visit(d);
        }

        public void Visit(IList value)
        {
            Console.WriteLine($"IList       {value.Count}");
            foreach (dynamic d in value)
                Visit(d);
        }

        public void Visit(Object value) => Console.WriteLine($"Object      {value}");
        public void Visit(Int32 value) => Console.WriteLine($"Int32       {value}");
        public void Visit(DateTime value) => Console.WriteLine($"DateTime    {value}");
        public void Visit(String value) => Console.WriteLine($"String      {value}");
        public void Visit(Double value) => Console.WriteLine($"Double      {value}");
    }

    public class DynamicVisitorBase
    {
        public void Visit(IEnumerable value)
        {
            Console.WriteLine($"IEnumerable {value}");
            foreach (dynamic d in value)
                Visit(d);
        }

        public void Visit(IList value)
        {
            Console.WriteLine($"IList       {value.Count}");
            foreach (dynamic d in value)
                Visit(d);
        }
    }

    public class DynamicVisitorSubclass : DynamicVisitorBase
    {
        public void Visit(Object value) => Console.WriteLine($"Object      {value}");
        public void Visit(Int32 value) =>  Console.WriteLine($"Int32       {value}");
        public void Visit(DateTime value) => Console.WriteLine($"DateTime    {value}");
        public void Visit(String value) => Console.WriteLine($"String      {value}");
        public void Visit(Double value) => Console.WriteLine($"Double      {value}");
    }

    class Program
    {
        static void Main()
        {
            var objects = new ArrayList()
            {
                "Foo", 1L, 1.1, 2.2M,
                DateTime.Now,
                new Nullable<DateTime>(DateTime.Now.AddDays(1)),
                new List<object>()
                {
                    new List<String> { "list1", "list2" },
                    Enumerable.Range(0,2)
                },
                new Dictionary<String, String> {
                    ["key string"] = "value string"
                }
            };

            Console.WriteLine("DynamicVisitor:");
            new DynamicVisitor().Visit(objects);

            Console.WriteLine("\n\nDynamicVisitorSubclass -- inheritance doesn't work");
            new DynamicVisitorSubclass().Visit(objects);
        }
    }
}

Program output:

DynamicVisitor:
IList       8
String      Foo
Double      1
Double      1.1
Object      2.2
DateTime    10/28/2016 12:07:01 PM
DateTime    10/29/2016 12:07:01 PM
IList       2
IList       2
String      list1
String      list2
IEnumerable System.Linq.Enumerable+<RangeIterator>d__110
Int32       0
Int32       1
IEnumerable System.Collections.Generic.Dictionary`2[System.String,System.String]
Object      [key string, value string]


DynamicVisitorSubclass -- inheritance doesn't work
Object      System.Collections.ArrayList

Aphorism

Using UI controls as a data structure is one of those square wheels that every programmer instinctively reinvents.

It’s so universal that I suspect it may be hard-wired in the human brain, like Chomsky’s universal grammar (which if I understand correctly still hasn’t been disproven, largely because the theory’s been refined to the point where it basically amounts to “most people say stuff”).

Styling first and last items of a StackPanel or Grid in WPF

Here’s a good StackOverflow question: Xamarin.Forms: Equivalent to CSS :last-of-type selector.

He wanted a Style Trigger that applies on the first or last item of a StackPanel. You can’t do that out of the box, but you can write a behavior in five or ten minutes that decorates the control’s children with their relative positions via an attached property. Panel is the base class for StackPanel, Grid, and UniformGrid, so Panel is our target for the behavior.

This is WPF, not Xamarin, but I wrote it in the hope that the guy asking the question would be able to convert it to Xamarin without any real trouble. That turned out to be true, and he found that it worked as intended. His Xamarin version is at the link.

using System;
using System.Windows;
using System.Windows.Controls;
namespace HollowEarth.AttachedProperties
{
    public static class PanelBehaviors
    {
        public static void UpdateChildFirstLastProperties(Panel panel)
        {
            for (int i = 0; i < panel.Children.Count; ++i)
            {
                var child = panel.Children[i];
                SetIsFirstChild(child, i == 0);
                SetIsLastChild(child, i == panel.Children.Count - 1);
            }
        }
        #region PanelExtensions.IdentifyFirstAndLastChild Attached Property
        public static bool GetIdentifyFirstAndLastChild(Panel panel)
        {
            return (bool)panel.GetValue(IdentifyFirstAndLastChildProperty);
        }
        public static void SetIdentifyFirstAndLastChild(Panel panel, bool value)
        {
            panel.SetValue(IdentifyFirstAndLastChildProperty, value);
        }
        /// <summary>
        /// Behavior which causes the Panel to identify its first and last children with attached properties. 
        /// </summary>
        public static readonly DependencyProperty IdentifyFirstAndLastChildProperty =
            DependencyProperty.RegisterAttached("IdentifyFirstAndLastChild", typeof(bool), typeof(PanelBehaviors),
                new PropertyMetadata(false, IdentifyFirstAndLastChild_PropertyChanged));
        private static void IdentifyFirstAndLastChild_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Panel panel = (Panel)d;
            ((Panel)d).LayoutUpdated += (s, e2) => UpdateChildFirstLastProperties(panel);
        }
        #endregion PanelExtensions.IdentifyFirstAndLastChild Attached Property
        #region PanelExtensions.IsFirstChild Attached Property
        public static bool GetIsFirstChild(UIElement obj)
        {
            return (bool)obj.GetValue(IsFirstChildProperty);
        }
        public static void SetIsFirstChild(UIElement obj, bool value)
        {
            obj.SetValue(IsFirstChildProperty, value);
        }
        /// <summary>
        /// True if UIElement is first child of a Panel
        /// </summary>
        public static readonly DependencyProperty IsFirstChildProperty =
            DependencyProperty.RegisterAttached("IsFirstChild", typeof(bool), typeof(PanelBehaviors),
                new PropertyMetadata(false));
        #endregion PanelExtensions.IsFirstChild Attached Property
        #region PanelExtensions.IsLastChild Attached Property
        public static bool GetIsLastChild(UIElement obj)
        {
            return (bool)obj.GetValue(IsLastChildProperty);
        }
        public static void SetIsLastChild(UIElement obj, bool value)
        {
            obj.SetValue(IsLastChildProperty, value);
        }
        /// <summary>
        /// True if UIElement is last child of a Panel
        /// </summary>
        public static readonly DependencyProperty IsLastChildProperty =
            DependencyProperty.RegisterAttached("IsLastChild", typeof(bool), typeof(PanelBehaviors),
                new PropertyMetadata(false));
        #endregion PanelExtensions.IsLastChild Attached Property
    }
}

And here’s a usage example in XAML:

<StackPanel 
    xmlns:heap="clr-namespace:HollowEarth.AttachedProperties"
    heap:PanelBehaviors.IdentifyFirstAndLastChild="True"
    HorizontalAlignment="Left"
    Orientation="Vertical"
    >
    <StackPanel.Resources>
        <Style TargetType="Label">
            <Setter Property="Content" Value="Blah blah" />
            <Setter Property="Background" Value="SlateGray" />
            <Setter Property="Margin" Value="4" />
            <Style.Triggers>
                <Trigger Property="heap:PanelBehaviors.IsFirstChild" Value="True">
                    <Setter Property="Background" Value="DeepSkyBlue" />
                    <Setter Property="Content" Value="First Child" />
                </Trigger>
                <Trigger Property="heap:PanelBehaviors.IsLastChild" Value="True">
                    <Setter Property="Background" Value="SeaGreen" />
                    <Setter Property="Content" Value="Last Child" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </StackPanel.Resources>
    <Label />
    <Label />
    <Label />
    <Label />
    </StackPanel>

Here’s what makes XAML cool: You can apply this to an ItemsControl, or any ItemsControl descendant such as a ListBox or TreeView/TreeViewItem, by applying it to the ItemsPanel:

<ListBox.ItemsPanel>
    <ItemsPanelTemplate>
        <StackPanel heap:PanelBehaviors.IdentifyFirstAndLastChild="True"
                    Orientation="Vertical" />
    </ItemsPanelTemplate>
</ListBox.ItemsPanel>

WPF TreeView ContainerFromItem returns null

Somebody asked me about this on StackOverflow, and I gave him the wrong answer at first. He told me, bro, listen, this thing returns null, seriously. I tried it and he was right.

Turns out it’s not quite exactly supported, in the way a naive person might expect: A TreeView in WPF is a gaggle of nested TreeViewItems (derived from HeaderedItemsControl), within a TreeView (derived from ItemsControl).

Each has its own ItemContainerGenerator.

A child item can have a different child type than its parent does, and there’s obviously no entirely transparent way to get a child item’s parent even if it’s consistent throughout the item tree. It just gets weird. They just didn’t bother.

So what you need is recursion (or a Rube Goldberg horror where you maintain the stack by hand — but That’s Not Who We Are). And I’ve got what you need.

namespace HollowEarth.ExtensionMethods
{
    public static class ItemContainerGeneratorExtensionMethods
    {
        /// <summary>
        /// We extend ItemContainerGenerator rather than TreeView itself because there 
        /// are other hierarchical ItemsControls, like MenuItem. 
        /// </summary>
        /// <typeparam name="TItem">The type of the item displayed in the treeview 
        /// (or whatever). If your items are of multiple types, use a common base class 
        /// and/or write a Byzantine itemParentSelector</typeparam>
        /// <param name="rootContainerGenerator"></param>
        /// <param name="item">Item to find the container for</param>
        /// <param name="itemParentSelector">Lambda to return the parent property 
        /// value from a TItem</param>
        /// <returns></returns>
        public static ItemsControl ContainerFromItem<TItem>(
            this ItemContainerGenerator rootContainerGenerator,
            TItem item,
            Func<TItem, TItem> itemParentSelector)
        {
            //  Caller can pass in treeView.SelectedItem as TItem in cases where 
            //  treeView.SelectedItem is null. Seems to me correct behavior there is 
            //  "No container". 
            if (item == null)
            {
                return null;
            }

            if (itemParentSelector == null)
            {
                throw new ArgumentNullException(nameof(itemParentSelector));
            }

            var parentItem = itemParentSelector(item);

            //  When we run out of parents, we're a root level node so we query the 
            //  rootContainerGenerator itself for the top level child container, and 
            //  start unwinding back to the item the caller gave us. 
            if (parentItem == null)
            {
                //  Our item is the parent of our caller's item. 
                //  This is the parent of our caller's container. 
                return rootContainerGenerator.ContainerFromItem(item) as ItemsControl;
            }
            else
            {
                //  This gets parents by unwinding the stack back down from root
                var parentContainer =
                    ContainerFromItem<TItem>(rootContainerGenerator,
                                             parentItem, itemParentSelector);

                return parentContainer.ItemContainerGenerator.ContainerFromItem(item)
                    as ItemsControl;
            }
        }
    }
}

Here’s what it looks like in use. This is the code the guy from StackOverflow was trying to get to work. I don’t know why you’d want to prevent people from selecting TreeViewItems, but that’s his problem. There are plenty of other reasons to want the selected TreeViewItem from a TreeView.

using HollowEarth.ExtensionMethods;

// ... snip ...

        private void TreeView_SelectedItemChanged_1(object sender, 
            RoutedPropertyChangedEventArgs<object> e)
        {
            //  Another way to do this would be to bind TreeViewItem.IsSelected to 
            //  a viewmodel IsSelected property in ItemContainerStyle, and have the 
            //  viewmodel property do the BeginInvoke() call. That's simpler, but 
            //  it's not appropriate for the viewmodel to be responsible for 
            //  worrying about how or if selection state is displayed in the UI. 

            var treeView = sender as TreeView;
            var selectedNode = treeView.SelectedItem as Node;

            TreeViewItem tvi = treeView.ItemContainerGenerator
                .ContainerFromItem(selectedNode, n => n.Parent) as TreeViewItem;

            Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() =>
            {
                if (tvi == null)
                    return;
                tvi.IsSelected = false;
                tvi.Focus();
            }));

        }

By the way, WordPress’s handling of pre-formatted text with < and > is hilariously broken if you go from “HTML” mode in the editor back to “Visual” mode. It converts the character entities to angle brackets, and then makes an insane attempt to rewrite your C# code as HTML.

ViewModel Property Snippets, C#6

cmdprop and cmdpropparam assume that you have a DelegateCommand class. cmdprop assumes that you DelegateCommand has a constructor that takes Action and Func<bool> parameters, and cmdpropparam assumes that it has a constructor that takes Action<T> and Func<T, bool>. The other assumption is that your viewmodel classes raise PropertyChanged via a method named OnPropertyChanged, which uses CallerMemberNameAttribute. All of this can be altered easily.

All the snippets use the $end$ element to leave the insertion point in front of the private backing field name, so that I can type Ctrl+R,Ctrl+R and rename it from _Identifier to _identifier.

These aren’t too hard to install; Google knows how.

 <?xml version="1.0" encoding="utf-8"?>
 <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
   <CodeSnippet Format="1.0.0">
     <Header>
       <Title>inotprop</Title>
       <Author>McNamara</Author>
       <Description>INotifyPropertyChanged Property</Description>
       <HelpUrl></HelpUrl>
       <Shortcut>inotprop</Shortcut>
     </Header>
     <Snippet>
       <Declarations>
         <Literal Editable="true">
           <ID>PropName</ID>
           <Type />
           <ToolTip />
           <Default>PropertyName</Default>
           <Function />
         </Literal>
         <Literal Editable="true">
           <ID>PropType</ID>
           <Type />
           <ToolTip />
           <Default>String</Default>
           <Function />
         </Literal>
       </Declarations>
       <Code Language="csharp" Kind="method decl" Delimiter="$">
         <![CDATA[
         #region $PropName$ Property
         private $PropType$ $end$_$PropName$ = default($PropType$);
         public $PropType$ $PropName$
         {
             get { return _$PropName$; }
             set
             {
                 if (value != _$PropName$)
                 {
                     _$PropName$ = value;
                     OnPropertyChanged();
                 }
             }
         }
         #endregion $PropName$ Property
 ]]></Code>
     </Snippet>
   </CodeSnippet>
   <CodeSnippet Format="1.0.0">
     <Header>
       <Title>cmdprop</Title>
       <Author>McNamara</Author>
       <Description>DelegateCommand Property</Description>
       <HelpUrl></HelpUrl>
       <Shortcut>cmdprop</Shortcut>
     </Header>
     <Snippet>
       <Declarations>
         <Literal Editable="true">
           <ID>CommandName</ID>
           <Type />
           <ToolTip />
           <Default>NewCommand</Default>
           <Function />
         </Literal>
       </Declarations>
       <Code Language="csharp" Kind="method decl" Delimiter="$"><![CDATA[
         #region $CommandName$Command Property
         private ICommand $end$_$CommandName$Command = null;
         public ICommand $CommandName$Command
         {
             get {
                 if (null == _$CommandName$Command)
                 {
                     _$CommandName$Command = new DelegateCommand($CommandName$, () => Can$CommandName$);
                 }
                 return _$CommandName$Command;
             }
         }
 
         public bool Can$CommandName$ {
             get { return true; }
         }
 
         protected void $CommandName$()
         {
         }
         #endregion $CommandName$Command Property
 ]]>
       </Code>
     </Snippet>
   </CodeSnippet>
 
   <CodeSnippet Format="1.0.0">
     <Header>
       <Title>cmdpropparam</Title>
       <Author>McNamara</Author>
       <Description>DelegateCommand Property</Description>
       <HelpUrl></HelpUrl>
       <Shortcut>cmdpropparam</Shortcut>
     </Header>
     <Snippet>
       <Declarations>
         <Literal Editable="true">
           <ID>CommandName</ID>
           <Type />
           <ToolTip />
           <Default>NewCommand</Default>
           <Function />
         </Literal>
         <Literal Editable="true">
           <ID>ParamType</ID>
           <Type />
           <ToolTip />
           <Default>String</Default>
           <Function />
         </Literal>
       </Declarations>
       <Code Language="csharp" Kind="method decl" Delimiter="$"><![CDATA[
         #region $CommandName$Command Property
         private ICommand $end$_$CommandName$Command = null;
         public ICommand $CommandName$Command
         {
             get {
                 if (null == _$CommandName$Command)
                 {
                     _$CommandName$Command = new DelegateCommand<$ParamType$>($CommandName$, Can$CommandName$);
                 }
                 return _$CommandName$Command;
             }
         }
 
         public bool Can$CommandName$($ParamType$ param) {
             return true;
         }
 
         protected void $CommandName$($ParamType$ param)
         {
         }
         #endregion $CommandName$Command Property
 ]]></Code>
     </Snippet>
   </CodeSnippet>
 </CodeSnippets>