WPF ListBox - Getting UIElement instead of of SelectedItem - wpf

I created a ListBox that has a DataTemplate as Itemtemplate. However, is there an easy way to access the generated UIElement instead of the SelectedItem in codebehind?
When I access SelectedItem, I just get the selected object from my
ItemsSource collection. Is there a way to access the UIElement (ie. the
element generated from the DataTemplate together with the bound object)?

You are looking for the ItemContainerGenerator property. Each ItemsSource has an ItemContainerGenerator instance. This class has the following method that might interest you: ContainerFromItem(object instance).
Once you have a handle to the ListBoxItem, you can go ahead and browse the logical and visual tree. Check out Logical Tree Helper and Visual Tree Helper.
Like Andy said in the comments, just because the item exists in your collection doesn't mean a container has been generated for it. Any kind of virtualizing panel scenario will raise this issue; UIElements will be reused across the different items. Be careful with that as well.

siz, Andy and Bodeaker are absolutely right.
Here is how I was able to retrieve the textbox of the listbox's selected item using its handle.
var container = listboxSaveList.ItemContainerGenerator.ContainerFromItem(listboxSaveList.SelectedItem) as FrameworkElement;
if (container != null)
{
ContentPresenter queueListBoxItemCP = VisualTreeWalker.FindVisualChild<ContentPresenter>(container);
if (queueListBoxItemCP == null)
return;
DataTemplate dataTemplate = queueListBoxItemCP.ContentTemplate;
TextBox tbxTitle = (TextBox)dataTemplate.FindName("tbxTitle", queueListBoxItemCP);
tbxTitle.Focus();
}
(Note: Here, VisualTreeWalker is my own wrapper over VisualTreeHelper with various useful functions exposed)

Related

TabControl - databinding TabItem order

I've got a datababound TabControl and would like to bind the index of each TabItem to a corresponding property in my view model. The ItemsSource is an ObservableCollection, and I'm using Bea Stollnitz's Drag/Drop functionality to provide tab control re-ordering.
My gut feeling is that it should be able to be handled in the data template for the tab item header, but I haven't been able to get it working.
Your TabControl.ItemsSource should be bound to your collection, so to re-arrange the order of tab items, simply re-arrange the collection.
I've worked with Bea's drag/drop code before to create a TabControl that allowed users to drag/drop the tab items, and I think most of what was needed is in the code she provides. On drop, it removes the dragged object from it's parent collection, and inserts it to its new location in the drop target collection, which in your case is the same collection.
Edit
Based on your comment below about updating your ViewModel with the Tab Index, try using the CollectionChanged event.
void MyCollection_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
foreach (var item in MyCollection)
item.TabIndex = MyCollection.IndexOf(item);
}

How to find and delete Binding from dynamically added and deleted UserControl

I have a set of UserControl, which share the same business object (ViewModel), but only
display data from this in different way.
I select an active UserControl via combobox. Old UserControl I delete from StackPanel and add new UserControl.
var uiElement = thisObject.EditorsContainer.Children.FirstOrDefault();
if (uiElement != null)
{
thisObject.EditorsContainer.Children.Remove(uiElement);
uiElement.Cast<UserControl>().ClearValue(DataContextProperty);
}
EditorsContainer is the StackPanel
It looks like a parent control keeps reference on the deleted control, because when I edit value in active control, the binding to binded property updates deleted control in memory.
As workaround I have implemented an interface IActiveAware with IsActive property, which is false when the logical life of user control has ended. On the bound property in ViewModel I check this property.

WPF Accessing Items inside Listbox which has a UserControl as ItemTemplate

I have Window that has a ListBox
ListBox(MyListBox) has a DataTable for its DataContext
ListBox's ItemSource is : {Binding}
Listbox has a UserControl(MyUserControl) as DataTemplate
UserControl has RadioButtons and TextBoxes (At first They're filled with values from DataTable and then user can change them)
Window has one Submit Button
What I want to do is, when user clicks the submit button
For each ListBox Item, get the values form UserControl's TextBoxes and RadioButtons.
I was using that method for this job :
foreach(var element in MyListBox.Items)
{
var border = MyListBox.ItemContainerGenerator.ContainerFromItem(element)as FrameworkElement;
MyUserControl currentControl = VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(myBorder,0) as Border,0)as ContentPresenter,0)as MyUserControl;
//And use currentControl
}
I realised nothing when using 3-5 items in Listbox. But when I used much more items, I saw that "var border" gets "null" after some elements looped in foreach function.
I found the reason here :
ListView.ItemContainerGenerator.ContainerFromItem(item) return null after 20 items
So what can I do now? I want to access all items and get their values sitting on user controls.
Thanks
You should use objects who implement INotifyPropertyChanged and bind an ObservableCollection of it to the ItemSource
And then you can get all the list of items.
Here some quick links from MSDN to get more informations
How to: Implement Property Change Notification
Binding Sources Overview
You should google for some tutorials about this.
Zied's post is a solution for this problem. But I did the following for my project:
I realised that there's no need to use UserControl as DataTemplate in my project. So I removed ListBox's DataTemplate.
I removed MyListBox.DataContext = myDataTable and used this:
foreach(DataRow dr in myDataTable.Rows)
{
MyUserControl muc = new MyUserControl(dr);
myListBox.Items.Add(muc);
}
I took DataRow in my UserControl's constructor and did what I want.
And at last I could access my UserControls in ListBox by using :
foreach(MyUserControl muc in
myListBox)
{
//do what you want
}
Easy huh? :)

WPF CollectionViewSource Multiple Views?

I've written a Custom WPF Control with search extension, let's name it MyControl.
The Control is a descendent of an ItemsControl class.
So I feed the the data source to it like this:
The control itself uses
protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
{
if (newValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += this.FilterPredicate;
}
if (oldValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);
view.Filter -= this.FilterPredicate;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
to filter the view of the source collection (thus displaying it in an inner ListBox).
Now suppose we have 10 of these MyControls defined in XAML with the same DynamicSource.
The problem is that if one of them applies the Filter on the source collection, it will affect all other instances too.
How would you change the Control to avoid this behaviour ?
In situations like this you would generally want to create a separate ICollectionView instance for each differently filtered usage of the collection. It's not a good idea to use a specific implementation of ICollectionView since it's possible for the CollectionView type needed to change if the ItemsSource is bound to a different type of collection. Using
ICollectionView filteredView = new CollectionViewSource { Source=newValue }.View;
will give you an ICollectionView that's the correct type automatically.
Unfortunately, what you may find in this case is that it is very difficult to apply a different collection to the ItemsPresenter of your custom control since all of that magic is done for you by the base ItemsControl class and relies on the ItemsSource/Items properties which it manages. This happens when using something similar to ItemsControl's default template.
If you are in fact using a separate ListBox control (and TemplateBinding all the ItemsSource properties if you need them) inside your ControlTemplate then you should be able to simply add a new ICollectionView DP (I'd recommend read-only) on your control to hold your filtered version of the collection and bind the template ListBox's ItemsSource to that new property.
The problem there is that CollectionViewSource.GetDefaultView(object) will always return the same ICollectionView instance for a given source, and this is what any ItemsControl extension will use when displaying that source.
You can get around this by creating a new instance of ICollectionView to be used by each control that you want to be able to independently filter the collection, and then explicitly binding the ItemsSource property of each control to that specific view. The type of ICollectionView needed would depend on your scenario, but ListCollectionView is generally appropriate.

C# WPF - Adding a child node to a selected node in treeview

In WPF treeview control, I need to add a child node to a parent node i select using mousedoubleclick event.
http://msdn.microsoft.com/en-us/library/system.windows.controls.treeview.selecteditem.aspx
I followed the step in the MSDN, but i get invalidCastException when i do this.
TreeViewItem newChild =
(TreeViewItem)treeView1.SelectedItem;
How can i solve this?
Thanks
SelectedItem returns the selected data item, not the visual representing it.
If you need to access the selected TreeViewItem, use the ItemContainerGenerator :
TreeViewItem item = treeView1.ItemContainerGenerator.ContainerFromItem(treeView1.SelectedItem) as TreeViewItem;
Not sure it works for nested items though... you might have to use the ItemContainerGenerator of the parent TreeViewItem, which wouldn't be very convenient
EDIT: just tested, indeed it only works for root nodes...
Anyway, the best way to add a node is to use bindings and HierarchicalDataTemplates. You just need to add the object to the data source, and the corresponding TreeViewItem will be added automatically (provided the containing collection implements INotifyCollectionChanged...)
What type of Items do you Add() to the Tree? The same type will be returned.
If it is mixed, use
TreeViewItem newChild = treeView1.SelectedItem as TreeViewItem;
if (newChild != null) { ... }

Resources