我正在尝试编写一个自定义WPF / MVVM TreeView控件,当父项扩展时,它将自动滚动(尽可能多)子项目到视图中。
我发现了这篇帖子WPF TreeView - How to scroll so expanded branch is visible,但它似乎只有在树项实际上从TreeViewItem下降时才有效。
My Tree Item类只是一个简单的C#对象 - 沿着Josh Smith的博客http://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode,它们在Tree Item类的IsExpanded属性setter中被延迟加载。
进入OnExpand事件处理程序,我可以看到原始源是一个TreeViewItem(由幕后框架创建),它在标题字段中有我的树项对象。这个TreeViewItem只有一个可视子项,一个网格,后者又有3个可视子项,ToggleButton,Border和ItemsPresenter。
有没有人提示我如何为我的子项获取相应的可视组件,然后我可以调用BringIntoView()?
答案 0 :(得分:1)
找到一个非常好的行为,将所选项目滚动到视图中,位于此处:
遗憾的是,这不足以满足项目扩展的需要,但可以根据您的具体要求进行修改。如果您需要任何帮助,请告诉我。
http://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF
答案 1 :(得分:0)
在编程方面,五年是一段很长的时间......
我在获得2500次观看徽章后最近又回到了这个问题。我刚刚在我的博客上发布了一篇关于这些问题的解决方案的帖子 - http://peregrinesview.uk/wpf-behaviors-part-2-treeview/。
我知道在stackoverflow上通常不会批准仅限链接的答案,所以我也会在这里发布关键元素。
1)TreeViewItem的帮助器包含两个附加属性,一个用于将所选项目滚动到视图中,另一个用于在展开项目时将尽可能多的子项滚动到视图中。
public static class perTreeViewItemHelper
{
public static bool GetBringSelectedItemIntoView(TreeViewItem treeViewItem)
{
return (bool)treeViewItem.GetValue(BringSelectedItemIntoViewProperty);
}
public static void SetBringSelectedItemIntoView(TreeViewItem treeViewItem, bool value)
{
treeViewItem.SetValue(BringSelectedItemIntoViewProperty, value);
}
public static readonly DependencyProperty BringSelectedItemIntoViewProperty =
DependencyProperty.RegisterAttached(
"BringSelectedItemIntoView",
typeof(bool),
typeof(perTreeViewItemHelper),
new UIPropertyMetadata(false, BringSelectedItemIntoViewChanged));
private static void BringSelectedItemIntoViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (!(args.NewValue is bool))
return;
var item = obj as TreeViewItem;
if (item == null)
return;
if ((bool)args.NewValue)
item.Selected += OnTreeViewItemSelected;
else
item.Selected -= OnTreeViewItemSelected;
}
private static void OnTreeViewItemSelected(object sender, RoutedEventArgs e)
{
var item = e.OriginalSource as TreeViewItem;
item?.BringIntoView();
// prevent this event bubbling up to any parent nodes
e.Handled = true;
}
public static bool GetBringExpandedChildrenIntoView(TreeViewItem treeViewItem)
{
return (bool)treeViewItem.GetValue(BringExpandedChildrenIntoViewProperty);
}
public static void SetBringExpandedChildrenIntoView(TreeViewItem treeViewItem, bool value)
{
treeViewItem.SetValue(BringExpandedChildrenIntoViewProperty, value);
}
public static readonly DependencyProperty BringExpandedChildrenIntoViewProperty =
DependencyProperty.RegisterAttached(
"BringExpandedChildrenIntoView",
typeof(bool),
typeof(perTreeViewItemHelper),
new UIPropertyMetadata(false, BringExpandedChildrenIntoViewChanged));
private static void BringExpandedChildrenIntoViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (!(args.NewValue is bool))
return;
var item = obj as TreeViewItem;
if (item == null)
return;
if ((bool)args.NewValue)
item.Expanded += OnTreeViewItemExpanded;
else
item.Expanded -= OnTreeViewItemExpanded;
}
private static void OnTreeViewItemExpanded(object sender, RoutedEventArgs e)
{
var item = e.OriginalSource as TreeViewItem;
if (item == null)
return;
// use DispatcherPriority.ContextIdle, so that we wait for all of the UI elements for any newly visible children to be created
// first bring the last child into view
Action action = () =>
{
var lastChild = item.ItemContainerGenerator.ContainerFromIndex(item.Items.Count - 1) as TreeViewItem;
lastChild?.BringIntoView();
};
item.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
// then bring the expanded item (back) into view
action = () => { item.BringIntoView(); };
item.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
// prevent this event bubbling up to any parent nodes
e.Handled = true;
}
}
2)TreeViewItem的样式包括这些属性
<Style x:Key="perExpandCollapseToggleStyle"
TargetType="ToggleButton">
<Setter Property="Focusable" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid Width="10"
Height="10"
Background="Transparent">
<Path x:Name="ExpanderGlyph"
Margin="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Data="M 0,3 L 0,5 L 3,5 L 3,8 L 5,8 L 5,5 L 8,5 L 8,3 L 5,3 L 5,0 L 3,0 L 3,3 z"
Fill="LightGreen"
Stretch="None" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="ExpanderGlyph" Property="Data" Value="M 0,0 M 8,8 M 0,3 L 0,5 L 8,5 L 8,3 z" />
<Setter TargetName="ExpanderGlyph" Property="Fill" Value="Red" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="ExpanderGlyph" Property="Fill" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="perTreeViewItemContainerStyle"
TargetType="{x:Type TreeViewItem}">
<!-- Link the properties of perTreeViewItemViewModelBase to the corresponding ones on the TreeViewItem -->
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
<!-- Include the two "Scroll into View" behaviors -->
<Setter Property="vhelp:perTreeViewItemHelper.BringSelectedItemIntoView" Value="True" />
<Setter Property="vhelp:perTreeViewItemHelper.BringExpandedChildrenIntoView" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
MinWidth="14" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ToggleButton x:Name="Expander"
Grid.Row="0"
Grid.Column="0"
ClickMode="Press"
IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource perExpandCollapseToggleStyle}" />
<Border x:Name="PART_Border"
Grid.Row="0"
Grid.Column="1"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter x:Name="PART_Header"
Margin="0,2"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
ContentSource="Header" />
</Border>
<ItemsPresenter x:Name="ItemsHost"
Grid.Row="1"
Grid.Column="1" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter TargetName="Expander" Property="Visibility" Value="Hidden" />
</Trigger>
<!-- Use the same colors for a selected item, whether the TreeView is focussed or not -->
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="PART_Border" Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type TreeView}">
<Setter Property="ItemContainerStyle" Value="{StaticResource perTreeViewItemContainerStyle}" />
</Style>