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>
Related
I am trying to make a form of sorts where the user will have the option to press a button to dynamically add more text boxes, with each text box to contain the path of a directory to exclude from a search. This is relatively trivial using code-behind, but my problem is doing it in proper MVVM.
The general markup for the structure is:
<ScrollViewer >
<StackPanel>
<DockPanel LastChildFill="False">
<TextBox DockPanel.Dock="Left"/>
<Button DockPanel.Dock="Right"
Content="+"/>
</DockPanel>
</StackPanel>
</ScrollViewer>
Clicking the button will add a new DockPanel with a TextBox and Button to the StackPanel. All buttons but the bottom-most will change to a minus sign. How can I somehow bind to the text of all the text boxes?
As an aside, once/if I get this working, would it be better to make it into its own component?
Quick pseudo-code to get you started:
cs (view model):
// INotifyPropertyChanged if you need, as well as full-properties with notification
public class Item
{
public string Text {get; set;}
}
public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>();
void OnCommandAdd() => Items.Add(new Item());
xaml (view):
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The idea is to use control able to display list (e.g. ItemsControl) and collection of items in view model. Adding new element to view is done by adding item to that collection.
All TextBoxes will have Text bound to corresponding property of item.
My name is Andrea this is my first post ever.
Frequently you have helped me as a simple reader, now I'm writing because I wanted to direct support.
I have to create and a tab control and with a button "Add Tab" I have to add a new tab with the same content.
Up to this everything is fine.
Within Tab I have a textedit and a combobox.
My problems are two:
1 How do I load the contents of the combobox for each tab I add?
2 Every time I write the text of and a edit tab override also edit the text of the other tab.
Here the code:
Data Template in Xaml:
<DataTemplate x:Key="tabItemContent">
<dxlc:LayoutGroup Orientation="Vertical" Header="Target Description" IsCollapsible="True">
<!--Name-->
<dxlc:LayoutItem>
<dxlc:LayoutGroup Orientation="Horizontal" ItemSpace="4" >
<dxlc:LayoutItem Label="Name" Margin="10">
<dxe:TextEdit x:Name="TextEdit_NameTarget"/>
</dxlc:LayoutItem>
</dxlc:LayoutGroup>
</dxlc:LayoutItem>
<!--Nation e Label-->
<dxlc:LayoutItem>
<dxlc:LayoutGroup Orientation="Horizontal" ItemSpace="12" >
<dxlc:LayoutItem Label="Nation" Margin="10">
<ComboBox x:Name="ComboBox_TargetNazione" />
</dxlc:LayoutItem>
</dxlc:LayoutGroup>
</dxlc:LayoutItem>
</dxlc:LayoutGroup>
</DataTemplate>
C#:
private void Button_Click_Add(object sender, RoutedEventArgs e)
{
DataTemplate tabItemDataTemplate = this.TryFindResource("tabItemContent") as DataTemplate;
DXTabItem tabItem = new DXTabItem();
tabItem.Header = "New Tab";
tabItem.ContentTemplate = tabItemDataTemplate;
tabControl_Targets.Items.Add(tabItem);
}
Here's where to load the list into the combobox:
private void LoadComboBoxNation()
{
ComboBox_TargetNazione.ItemsSource =
ManagementTriple.Istance().get_Nation_byTipologyAndContext(ComboBox_TypologyScenario.SelectedItem.ToString(),
ComboBox_ContextScenario.SelectedItem.ToString());
controlloselecteditem(ComboBox_SourceNazione.SelectedItem.ToString());
controlloselecteditem(ComboBox_TargetNazione.SelectedItem.ToString());
}
Thank you all for the help that you can give me.
DataTemplates require a simple but fundamental requirement to work properly: you should use the ViewModel-First approach.
Ideally, your tab control should have a Binding to some ViewModel. Then, if you want another tab to appear, you should use your button click to call a Command in your ViewModel, then the ViewModel would add another item to your TabControl ItemsSource property (which would be some collection), and the new item would be displayed "automagically" with its respective DataTemplate.
The idea of WPF is to replace all this imperative code in the View (like the one you posted) with a more indirect one, where your only worry is to manipulate things in the ViewModel, and the "Dumb View" just follows.
Hope this helps, but don't hesitate to ask for additional details in the comments.
I have a window with a property called IpList of type ObervableCollection<string> and I set the window's DataContext property to the window itself so that I can bind properties from XAML elements to properties of the window. One XAML element in my window is a ListBox that has an ItemTemplate:
<ListBox
ItemsSource="{Binding IpList}"
x:Name="lbIps">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<Button
Click="lbIps_bnClose_Click"
Content="X"
DockPanel.Dock="Left" />
<TextBlock
Text="{Binding}" />
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I want the list box items to remove themselves when their "X" button is clicked. My first attempt was to obtain the selected item index and remove this from the list. But when I only click on the button, no item is selected. My second attempt was to remove the item by value instead of index, but I couldn't figure out how to obtain the value of the list box item containing the clicked button. So my questions are: Is there a possibility to obtain the index/value of the list box item containing the clicked button? If so, how can I do this? Is there another way to remove the parent list box item of a clicked "X" button?
private void lbIps_bnClose_Click(object sender, RoutedEventArgs e)
{
var vm = this.DataContext as [yourViewModelName];
var button = sender as Button;
var item = (string)button.DataContext;
vm.IpList.Remove(item);
}
If you're not using MVVM:
Put this in the click event
string s=(string)((sender as Button).DataContext);
IpList.Remove(item);
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.
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