So, I have two options:
aComboBox.Item.AddRange(stringArray);
aComboBox.SelectedItem = stringFromStringArray;
and
aComboBox.DataSource = stringArray;
aComboBox.SelectedItem = stringFromStringArray;
Now, the first one is waaaaay slower when it comes to initialization (about 5-6 times). It does set the selected item properly, but still, it's very slow so I decided to go with the second one.
But, if I use the second one, the Items array within the aComboBox is not yet set when the second command is executed, so the selected item is the one at index 1, instead the one specified.
The question is, how do I get the performance of the second one with the functionality of the first one?
EDIT:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ComboBoxTest
{
class MainWindow : Form
{
string[] list = new string[1548];
TableLayoutPanel panel = new TableLayoutPanel();
public MainWindow() : base()
{
Height = 2000;
Width = 1000;
Random rand = new Random();
for (int i = 0; i < 1547; i++)
{
list[i] = rand.Next().ToString();
}
list[1547] = 5.ToString();
Button button = new Button();
button.Text = "Press me";
button.Click += Button_Click;
panel.Controls.Add(button, 0, 0);
panel.Height = 2000;
panel.Width = 1000;
Controls.Add(panel);
Show();
}
private void Button_Click(object sender, EventArgs e)
{
for (int i = 0; i < 36; i++)
{
ComboBox box = new ComboBox();
box.DataSource = list; //box.Items.AddRange(list);
box.SelectedItem = 5.ToString();
panel.Controls.Add(box, 0, i+1);
}
}
}
}
I reproduced the problem with this program. If you change it to addRange(), it will take a lot more time, but it will set the item.
Try adding a breakpoint to the SelectedItem and then look at the ComboBox.
If you set the one, the other will be null (DataSource vs. Items). The ComboBox seems to look into Items to check if the string exists within the list, and that's why it fails with DataSource method.
Bonus question: Why are all ComboBoxes working as one (try changing the value)?
The question is, how do I get the performance of the second one with
the functionality of the first one?
If you want it to work properly, you can move the line box.SelectedItem = 5.ToString(); to the line after adding boxes to panel.
When you use DataSource for your combo box, setting SelectedItem works only if your combo box exists on your form.
I'm not sure about performance, but sure about functionality.
Bonus question: Why are all ComboBoxes working as one (try changing
the value)?
Because they are bound to the same DataSource. In fact they are using a single BindingManagerBase.
You can use different BindingSource for them. Also you can bind them to list.ToList().
Use teh DataSource method and use the FindString method to find the index of your desired selected text:
string[] arrString = {"hello", "how r u", "fine" };
comboBox1.DataSource = arrString;
comboBox1.SelectedIndex=comboBox1.FindString("fine");
MessageBox.Show(comboBox1.SelectedIndex.ToString());
Related
Basically I'm making a program that allows you to add to a stackpanel another stackpanel with several horizontally aligned textboxes with the press of a button. So far, everything is working as intented. Here's my code so far ,Stacker is the name of the parent stackpanel and it starts off empty:
private void Add_Click(object sender, RoutedEventArgs e)
{
Stacker.Children.Add(NewXD(Stacker.Children.Count + 1));
}
public System.Windows.Controls.StackPanel NewXD(int XD)
{
System.Windows.Controls.StackPanel NewP = new StackPanel();
NewP.Orientation = Orientation.Horizontal;
System.Windows.Controls.TextBox HAHA = new TextBox();
HAHA.Name = "question" + XD.ToString();
//HAHA.Text = HAHA.Height.ToString()+" "+HAHA.Width.ToString();
HAHA.Height = Double.NaN;
HAHA.Width = 120;
HAHA.FontSize=20;
NewP.Children.Add(HAHA);
for (int i = 1; i < 6; i++)
{
System.Windows.Controls.TextBox newBox = new TextBox();
newBox.Name = "answer"+XD.ToString()+"_"+i.ToString();
newBox.Height = Double.NaN;
newBox.Width = 120;
NewP.Children.Add(newBox);
}
System.Windows.Controls.ComboBox correct = new ComboBox();
correct.Name = "correct" + XD.ToString();
for (int i = 1; i < 6; i++)
{
System.Windows.Controls.ComboBoxItem newItem = new ComboBoxItem();
newItem.Name = "ans" + XD.ToString() + "_" + i.ToString();
newItem.Content = i.ToString();
correct.Items.Add(newItem);
}
NewP.Children.Add(correct);
return NewP;
}
I apologize for the lack of seriousness in some of my code.
Now, what I need to do is for the child stackpanels to also contain independent file pickers that work like the one sampled in this thread: Open file dialog and select a file using WPF controls and C#
What I don't know how to perform is that each of these generated buttons have the same basic funcion but are linked with each of their corresponding textbox.
Thanks in advance :)
Edit: As I was writing this it occured to me that perhaps I could use the help of the child stackpanel's array-like properties to choose the corresponding textbox, because the file selector's textbox and button will always be the last two items in the stackpanel, but I'm not very sure how to perform this.
For functionality you can create an EventHandler that will be assigned to each button. Your event handler will then open File Dialog...
Buttons have Tag property which you could use to identify your TextBoxes, or you could derive from Button class and add AssociatedTextBox property for example.
Basically I am trying to create an attachment window utilizing keeping everything in lists for easy use later. So, every time the form loads it goes through everything in the list and creates both labels and buttons for them. There is no errors until I click my button. If I click any of the X buttons, I get an argument out of bounds exception on the click += line. What's interesting is why its being called? The click isn't supposed to add another event handler to itself. Its also interesting that on click the indicie is one greater than the total count so how its even executing that line is beside me considering it should never iterate higher that its max count. Any help would be greatly appreciated.
ArrayList attachmentFiles;
ArrayList attachmentNames;
public Attachments(ArrayList attachments, ArrayList attachmentFileNames)
{
InitializeComponent();
attachmentFiles = attachments;
attachmentNames = attachmentFileNames;
}
private void Attachments_Load(object sender, EventArgs e)
{
ScrollBar vScrollBar1 = new VScrollBar();
ScrollBar hScrollBar1 = new HScrollBar();
vScrollBar1.Dock = DockStyle.Right;
hScrollBar1.Dock = DockStyle.Bottom;
vScrollBar1.Scroll += (sender2, e2) => { pnl_Attachments.VerticalScroll.Value = vScrollBar1.Value; };
hScrollBar1.Scroll += (sender3, e4) => { pnl_Attachments.HorizontalScroll.Value = hScrollBar1.Value; };
pnl_Attachments.Controls.Add(hScrollBar1);
pnl_Attachments.Controls.Add(vScrollBar1);
Label fileName;
for (int i = 0; i < attachmentNames.Count; i++)
{
fileName = new Label();
fileName.AutoSize = true;
fileName.Text = attachmentNames[i].ToString();
fileName.Top = (i + 1) * 22;
pnl_Attachments.Controls.Add(fileName);
Button btn_RemoveAttachment = new Button();
btn_RemoveAttachment.Text = "X";
btn_RemoveAttachment.Tag = i;
btn_RemoveAttachment.Click += new System.EventHandler((s, e3) => removeAttachment(s, e3, attachmentFiles[i].ToString(), attachmentNames[i].ToString()));
btn_RemoveAttachment.Top = (i + 1) * 22;
btn_RemoveAttachment.Left = fileName.Right + 22;
pnl_Attachments.Controls.Add(btn_RemoveAttachment);
}
}
private void removeAttachment(object sender, EventArgs e, string file, string fileName)
{
attachmentNames.Remove(fileName);
attachmentFiles.Remove(file);
pnl_Attachments.Controls.Clear();
this.Close();
}
In my test, attachmentFiles had a count of 3 and attachmentNames had a count of 3. On form load, there are no issues. But, on button click I get an exception because somehow its trying to add another click listener to a button with i = 3 (a.k.a a 4th element)
The problem is not with the event subscription, but with the event handler execution.
You are running into this problem because a closure is created for the event handler, but the value i is modified by the for loop. The last value of i will be 1 + attachmentNames.Count and this will be the value used by each invocation of the event handler.
For more detail as to why this happens you can check out the question and answer here: Access to Modified Closure.
To resolve the problem, you can assign i to another variable:
var currentAttachmentIndex = i;
btn_RemoveAttachment.Click += new System.EventHandler((s, e3) => {
removeAttachment(s,
e3,
attachmentFiles[currentAttachmentIndex].ToString(),
attachmentNames[currentAttachmentIndex].ToString())
});
Or you can use the value you already captured in the Tag property of the btn_RemoveAttachment control.
btn_RemoveAttachment.Click += new System.EventHandler((s, e3) => {
var senderButton = (Button)s;
var currentAttachmentIndex = (int)senderButton.Tag;
removeAttachment(s,
e3,
attachmentFiles[currentAttachmentIndex].ToString(),
attachmentNames[currentAttachmentIndex].ToString())
});
Keep in mind, though, if you are removing items from the List, the indexes will not be valid. Understanding how closures work, however, should help you solve that problem if it arises (it looks like you close the form anyway after the first removal).
Presumably, the attachmentFiles[i] is what is causing the out of bounds exception, perhaps attachmentFiles has fewer elements than attachmentNames?
Why not set a breakpoint on that line and check what is causing the out of bounds exception?
I get an argument out of bounds exception on the click += line. What's interesting is why its being called? The click isn't supposed to add another event handler to itself
It looks like the exception is not thrown at the event subscription (+=) but at the lambda function declared in that same line
Its also interesting that on click the indicie is one greater than the total count so how its even executing that line is beside me considering it should never iterate higher that its max count. Any help would be greatly appreciated
The value of i is fixed when you assign the lambda to the event. The indexes at the attachmentFiles change as you remove elements, but the indexes used by the lambda to access it don't. Let's try an example.
let's assume we have an array with 4 attchements (index:attachment))
[0:att0, 1:att1, 2:att2, 3:att3]
And 4 buttons that execute this lambdas
[removeAt(0), removeAt(1), removeAt(2), removeAt(3)]
We click the second button and it correctly removes the second attachment from array, now we have:
[0:att0, 1:att2, 2:att3]
[removeAt(0), removeAt(1), removeAt(2), removeAt(3)]
Now we click the fourth button. It tries to remove the attachment with index 3 and the out of bounds exception is thrown because that index doesn't exist anymore (and even if it existed, it might not point to the right attachment!)
Another approach would be to modify your 'removeAttachment' method, and use that as your event handler for all buttons.
An example of this would be:
for (int i = 0; i < attachmentNames.Count; i++)
{
var lbl_FileName = new Label
{
AutoSize = true,
Name = "Label" + i, // Give this a name so we can find it later
Text = attachmentNames[i],
Top = (i + 1) * 22
};
var btn_RemoveAttachment = new Button
{
Text = "X",
Tag = i,
Top = (i + 1) * 22,
Left = lbl_FileName.Right + 22
};
btn_RemoveAttachment.Click += removeAttachment;
pnl_Attachments.Controls.Add(lbl_FileName);
pnl_Attachments.Controls.Add(btn_RemoveAttachment);
}
Then you can modify your removeAttachment method to be an EventHandler, and to detect the button and associated label using the sender As Button and Button.Tag property:
private void removeAttachment(object sender, EventArgs e)
{
// Get associated Label and Button controls
var thisButton = sender as Button;
var index = Convert.ToInt32(thisButton.Tag);
var thisLabel = (Label) Controls.Find("NameLabel" + index, true).First();
// Remove the files
int itemIndex = attachmentNames.IndexOf(thisLabel.Text);
attachmentNames.RemoveAt(itemIndex);
attachmentFiles.RemoveAt(itemIndex);
// Disable controls and strikethrough the text
thisButton.Enabled = false;
thisLabel.Font = new Font(thisLabel.Font, FontStyle.Strikeout);
thisLabel.Enabled = false;
}
i'm stuck....
this my code to add items to my listview:
ListViewItem item = new ListViewItem(ProjectDomainName);
item.Tag = relatedProject.ProjectId;
lvwSelectedProjects.Items.Add(item);
when i choose 'View.List' as viewmode, i see all items.
When i choose 'View.Details' (which is the setting that i want) i see.... nothing. Well, nothing, i DO get a vertical scrollbar, but no items. And i can scroll too, but no items....
I also added a column in the listview (didn't change the add items code), but that also didn't work
i must be overlooking something?
This code works for me:
using System;
using System.Windows.Forms;
public class LVTest : Form {
public LVTest() {
ListView lv = new ListView();
lv.Columns.Add("Header", 100);
lv.Columns.Add("Details", 100);
lv.Dock = DockStyle.Fill;
lv.Items.Add(new ListViewItem(new string[] { "Alpha", "Some details" }));
lv.Items.Add(new ListViewItem(new string[] { "Bravo", "More details" }));
lv.View = View.Details;
Controls.Add(lv);
}
}
public static class Program {
[STAThread] public static void Main() {
Application.Run(new LVTest());
}
}
Try this code for yourself in an empty project. Then, focus on adapting it to your application: compare how your program is different from this code, and work on changing it to more closely match mine. It's OK if you lose functionality in your program; just try to get a basic version working. Then, add functionality back bit by bit so you can be sure that the program still works every step of the way.
If you're still stuck, post more code from your project and we might have a better idea of why you're having trouble.
Another possible cause of blank items when listview.View = View.Details, is if you don't add any columns to the listview.
For example:
ListView lv = new ListView();
lv.View = View.Details;
lv.Items.Add("Test");
.. will result in a blank ListView.
Adding a column will correct:
...
lv.View = View.Details;
// Add one auto-sized column, to show Text field of each item.
lv.Columns.Add("YourColumnTitle", -2);
...
Because, you should be using a ListViewDataItem instead of a ListViewItem, observe ...
for (int i = 0; i < AudioCdWriter.FileCount; ++i) {
var item = new ListViewDataItem(i.ToString());
item.SubItems.Add(AudioCdWriter.TrackLength((short)i).ToString());
item.SubItems.Add(AudioCdWriter.file[(short)i]);
lvwAudioFiles.Items.Add(item);
}
This happened to me as well (listview not showing items in details view) I just put the following into the code (previously was only in the design) after adding items to the listview and it started showing the items.
lv.View = View.Details;
ListViewItem item = new ListViewItem("item1");
item.SubItems.Add("subitem"); //add subitem if applicable
listview1.Items.Add(item);
this Result can be sold your problem
I've got a Windows Forms application with two ListBox controls on the same form.
They both have their SelectionMode set to 'MultiExtended'.
When I change the selection of one the selection of the other changes.
Now I thought I'd done something stupid with my SelectedIndexChanged handlers so I removed them and re-wrote them from scratch, and got the problem.
So I created a brand new WinForms app and dragged two ListBoxes onto the forms surface.
In the constructor I populated them both with the following.
List<Thing> data = new List<Thing>();
for ( int i = 0; i < 50; i++ ) {
Thing temp = new Thing();
temp.Letters = "abc " + i.ToString();
temp.Id = i;
data.Add(temp);
}
listBox1.DataSource = data;
listBox1.DisplayMember = "Letters";
listBox1.ValueMember = "Id";
List<Thing> data2 = new List<Thing>();
for ( int i = 0; i < 50; i++ ) {
Thing temp = new Thing();
temp.Letters = "abc " + i.ToString();
temp.Id = i;
data2.Add(temp);
}
listBox2.DataSource = data2;
listBox2.DisplayMember = "Letters";
listBox2.ValueMember = "Id";
And then I built and ran the app.
Started selecting some values to see if the symptoms were present.
And they were!
This is literally all the code I added to the form,I had not added any event handlers, I have tried it with the SelectionMode set to 'One' and 'MultiExtended'.
Can anyone give me a clue as to why this is happening.
Cheers
It isn't the list that stores the current position - it is the CurrencyManager. Any controls (with the same BindingContext) with the same reference as a DataSource will share a CurrencyManager. By using different list instances you get different CurrencyManager instances, and thus separate position.
You could achieve the same simply by using .ToList(), or creating a new List<T> with the same contents (as per your original post), or by assigning a new BindingContext to one of the controls:
control.BindingContext = new BindingContext();
I believe that your two controls are sharing a CurrencyManager. I'm not sure exactly why.
As a workaround, you could try just populating your listboxes with simple strings. Or you may want to try creating separate instances of the BindingSource component, and bind to those.
I experienced the same when I used the same datasource on both listboxes, but creating two equal datasources solved the problem for me. I can see nothing wrong with your code. Anything particular with the Thing class ? Or does it just contain the two members Letters and Id ?
And more...
I finally got ot the bottom of it.
I was binding the two ListBoxes to the same List. by changing the code to
theListBox.DataSource = _contacts.Take(_contacts.Count).ToList();
the issue was curcumvented.
It seems that the reference to the List that it stored also caries any binding or selection information over to the other ListBox.
be careful. ;)
I created a new win forms app with two list boxes on it. They behave as I would expect. Can you post the complete code? Here is my code.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private class Thing
{
public String Letters { get; set; }
public Int32 Id { get; set; }
}
private void Form1_Load(object sender, EventArgs e)
{
List<Thing> data = new List<Thing>();
for (int i = 0; i < 50; i++)
{
Thing temp = new Thing();
temp.Letters = "abc " + i.ToString();
temp.Id = i;
data.Add(temp);
}
listBox1.DataSource = data;
listBox1.DisplayMember = "Letters";
listBox1.ValueMember = "Id";
List<Thing> data2 = new List<Thing>();
for (int i = 0; i < 50; i++)
{
Thing temp = new Thing();
temp.Letters = "abc " + i.ToString();
temp.Id = i;
data2.Add(temp);
}
listBox2.DataSource = data2;
listBox2.DisplayMember = "Letters";
listBox2.ValueMember = "Id";
}
}
I tried to duplicate this behavior on my own machine and was unable to do so running just the code your provided.
I stand corrected.
In another instance of VS in a brand new solution, this works as expected.
God only know's what i've done to make it do what it's doing
We have a custom collection of objects that we bind to a listbox control. When an item is added to the list the item appears in the listbox, however when one selects the item the currency manager position will not go to the position. Instead the currency manager position stays at the existing position. The listbox item is high lighted as long as the mouse is press however the cm never changes position.
If I copy one of the collection objects the listbox operates properly.
One additional note the collection also has collections within it, not sure if this would be an issue.
I found the issue, after spending way too much time....
This issue was related to one of the propertys of the item(custom class) in the collection which was bound to a date picker control. The constructor for the class never set the value to a default value.
This caused an issue with the currency manager not allowing the position to change as the specific property (bound to the date picker) was not valid.
Me bad! I know better!
You might need to post some code; the following (with two lists tied together only by the CM) shows that it works fine... so to find the bug we might need some code.
using System;
using System.ComponentModel;
using System.Windows.Forms;
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
BindingList<Foo> foos = new BindingList<Foo>();
foos.Add(new Foo("abc"));
foos.Add(new Foo("def"));
ListBox lb1 = new ListBox(), lb2 = new ListBox();
lb1.DataSource = lb2.DataSource = foos;
lb1.DisplayMember = lb2.DisplayMember = "Bar";
lb1.Dock = DockStyle.Left;
lb2.Dock = DockStyle.Right;
Button b = new Button();
b.Text = "Add";
b.Dock = DockStyle.Top;
b.Click += delegate
{
foos.Add(new Foo("new item"));
};
Form form = new Form();
form.Controls.Add(lb1);
form.Controls.Add(lb2);
form.Controls.Add(b);
Application.Run(form);
}
}
class Foo
{
public Foo(string bar) {this.Bar = bar;}
private string bar;
public string Bar {
get {return bar;}
set {bar = value;}
}
}
Collections don't have a sense of "current item". Perhaps your custom collection does, but the ListBox is not using that. It has its own "current item" index into the collection. You need to handle SelectedIndexChanged events to keep them in sync.