是否可以使用WPF的ItemsControl执行类似的操作:Demo
我正在尝试冻结GroupedItems而不是GridView列。
资源:
<Window.Resources>
<CollectionViewSource x:Key="data" Source="{Binding}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Date"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
的ListView:
<ListView Grid.Column="0" ItemsSource="{Binding Source={StaticResource data}}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Col 1" DisplayMemberBinding="{Binding Col1}" Width="100"/>
<GridViewColumn Header="Col 2" DisplayMemberBinding="{Binding Col2}" Width="100"/>
<GridViewColumn Header="Col 3" DisplayMemberBinding="{Binding Col3}" Width="100"/>
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Background="Beige" FontWeight="Bold" Text="{Binding Path=Name, StringFormat={}{0}}"/>
</Grid>
<DockPanel Grid.Row="1">
<ItemsPresenter Grid.Row="2"></ItemsPresenter>
</DockPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
背后的代码:
public MainWindow()
{
InitializeComponent();
List<String> colList1 = new List<string>(){"Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7"};
List<String> colList2 = new List<string>(){"1", "2", "3", "4", "5", "6"};
ObservableCollection<Data> dataCollection = new ObservableCollection<Data>();
for (var a = 0; a<100; a++){
Random rnd = new Random();
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;
}
public class Data
{
public string Date { get; set; }
public string Col1{ get; set; }
public string Col2{ get; set; }
public string Col3{ get; set; }
}
答案 0 :(得分:3)
我的解决方案使用共享组头样式的TextBlock叠加层。定位和正确的HitTesting是一个棘手的部分,但我非常有信心这不会因为布局或逻辑上的微小变化而中断。
我不确定您是否要隐藏ColumnHeader,但这很容易,并且不需要进行除here所示的任何其他调整。
代码背后:
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<String> colList1 = new List<string>() { "Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7" };
List<String> colList2 = new List<string>() { "1", "2", "3", "4", "5", "6" };
ObservableCollection<Data> dataCollection = new ObservableCollection<Data>();
Random rnd = new Random();
for (var a = 0; a < 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();
}
/// <summary>
/// Sets text and visibility of frozen header
/// </summary>
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;
}
/// <summary>
/// Sets values that will be used in the positioning of the frozen header
/// </summary>
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<GroupItem>(firstItem);
Point p = group.TranslatePoint(new Point(0, 0), listview);
_listviewHeaderHeight = p.Y; // height of columnheader
_listviewSideMargin = p.X; // listview borders
}
}
}
/// <summary>
/// Gets the first visible GroupItem in the listview
/// </summary>
private GroupItem GetFirstVisibleGroupItem(ListView listview)
{
HitTestResult hitTest = VisualTreeHelper.HitTest(listview, new Point(5, _listviewHeaderHeight + 5));
GroupItem group = FindUpVisualTree<GroupItem>(hitTest.VisualHit);
return group;
}
/// <summary>
/// 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
/// </summary>
private static T FindUpVisualTree<T>(DependencyObject initial) where T : DependencyObject
{
DependencyObject current = initial;
while (current != null && 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; }
}
}
}
的Xaml:
<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"
>
<UserControl.Resources>
<CollectionViewSource x:Key="data" Source="{Binding}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Date"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="GroupHeaderStyle1" TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="Beige" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
</UserControl.Resources>
<Grid>
<ListView x:Name="listview1" Grid.Column="0" ItemsSource="{Binding Source={StaticResource data}}" ScrollViewer.ScrollChanged="listview1_ScrollChanged" >
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Col 1" DisplayMemberBinding="{Binding Col1}" Width="100"/>
<GridViewColumn Header="Col 2" DisplayMemberBinding="{Binding Col2}" Width="100"/>
<GridViewColumn Header="Col 3" DisplayMemberBinding="{Binding Col3}" Width="100"/>
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource GroupHeaderStyle1}" Text="{Binding Name, StringFormat={}{0}}" />
</Grid>
<DockPanel Grid.Row="1">
<ItemsPresenter Grid.Row="2"></ItemsPresenter>
</DockPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
<TextBlock x:Name="frozenGroupHeader" Style="{StaticResource GroupHeaderStyle1}" VerticalAlignment="Top"/>
</Grid>
</UserControl>
答案 1 :(得分:1)
这个解决方案不是很好,而且它很糟糕,但它基本上可以做你想要的。我使listview标头不可见,在listview上方放置一个文本块,并将文本值设置为列表框中第一个可见项的groupitem。哈基,但这是我想出的最好的。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<String> colList1 = new List<string>() { "Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7" };
List<String> colList2 = new List<string>() { "1", "2", "3", "4", "5", "6" };
ObservableCollection<Data> dataCollection = new ObservableCollection<Data>();
for (var a = 0; a < 100; a++)
{
Random rnd = new Random();
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;
}
public class Data
{
public string Date { get; set; }
public string Col1 { get; set; }
public string Col2 { get; set; }
public string Col3 { get; set; }
}
private void grid_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (grid.Items.Count > 0)
{
HitTestResult hitTest = VisualTreeHelper.HitTest(grid, new Point(5, 5));
System.Windows.Controls.ListViewItem item = GetListViewItemFromEvent(null, hitTest.VisualHit) as System.Windows.Controls.ListViewItem;
if (item != null)
Head.Text = ((Data)item.Content).Date;
}
}
System.Windows.Controls.ListViewItem GetListViewItemFromEvent(object sender, object originalSource)
{
DependencyObject depObj = originalSource as DependencyObject;
if (depObj != null)
{
// go up the visual hierarchy until we find the list view item the click came from
// the click might have been on the grid or column headers so we need to cater for this
DependencyObject current = depObj;
while (current != null && current != grid)
{
System.Windows.Controls.ListViewItem ListViewItem = current as System.Windows.Controls.ListViewItem;
if (ListViewItem != null)
{
return ListViewItem;
}
current = VisualTreeHelper.GetParent(current);
}
}
return null;
}
}
XAML:
<Window x:Class="header.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="auto" SizeToContent="Width">
<Window.Resources>
<CollectionViewSource x:Key="data" Source="{Binding}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Date"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="myHeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<TextBlock Name="Head" Grid.Row="0"/>
<ListView Name="grid" Grid.Column="0" Grid.Row="1" ItemsSource="{Binding Source={StaticResource data}}" Height="300" ScrollViewer.ScrollChanged="grid_ScrollChanged">
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource myHeaderStyle}">
<GridView.Columns>
<GridViewColumn DisplayMemberBinding="{Binding Col1}" Width="100"/>
<GridViewColumn DisplayMemberBinding="{Binding Col2}" Width="100"/>
<GridViewColumn DisplayMemberBinding="{Binding Col3}" Width="100"/>
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Background="Beige" FontWeight="Bold" Text="{Binding Path=Name, StringFormat={}{0}}"/>
</Grid>
<DockPanel Grid.Row="1">
<ItemsPresenter Grid.Row="2"></ItemsPresenter>
</DockPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</StackPanel>
</Grid>
编辑:修复了ScrollChanged事件。
private void grid_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (grid.Items.Count > 0)
{
Point point = new Point(5, 5);
foreach(Data lvItem in grid.Items)
{
HitTestResult hitTest = VisualTreeHelper.HitTest(grid, point);
ListViewItem item = GetListViewItemFromEvent(null, hitTest.VisualHit) as System.Windows.Controls.ListViewItem;
if (item != null)
{
Data value = ((Data)item.Content);
Head.Text = ((Data)item.Content).Date;
break;
}
else
{
point.X += 5;
point.Y += 5;
}
}
}
}
答案 2 :(得分:0)
因为我刚遇到了类似的问题,并且&#39; hack-ish&#39;解决方案不符合我的需求,我一般不喜欢&#39; hack-ish&#39;在生产环境中的东西,我开发了一个通用的解决方案,我想分享。 附加的类具有以下关键功能:
xaml用法(只是你的内部ControlTemplate):
<ControlTemplate TargetType="{x:Type GroupItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" local:StickyScrollHeader.AttachToControl="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Grid}}">
<TextBlock Background="Beige" FontWeight="Bold" Text="{Binding Path=Name, StringFormat={}{0}}"/>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
<DockPanel Grid.Row="1">
<ItemsPresenter Grid.Row="2"></ItemsPresenter>
</DockPanel>
</Grid>
</ControlTemplate>
Class(放在任何地方,必要时添加xaml-namespace):
public static class StickyScrollHeader
{
public static FrameworkElement GetAttachToControl(FrameworkElement obj)
{
return (FrameworkElement)obj.GetValue(AttachToControlProperty);
}
public static void SetAttachToControl(FrameworkElement obj, FrameworkElement value)
{
obj.SetValue(AttachToControlProperty, value);
}
private static ScrollViewer FindScrollViewer(FrameworkElement item)
{
FrameworkElement treeItem = item;
FrameworkElement directItem = item;
while (treeItem != null)
{
treeItem = VisualTreeHelper.GetParent(treeItem) as FrameworkElement;
if (treeItem is ScrollViewer)
{
return treeItem as ScrollViewer;
}
else if (treeItem is ScrollContentPresenter)
{
return (treeItem as ScrollContentPresenter).ScrollOwner;
}
}
while (directItem != null)
{
directItem = directItem.Parent as FrameworkElement;
if (directItem is ScrollViewer)
{
return directItem as ScrollViewer;
}
else if (directItem is ScrollContentPresenter)
{
return (directItem as ScrollContentPresenter).ScrollOwner;
}
}
return null;
}
private static ScrollContentPresenter FindScrollContentPresenter(FrameworkElement sv)
{
int childCount = VisualTreeHelper.GetChildrenCount(sv);
for (int i = 0; i < childCount; i++)
{
if (VisualTreeHelper.GetChild(sv, i) is FrameworkElement child && child is ScrollContentPresenter)
{
return child as ScrollContentPresenter;
}
}
for (int i = 0; i < childCount; i++)
{
if (FindScrollContentPresenter(VisualTreeHelper.GetChild(sv, i) as FrameworkElement) is FrameworkElement child && child is ScrollContentPresenter)
{
return child as ScrollContentPresenter;
}
}
return null;
}
public static readonly DependencyProperty AttachToControlProperty =
DependencyProperty.RegisterAttached("AttachToControl", typeof(FrameworkElement), typeof(StickyScrollHeader), new PropertyMetadata(null, (s, e) =>
{
try
{
if (!(s is FrameworkElement targetControl))
{ return; }
Canvas.SetZIndex(targetControl, 999);
ScrollViewer sv;
FrameworkElement parent;
if (e.OldValue is FrameworkElement oldParentControl)
{
ScrollViewer oldSv = FindScrollViewer(oldParentControl);
parent = oldParentControl;
oldSv.ScrollChanged -= Sv_ScrollChanged;
}
if (e.NewValue is FrameworkElement newParentControl)
{
sv = FindScrollViewer(newParentControl);
parent = newParentControl;
sv.ScrollChanged += Sv_ScrollChanged;
}
void Sv_ScrollChanged(object sender, ScrollChangedEventArgs sce)
{
if (!parent.IsVisible) { return; }
try
{
ScrollViewer isv = sender as ScrollViewer;
ScrollContentPresenter scp = FindScrollContentPresenter(isv);
var relativeTransform = parent.TransformToAncestor(scp);
Rect parentRenderRect = relativeTransform.TransformBounds(new Rect(new Point(0, 0), parent.RenderSize));
Rect intersectingRect = Rect.Intersect(new Rect(new Point(0, 0), scp.RenderSize), parentRenderRect);
if (intersectingRect != Rect.Empty)
{
TranslateTransform targetTransform = new TranslateTransform();
if (parentRenderRect.Top < 0)
{
double tempTop = (parentRenderRect.Top * -1);
if (tempTop + targetControl.RenderSize.Height < parent.RenderSize.Height)
{
targetTransform.Y = tempTop;
}
else if (tempTop < parent.RenderSize.Height)
{
targetTransform.Y = tempTop - (targetControl.RenderSize.Height - intersectingRect.Height);
}
}
else
{
targetTransform.Y = 0;
}
targetControl.RenderTransform = targetTransform;
}
}
catch { }
}
}
catch { }
}));
}
希望这也有助于其他人遇到这个问题;)