I have diffrent entries in a ListView. I would like to count the entries (starting from the second column). The output should be under the "Total" column (see figure below, framed in red) How can this be achieved? With this code I can only access the individual rows:
for (int j = 1; j < listView1.Items[1].SubItems.Count; j++)
{
string s = listView1.Items[1].SubItems[j].Text;
}
Many thanks for your help!
Here's your solution
//iterate through all rows
for (int i = 0; i < listView1.Items.Count; i++)
{
//make sure that 5 subitems are present
int missingSubitemsCount = 5 - listView1.Items[i].SubItems.Count;
for (int j = 0; j < missingSubitemsCount; j++)
listView1.Items[i].SubItems.Add(new ListViewItem.ListViewSubItem());
int count = 0;
//test columns 1-3
for (int j = 1; j < listView1.Items[i].SubItems.Count - 1; j++)
{
//check if empty
if (String.IsNullOrEmpty(listView1.Items[i].SubItems[j].Text) == false)
count++;
}
//update last column
listView1.Items[i].SubItems[4].Text = count.ToString();
}
However, i think it would be better to use DataGridView and data binding instead. Operating directly on control can cause problems when project gets bigger. Its just better to separate data from view
Here's how i would do it with DataGridView
class MyItem
{
public string Name { get; set; }
public string Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
public int Total { get; set; }
}
private void button2_Click(object sender, EventArgs e)
{
var items = new List<MyItem>();
items.Add(new MyItem
{
Name = "Books",
Property1 = "A",
Property2 = "B",
Property3 = "C"
});
items.Add(new MyItem
{
Name = "Pencils",
Property1 = "A",
Property2 = "B",
});
items.Add(new MyItem
{
Name = "Tapes",
Property1 = "A",
});
//iterate through all items
foreach (var item in items)
{
//get list of MyItem properties starting with "Property"
foreach (var property in typeof(MyItem).GetProperties().Where(u => u.Name.StartsWith("Property")))
{
//test if null or empty, increment Total if not
if (property.GetValue(item) != null && String.IsNullOrEmpty(property.GetValue(item).ToString()) == false)
item.Total++;
}
//Alternative: Same calculation in one line
item.Total = typeof(MyItem).GetProperties().Count(u => u.Name.StartsWith("Property") && u.GetValue(item) != null && String.IsNullOrEmpty(u.GetValue(item).ToString()) == false);
}
//bind items to DataGridView
dataGridView1.DataSource = items;
}
Related
I currently have this loop:
if (lists.Date.Count() == 0)
{
message = "There Have Been No Merged Pull Requests In This Repository";
}
else
{
for (int i = 0; i < lists.Date.Count(); i++)
PullRequests = new List<PullRequestDetails>
{
new PullRequestDetails()
{
Title = lists.ShortRequestTitle[i],
Name = lists.Name[i],
Date = lists.Date[i],
CommitLink = lists.ShortCommitList[i],
},
};
}
which takes strings from lists and sets them in this model:
public class PullRequestDetails
{
public string? Title { get; set; }
public string? Name { get; set; }
public string? Date { get; set; }
public string? CommitLink { get; set; }
}
I want my HTML to display every item from the list when the button is clicked, but currently it is only displaying the last item from the list:
#if (Model.PullRequests != null)
{
#foreach (var requests in Model.PullRequests)
{
<tbody>
<tr>
<td>#requests.Title</td>
<td>#requests.Name</td>
<td>#requests.Date</td>
<td> Link </td>
</tr>
</tbody>
}
}
This works when I manually add each value of the list, but his gives an out of range exception when there are less or more than 4 values in each list (which there are in most cases):
if (lists.Date.Count() == 0)
{
html = "There Have Been No Merged Pull Requests In This Repository";
}
else
{
for (int i = 0; i < lists.Date.Count(); i++)
PullRequests = new List<PullRequestDetails>
{
new PullRequestDetails()
{
Title = lists.ShortRequestTitle[0],
Name = lists.Name[0],
Date = lists.Date[0],
CommitLink = lists.ShortCommitList[0]
},
new PullRequestDetails()
{
Title = lists.ShortRequestTitle[1],
Name = lists.Name[1],
Date = lists.Date[1],
CommitLink = lists.ShortCommitList[1]
},
new PullRequestDetails()
{
Title = lists.ShortRequestTitle[2],
Name = lists.Name[2],
Date = lists.Date[2],
CommitLink = lists.ShortCommitList[2]
},
new PullRequestDetails()
{
Title = lists.ShortRequestTitle[3],
Name = lists.Name[3],
Date = lists.Date[3],
CommitLink = lists.ShortCommitList[3]
},
};
}
So how can I fix this with a loop?
Just as #Camilo Terevinto said you need too take the List instantiation out of the loop
if (lists.Date.Count() == 0)
{
message = "There Have Been No Merged Pull Requests In This Repository";
}
else
{
PullRequests = new List<PullRequestDetails>();
for (int i = 0; i < lists.Date.Count(); i++)
{
PullRequests.Add(new PullRequestDetails()
{
Title = lists.ShortRequestTitle[i],
Name = lists.Name[i],
Date = lists.Date[i],
CommitLink = lists.ShortCommitList[i]
});
};
}
When I use if (moscowCars.Contains(cars[x].Name)) it founds the value in a collection and I believe that not necessary to use moscowCars.RemoveAt(moscowCars.FindIndex(o => o.Equals(cars[x].Name))); to find it for a second time, just: moscowCars.Remove(cars[x].Name);. Of course, I can use try & catch instead of if, but I just want to know can I get the item index using Contains?
using System.Collections.Generic;
namespace Autoworld
{
class GoodCars
{
static List<Tech> cars = new List<Tech>();
public class Tech
{
public string Name { get; set; }
public double KM { get; set; }
}
static void Main()
{
List<string> moscowCars = new List<string>
{
"GAZ-330811 Aper", "Lada Vesta Sport"
};
cars.Add(new Tech() { Name = "Lada Vesta Sport", KM = 190 });
for (int x = 0; x < cars.Count; x++)
{
if (moscowCars.Contains(cars[x].Name))
{
moscowCars.RemoveAt(moscowCars.FindIndex(o => o.Equals(cars[x].Name)));
}
}
}
}
}
You could remove the two-step process entirely and just use .Remove which will return:
true if item is successfully removed; otherwise, false. This method
also returns false if itemwas not found in the List.
This would then look like:
for (int x = 0; x < cars.Count; x++)
{
moscowCars.Remove(cars[x].Name);
}
And if you need to handle the case where no car is found to be removed, you can wrap that call in an if condition like:
for (int x = 0; x < cars.Count; x++)
{
if (!moscowCars.Remove(cars[x].Name))
{
// Handle no cars to remove
}
}
Worth noting that behind the scenes, .Remove ultimately just gets the index and then removes the item at that index (which is what you were originally trying to do anyways):
public bool Remove(T item) {
int index = IndexOf(item);
if (index >= 0) {
RemoveAt(index);
return true;
}
return false;
}
See here for the source.
Alternatively, as others have stated, if you expect the List to contain more than item to be removed, you can use .RemoveAll:
moscowCars.RemoveAll(y => y == cars[x].Name);
And again, to handle the case where nothing is found:
if (moscowCars.RemoveAll(y => y == cars[x].Name) == 0)
{
// Handle no cars to remove
}
You can indeed use IndexOf(item)
this will give you the index of the item, or -1 if 'item' was not found (making this method double as a "contains" as well)
Use simply RemoveAt if you are sure you don't have any duplicated items anyway use the second way.
Solution
static List<Tech> cars = new List<Tech>();
public class Tech
{
public string Name { get; set; }
public double KM { get; set; }
}
static void Main()
{
List<string> moscowCars = new List<string>
{
"GAZ-330811 Aper", "Lada Vesta Sport"
};
cars.Add(new Tech() { Name = "Lada Vesta Sport", KM = 190 });
for (int x = 0; x < cars.Count; x++)
{
if (moscowCars.Contains(cars[x].Name))
{
moscowCars.RemoveAt(moscowCars.IndexOf(cars[x].Name));
}
}
}
Solution
static List<Tech> cars = new List<Tech>();
public class Tech
{
public string Name { get; set; }
public double KM { get; set; }
}
static void Main()
{
List<string> moscowCars = new List<string>
{
"GAZ-330811 Aper", "Lada Vesta Sport"
};
cars.Add(new Tech() { Name = "Lada Vesta Sport", KM = 190 });
for (int x = 0; x < cars.Count; x++)
{
if (moscowCars.Contains(cars[x].Name))
{
moscowCars.RemoveAll(o => o == cars[x].Name);
}
}
}
I hope it will help.
What I'm trying to do is remove duplicate rows in a listView but only if the 1st column is duplicated for example:
NAME / AGE / JOB
John / 24 / Engineer
Tom / 32 / Golfer
John / 55 / Scientist
The name John is in there twice, i would just prefer to have it once, and delete all other rows, this is the basic code i have so far is:
public void RemoveDuplicates() {
for (int i = 0; i < listViewTwitter.Items.Count - 1; i++)
{
if (listViewTwitter.Items[i].Tag == listViewTwitter.Items[i + 1].Tag)
{
listViewTwitter.Items[i + 1].Remove();
}
}
}
I cannot think of the best way to do it, any help would be appreciated.
The below code is an example which I wrote for you.
To make my exmaple better, First I created a class:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Job { get; set; }
}
Then I declared a list of objects of Person class.
private void frmMain_Load(object sender, EventArgs e)
{
var list = new List<Person>()
{
new Person() { Age = 24 , Job = "Engineer", Name = "John" },
new Person() { Age = 32, Job = "Golfer", Name = "Tom " },
new Person() { Age = 55, Job = "Scientist",Name = "John" },
};
foreach (var person in list)
{
ListViewItem item = new ListViewItem(person.Name);
item.Tag = person;
listView1.Items.Add(item);
}
}
Then I remove all dupplicates by pressing a button, with two for-loop
private void btnRemoveDupplicates_Click(object sender, EventArgs e)
{
for (int i=0;i<listView1.Items.Count;i++)
{
var person = (Person)listView1.Items[i].Tag;
for (int j = 0; j < listView1.Items.Count; j++)
{
if(
((Person)listView1.Items[j].Tag).Name == person.Name &&
listView1.Items[i].Index != listView1.Items[j].Index)
{
listView1.Items[j].Remove();
continue;
}
}
}
}
I'm assigning values to a class by using ObservableCollection.Class contains MainItems and it's SubItems. Now how can I read all SubItems for each input of MainItem?
public class MainItems
{
public string ItemName { get; set; }
public ObservableCollection<SubItems> SubItemsList { get; set; }
}
public class SubItems
{
public string SubItemName { get; set; }
}
ObservableCollection<MainItems> _data = new ObservableCollection<MainItems>();
for (int i = 1; i <= 5; i++)
{
MainItems _mainItems = new MainItems();
_mainItems.ItemName = "Main" + i.ToString();
_mainItems.SubItemsList = new ObservableCollection<SubItems>();
for (int j = 1; j <= 3; j++)
{
SubItems _subItems = new SubItems()
{
SubItemName = "SubItem" + i.ToString()
};
_mainItems.SubItemsList.Add(_subItems);
}
_data.Add(_mainItems);
}
The foreach loop alway honors the collections(List, Array, Dictionary(special), ...) boundaries and iterates over all Elements, so its the shortest way to achieve what you want. It disallows you to add/remove elements from the currently iterated collection. In this case the classic for loop is your friend.
Full description from Microsoft:
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/foreach-in
Based on Fangs comment:
// foreach Version
foreach (MainItems mainItem in _data)
{
foreach (SubItems subItems in mainItem.SubItemsList)
{
Console.WriteLine($"{mainItem.ItemName} has a child {subItems.SubItemName}!");
}
}
// for Version
for (int i = 0; i < _data.Count; i++)
{
MainItems mainItem = _data[i];
for (int k = 0; k < mainItem.SubItemsList.Count; k++)
{
SubItems subItem = mainItem.SubItemsList[k];
Console.WriteLine($"{mainItem.ItemName} has a child {subItem.SubItemName}!");
}
}
// For Enumerator version
// get the input main item
string input = "Main1";
IEnumerable<ObservableCollection<SubItems>> ItemsforSelectedMainIem = _data.Where(x => x.ItemName == input).Select(x => x.SubItemsList);
var e = ItemsforSelectedMainIem.GetEnumerator();
while (e.MoveNext())
{
var v = e.Current.Select(x=>x.SubItemName).ToList();
foreach (var item in v)
{
Console.WriteLine(item);
}
}
I've to show user details in DataGridView with pagination and almost done working with it. The problem is when I use skip and Take in LINQ, it doesn't show any data but without it works as follows:
grdUserDetails.DataSource = aUserDetail; //shows list of user details
grdUserDetails.DataSource = aUserDetail.Skip(startRow).Take(pageSize); //Doesn't show anything
This is the code that I've tried so far:
public int pageSize = 1;
public Users()
{
InitializeComponent();
BindDataGridView(1);
}
private void BindDataGridView(int pageIndex)
{
List<UserDetails> aUserDetail = null;
aUserDetail = GetUserDetails();
totalRecordCount = aUserDetail.Count;
int startRow = pageIndex * pageSize;
grdUserDetails.DataSource = aUserDetail.Skip(startRow).Take(pageSize); //Here is the main issue that I am stuck with
BindPager(totalRecordCount, pageIndex);
}
private void BindPager(int recordCount, int currentPage)
{
double getPageCount = (double)((decimal)totalRecordCount / (decimal)pageSize);
int pageCount = (int)Math.Ceiling(getPageCount);
List<ListItem> lstItem = new List<ListItem>();
if (currentPage > 1)
{
lstItem.Add(new ListItem { Text = "First", Value = "1" });
}
if (currentPage > 1)
{
lstItem.Add(new ListItem { Text = "<<", Value = (currentPage - 1).ToString() });
}
for (int i = 1; i <= recordCount; i++)
{
lstItem.Add(new ListItem { Text = i.ToString(), Value = i.ToString(), Selected = i == currentPage });
}
if (currentPage < pageCount)
{
lstItem.Add(new ListItem { Text = ">>", Value = (currentPage + 1).ToString() });
}
if (currentPage != pageCount)
{
lstItem.Add(new ListItem { Text = "Last", Value = pageCount.ToString() });
}
plPager.Controls.Clear();
int count = 0;
foreach (ListItem lst in lstItem)
{
Button btnPage = new Button();
btnPage.Location = new System.Drawing.Point(38 * count, 6);
btnPage.Size = new System.Drawing.Size(36, 20);
btnPage.Name = lst.Value;
btnPage.Text = lst.Text;
btnPage.Enabled = !lst.Selected;
btnPage.Click += new System.EventHandler(this.ListItem_Click);
plPager.Controls.Add(btnPage);
count++;
}
}
private void ListItem_Click(object sender, EventArgs e)
{
Button btnPager = (sender as Button);
this.BindDataGridView(int.Parse(btnPager.Name));
}
I've used two classes for the WinForm project. One is the UserDetails and another is Page to bind controls dynamically with a Panel:
public class ListItem
{
public string Text { get; set; }
public string Value { get; set; }
public bool Selected { get; set; }
}
public class UserDetails
{
public int Id { get; set; }
public string Name { get; set; }
}
public List<UserDetails> GetUserDetails()
{
List<UserDetails> lst = new List<UserDetails>
{
new UserDetails { Id = 1001, Name = "John" },
new UserDetails { Id = 1001, Name = "Jack" }
};
return lst;
}
Finally in the form, I've DataGridView named grdUserDetails and a panel plPager to show user details and the pagination. I am hoping, it would be a silly mistake and missed something doing so.
Two things.
1) startRow needs to be less by one because of zero-based indexing.
2) Your LINQ needs to be converted to a List<>.
So, try it this way:
grdUserDetails.DataSource = aUserDetail.Skip(startRow - 1).Take(pageSize).ToList();