WPF ListBox Multi-Select binding - c#

I have two listBoxes one on the left and one on the right. When I select a 'contactList' item on the left listBox the 'label' information should be displayed on the right listBox and this part works fine. The problem I am having is to do with multi-select because at the moment it will only display the information from one selection. I changed Selection mode in my XAML to multi-select but that did not seem to work. Would appreciate any assistance. Thanks.
XAML
<Grid x:Name="LayoutRoot" Background="#FFCBD5E6">
<ListBox x:Name="contactsList" SelectionMode="Multiple" Margin="7,8,0,7" ItemsSource="{Binding ContactLists, Mode=Default}" ItemTemplate="{DynamicResource ContactsTemplate}" HorizontalAlignment="Left" Width="254" SelectionChanged="contactsList_SelectionChanged"/>
<ListBox x:Name="tagsList" Margin="293,8,8,8" ItemsSource="{Binding AggLabels, Mode=Default}" ItemTemplate="{StaticResource TagsTemplate}" Style="{StaticResource tagsStyle}" />
</Grid>
Code
private void contactsList_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (contactsList.SelectedItems.Count > 0)
{
CollectionViewGroup collectionView = contactsList.SelectedItems[0] as CollectionViewGroup;
ContactList selectedContact = contactsList.SelectedItems[0] as ContactList;
ObservableCollection<AggregatedLabel> labelList = new ObservableCollection<AggregatedLabel>();
foreach (ContactList contactList in collectionView.Items)
{
foreach (AggregatedLabel aggLabel in contactList.AggLabels)
{
labelList.Add(aggLabel);
tagsList.ItemsSource = labelList;
}
}
}
}

I think everyone is confused about this part
CollectionViewGroup collectionView = contactsList.SelectedItems[0] as CollectionViewGroup;
ContactList selectedContact = contactsList.SelectedItems[0] as ContactList;
you're only looking at the first selected item. (SelectedItems[0]), but treating it as one thing or another?
you probably need something like
// only create the list once, outside all the loops
ObservableCollection<AggregatedLabel> labelList = new ObservableCollection<AggregatedLabel>();
foreach (var selected in contactsList.SelectedItems)
{
// pretty much your existing code here, referencing selected instead of SelectedItems[0]
}
// only set the list once, outside all the loops
tagsList.ItemsSource = labelList;
ideally, you wouldn't be setting the items source on the tagsList, you'd have that bound to a collection already, and you'd just be replacing the contents in this method. (just one call to clear the collection at the top, and no call to set ItemsSource, since it would have already been bound)

I don't really get what you are doing there at all with all that code but how you normally approach the kind of scenario you described is by binding the second ListBox directly to the first one, should look something like this:
<ListBox Name="ListBox1" ItemsSouce="{Binding SomeOriginalSource}" .../>
<ListBox ItemsSouce="{Binding ElementName=ListBox1, Path=SelectedItems}".../>
Edit: You then can either use a DataTemplate which enumerates the internal collections (which for example could cause you to have a ListBox containing other ListBoxes), or you add a converter to the the binding which merges the internal collections into a single collection like John Gardner noted.

Related

How to dynamically show ListBoxes in two synchronized boxes

I'm currently making a transferring data from one listbox to another.
With WPF I have:
<Grid>
<ListBox Margin="10,29,194,301" Name="LeftListBox"/>
<ListBox Margin="0,29,16,301" Name="RightListBox" HorizontalAlignment="Right" Width="173" />
<Button Name="AddButton" Height="23" Margin="34,135,227,0" VerticalAlignment="Top"
Click="AddButton_Click">Add >></Button>
<Button Name="RemoveButton" Margin="227,135,34,264"
Click="RemoveButton_Click"><< Remove</Button>
</Grid>
For my C# code, I created two methods that loads the left box's elements by using an array of Strings and the right one's.
Now My issue is that I want an element of the left box to be placed into the right box after the last element of the right box's list. So when I click on add, it should execute this method:
private void AddButton_Click(object sender, RoutedEventArgs e)
{
// Find the right item and it's value and index
currentItemText = LeftListBox.SelectedValue.ToString();
currentItemIndex = LeftListBox.SelectedIndex;
ObservableCollection<string> oList;
oList = new System.Collections.ObjectModel.ObservableCollection<string>(toRemoveList);
RightListBox.DataContext = oList;
Binding binding = new Binding();
RightListBox.SetBinding(ListBox.ItemsSourceProperty, binding);
(RightListBox.ItemsSource as ObservableCollection<string>).Add(currentItemText);
if (toAddList != null)
{
toAddList.RemoveAt(currentItemIndex);
}
// Refresh data binding
ApplyDataBinding();
}
But the problem is that when I select an item from the left box, then click on add, it adds the new item into the right box but when I add a second item, it replaces the last one item that I added at the first step.
After that, the second problem is, how would be implemented the RemoveButton_Click ? Is it the same way as the previous method ?
You need not to do this much of code for this. Follow below given steps for more robust and maintainable approach.
Create two Observablecollection corresponding to left and right ListBox
Bind Observablecollection to ListBox
On Add button click, remove the item from observable collection assigned to left listbox and add to the oberservable collection binded to right grid.
You need not to update the bindings explicitly. Observable collection notify the collection changes automatically.
In code -
public ObservableCollection<ApplicationFormats> Formats { get; set; }
Xaml -
<ListBox Name="LeftListBox" ItemsSource="{Binding Formats}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Overwrite ListBoxitem?

I'm new to WPF and I have this ListBox which I want to instantiate with a specific ListBoxItem, so the user knows what to do with the ListBox.
<ListBox Name="DbListBox"
Grid.Column="3"
HorizontalAlignment="Left"
Height="246"
Margin="0,99,0,0"
Grid.Row="1"
VerticalAlignment="Top"
Width="211"
SelectionMode="Single"
SelectedItem="{Binding Path=selectedDB,Mode=TwoWay}"
AllowDrop="True"
Drop="DbListBox_Drop">
<ListBoxItem Name="ListBoxItem" FontStyle="Italic">Drag .db file here or add below</ListBoxItem>
</ListBox>
Then I have some code which adds a collection of items to the ItemsSource of this ListBox, but I can't do that since the ItemsSource is not empty
DbListBox.ItemsSource = DbCollection;
My question is, how can I start up the ListBox with the item inserted first, and then when DbCollection is added to it, it simply overwrites the first ListBoxItem?
When using WPF properly, we'd normally have something like this, where a collection property would be data bound to the ListBox.ItemsSource property:
<ListBox ItemsSource="{Binding SomeCollectionProperty}" />
Once we have this XAML, we don't need to touch the ListBox again, as we can add or remove items from the data bound collection and they will magically appear (or disappear) from the ListBox:
SomeCollectionProperty.Add(new SomeDataType());
SomeCollectionProperty.Remove(someItemFromCollection);
The SomeDataType used here is up to you... it depends on what you want to display in your items. If it was just a plain string for example, then you could simply do this to add your initial item into the collection:
SomeCollectionProperty.Add("Drag .db file here or add below");
If you wanted to make that item look different to the others then you'd need to data bind a custom class that has a Text property and FontStyle property for example. You could data bind these properties in a DataTemplate to design each item to be exactly as you want it. However, that's a completely different question.
To find out more about these things, you can read the Data Binding Overview and Data Templating Overview page on MSDN.

Items collection must be empty before using ItemsSource. Telerik Treeview

Im getting this error while trying to giva my treeview an itemsource
"Items collection must be empty before using ItemsSource."
I have checked a lot of solutions and I cant seem to find a way to solve this. Here are my code snippets:
XAML:
<HierarchicalDataTemplate x:Key="Category">
<TextBlock Text="{Binding Name}">
</TextBlock>
</HierarchicalDataTemplate>
XAML
<telerik:RadTreeView x:Name="treeview" IsDragDropEnabled="True" HorizontalAlignment="Left" Height="250" Margin="10,10,0,-3" VerticalAlignment="Top" Width="190" IsManipulationEnabled="True" IsLoadOnDemandEnabled="True" LoadOnDemand="treeview_LoadOnDemand" IsExpandOnSingleClickEnabled="True" ItemTemplate="{StaticResource Category}">
</telerik:RadTreeView>
C# - Giving the treeview a data source:
Data d = new Data();
treeview.ItemsSource = d.Get_Categories();
C# - My database query:
public List<Category> Get_Categories()
{
using (var context = new ProcessDatabaseEntities())
{
return context.Category.ToList();
}
}
Category only has two properties, Name and ID. I know that the itemsource-list is not empty when I assign it. So it's probably something wrong with my XAML-code. Thank you in advance!
I believe that your problem is a common one. Basically, you cannot use both the TreeView.ItemsSource and the TreeView.Items properties together... you must choose one way or the other. Usually this problem manifests itself because a developer has done something like this...:
<TreeView Name="TreeView" ItemsSource="{Binding SomeCollection}" ... />
... and then tried to do something like this in the code behind:
TreeView.Items.Add(someItem);
The solution in that case would be to manipulate the data bound collection instead of the TreeView.Items collection:
SomeCollection.Add(someItem);
However, in your case (and it's a little bit difficult to guess without seeing your code), you have probably done the second part first (set or manipulated the Items property) and then tried to set the ItemsSource property. Your solution is the same... use one method of editing the items or the other... not both.

Items doesn't appear in ListBox in C#, but in XAML yes

I have a little problem with a ListBox.
If I add an item by code, as it :
ListBox1.Items.Add(new MyData());
<ListBox Name="ListBox1" Height="Auto"
Style="{DynamicResource MyListBoxStyle}"/>
my ListBox display nothing, while Items contain the good number of item that I have add.
But if I put an item directy in my XAML and also in c#, MyData appear correctly :
ListBox1.Items.Add(new MyData());
<ListBox Name="ListBox1" Height="Auto"
Style="{DynamicResource MyListBoxStyle}">
<data:MyData/>
</ListBox>
Anyone know why it doesn't display correctly if I don't put an item in my XAML, and how to correct it?
Thank you in advance :)
[SOLVED]
My ListBox was already shown when I adding an item. So to solve this problem, just ask to update layout to notify that ItemSource has change :
ListBox1.UpdateLayout();
I think it's because the ListBox1.Items collection does not implement the INotifyPropertyChanged/INotifyCollectionChanged. You could create an observable collection an assign it to the ItemSource.
ObservableCollection<MyData> items = new ObservableCollection()
items.Add(new MyData());
ListBox1.ItemSource = items;
The reason you don't see it added, is that the listbox is already created/filled and the collection of Items does not notify the listbox when you call the .Add method on the Items property.

In Silverlight, how to bind ListBox item selection to a Navigate event?

I am writing a windows-phone 7 application. I've got a page with a list of TextBlock(s) contained in a ListBox. The behavior I want is that upon clicking one of those TextBlock(s) the page is redirected to a different one, passing the Text of that TextBlock as an argument.
This is the xaml code: (here I am binding to a collection of strings, and the event MouseLeftButtonDown is attached to each TextBlock).
<ListBox x:Name="List1" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock MouseLeftButtonDown="List1_MouseLeftButtonDown" Text="{Binding}"
FontSize="20"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
But this has been unsuccessful for me. I have tried attaching MouseLeftButtonDown event to either the individual TextBox(es) or to the ListBox. And I have had exceptions raised as soon as I use NavigationService.Navigate(uri). Which event should be attached? Should the event be attached to the individual items or to the list as a whole?
I have found a way to work around this problem by populating ListBox with HyperlinkButton(s). However, I would like to understand why the TextBox approach did not work.
This is my first attempt with Silverlight, so I might be missing something basic here.
There are a few ways to do this but I'll walk you through one of the the simplest (but not the purest from an architectural perspective).
Basically you want to find out when the selection of the ListBox changes. The ListBox raises a SelectionChanged event which can be listened to in the code behind.
<ListBox x:Name="List1" ItemsSource="{Binding}" SelectionChanged="SelectionChangedHandler" SelectionMode="Single" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontSize="20"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Then have a handler something like:
private void SelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
IList selectedItems = e.AddedItems;
string val = selectedItems.OfType<string>().FirstOrDefault();
NavigationService.Navigate(new Uri(val));
}
One thing you'll need to be aware of is that ListBoxes support multiple selection. For this reason, the event arguments give you back a list of the selected items. For simplicity, all I've done is taken the first value from this list and used that as the navigation value. Notice how I've also set the SlectionMode property of the ListBox to Single which will ensure the user can only select one item.
If I were doing this for real I'd look into creating an TriggerAction tat can be hooked up to an event trigger through xaml which will remove the for code behinds. Take a look at this link if you're interesetd.
In addition to Chris' and James' replies, I'd add that you will also need to clear the listbox selection in the event handler, otherwise the user won't be able to tap the same item twice on the listbox (because the item will already be selected).
Using James' approach, I would change the SelectionChangedHandler() implementation as follows:
private void SelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
// Avoid entering an infinite loop
if (e.AddedItems.Count == 0)
{
return;
}
IList selectedItems = e.AddedItems;
string val = selectedItems.OfType<string>().FirstOrDefault();
NavigationService.Navigate(new Uri(val));
// Clear the listbox selection
((ListBox)sender).SelectedItem = null;
}
What I would recommend is binding the SelectedItem property of the ListBox to a property in your ViewModel. Then, on the ListBox's SelectedItemChanged event, navigate to to the appropriate URL passing the data key on the QueryString, or upgrade to something like MVVM Light and put the actual SelectedItem object on the message bus for the child window to pick up. I have a sample of this second method on my Skydrive that you can check out.
HTH!
Chris

Categories

Resources