WPF:滚动 Itemcontrol 内容固定标题 [英] WPF: Scroll Itemcontrol Content Fixed Header
问题描述
是否可以使用 WPF 的 ItemsControl 执行类似操作:Demo>
我正在尝试冻结 GroupedItems 而不是 GridView 列.
资源:
<CollectionViewSource x:Key="data" Source="{Binding}"><CollectionViewSource.GroupDescriptions><PropertyGroupDescription PropertyName="日期"/></CollectionViewSource.GroupDescriptions></CollectionViewSource></Window.Resources>
列表视图:
<ListView.View><网格视图><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 属性="模板"><Setter.Value><ControlTemplate TargetType="{x:Type GroupItem}"><网格><Grid.RowDefinitions><RowDefinition Height="自动"/><RowDefinition Height="自动"/></Grid.RowDefinitions><网格 Grid.Row="0"><TextBlock Background="Beige" FontWeight="Bold" Text="{Binding Path=Name, StringFormat={}{0}}"/></网格><DockPanel Grid.Row="1"><ItemsPresenter Grid.Row="2"></ItemsPresenter></DockPanel></网格></控制模板></Setter.Value></Setter></风格></GroupStyle.ContainerStyle></GroupStyle></ListView.GroupStyle></ListView>
背后的代码:
public MainWindow(){初始化组件();列表<字符串>colList1 = new List() { "Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7" };列表<字符串>colList2 = new List() { "1", "2", "3", "4", "5", "6" };ObservableCollection<数据>dataCollection = new ObservableCollection();for (var a = 0; a <100; a++){随机 rnd = 新随机();int min = rnd.Next(5000);int rnd1 = rnd.Next(0, 6);int rnd2 = rnd.Next(0, 5);dataCollection.Add(新数据(){Date = DateTime.Now.AddMinutes(min).ToString("hh:MM tt"),Col1 = colList1[rnd2],Col2 = String.Format("Col2: {0}", "X"),Col3 = colList2[rnd2]});}this.DataContext = dataCollection;}公共类数据{公共字符串日期{获取;放;}公共字符串 Col1 { 获取;放;}公共字符串 Col2 { 获取;放;}公共字符串 Col3 { 获取;放;}}
因为我刚刚遇到了类似的问题并且hack-ish"解决方案不适合我的需求,而且我通常不喜欢hack-ish"的东西生产环境,我为此开发了一个通用的解决方案,我想分享一下.附加的类具有以下主要功能:
- MVVM 兼容
- 无代码隐藏
- 兼容ListView、GridView、ItemsControl,甚至静态xaml!- 应该使用 ScollViewer 处理任何事情......
- 使用附加属性声明组项
xaml 用法(只是您内部的 ControlTemplate):
<网格><Grid.RowDefinitions><RowDefinition Height="自动"/><RowDefinition Height="自动"/></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></网格><DockPanel Grid.Row="1"><ItemsPresenter Grid.Row="2"></ItemsPresenter></DockPanel></网格></控制模板>
类(放在任何地方,必要时添加 xaml 命名空间):
公共静态类 StickyScrollHeader{公共静态 FrameworkElement GetAttachToControl(FrameworkElement obj){返回(FrameworkElement)obj.GetValue(AttachToControlProperty);}公共静态无效 SetAttachToControl(FrameworkElement obj, FrameworkElement 值){obj.SetValue(AttachToControlProperty, value);}私有静态 ScrollViewer FindScrollViewer(FrameworkElement item){FrameworkElement treeItem = item;FrameworkElement directItem = item;while (treeItem != null){treeItem = VisualTreeHelper.GetParent(treeItem) as FrameworkElement;if (treeItem 是 ScrollViewer){将 treeItem 作为 ScrollViewer 返回;}else if (treeItem 是 ScrollContentPresenter){返回 (treeItem 作为 ScrollContentPresenter).ScrollOwner;}}while (directItem != null){directItem = directItem.Parent 作为 FrameworkElement;if (directItem 是 ScrollViewer){将 directItem 作为 ScrollViewer 返回;}else if (directItem 是 ScrollContentPresenter){return (directItem as ScrollContentPresenter).ScrollOwner;}}返回空;}私有静态 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){将孩子作为 ScrollContentPresenter 返回;}}for (int i = 0; i < childCount; i++){if (FindScrollContentPresenter(VisualTreeHelper.GetChild(sv, i) as FrameworkElement) is FrameworkElement child && child is ScrollContentPresenter){将孩子作为 ScrollContentPresenter 返回;}}返回空;}public static readonly DependencyProperty AttachToControlProperty =DependencyProperty.RegisterAttached("AttachToControl", typeof(FrameworkElement), typeof(StickyScrollHeader), new PropertyMetadata(null, (s, e) =>{尝试{if (!(s is FrameworkElement targetControl)){ 返回;}Canvas.SetZIndex(targetControl, 999);滚动查看器 SV;FrameworkElement 父级;if (e.OldValue 是 FrameworkElement oldParentControl){ScrollViewer oldSv = FindScrollViewer(oldParentControl);父 = oldParentControl;oldSv.ScrollChanged -= Sv_ScrollChanged;}if (e.NewValue 是 FrameworkElement newParentControl){sv = FindScrollViewer(newParentControl);父 = 新父控件;sv.ScrollChanged += Sv_ScrollChanged;}void Sv_ScrollChanged(对象发送者,ScrollChangedEventArgs sce){如果(!parent.IsVisible){返回;}尝试{ScrollViewer isv = 发送者为 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();如果(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);}}别的{targetTransform.Y = 0;}targetControl.RenderTransform = targetTransform;}}抓住 { }}}抓住 { }}));}
希望这也能帮助其他人遇到这个问题;)
Is it possible to do something like this with WPF's ItemsControl: Demo
I am trying to freeze the GroupedItems rather than the GridView Columns.
Resources:
<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">
<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>
Code behind:
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; }
}
As i just ran into a similar issue and the 'hack-ish' solution did not fit my needs and i generally dont like 'hack-ish' stuff in production environments, i developed a generic solution to this which i'd like to share. The attached Class has following key-features:
- MVVM compatible
- no Code-Behind
- compatible with ListView, GridView, ItemsControl, even static xaml! - should work with anything using a ScollViewer ...
- Uses attached property to declare the group item
xaml usage (just your inner 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>
The Class (put anywhere, add xaml-namespace if necessary):
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 { }
}));
}
Hope this also helps others running into this issue ;)
这篇关于WPF:滚动 Itemcontrol 内容固定标题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!