I implemented a treeview with columns in WPF using ControlTemplate and a stackpanel of GridViewRowPresenter. I followed this article : http://blogs.msdn.com/b/atc_avalon_team/archive/2006/03/01/541206.aspx
It works perfectly!
However, I would like to keep the left column (with the names) visible while scrolling horizontally.
It would be like 'freeze panes' on microsoft excel on the first column.
An idea, anyone?
Thanks Frederic
The problem with the GridViewRowPresenter
solution is that the tree is inextricable from the other columns. I figure you need it to be separate so that you can put the horizontal-only ScrollViewer
around the columns, and I doubt this is easy (if possible) to do to the project in the article you linked.
This project that I slapped together to figure something out is quite rough around the edges. There are a number of issues you would need to work out separately that I didn't fine-tune:
GridView
aspects of the linked project for headers and the columns.Like the article project, I used a tree of Type
objects as the data source.
The crux of getting this to work was wrapping the data objects in an ExpandingContainer
object. The important things about this INPC class is the IsExpanded
property (for binding) and the collection of children:
public class ExpandingContainer : INotifyPropertyChanged {
public object Payload { get; private set; }
public ObservableCollection<ExpandingContainer> Children { get; private set; }
public ExpandingContainer( object payload ) { ... }
private bool _isexpanded;
public bool IsExpanded {
get { return _isexpanded; }
set {
if ( value == _isexpanded )
return;
_isexpanded = value;
PropertyChanged.Notify( () => IsExpanded );
}
}
public event PropertyChangedEventHandler PropertyChanged = (o,e) => {};
}
As for the XAML, first let's get some resources out of the way:
<!-- bind ExpandingContainer.IsExpanded to TreeViewItem.IsExpanded -->
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded"
Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
<!-- for binding ExpandingContainer.IsExpanded to visibility later -->
<BooleanToVisibilityConverter x:Key="boolvis" />
<!-- the TreeViewItems should display the Type's name -->
<HierarchicalDataTemplate DataType="{x:Type loc:ExpandingContainer}"
x:Key="treeViewSide"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Payload.Name}" />
</HierarchicalDataTemplate>
<!-- the column side are naively simple, the ItemsControl of children has its
visibility bound to ExpandingContainer, but the "columns" are just
StackPanels of TextBlocks -->
<HierarchicalDataTemplate DataType="{x:Type loc:ExpandingContainer}"
x:Key="columnSide">
<StackPanel>
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="10,0" />
</Style>
</StackPanel.Resources>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Payload.IsAbstract}" />
<TextBlock Text="{Binding Payload.Namespace}" />
<TextBlock Text="{Binding Payload.GUID}" />
</StackPanel>
<ItemsControl ItemsSource="{Binding Children}"
Visibility="{Binding IsExpanded, Converter={StaticResource boolvis}}" />
</StackPanel>
</HierarchicalDataTemplate>
<!-- a style can't refer to itself, so this was just to apply it to all ItemsControls -->
<Style TargetType="ItemsControl">
<Setter Property="ItemTemplate"
Value="{StaticResource columnSide}" />
</Style>
I originally tried nesting the horizontal-only ScrollViewer
containing the right columns inside the vertical-only ScrollViewer
that was responsible for the TreeView
, but that produced the strange requirement that you had to scroll to the bottom to scroll horizontally. So I separated them further, placing the ScrollViewer
s side-by-side.
To keep the vertical scrollbar on the far right, I hid both scrollbars around the TreeView
and use only the scrollbars around the columns. Syncing the vertical scrolling is done in code-behind, but for a more MVVM way to do it, you could make an attached behavior to facilitate binding them to each other.
<DockPanel>
<ScrollViewer VerticalScrollBarVisibility="Hidden"
HorizontalScrollBarVisibility="Hidden"
DockPanel.Dock="Left"
Name="treescroller">
<TreeView ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource treeViewSide}"
Padding="0,0,0,20">
</TreeView>
</ScrollViewer>
<ScrollViewer Name="columnscroller"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
ScrollChanged="columnscroller_ScrollChanged">
<ItemsControl ItemsSource="{Binding Items}" />
</ScrollViewer>
</DockPanel>
And lastly, the important bit of the code-behind (minus making the data objects and setting the DataContext
property):
private void columnscroller_ScrollChanged( object sender, ScrollChangedEventArgs e ) {
treescroller.ScrollToVerticalOffset( columnscroller.VerticalOffset );
}
Hope it helps, or at least provides a different perspective.
If I really needed a good one that filled every need I could think of for a hybrid TreeView
+ListView
, I'd probably look at professional controls first before spending the necessary time to polish a home-grown solution. This kind of thing is better when the requirements for such display are simple.
Would it be worthwhile to arrange another StackPanel of rows on the left, and then somehow bind the data of the left panel to the first column of the right panel? Then you could hide the first column of the right panel. The left panel could be sized appropriately without horizontal scrolling, and the right panel would have ordinary horizontal scrolling.
I'd imagine the vertical scrolling of the left panel would somehow have to be bound to that of the right panel.
Just an idea; hope you can find a better way.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With