LINQ: find all checked checkboxes in a GridView - c#

Consider the current algorithm below that iterates through a GridView's rows to find whether the contained Checkbox is selected/checked.
List<int> checkedIDs = new List<int>();
foreach (GridViewRow msgRow in messagesGrid.Rows)
{
CheckBox chk = (CheckBox)msgRow.FindControl("chkUpdateStatus");
if (chk.Checked){
//we want the GridViewRow's DataKey value
checkedMsgIDs.Add(int.Parse(messagesGrid.DataKeys[msgRow.RowIndex].Value.ToString()));
}
}
This works as expected: you're left with a fully populated List<int>.
Question: How would you or could you re-write or improve this algorithm using LINQ to search the GridView for all the rows who have their Checkbox selected/checked?

I'm pretty sure you're not going to get any performance improvement from this, but it might make it slightly easier to read:
var checkedIDs = from GridViewRow msgRow in messagesGrid.Rows
where ((CheckBox)msgRow.FindControl("chkUpdateStatus")).Checked
select Int32.Parse(messagesGrid.DataKeys[msgRow.RowIndex].Value.ToString());
Again, not sure it makes a difference. Also, why are you converting to a string then to an int? Is there something Convert.ToInt32 can't do for you?

I am not sure if Rows is IEnumerable they may not be, but I am going to assume they are
List<int> checkedIDs = messagesGrid.Rows
.Where<GridViewRow>(i => (CheckBox)i.FindControl("chkUpdateStatus").Checked)
.Select<GridViewRow, int>(i => return int.Parse(messagesGrid.DataKeys[i.RowIndex].Value.ToString()))
.ToList<int>();
I just did this in notepad, there might be a compile error in there. But this is how you could do the same thing with Linq.

I have something similar but I was using it in more than one place so I created an extension method.
public static void ActOnCheckedRows(this GridView gridView, string checkBoxId, Action<IEnumerable<int>> action)
{
var checkedRows = from GridViewRow msgRow in gridView.Rows
where ((CheckBox)msgRow.FindControl(checkBoxId)).Checked
select (int) gridView.DataKeys[msgRow.RowIndex].Value;
action(checkedRows);
}
So now I can do something with all the checked rows. The compiler is pretty good at deducing the types but occasionally I need to explicitly declare checkedRows as type IEnumerable.
gvTasksToBill.ActOnCheckedRows("RowLevelCheckBox", checkedRows =>
{
foreach (int id in checkedRows)
{
// do something with id
}
});

Related

Remove foreach loop with the help of linq

I have some problem with my code. I want to replace the ForEach loop with the help of LINQ here, is there any way or solution to solve my problem? My code is given bellow.
static public string table2Json(DataSet ds, int table_no)
{
try
{
object[][] tb = new object[ds.Tables[table_no].Rows.Count][];
int r = 0;
foreach (DataRow dr in ds.Tables[table_no].Rows)
{
tb[r] = new object[ds.Tables[table_no].Columns.Count];
int col = 0;
foreach (DataColumn column in ds.Tables[table_no].Columns)
{
tb[r][col] = dr[col];
if ((tb[r][col]).Equals(System.DBNull.Value))
{
tb[r][col] = "";
}
col++;
}
r++;
}
string table = JsonConvert.SerializeObject(tb, Formatting.Indented);
return table;
}
catch (Exception ex)
{
tools.log(ex.Message);
throw ex;
}
}
This question really asks 3 different things:
how to serialize a DataTable
how to change the DataTable serialization format and finally
how to replace nulls with empty strings, even though an empty string isn't a NULL.
JSON.NET already handles DataSet and DataTable instance serialization with a DataTableConverter whose source can be found here. You could just write :
var str = JsonConvert.SerializeObject(data);
Given this DataTable :
var dataTable=new DataTable();
dataTable.Columns.Add("Name",typeof(string));
dataTable.Columns.Add("SurName",typeof(string));
dataTable.Rows.Add("Moo",null);
dataTable.Rows.Add("AAA","BBB");
You get :
[{"Name":"Moo","SurName":null},{"Name":"AAA","SurName":"BBB"}]
DataTables aren't 2D arrays and the column names and types matter. Generating a separate row object with named fields is far better than generating an object[] array. It also allows makes it far easier for clients to handle the JSON string without knowing its schema in advance. With an object[] for each row, the clients will have to know what's stored in each location in advance.
If you want to use a different serialization format, you could customize the DataTableConverter. Another option though, is to use DataRow.ItemArray to get the values as an object[] and LINQ to get the rows, eg :
object[][] values=dataTable.Rows.Cast<DataRow>()
.Select(row=>row.ItemArray)
.ToArray();
Serializing this produces :
[["Moo",null],["AAA","BBB"]]
And there's no way to tell which item is the name and which is the surname any more.
Replacing DBNulls with strings in this last form needs an extra Select() to replace DBNull.Value with "" :
object[][] values=dataTable.Rows.Cast<DataRow>()
.Select(row=>row.ItemArray
.Select(x=>x==DBNull.Value?"":x)
.ToArray())
.ToArray();
Serializing this produces :
[["Moo",""],["AAA","BBB"]]
That's what was asked, but now we have no way to tell whether the Surname is an empty string, or just doesn't exist.
This may sound strange, but Arabic names may be one long name without surname. Makes things interesting for airlines or travel agents that try to issue tickets (ask me how I know).
We can get rid of ToArray() if we use var :
var values=dataTable.Rows.Cast<DataRow>()
.Select(row=>row.ItemArray
.Select(x=>x==DBNull.Value?"":x));
JSON serialization will work the same.
LINQ is not a nice fit for this sort of thing because you are using explicit indexes r and col into multiple "array structures" (and there is no easy/tidy way to achieve multiple, parallel enumeration).
Other issues
tb is repeatedly newed, filled with data and then replaced in the next iteration, so you end up capturing only the last row of input to the JSON string - that's a logical bug and won't work as I think you intend.
The inner foreach loop declares but does not use the iteration variable column - that's not going to break anything but it is redundant.
You will get more mileage out of using JSON.Net properly (or coding the foreach loops as for loops instead if you want to navigate the structures yourself).

DataGridView, how to skip duplicates

I have a DataGridView and im trying to check if row already exist if yes I want to skip a loop from outer "foreach" statment.
I figured something like this, but maybe there is some more optimized way to do this.
(This function is deleting current row, and inserting new one, to not duplicating)
(outer foreach)
for (int i = 0; i < dataGridViewFarm.RowCount; i++)
{
if (villageParams[3] == dataGridViewFarm.Rows[i].Cells[3].Value.ToString())
{
dataGridViewFarm.Rows.Remove(dataGridViewFarm.Rows[i]);
}
}
You can remove duplicated rows from the source of dataGridView using Linq to objects, and set the new object as the new DataSource
Method1:
Example: suppose that cell[3] is mycell3
var noDuplicationList = myList.GroupBy(x => x.mycell3)
.Select(g => g.First())
.ToList();
Method2: use DistinctBy() in the MoreLinq project
Install-Package morelinq
var noDuplicationList = myList.DistinctBy( x => x.mycell3).ToList();
It seams like you are using Windows Forms. Adding this tag might help, in order to get answers.
Never the less. How are those rows created? User-input or form a collection?
In case of a collection: I would just check for duplicates before adding the to the dataGridView.
In case of user-input: Also check for duplicates before creating a row.

is there an easier way to reverse the order of a DataGridViewSelectedRowCollection?

For some strange reason, a DataGridViewSelectedRowCollection is populated in reverse order from what is displayed in theDataGridView. But what is more puzzling is why there isn't a straightforward way of reversing the order to use in a foreach loop.
I would like to be able to use syntax as simple as this:
foreach (DataGridViewRow r in dataGridView1.SelectedRows.Reverse())
...but of course, that is not supported.*
So, currently I am using this monstrosity:
//reverse the default selection order:
IEnumerable<DataGridViewRow> properlyOrderedSelectedRows
= dataGridView1.SelectedRows.Cast<DataGridViewRow>().ToArray().Reverse();
foreach (DataGridViewRow r in properlyOrderedSelectedRows )
{
MessageBox.Show( r.Cells["ID"].Value.ToString());
}
...which is terribly ugly and convoluted. (I realize I could use a reverse For loop, but I prefer the foreach for its readability.)
What am I missing here? Is there a simpler approach?
*Actually, I would have expected this version to work, according to the discussion here, since DataGridViewSelectedRowCollection implements IEnumerable; but it doesn't compile.
I think you just need to cast in your for each loop like:
foreach (DataGridViewRow row in dataGridView1.SelectedRows.Cast<DataGridViewRow>().Reverse()) {
}
however this isn't as efficient even if it appears to be less code as it has to basically go through the enumerator forwards putting everything on a stack then pops everything back out in reverse order.
If you have a directly-indexable collection you should definitely use a for loop instead and enumerate over the collection in reverse order.
As mentioned here Possible to iterate backwards through a foreach?
You could add the rows to a stack...
stack<DataGridViewRow> properlyOrderedSelectedRows = new stack<DataGridViewRow>(dataGridView1.SelectedRows);
foreach (DataGridViewRow r in properlyOrderedSelectedRows )
{
MessageBox.Show( r.Cells["ID"].Value.ToString());
}
Stack<T> has a constructor that accepts IEnumerable<T>
But what is more puzzling is why there isn't a straightforward way of reversing the order to use in a foreach loop.
I would like to be able to use syntax as simple as this:
...
...but of course, that is not supported.*
How about making it work yourself instead of all these pseudo witty statements, "monstrosities" and highly inefficient LINQ-es. All you need is to write a one liner function in some common place.
public static IEnumerable<DataGridViewRow> GetSelectedRows(this DataGridView source)
{
for (int i = source.SelectedRows.Count - 1; i >= 0; i--)
yield return source.SelectedRows[i];
}

Datagridview how to cast selected row to custom object

I use c# winforms with MSSQL database.
I have table in database "Pilots"
, i fill datagridview "dgvPilots", with data from Pilots table.
dgvPilots.DataSource = Connection.dm.Pilots.ToList();
I enable multiselect.
now i need to get multiselected data from datagridview.
How can i multiselected rows cast to "Pilots" object and get PilotsID.
My current error is "Unable to cast object type DataGridViewRow to type ".Data.Pilots"...
i also try casting like this
dgvPilots.SelectedRows.Cast<Pilots>().ToList();
but it return DataGridViewRow item type.
You will need to iterate the collection and go after the DataBoundItem property which is the underlying data.
var pilots = new List<Pilots>(grid.SelectedRows.Count);
for(int index = 0; index < grid.SelectedRows.Count; index++)
{
var selectedRow = grid.SelectedRows[index];
var pilot = (Pilots)selectedRow.DataBoundItem;
pilots.Add(pilot);
}
The code above shows how you can achieve this, (I freehanded the code so forgive any syntax errors).
Here is the msdn article on the DataBoundItem property: http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridviewrow.databounditem(v=vs.110).aspx
I don't know what your DB structure is but
var selectedPilots = dgvPilots.SelectedRows.Cast<Pilots>().ToList();
is the proper way to do it. However I do suspect Pilots in your case is a DataTable, and what you need is to cast those items to proper Class type - If I'd have to shoot, I'd say you've got a Pilot (singular) class, that you should cast to.
List<int> indexes = DataGrid1.SelectedRows.Cast<DataGridViewRow>().Select(x => x.Index).ToList();
foreach (i in indexes)
{
Pilots Pilot = (Pilots)DataGrid1.Rows[i].DataBoundItem;
}
This simple generic extension method is what I've always used:
public static List<T> ToList<T>(this DataGridViewSelectedRowCollection rows)
{
var list = new List<T>();
for (int i = 0; i < rows.Count; i++)
list.Add((T)rows[i].DataBoundItem);
return list;
}
Use: dgvPilots.SelectedRows.ToList<Pilots>()
First, wait three years for your answer.
Second, please, don't iterate.
var selectedPilots =
(from DataGridViewRow cada in dgvPilots.SelectedRows select cada.DataBoundItem)
.Select(x => (Pilots)x).ToList();
This is how you can do it without explicitly iterating over the SelectedRows
dgvPilots.SelectedRows.Cast<DataGridViewRow>().Select(row => row.DataBoundItem as Pilots);
or
dgvPilots.SelectedRows.Cast<DataGridViewRow>().Select(row => (Pilots) row.DataBoundItem);
Not sure if this is still relevant, but I found out this:
IList<Pilots> pilots = (BindingList<Pilots>)((BindingSource)dataGridView1.DataSource).List;

Foreach Question

I am trying to use a foreach statement. I would like to be able to do something like this:
foreach (int item in itemcount)
{
label{0}.Text = item.ToString()
}
Where {0} would be the number that is in item.
Is this possible?
You can do something like this:
foreach (int item in itemcount)
{
Controls[string.Format("label{0}", item.Id)].Text = item.ToString();
}
Also, if you need to access properties of the label class you need to cast the left expression to Label class.
If your class is an ASP.NET page then you should be able to find it with FindControl:
foreach (int item in itemcount)
{
string name = string.Format("label{0}", item);
Label label = FindControl(name) as Label;
if (label != null)
{
label.Text = name;
}
}
If you're trying to programmatically access a variable name, you need to use Reflection.
Off the top of my head, the answer is no. As you do not have access to the iterator the foreach loop is using.
Instead do something like this
IList<Label> labels = new ArrayList<Label>();
for(int i = 0;i<labels.count;i++)
{
labels[i].text = i.toString();
}
Is "label" an array? Then you should do this :
label[item] = item.ToString();
And if label is not an array, probably you should use an array instead of using variable names like label0, label1, ... it would be a lot easier than using reflection.
foreach (int item in itemcount)
{
label[label.IndexOf(item)].Text = item.ToString()
}
If I need to assume that "label" is a list of labels (List/IList), you've an indexer accessed with [], and lists have an IndexOf method accepting a given existing object in the whole list that returns the index of the "item" - in the list -, as you can check in MSDN:
http://msdn.microsoft.com/en-us/library/5y536ey6.aspx (IList interface, see methods section).
Just for the sake of throwing answers, depending on itemcount type, you could write:
Array.ForEach(itemcount, item => label[Convert.ToInt32(item)].Text = item.ToString());
if itemcount is an object[], or
itemcount.ForEach(item => label[Convert.ToInt32(item)].Text = item.ToString());
if itemcount is a List<object>.
While code such as those examples posted earlier does in a strict sense answer the question, it seems like really, really poor design to go down that path.
Depending on your needs, and what framework you are using, you are much better off using some other technique. Arrays of controls are one option; if you're using WPF then this can be achieved quite elegantly using a list that visualizes its elements as labels (you should get that for free, but if not, it'd be trivial to change the control's ItemTemplate - sorry, I don't have any example immediately at hand).

Categories

Resources