I have an object that has some attributes from the list selected - let's say a Promotion that can have 0 to X communication channels. To display/edit this information I am using a listbox with option SelectionMode==MultiExtended.
But in some cases it is behaving strangely
I have Promotion with 2 communication channels selected (first and last out of three channels),
I click on a second channel (that previously was the only unselected channel) and know it shows, that 1st and 2nd channels are selected (I placed a check at the beginning of the listbox SelectedIndexChanged event - and it shows that SelectedItems.Count==2, although I clicked on a single item not holding Ctrl or Shift keys) and in this case SelectedIndexChanged event is triggered twice in all other cases it is triggered just once
This happens only after the first time I open this dialogform, if I manually select 1st and 3rd item of Channels, and then click on the 2nd item - then it works properly
Screencast of a problem in action
http://screencast.com/t/lVs0e9oau
This is how I load list of all possible channels into listbox
foreach (var ct in Promotion_operations.Configuration.PromoCommunicationTypes)
{
KeyValuePair<string, PromotionCommunicationType> nct =
new KeyValuePair<string, PromotionCommunicationType>(ct.Name, ct);
communications.Add(nct);
}
PromotionCommunicationList.DataSource = communications; //Promotion_operations.Configuration.PromoCommunicationTypes;
PromotionCommunicationList.DisplayMember = "Key";
PromotionCommunicationList.ValueMember = "Value";
This is how I load selecteditems based on Promotion's data
private void LoadSelectedCommunicationsList(ListBox lstbox, List<PromotionCommunication> communications)
{
lstbox.SelectedItems.Clear();
foreach (var ct in communications)
{
for (int j = 0; j < lstbox.Items.Count; j++)
{
if (ct.CommunicationType.Id == ((KeyValuePair<string, PromotionCommunicationType>)lstbox.Items[j]).Value.Id)
{
lstbox.SelectedItems.Add(lstbox.Items[j]);
}
}
}
}
What could be the cause of this behaviour?
that clicking on one previously unselected list selects both - newly selected item and first item of the list?
Your PromotionCommunicationList and HistoryCommunicationList are sharing the same reference to your list of objects as DataSource. That said, they have the same BindingContext and share the same CurrencyManager. CurrencyManager is remembering selected items of your ListBox control and that's where your conflict is created because he's saving selected items of both of your ListBoxes. You already found the solution for your problem because new CurrencyManager is created when you set "different" list (the copy of your original one) as DataSource. Another possible solution would be the creation of new BindingContext for one of your ListBox controls.
You can try this out:
PromotionCommunicationList.DataSource = communications;
(..)
HistoryCommunicationList.BindingContext = new BindingContext(); // Add this
HistoryCommunicationList.DataSource = communications;
It should solve your problem. For more information about BindingContext check this link on MSDN.
I found the cause of the problem, though I don't really understand why it caused such a behaviour (if someone will answer that question, I will accept it as an answer to this question)
I had 2 listbox-es in my form and both of them where using the same collection as a Datasource, BUT!!! SelectedItems was selected using code (acctually it seems that in winforms it is not possible to databind listbox's selecteditems)
INITIALLY My code was:
PromotionCommunicationList.DataSource = communications;
(..)
HistoryCommunicationList.DataSource = communications;
Corrected version is:
PromotionCommunicationList.DataSource = communications.ToList();
(..)
HistoryCommunicationList.DataSource = communications.ToList();
I know that ToList() makes a copy, but I don't understand what's wrong with having the same collection as DataSource for list items of 2 listbox-es? Why does this have an impact on SelectedItems collection?
Related
I have a form that takes in an object in it's constructor and populates controls on the form from properties in that object. I am having an issue where I can't set a ComboBox's SelectedText property, or at least it isn't working how I expect it to.
public Form(ValueHoldingObject obj)
{
// yeah I know this is not a very clean way to populate the combobox, the issue
// isn't limited to the combobox so I don't think this is relevant
List<int> items = Repo.GetAllItems().Reverse();
foreach (int id in checkInPrizeIds.Take(100))
// Insert at beginning to put more recently used items at the top
combobox.Items.Insert(0, id);
combobox.DropDownHeight = 200;
combobox.SelectedText = obj.StringProperty;
}
When I am testing this form the text of the combobox isn't being populated. If I add a breakpoint on the line where I assign the text it DOES get assigned, so some event is firing (multiple focus change events probably) and making it work the way I want. Obviously I can't use a breakpoint as a fix in production code. Am I assigning this value incorrectly? Should I be using a different method to populate the values?
Further testing has reviled that it isn't just the combobox, all of my controls are only being populated correctly if I have the breakpoint.
In the constructor, you need to set the selected item, for example:
foreach ( var item in combobox.Items )
if ( (string)item == obj.StringProperty )
combobox.SelectedItem = item;
Or:
foreach ( var item in combobox.Items )
if ( (int)item == Convert.ToInt32(obj.StringProperty) )
combobox.SelectedItem = item;
It's confusing but despite its name, the property SelectedText is not really the selected item... because combo box items are objects and not strings: texts shown are a representation of the item objects using ToString().
Therefore setting the selected text will not guarantee to select an item and we can prefer setting the SelectedItem.
In addition to these considerations, you set the selected text property in the constructor after populating the combo box and that can cause problems because it is before the form and the control are drawn or something like that... that is to say perhaps before the ToString() methods are called on items to prepare the visual cache, so setting the selected text can't get a match with the list.
Setting the selected text selects an existing item if done in the form load or shown events.
private void Form_Load(object sender, EventArgs e)
{
combobox.SelectedText = obj.StringProperty;
}
ComboBox.SelectedText doesn't give me the SelectedText
ComboBox.SelectedText Property
I am working in a simple C# form. I have a total of 20 ComboBoxes . 10 ComboBoxes will contain similar data type and will have very similar name (CB1, CB2, CB3, ... CB10). Each ComboBox was loaded with a list of 5 elements A,B,C,D,E (by this I meant that I added those values to each of the 10 "CB" ComboBoxes). My intend is that the user can have the ability to select items (one out of A,B,C,D,E) from either 1 combobox, or 2 comboboxes, . . . ., or all of them (10 comboboxes).
I wish to store the data from the ComboBoxes where an item was selected in a list or array. For that I would like to iterate through the 10 ComboBoxes (the comboboxes which names are CB1, CB2, CB3, ..., CB10), check if the an item was selected in the combobox, and store the value selected in the ComboBox into a list (in the code below the list is called symbols). At the end the length of my symbols list (number of items) will depend on how many ComboBoxes the user selected from. Here is the code I am using:
List<string> symbols = new List<string>();
for (int i = 1; i <= 10; i++)
{
var my_comboBox = this.Controls["CB" + i.ToString()];
if (null != my_comboBox.SelectedItem)
{ symbols.add(my_comboBox.Text); }
}
when I run the code I get the following error.
Object reference not set to an instance of an object.
Could anyone please explain what I am doing wrong? I got the code from another question that was posted and answered, below is the link to that question. Thanks in advance.
How can I iterate all ComboBoxes controls with a loop in C#?
I also tried the other alternative proposed on the answers to the questions cited. But it did not work. It does not go through the foreach loop (no error is shown though). Here is the code for option 2.
List<string> symbols = new List<string>();
var comboBoxes = this.Controls.OfType<ComboBox>().Where(x => x.Name.StartsWith("CB"));
foreach (var cmbBox in comboBoxes)
{
if (null != my_comboBox.SelectedItem)
{ symbols.add(my_comboBox.Text); }
}
Again if anyone can please provide me with ideas to what I am doing wrong that would be very nice. Thanks in advance once more.
The ComboBoxes belong to the form as shown in the picture below. In there the ComboBoxes are called component_CB1, component_CB2, component_CB3, ..., component_CB10 (I changed the name of the ComboBoxes in the question to CB to make it easier to understand).
screenshot of my solution explorer
Thanks to everyone who contributed to find the answer to this problem. Please read the comments to identify the contributors.
The answer is that you can iterate through selected ComboBoxes in C#. However for this to work you need to make sure you know to what container control your ComboBoxes are added.
To know to container control your ComboBoxes are added to, go to View → Other Windows → Document Outline. You can see if those controls are directly children of the form or they are children of another container control.
If the ComboBoxes are added to your form directly, then here there are two alternatives to iterate through the ComboBoxes:
ALTERNATIVE 1: (ComboBoxes added to the form directly)
List<string> symbols = new List<string>();
for (int i = 1; i <= 10; i++)
{ //CB is the begining of the name of the comboboxes CB1, CB2, ... CB10
var my_comboBox = this.Controls["CB" + i.ToString()];
if (null != my_comboBox.SelectedItem)
{ symbols.add(my_comboBox.Text); }
}
ALTERNATIVE 2: (ComboBoxes added to the form directly)
List<string> symbols = new List<string>();
//CB is the begining of the name of the comboboxes CB1, CB2, ... CB10
var comboBoxes = this.Controls.OfType<ComboBox>().Where(x=>x.Name.StartsWith("CB"));
foreach (var cmbBox in comboBoxes)
{
if (null != my_comboBox.SelectedItem)
{ symbols.add(my_comboBox.Text); }
}
If the ComboBoxes are added to a different control container (e.g. a tab in a TabControl as in my initial case), you must specify the control container name rather than "this". Here are two alternatives taking as example the ComboBoxes Cb1, CB2, CB3, ..., CB10 that are added to a tab called tab1.
ALTERNATIVE 1: (ComboBoxes added to a tab of a TabControl)
List<string> symbols = new List<string>();
for (int i = 1; i <= 10; i++)
{//CB is the begining of the name of the comboboxes CB1, CB2, ... CB10
var my_comboBox = tab1.Controls["CB" + i.ToString()];
if (null != my_comboBox.SelectedItem)
{ symbols.add(my_comboBox.Text); }
}
ALTERNATIVE 2: (ComboBoxes added to a tab of a TabControl)
List<string> symbols = new List<string>();
//CB is the begining of the name of the comboboxes CB1, CB2, ... CB10
var comboBoxes = tab1.Controls.OfType<ComboBox>().Where(x
=>x.Name.StartsWith("CB"));
foreach (var cmbBox in comboBoxes)
{
if (null != my_comboBox.SelectedItem)
{ symbols.add(my_comboBox.Text); }
}
Thanks for the help again. I hope this can be useful for others.
Hi there I have searched for a while now and can't seem to find a solution to my problem, I have tried multiple methods to select multiple items in my listbox through code however none have worked, The best result I got was 1 selected item in my listbox.
Basically I want to select multiple items of the same value.
below is my code, sorry if I seem newbie but I am new to programming and still learning basic stuff.
foreach (string p in listBox1.Items)
{
if (p == searchstring)
{
index = listBox1.Items.IndexOf(p);
listBox1.SetSelected(index,true);
}
}
So as you can see I am trying to tell the program to loop through all the items in my listbox, and for every item that equals "searchstring" get the index and set it as selected.
However all this code does is select the first item in the list that equals "searchstring" makes it selected and stops, it doesn't iterate through all the "searchstring" items.
As suggested in the comment, you should set SelectionMode to either MulitSimple or MultiExpanded depending on your needs, but you also need to use for or while loop instead offoreach, because foreach loop doesn't allow the collection to be changed during iterations. Therefore, even setting this Property won't make your code run and you will get the exception. Try this:
for(int i = 0; i<listBox1.Items.Count;i++)
{
string p = listBox1.Items[i].ToString();
if (p == searchstring)
{
listBox1.SetSelected(i, true);
}
}
You can set SelectionMode either in the Properties window when using designer or in, for instance, constructor of your Form using this code:
listBox1.SelectionMode = System.Windows.Forms.SelectionMode.MultiSimple;
I have a drop down list control populated with items and some code to take the currently selected item value. The problem is I only get the value of the first item in the list regardless of what item is actually selected.
Here is my code to populate the drop down:
protected void displayCreateCategories()
{
StoreDataContext db = new StoreDataContext();
var a = from c in db.Categories
orderby c.Name
select new{catName= c.Name,
catId=c.CategoryID};
ddlCategory.DataSource = a;
ddlCategory.DataTextField = "catName";
ddlCategory.DataValueField = "catId";
ddlCategory.DataBind();
}
To get the value of the currently selected item which in my case is always of type integer I do label1.text=Convert.toInt32(ddlCategory.SelectedValue);
I get the selected value, but it is always for the 1st item in the list. I'm pulling my hair out over this. :(
I suspect you're running the list loading code every time the page loads, which is destroying the list, repopulating the list, and auto-selecting the first item before your selection retrieval code gets run.
Use this construction in Page_Load:
if (!IsPostBack)
{
// Initial control population goes here
}
Data binding will reset the control's selected value so make sure you retrieve the selected value before data binding on postback.
Ok, this has been a head scratcher for me. I have a ListBox I am binding to a linq query like so:
private IQueryable<Feed> _feeds;
public IQueryable<Feed> Feeds
{
get
{
if (_feeds == null)
{
var feedsQuery = from f in _db.Feed orderby f.Title select f;
_feeds = feedsQuery;
}
return _feeds;
}
}
public Options()
{
InitializeComponent();
this.DataContext = Feeds;
}
(For the record I've also tried List, instead of IQueryable)
Everything shows up great and I have a databound form that allows you to edit a record and all of those changes work just fine, the modified data shows up in the list.
The problem comes with I add an item. Nothing shows up in the list. The data goes into the database fine, but the only way to see the data is closing and restarting my app. I'm using the code below as an example:
Feed feed = new Feed()
{
ID = Guid.NewGuid(),
Url = "http://www.test.com",
Title = "Test"
};
_db.Feed.InsertOnSubmit(feed);
_db.SubmitChanges();
_db.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues);
(with or without the _db.Refresh nothing happens)
What's going on?
You are doing everything right, you jus need to use ObservableCollection. This will notify the ListBox about any changes in the list and refresh it automatically.
From MSDN
In many cases the data that you work
with is a collection of objects. For
example, a common scenario in data
binding is to use an ItemsControl
such as a ListBox, ListView, or
TreeView to display a collection of
records.
P.S. you don't need a db refresh
Unless notified otherwise, the ListBox only iterates once over its ItemsSource. Your query is only being run once.
The query object doesn't know when the database changes (and Refresh doesn't help; see below)--it's up to you to know (or anticipate) that and to rerun relevant queries at the appropriate times.
Stan R mentions ObservableCollection. That's fine, but simply storing the result of your query in an ObservableCollection won't solve the problem unless you do some work to update the collection yourself when the database changes. This means rerunning the query and manually adding new items and removing deleted items from the collection. (You could alternatively just rerun the query and set the entire result back in to the ListBox, but that means a whole new set of items will be created--not very performant, and maybe not what you want for other reasons.)
As an aside, your call to DataContext.Refresh is probably not doing what you think it is. From the docs:
This method is useful after an optimistic concurrency error to bring items into a state for another attempt. It updates the state of the primitive fields and properties on the objects.
Okay. I'm not positive this is 100% the correct way to use the ObservableCollection, but this seems to work:
private ObservableCollection<Feed> _feeds;
public ObservableCollection<Feed> Feeds
{
get
{
if (_feeds == null)
{
var feedsQuery = from f in _db.Feed orderby f.Title select f;
_feeds = new ObservableCollection<Feed>();
foreach (var item in feedsQuery)
{
_feeds.Add(item);
}
}
return _feeds;
}
}
And add my item:
Feed feed = new Feed()
{
ID = Guid.NewGuid(),
Url = "http://www.test.com",
Title = "Test"
};
_db.Feed.InsertOnSubmit(feed);
_db.SubmitChanges();
// manually update the list
Feeds.Add(feed);
It took me a little while to figure out I had to update the list manually (thanks Ben), but it all seems to work. Sorting would be nice, but I'll worry about that another time.