<|start_filename|>Library/HeaderAdorner.cs<|end_filename|>
﻿// Copyright (c) '<NAME>'. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;

namespace WpfGhost.Controls
{
    public class HeaderAdorner : Adorner
    {
        private readonly ContentControl _contentControl;

        public HeaderAdorner(UIElement adornedElement) : base(adornedElement)
        {
            _contentControl = new ContentControl();

            // define a two way binding to the template
            var headerTemplateBinding = new Binding
            {
                Source = this,
                Path = new PropertyPath(HeaderTemplateProperty),
                Mode = BindingMode.TwoWay
            };
            _contentControl.SetBinding(ContentControl.ContentTemplateProperty, headerTemplateBinding);

            // content binding
            var contentBinding = new Binding
            {
                Source = this,
                Path = new PropertyPath(DataContextProperty),
                Mode = BindingMode.TwoWay
            };
            _contentControl.SetBinding(ContentControl.ContentProperty, contentBinding);

            this.AddVisualChild(_contentControl);
            this.AddLogicalChild(_contentControl);
        }

        protected override int VisualChildrenCount
        {
            get { return 1; }
        }

        protected override Visual GetVisualChild(int index)
        {
            if (index != 0)
            {
                throw new ArgumentOutOfRangeException("index");
            }
            return _contentControl;
        }

        protected override IEnumerator LogicalChildren
        {
            get
            {
                var list = new ArrayList {_contentControl};
                return list.GetEnumerator();
            }
        }


        #region HeaderTemplate

        /// <summary>
        /// Identifies the <see cref="HeaderTemplate"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register(
            "HeaderTemplate",
            typeof (DataTemplate),
            typeof (HeaderAdorner),
            new FrameworkPropertyMetadata(null));

        /// <summary>
        /// Gets or sets the HeaderTemplate property. This is a dependency property.
        /// </summary>
        /// <value>
        ///
        /// </value>
        public DataTemplate HeaderTemplate
        {
            get { return (DataTemplate) GetValue(HeaderTemplateProperty); }
            set { SetValue(HeaderTemplateProperty, value); }
        }

        #endregion

        protected override Size MeasureOverride(Size constraint)
        {
            _contentControl.Measure(constraint);

            // get the AdornedElement size
            return this.AdornedElement.RenderSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            double top = this.Top;
            if ((top + _contentControl.DesiredSize.Height) > finalSize.Height)
            {
                top = finalSize.Height - _contentControl.DesiredSize.Height;
            }

            var location = new Point(0, top);
            var size = new Size(finalSize.Width, _contentControl.DesiredSize.Height);
            _contentControl.Arrange(new Rect(location, size));

            return finalSize;
        }

        public double Top { get; set; }

        public void UpdateLocation(double top)
        {
            this.Top = Math.Abs(top);
            this.InvalidateArrange();
        }
    }
}


<|start_filename|>Demo/ViewModels/MainViewModel.cs<|end_filename|>
﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Demo.Model;

namespace Demo.ViewModels
{
    public class MainViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<Commit> _commits;

        public ObservableCollection<Commit> Commits
        {
            get { return _commits; }
            set
            {
                if (Equals(value, _commits)) return;
                _commits = value;
                OnPropertyChanged("Commits");
            }
        }

        public MainViewModel()
        {
            this.Commits = new ObservableCollection<Commit>();

            for (int i = 0; i < 10; i++)
            {
                this.Commits.Add(new Commit
                {
                    User = "<NAME>",
                    Date = new DateTime(2013, 7, 14),
                    Email = "<EMAIL>",
                    FilesCount = 10,
                    Hash = "b5e7a1",
                    Message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam quis interdum arcu."
                });

                this.Commits.Add(new Commit
                {
                    User = "<NAME>",
                    Date = new DateTime(2013, 7, 9),
                    Email = "<EMAIL>",
                    FilesCount = 20,
                    Hash = "0c47a0",
                    Message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam quis interdum arcu."
                });
            }

        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

<|start_filename|>Library/HeaderPersister.cs<|end_filename|>
﻿// Copyright (c) '<NAME>'. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using WpfGhost.Controls.Helpers;

namespace WpfGhost.Controls
{
    public static class HeaderPersister
    {
        #region "Fields"

#if NET40
        // maps an itemsControl to the currently highlighted group item
        private static readonly Dictionary<GroupItem, WeakReference> CurrentGroupItem;
#else
        // maps an itemsControl to the currently highlighted group item
        private static readonly Dictionary<GroupItem, WeakReference<HeaderAdorner>> CurrentGroupItem;
#endif

        #endregion

        #region "Constructors"

        /// <summary>
        /// Initializes static members of the <see cref="HeaderPersister"/> class.
        /// </summary>
        static HeaderPersister()
        {
#if NET40
            CurrentGroupItem = new Dictionary<GroupItem, WeakReference>();
#else
            CurrentGroupItem = new Dictionary<GroupItem, WeakReference<HeaderAdorner>>();
#endif
        }

        #endregion

        #region "Properties"

        #region HeaderTempate

        /// <summary>
        /// Identifies the <see cref="HeaderTempate"/> Attached Dependency Property.
        /// </summary>
        public static readonly DependencyProperty HeaderTemplateProperty =
            DependencyProperty.RegisterAttached("HeaderTemplate", typeof (DataTemplate), typeof (HeaderPersister),
                new FrameworkPropertyMetadata((DataTemplate) null));

        /// <summary>
        /// </summary>
        public static DataTemplate GetHeaderTemplate(DependencyObject d)
        {
            return (DataTemplate) d.GetValue(HeaderTemplateProperty);
        }

        /// <summary>
        /// </summary>
        public static void SetHeaderTemplate(DependencyObject d, DataTemplate value)
        {
            d.SetValue(HeaderTemplateProperty, value);
        }

        #endregion

        #region EnableHeaderFixing

        /// <summary>
        /// Identifies the <see cref="IsEnabled"/> Attached Dependency Property.
        /// </summary>
        public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached(
            "IsEnabled",
            typeof (bool),
            typeof (HeaderPersister),
            new FrameworkPropertyMetadata(false, OnEnableHeaderFixingChanged));

        /// <summary>
        /// Gets the EnableHeaderFixing property. This dependency property 
        /// indicates ....
        /// </summary>
        public static bool GetIsEnabled(DependencyObject d)
        {
            return (bool) d.GetValue(IsEnabledProperty);
        }

        /// <summary>
        /// Sets the EnableHeaderFixing property. This dependency property 
        /// indicates ....
        /// </summary>
        public static void SetIsEnabled(DependencyObject d, bool value)
        {
            d.SetValue(IsEnabledProperty, value);
        }

        /// <summary>
        /// Handles changes to the EnableHeaderFixing property.
        /// </summary>
        private static void OnEnableHeaderFixingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var targetItemsControl = (Control) d;

            if ((bool) e.NewValue)
            {
                targetItemsControl.Loaded += OnItemsControlLoaded;
                targetItemsControl.Unloaded += OnItemsControlUnloaded;
            }
            else
            {
                targetItemsControl.Loaded -= OnItemsControlLoaded;
                targetItemsControl.Unloaded -= OnItemsControlUnloaded;
            }
        }

        private static void OnItemsControlLoaded(object sender, RoutedEventArgs args)
        {
            var targetItemsControl = (ItemsControl) sender;

            // find the ScrollViewer
            var parentScrollViewer = targetItemsControl.FindChild<ScrollViewer>();
            if (parentScrollViewer == null)
            {
                return;
            }
            parentScrollViewer.ScrollChanged += OnScrollChanged;
        }

        private static void OnItemsControlUnloaded(object sender, RoutedEventArgs args)
        {
            var targetItemsControl = (ItemsControl) sender;

            // find the ScrollViewer
            var parentScrollViewer = targetItemsControl.FindChild<ScrollViewer>();
            if (parentScrollViewer == null)
            {
                return;
            }
            parentScrollViewer.ScrollChanged -= OnScrollChanged;
        }

        #endregion

        #endregion

        #region "Methods"

        private static void OnScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            var viewer = (ScrollViewer) sender;

            //find the adorner layer on which we will store items
            AdornerLayer adornerLayer;
            var scrollContentPresenter = viewer.Template.FindName("PART_ScrollContentPresenter", viewer) as ScrollContentPresenter;
            if (scrollContentPresenter != null)
            {
                adornerLayer = scrollContentPresenter.AdornerLayer;
            }
            else
            {
                return; // no need to continue
            }


            var itemsControl = viewer.FindParent<ItemsControl>();
            var headerTemplate = GetHeaderTemplate(itemsControl);

            var generator = itemsControl.ItemContainerGenerator;
            IList<object> items;
            

#if NET40
            items = generator.GetItems();
#else
            items = generator.Items.ToList();
#endif

            // find the view rect
            var viewRect = new Rect(new Point(0, 0), scrollContentPresenter.RenderSize);

            foreach (var item in items)
            {
                // if no container is defined.
                var container = generator.ContainerFromItem(item) as GroupItem;
                if (container == null)
                {
                    continue;
                }

                // Is the container in view?
                var childTransform = container.TransformToAncestor(scrollContentPresenter);
                var rect = childTransform.TransformBounds(
                    new Rect(new Point(0, 0), container.RenderSize));

                // intersects our child rect with the scroll viewer rect?
                var result = Rect.Intersect(viewRect, rect);

                var displayAdorner = true;
                displayAdorner &= result != Rect.Empty;
                displayAdorner &= rect.Top < 0;

                // the child is partially or completely in view
                if (displayAdorner)
                {
                    double top = Math.Abs(rect.Top);

                    // if we already have an adorner, update position
                    var headerAdorner = GetAdorner(container);
                    if (headerAdorner != null)
                    {
                        headerAdorner.UpdateLocation(top);
                        return;
                    }

                    // else, construct a new one.
                    var adorner = new HeaderAdorner(container)
                    {
                        DataContext = item,
                        HeaderTemplate = headerTemplate,
                        Top = top
                    };
                    adornerLayer.Add(adorner);

                    // save a reference to this element,
#if NET40
                    CurrentGroupItem.Add(container, new WeakReference(adorner));
#else
                    CurrentGroupItem.Add(container, new WeakReference<HeaderAdorner>(adorner));
#endif



                }
                else
                {
                    // locate and remove the adorner
                    var adorner = GetAdorner(container);
                    if (adorner != null)
                    {
                        adornerLayer.Remove(adorner);
                        CurrentGroupItem.Remove(container);
                    }
                }
            }
        }

        private static HeaderAdorner GetAdorner(GroupItem container)
        {
#if NET40
            if (CurrentGroupItem.ContainsKey(container))
            {
                var weakRef = CurrentGroupItem[container];
                if (weakRef.IsAlive)
                {
                    return (HeaderAdorner) weakRef.Target;
                }
            }
#else
            if (CurrentGroupItem.ContainsKey(container))
            {
                HeaderAdorner adorner;
                if (CurrentGroupItem[container].TryGetTarget(out adorner))
                {
                    return adorner;
                }
            }
#endif

            return null;
        }

        #endregion
    }
}

<|start_filename|>Demo/Model/Commit.cs<|end_filename|>
﻿using System;
using System.ComponentModel;

namespace Demo.Model
{
    public class Commit : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public string Message { get; set; }
        public string User { get; set; }
        public string Email { get; set; }
        public DateTime Date { get; set; }
        public string Hash { get; set; }
        public int FilesCount { get; set; }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}


<|start_filename|>Library.4/Helpers/ItemContainerGeneratorExtensions.cs<|end_filename|>
﻿// Copyright (c) '<NAME>'. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows.Controls;

namespace WpfGhost.Controls.Helpers
{
    internal static class ItemContainerGeneratorExtensions
    {
        private static readonly MethodInfo CachedItemsGetterMethod;

        static ItemContainerGeneratorExtensions()
        {
            CachedItemsGetterMethod = typeof(ItemContainerGenerator).GetProperty("Items", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).GetGetMethod();

        }

        internal static IList<object> GetItems(this ItemContainerGenerator target)
        {
            if (CachedItemsGetterMethod == null)
            {
                throw new InvalidOperationException();
            }

            return CachedItemsGetterMethod.Invoke(target, null) as IList<object>;
        }
    }
}


<|start_filename|>Library/Helpers/VisualTreeHelpers.cs<|end_filename|>
﻿// Copyright (c) '<NAME>'. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Windows;
using System.Windows.Media;

namespace WpfGhost.Controls.Helpers
{
    public static class VisualTreeHelpers
    {
        public static T FindParent<T>(this FrameworkElement child) where T : DependencyObject
        {
            T parent = null;
            var currentParent = VisualTreeHelper.GetParent(child);

            while (currentParent != null)
            {

                // check the current parent
                if (currentParent is T)
                {
                    parent = (T) currentParent;
                    break;
                }

                // find the next parent
                currentParent = VisualTreeHelper.GetParent(currentParent);
            }

            return parent;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parent"></param>
        /// <returns></returns>
        public static T FindChild<T>(this FrameworkElement parent) where T : DependencyObject
        {
            T child = null;

            var count = VisualTreeHelper.GetChildrenCount(parent);
            if (count == 0)
            {
                return null;
            }

            // check the children
            for (var i = 0; i < count; i++)
            {
                var currentChild = VisualTreeHelper.GetChild(parent, i);
                if (currentChild is T)
                {
                    child = (T) currentChild;
                    break;
                }

                // iterate over child sub-tree if nothing is yet found
                currentChild = FindChild<T>((FrameworkElement) currentChild);
                if (currentChild != null)
                {
                    child = (T) currentChild;
                    break;
                }
            }

            return child;
        }
    }
}
