Silverlight - Binding with ObservableCollections - c#

This is no doubt a newbish question, but I have looked for an answer to no avail. My setup is simple: I have a ListBox control defined in XAML and an ObservableCollection<MyClass> in the same class. I am binding the ObservableCollection<MyClass> to the ListBox.
Within the hierarchy of this ListBox in XAML, I want to bind to a given MyClass object, not to a child property of the MyClass object.
To clarify, I have XAML that looks like the following (I bind the ObservableCollection in code):
<ListBox x:Name="MyListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<MyControls:SpecialControl MyClassObj="{Binding !!!}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Surely there is a way to get at the object of an ObservableCollection rather than being forced to bind to one of its child properties.

You do not have to specify a Path if you want to use the bound object itself:
<ListBox x:Name="MyListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<MyControls:SpecialControl MyClassObj="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
BTW: Instead of your custom property, you can use the DataContext property of your control to bind the control to the object:
<ListBox x:Name="MyListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<MyControls:SpecialControl DataContext="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

As well as specifying the binding path in your XAML you have to bind your collection to the ListBox.
C#
ObservableCollection<MyClass> myCollection = new ObservableCollection<MyClass>();
MyListBox.DataContext = myCollection;
The XAML you have used won't be particularly useful unless you have overriden the ToString method on MyClass. Even though you say you're not are you sure it's not a property of MyClass that you want to bind to? I can't see why you'd want to bind directly to a collection object.
XAML
<ListBox x:Name="MyListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<MyControls:SpecialControl MyClassObj="{Binding Path=MyClassProperty}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Related

How to bind to a source inside a ListBox different from the ItemsSource already specified

I have a ListBox inside a HubSection, whose Items are bound to a class "players" added to my DefaulViewModel via code behind.
First I simply put a TextBox bound to the property "PlayerName" of my class "players".
Now I would like to add a ComboBox with some items that are NOT part of the class players.
Is it possible ? I thought that definind an ItemsSource in the ComboBox would sort of override the ItemsSource of the ListBox, but nothing displays.
The DataContext of the whole page is defined like so:
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
Then the HubSection is like so:
<HubSection x:Name="HubSec1">
<DataTemplate>
<ListBox x:Name="ListBox1" ItemsSource="{Binding players}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Path=PlayerName, Mode=TwoWay}"/>
<ComboBox ItemsSource="{Binding Path=ListOfElements}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</HubSection>
If I define the ComboBox in the same way but outside the ListBox, it will display the string elements of "ListOfElements" properly.
But in this ListBox, the ComboBox is empty. So my guess is that having defined an ItemsSource for the ListBox, it is not possible to override it.
I have tried to define a DataTemplate but was not successful doing so, but it might be the good solution (and I did not proceed properly)
What am I missing ?
Edit :
The ComboBox items is an ObservableCollection. It is not part of the "players" class.
Here is how I added these elements to the DefaultViewModel
DefaultViewModel.Add("players", players);
DefaultViewModel.Add("MyItemsList", ListOfElements);
You can walk up the visual tree and bind to an ancestors datacontext:
{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
EX:
{Binding Path=ListOfItems, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}
that should give you the datacontext that the listbox has, so assuming your ListOfItems exists in that data context.
Or you can name your control, and then bind to its datacontext by element name:
{Binding ElementName=mySourceElement,Path=ListOfItems}
It can be a little bit tricky to create a good working binding in Windows Apps. A widely used work around is to use the Tag property.
<ListBox x:Name="ListBox1" ItemsSource="{Binding players}" Margin="0,184,0,0" Tag="{Binding Path=ListOfElements}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Path=PlayerName, Mode=TwoWay}"/>
<TextBox Text="{Binding Path=Tag, ElementName=ListBox1}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
A binding to an element wirh the specific name will work always. And the ListOfElements should be in the scope of the ListBox so you can use the Tag property as a proxy. If you need to bind more than one property, you can also use dummy XAML elements:
<Border Tag="{Binding ...}" Name="dummy1"/>

Hide last item in a ItemsControl

I've a ItemsControl that I bind to my viewmodel, but inside the datatemplate I also have an image. I want that image to be visible as long as it's not the last item in the list, then it should be hidden (it's an arrow that point down to the next control).
The xaml look like this:
<ItemsControl ItemsSource="{Binding PageContainers}" x:Name="Items">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<controls:DesignControl DataContext="{Binding}" MouseDown="UIElement_OnMouseDown" MouseUp="UIElement_OnMouseUp" MouseMove="UIElement_OnMouseMove"/>
<Image Source="/Resources/Images/arrow.png" Height="16" Width="16" Margin="0,10,0,0"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
So is there any easy to check if the Image/Stackpanel is last in the list? I guess I could subscribe to some event and do it in the code behind, but I guess it's cleaner if I could do it inside the xaml.
You're binding to PageContainers which I assume to be a collection. Can the type of that collection be extended to include an IsLast property?
If it can, you can bind the visibility to that.

How to add more items to ItemsControl after binding

In my C# windows phone app, I create a binding to bind a list of string to ItemsControl.
// MyCollections is a List<string>
<ItemsControl x:Name="ContentRoot" ItemsSource="{Binding MyCollections}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding }" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It works. But My question is how can I add my own item (e.g. 'Click to add more') to this ItemsControl after it is binded?
There are two answers to this question:
Use an ObservableCollection instead of a List, as it will notify the UI when items are added/removed from it. Then you just add your new item to the list in the view model.
Use a CompositeCollection so you can have the "additional" item without modifying the actual collection.
Normally you would do 1, but since you want a "Click to add more" type of option, CompositeCollection is probably the way to go.
Since you metioned windows phone (but tagged WPF) you may want to look at this post for how to write your own CompositeCollection object: how to do a CompositeCollection in WP8?
Use CompositeCollection to add additional items in your XAML. This should work:
<StackPanel x:Name="stackPanel">
<StackPanel.Resources>
<CompositeCollection x:Key="myCollection">
<CollectionContainer Collection="{Binding DataContext.MyCollections,
Source={x:Reference stackPanel}}"/>
<ContentControl Content="Click to add more"/>
</CompositeCollection>
</StackPanel.Resources>
<ItemsControl x:Name="ContentRoot"
ItemsSource="{StaticResource myCollection}"/>
</StackPanel>

WPF: Accessing two DataContexts in the same control

I am using an MVVM approach, and I have an object from my ViewModel called DatabasesSubFrame which is DataTemplated to show a ListBox. I want to display a Button below the ListBox, which binds to both the currently SelectedItem, and a property on the DatabasesSubFrame object which is being DataTemplated.
I know how to refer to the currently selected item, by setting the DataContext on a shared ancestor with the ListBox and use {Binding /}. In this example the shared ancestor is a StackPanel. And if the DataContext wasn't explicitly set there I could easily bind to a property on the DatabasesSubFrame object by just doing {Binding SomeProperty}. However, if I do {Binding SomeProperty} within the explicitly set DataContext, it refers to the wrong DataContext.
How do I access the "original" DataContext here? I tried messing with RelativeSources and TemplatedParents but couldn't figure out how to fit them in.
<DataTemplate DataType="{x:Type VM:DatabasesSubFrame}">
<StackPanel DataContext="{Binding Databases}" >
<ListBox Name="DbInfoBox"
ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding ShortName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- Problem: The Command and V:CreateCommandBinding.Command are set incorrectly here. How do I access OpenDbCommand from the top-level DataTemplate's DataContext? -->
<Button Content="Open Database"
CommandParameter="{Binding /}"
Command="{Binding ???, Path=OpenDbCommand.Command}"
V:CreateCommandBinding.Command="{Binding ???, Path=DataContext.OpenDbCommand}"/>
</StackPanel>
</DataTemplate>
I think this question will help you to find the answer to yours. Another trick is to set the Name of the Window to something like "Root". You can then get at the window's original datacontext by using:
{Binding ElementName=Root, Path=DataContext.MyViewModelsProperty}

Display properties of the ListBox.ItemsSource

I am new to WPF. I have a ListBox that has its ItemSource set to a instance of WorkItemCollection. (A collection of WorkItem objects.)
When the list is displayed it only displays the type of each object (Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem). Is there a way to make the list display WorkItem.Title?
You have two options.
The simplest method is to set the DisplayMemberPath property of your ListBox to "Title".
If you want to set not only what gets displayed, but the type of control that is used to display it, then you would set the ListBox's ItemTemplate.
For what your goal is, I would recommend the first option.
You can set a DataTemplate on the ItemTemplate property of the ListBox:
<ListBox ItemSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate DataType="tfs:WorkItem">
<StackPanel>
<TextBlock Text="{Binding Title}" />
<!-- Others -->
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Categories