What is the best way to find the names of the columns of a ListView?
I converted a DataTable to a List using a procedure I found on this forum, but I cannot make it to put the Id column first, especially because not all of my DataTables have a column "Id".
I can search in collection listView.Columns.ToString() but the format I am seeing is:
"ColumnHeader: Text: Id"
which I have to parse to find the proper name "Id".
This does not look like the spirit of C#.
I also tried: listView.SelectedItems[0].SubItems["Id"]
but that does not compile.
Ok Here is the complete code.
The exact problem is that the user selects a row in the listView with Courier Names and Ids, but it could also be Ids and Names, in that order. The fastest way to find the Id of the selected courier would be:
ListViewItem si = listCouriers.SelectedItems[0];
CourierId = si.SubItems["Id"].Text;
but that does not work. The hardcoded way would be this, but I cannot guarantee that some day the wrong column will be used:
ListViewItem si = listCouriers.SelectedItems[0];
CourierId = si.SubItems[1].Text;
Using #HuorSwords method leads to this not-so-simple solution, which works for me, but depends on the reasonable assumption that the order of columns in the ColumnHeaderCollection corresponds to the display on the form:
ListViewItem si = listCouriers.SelectedItems[0];
string CourierId = null;
int icol = 0;
foreach (ColumnHeader header in listCouriers.Columns)
{
if (header.Text == "Id")
{
CourierId = si.SubItems[icol].Text;
break;
}
icol++;
}
As listView.Columns is of type ListView.ColumnHeaderCollection, then it contains ColumnHeader objects.
The ColumnHeader.Text contains the column title, so you can check for concrete column with:
foreach (ColumnHeader header in listView.Columns)
{
if (header.Text == "Id")
{
// Do something...
}
}
I don't know if is the best approach, but you don't need to parse the results to find "Id" value...
UPDATE
Also, have you tried to reference it with the String indexer? > listView.Columns["Id"]
use this code:
private ColumnHeader GetColumn(string Text)
{
for (int i = 0; i < listView1.Columns.Count; i++)
for (int j = 0; j < listView1.Items.Count; j++)
if (listView1.Items[j].SubItems.Count - 1 >= i)
if (listView1.Items[j].SubItems[i].Text == Text)
return listView1.Columns[i];
return null;
}
just give item text to this code and get everything you want of a column.
Enjoy ;)
Related
I'm using a three columns DataGridview. Two columns are filled by data read with SQL.
The third column is a checkbox.
I tried to use these two code snippets:
for (int i=0, i<datagrid.RowCount; i++)
{
if (datagrid.Rows[i].Cells[0].Value.Equals(true))
{
string name=datagrid.Rows[i].Cells[1].Value.ToString();
}
}
for (int i=0, i<datagrid.RowCount; i++)
{
if (datagrid.Rows[i].Cells[0]=true)
{
string name=datagrid.Rows[i].Cells[1].Value.ToString();
}
}
In the second case, I get an error that it is impossible to convert "bool" in "system.windows.forms.datagridviewcell".
You can get cell value from property "Value" in DataGridViewCell object that's explained if official API doc
And of course you can't convert the DataGridViewCell to boolean type or assign to DataGridViewCell variable, a boolean type variable. C# a strongly typed language.
Here at datagrid.Rows[i].Cells[0]=true you are trying to assign that datagrid.Rows[i].Cells[0] now is 'True', but in strong typed language you can't do it.
Firstly, datagrid.Rows[i].Cells[0] == true now you are compare, and even this is not good (at all).
Probably you are trying to check cell is exists or something in this matter, but this is other question.
If you want to get value from cell, just do it as that explained above and work with that.
If you want to get a single checked row the following provides that.
DataGridViewRow checkedRow = datagrid.Rows.Cast<DataGridViewRow>()
.FirstOrDefault(row => Convert.ToBoolean(row.Cells[0].Value));
if (checkedRow != null)
{
MessageBox.Show($"{checkedRow.Cells[1].Value.ToString()} on row {checkedRow.Index}");
}
else
{
MessageBox.Show("Nothing checked");
}
For obtaining all checked rows
var checkedRows = datagrid.Rows.Cast<DataGridViewRow>()
.Where(r => Convert.ToBoolean(r.Cells["Selected"].Value) ).ToList();
if (checkedRows.Count >0)
{
for (int index = 0; index < checkedRows.Count; index++)
{
Console.WriteLine(checkedRows[index].Cells[1].Value.ToString());
}
}
else
{
// no checked rows
}
UPDATE
I think I have found what is causing the issue here https://stackoverflow.com/a/5665600/19393524
I believe my issue lies with my use of .DefaultView. The post thinks when you do a sort on it it is technically a write operation to the DataTable object and might not propagate changes made properly or entirely. It is an interesting read and seems to answer my question of why passing valid data to a DataRow is throwing this exception AFTER I make changes to the datatable
UPDATE:
Let me be crystal clear. I have already solved my problem. I would just like to know why it is throwing an error. In my view the code should work and it does.. the first run through.
AFTER I have already deleted the column then added it back (run this code once)
When I debug my code line by line in Visiual studio and stop at the line:
data.Rows[i].SetField(sortColumnNames[k], value);
the row exists
the column exisits
value is not null
sortColumnNames[k] is not null and contains the correct column name
i is 0
Yet it still throws an exception. I would like to know why. What am I missing?
Sorry for the long explanation but this one needs some context unfortunately.
So my problem is this, I have code that sorts data in a DataTable object by column. The user picks the column they want to sort by and then my code sorts it.
I ran into an issue where I needed numbers to sort as numbers not strings (all data in the table is strings). eg (string sorting would result in 1000 coming before 500)
So my solution was to create a temporary column that uses the correct datatype so that numbers get sorted properly and the original string data of the number remains unchanged but is now sorted properly. This worked perfectly. I could sort string numeric data as numeric data without changing the formatting of the number or data type.
I delete the column I used to sort afterwards because I use defaultview to sort and copy data to another DataTable object.
That part all works fine the first time.
The issue is when the user needs to do a different sort on the same column. My code adds back the column. (same name) then tries to add values to the column but then I get a null reference exception "Object not set to an instance of an object"
Here is what I've tried:
I've tried using AcceptChanges() after deleting a column but this did nothing.
I've tried using column index, name, and column object returned by DataTable.Columns.Add() in the first parameter of SetField() in case it was somehow referencing the "old" column object I deleted (this is what I think the problem is more than likely)
I've tried changing the value of the .ItemArray[] directly but this does not work even the first time
Here is the code:
This is the how the column names are passed:
private void SortByColumn()
{
if (cbAscDesc.SelectedIndex != -1)//if the user has selected ASC or DESC order
{
//clears the datatable object that stores the sorted defaultview
sortedData.Clear();
//grabs column names the user has selected to sort by and copies them to a string[]
string[] lbItems = new string[lbColumnsToSortBy.Items.Count];
lbColumnsToSortBy.Items.CopyTo(lbItems, 0);
//adds temp columns to data to sort numerical strings properly
string[] itemsToSort = AddSortColumns(lbItems);
//creates parameters for defaultview sort
string columnsToSortBy = String.Join(",", itemsToSort);
string sortDirection = cbAscDesc.SelectedItem.ToString();
data.DefaultView.Sort = columnsToSortBy + " " + sortDirection;
//copies the defaultview to the sorted table object
sortedData = data.DefaultView.ToTable();
RemoveSortColumns(itemsToSort);//removes temp sorting columns
}
}
This is where the temp columns are added:
private string[] AddSortColumns(string[] items)//adds columns to data that will be used to sort
//(ensures numbers are sorted as numbers and strings are sorted as strings)
{
string[] sortColumnNames = new string[items.Length];
for (int k = 0; k < items.Length; k++)
{
int indexOfOrginialColumn = Array.IndexOf(columns, items[k]);
Type datatype = CheckDataType(indexOfOrginialColumn);
if (datatype == typeof(double))
{
sortColumnNames[k] = items[k] + "Sort";
data.Columns.Add(sortColumnNames[k], typeof(double));
for (int i = 0; i < data.Rows.Count; i++)
{
//these three lines add the values in the original column to the column used to sort formated to the proper datatype
NumberStyles styles = NumberStyles.Any;
double value = double.Parse(data.Rows[i].Field<string>(indexOfOrginialColumn), styles);
bool test = data.Columns.Contains("QtySort");
data.Rows[i].SetField(sortColumnNames[k], value);//this is line that throws a null ref exception
}
}
else
{
sortColumnNames[k] = items[k];
}
}
return sortColumnNames;
}
This is the code that deletes the columns afterward:
private void RemoveSortColumns(string[] columnsToRemove)
{
for (int i = 0; i < columnsToRemove.Length; i++)
{
if (columnsToRemove[i].Contains("Sort"))
{
sortedData.Columns.Remove(columnsToRemove[i]);
}
}
}
NOTE:
I've been able to fix the problem by just keeping the column in data and just deleting the column from sortedData as I use .Clear() on the sorted table which seems to ensure the exception is not thrown.
I would still like an answer though as to why this is throwing an exception. If I use .Contains() on the line right before the one where the exception is thrown is says the column exists and returns true and in case anyone is wondering the params sortColumnNames[k] and value are never null either.
Your problem is probably here:
private void RemoveSortColumns()
{
for (int i = 0; i < data.Columns.Count; i++)
{
if (data.Columns[i].ColumnName.Contains("Sort"))
{
data.Columns.RemoveAt(i);
sortedData.Columns.RemoveAt(i);
}
}
}
If you have 2 columns, and the first one matches the if, you will never look at the second.
This is because it will run:
i = 0
is i < columns.Count which is 2 => yes
is col[0].Contains("sort") true => yes
remove col[0]
i = 1
is i < columns.Count which is 1 => no
The solution is to readjust i after the removal
private void RemoveSortColumns()
{
for (int i = 0; i < data.Columns.Count; i++)
{
if (data.Columns[i].ColumnName.Contains("Sort"))
{
data.Columns.RemoveAt(i);
sortedData.Columns.RemoveAt(i);
i--;//removed 1 element, go back 1
}
}
}
I fixed my original issue by changing a few lines of code in my SortByColumn() method:
private void SortByColumn()
{
if (cbAscDesc.SelectedIndex != -1)//if the user has selected ASC or DESC order
{
//clears the datatable object that stores the sorted defaultview
sortedData.Clear();
//grabs column names the user has selected to sort by and copies them to a string[]
string[] lbItems = new string[lbColumnsToSortBy.Items.Count];
lbColumnsToSortBy.Items.CopyTo(lbItems, 0);
//adds temp columns to data to sort numerical strings properly
string[] itemsToSort = AddSortColumns(lbItems);
//creates parameters for defaultview sort
string columnsToSortBy = String.Join(",", itemsToSort);
string sortDirection = cbAscDesc.SelectedItem.ToString();
DataView userSelectedSort = data.AsDataView();
userSelectedSort.Sort = columnsToSortBy + " " + sortDirection;
//copies the defaultview to the sorted table object
sortedData = userSelectedSort.ToTable();
RemoveSortColumns(itemsToSort);//removes temp sorting columns
}
}
Instead of sorting on data.DefaultView I create a new DataView object and pass data.AsDataView() as it's value then sort on that. Completely gets rid of the issue in my original code. For anyone wondering I still believe it is bug with .DefaultView in the .NET framework that Microsoft will probably never fix. I hope this will help someone with a similar issue in the future.
Here is the link again to where I figured out a solution to my problem.
https://stackoverflow.com/a/5665600
I have a list of strings that I'm trying to use to control the columns shown in a gridview, but can't seem to figure out how to get it to work. Here's an example List<string> selectedHeaders = new List<string>(new string[] { "header1", "header2", "header3", "header4" });
How can I loop through the gridview columns and compare them to the values in selectedHeaders and set the visibility to false for all columns that don't match. Also note that the number of selectedHeaders can be different than the total number of columns within the gridview.
Here is what I have so far:
foreach (GridViewRow row in gvEmployees)
{
for (int i = 0; i < gvEmployees.Columns.Count; i++)
{
if (gvEmployees.Column[i].HeaderText != selectedHeaders[i])
{
gvEmployees.Column[i].Visible = false;
}
}
}
I'm not sure how to refractor this it gives me an index out of range error, because the gridview has 6 columns, but selectedHeaders could contain 1-6 values.
Your loops don't make sense for what you're trying to accomplish.
What you're doing: looping through every row in the GridView, and looping through every column within that, and looking for a string in your selectedHeaders with a matching index
What you need to be doing: looping through every column, and checking to see if there's a corresponding record in selectedHeaders by value, not by index position.
Change your code to this:
for (int i = 0; i < gvEmployees.Columns.Count; i++)
{
if (!selectedHeaders.Any(h => h == gvEmployees.Column[i].HeaderText))
{
gvEmployees.Column[i].Visible = false;
}
}
I have a DataGridView that gets all its data from an XML file and I'm trying to convert all the cell values of a specific column to int, here's the code I'm using for it.
for (int i = 0; i < targetTable.Rows.Count; i++)
{
if (!targetTable[6, i].Value.Equals(null) && !targetTable[6, i].Value.Equals(""))
{
targetTable[6, i].Value = int.Parse(targetTable[6, i].Value.ToString());
}
else
{
targetTable[6, i].Value = i + 1;
}
}
When I debug this for loop the index alternates only between 0 and 1 forever, which leads to a StackOverFlowException.
I can't figure out the reason why it's happening, any help will be much appreciated.
ORINGINAL:
I think I would be using a Foreach on the row instead of doing a for, it may help with figuring out what's behind the strange behavior.
foreach (DataGridViewRow row in targetTable.Rows)
{
if (!row.Cells[6].Value.Equals(null) && !row.Cells[6].Value.Equals(""))
{
row.Cells[6].Value = int.Parse(row.Cells[6].Value.ToString());
}
else
{
row.Cells[6].Value = row.Index + 1;
}
}
EDIT:
Given the new information: that you just want to sort the columns as int, perhaps adding a custom SortCompare event.
Link how to sort string as number in datagridview in winforms
I have a combobox and a datatable.
I've added all of the elements of one column in the datatable to the combobox items.
Now whenever the user chooses a item in the combobox, I want to go to the datatable and compare the column, if there's a match, it will do some code.
I have the following
private void comboBox8_SelectedIndexChanged(object sender, EventArgs e)
{
string str = comboBox8.SelectedItem.ToString();
int z = 0;
foreach (var row in datatable.Rows)
{
int i = 0; i++;
if (datatable.Rows[row]["Cidade"] == str)
{
z = i;
}
}
}
"Cidade" is the column name that matches the options in the combobox.
The Problem is that the code doesn't identify the ìf` condition as valid, saying there are invalid arguments
Edit: worked it around like this:
private void comboBox8_SelectedIndexChanged(object sender, EventArgs e)
{
string str = comboBox8.SelectedItem.ToString();
int z = 0;
for (int i = 0; i < DataAccess.Instance.tabelasismica.Rows.Count; i++)
{
if (DataAccess.Instance.tabelasismica.Rows[i]["Cidade"] == str)
{
z = i;
}
}
MessageBox.Show(z.ToString());
MessageBox.Show(DataAccess.Instance.tabelasismica.Rows[z]["Cidade"].ToString());
}
Standard way of doing things like this is to use data-binding. You'd simply set your ComboBox's DataSource to your DataTable. The code would roughly look like this:
comboBox8.DataSource = datatable;
comboBox8.DisplayMember = "Cidade"
comboBox8.ValueMember = "PrimaryKeyColumnOfYourTable"
Now in the SelectedIndexChanged event, you simply use comboBox8.SelectedValue property to get the ID of the selected row. If you have strongly typed DataSet, your DataTable will have a function named FindByYourPKColumn() that you can use to find the row using this ID.
datatable.Rows[row]["Cidade"] is of type object - you need to convert it to a string before comparing it to str, like this:
if (datatable.Rows[row]["Cidade"].ToString() == str)
{ ... }
Try this in place of the for loop
foreach (DataRow row in dDataAccess.Instance.tabelasismica.Rows)
{
if (row["Cidade"].ToString() == str)
{
z = dDataAccess.Instance.tabelasismica.Rows.IndexOf(row);
}
}
or
foreach (DataRow row in dataTable.Rows)
{
if (row["Cidade"].ToString() == str)
{
z = dataTable.Rows.IndexOf(row);;
}
}
Being said that, standard practice in using ComboBoxes, ListBoxes etc with datasources is to to have a distinct column in the data-table assigned as the ValueMember of the ComboBox, which makes life even easier - as suggested by #dotNET.
comboBox8.DataSource= dataTable; //the data table which contains data
comboBox8.ValueMember = "id"; // column name which you want in SelectedValue
comboBox8.DisplayMember = "name"; // column name that you need to display as text
That way you don't have to iterate through the dataTable to find the index of the row, and you can use the ID (ValueMember) to continue process as required.
Example here
#dotNET's answer is the preferred method to solve your specific problem.
However to solve the general problem find a value in a dataset your best bets are to either
Use the ADO.NET methods Find or Select e.g.
var results = dataset.Select(string.Format("Cidade = {0}",str));
if (results.Count() != 0 )
{
...
}
Or use System.Data.DataSetExtensions
if (datatable.AsEnumerable().Any( x=> x.Field<string>("Cidade") == str ))
{
....
}