Deleting dynamically created button from ListBox in WPF - wpf

I'm attempting to delete a dynamically created button from a listbox using the ListBox.Items.Remove, but I keep getting the error of "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." Problem is, ItemsControl.ItemsSource is not a valid option in my code.
A little run-down on the code: I have a MainWindow that contains a ListBox and the "Add" and "Delete" buttons. Adding a button sends you to a window where you can input a firstname and lastname. Clicking "Done" adds the newly-created profile's Button to the Listbox (you can access the profile by clicking on said button). I didn't include the Profile code as its empty except for the firstname and lastname being bound to labels there.
How would I access/modify the the button/profile in order to delete them? I know it has to do with the databinding, but I'm thoroughly confused on how to delete the item.
Any help would be much appreciated. I've included the MainWindow and ProfileCreator code below.
<Window x:Class="SavingButtons.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="UserTemplate">
<StackPanel Orientation="Horizontal">
<Button Name="TestAddButton" Click="TestAddButton_Clicked" Content="{Binding FirstName}" Width="100" Height="40"></Button>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Button Name="AddProfileButton" Content="Add Profile" HorizontalAlignment="Left" Margin="22,29,0,0" VerticalAlignment="Top" Width="75" Click="AddProfileButton_Click"/>
<ListBox Name="ButtonHoldersListbox" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}" ItemTemplate="{StaticResource UserTemplate}" HorizontalAlignment="Left" Height="202" Margin="22,69,0,0" VerticalAlignment="Top" Width="183" />
<Button Name="DeleteUserButton" Click="DeleteUserButton_Click" Content="Delete User" HorizontalAlignment="Left" Margin="246,69,0,0" VerticalAlignment="Top" Width="105"/>
</Grid>
namespace SavingButtons
{
public partial class MainWindow : Window
{
NewProfile np;
public int buttonNumberID;
public MainWindow()
{
InitializeComponent();
np = new NewProfile(this);
}
private void AddProfileButton_Click(object sender, RoutedEventArgs e)
{
np.Show();
}
//adds button to listbox
internal void TestAddButton_Clicked(object sender, RoutedEventArgs e)
{
Button cmd = (Button)sender;
if (cmd.DataContext is User)
{
//Profile is where the finished information is displayed//
Profile pro = new Profile();
pro.DataContext = cmd.DataContext;
pro.Show();
}
}
//this is where confusion ensues
private void DeleteUserButton_Click(object sender, RoutedEventArgs e)
{
//error occurs here
ButtonHoldersListbox.Items.Remove(ButtonHoldersListbox.SelectedItem);
}
}
}
The Profile Creator:
<Window x:Class="SavingButtons.NewProfile"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="NewProfile" Height="300" Width="500">
<Grid>
<Label Content="FirstName" HorizontalAlignment="Left" Margin="64,44,0,0" VerticalAlignment="Top"/>
<Label Content="LastName" HorizontalAlignment="Left" Margin="64,97,0,0" VerticalAlignment="Top"/>
<Button Name="UploadImageButton" Click="UploadImageButton_Click" Content="Upload Image" HorizontalAlignment="Left" Margin="64,146,0,0" VerticalAlignment="Top" Width="75"/>
<TextBox Name="FirstNameTextBox" HorizontalAlignment="Left" Height="23" Margin="126,47,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120"/>
<TextBox Name="LastNameTextBox" HorizontalAlignment="Left" Height="23" Margin="126,99,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120"/>
<Image Name="imgPhoto" HorizontalAlignment="Left" Height="100" Margin="173,146,0,0" VerticalAlignment="Top" Width="100"/>
<Button Name="ProfileFinishedLaunch" Content="Done" HorizontalAlignment="Left" Margin="360,232,0,0" VerticalAlignment="Top" Width="75" Click="ProfileFinishedLaunch_Click"/>
</Grid>
namespace SavingButtons
{
public partial class NewProfile : Window
{
public ObservableCollection<User> ProfileList;
public MainWindow mMain;
public NewProfile(MainWindow main)
{
InitializeComponent();
ProfileList = new ObservableCollection<User>();
mMain = main;
}
//loads image
private void UploadImageButton_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog op = new OpenFileDialog();
op.Title = "Select a picture";
op.Filter = "All supported graphics|*.jpg;*.jpeg;*.png|" +
"JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
"Portable Network Graphic (*.png)|*.png";
if (op.ShowDialog() == true)
{
imgPhoto.Source = new BitmapImage(new System.Uri(op.FileName));
}
}
//creates a new user out of all the info, inserts new user into the collection, adds new button
private void ProfileFinishedLaunch_Click(object sender, RoutedEventArgs e)
{
mMain.buttonNumberID++;
ProfileList.Add(new User { FirstName = FirstNameTextBox.Text, LastName = LastNameTextBox.Text, imgPhoto = imgPhoto.Source });
mMain.ButtonHoldersListbox.DataContext = ProfileList;
mMain.Show();
this.Hide();
}

You are setting yourListbox` to the others window property and you do it every time after a new item was added.
The error occurs, because the listbox items were set through binding to the ItemsSource property and in this case the ListBox.Items is read only so you can't remove or add item directly.
Instead of what you have now, add an ObservableCollection<User> property to your MainWindow class and bind the ListBox to this property. In the NewProfile window you need to add the new User item to this collection. The delete operation will work with removing the item from that collection (actually the senders DataContext)
public partial class MainWindow : Window
{
public ObservableCollection<User> Profiles {get; set;}
//...
private void DeleteUserButton_Click(object sender, RoutedEventArgs e)
{
var removable = ButtonHoldersListbox.SelectedItem as User;
if(removable != null)
Profiles.Remove(removable);
}
}
<ListBox Name="ButtonHoldersListbox" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Profiles}" ItemTemplate="{StaticResource UserTemplate}" HorizontalAlignment="Left" Height="202" Margin="22,69,0,0" VerticalAlignment="Top" Width="183" />
public partial class NewProfile : Window
{
//creates a new user out of all the info, inserts new user into the collection, adds new button
private void ProfileFinishedLaunch_Click(object sender, RoutedEventArgs e)
{
mMain.buttonNumberID++;
var newUser = new User { FirstName = FirstNameTextBox.Text, LastName = LastNameTextBox.Text, imgPhoto = imgPhoto.Source };
mMain.Profiles.Add(newUser);
//Don't set the listbox.DataContext here
mMain.Show();
this.Hide();
}

if you set itemsource to usercontrol you can't operate it's items directly. Edit it's itemsource instead. give you a simple example.
public partial class MainWindow : Window
{
ObservableCollection<int> ProfileList;
public MainWindow()
{
InitializeComponent();
ProfileList = new ObservableCollection<int>();
this.DataContext = ProfileList;
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
Random r = new Random();
int num = r.Next(100);
ProfileList.Add(num);
//lstShow.Items.Add(num); error!
}
private void btnDel_Click(object sender, RoutedEventArgs e)
{
if (lstShow.SelectedIndex > -1)
{
ProfileList.Remove((int)lstShow.SelectedItem);
//lstShow.Items.Remove((int)lstShow.SelectedItem); error!
}
}
}

Thanks to Miklos, I did get my problem solved, however, the binding is still pretty confusing. Mainly: how does the ListBox know to bind the ObservableCollection ProfileList? In Mikalos version, he explicitly binds the ObservableCollection to the Listbox in the XAML(NOTE: Mikalos observable collection is named "Profile")
ItemsSource="{Binding Profiles}"
That would seem the most explicit. Instead, the only way I was only able to make it work was this way(ProfileList is the name I used for the observable collection):
ItemsSource="{Binding}"
Not sure how it knows to bind to my observableCollection. I will include my working code below.
<Window x:Class="SavingButtons.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="UserTemplate">
<StackPanel Orientation="Horizontal">
<Button Name="TestButton" Click="cmdDeleteUser_Clicked" Content="{Binding FirstName}" Width="100" Height="40"></Button>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Button Name="AddProfileButton" Content="Add Profile" HorizontalAlignment="Left" Margin="22,29,0,0" VerticalAlignment="Top" Width="75" Click="AddProfileButton_Click"/>
<ListBox Name="ButtonHoldersListbox" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}" ItemTemplate="{StaticResource UserTemplate}" HorizontalAlignment="Left" Height="202" Margin="22,69,0,0" VerticalAlignment="Top" Width="183" />
<Button Name="DeleteUserButton" Click="DeleteUserButton_Click" Content="Delete User" HorizontalAlignment="Left" Margin="246,69,0,0" VerticalAlignment="Top" Width="105"/>
</Grid>
My ProfileCreator Cs:
public partial class NewProfile : Window
{
public MainWindow mMain;
public NewProfile(MainWindow main)
{
InitializeComponent();
mMain = main;
}
//creates a new user out of all the info, inserts new user into the collection, adds new button
private void ProfileFinishedLaunch_Click(object sender, RoutedEventArgs e)
{
////Mikalos CODE-----------------------------------------------------------//
var newUser = new User { FirstName = FirstNameTextBox.Text, LastName = LastNameTextBox.Text, imgPhoto = imgPhoto.Source };
mMain.ProfileList.Add(newUser);
mMain.ButtonHoldersListbox.DataContext = mMain.ProfileList;//Mikalo suggested not putting ListBox.DataContext here,
//however, this is the only place it works.
mMain.Show();
this.Hide();
//---------------------------------------------------------------//
}

Related

ContextMenu StaysOpen is not working

I am showing a UserControl inside ContextMenu. Futhermore in that UserControl I am showing a Popup which contains some buttons and datagrid. All the data in Popup loads during runtime.
The problem is ContextMenu ignores the property StaysOpen even though it is set to true.
The Popup does stay open when I set StaysOpen to true in code behind but ContextMenu doesn't.
I tried it with following code:
<UserControl x:Class="UserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Popup Name="popupState" PlacementTarget="{Binding ElementName=txtSearch}" PopupAnimation="Slide" Placement="Bottom" Focusable="True" AllowsTransparency="True" VerticalAlignment="Top">
<Button HorizontalAlignment="Right" Margin="5" Background="GhostWhite" Name="btnSelectAll" Click="btnSelectAll_Click" Width="30" Height="30">
<my:DataGrid VerticalAlignment="Stretch" MaxHeight="300" VerticalScrollBarVisibility="Auto" RowHeaderWidth="0" Margin="5,5,5,1" Background="White" HorizontalAlignment="Stretch" Name="DGTeamCommunicator" HorizontalContentAlignment="Left" HorizontalGridLinesBrush="#D6D7D6" GridLinesVisibility="None">
<my:DataGridTemplateColumn Width="Auto" MinWidth="30">
<my:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" ContextMenuService.IsEnabled="True" ContextMenuService.HasDropShadow="True">
<Button Name="btnCall" Click="btnCall_Click" ContextMenuService.IsEnabled="True">
</Button>
</StackPanel>
</DataTemplate>
</my:DataGridTemplateColumn.CellTemplate>
</my:DataGridTemplateColumn>
</my:DataGrid>
</Popup>
My requirement is to Prevent the Popup as well as ContextMenu from closing when the buttons in Popup is clicked.
Kindly provide me a solution for this.
The bad news is that this behavior is by design. ContextMenu is defined to display few menus and when one of them is being clicked the ContextMenu internally sets the IsOpen to false.
The good news is that behavior should stay so and for any other customization you should take a Popup instead of ContextMenu. BUT if you MUST use ContextMenu for whatever reason here is a workaround:
<StackPanel>
<Button>
Popup Demo
<Button.ContextMenu>
<local:StaysOpenContextMenu x:Name="ContextMenu1" StaysOpen="True">
<StackPanel>
<TextBox x:Name="TextBox1" Width="100" TextChanged="OnTextChanged"/>
<Popup x:Name="Popup1" Placement="Bottom" PlacementTarget="{Binding ElementName=tbx}" StaysOpen="True">
<Button Content="click me"/>
</Popup>
</StackPanel>
</local:StaysOpenContextMenu>
</Button.ContextMenu>
</Button>
<Button Click="OnClick">Close Popup</Button>
</StackPanel>
You will need a custom ContextMenu to get the job done:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
this.Popup1.IsOpen = true;
}
private void OnClick(object sender, RoutedEventArgs e)
{
this.Popup1.IsOpen = false;
this.ContextMenu1.CloseContextMenu();
}
}
public class StaysOpenContextMenu : ContextMenu
{
private bool mustStayOpen;
static StaysOpenContextMenu()
{
IsOpenProperty.OverrideMetadata(
typeof(StaysOpenContextMenu),
new FrameworkPropertyMetadata(false, null, CoerceIsOpen));
StaysOpenProperty.OverrideMetadata(
typeof(StaysOpenContextMenu),
new FrameworkPropertyMetadata(false, PropertyChanged, CoerceStaysOpen));
}
private static void PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
StaysOpenContextMenu menu = (StaysOpenContextMenu)d;
menu.mustStayOpen = (bool)e.NewValue;
}
private static object CoerceStaysOpen(DependencyObject d, object basevalue)
{
d.CoerceValue(IsOpenProperty);
return basevalue;
}
private static object CoerceIsOpen(DependencyObject d, object basevalue)
{
StaysOpenContextMenu menu = (StaysOpenContextMenu)d;
if (menu.StaysOpen && menu.mustStayOpen)
{
return true;
}
return basevalue;
}
public void CloseContextMenu()
{
this.mustStayOpen = false;
this.IsOpen = false;
}
}

How can I bind a List as ItemSource to ListView in XAML?

I'm learning WPF and would like to have a collection similar to a LinkedList, to where I can add and remove strings. And I want to have a ListView that listen to that collection with databinding. How can I do bind a simple list collection to a ListView in XAML?
My idea (not working) is something like this:
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<LinkedList x:Key="myList"></LinkedList> //Wrong
<Window.Resources>
<Grid>
<ListView Height="100" HorizontalAlignment="Left" Margin="88,134,0,0"
Name="listView1" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Source={StaticResource myList}}"/> //Wrong
</Grid>
</Window>
All my code (updated version, not working):
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="12,12,0,0"
Name="textBox1" VerticalAlignment="Top" Width="120" />
<Button Content="Button" Height="23" HorizontalAlignment="Right"
Margin="0,12,290,0" Name="button1" VerticalAlignment="Top" Width="75"
Click="button1_Click" />
<ListView Height="100" HorizontalAlignment="Left" Margin="88,134,0,0"
Name="listView1" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding myList}"/>
</Grid>
</Window>
C#-code:
namespace TestApp
{
public partial class MainWindow : Window
{
ObservableCollection<string> myList = new ObservableCollection<string>();
public MainWindow()
{
InitializeComponent();
myList.Add("first string");
}
private void button1_Click(object sender, RoutedEventArgs e)
{
myList.Add(textBox1.Text);
textBox1.Text = myList.Count+"st";
}
}
}
The approach selected as an answer works fine... but I don't specifically like having to specify the DataContext programmatically while setting everything else in XAML, I don't feel like it's "proper" (maybe this is just me). So for the next person, or anyone else who thinks like me and finds this off a search engine (like i did), here is the way to do it in XAML:
C#
public sealed partial class MainPage : Page
{
public ObservableCollection<string> Messages { get; set; }
public MainPage()
{
this.Messages = new ObservableCollection<string>();
this.InitializeComponent();
}
}
XAML
<Window
....
DataContext="{Binding RelativeSource={RelativeSource Self}}"
...>
<ListView ItemsSource="{Binding Messages}" ... />
</Window>
To be honest I think {Binding RelativeSource={RelativeSource Self}} should be the default value any top level element's (Page, Window, etc...) DataConext because it is simply how a lot of people expect it to work, I know it's how I assume it would work. Honestly, I feel like {Binding RelativeSource={RelativeSource Self}} is a bit verbose and almost long for a shorter syntax.
You can only databind to public properties and you need to set the DataContext.
public partial class MainWindow : Window
{
public ObservableCollection<string> myList { get; private set; }
public MainWindow()
{
InitializeComponent();
myList = new ObservableCollection<string>();
myList.Add("first string");
DataContext = this;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
myList.Add(textBox1.Text);
textBox1.Text = myList.Count + "st";
}
}

Two way binding use a user control...binding to object, not an element?

I created an object with a simple property with a default value. I then created a user control that has a text box in it. I set the datacontext of the user control to the object.
The text box correctly shows the properties default value but I can't seem to update the property value when the user changes the text box value. I created a simple project to illustrate my code.
Thanks for the help!!
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private string _titleValue;
public string TitleValue
{
get
{
return _titleValue;
}
set
{
_titleValue = value;
textBox1.Text = _titleValue;
}
}
public static readonly DependencyProperty TitleValueProperty = DependencyProperty.Register(
"TitleValue", typeof(string), typeof(UserControl1), new FrameworkPropertyMetadata(new PropertyChangedCallback(titleUpdated))
);
//Don't think I should need to do this!!!
private static void titleUpdated(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((UserControl1)d).TitleValue = (string)e.NewValue;
}
}
<UserControl x:Class="WpfApplication1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="94,97,0,0" Name="textBox1" VerticalAlignment="Top" Width="120"
Text="{Binding Path=TitleValue, Mode=TwoWay}"/>
</Grid>
</UserControl>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var dummy = new DummyObject("This is my title.");
userControl11.DataContext = dummy;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("The value is: " + ((DummyObject)userControl11.DataContext).Title);
}
}
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:WpfApplication1">
<Grid>
<my:UserControl1 HorizontalAlignment="Left" Margin="95,44,0,0" x:Name="userControl11" VerticalAlignment="Top" Height="191" Width="293"
TitleValue="{Binding Path=Title, Mode=TwoWay}"/>
<Button Content="Check Value" Height="23" HorizontalAlignment="Left" Margin="20,12,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</Window>
The DataContext on your usercontrol isn't set. Specify a Name for it (I usually call mine "ThisControl") and modify the TextBox's binding to Text="{Binding ElementName=ThisControl, Path=TitleValue, Mode=TwoWay}". You can also set the DataContext explicitly, but I believe this is the preferred way.
It seems like the default DataContext should be "this", but by default, it's nothing.
[edit] You may also want to add , UpdateSourceTrigger=PropertyChanged to your binding, as by default TextBoxes' Text binding only updates when focus is lost.

Dependency propery Binding Problem

Main WINDOW
<Window x:Class="dep2.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:dep2"
Title="Window1" Height="300" Width="381">
<Grid>
<local:UserControl1></local:UserControl1>
<Button Height="23" HorizontalAlignment="Right" Margin="0,0,77,36" Name="button1" VerticalAlignment="Bottom" Width="75" Click="button1_Click">Button</Button>
</Grid>
</Window>
public partial class Window1 : Window
{
UserControl1 uc = new UserControl1();
public Window1()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
uc.InfoText = "SAMPLE";
}
}
My User CONTROL
<UserControl x:Class="dep2.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="32" Width="300">
<Grid Height="30">
<StackPanel Background="LightCyan">
<TextBox Height="21" Name="textBlock1" Width="120" Text="{Binding Text}" />
</StackPanel>
</Grid>
</UserControl>
public partial class UserControl1 : UserControl
{
public string InfoText
{
get
{
return (string)GetValue(InfoTextProperty);
}
set
{
SetValue(InfoTextProperty, value);
}
}
public static readonly DependencyProperty InfoTextProperty =
DependencyProperty.Register(
"InfoText",
typeof(string),
typeof(UserControl1),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(ChangeText)));
private static void ChangeText(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as UserControl1).UpdateText(e.NewValue.ToString());
}
private void UpdateText(string NewText)
{
textBox1.Text = NewText;
}
public UserControl1()
{
InitializeComponent();
DataContext = this;
}
}
Am getting my value in user control dependency property, but i cant able to bind my value to the text box.am using like this to bind Text="{Binding Text}" is it right,or how to bind my value in user control
i have attached my sample project here,
http://cid-08ec3041618e8ee4.skydrive.live.com/self.aspx/.SharedFavorites/dep2.rar
Can any one look and tell whats wrong in that,
everythng working well, but i cant bind the value in text box,
when u click the button u can see the passed value to usercontrol in message box, but i cant bind that value in text box.
Why????
Your code handles the callback from the dependency property and sets the text box value directly. This is not the role of this callback.
And by setting the Text property, you have lost the binding. Local property setting has a higher priority than bindings. See this blog

How to access ListBox dynamically-created-items' properties from code-behind?

XAML:
<Window x:Class="WpfApp_ListBoxTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<ListBox Name="lb" Margin="0,0,0,70"></ListBox>
<Button Height="23" HorizontalAlignment="Left" Margin="12,0,0,41" Name="btnAdd" VerticalAlignment="Bottom" Content="Add item" Width="75" Click="btnAdd_Click"></Button>
<TextBox Height="23" Margin="93,0,12,41" Name="txtInput" VerticalAlignment="Bottom" />
<Button Height="23" HorizontalAlignment="Left" Margin="12,0,0,12" Name="btnGet" VerticalAlignment="Bottom" Content="Get value" Width="75" Click="btnGet_Click"></Button>
<TextBox Height="23" Margin="93,0,12,12" Name="txtReturn" VerticalAlignment="Bottom" IsReadOnly="True" />
</Grid>
</Window>
Csharp:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml;
namespace WpfApp_ListBoxTest
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
TextBox txt = new TextBox();
txt.Width = 200;
txt.Text = txtInput.Text;
lb.Items.Add(txt);
}
private void btnGet_Click(object sender, RoutedEventArgs e)
{
// What do I need to write here to get the value of the Text property of the selected TextBox?
}
}
}
And screenshot: (Sorry I'm not allowed to post picture directly)
http://i825.photobucket.com/albums/zz180/mGlushed/get_listbox_item_property.png
(In the picture above, I want to get the value "b" when I click the "Get value" button.)
I would like to know if there is a simple way to achieve this.
I'm new to WPF, so I only know to do this the long way, which is: Create an array. Everytime a new TextBox is created, add it into the array. Then access the TextBox'es through the array. But that doesn't sound very optimal, I think.
The 'WPF Way' of doing what you want is to use data binding:
Define a class with a string property called Text.
Create a collection of that class.
Bind your list box ItemsSource to the collection.
Create a DataTemplate that shows a TextBox with its Text property bound using {Binding Path=Text}.
In btnAdd_Click add an item to the collection (not directly to the ListBox)
In btnGet_Click you can get the text entered by casting ListBox.SelectedItem to your class and getting its Text property.
Example:
The simple class:
public class VMObject
{
public VMObject(string text)
{
Text = text;
}
public string Text { get; set; }
}
The window code-behind:
public partial class Window1 : Window
{
public ObservableCollection<VMObject> VM { get; set; }
public Window1()
{
VM = new ObservableCollection<VMObject>();
InitializeComponent();
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
VM.Add(new VMObject(txtInput.Text));
}
private void btnGet_Click(object sender, RoutedEventArgs e)
{
if (lb.SelectedItem == null)
MessageBox.Show("No item is selected!");
txtReturn.Text = ((VMObject)lb.SelectedItem).Text;
}
}
The XAML:
<Window x:Class="lbtest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Window"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="TextBoxTemplate">
<TextBox Text="{Binding Path=Text}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Name="lb" Margin="0,0,0,70"
ItemsSource="{Binding ElementName=Window, Path=VM}"
ItemTemplate="{StaticResource TextBoxTemplate}" />
<Button Height="23" HorizontalAlignment="Left" Margin="12,0,0,41"
Name="btnAdd" VerticalAlignment="Bottom"
Content="Add item" Width="75" Click="btnAdd_Click" />
<TextBox Height="23" Margin="93,0,12,41"
Name="txtInput" VerticalAlignment="Bottom" />
<Button Height="23" HorizontalAlignment="Left" Margin="12,0,0,12"
Name="btnGet" VerticalAlignment="Bottom"
Content="Get value" Width="75" Click="btnGet_Click" />
<TextBox Height="23" Margin="93,0,12,12"
Name="txtReturn" VerticalAlignment="Bottom" IsReadOnly="True" />
</Grid>
</Window>
for a checkbox item:
private void chk_Checked(object sender, RoutedEventArgs e)
{
CheckBox chk = (CheckBox)sender;
MessageBox.Show(chk.Content.ToString());
}
No need for TextBox:s. ListBox handle strings fine.
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
// No need to create TextBox, ListBox handle strings fine.
lb.Items.Add(txtInput.Text);
}
private void btnGet_Click(object sender, RoutedEventArgs e)
{
// No selection, but button has been pressed.
if(lb.SelectedItem == -1)
return;
// Get selected item.
txtReturn.Text = (string)lb.SelectedItem;
/* If you change ListBox selection mode to multiple
* you can get all selected items by using foreach loop.
foreach (Object selected in lb.SelectedItems)
{
txtReturn.Text += (string) selected;
}
*/
}
If you just want to get the Text property of the selected TextBox (admiting your ListBox is in single selection mode) it it quite simple:
private void btnGet_Click(object sender, RoutedEventArgs e)
{
if(lb.SelectedItem != -1)
{
TextBox selectedTextBox = (TextBox)lb.SelectedItem;
txtReturn.Text = selectedTextBox.Text;
}
}
But if you want to implement the pretty WPF way, you should follow the Aviad P. solution, my solution do it well too.
Regards.
EDIT: If do not have a real need of TextBox functionalities, but only a string container, so follow Tuukka's solution.

Resources