I want to get rid of having negative values on the datagrid and I only want to show that once the item goes out of stock it will only show zero instead of -4
things to consider:
- I call my datagridview manually here:
void GetInventory()
{
using (SqlConnection con = new SqlConnection(Helper.GetConnection()))
{
con.Open();
string query = #"SELECT * FROM InventoryTable";
using (SqlCommand cmd = new SqlCommand(query, con))
{
using (SqlDataAdapter sda = new SqlDataAdapter(cmd))
{
DataTable dt = new DataTable();
sda.Fill(dt);
dgvItem.DataSource = dt;
}
}
}
}
This shouldn't be handled in the sql query.
This is responsibility of the view logic. DataGridView is view's control and could be right place to convert quantity into "friendly" value.
DataGridView.CelLFormatting event handler could be right tool for the job.
// In constructor
dgvItem.CellFormatting += dgvItem_CellFormatting;
private void dgvItem_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
var dgv = sender as DataGridView;
if (dgv.Columns[e.ColumnIndex].Name != "Quantity")
{
return;
}
if (e.Value == null)
{
return;
}
var quantity = (decimal)e.Value;
if (quantity < 0)
{
e.Value = 0;
}
}
So you are having products that have minus stock but you do not want final user too see it but just to see 0 (which means out of stock).
There are many approach to this problem but let's say you cannot avoid getting into minus then you can filter your datagridview after populating it. Additional function = slower program so reconsider solving problem with not getting into minus.
So how it could be done is with extension.
You create it like this (i will put simple example):
public static void ReplaceAllNegativeWithZeros(this DataGridView dgv)
{
foreach(DataGridViewRow row in dgv.Rows)
{
foreach(DataGridViewCell cell in dgv.Cells)
{
//This will loop through all cells in your currently displayed datagridview
//You call this function like yourDataGridViewInForm.ReplaceAllNegativeWithZeros();
//Here check if value from cell meets your condition and then change it.
}
}
}
With this you can check all your cells (or just one column) and do with it's values whatever you need (replace them to 0 if < 0)
One way to do this is to have your query return a formatted or calculated value, like this for example
select case when i.quantity < 0 then 0 else i.quantity end as quantityZero,
i.*
from InventoryTable i
Now you can put the original quantity column invisible on your datagridview.
This way you have both the original value at hand should you need it, and a value that will show zero when < 0 to display
It is also best practice to not do select * but to always list the fields you need.
public static void ReplaceAllNegativeWithZeros(DataGridView dgv)
{
foreach (DataGridViewRow row in dgv.Rows)
{
foreach (DataGridViewCell cell in row.Cells)
{
if (cell.Value != null)
{
if (Convert.ToInt32(cell.Value.ToString()) < 0)
{
cell.Value = 0;
}
}
}
}
}
Related
I would like to select my DataGridView's Rows by putting the value I'm looking for in a textbox. Also I would like the most identical value to be focused on / selected.
I already tried using the rowfilter function, which gave me this:
(dgv_DetailComptes.DataSource as DataTable).DefaultView.RowFilter = string.Format("Champ LIKE '%{0}%'", tbx_champ_Cpt.Text);
However, it filters the rows, meaning the other rows disapear when their content isn't the one I'm looking for. I would like to keep the rows in my table, and select the rows containing the value I'm looking for.
Also, my DGV takes it's Rows / Columns / values from a data Table so that might prevent me from using my DataGridView's row's index to search for the row containing the value.
Is there a way to select my rows this way ?
Thank you for your answers.
In the end I managed to find, doing maybe some useless stuff I don't know.
here's the code:
private void tbx_champ_Cpt_TextChanged(object sender, EventArgs e)
{
if (tbx_champ_Cpt.Text.ToString() == "")
{
for (int i = 0; i < dgv_DetailComptes.Rows.Count - 1; i++)
{
dgv_DetailComptes.Rows[i].Selected = false;
}
}
else
{
tbx_champ_Cpt.SelectionStart = tbx_champ_Cpt.Text.Length;
tbx_champ_Cpt.Text = tbx_champ_Cpt.Text.ToString().ToUpper();
DataTable d = (DataTable)dgv_DetailComptes.DataSource;
String text = tbx_champ_Cpt.Text.ToString();
DataRow[] row = d.Select("Champ like '%" + text + "%'");
List<int> listeIndex = new List<int>();
for (int i = 0; i < dgv_DetailComptes.Rows.Count - 1; i++)
{
foreach (DataRow r in row)
{
if (((DataRowView)dgv_DetailComptes.Rows[i].DataBoundItem).Row == r)
{
dgv_DetailComptes.Rows[i].Selected = true;
listeIndex.Add(i);
}
else if (!listeIndex.Contains(i))
{
dgv_DetailComptes.Rows[i].Selected = false;
}
}
}
}
if (dgv_DetailComptes.SelectedRows.Count != 0)
{
dgv_DetailComptes.FirstDisplayedScrollingRowIndex = dgv_DetailComptes.SelectedRows[0].Index;
}
}
So I have a gridview and I wanted to make certain columns text a different colour... i.e every column that says actual I want this text to be green... can anybody help? My gridlooks similar to this.
Hour - actual A - target A - actual aa - target aa - actual b - target b.
And finally is there a way to reset the data in my gridview after a certain amount of time... i.e shiftstart 6am-2pm 2pm-10pm 10pm-6am... So the data refreshes after 8 hours back to zero.
public void Refreshdata(int selectedProduct, DateTime shiftStart, DateTime shiftEnd)
{
BizManager biz = new BizManager();
GridView1.DataSource = biz.GetPacktstatisticsForShift(
shiftStart
, shiftEnd
, selectedProduct).DefaultView;
GridView1.DataBind();
public DataTable CreatePackingStats(DataSet dset)
{
using (DataManager dmgr = new DataManager())
{
DataTable target = dset.Tables[0];
DataTable actual = dset.Tables[1];
DataColumn[] cols = new DataColumn[1];
cols[0] = actual.Columns["Hour"];
actual.PrimaryKey = cols;
DataTable final = new DataTable();
// Create table columns
foreach (DataColumn col in target.Columns)
{
final.Columns.Add(new DataColumn(col.ColumnName, col.DataType));
if (col.ColumnName.Contains("Target"))
{
// Add an equivilant actual column
string newColumnName = col.ColumnName.Replace("Target", "Actual");
final.Columns.Add(newColumnName, col.DataType);
}
}
//// Add rows to new table
foreach (DataRow row in target.Rows)
{
string key = row["Hour"].ToString();
DataRow newRow = final.Rows.Add();
// Store column value
foreach (DataColumn col in final.Columns)
{
if (col.ColumnName.Contains("HOUR") || col.ColumnName.Contains("Target"))
{
newRow[col.ColumnName] = row[col.ColumnName];
}
else
{
// Find actual data
DataColumn actColumn = actual.Columns[col.ColumnName] as DataColumn;
if (actColumn == null)
{
newRow[col.ColumnName] = 0;
}
else
{
if (string.IsNullOrEmpty(actual.Rows.Find(key)[col.ColumnName].ToString()))
{
newRow[col.ColumnName] = 0;
}
else
{
newRow[col.ColumnName] = actual.Rows.Find(key)[col.ColumnName].ToString();
}
}
}
}
}
return final;
The CreatePackingStats is populating my grid with added columns FYI.
I guess there is a way to add colour text whilst the code is looping through the data and creating extra columns, not sure how to do this tho.?
And also the CreatePackingStats is located in a class and not in the page behind aspx.
Sorry about all the questions I am new and learning, all your help will help to develop and I appreciate all the help I receive.
Right-click on your GridView then go to the properties tab and select events.In there you will find the event called RowDataBound.
In that event write your code to change the forecolor like:
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
//here the Cells is an array where you can pass the index value of the cell where you want to check and if you don't know where the value is then you can do a for loop and then check the value
if (e.Row.Cells[0].Text == "someValue")
{
e.Row.Cells[0].ForeColor = System.Drawing.Color.Red;
}
}
}
Update 1 for comparing the value using the IndexOf()
As for the data what you have given, you have to change the compare function from == to IndexOf("SomeValue").For that, you can try the IndexOf("actual"). If it gives value > -1 then change the color.
or you can try the below code where I am looping through all the columns in the row(you can try to avoid the looping if you have knowledge on which column the value will occur):
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
for (int i = 0; i < e.Row.Cells.Count; i++)
{
if (e.Row.Cells[i].Text.ToLower().IndexOf("actual") > -1)
{
e.Row.Cells[i].ForeColor = System.Drawing.Color.Red;
}
}
}
}
Update 2 Adding the snapshots of sample data and it's output.
Here is the sample data with which I am working:
And here is the processed output using the IndexOf() loop over the in RowDataBound event.
Hope this helps.
I have grid with data being input.
Item Price Type
A 1000 1
B 1000 2
C 2000 2
D 3000 3
I want sum(price) with type that has value "2".
try
{
foreach (int i in gridView1.GetSelectedRows())
{
DataRow newRow = gridView1.GetDataRow(i);
if (newRow["NOMINAL"] is DBNull) { newRow["NOMINAL"] = 0; }
if (e.Column.FieldName == "IDISJ")
{
if (verifikasiNamaISJ(IDisj, e.RowHandle) == true)
{
TampilkanPesan.Error("Nama Item Sudah Ada!");
newRow["IDISJ"] = 0;
return;
}
newRow["IDISJ"] = IDisj;
gridView1.FocusedColumn = colNominal;
gridView1.FocusedRowHandle = e.RowHandle;
this.BeginInvoke((MethodInvoker)delegate
{
gridView1.ShowEditor();
});
cariDataItem(Convert.ToInt64(Global.PeriksaDBNullAngka(newRow["IDISJ"])));
newRow["NAMAISJ"] = NamaISJ;
newRow["NAMAJENISISJ"] = Jenis;
newRow["NOMINAL"] = 0;
}
DataTable dt = new DataTable()
>>>> txtTotalPotongan.EditValue = Convert.ToString(dt.Compute("SUM(NOMINAL)", "NAMAJENISISJ = 'Pemotongan'"));
>>>> txtTotalDiterima.EditValue = Convert.ToString(dt.Compute("SUM(NOMINAL)", "NAMAJENISISJ = 'Pendapatan'"));
}
}
catch (FbException ex)
{
TampilkanPesan.Error(ex.Message.ToString());
}
the line with >>>> was my work, but I have no idea how to do it. I guess I'm doing it wrong, since I put Datatable there. It's not data yet, still value of row.
But I don't know how to sum with filter, if not using filter I can do it. Please help me the right line of code for it.
I put this on gridview1_cellvaluechanged for every time something changes, it will update the value on textbox as well.
You could convert your datable to a list using System.Data.DataSetExtensions; and use Linq to filter and find the sum you are looking for.
var sum = dt.AsEnumerable().Where(x=> (int)x["Type"] == 2).Sum(x => (int)x["Price"]);
Here you can find a working example using your Items table.
I have a datagridview and a checkbox column attached to it. If the user checks a few rows and then presses a button, I would like to be able to get a certain cell value from each row where the box was ticked.
Something maybe like this:
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if (Convert.ToBoolean(row.Cells[CheckBoxColumn1.Name].Value) == true)
{
//...
}
}
The problem is that the datagridview might contain up to 3000 or 4000 rows. I would like to see if there is a faster way to get the checked rows, other than to iterate through all the rows for the grid.
If you don't want to iterate all rows, then use temporary list of checked rows.
Then after button was clicked use values from that List
HashSet<DataGridViewRow> _CheckedRows = new HashSet<DataGridViewRow>();
private void DataGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
if (DataGridView.Columns[e.ColumnIndex].Name.Equals(CheckBoxColumn1.Name) == false)
return;
DataGridViewRow row = DataGridView.Rows[e.RowIndex];
if (Convert.ToBoolean(row.Cells[CheckBoxColumn1.Name].Value) == true)
{
_CheckedRows.Add(row);
}
else
{
_CheckedRows.Remove(row);
}
}
You could manage your own list of checked rows.
You would bind to the dataGridView1.CellClick event, and add/remove rows from the list:
var checkedRows = new List<DataGridViewRow>();
dataGridView1.CellClick += (sender, args) =>
{
if (args.RowIndex != YOUR_CHECKBOX_COLUMN_INDEX)
{
return;
}
var cell = dataGridView1[args.ColumnIndex, args.RowIndex];
if (cell.Value == null)
{
cell.Value = false;
}
cell.Value = !(bool)cell.Value;
if ((bool)cell.Value)
{
checkedRows.Add(dataGridView1.Rows[args.RowIndex]);
}
else
{
checkedRows.Remove(dataGridView1.Rows[args.RowIndex]);
}
};
All you have to do then is:
foreach (DataGridViewRow row in checkedRows)
{
//...
}
You can use Linq like this :
var checkedRows = from DataGridViewRow r in dataGridView1.Rows
where Convert.ToBoolean(r.Cells[CheckBoxColumn1.Name].Value) == true
select r;
foreach (var row in checkedRows)
{
//
}
Using CheckBoxColumn1.Name instead of CheckBoxColumn1.Index seems like a tiny bottleneck to me.
To avoid the casting to DataGridViewRow and Boolean, my suggestion is something like (not tested):
int colIndex = CheckBoxColumn1.Index; // or dataGridView1.Columns.IndexOf(CheckBoxColumn1.Name) ?
for ( int r = 0; r < dataGridView1.RowCount; r++ )
{
if ( true.Equals( dataGridView1[colIndex, r].Value ) )
{
//...
}
}
The other answers that use cell events are better because the list of checked rows will be ready when needed, but also can be a bit harder to maintain/debug depending on how you do the filtering and rest. Here is my version:
private HashSet<int> checkedRowIndexes = new HashSet<int>();
private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if ( e.ColumnIndex == CheckBoxColumn1.Index )
{
if ( true.Equals( dataGridView1[CheckBoxColumn1.Index, e.RowIndex].Value ) )
checkedRowIndexes.Add(e.RowIndex);
else
checkedRowIndexes.Remove(e.RowIndex);
}
}
I have a datagridview named PTable which shows a table from database. I also have a button that functions to copy the data from selected rows to the textboxes I have.
This code that I have right now copies two selected rows from the datagridview but when I only select one row or when I select more than 2 rows, it says that "Index was out of range"
What should happen is that it should copy any number of rows and not only 2 rows
private void button11_Click(object sender, EventArgs e)
{
productid.Text = PTable.SelectedRows[0].Cells[0].Value + string.Empty;
productname.Text = PTable.SelectedRows[0].Cells[1].Value + string.Empty;
unitprice.Text = PTable.SelectedRows[0].Cells[4].Value + string.Empty;
productid2.Text = PTable.SelectedRows[1].Cells[0].Value + string.Empty;
productname2.Text = PTable.SelectedRows[1].Cells[1].Value + string.Empty;
unitprice2.Text = PTable.SelectedRows[1].Cells[4].Value + string.Empty;
}
If a user have not selected two rows, your index 1 (PTable.SelectedRows[1]) is not valid, because the item is not there.
Before you can run this code, you have to check, if the user selected two rows:
if (PTable.SelectedRows.Count == 2)
{
//Your code ...
}
else
{
//Not two rows selected
}
First, make sure SelectionMode of DataGridView to FullRowSelect(Hope you have done it already)
Second, Use foreach or any looping logic to go through each selected rows.
Third, It's always better to write modular code. Write a validation method which returns TRUE or FALSE based on selection of the grid rows.
Now based on returned value you need to continue writing your business logic.
Fourth, Make sure to use NULL checks
So let's start by re-factoring the code.
private void button11_Click(object sender, EventArgs e)
{
If(IsRowOrCellSelected())
{
//loop through selected rows and pick your values.
}
}
I am just writing sample, make sure PTable is accessible.
private boolean IsRowOrCellSelected()
{
if (PTable.SelectedRows.Count > 0)
{
DataGridViewRow currentRow = PTable.SelectedRows[0];
if (currentRow.Cells.Count > 0)
{
bool rowIsEmpty = true;
foreach(DataGridViewCell cell in currentRow.Cells)
{
if(cell.Value != null)
{
rowIsEmpty = false;
break;
}
}
}
if(rowIsEmpty)
return false;
else
return true;
}
Code can be still improved.
To work with varying number of selected rows I suggest the following.
My approach is:
There may be more than 5 rows, that You have to display. First create 3 panels for the TextBoxes. I have named the one for the productids as here. Generate the textboxes from code, this way it's easier to maintain more than 5 selected rows:
// This code goes to the constructor of the class. Not in the button click
List<TextBox> productids = new List<TextBox>();
List<TextBox> productnames = new List<TextBox>();
List<TextBox> unitprices = new List<TextBox>();
for (int i = 0; i < 5; i++)
{
productids.Add(new TextBox { Top = i * 32 });
productnames.Add(new TextBox { Top = i * 32 });
unitprices.Add(new TextBox { Top = i * 32 });
here.Controls.Add(productids[i]);
here2.Controls.Add(productnames[i]);
here3.Controls.Add(unitprices[i]);
}
Than You can in the button click set the value for each selected row:
// This code goes to the button click
// first empty the Textboxes:
foreach (TextBox productid in productids)
{
productid.Text = string.Empty;
}
foreach (TextBox productname in productnames)
{
productname.Text = string.Empty;
}
foreach (TextBox unitprice in unitprices)
{
unitprice.Text = string.Empty;
}
for (int i = 0; i < PTable.SelectedRows.Count; i++)
{
productids[i].Text = PTable.SelectedRows[i].Cells[0].Value.ToString();
productnames[i].Text = PTable.SelectedRows[i].Cells[1].Value.ToString();
// or better... Name the columns
// unitprices[i].Text = PTable.SelectedRows[i].Cells["unitPrices"].Value.ToString();
unitprices[i].Text = PTable.SelectedRows[i].Cells[4].Value.ToString();
}