How to add more items to ItemsControl after binding - c#

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>

Related

Unable to bind collection to ComboBox in silverlight

I've a ObservableCollection, when I want to display this ObservableCollection with CheckBox I am simply binding the contents to CheckBox but when I want to display the same collection by using ComboBox I am unable to do that. Any suggestions?
XAML: Display collection using CheckBox --WORKS
<ItemsControl ItemsSource="{Binding Synonyms}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Margin="0,5,0,0" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--Display items in CheckBox-->
<CheckBox Content="{Binding Display}" Margin="10,0,0,0" /> // Display is the collection.
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Output:
XAML: Display collection using ComboBox --NOTHING OVER HERE
<ItemsControl ItemsSource="{Binding Synonyms}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Margin="0,5,0,0" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--Display items in ComboBox.-->
Approach - 1
<ComboBox>
<ComboBoxItem Content="{Binding Display}"/>
</ComboBox>
Approach - 2
<ComboBox ItemsSource="{Binding Path=Synonyms}" DisplayMemberPath="Display"/>
Approach - 3
<ComboBox >
<ComboBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Green" BorderThickness="1" Padding="5">
<TextBlock Text="{Binding Path=Display,StringFormat='Display: {0}'}" />
</Border>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Output:
I want to display items(one,two,three, etc.) inside oneComboBox with Select All option. I've tried several approaches but nothing. What am I missing here?
The checkbox is not designed to hold multiple items unlike the Combobox so implementing a system designed for the checkbox is not relevant for the combobox.
To solve your issue, remove the combobox from the ItemsControl and have it stand on its own:
<ComboBox ItemsSource="{Binding Synonyms}" DisplayMemberPath="Display"/>
Which tells the combobox to bind its ItemsSource to the data context which is unspecified, which is fine, so it then gets it's parent's data context. That process works its way up to each parent until it finds a bound data context (most likely the page's datacontext to a VM instance).
Assuming that the data context is valid at some point in the visual tree, it will bind to that instance and look for a property named Synonyms. From the Synonyms property it will use that as a list to display items.
To show (display) text in the combobox (instead defaulting to the item's ToString()) the combobox will show the string from the item's property Display.
Giving a list of items in one drop down.
The short answer, is you should use a ComboBox as the root element, not ItemsControl. CompboBox is just a specialized version of ItemsControl.
<ComboBox ItemsSource="{Binding Synonyms}" DisplayMemberPath="Display"/>
The longer answer.
ComboBox derives from ItemsControl, so you get all the features the base class, plus additional features.
ItemsControl (and its derived classes) provides a way of repeating a set of data in the UI. The DataTemplate is where you specify what UI you want for each "row" of data in the Synonyms source.
What you are doing is asking Silverlight to create a separate ComboBox for each underlying data row.
You can still use a DataTemplate within the ComboBox. Like this.
<ComboBox ItemsSource="{Binding Synonyms}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--Display items in CheckBox-->
<TextBox Text="{Binding Display}"
Margin="10,0,0,0"
FontWeight="Bold" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

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.

Optimal way to populate a large number of textboxes in MVVM

I'm currently creating a WPF application using MVVM. I have a large number of textboxes in a window (about 20) that need to be bound to specific elements in a list and need to be populated all at once. Normally I'd push them into an array and populate them that way but I can't do so without breaking the MVVM model. Is there a quick and efficient way I can do this while still adhering to MVVM?
You could bind your list to an ItemsControl and change it's item template to be a TextBox.
<ItemsControl ItemSource={Binding aList}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I don't see why strictly this would break MVVM, if instead of using an Array you used a List, put it in your ViewModel and then use indexed binding to bind to specific elements.
Something like:
<StackPanel>
<TextBox Text="{Binding MyViewModelList[0]}">
<TextBox Text="{Binding MyViewModelList[1]}">
<TextBox Text="{Binding MyViewModelList[2]}">
</StackPanel>
or if you want something more dynamic, instead of List, put an ObservableCollection in your VM, and bind to it in an ItemsControl with a DataTemplate.
<ItemsControl ItemsSource="{Binding Path=MyViewModelObsCol}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Syntax might not be 100% as I don't have an IDE to test, but something along these lines might be what you're after.
If you are trying to populate textbox on the bases of selection in list box try this
Another option is creating a COllection view source which i don't think you will require here

Silverlight - Binding with ObservableCollections

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>

Categories