ListViewGroups not working correctly - c#

I'm working with a ListView control in Windows Forms using C# and .NET 3.5. I have several items that I'm adding to this ListView control, and I have functionality in place that is supposed to allow the user to select different ways to group the items in the list. However, sometimes items get dumped into the automatically generated "Default" group for no explicable reason.
The code is too large to just put here, but let me explain what I'm doing algorithmically. Suppose we're starting with a ListView that contains items and may or may not already contain groups.
Cycle through every item and set
it's Group property to null.
Empty the ListView's Groups
collection.
Add new Groups to the ListView's
Group collection.
Cycle through every item and set the
Group property using a value
obtained from the ListView's Group
collection via index.
I've stepped through the code and observed everything as it should be. With each item it obtains the appropriate group from the ListView's Group collection and sets the Group property of the item, yet sometimes they end up listed under the "Default" group.
Has anyone else ever observed this or have any theories as to why it might be happening?

It doesn't sound familiar, and I can't reproduce (see below, which follows your approach). Can you post anything related to your "update the groups" code?
using System;
using System.Windows.Forms;
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
ListView lv;
Button btn;
Form form = new Form {
Controls = {
(lv = new ListView { Dock = DockStyle.Fill,
ShowGroups = true}),
(btn = new Button { Dock = DockStyle.Bottom,
Text = "Scramblle" })
}
};
Random rand = new Random();
for (int i = 0; i < 40; i++) {
lv.Items.Add("Item " + i);
}
btn.Click += delegate {
// 1: Cycle through every item and set it's Group
// property to null.
foreach (ListViewItem item in lv.Items) {
item.Group = null;
}
// 2: Empty the ListView's Groups collection.
lv.Groups.Clear();
// 3: Add new Groups to the ListView's Group collection.
int groupCount = rand.Next(lv.Items.Count) + 1;
for (int i = 0; i < groupCount; i++) {
lv.Groups.Add("grp" + i, "Group " + i);
}
// 4: Cycle through every item and set the Group property
// using a value obtained from the ListView's Group collection
// via index.
foreach (ListViewItem item in lv.Items) {
item.Group = lv.Groups[rand.Next(groupCount)];
}
};
Application.Run(form);
}
}

Is this happening on multiple threads?
It sounds like you might be adding some ListViewItems with Groups taken before the the groups were cleared.

Yes... It happened to me too. Try to set the Group before you add the item. I mean when you initialize the ListViewItem you add to the constructor the group it takes part of. That way is going to work.

Yes, I have seen similar behavior. The solution I followed is based on the code here.
public partial class ListViewExtended : ListView
{
private Collection<Dictionary<string, ListViewGroup>> groupTables = new Collection<Dictionary<string,ListViewGroup>>();
public ListViewExtended()
{
InitializeComponent();
}
/// <summary>
/// Create groups for each column of the list view.
/// </summary>
public void CreateGroups()
{
CreateGroups(false);
}
/// <summary>
/// Create groups for each column of the list view.
/// </summary>
public void CreateGroups(bool reset)
{
if (OSFeature.Feature.IsPresent(OSFeature.Themes))
{
if (reset)
{
this.groupTables.Clear();
}
for (int column = 0; column < this.Columns.Count; column++)
{
Dictionary<string, ListViewGroup> groups = new Dictionary<string, ListViewGroup>();
foreach (ListViewItem item in this.Items)
{
string subItemText = item.SubItems[column].Text;
// Use the initial letter instead if it is the first column.
if (column == 0)
{
subItemText = subItemText.Substring(0, 1).ToUpperInvariant();
}
if (!groups.ContainsKey(subItemText))
{
groups.Add(subItemText, new ListViewGroup(subItemText) { Name = subItemText });
}
}
this.groupTables.Add(groups);
}
}
}
/// <summary>
/// Sets the list view to the groups created for the specified column.
/// </summary>
/// <param name="column"></param>
public void SetGroups(int column)
{
if (OSFeature.Feature.IsPresent(OSFeature.Themes))
{
try
{
this.BeginUpdate();
this.Groups.Clear();
if (column == -1)
{
this.ShowGroups = false;
}
else
{
this.ShowGroups = true;
Dictionary<string, ListViewGroup> groups = groupTables[column];
this.Groups.AddRange(groups.Values.OrderBy(g => g.Name).ToArray());
foreach (ListViewItem item in this.Items)
{
string subItemText = item.SubItems[column].Text;
// For the Title column, use only the first letter.
if (column == 0)
{
subItemText = subItemText.Substring(0, 1).ToUpperInvariant();
}
item.Group = groups[subItemText];
}
groups.Values.ForEach<ListViewGroup>(g => g.Header = String.Format(CultureInfo.CurrentUICulture, "{0} ({1})", g.Name, g.Items.Count));
}
}
finally
{
this.EndUpdate();
}
}
}
}
In the code that actually adds the items to the listview, you want to do something like this:
this.itemsListView.Items.AddRange(items.ToArray());
this.itemsListView.CreateGroups(true);
this.itemsListView.SetGroups(0); // Group by the first column by default.

Related

Setting the Data within a nested datagrid programatically

SubVwr.Tables[0].tbl is a table of several items, some of which require specific Trace. Those items which require trace have serial numbers assiciated with them. To handle this, I created a new window for each, where I'd like the user to input serial numbers. Some of the Data inside of the new table (dialog) should be pulled from the previous table (Subvwr). What would be the syntax here if I wanted to complete an assignment for a specific value within each row of dialog?
private void requestSerials()
{
// needs to check for a difference. If QtyRcvd has changed, then we need to request "QtyRcvd" number of serials
//for each row in our received order..
foreach (DataRow row in SubVwr.Tables[0].Tbl.Rows)
{
// check to see if it neesd to be traced..
if (row["TraceReq"].ToString() == "Y")
{
//if yes, create a serialInputDialogWindow
SerialInputDialogWindow dialog = new SerialInputDialogWindow()
{
Topmost = true
};
//Iterate through the dialogWindow before we display it to fill it with some necessary data.
var itemsSource = dialog.serialtable.ItemsSource as IEnumerable;
if (itemsSource != null)
{
foreach (var item in itemsSource)
{
var serialrow = item as System.Data.DataRowView;
serialrow["PN"] = "test"; //doesn't work
serialrow.Row["PN"] = "test"; //doesn't work
}
}
dialog.ShowDialog();
}
}
}
An example of similar code that works. It populates that specific column for each row as specified. I'd like to take Data from Subvwr and populate Dialog in a similar way
foreach(DataRow row in SubVwr.Tables[0].Tbl.Rows)
{
row["DateRcvd"] = DateTime.Today;
row["QtyRcvd"] = row["QtyPer"];
}
The items in the ItemsSource are of some type. If for example dialog.serialtable.ItemsSource returns a DataView, you can cast the items to DataRowViews and then access any columns using the indexer:
foreach (var item in itemsSource)
{
var serialrow = item as System.Data.DataRowView;
if (serialrow != null)
{
var name = serialrow["ColumnName"].ToString();
}
}
If it is an IEnumerable<T> and T is a YourClass, you cast it to this type:
foreach (var item in itemsSource)
{
var serialrow = item as YourType;
if (serialrow != null)
{
var name = serialRow.Property1;
}
}

Display the sum of all items inside listbox

Is it possible to sum up all of the item's price inside a ListBox? I have this ListBox that displays items from a DataGridView, and each of their prices are in priceTextBox Please refer from the picture below.
What I want to do is to display the sum of all the item's price and display it at the totalTextBox.
I have already done this code but I think this won't work.
private void menuListBox_SelectedValueChanged(object sender, EventArgs e)
{
int x = 0;
string Str;
foreach (DataGridViewRow row in menuDataGrid.Rows) //this part displays the price of each item to a textbox so this is good.
{
if (row.Cells[3].Value.ToString().Equals(menuListBox.SelectedItem.ToString()))
{
pricetxtbox.Text = row.Cells[5].Value.ToString();
break;
}
}
foreach (string Str in row.Cells[5].Value.ToString().Equals(menuListBox.SelectedItem.ToString())) //now this is the part where I want to happen the adding the prices of all items in the listbox. this also gives me an error at row saying it doesn't exist in the context
{
x = x + Convert.ToInt32(Str);
totaltxtbox.Text = x;
}
}
Will appreciate any help! Thanks!
Try this out...
Func<string, DataGridViewRow> getRow = (menuCode) =>
{
return menuDataGrid.Rows.Cast<DataGridViewRow>()
.First(r => ((string)r.Cells[3].Value).Equals(menuCode));
};
var selected = menuListBox.SelectedItem.ToString();
pricetxtbox.Text = getRow(selected).Cells[5].Value.ToString();
totaltxtbox.Text = menuListBox.Items.Cast<object>()
.Select(o => o.ToString())
.Select(i => (int)getRow(i).Cells[5].Value)
.Sum()
.ToString();
I think you should change your approach, by separating completely data and display.
To do that, you could create a class which will contain data for each row of your DataGrid :
public class MyItem
{
public string Caption { get; set; }
public int Price { get; set; }
}
And then in your codebehind, you store a list of these items :
private List<MyItem> AllItems = new List<MyItem>();
Finally, you set this collection as the source of your DataGrid :
menuDataGrid.DataSource = AllItems;
Then, all your data is stored in a collection you own, and to sum prices it's much simpler :
using System.Linq;
private int ComputeSum()
{
return (AllItems.Sum(item => item.Price));
}
The next step is to use Binding and a BindingList, wich allows the DataGrid to refresh automatically when new items are added in "AllItems": see this well explained post.

c# How to prevent duplicate listview by column text

I need to prevent Duplicate entries from ListView controller by column text. if duplicate found I need to get the ListView Item for further process. I saw every one says
ListViewItem item = ListView3.FindItemWithText("test");
if (!listView1.Items.ContainsKey(txt))
{
// doesn't exist, add it
}
but how can I point which Column text?? I did prevent duplicates by adding ids into a array and after check array value exists. but in that case I can find which entry duplicated.
this is my code.
rd = cmd.ExecuteReader();
// Validation not working - duplicating ListviewItems
while (rd.Read()) {
ListViewItem lvvi = new ListViewItem(rd.GetString(0));
lvvi.SubItems.Add(rd.GetString(1));
lvvi.SubItems.Add(rd.GetString(5));
lvvi.SubItems.Add("1");
lvvi.SubItems.Add(rd.GetString(0));
int listViewItemID;
int[] ids;
ids = new int[100];
if (listView3.Items.Count > 0)
{
int addingItemID;
//ADD ListView ids into array
int i=0;
foreach (ListViewItem li in listView3.Items)
{
listViewItemID = Int32.Parse(li.SubItems[0].Text);
addingItemID = Int32.Parse(rd.GetString(0));
ids[i] = listViewItemID;
i++;
}
//Check item allready exsist
if (ids.Contains(Int32.Parse(rd.GetString(0))))
{
MessageBox.Show("sdsd");
}
else {
listView3.Items.Add(lvvi);
}
}
else {
listView3.Items.Add(lvvi);
}
}
//Calculate Price summery
this.calculatePrice();
Instead of looping to get all id's, you can loop through the items or use linq to find the specific id and keep the result. This can be done in an external function or by replacing the ids portion with the loop or use something like FirstOrDefault:
addingItemID = rd.GetString(0);
ListViewItem existing = listView3.Items.Cast<ListViewItem>().FirstOrDefault(li => li.SubItems[0].Text == addingItemID); //(not sure if the cast is needed)
if (existing != null)
{
//item exists, variable existing refers to the item
MessageBox.Show("sdsd");
}
else
{
listView3.Items.Add(lvvi);
}

How can I Validate Textboxes Values To Be Unique?

I have 12 text boxes, and I am trying to find a strategy to not permit duplicate entries in the TextBoxes by the user at run-time.
List<string> lstTextBoxes = new List<string>();
private void Textbox1(object sender, EventArgs e)
{
lstTextBoxes.Add(Textbox1.Text);
}
public bool lstCheck(List<string> lstTextBoxes,string input)
{
if(lstTextBoxes.Contains(input))
{
return true;
}
else
{
return false;
}
}
private void Textbox2(object sender, EventArgs e)
{
lstTextBoxes.Add(Textbox2.Text);
if (lstCheck(lstTextBoxes, Textbox2.Text))
{
MessageBox.Show("test");
}
}
public bool CheckForDuplicates()
{
//Collect all your TextBox objects in a new list...
List<TextBox> textBoxes = new List<TextBox>
{
textBox1, textBox2, textBox3
};
//Use LINQ to count duplicates in the list...
int dupes = textBoxes.GroupBy(x => x.Text)
.Where(g => g.Count() > 1)
.Count();
//true if duplicates found, otherwise false
return dupes > 0;
}
There are many ways to accomplish this. I've chosen to present a solution as an extension method but you can achieve the same result by simply placing the method within the class containing the list of textboxes or passing the list as a parameter. Which ever you prefer.
Cut and paste following into your project. Make sure you put it within the same namespace, otherwise, add the containing namespace as a reference.
public static class TextBoxCollectionExtensions
{
/// <summary>
/// Extension method that determines whether the list of textboxes contains a text value.
/// (Optionally, you can pass in a list of textboxes or keep the method in the class that contains the local list of textboxes.
/// There is no right or wrong way to do this. Just preference and utility.)
/// </summary>
/// <param name="str">String to look for within the list.</param>
/// <returns>Returns true if found.</returns>
public static bool IsDuplicateText(this List<TextBox> textBoxes, string str)
{
//Just a note, the query has been spread out for readability and better understanding.
//It can be written Inline as well. ( ex. var result = someCollection.Where(someItem => someItem.Name == "some string").ToList(); )
//Using Lambda, query against the list of textboxes to see if any of them already contain the same string. If so, return true.
return textBoxes.AsQueryable() //Convert the IEnumerable collection to an IQueryable
.Any( //Returns true if the collection contains an element that meets the following condition.
textBoxRef => textBoxRef //The => operator separates the parameters to the method from it's statements in the method's body.
// (Word for word - See http://www.dotnetperls.com/lambda for a better explanation)
.Text.ToLower() == str.ToLower() //Check to see if the textBox.Text property matches the string parameter
// (We convert both to lowercase because the ASCII character 'A' is not the same as the ASCII character 'a')
); //Closes the ANY() statement
}
}
To use it, you do something like this:
//Initialize list of textboxes with test data for demonstration
List<TextBox> textBoxes = new List<TextBox>();
for (int i = 0; i < 12; i++)
{
//Initialize a textbox with a unique name and text data.
textBoxes.Add(new TextBox() { Name = "tbField" + i, Text = "Some Text " + i });
}
string newValue = "some value";
if (textBoxes.IsDuplicateText(newValue) == true) //String already exists
{
//Do something
}
else
{
//Do something else
}

Prevent double entries in ListView using C#?

How can we access the items added to a ListView?
The thing I have to do is: add an item to the list view. I want to check if the item to add to the listview is already present in the ListView.
I'm using C# and Visual Studio 2005.
The ListView class provides a few different methods to determine if an item exists:
Using Contains on the Items collection
Using one of the FindItemWithText methods
They can be used in the following manner:
// assuming you had a pre-existing item
ListViewItem item = ListView1.FindItemWithText("test");
if (!ListView1.Items.Contains(item))
{
// doesn't exist, add it
}
// or you could find it by the item's text value
ListViewItem item = ListView1.FindItemWithText("test");
if (item != null)
{
// it exists
}
else
{
// doesn't exist
}
// you can also use the overloaded method to match sub items
ListViewItem item = ListView1.FindItemWithText("world", true, 0);
Just add your items and make sure you assign a name. Then
just use the ContainsKey method of the Items collection to
determine if it's there, like this.
for (int i = 0; i < 20; i++)
{
ListViewItem item = new ListViewItem("Item" + i.ToString("00"));
item.Name = "Item"+ i.ToString("00");
listView1.Items.Add(item);
}
MessageBox.Show(listView1.Items.ContainsKey("Item00").ToString()); // True
MessageBox.Show(listView1.Items.ContainsKey("Item20").ToString()); // False
You could do something like this:
ListViewItem itemToAdd;
bool exists = false;
foreach (ListViewItem item in yourListView.Items)
{
if(item == itemToAdd)
exists=true;
}
if(!exists)
yourListView.Items.Add(itemToAdd);
The following will help to locate a ListViewItem within the ListView control once you've added it:
string key = <some generated value that defines the key per item>;
if (!theListViewControl.Items.ContainsKey(key))
{
item = theListViewControl.Items.Add(key, "initial text", -1);
}
// now we get the list item based on the key, since we already
// added it if it does not exist
item = theListViewControl.Items[key];
...
Note
The key used to add the item to the ListView items collection can be any unique value that can identify the ListViewItem within the collection of items. For example, it could be a hashcode value or some property on an object attached to the ListViewItem.
A small correction in Robban's answer
ListViewItem itemToAdd;
bool exists = false;
foreach (ListViewItem item in yourListView.Items)
{
if(item == itemToAdd)
{
exists=true;
break; // Break the loop if the item found.
}
}
if(!exists)
{
yourListView.Items.Add(itemToAdd);
}
else
{
MessageBox.Show("This item already exists");
}
In case of multicolumn ListView, you can use following code to prevent duplicate entry according to any column:
Let us suppose there is a class Judge like this
public class Judge
{
public string judgename;
public bool judgement;
public string sequence;
public bool author;
public int id;
}
And i want to add unique object of this class in a ListView. In this class id is unique field, so I can check unique record in ListView with the help of this field.
Judge judge = new Judge
{
judgename = comboName.Text,
judgement = checkjudgement.Checked,
sequence = txtsequence.Text,
author = checkauthor.Checked,
id = Convert.ToInt32(comboName.SelectedValue)
};
ListViewItem lvi = new ListViewItem(judge.judgename);
lvi.SubItems.Add(judge.judgement ? "Yes" : "No");
lvi.SubItems.Add(string.IsNullOrEmpty(judge.sequence) ? "" : txtsequence.Text);
lvi.SubItems.Add(judge.author ? "Yes" : "No");
lvi.SubItems.Add((judge.id).ToString());
if (listView1.Items.Count != 0)
{
ListViewItem item = listView1.FindItemWithText(comboName.SelectedValue.ToString(), true, 0);
if (item != null)
{
// it exists
}
else
{
// doesn't exist
}
}

Categories