I'm working on a ListBox
that overrides its ItemsPanelTemplate
to use a Canvas
instead of a StackPanel
. ListBoxItems
have a DataTemplate
that uses a converter to define the look and position of each ListBoxItem
on the canvas. When I add an item to the collection that the ListBox
is bound to, I'd like to be able to add other UIElement
s to the canvas. Can I accomplish this outside of the ListBoxItem
's converter?
my resources section is like this:
<Style TargetType="ListBox">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas x:Key="cnvAwesome">
</Canvas>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding Path=Position.X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Position.Y}" />
</Style>
And a ListBox:
<ListBox x:Name="lstAwesome" ItemsSource="{Binding Source={StaticResource someCollection}}"/>
And the collection that the ListBox is bound to:
public ObservableCollection<SomeType> someCollection {get; set; }
So, if I have a method to add an item to the collection someCollection that the ListBox lstAwesome is bound to:
private void AddItemToDataboundCollection(SomeType someItem)
{
someCollection.Add(someItem)
// I'd like to add a UIElement to the canvas that the ListBox uses to layout items here.
}
So, can I add UIElements to a canvas being used for an ItemsPanel by a databound listbox? Programmatically, when I'm adding items to the collection that that ListBox is bound to?
I'd greatly appreciate any input!
Best Solution
I think that you have everything in place in your question to add items to the
Canvas
of yourListBox
. I just put together a simple WPF test app using the code in your question, and it just worked. Whenever I added items to theObservableCollection
that is bound to theItemsSource
of theListBox
, WPF would create aListBoxItem
for that object, and based on the style of theListBoxItem
, the item would be drawn at the correct location by theCanvas
.Were you not able to get your code above working? If that is the case, what error did you get, or what did not work as you expected it to?
Edit: After reading the question again, I think that the confusion is due to a misunderstanding of how WPF binding works. When you bind a collection to a
ListBox
'sItemsSource
, all that you need to do is add items to that collection (assuming that the collection implementsINotifyCollectionChanged
, whichObservableCollection
does). WPF subscribes to the events provided by that interface, and creates/destroysListBoxItems
based on the changes made to the list.Edit 2: While it would be better to use a
DataTemplate
for this, so that the line will automatically be added when an item is added to the collection, I'm not sure how you could get the position of the previously added item so that the line would start at the right location.One option that could work would be to subclass
Canvas
, and set that as theItemsPanelTemplate
for theListBox
. I believe that it has a function that you can override which gets called whenever a control is added to it. In that function, you could get the position of the previously added control (which would be cached in yourCanvas
subclass), and then add to line control to the controls of theCanvas
directly. This does assume that the position of controls in theCanvas
can't change. If they can, you'll probably have to subscribe to the object'sPropertyChanged
event and update the lines accordingly.Edit 3: Since binding doesn't seem like an option due to your requirements, the least bad option is to just add the children directly. You can do that by searching through the visual tree using VisualTreeHelper.GetChild():
Use it like this:
This code recursively searches through the visual tree of
parent
for a control of the specified type and name. It would be better to useDependencyObject
overFrameworkElement
here (since that is what is returned byVisualTreeHelper.GetChild()
), howeverName
is only defined onFrameworkElement
.If you want to get fancy, you can even make this an extension method.