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;
}
}
Related
I am doing a code where I compare two columns of DGV roles, the first DGV (DGV1) has the raw data with duplicate roles, and the second DGV (DGV4) is a dictionary with all existing roles (no duplicates), it has to go to each row of the dictionary and if the role exists in the DGV1, it should be removed from the dictionary, leaving only the roles in the dictionary that are not currently being used in the raw data. My code is removing the roles, but when the dictionary has a value that doesn't exist in DGV1, it stops working (DGV1 continues to loop until it has an index error). Any suggestion?
NOTE: The rows in the dictionary automatically go to the first index, so there is no need to increment int i.
int eliminado = 0;
int filasDGV1 = dataGridView1.Rows.Count;
int filasDGV4 = dataGridView4.Rows.Count;
int i = 0;
int j = 0;
do
{
string perfilVacio = dataGridView4["GRANTED_ROLE", i].Value.ToString();
string perfiles = dataGridView1["GRANTED_ROLE", j].Value.ToString();
if(perfiles != perfilVacio)
{
j++;
}
else if(perfiles == perfilVacio)
{
dataGridView4.Rows.RemoveAt(i);
}
}
while (eliminado <= filasDGV4);
The first excel is DGV1 and the other is DGV2, I highlighted where is the code looping currently
The orange highlight is where the loop change in DGV1 but in the dictionary doesnt exist so its stuck there
Change your loop condition to include a test for the changing index j and also to check whether there are rows left to be eliminated.
int filasDGV1 = dataGridView1.Rows.Count;
int j = 0;
while (j < filasDGV1 && dataGridView4.Rows.Count > 0)
{
string perfilVacio = dataGridView4["GRANTED_ROLE", 0].Value.ToString();
string perfiles = dataGridView1["GRANTED_ROLE", j].Value.ToString();
if(perfiles == perfilVacio)
{
dataGridView4.Rows.RemoveAt(0);
}
else
{
j++;
}
}
If you test perfiles != perfilVacio in if you don't have to test perfiles == perfilVacio in else if, because this automatically the case. Either they are equal or they are not. There no other possibility.
Also, it is generally more readable if you ask a positive question in if like == instead of a negative one like !=.
Since i is always 0 I replaced it by the constant 0. The variable eliminado is not required (unless it is incremented when rows are removed to display the number of deleted rows).
The number of rows in dataGridView4 should not be stored in filasDGV4 as this number changes.
Update
According to your comments and the new screenshots, you need two loops. (The code above only works if both lists are sorted). We could use two nested loops; however, this is slow. Therefore, I suggest collecting the unwanted roles in a HashSet<string> first. Testing whether an item is in a HashSet is extremely fast. Then we can loop through the rows of the dictionary and delete the unwanted ones.
var unwanted = new HashSet<string>();
for (int i = 0; i < dataGridView1.Rows.Count: i++)
{
unwanted.Add(dataGridView1["GRANTED_ROLE", i].Value.ToString());
}
int row = 0;
while (row < dataGridView4.Rows.Count)
{
string perfilVacio = dataGridView4["GRANTED_ROLE", row].Value.ToString();
if(unwanted.Contains(perfilVacio))
{
dataGridView4.Rows.RemoveAt(row);
}
else
{
row++;
}
}
Suggestion: Using data binding to bind your DataGridViews to generic lists would enable you to work on these lists instead of working on the DGVs. This would simplify the data handling considerably.
I want to add the 'OrderID' in the below DataGridView to a list for each row that I select.
I have managed to get it to cycle through a foreach loop but I am trying to figure out how to specify the selected row index, as it currently pulls the same indexed 'OrderID' and not the different ones selected.
See result I am currently getting:
See current code:
List<string> orders = new List<string>();
foreach (var row in grid.SelectedRows)
{
orders.Add(grid.Rows[grid.SelectedRows[0].Index].Cells[0].Value.ToString());
}
I know I shouldn't be using SelectedRows[0], but I cant think of how to index the specific 'row'
I used a for loop instead:
for (int i = 0; i < grid.SelectedRows.Count; i++)
{
orders.Add((grid.Rows[i].Cells["Order ID"].Value).ToString());
}
I have (asp) GridView with an item template inside. There are ten item templates with one textbox each. The GridView is bound in the behind code. A data table is made and depending on certain criteria only certain columns will be visible. This is how the table changes dynamically based on user criteria.
This is the code I use to collect the values inside those textboxes.
List<string> myList = new List<string>();
foreach (GridViewRow row in GridView1.Rows)
{
for (int i = 0; i < GridView1.Columns.Count; i++)
{
if (GridView1.Columns[i].Visible == true)
{
TextBox tb = (TextBox)row.FindControl($"TB{i}");
if (tb.Text == string.Empty)
{
myList.Add("Null Value");
}
else
{
string someVariableName = tb.Text;
myList.Add(tb.Text);
}
}
}
}
The TB{i} references the text boxes in the item template. They are named TB0 - TB9. This code will take the values and combine them into the list. That list will be stored in the database.
The issue I'm facing is redistributing those values back to the text boxes after that string has been queried from the database. Essentially, the string is queried and stored in a variable. That variable is then split on a comma delimiter and stored inside a new list. Here is the code I have so far, this is where the problem is.
TextBox dbInput = new TextBox();
dbInput.Text = "box1,box2,box3,box4,box5,box6,box7,box8,null,null,null,null,null,null,null,null,box13,null,null,null,null,null,null,null,null,11,222,null,null,null,null,null,null,null,null,null,null,null,null,333,null,null,null,null,444,null,333,1343";
List<string> stringList = dbInput.Text.Split(',').ToList();
int counter = 1;
foreach (GridViewRow row in GridView1.Rows)
{
for (int i = 0; i < GridView1.Columns.Count; i++)
{
if (GridView1.Columns[i].Visible == true)
{
TextBox tb = (TextBox)row.FindControl($"TB{i}");
tb.Text = stringList[i];
}
counter = counter + 1;
}
}
The input is hard coded in this instance (dbInput). I'm fairly certain the issue is this line tb.Text = stringList[i];. The reason for that is because there should only be four columns displayed with this output. It only prints the first four entries of the list into the first four boxes on row one. Then it goes to row two and prints the same four entries, repeat.
I have tried changing tb.Text = stringList[i]; [i] to another variable and incrementing it. I placed the variable outside the foreach loop but incremented it right after the inner for loop. This resulted everytime in this error Index was outside the bounds of the array.
Line 854.
Line 852: {
Line 853: TextBox tb = (TextBox)row.FindControl($"TB{i}");
Line 854: tb.Text = stringArray[counter];
Line 855: }
Line 856: counter = counter + 1;
How can I alter the code to loop through the contents of the list showing (for example) the first four items on row one, the next four items on row two and repeat till the list is empty?
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 ;)
I've got a grid (FlexGrid, from ComponentOne) in a Winform application and I'm trying to find a cell in that grid, given the cell's column index and its value.
I've written the extension method below to loop through the grid and find that cell.
I'm testing that method on a grid that has 6 columns and 64 rows. It took 10 minutes for my code to find the correct cell (which was on the last row)
Is there any way I can speed up my algorithm ?
Note: I've also tried setting PlayBack.PlayBackSetting.SmartMatchOption to TopLevelWindow, but it does not seem to change anything...
Thanks!
public static WinCell FindCellByColumnAndValue(this WinTable table, int colIndex, string strCellValue)
{
int count = table.GetChildren().Count;
for (int rowIndex = 0; rowIndex < count; rowIndex++)
{
WinRow row = new WinRow(table);
WinCell cell = new WinCell(row);
row.SearchProperties.Add(WinRow.PropertyNames.RowIndex, rowIndex.ToString());
cell.SearchProperties.Add(WinCell.PropertyNames.ColumnIndex, colIndex.ToString());
cell.SearchProperties.Add(WinCell.PropertyNames.Value, strCellValue);
if (cell.Exists)
return cell;
}
return new WinCell();
}
Edit
I modified my method to be like below(e.g. I don't use winrow anymore), this seems to be around 3x faster. It still needs 7 sec to find a cell in a table with 3 rows and 6 columns though, so it's still quite slow...
I'll mark this answer as accepted later, to leave time to other people to suggest something better
public static WinCell FindCellByColumnAndValue(this WinTable table, int colIndex, string strCellValue, bool searchHeader = false)
{
Playback.PlaybackSettings.SmartMatchOptions = Microsoft.VisualStudio.TestTools.UITest.Extension.SmartMatchOptions.None;
int count = table.GetChildren().Count;
for (int rowIndex = 0; rowIndex < count; rowIndex++)
{
WinCell cell = new WinCell(table);
cell.SearchProperties.Add(WinRow.PropertyNames.RowIndex, rowIndex.ToString());
cell.SearchProperties.Add(WinCell.PropertyNames.ColumnIndex, colIndex.ToString());
cell.SearchProperties.Add(WinCell.PropertyNames.Value, strCellValue);
if (cell.Exists)
return cell;
}
return new WinCell();
}
Edit #2:
I've tried using FindMatchingControls as per #Andrii's suggestion, and I'm nearly there, except that in the code below the cell's column index (c.ColumnIndex) has the wrong value..
public static WinCell FindCellByColumnAndValue2(this WinTable table, int colIndex, string strCellValue, bool searchHeader = false)
{
WinRow row = new WinRow(table);
//Filter rows containing the wanted value
row.SearchProperties.Add(new PropertyExpression(WinRow.PropertyNames.Value, strCellValue, PropertyExpressionOperator.Contains));
var rows = row.FindMatchingControls();
foreach (var r in rows)
{
WinCell cell = new WinCell(r);
cell.SearchProperties.Add(WinCell.PropertyNames.Value, strCellValue);
//Filter cells with the wanted value in the current row
var controls = cell.FindMatchingControls();
foreach (var ctl in controls)
{
var c = ctl as WinCell;
if (c.ColumnIndex == colIndex)//ERROR: The only cell in my table with the correct value returns a column index of 2, instead of 0 (being in the first cell)
return c;
}
}
return new WinCell();
}
I would suggest to perform direct looping through child controls - according to my experience searching controls with complicated search crieteria in Coded UI often works slow.
Edit:
To improve performance it is better to remove counting table's children and looping through rows. Also to avoid exception when finding control without row number you can use FindMatchingControls method, like in following:
public static WinCell FindCellByColumnAndValue(this WinTable table, int colIndex, string strCellValue, bool searchHeader = false)
{
Playback.PlaybackSettings.SmartMatchOptions = Microsoft.VisualStudio.TestTools.UITest.Extension.SmartMatchOptions.None;
WinCell cell = new WinCell(table);
cell.SearchProperties.Add(WinCell.PropertyNames.ColumnIndex, colIndex.ToString());
cell.SearchProperties.Add(WinCell.PropertyNames.Value, strCellValue);
UITestControlCollection foundControls = cell.FindMatchingControls();
if (foundControls.Count > 0)
{
cell = foundControls.List[0];
}
else
{
cell = null;
}
return cell;
}
When table field will be searched directly in the table it will save time for counting child controls in table. Also searching without for loop will save time for search of field for each iteration of row number that does not match.
As row number is iterated through all available values in your extension - it is not essential search criterion in the long run. In same time each iteration through a row number value invokes additional control search request - eventually multiplying method's execution time by the number of rows in grid.