Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>My solution uses a TextBlock overlay that shares the group header style. The positioning and correct HitTesting is the tricky part, but I'm quite confident this does not break for small changes in layout or logic.</p> <p>I was not sure if you want to hide the ColumnHeader or not, but this is easy and doesn't need any other adjustments than what is depicted <a href="https://stackoverflow.com/a/632556/385995">here</a>.</p> <p><img src="https://i.stack.imgur.com/bhxUy.png" alt="enter image description here"></p> <p>Code behind:</p> <pre><code>using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace WpfApplication1 { public partial class FreezingGroupHeader : UserControl { private double _listviewHeaderHeight; private double _listviewSideMargin; public FreezingGroupHeader() { InitializeComponent(); List&lt;String&gt; colList1 = new List&lt;string&gt;() { "Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7" }; List&lt;String&gt; colList2 = new List&lt;string&gt;() { "1", "2", "3", "4", "5", "6" }; ObservableCollection&lt;Data&gt; dataCollection = new ObservableCollection&lt;Data&gt;(); Random rnd = new Random(); for (var a = 0; a &lt; 100; a++) { int min = rnd.Next(5000); int rnd1 = rnd.Next(0, 6); int rnd2 = rnd.Next(0, 5); dataCollection.Add( new Data() { Date = DateTime.Now.AddMinutes(min).ToString("hh:MM tt"), Col1 = colList1[rnd2], Col2 = String.Format("Col2: {0}", "X"), Col3 = colList2[rnd2] } ); } this.DataContext = dataCollection; this.Loaded += OnLoaded; } private void OnLoaded(object sender, RoutedEventArgs e) { // Position frozen header GetListViewMargins(this.listview1); Thickness margin = this.frozenGroupHeader.Margin; margin.Top = _listviewHeaderHeight; margin.Right = SystemParameters.VerticalScrollBarWidth + _listviewSideMargin; margin.Left = _listviewSideMargin; this.frozenGroupHeader.Margin = margin; UpdateFrozenGroupHeader(); } private void listview1_ScrollChanged(object sender, ScrollChangedEventArgs e) { UpdateFrozenGroupHeader(); } /// &lt;summary&gt; /// Sets text and visibility of frozen header /// &lt;/summary&gt; private void UpdateFrozenGroupHeader() { if (listview1.HasItems) { // Text of frozenGroupHeader GroupItem group = GetFirstVisibleGroupItem(this.listview1); if (group != null) { object data = group.Content; this.frozenGroupHeader.Text = data.GetType().GetProperty("Name").GetValue(data, null) as string; // slight hack } this.frozenGroupHeader.Visibility = Visibility.Visible; } else this.frozenGroupHeader.Visibility = Visibility.Collapsed; } /// &lt;summary&gt; /// Sets values that will be used in the positioning of the frozen header /// &lt;/summary&gt; private void GetListViewMargins(ListView listview) { if (listview.HasItems) { object o = listview.Items[0]; ListViewItem firstItem = (ListViewItem)listview.ItemContainerGenerator.ContainerFromItem(o); if (firstItem != null) { GroupItem group = FindUpVisualTree&lt;GroupItem&gt;(firstItem); Point p = group.TranslatePoint(new Point(0, 0), listview); _listviewHeaderHeight = p.Y; // height of columnheader _listviewSideMargin = p.X; // listview borders } } } /// &lt;summary&gt; /// Gets the first visible GroupItem in the listview /// &lt;/summary&gt; private GroupItem GetFirstVisibleGroupItem(ListView listview) { HitTestResult hitTest = VisualTreeHelper.HitTest(listview, new Point(5, _listviewHeaderHeight + 5)); GroupItem group = FindUpVisualTree&lt;GroupItem&gt;(hitTest.VisualHit); return group; } /// &lt;summary&gt; /// walk up the visual tree to find object of type T, starting from initial object /// http://www.codeproject.com/Tips/75816/Walk-up-the-Visual-Tree /// &lt;/summary&gt; private static T FindUpVisualTree&lt;T&gt;(DependencyObject initial) where T : DependencyObject { DependencyObject current = initial; while (current != null &amp;&amp; current.GetType() != typeof(T)) { current = VisualTreeHelper.GetParent(current); } return current as T; } public class Data { public string Date { get; set; } public string Col1 { get; set; } public string Col2 { get; set; } public string Col3 { get; set; } } } } </code></pre> <p>Xaml:</p> <pre><code>&lt;UserControl x:Class="WpfApplication1.FreezingGroupHeader" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" &gt; &lt;UserControl.Resources&gt; &lt;CollectionViewSource x:Key="data" Source="{Binding}"&gt; &lt;CollectionViewSource.GroupDescriptions&gt; &lt;PropertyGroupDescription PropertyName="Date"/&gt; &lt;/CollectionViewSource.GroupDescriptions&gt; &lt;/CollectionViewSource&gt; &lt;Style x:Key="GroupHeaderStyle1" TargetType="{x:Type TextBlock}"&gt; &lt;Setter Property="Background" Value="Beige" /&gt; &lt;Setter Property="Foreground" Value="Black" /&gt; &lt;Setter Property="FontWeight" Value="Bold" /&gt; &lt;/Style&gt; &lt;/UserControl.Resources&gt; &lt;Grid&gt; &lt;ListView x:Name="listview1" Grid.Column="0" ItemsSource="{Binding Source={StaticResource data}}" ScrollViewer.ScrollChanged="listview1_ScrollChanged" &gt; &lt;ListView.View&gt; &lt;GridView&gt; &lt;GridView.Columns&gt; &lt;GridViewColumn Header="Col 1" DisplayMemberBinding="{Binding Col1}" Width="100"/&gt; &lt;GridViewColumn Header="Col 2" DisplayMemberBinding="{Binding Col2}" Width="100"/&gt; &lt;GridViewColumn Header="Col 3" DisplayMemberBinding="{Binding Col3}" Width="100"/&gt; &lt;/GridView.Columns&gt; &lt;/GridView&gt; &lt;/ListView.View&gt; &lt;ListView.GroupStyle&gt; &lt;GroupStyle&gt; &lt;GroupStyle.ContainerStyle&gt; &lt;Style TargetType="{x:Type GroupItem}"&gt; &lt;Setter Property="Template"&gt; &lt;Setter.Value&gt; &lt;ControlTemplate TargetType="{x:Type GroupItem}"&gt; &lt;Grid&gt; &lt;Grid.RowDefinitions&gt; &lt;RowDefinition Height="Auto"/&gt; &lt;RowDefinition Height="Auto"/&gt; &lt;/Grid.RowDefinitions&gt; &lt;Grid Grid.Row="0"&gt; &lt;Grid.ColumnDefinitions&gt; &lt;ColumnDefinition Width="*"/&gt; &lt;/Grid.ColumnDefinitions&gt; &lt;TextBlock Style="{StaticResource GroupHeaderStyle1}" Text="{Binding Name, StringFormat={}{0}}" /&gt; &lt;/Grid&gt; &lt;DockPanel Grid.Row="1"&gt; &lt;ItemsPresenter Grid.Row="2"&gt;&lt;/ItemsPresenter&gt; &lt;/DockPanel&gt; &lt;/Grid&gt; &lt;/ControlTemplate&gt; &lt;/Setter.Value&gt; &lt;/Setter&gt; &lt;/Style&gt; &lt;/GroupStyle.ContainerStyle&gt; &lt;/GroupStyle&gt; &lt;/ListView.GroupStyle&gt; &lt;/ListView&gt; &lt;TextBlock x:Name="frozenGroupHeader" Style="{StaticResource GroupHeaderStyle1}" VerticalAlignment="Top"/&gt; &lt;/Grid&gt; &lt;/UserControl&gt; </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload