WPF TreeView with horizontal orientation? - wpf

What would be the bast way to change the orientation of the WPF treeview. I would like to work the expand-collapse-functionality to work left to right instead of top to down. I.e. when I click on on the expand button of a treenode I would its subnode to appear right of the parent and the indent should work top-down instead. Also the vertical lines that connect the node must now be horizontal.

Here is a great article by Josh Smith on CodeProject detaling exactly how to do this kind of thing.

To expand on John Smith's CodeProject article, if you want to have horizontal layout on only a particular level in the tree (instead of on all levels like his article shows), then just set the ItemsPanel property on the TreeViewItem at the level you want to have a StackPanel.
It wasn't intuitive to me at first, but you can get to this property through the ItemContainerStyle property of the HierarchicalDataTemplate for the layer above the layer you want to be horizontal.
Like this:
<ItemsPanelTemplate
x:Key="ItemsPanelForHorizontalItems">
<StackPanel
Orientation="Horizontal"/>
</ItemsPanelTemplate>
<HierarchicalDataTemplate
x:Key="DataTemplateForLayerAboveHorizontalItems"
DataType="{x:Type viewModel:ThingHavingHorizontalItems}"
ItemsSource="{Binding HorizontalItems}"
ItemTemplate="{StaticResource DataTemplateForLayerWithHorizontalItems}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style
TargetType="TreeViewItem">
<Setter
Property="ItemsPanel"
Value="{StaticResource ItemsPanelForHorizontalItems}"/>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<ContentControl
Content="{Binding}"
ContentTemplate="{StaticResource DataTemplateForThingHavingHorizontalItems}"/>
</HierarchicalDataTemplate>
Following this pattern will let you set horizontal layout for any individual layer within your tree, except the root layer. And if you want the root layer to be horizontal, then just set the ItemsPanel property on the TreeView to use a horizontal StackPanel.

Related

Adding shapes to Canvas. How to bind to VM

Sorry that the title is a bit vague but I could'n come up with a better one.
For arguments sake let's say I'm developing a simple drawing application, where the user just clicks and drags to draw a line (I'm not really developing that, but just to keep it simple).
I have a custom shape for the line to draw. Now I want to add new lines to the view as needed, but I'd like to use an ObservableCollection property via data binding on the view model to do that. Usaully I would use an ItemsControl. But of course the ItemsControl automatically positions it's items, which is not what I want.
Does anyone have an idea how to do that? Is there a way to disable the layout functions of an ItemsControl?
You can change the ItemsPanelTemplate of an ItemsControl so it uses a Canvas instead of a StackPanel to hold its items, then use the ItemContainerStyle to bind the Canvas.Top and Canvas.Left properties to your data object to position them.
<ItemsControl ItemsSource="{Binding MyCollection}">
<!-- ItemsPanelTemplate -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- ItemContainerStyle -->
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding Y}" />
<Setter Property="Canvas.Left" Value="{Binding X}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
I have a blog article about the ItemsControl that explains in more detail how an ItemsControl works if you're interested.

Unable to drag drop items between bound itemscontrols

iv'e got several bound itemscontrols which all play the role of of drop target
i need to be able to drag drop items between these items controls .
the problem is that the items control's are not recognized as drop targets by the drag drop framework
The ItemsControl Panel :
<ItemsPanelTemplate x:Key="TopPipePanelTemplate">
<StackPanel></StackPanel>
</ItemsPanelTemplate>
The DataTemplate :
<DataTemplate x:Key="PipeDataItem" >
<Ellipse Width="45" Height="45" Fill="{Binding IsMine,Converter={StaticResource MyCheckerOwnerToColorConverter}}"></Ellipse>
</DataTemplate>
The ItemsControl Style :
<Style TargetType="{x:Type ItemsControl}" x:Key="ItemsControlStyle">
<Setter Property="ItemTemplate" Value="{StaticResource PipeDataItem}"></Setter>
<Setter Property="AllowDrop" Value="True"></Setter>
<EventSetter Event="Drop" Handler="ItemsControlDropTarget"></EventSetter>
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="ItemsControl_MouseLeftButtonDown"></EventSetter>
</Style>
** for this example lets take 2 of The ItemsControls :**
<ItemsControl ItemsSource="{Binding Path=Pipes[23].Checkers}" Style="{StaticResource ItemsControlStyle}"/>
<ItemsControl Grid.Column="1" ItemsSource="{Binding Path=Pipes[22].Checkers}" Style="{StaticResource ItemsControlStyle}"/>
when regularly displayed :
the empty ( Left ) one is not recognized as a drop target although it as AllowDrop set to True and
handles the Drop event ( as do all the itemscontrols in this screen , look at ItemsControl Style above )
now when i color the itemscontrol panel it s suddenly is recognized :
<ItemsPanelTemplate x:Key="TopPipePanelTemplate">
<StackPanel Background="AliceBlue"></StackPanel>
</ItemsPanelTemplate>
as if now it takes up the entire cell which it occupies ..
iv'e tried setting the panel to VerticalAlignment="Stretch" but this also had no affect
i'm trying to understand why my itemcontrols are not recognized as drop enabled ,
even though i expect that they take up that space , in addition the itemscontrol with the ellipses
is only recognized until the height of the ellipses that occupie it's content .
any ideas ?
just to clarify what i mean by Recognized as drop target is when you drag the ellipse you are able
to drop it on top of the itemscontrol .
for now iv'e just set the background as Transparent
The default background color of a panel doesn't exist, which means hit tests pass through it. To get it to register hit tests, such as mouse over events, you need to give it a background color. Usually I just use White, although you said Transparent also works and would be a better choice.
In addition, StackPanels will usually only take up the space they need. You might be better off using a Panel that stretches to fill all available space, such as a DockPanel with LastChildFill="False", and set DockPanel.Dock="Top" on your items

How to load large number of controls in Canvas selectively

I have a big size Canvas and a lot of small user controls placed over it. At a time only a small portion of the canvas is visible on screen. The usercontrols are created by the ItemsControl which is Data bound to a ViewModel.
<Canvas Height="10000" Width="10000" Background="White" >
<ItemsControl ItemsSource="{Binding Path=MyData}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Views:MyControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Left}"/>
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Canvas>
Now since the number of usercontrols is huge, it takes a lot of time to load the whole canvas. I want to load only those controls which are in the visible frame currently. If I move the canvas, the usercontrols under the new visible area should be loaded. The prime reason I want to do this is because of performance lag associated with loading large number of usercontrols. I want to do it in Canvas what VirtualizingStackPanel does to StackPanel.
Is there a way to do it?
Thanks for the help
First, test loading blank user controls. That is user controls that display nothing at all. If this is fast enough it tells us that the problem is not in the canvas handling the large number of user controls but in initialising the user controls.
So, if that is the case, create a simple place holder user control that on IsVisibleChanged event firing loads the target user control using itself as the canvas.
All depends on the first test.
Look into virtualization. Instead of using Canvas, use a ListBox with your own implementation of VirtualizingPanel, that can act just as a Canvas and will only create the controls that are supposed to be visible.
There are many great resources on UI virtualization, here are two quick picks:
UI Virtualization in WPF by kirupa
Implementing a virtualized panel in WPF (Avalon) by Dan Crevier

WPF : TreeView virtualization not working

What can stop a TreeView from virtualizing if the TreeView is set up as follows?
<TreeView
ItemsSource="{Binding}"
VirtualizingStackPanel.IsVirtualizing="True">
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
<TreeView.ItemContainerStyle>
<Style
TargetType="{x:Type TreeViewItem}">
<Setter
Property="IsExpanded"
Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
I have one that is not virtualizing, when i expand the nodes (and use snoop to check) i have all of the TreeViewItems being created. I am wondering if there is some combination of containers that would prevent the TreeView from virtualizing its content. (like hosting it in a StackPanel for example)
The problem was with the styling. After some research we found that there was an unnamed style targeting the TreeView (i.e. one with DataType={x:Type TreeView} without an x:Key) and one targetting the TreeViewItem in our App.xaml (or equivalent) It was overriding the ControlTemplate for each respectively.
These styles did not have the triggers to set the ItemsPanel to a VirtualizingStackPanel and had no mention of any virtualization. When the styles are removed the TreeView works fine. Even though the local properties set the ItemsPanel and the VirtualizingStackPanel.Isvirtualizing="True" on the TreeView these properties were not being propogated to the TreeViewItems so the top level of the TreeView would virtualize whilst the sub categories would not (as their virtualization behaviour was dependant on the TreeViewItem)
Had the same problem. In my case, the initial size of the TreeView was not limited (TreeView is within a popup). Therfore, the virtualization panel initialized all controls for the first time.
Setting the MaxHeigt of the TreeView to 1000 solvend the problem.

Silverlight - Add Ellipses to a canvas dynamically with MVVM

I want to add a dynamic number of ellipses to a canvas and set the ellipse position (canvas.top, canvas.left). I tried binding to an ItemsControl but it each item (ellipse) has a container, so I cant set the ellipses position directly. I don't want an items container, I just want a canvas that contains ellipses.
Can this be done?
Try this - worked for me -- I use it to freely place textblocks on a canvas.
Re: Re: Positioning Items when Canvas is the ItemsPanel of an ItemsControl
02-26-2010 7:17 AM |
There is an alternative simpler solution that does work in silverlight 3.
<Canvas>
<ItemsControl ItemsSource={Binding MyItems}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<TextBlock Canvas.Left={Binding Left} Canvas.Top={Binding Top} Text={Binding Text} />
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
If the MyItems is a list of items that are of a class that has Left, Top and Text public properties, this works fine. I have also tested with Line and Border to draw simple bar graph graphics in silverlight 3.
From the bottom of this post:
http://forums.silverlight.net/forums/p/29753/450510.aspx#450510
Combine it with a Silverlight DataTemplateSelector and you can change the objects you draw based on view model properties:
http://www.codeproject.com/KB/silverlight/SLTemplateSelector.aspx
Ordinarily I would say use an ItemsControl in conjunction with a Canvas:
<ItemsControl ItemsSource="{Binding Ellipses}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Left}"/>
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
</Style>
</ItemsControl.ItemsContainerStyle>
</ItemsControl>
But in a display of Silverlight suckiness, the ItemContainerStyle property does not work on ItemsControl. It has support in ItemsControl, but it's not exposed by ItemsControl itself. Instead, it's up to subclasses of ItemsControl - such as ListBox - to expose it. Oh, and those subclasses have to be provided by Microsoft because the functionality is protected internal, so you can't just subclass ItemsControl and expose this stuff yourself. :S
So you could use ListBox instead, possibly by subclassing it and changing its item container to something simpler than a ListBoxItem. Or you could just use ListBox directly and fiddle around until the ListBoxItems look the way you want them to (i.e. not selected).

Resources