How to get item from item container in WPF? - c#

Im currently trying to get drag/drop within a treeview working(using databinding and HierarchicalDataTemplate), and have the dragging working, but im running into a problem when trying to get the drop working, since i need to get the data item being dropped on and add it to its item collection of children nodes.
//treeitem is the name of my item data class
private void TreeViewItem_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent("DragableTreeViewItem"))
{
//the data of the treeitem i need to duplicate, provided by the drag operation
TreeItem data = e.Data.GetData("DragableTreeViewItem") as TreeItem;
TreeViewItem tvi = sender as TreeViewItem;
//here im getting the treeviewitem, but i need the treeitem
}
}
My best idea on how to get around this was to assign the treeitem as the tag of the treeviewitem on initialization/loading of the tree, and to do so i need to get the itemcontainer of the item, which i already have a working function to get that which i use when initiating the DoDrag()
//returns the item container of the parent of the TreeItem given
private TreeViewItem GetParentContainerFromItem(TreeItem ti)
{
List<TreeItem> GetOrderedParents(TreeItem item)
{
TreeItem currentParent = item;
List<TreeItem> items = new List<TreeItem>();
int i = 0;
do
{
if (currentParent.parentItem != null)
{
items.Insert(0, currentParent);
currentParent = currentParent.parentItem;
}
else
{
items.Insert(0, currentParent);
i++;
return items;
}
} while (i == 0);
return null;
}
//the local tree in a list, ordered from the original item (in this case "ti") at 0, down to the root at the end of the list
List<TreeItem> LocalHierarchy = GetOrderedParents(ti);
if (LocalHierarchy != null)
{
//print out the names of each treeitem in it, in order from the root down
string hierarchyString = "";
foreach (TreeItem t in LocalHierarchy)
{
if (hierarchyString == "")
{
hierarchyString = t.Title;
}
else
{
hierarchyString = (hierarchyString + ", " + t.Title);
}
}
System.Console.WriteLine(hierarchyString);
TreeViewItem localCurrentParent = null;
TreeViewItem finalContainer = null;
//walk down the tree in order to get the container of the parent
foreach (TreeItem t in LocalHierarchy)
{
//if the parent of the item given is a root node, meaning we can return its container
if (LocalHierarchy.IndexOf(t) == 0 && (LocalHierarchy.IndexOf(t) == (LocalHierarchy.Count - 2)))
{
finalContainer = treeView.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
break;
}
else
//if we're at a root node
if (LocalHierarchy.IndexOf(t) == 0)
{
localCurrentParent = treeView.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
}
else
//if we're at the 2nd to last, AKA the parent of the item given
if (LocalHierarchy.IndexOf(t) == (LocalHierarchy.Count - 2))
{
finalContainer = localCurrentParent.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
break;
}
else
{
localCurrentParent = localCurrentParent.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
}
}
if (finalContainer == null)
{
System.Console.WriteLine("Final container is null");
}
return finalContainer;
}
else
{
System.Console.WriteLine("ERROR: LocalHierarchy is null");
return null;
}
}
This seems to work perfectly when using to start the DoDrag()
private void DoDrag()
{
if(selectedItem != null)
{
TreeItem t = selectedItem;
TreeViewItem tvi = null;
if (t.parentItem == null)
{
//it has no parent, and is a root node
tvi = treeView.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
}
else
{
//it has a parent, and i can get the container for the parent
tvi = GetParentContainerFromItem(t).ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
}
DragDrop.DoDragDrop(tvi, new DataObject("DragableTreeViewItem", t, true), DragDropEffects.Copy);
dragNeeded = false;
}
else if(selectedItem == null)
{
Console.WriteLine("Selected item was null; cant drag");
}
}
But when i try to use it in my function to assign container tags it says that Container was null and that GetParentContainerFromItem returned null, yet my function does not log that it returned null
private void AssignContainerTag(TreeItem t)
{
TreeViewItem Container = null;
if (t.parentItem == null)
{
//its a root node
Container = treeView.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
Container.Tag = t;
}
else
{
//it is not a root node
Container = GetParentContainerFromItem(t).ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
Container.Tag = t;
}
}
Ive spent days stumped on why this does not seem to work, so if someone could give me some pointers on what im doing wrong or another way i could get the item from its container it would be a lifesaver. Also please excuse if this is poorly written, i am exhausted and about to go to sleep.

Your question is a little bit confusing. But it looks like you are trying to get the data item of the TreeViewItem which is the drop target of the drag&drop operation.
This is pretty simple. All you need to know is that if the item container is auto-generated via data binding (ItemsControl.ItemsSource) the DataContext of the container is the data item itself.
This applies to all item containers of an ItemsControl (e.g., ComboBoxItem, ListBoxItem, ListViewItem).
So TreeViewItem.DataContext references the underlying TreeItem instance that is wrapped by the TreeViewItem:
private void TreeViewItem_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent("DragableTreeViewItem"))
{
var sourceItem = e.Data.GetData("DragableTreeViewItem") as TreeItem;
var dropTargetItemContainer = sender as TreeViewItem;
var dropTargetItem = targetItemContainer.DataContext as TreeItem;
}
}
Remark
It looks like you are using the ItemContainerGenerator wrong. TreeView.ItemContainerGenerator will only handle top level items (i.e. child items). But as a tree node can have child nodes, each TreeViewItem is itself an ItemsControl as it contains an ItemsPresenter to display child items.
Therefore you have to use the appropriate ItemContainerGenerator to retrieve the child container or ItemContainerGenerator will return null.
For top-level items use TreeView.ItemContainerGenerator.
For child items use the parent's TreeViewItem.ItemContainerGenerator.
Also, in case of UI virtualization is enabled not all containers are generated when the TreeView is loaded. They are generated when need e.g., for display. Those containers (the TreeViewItem) are also shared to save resources. So once you set the TreeViewItem.Tag property its value might get lost as a new TreeViewItem instance is generated later to wrap the data item.
So you start at the root node and get its generated container. Now perform a tree search by traversing the TreeViewItems using a specific algorithm until you found the node where the DataContext equals the data item you are looking for and e.g., modify the Tag property.
You access the children of e.g. treeViewItemA by referencing the treeViewItemA.Items property and get their containers by calling treeVieItemA.ItemContainerGenerator.ContainerFromItem method for each child:
Example
public static class MyExtensions
{
// Get item container of item from TreeView, TreeViewItem, ListView or any ItemsControl
public static bool TryGetContainerOfChildItem<TItemContainer>(this ItemsControl itemsControl, object item, out TItemContainer itemContainer) where TItemContainer : DependencyObject
{
itemContainer = null;
foreach (object childItem in itemsControl.Items)
{
if (childItem == item)
{
itemContainer = (TItemContainer) itemsControl.ItemContainerGenerator.ContainerFromItem(item);
return true;
}
DependencyObject childItemContainer = itemsControl.ItemContainerGenerator.ContainerFromItem(childItem);
if (childItemContainer is ItemsControl childItemsControl && childItemsControl.TryGetContainerOfChildItem(item, out itemContainer))
{
return true;
}
}
return false;
}
}
Usage
// Search whole TreeView
if (treeView.TryGetContainerOfChildItem(item, out TreeViewItem itemContainer)
{
...
}
// Search from a specific parent TreeViewItem node
if (treeViewItem.TryGetContainerOfChildItem(item, out TreeViewItem itemContainer)
{
...
}

Related

Find all TextBox controls in UWP Page

I need to find all TextBox(es) that are on a UWP Page but having no luck. I thought it would be a simple foreach on Page.Controls but this does not exist.
Using DEBUG I am able to see, for example, a Grid. But I have to first cast the Page.Content to Grid before I can see the Children collection. I do not want to do this as it may not be a Grid at the root of the page.
Thank you in advance.
UPDATE: This is not the same as 'Find all controls in WPF Window by type'. That is WPF. This is UWP. They are different.
You're almost there! Cast the Page.Content to UIElementCollection, that way you can get the Children collection and be generic.
You'll have to make your method recurse and look either for Content property if element is a UIElement or Children if element is UIElementCollection.
Here's an example:
void FindTextBoxex(object uiElement, IList<TextBox> foundOnes)
{
if (uiElement is TextBox)
{
foundOnes.Add((TextBox)uiElement);
}
else if (uiElement is Panel)
{
var uiElementAsCollection = (Panel)uiElement;
foreach (var element in uiElementAsCollection.Children)
{
FindTextBoxex(element, foundOnes);
}
}
else if (uiElement is UserControl)
{
var uiElementAsUserControl = (UserControl)uiElement;
FindTextBoxex(uiElementAsUserControl.Content, foundOnes);
}
else if (uiElement is ContentControl)
{
var uiElementAsContentControl = (ContentControl)uiElement;
FindTextBoxex(uiElementAsContentControl.Content, foundOnes);
}
else if (uiElement is Decorator)
{
var uiElementAsBorder = (Decorator)uiElement;
FindTextBoxex(uiElementAsBorder.Child, foundOnes);
}
}
Then you call that method with:
var tb = new List<TextBox>();
FindTextBoxex(this, tb);
// now you got your textboxes in tb!
You can also use the following generic method from the VisualTreeHelper documentation to get all your child controls of a given type:
internal static void FindChildren<T>(List<T> results, DependencyObject startNode)
where T : DependencyObject
{
int count = VisualTreeHelper.GetChildrenCount(startNode);
for (int i = 0; i < count; i++)
{
DependencyObject current = VisualTreeHelper.GetChild(startNode, i);
if ((current.GetType()).Equals(typeof(T)) || (current.GetType().GetTypeInfo().IsSubclassOf(typeof(T))))
{
T asType = (T)current;
results.Add(asType);
}
FindChildren<T>(results, current);
}
}
It basically recursively get the children for the current item and add any item matching the requested type to the provided list.
Then, you just have to do the following somewhere to get your elements:
var allTextBoxes = new List<TextBox>();
FindChildren(allTextBoxes, this);
To my mind, you could do it in the same way as in WPF. Because UWP uses mostly the same XAML that WPF.
So, please check out answer for the same question about WPF

Newly Added Item is added to the collection but not in the treeViewItems

I have a TreeView. I have a contextMenu defined for the TreeViewItems.
When I right click on any TreeViewItem, I get a ContextMenu which has an option to add a new TreeViewItem under the Item on which user Right-clicked.
Here is my code:
private void AddContextMenu_Click(object sender, RoutedEventArgs e)
{
TreeViewItem treeViewItem = null;
foreach (TreeViewItem tvItem in StaticHelpers.FindVisualChildren<TreeViewItem>(cityTreeView))
{
if (tvItem.Header == ServiceLocator.Instance.SelectedCity)
{
treeViewItem = tvItem;
break;
}
}
AddNewCity(ServiceLocator.Instance.SelectedCity);
treeViewItem.IsExpanded = true;
City newlyAddedCity = getNewlyAddedCity(ServiceLocator.Instance.Cities);
foreach (TreeViewItem tvItem in StaticHelpers.FindVisualChildren<TreeViewItem>(cityTreeView))
{
if (tvItem.Header == newlyAddedCity)
{
treeViewItem = tvItem;
break;
}
}
this.RenameContextMenu_Click(treeViewItem, e);
}
Now in the code above if I keep a breakpoint on last foreach loop, I can see that it loops through all the parent Items and those child Items whose parent's IsExpanded is set to true.
But I can also notice that when it loops through all the items, it does not get newlyAddedCity in the collection and as a result in second for loop tvItem.Header == newlyAddedCity never becomes true.
At last when the task is finished by computer I can see the newlyAddedCity in TreeView.
Update:
Here is my AddNewCity Method:
public static void AddNewCity(City parentCity)
{
City lastAddedCity = ServiceLocator.Instance.Cities.Where(x => x.IsLastAdded == true).Select(x => x).FirstOrDefault();
lastAddedCity.IsLastAdded = false;
City newCity = new City()
{
Id = lastAddedCity.Id + 1,
IsLastAdded = true,
Name = "New City"
};
ServiceLocator.Instance.Cities.Where(x => x.Id == parentCity.Id).FirstOrDefault().Children.Add(newCity);
CityMethods.SaveNew(parentCity.Id, "");
}
TreeViewItem is an UI container which gets generated when it comes in View. In code you set IsExpanded to True but UI dispatcher didn't get time to do that processing for generating container because it's busy in executing click handler.
In case you interested in seeing item generated, you need to give UI thread some time to process it before running last foreach loop.
Invoke empty delegate on UI dispatcher with priority Render so that all delegates queued on UI dispatcher with priority higher or equal to Render gets time to execute. (UI refresh on priority Render)
Dispatcher.Invoke((Action)(() => { }), DispatcherPriority.Render);
Just place this code before your last foreach loop and you will see breakpoint gets hit.
Dispatcher.Invoke((Action)(() => { }), DispatcherPriority.Render);
foreach (TreeViewItem tvItem in StaticHelpers.FindVisualChildren<TreeViewItem>(cityTreeView))
{
if (tvItem.Header == newlyAddedCity)
{
treeViewItem = tvItem;
break;
}
}
the mvvm way would be to add a new item to the underlying collection and not to the treeview.
public OberservableCollection<City> MyCollection {get;set;}
this.MyCollection.Add(getNewlyAddedCity(ServiceLocator.Instance.Cities));
xaml
<TreeView ItemsSource="{Binding MyCollection}"/>

how to assign image for Parent node and child nodes in treeview from assigned imagelist in c#?

I have a TreeView and an associated ImageList. What are the steps to add images to the Parent and child nodes ?
All the nodes are being added from the code. Nothing is done from the Design.
public void fill_tree()
{
host_listbox_new.Items.Clear();
foreach (KeyValuePair<string, host_config> hlitem in host_list)
{
string sitem = hlitem.Key;
if (host_list[sitem].sessionOptions == null)
host_list[sitem].sessionOptions = new SessionOptions();
host_list[sitem].sessionOptions.Protocol = Protocol.Sftp;
host_list[sitem].sessionOptions.HostName = host_list[sitem].ip;
host_list[sitem].sessionOptions.UserName = host_list[sitem].username;
host_list[sitem].sessionOptions.Password = host_list[sitem].password;
host_list[sitem].sessionOptions.PortNumber = Convert.ToInt32(host_list[sitem].port);
//host_list[sitem].sessionOptions.SshHostKeyFingerprint = host_list[sitem].rsa;
if (treeView1.SelectedNode != null)
{
treeView1.SelectedNode.Nodes.Add(hlitem.Key.ToString());
}
else
{
treeView1.Nodes[0].Nodes.Add(hlitem.Key.ToString());
}
}
}
private void Parent_Load(object sender, EventArgs e)
{
read_process_config();
read_host_config();
host_listbox.Items.Clear();
treeView1.BeginUpdate();
treeView1.Nodes.Add("Servers");
fill_tree();
treeView1.EndUpdate();
treeView1.ExpandAll();
connect_server_bttn.Enabled = false;
}
i want to add items i.e child nodes to Server Parent node each of them having one image before them ( green image if hlitem.Value.connected is true. red image if hlitem.Value.connected is false)
But i have no idea about treeview or imagelist.
Can anyone help me about the whole thing?
The Add command returns a reference to the new Node. You can use it to style the Node.
Change your code to this:
if (treeView1.SelectedNode != null)
{
TreeNode tn =treeView1.SelectedNode.Nodes.Add(hlitem.Key.ToString());
tn.ImageIndex = yourIndex;
}
else
{
TreeNode tn =treeView1.Nodes[0].Nodes.Add(hlitem.Key.ToString());
tn.ImageIndex = yourIndex;
}
Or whatever logic you need to set the index.
If you need the parent node's index you could write:
tn.ImageIndex = tn.Parent.ImageIndex;
You may also want ot check out the other formats of the Add method. Some let you include the ImageIndex directly. You can also include the SelectedIndex; especially if you don't want that you should include it to prevent the Tree using its default SelectedIndex!
This will set the node to show the 2nd image, whether selected or not:
TreeNode tn =treeView1.Nodes[0].Nodes.Add(sitem, sitem, 1,1 );
Since you can't set a property of an object before you have created it, you can't set the Child nodes when you create the parent node. Instead you can use a simple function to do the changes:
void copyImgIndexToChildren(TreeNode tn)
{
if (tn.Nodes.Count > 0)
foreach (TreeNode cn in tn.Nodes) cn.ImageIndex = tn.ImageIndex;
}
void copyImgIndexToAllChildren(TreeNode tn)
{
if (tn.Nodes.Count > 0)
foreach (TreeNode cn in tn.Nodes)
{
cn.ImageIndex = tn.ImageIndex;
copyImgIndexToAllChildren(cn);
}
}
The first method changes the direct ChildNodes only , the 2nd recursively changes all levels below the starting node.
BTW: Is there a reason to use hlitem.Key.ToString() in your code instead of sitem?

How to highlight the first inserted node in a tree view?

I have an application with two forms. The first form is used to create TreeView nodes programmatically, and the second form has the actual TreeView. When the application loads I create two root nodes in the TreeView.
My problem is when I create my first sub-node for either of the root nodes, it is not highlighted. I give the Form and the TreeView focus, and also disabled the HideSelection property for the TreeView.
Once I add a another sub-node to either of the root nodes is when the inserted node becomes highlighted. I want each inserted node to be highlighted once it has been inserted, but that only works after the first one has been inserted.
Example Code:
m_ObjectAnimationForm.tr_vw_ANIMATION_OBJECT_LIST.SelectedNode = m_ObjectAnimationForm.tr_vw_ANIMATION_OBJECT_LIST.Nodes["OBJECTS_ROOT"].Nodes.Add(NewObject.ID, NewObject.ID);
I create a new tree node using the ID of the object for the 'KEY' and the string of the node, then that function returns the newly created tree node, making it the selected node in the tree.
After that code I call:
m_ObjectAnimationForm.tr_vw_ANIMATION_OBJECT_LIST.ExpandAll();
m_ObjectAnimationForm.tr_vw_ANIMATION_OBJECT_LIST.Focus();
I have a slightly different setup, in which I control the highlighted item in the TreeView, by way of selecting an item in a datagridview. It is not the most elegant methodology, but it works.
In Summary:
1.) Get Index from Source TreeView, Other Control, or elsewhere
2.) Expand all Nodes in Target TreeView
3.) Iterate through Tree Nodes in Target, until Index is Reached
4.) Set TreeView.SelectedNode = "the node that was found"
5.) Set Focus on TreeView
private void selectTreeViewItem(int dataGridViewRowIndex)
{
expandAllTreeViewNodes();
setTreeViewItem(dataGridViewRowIndex);
}
private void setTreeViewItem(int dataGridViewRowIndex)
{
int iterator = 0;
TreeNode tempNode = testStepTreeView.Nodes[iterator];
//don't need to actually return the integer...
iterator = findNode(tempNode, dataGridViewRowIndex, iterator);
testStepTreeView.Focus();
nodeFound = false;
}
private void expandAllTreeViewNodes()
{
if (testStepTreeView.Nodes.Count != 0)
{
foreach (TreeNode x in testStepTreeView.Nodes)
{
expandNode(x);
}
}
}
private void expandNode(TreeNode x)
{
if (x.IsExpanded == false)
{
x.Expand();
}
if (x.Nodes.Count > 0)
{
foreach (TreeNode y in x.Nodes)
{
expandNode(y);
}
}
}
private int findNode(TreeNode tempNode, int dataGridViewRowIndex, int iterator)
{
if (iterator > dataGridViewRowIndex)
{
return iterator;
}
if (iterator == dataGridViewRowIndex)
{
testStepTreeView.SelectedNode = tempNode;
nodeFound = true;
return iterator;
}
if (tempNode.Nodes.Count != 0)
{
iterator++;
if (iterator > dataGridViewRowIndex)
{
return iterator;
}
if (nodeFound == false)
{
iterator = findNode(tempNode.Nodes[0], dataGridViewRowIndex, iterator);
}
}
if (tempNode.NextNode != null)
{
iterator++;
if (iterator > dataGridViewRowIndex)
{
return iterator;
}
if (nodeFound == false)
{
iterator = findNode(tempNode.NextNode, dataGridViewRowIndex, iterator);
}
}
return iterator;
}
try using Node.Select(), it will select the node and highlight it too.
focus will not work here.

TreeViewItem and null value

Not sure it is me after a long day of work ... or something is different in WPF type trees. I'm adding data to a tree and then when it is a parent node need to add its children but SelectedItem is always null!
any comment or direction would be helpful.
Thanks.
XAML:
<TreeView x:Name="TreeView1" Grid.Row="0">
</TreeView>
Code:
TreeView myTree = FindChild<TreeView>(Application.Current.MainWindow, "TreeView1");
myTree.Items.Add(ObjEmployee.Tag);
TreeViewItem tvi = (TreeViewItem) myTree.SelectedItem;
//my assumption was that when an item is added to tree most recent used node will be selected but it seems something is avoiding it or maybe works in a different way.
You should post some code snippet .. I do not understand what you need.
If you are using Hierarchical Template ( http://msdn.microsoft.com/en-us/library/ms742521.aspx )
The object of the SelectIedtem will be one of the data you put in ItemsSource
Otherwise, the "SelectedItem" can be any type of visual component that you put inside the treeview.
var treeItem = myTree.SelectedItem;
if (treeItem != null)
treeitem.GetType().Name;
Normally using TreeViewItem inside the treeview, all objects are in treeViewItem.Items
(Edit) You can try this:
var result = FindVisualChildren<TreeViewItem>(treeView);
foreach (var item in result)
{
if (item.IsSelected)
{
....
}
}
and
public static IEnumerable<T> FindVisualChildren<T>(FrameworkElement depObj) where T : FrameworkElement
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
FrameworkElement child = (FrameworkElement)VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}

Categories

Resources