Is there a way to evaluate the actual position of a TreeViewItem on a Canvas if its Parent TreeViewItem is collapsed (meaning <ParentTreeViewItem>.IsExpanded = false;)? When debugging neither the Visibility nor the Position information of the collapsed item in the parent´s ItemsHost seems to be updated.
Appreciating any hint!
Cheers, Alex
Found a workaround for this problem:
Basically whenever a TreeViewItem fires an OnCollapsed or OnExpanded event I force all other TreeViewItems to determine their own position based on the IsExpanded property of their ancestors up to the tree root.
If any of the ancestors is collapsed (i. e. IsExpanded == false) I apply its position to the current TreeViewItems position.
If none of the ancestors is collapsed, I apply its own position (whereas position is a custom Point property of the TreeViewItem).
Code sample:
private Point DeterminePosition()
{
Point point = this.position;
if (ParentTreeViewItem != null)
{
MyTreeViewItem parent = null, lastCollapsed = null;
parent = ParentTreeViewItem;
while (parent != null)
{
if (parent.IsExpanded == false)
{
lastCollapsed = parent;
}
parent = parent.ParentTreeViewItem;
}
if (lastCollapsed != null)
{
point = new Point(position.X, lastCollapsed.Position.Y);
}
}
return point;
}
Related
I'm trying to find the extents of a UI element in a ListBox. I use WPF / c# for this.
The code is roughly as follows:
ObservableCollection<SomeUserControl> TheCollection = new ObservableCollection<SomeUserControl>();
// setup
ListBox lb = new ListBox();
// code omitted here that setup the listbox with 2 columns
lb.ItemsSource = TheCollection;
// code omitted here that populates TheCollection with UI elements
Then I register a mouse event and I would like to know when a specific point is bounded by a specific element in the listbox. For this I have the following means to do it:
UIElement GetObjectHit(Point P) {
if (!lb.IsVisible) return null;
foreach(var i in TheCollection) {
FrameworkElement item = i as FrameworkElement;
if (item == null || !item.IsVisible)
continue;
Point P00 = item.TranslatePoint(new Point(0,0), lb);
Point P11 = item.TranslatePoint(new Point(item.ActualWidth, item.ActualHeight), lb);
if (IsBounded(P, P00, P11)
return item;
}
return null;
}
UIElement GetObjectHit(MouseButtonEventArgs e) {
return GetObjectHit(e.GetPosition(lb));
}
This works great with one exception...
The different user controls I have have varying sizes and the ListBox will then extend each item to the maximum element size currently in list and then center the smaller UserControls that are in the list.
So what's the problem?
Well when the position is in the white area which is inside the ListBoxItem area but outside the UserControl area the code above fails to find the intersection.
So my question is as follows:
Do I have to do a separate loop first to get the maximum extends of each UI element or is there any way to get the current extent of the ListBoxItem in the ListBox (even though the UserControl extent is smaller)?
In the unlikely event if anyone needs the answer to this I managed to get this to work as follows:
UIElement GetObjectHit(Point P) {
if (!lb.IsVisible) return null;
foreach(var i in TheCollection) {
var item = i as SomeUserControl;
ListBoxItem lbi = lb.ContainerFromElement(item) as ListBoxItem;
if (lbi == null || !lbi.IsVisible)
continue;
Point P00 = lbi.TranslatePoint(new Point(0,0), lb);
Point P11 = lbi.TranslatePoint(new Point(lbi.ActualWidth, lbi.ActualHeight), lb);
if (IsBounded(P, P00, P11)
return i;
}
return null;
}
I have created a WPF application . I need to identify Next focusable element . For that I have added following code.
UIElement elementWithFocus = System.Windows.Input.Keyboard.FocusedElement as UIElement;
var a = elementWithFocus.PredictFocus(FocusNavigationDirection.Next);
But it is showing that Next is not supported. How I can achieve the same?
Try to list all the controls on your window and search for the one which has it's TabIndex property equals to the current TabIndex property + 1
private UIElement SearchNextControl(int currentTabIndex)
{
foreach (UIElement element in AllTheControls)
{
if(element.TabIndex == (currentTabIndex + 1))
{
return element;
}
}
return null;
}
I have a TreeView with lots of items, obviously arranged in a tree structure. I am trying to print the parent node in bold and all its children in normal font. However, when I do:
TreeViewItem item = GetParentNode(...);
item.FontWeight = FontWeights.Bold;
this not only changes the parent's style to bold, but also all its children's. I have been looking for properties to disable this recursive update in the TreeView class, but I can't find any. How do I avoid this behaviour?
When you set the font properties it applies it to all children. You could create a new header template and apply it only to the nodes that are parents.
An alternative that has worked in my testing is setting the foreground color:
public MainWindow()
{
InitializeComponent();
StyleParents(MyTreeView.Items);
}
private void StyleParents(ItemCollection items)
{
foreach (var i in items)
{
TreeViewItem tvi = i as TreeViewItem;
if (tvi != null)
{
if (tvi.HasItems)
{
tvi.Foreground = Brushes.Magenta;
StyleParents(tvi.Items);
}
}
}
}
I found that in the WPF TreeView, properties are transferred onto their children if they haven't already been set. The easiest way to avoid this problem (programatically, at least) is to set the font of a child node to the default font as soon as it is created. E.g.
TreeViewItem item = new TreeViewItem ();
item.FontWeight = FontWeights.Normal;
// ...add item to tree
If you now set the FontWeight of a parent node to bold, the children will not change.
I am interested in capturing a drag/drop event that will start with the user dragging an existing TreeNode somewhere within the TreeView. While the user is dragging the TreeNode around, I am interested in capturing when the node has been dragged between two tree nodes. When the user does this, I wanted to display a hash mark in-between the tree nodes to designate if the node would be dropped within the node as a child or as a sibling. This hash mark would display either:
- underneath the destination node (to indicate the source node will be dropped as a child of the destination node
OR
- underneath the destination node to the left (to indicate the source node will be dropped as a sibling of the destination node), before or after...
I have made some headway using the DragOver event. I am calculating the mouse location and deriving what the top and bottom nodes are as I drag the mouse around..
int threshold = 8; //Joe(hack)
Point mouseLocation = mouseLocation = treeViewConditions.PointToClient(new Point(e.X, e.Y - threshold));
TreeNode topNode = treeViewConditions.GetNodeAt(mouseLocation);
mouseLocation = treeViewConditions.PointToClient(new Point(e.X + threshold, e.Y));
TreeNode bottomNode = treeViewConditions.GetNodeAt(mouseLocation);
if (topNode != null && bottomNode == null)
{
textBoxDescription.Text = "handling top node";
}
else if (topNode == null && bottomNode != null)
{
textBoxDescription.Text = "handling bottom node";
}
else if (topNode != null && bottomNode != null)
{
if (topNode != bottomNode)
{
textBoxDescription.Text = "between!";
}
else if (topNode == bottomNode)
{
}
}
However in doing this, it just feels dirty. I am wondering if anyone knew of a better way to do accomplish this.
Thanks a ton in advance!
Drawing the 'hash mark' is going to be the real problem. TreeView has a DrawMode property but its DrawItem event doesn't let you draw between the nodes.
You need to handle this by changing the cursor to indicate what is going to happen. Use the GiveFeedback event, set e.UseCustomCursors to false and assign Cursor.Current to a custom cursor that indicates the operation.
This article articulates the same issue and provides an approach somewhat similar to the one you are already following (with the exception that the thresholds are essentially percentages of the height of the tree node). Based on this, and the fact that when I was doing this before, that was the best approach I could find, I think you're basically on track.
I have a list of files in a ListView in WPF. Users can drag files onto the list view, and right now they are just appended to the end of the list. Is it possible to insert the file into the ListView right where the user dropped it?
WPF isn't really designed to be used that way. While you can brute force add ListViewItem's directly to the ListView, the way it's really supposed to work is that you have a collection of some kind (ObservableCollection<FileInfo> would work well) and bind the ListView's ItemsSource property to that collection.
Then the answer is simple. Instead of the Add method, you use the Insert method of the collection which takes an index.
As for finding which ListViewItem the mouse event occurred over, you could use the VisualTreeHelper.HitTest method.
From my point of view it is little tricky when I used the templated item. I have fight with it little bit. I am sharing my usecase which works with DraggableListBox. But I suppose the same solution works with ListBox control.
As the first I created the dependency object extension which is able to provide me ListItem element:
public static class WpfDomHelper
{
public static T FindParent<T>(this DependencyObject child) where T : DependencyObject
{
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
if (parentObject == null) return null;
T parent = parentObject as T;
if (parent != null)
return parent;
else
return FindParent<T>(parentObject);
}
}
Then I implemented Drop logic which inserts(adds) item according specific Drop Y position of destination ListBoxItems:
private void Grid_Drop(object sender, DragEventArgs e)
{
int dropIndex = -1; // default position directong to add() call
// checking drop destination position
Point pt = e.GetPosition((UIElement)sender);
HitTestResult result = VisualTreeHelper.HitTest(this, pt);
if (result != null && result.VisualHit != null)
{
// checking the object behin the drop position (Item type depend)
var theOne = result.VisualHit.FindParent<Microsoft.TeamFoundation.Controls.WPF.DraggableListBoxItem>();
// identifiing the position according bound view model (context of item)
if (theOne != null)
{
//identifing the position of drop within the item
var itemCenterPosY = theOne.ActualHeight / 2;
var dropPosInItemPos = e.GetPosition(theOne);
// geting the index
var itemIndex = tasksListBox.Items.IndexOf(theOne.Content);
// decission if insert before or below
if (dropPosInItemPos.Y > itemCenterPosY)
{ // when drag is gropped in second half the item is inserted bellow
itemIndex = itemIndex + 1;
}
dropIndex = itemIndex;
}
}
.... here create the item .....
if (dropIndex < 0)
ViewModel.Items.Add(item);
else
ViewModel.Items.Insert(dropIndex, item);
e.Handled = true;
}
So this solution works with my template DraggableListBoxView, I suppose the same solution must work with standard ListBoxView. Good Luck
You can do this. It takes a bit of work, but it can be done. There are a couple demos out there, here is one on CodeProject. This particular one is by the wpf master known as Josh Smith. It's probably not exactly what you are looking for, but it should be pretty darn close.