I have a ComboBox and an ObservableCollection set as DataSource for that ComboBox.
When I programmatically add/remove items from the observable collection, nothing changes in the ComboBox.
What am I doing wrong?
Part 2: tried to put a BindingSource as a proxy for ObservableCollection. When programmatically added/removed items from ObservableCollection, no event like ListChanged or similar fired.
How can I make a ComboBox automatically update its list when underlying collection changes?
public Form1()
{
InitializeComponent();
comboBox1.DataSource = new ObservableCollection<MyItem>(
new []
{
new MyItem() { Name = "AAA"},
new MyItem() { Name = "BBB"},
});
}
private void Button3_Click(object sender, EventArgs e)
{
// Nothing changes in the ComboBox when I add a new item to ObservableCollection
((ObservableCollection<MyItem>)(comboBox1.DataSource))
.Add(new MyItem() { Name = Guid.NewGuid().ToString()});
}
}
public class MyItem
{
public string Name { get; set; }
}
It helps to wrap a list in a BindingList<T>. Here a little test code:
public partial class Form1 : Form
{
private readonly List<string> _coll = new List<string> { "aaaaa", "bbbbb", "ccccc" };
private readonly BindingList<string> _blist;
private readonly Random _rand = new Random();
private const string Templ = "mcvnoqei4yutladfffvtymoiaro875b247ytmlarkfhsdmptiuo58y1toye";
public Form1()
{
InitializeComponent();
_blist = new BindingList<string>(_coll);
comboBox1.DataSource = _blist;
}
private void AddButton_Click(object sender, EventArgs e)
{
int i = _rand.Next(Templ.Length - 5);
string s = Templ.Substring(i, 5);
_blist.Add(s);
}
}
Note that you have to make the changes (Add, Remove etc.) to the BindingList. The BindingSource works the same way.
Related
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new AddItem();
taskList.ItemsSource = new List<AddItem>
{
new AddItem()
{
Title = "Task1",
},
new AddItem()
{
Title = "Task2",
},
};
}
private void addButton_Click(object sender, RoutedEventArgs e)
{
taskList.SelectedItem = new AddItem();
}
I want to add the Text in the TextBox as an AddItem to the ListView using a Button, but I don't know how to add the value to the ListView. The name of the TextBox is inputTitle.
If you intend to modify the collection you have to use an ObservableCollection that supports notifying collection changes through the INotifyCollectionChanged interface that tiggers updating your ListView. List<T> does not support that, so your user interface will not be updated when adding, removing or replacing items in the collection.
public partial class MainWindow : Window
{
private ObservableCollection<AddItem> _myItems;
public MainWindow()
{
InitializeComponent();
DataContext = new AddItem();
_myItems = new ObservableCollection<AddItem>
{
new AddItem()
{
Title = "Task1",
},
new AddItem()
{
Title = "Task2",
},
};
taskList.ItemsSource = _myItems;
}
private void addButton_Click(object sender, RoutedEventArgs e)
{
var newItem = new AddItem()
{
Title = inputTitle.Text,
};
_myItems.Add(newItem);
taskList.SelectedItem = newItem;
}
}
You should consider checking if the Text is empty, so you avoid adding useless items or deactivate the button if the TextBox is empty.
I have a method to populate a combobox with some strings. At the end of the method I assign to the SelectedIndexChanged event. Here's how that method looks
public ComboBox PopulateComboBox()
{
Worksheet sheetWithTemplateNames = _iReader.GetWorksheetByName("Templates");
int lastRowOfTemplates = _iReader.GetLastRow(sheetWithTemplateNames);
var templateNames = _iHandler.GetTemplateNames(sheetWithTemplateNames, lastRowOfTemplates);
foreach (var template in templateNames)
{
Box.Items.Add(template);
}
Box.SelectedIndexChanged += Box_SelectedIndexChanged;
return Box;
}
and it works as I want to. My problem is that I need to use this templateNames list in the actual event and that's causing trouble. Here's how my event looks like now but ain't functioning.
private void Box_SelectedIndexChanged(object sender, EventArgs e)
{
ComboBox cmb = (ComboBox)sender;
var chosenObject = cmb.SelectedIndex;
MessageBox.Show(templateNames[chosenObject]);
}
but my list is now empty. It's instantiated in the constructor so I'd assume it'd keep it's state but that isn't the situation. Here's the top of the class
public class TemplateListCombobox
{
public ComboBox Box { get; set; }
private IDataReader _iReader;
private IDataHandler _iHandler;
private List<string> templateNames;
public TemplateListCombobox()
{
Box = new ComboBox();
_iReader = new DataReader();
_iHandler = new DataHandler();
templateNames = new List<string>();
}
}
so how could I possibly keep the state of my list through the event?
UPDATE:
MY class that calls this:
public static class GroupBoxHolder
{
private static GroupBox _thisGroupBox;
public static GroupBox GetGroupBox()
{
PopulateGroupBox();
return _thisGroupBox;
}
public static void PopulateGroupBox()
{
_thisGroupBox = new GroupBox();
TemplateListCombobox combo = new TemplateListCombobox();
ComboBox box = combo.GetComboBox();
_thisGroupBox.Controls.Add(box);
ConfigureGroupBox();
}
public static void ConfigureGroupBox()
{
_thisGroupBox.Location = new Point { X = 75, Y = 15 };
_thisGroupBox.Height = 150;
_thisGroupBox.Width = 400;
}
}
and my updated class
public class TemplateListCombobox
{
private ComboBox _box;
private readonly IDataReader _iReader;
private readonly IDataHandler _iHandler;
private readonly Worksheet _sheetWithTemplateNames;
public TemplateListCombobox()
{
_box = new ComboBox();
_iReader = new DataReader();
_iHandler = new DataHandler();
_sheetWithTemplateNames = _iReader.GetWorksheetByName("Templates");
PopulateComboBox();
}
public void PopulateComboBox()
{
int lastRowOfTemplates = _iReader.GetLastRow(_sheetWithTemplateNames);
var templateNames = _iHandler.GetTemplateNames(_sheetWithTemplateNames, lastRowOfTemplates);
foreach (var template in templateNames)
{
_box.Items.Add(template);
}
_box.SelectedIndexChanged += Box_SelectedIndexChanged;
}
public ComboBox GetComboBox()
{
return _box;
}
private void Box_SelectedIndexChanged(object sender, EventArgs e)
{
ComboBox cmb = (ComboBox)sender;
var chosenObject = cmb.SelectedItem.ToString();
var firstRowForTemplate = _iReader.GetFirstRowForTemplate(_sheetWithTemplateNames, chosenObject.ToString());
var attributes = _iReader.GetTemplateAttributes(_sheetWithTemplateNames, chosenObject, firstRowForTemplate);
}
}
A. Two lists?
var templateNames = creates a new local variable.
Do you want to set a member field instead? If so, remove var.
B. Read from comobox
Since the combo box contains the names read directly from it.
The flow of the code is something like the following:
var templateNames = _iHandler.GetTemplateNames(sheetWithTemplateNames, lastRowOfTemplates);
foreach (var template in templateNames)
{
Box.Items.Add(template);
}
... time passes
var chosenObject = cmb.SelectedIndex;
MessageBox.Show(templateNames[chosenObject]);
The combobox contains all the information you need. (You could use SelectedItem).
Note on two sources.
Since you're not clearing the combobox's items in the populate method, if it was called twice the combobox would gain new elements. That's not great but more importantly templateNames[chosenObject] would not work anymore because the combobox and the list would be out of sync.
In addition to the var templateNames mistake that doesn't cause the problem, you don't call PopulateComboBox in the code you provide, so the list is empty...
I don't understand why this method returns the Box since there is no parameter and it is a member field that you access directly in the code.
You only need to assign the event in the constructor, not each time you call the populate method that you need to call somewhere.
You should improve your class design because there is several clumsiness. For example, what is TemplateListCombobox without parent and how do you intend to use it? How are initialized _iReader and _iHandler? Do you need to keep templateNames? And so on...
I have list, listbox, button and a textbox.
My idea is to make that by clicking on the button, the content of the textbox is added to the list, and then pass the data to a listbox.
My problem is, if you add what I write, but the items that are in the listbox are overwritten by the new one that you insert. and I want only more items added. to the list and to go to the listbox. Thank you very much for your answers. This is the code of my button:
private void button54_Click(object sender, RoutedEventArgs e)
{
List<Playlists> List1 = new List<Playlists>();
List1.Add(new Playlists(textBox3.Text, #rutaalbum));
lbListbox.ItemsSource = List1;
}
I am just making a demo stand in for your Playlists class.
ToString has been overridden in order to display some text in the listbox. Without it, you will only display the class name.
ObservableCollection is used instead of List, so that the listbox updates when a new item is added.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MyList = new ObservableCollection<Playlists>();
MyListBox.ItemsSource = MyList;
}
private ObservableCollection<Playlists> MyList { get; }
private void Button_Click(object sender, RoutedEventArgs e)
{
MyList.Add(new Playlists(textBox3.Text));
}
}
public class Playlists
{
public Playlists(string title) { Title = title; }
public string Title { get; }
public override string ToString() { return Title; }
}
It seems , you are always creating items with new list. need to add items to existing list.
use below code. it would be useful.
private void button54_Click(object sender, RoutedEventArgs e) {
lbListBox.Items.Add(new Playlists(textBox3.Text, #rutaalbum));
}
I have bound to my combobox this simple class:
public class Company
{
public Guid CorporationId { set; get; }
public Guid TokenId { set; get; }
public string Name { set; get; }
}
And this is my binding:
private void FillCompaniesComboBox()
{
_doneLoadingComboBox = false;
comboBox_Companies.Items.Clear();
if (CurrentSettings.AllCompanies.Count == 0)
{
return;
}
bindingSource1.DataSource = CurrentSettings.AllCompanies;
comboBox_Companies.DataSource = bindingSource1.DataSource;
comboBox_Companies.DisplayMember = "Name";
comboBox_Companies.ValueMember = "CorporationId";
comboBox_Companies.SelectedIndex = 1;
_doneLoadingComboBox = true;
}
When I attempt to get the value of the selected item, I'm getting different results. Here is the code I am using to get my value:
private void comboBox_Companies_SelectedIndexChanged(object sender, EventArgs e)
{
if (!_doneLoadingComboBox && comboBox_Companies.SelectedIndex == -1)
{
return;
}
var value = (Company)comboBox_Companies.SelectedValue;
Console.WriteLine("Value: " + value.CorporationId);
}
Here is what is happening:
This one works at intended:
And this is were it is causing an issue:
Am I not retrieving the data correctly? I need the Company information that it is bound to.
Okay so here's what you need to do...
Assuming that your CurrentSettings.AllCompanies is an IList<Company> that you've already populated with data, here's what your code should look like:
public class ComboBoxItem {
// your class
private Company Comp;
}
private readonly BindingSource _bsSelectedCompany = new BindingSource();
private readonly ComboBoxItem _comboBoxItem = new ComboBoxItem();
// your main form method
public MainForm() {
// initialization code...
InitializeComponent();
// prevents errors in case your data binding objects are empty
ResetComboBox(comboBox1);
comboBox1.DataBindings.Add(new Binding(
"SelectedItem",
_bsSelectedCompany,
"Comp",
false,
DataSourceUpdateMode.OnPropertyChanged
));
comboBox1.DataSource = CurrentSettings.AllCompanies;
comboBox1.DisplayMember = "Name";
}
// simple method for resetting a given combo box to a default state
private static void ResetComboBox(ComboBox comboBox) {
comboBox.Items.Clear();
comboBox.Items.Add("Select a method...");
comboBox.SelectedItem = comboBox.Items[0];
}
By doing this, you're able to just use _comboBoxItem to safely get the information about your selected item without having to potentially Invoke it (in the case of accessing it on a separate thread).
I have combobox1 and comboox2 and in combobox1 my elements are A,B,C and combobox2 1,2,3,4,5,6...
A related 1,2,3 and B related 3,4 and C related 5,6.
When I choose "A" I want to see just 1,2,3 ; when select "B" just 3,4 etc.
How can I imagine it ?
I tried to do in selected index changed but I didn't
Try this , make a class like this:
public class Data
{
public string Name { get; set; }
public List<int> Values { get; set; }
}
then in your form have a variable like this:
private List<Data> data = new List<Data>
{
new Data{Name="A",Values=new List<int>{1,2,3}},
new Data{Name="B",Values=new List<int>{4,5}},
new Data{Name="C",Values=new List<int>{6,7}},
};
then in the form's constructor :
comboBox1.DisplayMember = "Name";
comboBox1.DataSource = data;
comboBox1.SelectedIndex = 0;
then on the combobox1's selectedindex changed event do like this:
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
int index = comboBox1.SelectedIndex;
comboBox2.DataSource = data[index].Values;
}
It should work.