getting the position of treeview items RELATIVE TO the top of the parent treeview.
I'm writing a custom control where I must know the offset of treeview items from the parent treeview. I've tried to make the treeview items text, TextBlocks, and have used PointToScreen, VisualTreeHelper.GetOffset, and adding up margin and height values, but absolutely all of these approaches gives me "zero." (I want the height offset primarily)
The reason I need this is that I am lining up more controls, on the other side of a grid splitter, with the leaves of my treeview. I realize I could place these controls IN the treeview, but how would I make a grid splitter separate the treeview from those items that are shown/hidden by the treeview? Is that even possible?
Thanks,
-Zom
Answers
I tried the following and seems to work
Code Snippet<
Canvas><
TreeView Name="tv1"><
TreeViewItem Header="root"><
TreeViewItem Header="child1"></TreeViewItem><
TreeViewItem Header="child2"></TreeViewItem><
TreeViewItem Header="child3"><
TreeViewItem Margin="20" Header="child3.1"></TreeViewItem><
TreeViewItem Header="child3.2"></TreeViewItem><
TreeViewItem Header="child3.3"></TreeViewItem></
TreeViewItem><
TreeViewItem Header="child4"></TreeViewItem></
TreeViewItem></
TreeView><
TextBlock Text="some text" Name="txt1" Canvas.Left="200" Canvas.Top="200"></TextBlock></
Canvas>
void tv1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e){
TreeViewItem tvi = e.NewValue as TreeViewItem; GeneralTransform myTransform = tvi.TransformToAncestor(tv1); Point myOffset = myTransform.Transform(new Point(0, 0));txt1.Text = myOffset.ToString();
txt1.SetValue(
Canvas.TopProperty, myOffset.Y);txt1.SetValue(
Canvas.LeftProperty, myOffset.X + 150);}
if its the height of the treeviewitems that you are asking, then it should be possible using the actualheight property...
All Replies
To answer your first question, the reason GetOffset is returning 0 is because that's relative to the immediate parent of the TextBlcok which is most certainly not the TreeView. Use Visual::TransformToAncestor instead and then calculate the offset as a Point... something like this:
Code SnippetGeneralTransform myTransform = myTextBlock.TransformToAncestor(myTreeView);
Point myOffset = myTransform.Transform(new Point(0, 0));
// myOffset.Y contains your distance from the top of the treeview now
To answer your second question, I can't try this out right now, but I think you should be able to pull this off by adding Grid.IsSharedSizeScope to your TreeView and then, in the template for your tree view items you define a grid giving each GridViewColumn a SharedSizeGroup name. The template also includes the splitter. Then because all the tree view items are within the scope of your treeview and share the same group names, when the splitter resizes the local grid's column it should cause all the columns to be resized.
HTH,
DrewThanks for your reply,
I tried your above suggestion, however I keep getting an exception which says: "The specified Visual is not an ancestor of this Visual." The structure of my tree view is as follows:
TreeView
TreeViewItem
TreeviewItem.Header = the text block I want the offset of.
Apparently this header field does not make the tree view an ancestor of the text block. I'm not really sure why. Is there a way around this?
Thanks,
-Zom.
- In which event are you trying to get the offset?
Okay, here's a program where I try to use the above suggestion. I thought my problem in my actual app was that I was making everything a treeview item, so in this example I tried adding textblocks to the Items collection of a treeviewitem, however this gave me the same problem. when this is run, I get "The specified Visual is not an ancestor of this Visual."
using
System;using
System.Collections.Generic;using
System.Text;using
System.Windows;using
System.Windows.Controls;using
System.Windows.Data;using
System.Windows.Documents;using
System.Windows.Input;using
System.Windows.Media;using
System.Windows.Media.Imaging;using
System.Windows.Shapes;using
System.Diagnostics;namespace
TreeViewblah{
/// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : System.Windows.Window{
public Window1(){
InitializeComponent();
Loaded +=
new RoutedEventHandler(Window1_Loaded);}
TreeView treeView; void Window1_Loaded(object sender, RoutedEventArgs e){
treeView =
new TreeView();Content = treeView;
TreeViewItem t = new TreeViewItem();t.Header =
"stuff"; TextBlock a = new TextBlock(); a.Text = "A"; TextBlock b = new TextBlock(); b.Text = "B"; TextBlock c = new TextBlock(); c.Text = "C";t.Items.Add(a);
t.Items.Add(b);
t.Items.Add(c);
treeView.Items.Add(t);
GeneralTransform myTransform = a.TransformToAncestor(treeView); Point myOffset = myTransform.Transform(new Point(0.0, 0.0)); Debug.WriteLine(myOffset.Y.ToString());}
}
}
- Replace the offset calculation code with the following:
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new System.Threading.ThreadStart(delegate
{GeneralTransform myTransform = a.TransformToAncestor(treeView);
Point myOffset = myTransform.Transform(new Point(0.0, 0.0));
Debug.WriteLine(myOffset.Y.ToString());
});
Sheva Here's an actual example of what I'm trying to accomplish, in xaml. I would like the grid splitter to separate the treeview from the ellipse item that appears when you click on the "propertyname" category.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="UntitledProject3.Window1"
x:Name="Window"
Title="Window1"
Width="640" Height="480"><Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.291*"/>
<ColumnDefinition Width="0.709*"/>
</Grid.ColumnDefinitions>
<TreeView Margin="0,0,72,30" Grid.ColumnSpan="2">
<TreeViewItem Header="CategoryName" IsExpanded="True">
<TreeViewItem Header="InstanceName" IsExpanded="True">
<TreeViewItem Header="PropertyName">
<Canvas Width="100">
<Ellipse Fill="#FFFFFFFF" Stroke="#FF000000" Width="48" Height="16" Canvas.Left="233" Canvas.Top="22.17"/>
</Canvas>
</TreeViewItem>
</TreeViewItem>
</TreeViewItem>
</TreeView>
<GridSplitter Margin="0,0,0,30" Width="7.912"/>
</Grid>
</Window>I tried the following and seems to work
Code Snippet<
Canvas><
TreeView Name="tv1"><
TreeViewItem Header="root"><
TreeViewItem Header="child1"></TreeViewItem><
TreeViewItem Header="child2"></TreeViewItem><
TreeViewItem Header="child3"><
TreeViewItem Margin="20" Header="child3.1"></TreeViewItem><
TreeViewItem Header="child3.2"></TreeViewItem><
TreeViewItem Header="child3.3"></TreeViewItem></
TreeViewItem><
TreeViewItem Header="child4"></TreeViewItem></
TreeViewItem></
TreeView><
TextBlock Text="some text" Name="txt1" Canvas.Left="200" Canvas.Top="200"></TextBlock></
Canvas>
void tv1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e){
TreeViewItem tvi = e.NewValue as TreeViewItem; GeneralTransform myTransform = tvi.TransformToAncestor(tv1); Point myOffset = myTransform.Transform(new Point(0, 0));txt1.Text = myOffset.ToString();
txt1.SetValue(
Canvas.TopProperty, myOffset.Y);txt1.SetValue(
Canvas.LeftProperty, myOffset.X + 150);}
That works great! I just added a splitter, and this example achieves exactly what I need:
Thanks so much.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="UntitledProject5.Window1"
x:Name="Window"
Title="Window1"
Width="640" Height="480"><Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.241*"/>
<ColumnDefinition Width="0.759*"/>
</Grid.ColumnDefinitions>
<Canvas HorizontalAlignment="Right" Width="456" Grid.Column="1">
<TextBlock Text="some text" Name="txt1" Canvas.Left="200" Canvas.Top="200"></TextBlock>
</Canvas>
<TreeView x:Name="tv1" Margin="0,0,8,0" SelectedItemChanged="tv1_SelectedItemChanged">
<TreeViewItem Header="root">
<TreeViewItem Header="child1"/>
<TreeViewItem Header="child2"/>
<TreeViewItem Header="child3">
<TreeViewItem Margin="20" Header="child3.1"/>
<TreeViewItem Header="child3.2"/>
<TreeViewItem Header="child3.3"/>
</TreeViewItem>
<TreeViewItem Header="child4"/>
</TreeViewItem>
</TreeView>
<GridSplitter Width="8" ShowsPreview="True"/>
</Grid>
</Window>- Applying what I've learned from this example, it appears that I cannot get the height of those items from the top of the treeview unless it is in the selected item changed event. Why is that? Note that my problem is solved, I just remain curious as to why I can't use that technique anywhere else but that SelectedItemChanged event. thanks
if its the height of the treeviewitems that you are asking, then it should be possible using the actualheight property...
There must be some other issue with the timing your measurement, because Visual::TransformToAncestor is a much lower level API and applies to way more than just TreeView/TreeViewItem. It should work just about everywhere. If you can explain where it's not working for you maybe we can help you understand the problem.
Cheers,
Drew

