I have a paging GridView in my ASP.NET project in which I do insertions of human resources into a database. My GridView loads everytime all the human resources inserted into the data base.
Now everytime I add a new row (human resource) or modify an existing one, I want it to be highlighted in the grid to make clear to the user that the operation was executed. I haven´t found a good way yet and the fact that the gridview is paged makes it more complex. I would appreciate some help :)
I'm adding the rows by binding de grid with a dataTable:
protected void llenarGrid() //se encarga de llenar el grid cada carga de pantalla
{
DataTable recursosHumanos = crearTablaRH();
DataTable dt = controladoraRecursosHumanos.consultarRecursoHumano(1, 0); // en consultas tipo 1, no se necesita la cédula
Object[] datos = new Object[4];
if (dt.Rows.Count > 0)
{
foreach (DataRow dr in dt.Rows)
{
datos[0] = dr[0];
datos[1] = dr[1];
datos[2] = dr[2];
int id = Convert.ToInt32(dr[3]);
String nomp = controladoraRecursosHumanos.solicitarNombreProyecto(id);
datos[3] = nomp;
recursosHumanos.Rows.Add(datos);
}
}
else
{
datos[0] = "-";
datos[1] = "-";
datos[2] = "-";
datos[3] = "-";
recursosHumanos.Rows.Add(datos);
}
RH.DataSource = recursosHumanos;
RH.DataBind();
}
protected DataTable crearTablaRH()
{
DataTable dt = new DataTable();
dt.Columns.Add("Cedula", typeof(int));
dt.Columns.Add("Nombre Completo", typeof(String));
dt.Columns.Add("Rol", typeof(String));
dt.Columns.Add("Nombre Proyecto");
//dt.
return dt;
}
Store the Primary Key / A unique value which uniquely identifies the row in View State when you are inserting the row :
Let's assume the first Column has unique value. Add below line at the end of your llenarGrid() method.
ViewState["LastRowUniqueValue"] = datos[0];
Handle the Page_PreRender event, highlight the inserted row:
protected void Page_PreRender(object sender, EventArgs e)
{
string lastInsertedRowValue = string.Empty;
// only highlight the row if last inserted values are NOT a Hyphen -
if (ViewState["LastRowUniqueValue"] != "-")
{
// Assuming the Unique value is String, else cast accordingly
string lastInsertedRowValue = (string)ViewState["LastRowUniqueValue"];
int rowCnt = 0;
foreach (GridViewRow row in GridView1.Rows)
{
string CellText = row.Cells[0].Text;
if (CellText.Equals(lastInsertedRowValue))
{
row.Attributes.Add(“bgcolor”, “Yellow”);
break;
}
rowCnt++;
}
}
}
I use the rowdatabound event to find the row that has been edited and then assign a bootstrap css class to that row like this:
e.Row.CssClass = "danger";
Related
I have a datagridview with a column of type string that display values for age ranges such as:
0-18
19-100
0-100
I also have a filter textbox that would need to filter on the age range
(dgv1.DataSource as DataTable).DefaultView.RowFilter =
string.Format("AgeRange LIKE '%{0}' OR AgeRange LIKE '{0}%'", textBoxFilter.Text);
The problem is that if the user enter a number like 18, the grid does not return row for 0-100
How can I get the datagrid to return both 0-18 and 0-100?
I do not think you will be able to do this using the “LIKE” comparator since the values you are looking for are “numeric”. To get the filter you are looking for, you will need a filter with “>=” and “<=” to see if the target age is in the range. It is unclear how the data is originally received, if the “age range” in each row is a string as shown, then I suggest a couple of different hacky ways. In addition, it is unclear what other columns would be in the grid.
One “hacky” approach, would be to make a method that returns a new DataTable with only the rows that fall into the given target range. To help in this endeavor, a second method that takes an int (target value we are looking for), and a DataRowView (The AgeRange we are comparing the “target” value to). This “AgeRange” will be in the rows first column. Here we simply take that string range (“0-18”) and the target value (“18”) to see if this target value IS in the range, then return true or false depending on the result. This can be done using the string.split method to split the “AgeRange” string and int.TryParse to convert the strings into numbers. Below is an example of this.
private bool TargetIsInRange(int target, DataRowView row) {
if (row.Row.ItemArray[0] != null) {
string cellValue = row.Row.ItemArray[0].ToString();
string[] splitArray = cellValue.Split('-');
int startValue;
int endValue;
if (!int.TryParse(splitArray[0], out startValue)) {
return false;
}
if (!int.TryParse(splitArray[1], out endValue)) {
return false;
}
if (target >= startValue && target <= endValue)
return true;
}
return false;
}
The method above should come in handy when looping through the grids rows to figure out which rows go into the new filter DataTable. Next, a method that does this looping through the grid and returns a filtered DataTable. For each row in the grid, we could call the above method and add the rows that return true.
private DataTable GetFilterTable() {
DataTable filterTable = ((DataTable)dgv1.DataSource).Clone();
dgv1.DataSource = gridTable;
int targetValue = -1;
if (int.TryParse(textBox1.Text, out targetValue)) {
foreach (DataGridViewRow row in dgv1.Rows) {
DataRowView dataRow = (DataRowView)row.DataBoundItem;
if (dataRow != null) {
if (TargetIsInRange(targetValue, dataRow)) {
filterTable.Rows.Add(dataRow.Row.ItemArray[0]);
}
}
}
}
return filterTable;
}
It is unclear where you are calling this filter, if you are filtering “strings” then as the user types the filter string in the text box the grid will filter with each character pressed by the user. This is nice with strings, however, in this case using “numbers”, I am guessing a button would be more appropriate. I guess this is something that you will have to decide. Putting all this together using a Button click event to signal when to filter the grid may look something like below
private DataTable gridTable;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
gridTable = GetTable();
FillTable(gridTable);
dgv1.DataSource = gridTable;
textBox1.Text = "18";
}
private void FillTable(DataTable dt) {
dt.Rows.Add("0-18");
dt.Rows.Add("19-100");
dt.Rows.Add("0-100");
dt.Rows.Add("17-80");
dt.Rows.Add("18-80");
}
private DataTable GetTable() {
DataTable dt = new DataTable();
dt.Columns.Add("AgeRange", typeof(string));
return dt;
}
private void button1_Click(object sender, EventArgs e) {
if (textBox1.Text == "") {
dgv1.DataSource = gridTable;
return;
}
dgv1.DataSource = GetFilterTable();
}
Hacky Approach 2
The first approach works; however, I am guessing if there is a LOT of data and a LOT of filtering, this may become a performance issue. Therefore, in this approach, extra steps are taken in the beginning to take advantage of the DataTables RowFilter feature, as the posted code is doing. Obviously as stated previously, we will not use the “LIKE” comparator, instead the “<=” and “>=” operators are used.
In order to accomplish this, we MUST somehow turn the given string range “XX-XX” into two (2) ints. Then “add” these integers to the DataTable. Then it will be easy to filter the table using the RowFilter property and the less than and greater than operators. One problem is that it will require “extra” work on our part to set up the grid’s columns properly or these extra two columns of data will also display.
This can be done in the “designer” or manually in code. Without going into too much detail, it is useful to bear in mind that IF you assign a DataTable as a data source to the grid AND you set the grids AutoGenerateColumns property to false… THEN only the grid columns with DataPropertyName names that “match” one of the DataTable column names… will display. In this case, we only want the AgeRange column with the “XX-XX” strings to display, the other two new columns can remain hidden from the user. Setting up the grid column manually may look something like below, however you can do this in the designer. NOTE: the designer does not display an AutoGenerateColumns property, you have to do this in your code.
private void AddGridColumn() {
DataGridViewTextBoxColumn col = new DataGridViewTextBoxColumn();
col.Name = "AgeRange";
col.DataPropertyName = "AgeRange";
col.HeaderText = "Age Range";
dgv1.Columns.Add(col);
}
The important point is that the DataPropertyName MUST match the target column name in the DataTable, otherwise the column will not display.
Next is the construction of the new DataTable. This method is given the original DataTable. A new DataTable is created with three (3) columns, AgeRange-string (displayed), StartRange-int and EndRange-int. The start and end columns will not be displayed. Once this new table is constructed, a foreach loop is started through all the rows in the original table. The string digits from the original tables row are “parsed” into actual numbers and added to the new DataTable along with the original “range” string. This method could look something like below. A helper method is further below to help split the age range string and return a number.
private DataTable GetSplitTable(DataTable sourceTable) {
DataTable dt = new DataTable();
dt.Columns.Add("AgeRange", typeof(string));
dt.Columns.Add("StartRange", typeof(int));
dt.Columns.Add("EndRange", typeof(int));
foreach (DataRow row in sourceTable.Rows) {
int startValue = GetIntValue(row.ItemArray[0].ToString(), 0);
int endValue = GetIntValue(row.ItemArray[0].ToString(), 1);
dt.Rows.Add(row.ItemArray[0], startValue, endValue);
}
return dt;
}
private int GetIntValue(string rangeString, int index) {
string[] splitArray = rangeString.Split('-');
int value = 0;
int.TryParse(splitArray[index], out value);
return value;
}
Putting all this together may look like below. Note, the button click event checks to see if the text box is empty, and if it is, will remove the current filter if one is applied.
private DataTable gridTable;
private DataTable splitTable;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
gridTable = GetTable();
FillTable(gridTable);
splitTable = GetSplitTable(gridTable);
AddGridColumn();
dgv1.AutoGenerateColumns = false;
dgv1.DataSource = splitTable;
textBox1.Text = "18";
}
private void FillTable(DataTable dt) {
dt.Rows.Add("0-18");
dt.Rows.Add("19-100");
dt.Rows.Add("0-100");
dt.Rows.Add("17-80");
dt.Rows.Add("15-75");
}
private DataTable GetTable() {
DataTable dt = new DataTable();
dt.Columns.Add("AgeRange", typeof(string));
return dt;
}
private void AddGridColumn() {
DataGridViewTextBoxColumn col = new DataGridViewTextBoxColumn();
col.Name = "AgeRange";
col.DataPropertyName = "AgeRange";
col.HeaderText = "Age Range";
dgv1.Columns.Add(col);
}
private void button1_Click(object sender, EventArgs e) {
string filterString = "";
DataView dv;
if (textBox1.Text != "") {
filterString = string.Format("StartRange <= {0} AND EndRange >= {0}", textBox1.Text);
}
dv = new DataView(splitTable);
dv.RowFilter = filterString;
dgv1.DataSource = dv;
}
This Code:
("AgeRange LIKE '%{0}' OR AgeRange LIKE '{0}%'", textBoxFilter.Text)
is a redundant with two AgeRange LIKE
If you want to search like textBoxFilter.Text you can try
("AgeRange LIKE '%{0}%'", textBoxFilter.Text)
Or
StringBuilder rowFilter = new StringBuilder();
rowFilter.Append("AgeRange Like '%" + textBoxFilter.Text + "%'");
(dgv1.DataSource as DataTable).DefaultView.RowFilter = rowFilter.ToString();
I have the following code
private void bgwSendMail_DoWork(object sender, DoWorkEventArgs e)
{
DataSet ds = getMailToSend();
DataTable table = ds.Tables[0];
{
foreach (DataRow row in table.Rows)
{
{
string attachment1 = ds.Tables[0].Rows[0]["Attachment1"].ToString();
string attachment2 = ds.Tables[0].Rows[0]["Attachment2"].ToString();
string attachment3 = ds.Tables[0].Rows[0]["Attachment3"].ToString();
string attachment4 = ds.Tables[0].Rows[0]["Attachment4"].ToString();
string mailTo = ds.Tables[0].Rows[0]["EmailTo"].ToString();
string mailSubject = ds.Tables[0].Rows[0]["EmailSubject"].ToString();
string mailBody= ds.Tables[0].Rows[0]["EmailBody"].ToString();
string uid = ds.Tables[0].Rows[0]["uid"].ToString();
if (String.IsNullOrEmpty(attachment1))
{
//TODO Send Email Straight away ignore rest
}
else
{
if (!String.IsNullOrEmpty(attachment1))
{
bool attachment1Exists = checkFileExists(attachment1);
if (attachment1Exists == false)
{
continue;
}
}
Now I would expect, when we hit continue (which does get hit) at the bottom, that we should exit back up to the foreach as below and move on to the next record in the dataset
This does not happen, it iterates over the same record from which the continue came from over and over, is this normal?
If it's normal what's the best way to get the foreach to ignore that row in the datatable once it's been exited once?
The continue is working as expected.
You are enumerating all rows in the table but you aren't using it. Instead you are always accessing the first row in the table:
DataTable table = ds.Tables[0];
foreach(DataRow row in table.Rows)
{
string attachment1 = ds.Tables[0].Rows[0]["Attachment1"].ToString();
// ...
}
You are always accessing ds.Tables[0].Rows[0].
Instead you should use this code:
foreach(DataRow row in table.Rows)
{
string attachment1 = row["Attachment1"].ToString();
// ...
}
So you are actually enumerating all rows in the table as expected, it's not an infinite loop, but you are not using every row in the table but only the first.
Change
string attachment1 = ds.Tables[0].Rows[0]["Attachment1"].ToString();
to
string attachment1 = row["Attachment1"].ToString();
and all other subsequent references to the DataRow.
I want to display grid with multiple columns (like in RepositoryItemGridLookUpEdit) after click on cell in column, but if user don't want to pick item from grid, he can write something else (like in RepositoryItemComboBox). How to combine this two features?
//user can write, but only one column
RepositoryItemComboBox cbeMaterialy = new RepositoryItemComboBox();
DataTable dt = Getdt();
cbeMaterialy.Items.Clear();
foreach(DataRow item in dt.Rows)
{
cbeMaterialy.Items.Add(item);
}
gvView.Columns["ColumnName"].ColumnEdit = cbeMaterialy;
//user cannot write but multiple columns
RepositoryItemGridLookUpEdit editor = new RepositoryItemGridLookUpEdit();
editor.DataSource = dt;
column.ColumnEdit = editor;
//SOLUTION!
I mixed some answer from Devexpress team and came up with this:
public class Main()
{
//user can choose element from DB or write new value
RepositoryItemGridLookUpEdit riglue = new RepositoryItemGridLookUpEdit();
MyGridLookupDataSourceHelper.SetupGridLookUpEdit(riglue, GetMaterialyDataView(), "Kod", "Kod");
elementsEditGrid.gvView.Columns[ColumnName].ColumnEdit = riglue;
}
///////////////////
public class MyGridLookupDataSourceHelper
{
RepositoryItemGridLookUpEdit edit;
public MyGridLookupDataSourceHelper(RepositoryItemGridLookUpEdit edit, ITypedList dataSource, string displayMember, string valueMember)
{
this.edit = edit;
//enable writing into RepositoryItemGridLookUpEdit
edit.TextEditStyle = DevExpress.XtraEditors.Controls.TextEditStyles.Standard;
edit.DataSource = dataSource;
edit.DisplayMember = displayMember;
edit.ValueMember = valueMember;
edit.ProcessNewValue += edit_ProcessNewValue;
AddExistingValuesToDataSource();
}
public static void SetupGridLookUpEdit(RepositoryItemGridLookUpEdit edit, ITypedList dataSource, string displayMember, string valueMember)
{
new MyGridLookupDataSourceHelper(edit, dataSource, displayMember, valueMember);
}
//Add new values to temporary data source (not to DataBase!)
void edit_ProcessNewValue(object sender, DevExpress.XtraEditors.Controls.ProcessNewValueEventArgs e)
{
GridLookUpEdit lookUp = sender as GridLookUpEdit;
RepositoryItemGridLookUpEdit ri = lookUp.Properties;
DataTable dt = (ri.DataSource as DataView).Table;
DataRow row = dt.NewRow();
row[ri.DisplayMember] = e.DisplayValue;
row[ri.ValueMember] = e.DisplayValue;
dt.Rows.Add(row);
ri.View.RefreshData();
e.Handled = true;
//if user wants to add new values to database (data source)
//INSERT INTO DataSourceTable (ColumnName) VALUES (e.DisplayValue)
}
//Adds to temporary data source values already stored in RepositoryItemGridLookUpEdit (in case values aren't from DB)
void ProcessExistingValues(RepositoryItemGridLookUpEdit sender, object value)
{
RepositoryItemGridLookUpEdit ri = sender;
DataTable dt = (ri.DataSource as DataView).Table;
DataRow row = dt.NewRow();
row[ri.DisplayMember] = value;
row[ri.ValueMember] = value;
dt.Rows.Add(row);
ri.View.RefreshData();
}
private void AddExistingValuesToDataSource()
{
//SELECT from DB values that are already in riglue and add them to TEMPORARY DATA SOURCE (in case values aren't from DB)
//DataView dataView = SELECT ColumnName FROM table WHERE rowID = XXX
foreach (DataRow row in dataView.AsEnumerable())
{
string kod = (string)row["ColumnName"];
ProcessExistingValues(this.edit, kod);
}
}
}
You can set the RepositoryItemGridLookUpEdit.TextEditStyle property to Standard and you will be able to type. When a user types something different from what is contained in the lookup data source, the RepositoryItemGridLookUpEdit.ProcessNewValue event will be raised. In the event handler, you can add a new value to the data source to use it further.
RepositoryItemComboBox doesn't has DataSourse property, which allow to bind table with multiple column.
ComboBoxEdit Has DataSourse property, So you can bnd table with multiple column, OR create multiple column.
New to asp.net and C# any help would be great Thank you.
My code
protected void Button1_Click(object sender, EventArgs e)
{
string x = "item_name1=number1&item_number1=product1";
NameValueCollection key = HttpUtility.ParseQueryString(x);
DataTable Theproducts = new DataTable();
Theproducts.Columns.Add("ProductID");
Theproducts.Columns.Add("ProductName");
DataRow row = Theproducts.NewRow();
int index = 1;
foreach(string keys in key.AllKeys)
{
if (keys == ("item_number" + index.ToString()))
{
row["ProductID"] = key[keys];
}
if (keys == ("item_name" + index.ToString()))
{
row["ProductName"] = key[keys];
}
Theproducts.Rows.InsertAt(row, 0);
}
GridView1.DataSource = Theproducts;
GridView1.DataBind();
}//end of button
getting error This row already belongs to this table.
You need to move the DataRow declaration inside the foreach loop.
foreach(string keys in key.AllKeys)
{
DataRow row = Theproducts.NewRow();
if (keys == ("item_number" + index.ToString()))
{
row["ProductID"] = key[keys];
}
if (keys == ("item_name" + index.ToString()))
{
row["ProductName"] = key[keys];
}
Theproducts.Rows.InsertAt(row,0);
}
Currently you are creating the DataRow object outside of the foreach loop, and on each iteration you are trying to insert the same object to the datatable. That is why you are getting the error.
It's like you were adding the same instance (row), to the datatable over and over each time. You were modifying the same row object. Moving the new row creation into the loop will solve that as new row object is created in each iteration
int index = 1;
foreach(string keys in key.AllKeys)
{
DataRow row = Theproducts.NewRow();
if (keys == ("item_number" + index.ToString()))
{
row["ProductID"] = key[keys];
}
if (keys == ("item_name" + index.ToString()))
{
row["ProductName"] = key[keys];
}
Theproducts.Rows.InsertAt(row, 0);
}
You need to move the row insert outside your loop
DataRow row = Theproducts.NewRow();
foreach(string keys in key.AllKeys)
{
-------
}
Theproducts.Rows.InsertAt(row, 0);
in your code, you try to insert the same Row for every key present (two or more times). But your table schema requires a row with only two columns.
So you need to wait the end of the loop ending before trying to insert.
I am importing data from csv file, sometimes there are column headers and some times not the customer chooses custom columns(from multiple drop downs)
my problem is I am able to change the columns type and name but when I want to import data row into cloned table it just adds rows but no data with in those rows. If I rename the column to old values it works, let's say column 0 name is 0 if I change that to something else which I need to it won't fill the row below with data but If I change zero to zero again it will any idea:
here is my coding:
#region Manipulate headers
DataTable tblCloned = new DataTable();
tblCloned = tblDataTable.Clone();
int i = 0;
foreach (string item in lstRecord)
{
if (item != "Date")
{
var m = tblDataTable.Columns[i].DataType;
tblCloned.Columns[i].DataType = typeof(System.String);
tblCloned.Columns[i].ColumnName = item;
}
else if(item == "Date")
{
//get the proper date format
//FillDateFormatToColumn(tblCloned);
tblCloned.Columns[i].DataType = typeof(DateTime);
tblCloned.Columns[i].ColumnName = item;
}
i++;
}
tblCloned.AcceptChanges();
foreach (DataRow row in tblDataTable.Rows)
{
tblCloned.ImportRow(row);
}
tblCloned.AcceptChanges();
#endregion
in the second foreach loop when it calls to import data to cloned table it adds empty rows.
After couple of tries I came up with this solution which is working:
foreach (DataRow row in tblDataTable.Rows)
{
int x = 0;
DataRow dr = tblCloned.NewRow();
foreach (DataColumn dt in tblCloned.Columns)
{
dr[x] = row[x];
x++;
}
tblCloned.Rows.Add(dr);
//tblCloned.ImportRow(row);
}
but I will accept Scottie's answer because it is less code after all.
Instead of
foreach (DataRow row in tblDataTable.Rows)
{
tblCloned.ImportRow(row);
}
try
foreach (DataRow row in tblDataTable.Rows)
{
tblCloned.LoadDataRow(row.ItemArray, true);
}