Iterate through the items in a Listbox - c#

I have a ListBox (sortedListBox) which I have populated like this by the items in a Combobox (allItemsComboBox):
int index = sortedListBox.FindString(allItemsComboBox.Text, -1);
if (index == -1)
{
var item=new { Text = allItemsComboBox.Text , Value = allItemsComboBox.Value};
sortedListBox.Items.Add(item);
}
The DisplayedMember of sortedListBox is "Text" and ValueMember of it is "Value".
Now I want to iterate through all items in the ListBox and get its values:
public static string ListBoxToString(ListBox lb)
{
List<string> values = new List<string>();
for (int i = 0; i < lb.Items.Count; i++)
{
values.Add(lb.Items[i].ToString());
}
string result = String.Join(",", values);
return result;
}
In this line: values.Add(lb.Items[i].ToString()); I get:
{ Text = "Size" , Value = "cte1.Size"}
I just want to have the value , which is "cte1.Size"
How can I iterate through the items in the ListBox and get the ValueMember of these?

I don't know that there's any way to ask the ListBox to evaluate the ValueMember for you in that way... and because you're using an anonymous type, it becomes harder to get the value.
Options:
Use a named type instead, and cast each item to that
Use dynamic typing
For example:
public static string ListBoxToString(ListBox lb)
{
var values = lb.Items
.Cast<dynamic>()
.Select(x => x.Value.ToString());
return string.Join(",", values);
}
Dynamic typing provides the most immediate fix, but I'd strongly encourage you to consider using a custom type. (It needn't take more than a few lines to write.)

There are two problems with your approach:
1.) The ListBox stores items as a collection of objects which means accessing them with listBox.Items[idx] will only give you back an object and not the actual type. You could get around that with casting it to the appropriate type but it won't work in your case because of the next point.
2.) You create your items as anonymous objects with var item = new { ... }. You can't cast to this kind of type. You could use the dynamic keyword to get around that but I wouldn't do that as you lose type safety.
What you could do is create a simple class for the date you want to store and use that instead of an anonymous type:
class MyData
{
public string Text { get; set; }
public string Value { get; set; }
}

Related

C# - How do I set the selected item in a combobox by comparing my int value?

I'm using a ComboBox with items having text and value. Now, I want to simply make an item selected by comparing its value with the provided value. I'm iterating through the items and comparing as follow. Below code works fine, but is there a better or more simpler way to do this? I found a possible duplicate here but it works with the string value not integer.
foreach (ComboboxItem item in this.CampaignList.Items)
{
if (Convert.ToInt16(item.Value) == objAACampaign.CompanyId)
{
this.CampaignList.SelectedIndex = this.CampaignList.Items.IndexOf(item);
break;
}
}
Use display and value memeber
Create custom class like this:
class MyCustomClass
{
//important to have get set part
public _int { get; set; }
public _string { get; set; }
}
now load data you want to display inside List<MyCustomClass>() and then bind that list to combobox and set it's display and value member like this:
myComboBox.DisplayMember = "_string";
myComboBox.ValueMember = "_int";
myComboBox.DataSource = myList; //this is List<MyCustomClass>
Now simply use myComboBox.SelectedValue = valueYouWant
IMPORTANT!!!
Declare displayMember and valueMember before binding datasource to combobox because of perfomance. Search internet for more info.

Binding List of strings to a DataGridView gives strings properties

I found one similar question in StackOverflow, but it has no answers. I'm trying to bind a IList<string> to a DataGridView as its DataSource, but instead of it output the list of strings, like in a ListView, it outputs me the properties of the elements in the list, in this case, Length.
My code:
public void FindMatches()
{
const string regex = #"\{([a-zA-Z_][a-zA-Z0-9_]*)\}";
IList<string> names = (from Match match in Regex.Matches(ObterConteudoArquivo(), regex) select match.Value).ToList();
_view.Lista = names;
}
Now that I have a list stored in List that contains all my matches, example given { "{CP}", "{DP}", "{EP"} }, I want to bind them to my DataGridView:
public IList<string> Lista
{
set
{
ltvCampos.DataSource = value;
}
}
This binds only the Length of each string.
I also did:
public IList<string> Lista
{
set
{
foreach (string name in value)
{
DataGridTextBox row = new DataGridTextBox();
row.Text = name;
ltvCampos.Rows.Add(row);
}
}
}
The lexer says:
Method with 'params' is invoked. Have you intended to call more specific method 'int Add(object)'?
You need to wrap your strings in a class, which exposes them as public properties with both a setter and a getter:
class aString { public string theString { get; set; }
public aString(string s) { theString = s; } }
Now create a list of strings..
List<aString> theStrings = new List<aString>();
..and fill it with your Matches:
theStrings = (from Match match in Regex.Matches(text, regex)
select new aString(match.Value)).ToList();
Now you can bind your list to the DGV:
ltvCampos.DataSource = theStrings;
For added functionality you may want to insert one or two more layers of binding by using a BindingList (which among others will raise change events):
var blist = new BindingList<aString>(theStrings);
ltvCampos.DataSource = theStrings;
or both a BindingList and a BindingSource, which will present you with a wider range of options and methods:
var blist = new BindingList<aString>(theStrings);
var source = new BindingSource(blist, null);
ltvCampos.DataSource = source ;
Take a look at the answer on this link. I think it is going to help you. They are using a BindingList<> instead of an IList<>

Filter duplicates from a list to populate ComboBox

I've been looking for a way to filter out duplicates from a list to populate a form, but so far all I have found are to create a duplicate list with either a Hashset or other methods that involve grouping the duplicates into separate list, however I'm not interested in keeping the extras.
Currently what I'm getting in my combobox is:
123
123
456
456
789
789
etc...
Trouble is, I'm collecting the data in models (or classes) as such:
List<ModelName>
ModelName<1>
{
string Name = Bob;
int Number = 123;
}
ModelName<2>
{
string Name = Jim;
int Number = 123;
}
ModelName<3>
{
string Name = Bob;
int Number = 456;
}
Is there a way to fill a list with unique classes:
ModelName<1>
{
Name;
Number;
}
ModelName<2>
{
Name;
}
ModelName<3>
{
Number;
}
and just filter out and dispose of any double ups?
You can use the LINQ Distinct operator to remove duplicates from a collection:
var listWithoutDuplicates = listWithDuplicates.Distinct().ToList();
If you want to customize the way elements are compared for equality you can use the overload that requires an IEqualityComparer<T>.
In your case, if you want to define "equality" as having the same value of the Location property you can use this EqualityComparer:
class EqualityComparer : IEqualityComparer<ClassName> {
public Boolean Equals(ClassName x, ClassName y) {
return Equals(x.Location, y.Location);
}
public Int32 GetHashCode(ClassName obj) {
return obj.Location.GetHashCode();
}
}
And to get distinct items by location:
var listWithoutDuplicates = listWithDuplicates.Distinct(new EqualityComparer).ToList();
yourList.GroupBy(x => x.Location).Select(x => x.First());
Use list.distinct() and populate a list
List<int> distinct = list.Distinct().ToList();

Finding a specfic int in a List

I have a list which is created from a Class:
class Vara
{
public int streckKod { get; set; }
public string artNamn { get; set; }
}
And the list looks like this:
List<Vara> minaVaror = new List<Vara>();
And I add to the list with this line:
minaVaror.Add(new Vara() {streckKod = inputBox1, artNamn = textBox2.Text });
But what I'm stuck at is how I can find a specific int within the list. Let's say I'm searching for the item in my list holding the number 293 in the variable streckKod.
I've tried using .IndexOf function but I haven't gotten it to work properly.
Also would it be possible to get the item number that the specific number is located in?
If you want to find all items whose streckKod value is 293 use Where
var items = minaVaror.Where(i => i.streckKod == 293);
If interested in only first item use FirstOrDefault -
var item = minaVaror.FirstOrDefault(i => i.streckKod == 293);
FirstOrDefault will return null in case no item exist in collection with value 293.
Make sure you add namespace System.Linq in your class to use these LINQ extension methods.
Use Linq
minaVarror.Find(item=>item.strekKod == 293);
Adding to Rohit Vats answer...
When you have found your item, with either Where or FirstOrDefault you can get the index by doing:
var item = minaVaror.FirstOrDefault(i => i.streckKod == 293);
// Get index of Vara (item) with streckKod = 293
int index = minaVaror.IndexOf(item);
As IndexOf returns the position of an exact item (Vara) within the list minaVaror

What is the most effective way to 'align' two separate lists by ordinal in a single ItemsControl?

Over-simplifying our model for the purposes of this example, let's say we have two lists of data, ListA and ListB, both of which are of type List<string>. From a data perspective, they are not related. ListA and ListB can be added to, removed from, or otherwise updated independently.
What we're trying to do is display them both at the same time in the same list, aligned by ordinal position.
Our first approach was to create a new ListMapping object as follows:
public class ListMapping
{
public int Index{ get; set; }
public string StringA{ get; set; }
public string StringB{ get; set; }
}
then create a List<ListMapping> relating the strings at ordinal position 'x' of ListA and ListB and we'd initialize it like this:
var MappedList = new List<ListMapping>();
var maxItems = Math.Max(ListA.Count, ListB.Count);
for(int index = 0; index < maxItems; index++)
{
var lm = new ListMapping(){
Index = index,
StringA = (index < ListA.Count) ? ListA[index] : null;
StringB = (index < ListB.Count) ? ListB[index] : null;
}
MappedList.Add(lm);
}
The problem with this approach is we had to manually manage this new list of ListMap objects. If an item is deleted from ListB, then we need to manually shift all the ListMapping.StringB properties up one position to 'realign' with the new ListMapping.StringA. Same thing with Insert, etc.
Our next approach was to not actually store the string values in ListMapping, just the index, and make the getters return the value directly from the underlying lists, like this...
public class ListMapping
{
public int Index{ get; set; }
public string StringA{ get{ (Index < ListA.Count) ? ListA[Index] : null; } }
public string StringB{ get{ (Index < ListB.Count) ? ListB[Index] : null; } }
}
And then we'd initialize the List<ListMapping> object like this...
var MappedList = new List<ListMapping>();
var maxItems = Math.Max(ListA.Count, ListB.Count);
for(int index = 0; index < maxItems; index++)
{
var lm = new ListMapping(){
Index = index
}
MappedList.Add(lm);
}
Using this design, we'd simply need to trigger property changed notifications for the StringA and StringB properties of any ListMapping with an index that would have been affected by an operation on either ListA or ListB. Definitely cleaner and no held references to the source objects, but now they had to have a reference to the List objects themselves. Plus, we still need to manually manage the overall list of ListMapping items to ensure there's at least 'maxItems' items at all times. Better, but not ideal.
I can't help but wonder if there's a way to construct an ItemsControl to have two ItemsSource properties then do something clever with its layout panel and ItemContainerGenerator, but that just seems like I'd be doing in the UI what I'm already doing in the data.
So, any thoughts on a way to solve this issue?

Categories