My current set up has a class with only 2 variables currently (string type and int amount).
I override the ToString to print both of those things together. In my form I instantiate different instances of this class and populate the combo box just fine, it prints my override.
My question is how do I determine which instance is being selected? I can use selecteditem to retrieve my tostring override fine, but what if i want to alter the amount variable of a specific instance if its selected?
SelectedItem.Instance.VariableName
I imagine it would be something like this, I'm just not familiar with that syntax.
You can add the object to the combobox and then use Display member to determine which property is shown.
https://msdn.microsoft.com/en-us/library/system.windows.forms.listcontrol.displaymember(v=vs.110).aspx
Well, that depends on how you populate the data into your ComboBox. For instance, if you use data binding, you can do something like the following:
Dictionary<string, YourClass> dict = new Dictionary<string, YourClass>();
for (int x = 0; x <= 5; x++)
{
YourClass instance = new YourClass("Test", x);
dict.Add(instance.ToString(), instance);
}
ComboBox1.DataSource = new BindingSource(dict, null);
ComboBox1.DisplayMember = "key";
ComboBox1.ValueMember = "value";
So you can easily interact with the interact with each instance based on the selected item of your ComboBox:
Console.WriteLine(((YourClass)ComboBox1.SelectedValue).amount.ToString());
Hope that helps :)
Create a List<> of your instances and set that as the DataSource() of your ComboBox. Then you can retrieve the selected item, update it somehow, then reset the DataSource causing the ComboBox to display the new values:
private List<Thing> things = new List<Thing>();
private void Form1_Load(object sender, EventArgs e)
{
Thing thing1 = new Thing();
thing1.Item = "Bob";
thing1.Value = 411;
Thing thing2 = new Thing();
thing2.Item = "Joe";
thing2.Value = -1;
things.Add(thing1);
things.Add(thing2);
comboBox1.DataSource = things;
}
private void button1_Click(object sender, EventArgs e)
{
if (comboBox1.SelectedIndex != -1)
{
Thing thing = (Thing)comboBox1.SelectedItem;
// now do something with "thing":
thing.Value = thing.Value + 1;
// reset the ComboBox to update the entries:
comboBox1.DataSource = null;
comboBox1.DataSource = things;
comboBox1.SelectedItem = thing;
}
}
With Class Thing:
public class Thing
{
public string Item = "";
public int Value = 0;
public override string ToString()
{
return Item + ": " + Value.ToString();
}
}
Related
Winforms, C#, VS2017
ImageList does not have an Insert method (however ListViewItemCollection does). I have tried a few different ways to insert a new image into the middle of a ListView and it's LargeImageList, but not getting it to work quite properly.
Anyone have any tried and true code that works properly?
This is what I have, but the images don't get synced properly to the items in the list.
protected void InsertThumbnail(string key, string keySelected)
{
var newImageList = new ImageList()
{
ImageSize = new Size(thumbWidth, thumbHeight)
};
var itemNew = new ListViewItem();
var foundSelected = false;
//lvAllPages.BeginUpdate();
for (int i = 0; i < lvAllPages.Items.Count; i++)
{
var item = lvAllPages.Items[i];
newImageList.Images.Add(item.Tag.ToString(), lvAllPages.LargeImageList.Images[i]);
if (item.Tag.ToString() == keySelected)
{
var image = batch.GetThumbnail(key);
newImageList.Images.Add(key, image);
itemNew = new ListViewItem()
{
BackColor = Color.Aquamarine,
ImageIndex = i,
Tag = key,
};
if (isLocal)
itemNew.Text = $"{GetFileName(key)} (insert) - {itemNew.ImageIndex}";
foundSelected = true;
}
if (foundSelected)
{
item.ImageIndex = item.ImageIndex + 1;
if (isLocal)
item.Text = $"{GetFileName(item.Tag.ToString())} - {item.ImageIndex}";
}
}
lvAllPages.LargeImageList.Dispose();
lvAllPages.LargeImageList = newImageList;
lvAllPages.Items.Insert(itemNew.ImageIndex, itemNew);
}
One more related thing, but not pertinent to the problems I am having. For anyone looking at this question and having similar issues, this helped with the issue of sorting items after inserting a new one. Default behavior when you insert a new ListViewItem at a given index, it will appear at the bottom of the list. I found this handy class to keep items sorted by index, which solved that problem:
class CompareByIndex : IComparer
{
private readonly ListView _listView;
public CompareByIndex(ListView listView)
{
this._listView = listView;
}
public int Compare(object x, object y)
{
int i = this._listView.Items.IndexOf((ListViewItem)x);
int j = this._listView.Items.IndexOf((ListViewItem)y);
return i - j;
}
}
And in the form load:
lvAllPages.ListViewItemSorter = new CompareByIndex(lvAllPages);
Obviously, that's a design decision. ImageList.Images is a ImageCollection and as such, it implements the IList interface.
Unfortunately, the Insert() method is allowed to throw a NotSupportedException. And that's what the list will do when used like a IList:
((IList)imageList.Images).Insert(5, new Bitmap(10,10));
System.NotSupportedException: 'Specified method is not supported.'
In order to have the images shown in a specific order, use the Add() method which takes the key:
imageList.Images.Add("1", new Bitmap(100,100));
That should also enable you to replace the image:
imageList.Images.RemoveByKey("1");
imageList.Images.Add("1", new Bitmap(200,200));
For that to work, you need to set the Sorting property:
listView1.Sorting = SortOrder.Ascending;
For storing additional information like path etc. use anotther data structure with the same key.
Here's the code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
ImageList imageList = new ImageList();
Dictionary<string, Metadata> metadata = new Dictionary<string, Metadata>();
private string dir = #"H:\temp";
private void button1_Click(object sender, EventArgs e)
{
// You would set this in the designer, probably
listView1.Sorting = SortOrder.Ascending;
listView1.View = View.LargeIcon;
listView1.LargeImageList = imageList;
// Make sure we start from the beginning
listView1.Items.Clear();
imageList.Images.Clear();
metadata.Clear();
// Add items
for (int i = 0; i < 10; i++)
{
var filename = "1 ("+(i+1)+").png"; // Just strange names I have
var fullFileName = Path.Combine(dir, filename);
imageList.Images.Add(i.ToString(), Bitmap.FromFile(fullFileName));
metadata.Add(i.ToString(), new Metadata{Path = fullFileName});
listView1.Items.Add(i.ToString(), "Image " + i, i.ToString());
}
// Update view
listView1.Refresh();
listView1.Invalidate();
}
private void button2_Click(object sender, EventArgs e)
{
for (int i = 3; i < 6; i++)
{
var filename = "1 ("+(i+2)+").png";
var fullFileName = Path.Combine(dir, filename);
// Change image
imageList.Images.RemoveByKey(i.ToString());
imageList.Images.Add(i.ToString(), Bitmap.FromFile(fullFileName));
// Match metadata and image
metadata[i.ToString()] = new Metadata{Path = fullFileName};
}
listView1.Refresh();
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
if (listView1.SelectedItems.Count > 0)
{
var key = listView1.SelectedItems[0].ImageKey;
label1.Text = metadata[key].Path;
}
else
{
label1.Text = "No image selected";
}
}
}
internal class Metadata
{
internal string Path;
}
I have the following class which fills a list as shown bellow from an event when a button bound to each column is clicked on a DataGridView called MenuGrid:
public class orderedfood
{
public string price;
public string item;
}
List<orderedfood> order = new List<orderedfood>();
private void MenuGrid_CellClick(object sender, DataGridViewCellEventArgs e)
{
order.Add(new orderedfood { item = MenuGrid.Rows[e.RowIndex].Cells[1].Value.ToString(), price = subtotal.ToString() });
}
This MenuGrid has the following format:
What I want to do is to reload the DataGridView bound to the order List, hence I tried the following code:
MenuGrid.DataSource = null;
MenuGrid.Rows.Clear();
for (int i = 0; i < order.Count; i++)
{
MenuGrid.Rows.Add(order[i].item, order[i].price);
}
MenuGrid.Refresh();
This gives the following output, which is not what I want:
The final screenshot is correct on the number of rows but it doesn't include the name and the price of the the item.
Any suggestions?
Dont set DataSource to null. And also you can try this inside your for loop,
DataGridViewRow row = new DataGridViewRow();
dataGridView1.Rows.Add(row);
dataGridView1.Rows.Insert(0, order[i].item, order[i].price);
I am creating a combobox from a List of KeyValuePair<int, string>. So far it has been working very well in offering the user the descriptive name while returning me a numeric id.However, whatever I try, I am not able to choose the initially selected value.
public StartUpForm()
{
InitializeComponent();
FlowLayoutPanel flowLayout = new FlowLayoutPanel(); //This is necessary to protect the table, which is for some reason collapsing...
flowLayout.FlowDirection = FlowDirection.TopDown;
flowLayout.AutoSize = true;
flowLayout.AutoSizeMode = AutoSizeMode.GrowAndShrink;
var comboBox = new ComboBox();
{
var choices = new List<KeyValuePair<int, string>> ();
choices.Add(new KeyValuePair<int, string>(1, "hello"));
choices.Add(new KeyValuePair<int, string>(2, "world"));
comboBox.DataSource = choices;
comboBox.ValueMember = "Key";
comboBox.DisplayMember = "Value";
flowLayout.Controls.Add(comboBox);
}
Controls.Add(flowLayout);
//None of these work:
comboBox.SelectedValue = 2;
comboBox.SelectedValue = 2.ToString();
comboBox.SelectedValue = new KeyValuePair<int, string>(2, "world");
comboBox.SelectedValue = "world";
comboBox.SelectedItem = 2;
comboBox.SelectedItem = 2.ToString();
comboBox.SelectedItem = new KeyValuePair<int, string>(2, "world");
comboBox.SelectedItem = "world";
return;
}
The result is always the same:
How can I choose the initially selected value in a ComboBox using as DataSource a List<KeyValuePair<int, string>>?
Binding doesn't work very well inside the constructor, so try moving the ComboBox declaration to the form scope and try using the OnLoad override:
ComboBox comboBox = new ComboBox();
protected override void OnLoad(EventArgs e) {
comboBox.SelectedValue = 2;
base.OnLoad(e);
}
Why are you using binding? Wouldn't it be easier to create a class with the int and the string properties and override ToString() to show the string text:
public class ComboItem
{
public int Key {get; set;}
public string Text {get; set;}
public override string ToString()
{
return this.Text;
}
}
public void OnFormLoad(object Sender, ...)
{
IEnumerable<ComboItem> comboItems = CreateListComboItems();
this.ComboBox1.Items.Clear();
foreach (var comboitem in comboItems)
{
this.comboBox1.Items.Add(comboItem);
}
}
Subscribe to comboBox event SelectedIndexChanged:
private void OnComboSelectedIndexChanged(object sender, EventArgs e)
{
ComboItem selectedItem = (ComboItem)this.comboBox1.SelectedItem;
ProcessSelectedKey(selectedItem.Key);
}
Addendum: I am not able to choose the initially selected value.
I guess you want to be able to programmatically select the combo box item that has a Key with a given value, causing the ComboBox to display the Text belonging to that key.
Although property ComboBox.Items is of type ComboBoxCollection, it implements IEnumerable, meaning you can use Enumerable.Cast() on it to cast it to a sequence of ComboItems. Use Linq to find if there is a ComboItem with the requested key and select the first found ComboItem, or select or select nothing if there is no ComboItem with this key
// selects the combobox item with key == value
private void SelectComboItem(int value)
{
this.comboBox1.SelectedItem =
this.ComboBox1.Items.Cast<ComboItem>()
.FirstOrDefault(comboItem => comboItem.Key == value);
}
The code will get the collection of items in the combo box (which we know is a sequence of ComboItems), hence we can cast all items to a ComboItem.
Then we try to find the first ComboItem that has a Key equal to value. Return null if there is no such ComboItem. Finally make it the selected item. A value of null indicates to select nothing
I'm trying to retrieve the selected item from a combobox, though i can't get it to work.
Form1 form = new Form1();
string cpuCount = form.comboBox1.SelectedItem.ToString();
Now, this is not returning anything. BUT, if i insert this code in my InitializeComponent(), it selects item with index = 3, and return that proper item.
comboBox1.SelectedIndex = 3;
Why does it behave like this? If I now select for example item with index = 5, it still will think the selected item is the one with index = 3.
---------- I think i should expand to show you how my code looks.
Form1 - adding all items to the comboboxes.
public partial class Form1 : Form
{
Profile profile = new Profile();
public Form1()
{
InitializeComponent();
Profile profile = new Profile();
string[] prof = profile.getProfiles();
foreach (var item in prof)
{
comboBox5.Items.Add(Path.GetFileNameWithoutExtension(item));
}
int ram = 1024;
for (int i = 0; i < 7; i++)
{
comboBox4.Items.Add(ram + " GB");
ram = ram * 2;
}
int vram = 512;
string size;
for (int i = 0; i < 5; i++)
{
if(vram > 1000)
{
size = " GB";
}
else
{
size = " MB";
}
comboBox2.Items.Add(vram + size);
vram = vram * 2;
}
for (int i = 1; i < 5; i++)
{
comboBox1.Items.Add(i * 2);
}
for (int i = 0; i < 5; i++)
{
comboBox3.Items.Add(i * 2);
}
private void button3_Click(object sender, EventArgs e)
{
string current = profile.currentProfile();
profile.saveProfile(current);
}
}
So, button3 is my "save"button.
And here is my "Profile"-class
class Profile
{
public string folder { get; set; }
public Profile()
{
this.folder = "Profiles";
if (!File.Exists(folder))
{
Directory.CreateDirectory(folder);
File.Create(folder + "/default.cfg").Close();
}
}
public string[] getProfiles()
{
string[] files = Directory.GetFiles(folder);
return files;
}
public void saveProfile(string filename)
{
Form1 form = new Form1();
string cpuCount = "cpuCount=" + form.comboBox1.SelectedItem;
string RAM = "maxRAM=" + form.comboBox4.SelectedItem;
string VRAM = "maxVRAM=" + form.comboBox2.SelectedItem;
string threads = "cpuThreads=" + form.comboBox3.SelectedItem;
string path = folder + "/" + filename;
StreamWriter sw = new StreamWriter(path);
string[] lines = { cpuCount, RAM, VRAM, threads };
foreach (var item in lines)
{
sw.WriteLine(item);
}
sw.Close();
}
public string currentProfile()
{
Form1 form = new Form1();
string selected = form.comboBox5.SelectedValue + ".cfg".ToString();
return selected;
}
}
Thank you.
The problem is that there is nothing selected in your ComboBox. You create your form and then, without previous user interaction, you want to get the SelectedItem which is null at that moment.
When you create ComboBox control and fill it with items, SelectedItem property is null until you either programratically set it (by using for example comboBox1.SelectedIndex = 3) or by user interaction with the control. In this case you are not doing anything of the above and that is why you are geting the mentioned error.
EDIT Based on the edited question
Change your code like this:
first change the saveProfile method so you could pass the four strings which you write into the text file. Note that you could alternatively pass the reference of the form but I wouldn't suggest you that. So change the method like this:
public void saveProfile(string filename, string cpuCount, string RAM , string VRAM , string threads)
{
string path = folder + "/" + filename;
using(StreamWriter sw = new StreamWriter(path))
{
sw.WriteLine("cpuCount=" + cpuCount);
sw.WriteLine("maxRAM=" + RAM );
sw.WriteLine("maxVRAM=" + VRAM );
sw.WriteLine("cpuThreads=" + threads);
}
}
And then call it from button3 Click event handler like this:
private void button3_Click(object sender, EventArgs e)
{
string current = profile.currentProfile();
string cpuCount = this.comboBox1.SelectedItem.ToString();
string RAM = this.comboBox4.SelectedItem.ToString();
string VRAM = this.comboBox2.SelectedItem.ToString();
string threads = this.comboBox3.SelectedItem().ToString();
profile.saveProfile(current, cpuCount, RAM, VRAM, threads);
}
Or alternatively
private void button3_Click(object sender, EventArgs e)
{
string current = profile.currentProfile();
profile.saveProfile(current, this.comboBox1.SelectedItem.ToString(), this.comboBox4.SelectedItem.ToString(), this.comboBox2.SelectedItem.ToString(), this.comboBox3.SelectedItem().ToString());
}
From what I can see, you are calling form.comboBox1.SelectedItem.ToString() right after the creation of Form1. This means that the cpuCount variable is initialized right after the form is created, thus far before you have the chance to change the selected item with your mouse.
If you want to retrieve the value of the combobox after it is changed, you can use the SelectedIndexChanged event.
First of all, add a Form_Load Event and put your code in the handler. (use constructor for property initialization and other variable initialization)
private void Form1_Load(object sender, EventArgs e)
{
this.comboBox1.SelectedItem= 5; // This will set the combo box to index 5
string cpuCount = this.comboBox1.SelectedText; // This will get the text of the selected item
}
so you get the value of item at index 5 in cpuCount variable.
The selected clause gives you the values AFTER you have selected something, by default(when you run your app) there is nothing selected in the comoboBox, hence, it displays the value as null, after selecting the item you can use the combobox's selectedItem, selectedIndex, selectedText and selectedValue properties.
You can also use databinding to display items in the combobox, which in my view is a better way then adding the items manually.
to databind your combobox you can use,
// Bind your combobox to a datasource, datasource can be a from a database table, List, Dataset, etc..
IDictionary<int, string> comboDictionary = new Dictionary<int, string>();
comboDictionary.Add(1, "first");
comboDictionary.Add(2, "second");
comboDictionary.Add(3, "third");
comboBox1.DataSource = comboDictionary;
comboBox1.DisplayMember = "Key";
comboBox1.ValueMember = "Value";
//
And here now you can use combobox1.SelectedIndex to go through the item collection in the datasource :) and it will give you the value against your keys when you use combobox1.SelectedValue. Hope this helps.
Your ComoboBox doesn't have items in it. So it will not return properly. You are accessing combobox selected value rights after making form object.
And if it comboBox has items then nothing is being selected. By default nothing is selected in comboBox. You need to set it. Use this. What it returns? Set comboBox.SelectedIndex and then get selectedItem.
int selectedIndex = form.comboBox1.SelectedIndex;
Try this. Add some items in ComboBox and then get selectedItem.
Form1 form = new Form1();
form.comboBox1.Add("Item 1");
form.comboBox1.Add("Item 2");
form.comboBox1.Add("Item 3");
form.comboBox1.SelectedIndex = 1;
string cpuCount = form.comboBox1.SelectedItem.ToString();
I create a combobox in a class and want to set the selected value for that combobox. But when I do that, the selectedValue stays null and when I try to set the selectedIndex I get a ArgumentOutOfRangeException.
Code:
public Control GenerateList(Question question)
{
// Clear the local givenAnswer collection
_givenAnswer.Clear();
// Get a list with answer possibilities
List<QuestionAnswer> answers = question.GetAnswerSort();
// Get a collection of given answers
Collection<QuestionnaireAnswer> givenAnswers = question.GetGivenAnswers();
_givenAnswer = givenAnswers;
ComboBox cmb = new ComboBox();
cmb.Name = "cmb";
cmb.DisplayMember = "Answer";
cmb.ValueMember = "Id";
cmb.DataSource = answers;
cmb.Dock = DockStyle.Top;
// Check an answer is given to the question
if (givenAnswers != null && givenAnswers.Count > 0)
{
cmb.Tag = givenAnswers[0].AnswerId;
cmb.SelectedValue = givenAnswers[0].AnswerId; // answerId = 55, but SelectedValue stays null
}
cmb.SelectedIndex = 1; // For testting. This will throw a ArgumentOutOfRangeException
cmb.DropDownStyle = ComboBoxStyle.DropDownList;
cmb.SelectedIndexChanged += new EventHandler(cmb_SelectedIndexChanged);
return cmb;
}
I hope someone can explain to me what is happening so I can understand why it isn't working.
Here is a complete little program what illustrates my problem. As you can see it doesn't set the SelectedValue, this stays null
namespace Dynamic_Create_Combo
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
GenerateControls gc = new GenerateControls();
Control c = gc.GenerateCombo();
this.SuspendLayout();
this.Controls.Add(c);
this.ResumeLayout(true);
}
}
public class GenerateControls
{
public Control GenerateCombo()
{
// Create datasource
Collection<Car> cars = new Collection<Car>();
Car c = new Car();
c.Id = 1;
c.Name = "Car one";
cars.Add(c);
Car c1 = new Car();
c1.Id = 2;
c1.Name = "Car two";
cars.Add(c1);
Car c2 = new Car();
c2.Id = 2;
c2.Name = "Car three";
cars.Add(c2);
ComboBox cmb = new ComboBox();
cmb.DropDownStyle = ComboBoxStyle.DropDownList;
cmb.DataSource = cars;
cmb.DisplayMember = "Name";
cmb.ValueMember = "Id";
cmb.SelectedValue = 2;
return cmb;
}
}
public class Car
{
private int _id;
private string _name;
public int Id
{
get { return _id; }
set { _id = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
}
}
You've set the value member to be "Id" but you're trying to use "AnswerId" as the selected value.
Without more details, it's hard to say why setting SelectedIndex is throwing an ArgumentOutOfRangeException - perhaps the combobox is ignoring all values which don't have an "Id" property, thus giving you no values, so selecting index 1 is impossible?
EDIT: Okay, so it looks like it's only actually doing the binding when it becomes visible - or at some stage in the process. I've tried a few things to accelerate this, but they don't appear to help. What you can do is defer your selection:
EventHandler visibleChangedHandler = null;
visibleChangedHandler = delegate {
cmb.SelectedIndex = 2;
cmb.VisibleChanged -= visibleChangedHandler; // Only do this once!
};
cmb.VisibleChanged += visibleChangedHandler;
It's an ugly workaround, but it should at least help you to get going for the moment.
This is just a guess, but maybe, the ComboBox doesn't bind the data in the DataSource until it's drawn. Check cmb.Items.Count in the line before the SelectedIndex = 1. If it is 0 try to first add the cmb to the Form before assigning SelectedIndex.
EDIT:
public Control GenerateCombo()
{
// Create datasource
Collection<Car> cars = new Collection<Car>();
Car c = new Car();
c.Id = 1;
c.Name = "Car one";
cars.Add(c);
Car c1 = new Car();
c1.Id = 2;
c1.Name = "Car two";
cars.Add(c1);
Car c2 = new Car();
c2.Id = 2;
c2.Name = "Car three";
cars.Add(c2);
ComboBox cmb = new ComboBox();
cmb.DropDownStyle = ComboBoxStyle.DropDownList;
cmb.DataSource = cars;
cmb.DisplayMember = "Name";
cmb.ValueMember = "Id";
// add this:
EventHandler visibleChangedHandler = null;
visibleChangedHandler = delegate {
cmb.SelectedIndex = 2;
cmb.VisibleChanged -= visibleChangedHandler;
};
cmb.VisibleChanged += visibleChangedHandler;
// delete this: cmb.SelectedValue = 2;
return cmb;
}
You need to set the cmb.DataBindings and this might help.
Maybe because the combo is created during the Form_load event. Try to create your combo in the constructor and set the selection in Form_Load.
public class Form1
{
private ComboBox _comboBox1;
private Car _audi = new Car("Audi)");
private Car _porsche = new Car("Porsche");
private Car _vw = new Car("VW");
private void Form1_Load(object sender, EventArgs e)
{
this._comboBox1.SelectedItem = _vw;
}
public Form1()
{
Load += Form1_Load;
// This call is required by the designer.
InitializeComponent();
// Add any initialization after the InitializeComponent() call.
this._comboBox1 = new ComboBox();
this._comboBox1.DataSource = {
_audi,
_porsche,
_vw
};
this.Controls.Add(this._comboBox1);
}
private class Car
{
public string Name { get; set; }
public override string ToString()
{
return this.Name;
}
public Car(name)
{
this.Name = name;
}
}
}
You may want to try SelectedItem instead of SelectedValue, and assigning the class that you want to it. For example:
Car c1 = new Car();
// snip
cmb.SelectedItem = c1;
Since more than one item can have a value of 2 (as far as the ComboBox knows, anyway), I think you will have difficulty setting the value via SelectedValue.