I want to draw some Bézier curve inside a canvas on the left side of a treeview to display refrerences between items. I did implement a TreeView with lazy loading and mvvm based on a tutorial from Josh Smith.
This is Minimum example how it should look.
To draw the line I want use a canvas element. So I need the correct coordinate of a TreeViewItem relative to TreeView. I didt get the screenPosition using the Initialized Event.
private static void OnTreeViewItemIsInitialized(object sender, RoutedEventArgs e)
{
// Only react to the Selected event raised by the TreeViewItem
// whose IsSelected property was modified. Ignore all ancestors
// who are merely reporting that a descendant's Selected fired.
if (!Object.ReferenceEquals(sender, e.OriginalSource))
return;
TreeViewItem item = e.OriginalSource as TreeViewItem;
var parent = item.Parent;
var point = item.PointToScreen(new Point(0, 0));
var treeViewItemViewModel = item.DataContext as TreeViewItemViewModel;
if (treeViewItemViewModel != null)
treeViewItemViewModel.Coordinate = point;
}
Two challanges Iam running into using this Code: first i only get the screenposition, second I dont know how to get the parent of TreeViewItem to get calculate the correct position.
Maybe Iam on the wrong way to solve my problem? I did found some example to do something similar, but no example for a spline between two TreeViewItems of the same TreeView.
Related
I am making an application in C# WPF and need the location of an object that I added to a panel. While debugging I found the VisualOffset property of the object that gives me the relative position. I just can't get the value from the code.
What I would like to do is (though it is not possible):
var display = new Display(); // This is a class that inhereit UserControl
.
.
// At some point when display is added to a panel
var position = display.VisualOffset; // This property is not accessible
So how can I get the relative position of an object?
Use TranslatePoint method of the Display instance. Set the parent control as a target. The code below will give you coordinates of display on its parent. If the container is further down the visual tree then you have to find a parent of a parent.
In my sample I'm finding that on the parent. I'm doing that on button click and then return the results as a string to a text box - purely for simplicity sake. But the idea is the same wherever you use it:
private void Button_Click(object sender, RoutedEventArgs e)
{
var parent = display.Parent as UIElement;
var location = display.TranslatePoint(new Point(0, 0), parent);
this.myTextBox.Text = $"x: {location.X}, y: {location.Y}";
}
display is of course an instance of the Display user control.
Is there anyway to get the horizontal position(pixel) and vertical position(pixel) of a Run element in a FlowDocument?
Edit:
All i need to do is scroll to that position and make it the top line of the FlowDocument.
To Answer Your Question
The code needed to get the position of a content element in a document is all internal to .NET and not publically exposed. You would need access to an IContentHost implementation, which the built-in document viewers do not publically expose. So, there is no supported way to do what you are asking.
To Solve Your Actual Problem
There is a way to achieve your desired result of scrolling the element to the top of the view. What you want to do is scroll to the end of the document, then call BringIntoView on the element you want to have at the top.
There are multiple ways a FlowDocument can be displayed in an application. How you handle the scrolling depends on which control you are using to present the FlowDocument.
In a RichTextBox, use the ScrollToEnd method.
In a FlowDocumentScrollViewer, you will need to get its internal ScrollViewer and call ScrollToBottom on it. (You have to wait until the control is loaded before you can get a template part from it.)
private void MyControl_Loaded(object sender, RoutedEventArgs e)
{
mScrollViewer = mViewer.Template.FindName("PART_ContentHost", mViewer) as ScrollViewer;
}
In a FlowDocumentReader, the process is a bit more complex.
When the control is loaded, register for changes to the ViewingMode property and run the handler once to account for the starting value:
private void MyControl_Loaded(object sender, RoutedEventArgs e)
{
var descriptor = DependencyPropertyDescriptor.FromProperty(FlowDocumentReader.ViewingModeProperty, typeof(FlowDocumentReader));
descriptor.AddValueChanged(mReader, (s, a) => Reader_ViewModeChanged());
Reader_ViewModeChanged();
}
In the handler, dig in to find the ScrollViewer. It will only be present when the ViewingMode is set to Scroll:
private void Reader_ViewModeChanged()
{
mScrollViewer = null;
if (mReader.ViewingMode == FlowDocumentReaderViewingMode.Scroll)
{
var contentHost = mReader.Template.FindName("PART_ContentHost", mReader) as DependencyObject;
if (contentHost != null && VisualTreeHelper.GetChildrenCount(contentHost) > 0)
{
var documentScrollViewer = VisualTreeHelper.GetChild(contentHost, 0) as FlowDocumentScrollViewer;
if (documentScrollViewer != null)
{
documentScrollViewer.ApplyTemplate();
mScrollViewer = documentScrollViewer.Template.FindName("PART_ContentHost", documentScrollViewer) as ScrollViewer;
}
}
}
}
Once you have the ScrollViewer, you can call ScrollToBottom on it when desired.
Now, scroll to the bottom of the document, then call BringIntoView on your Run, and it should be at the top of the view.
Does not bring it to the top but just call BringIntoView on the Run. Save a reference to the Run.
It may be late but i still want to share the way i DID it in WPF.
You need an offset to do so.
As the above said: Flow gave you:
flow.ScrollToHome(); // Bottom
But also gave: ScrollToVerticalOffset (get from Rect)
if you have index (offset of the char/line) - you can find it in you saved data or get the TextPointer with flow.Selection.Start/End
TextPointer t_st = flow.Selection.Start;
double offset = flow.Document.ContentStart.GetOffsetToPosition(t_st);
private void gotoOffset(double offset)
{
TextPointer myTextPointer1 = flow.Document.ContentStart.GetPositionAtOffset((int)offset);
flow.Selection.Select(myTextPointer1, myTextPointer1);
flow.Focus();
Rect screenPos2 = myTextPointer1.GetCharacterRect(LogicalDirection.Forward);
double offset2 = screenPos2.Top;
Thread.Sleep(100);
flow.ScrollToVerticalOffset(offset2);
flow.Focus();
}
As the code above, We get the Rect from TextPointer, the Textpointer and get from Offset.
The focus just to make sure to place the cursor in right place.
Sometime the issue happen when you jump to many offset.
I recomment to trigger flow.ScrollToHome(); Before jump (because this ScrollToVerticalOffset true from the start, not any line)
The project I am working currently is in wpf and I am newer to wpf. In my project I want to search the tree view and when match occurs I need to highlight that element. I worked like this and I failed to set the back ground color of the node.
foreach (object item in treeView1.Items)
{
cls.Node n=(cls.Node)item;
n.IsSelected = true;
}
Can anyone help me on this. the above code is a sample code.
You have to get the wrapper TreeViewItem to set the Background. Because TreeView does not use VirtualizingStackPanel in its ItemsPanelTemplate, so we can safely use the ItemContainerGenerator to get a TreeViewItem from some item:
foreach (object item in treeView1.Items) {
//cls.Node n=(cls.Node)item;
//n.IsSelected = true;
var tvItem = treeView1.ItemContainerGenerator.ContainerFromItem(item)
as TreeViewItem;
if(tvItem != null) tvItem.Background = Brushes.Blue;//just an example
}
I'm not sure if the original code (commented above) is your attempt or not, so just uncomment them if you want (cls.Node is actually your custom class).
I have a ListView control on my form and I am trying to get either a Point or even better, a Rectangle of each visible item in my ListView. Anyone know of a trick to do this?
foreach (ListViewItem item in myListView.Items)
{
Rectangle result = item.Bounds;
if(result.IntersectsWith(myListView.ClientRectangle))
{
//there you go
}
}
More about the Bounds you can find here.
What I have done is handle the ListView.DrawSubItem event, which gives me access to a DrawListViewSubItemEventArgs e instance with a e.Bounds property giving me the rectangle you are asking for.
If you don't want to do any drawing on your own just set e.DrawDefault = true;
Here is an example of the results:
we're using the MVVM pattern in our application and in a window, we have two TreeViews allowing to drag items from the first and drop it on the second tree. To avoid code behind, we're using behaviours to bind the drag and drop against the ViewModel.
The behaviour is implemented pretty much like this example and working like a charm, with one bug.
The scenario is a tree which is bigger than the window displaying it, therefore it has a vertical scroll bar. When an item is selected and the user wants to scroll, the program starts drag and drop (which prevents the actual scrolling and therefore isn't what we want).
This isn't very surprising as the scrollbar is contained in the TreeView control. But I'm unable to determine safely if the mouse is over the scrollbar or not.
The TreeViewItems are represented by a theme using Borders, Panels and so on, so a simple InputHitTest isn't as simple as one may think.
Has anybody already encountered the same problem?
If more code coverage of the problem is required, I can paste some lines from the .xaml.
Edit
Incorporating Nikolays link I solved the problem using a IsMouseOverScrollbar method, if anyone has this problem in the future the code from above must be altered in the following way:
private static void PreviewMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed || startPoint == null)
return;
if (!HasMouseMovedFarEnough(e))
return;
if (IsMouseOverScrollbar(sender, e.GetPosition(sender as IInputElement)))
{
startPoint = null;
return;
}
var dependencyObject = (FrameworkElement)sender;
var dataContext = dependencyObject.GetValue(FrameworkElement.DataContextProperty);
var dragSource = GetDragSource(dependencyObject);
if (dragSource.GetDragEffects(dataContext) == DragDropEffects.None)
return;
DragDrop.DoDragDrop(
dependencyObject, dragSource.GetData(dataContext), dragSource.GetDragEffects(dataContext));
}
private static bool IsMouseOverScrollbar(object sender, Point mousePosition)
{
if (sender is Visual)
{
HitTestResult hit = VisualTreeHelper.HitTest(sender as Visual, mousePosition);
if (hit == null) return false;
DependencyObject dObj = hit.VisualHit;
while(dObj != null)
{
if (dObj is ScrollBar) return true;
if ((dObj is Visual) || (dObj is Visual3D)) dObj = VisualTreeHelper.GetParent(dObj);
else dObj = LogicalTreeHelper.GetParent(dObj);
}
}
return false;
}
Take a look at this implementation of Drag and Drop behaviour for ListView by Josh Smith. It has code to deal with scrollbars and some other unobvious problems of DnD (like drag treshold, precise mouse coordinates and such). This behaviour can be easily adopted to work with TreeViews too.
I had the same Problem. I solved it by placing the TreeView inside a ScrollViewer.
<ScrollViewer Grid.Column="0">
<TreeView BorderThickness="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MouseMove="DeviceTree_OnMouseMove" PreviewMouseLeftButtonDown="DeviceTree_OnPreviewMouseLeftButtonDown" Name="DeviceTree" ItemsSource="{Binding Devices}"/>
</ScrollViewer>