I have a ListView on another form that I populate with this code
if (choice <= 1)
{
ListViewItem items = new ListViewItem(new[]
{
activity, statusType, blueprintName, runs, installerName, actTime, endDate, location
});
lstvw.Items.Add(items);
}
I change the data on the ListView with several different outputs using this code or similar for the other outputs.
private void radioButton1_CheckedChanged(object sender, EventArgs e)
{
listView1.Visible = false;
listView1.Items.Clear();
rbInt = 1;
choice = rbInt + cbInt;
listviewcolumnbuilder();
worker.IndustryReturns(choice, listView1);
this.Text = "Industry Jobs - All Jobs";
listView1.Visible = true;
}
I want to update a specific cell, in this case actTime, that is a DateTime that I convert to a string. That value specifically counts down to 0 on certain date.
What would be a good way to update just that specific cell automatically?
Related
What I want to achieve is to dynamically change the color of the row of datagridview based on a condition that is mentioned in the code below which means if the Expiry Date is greater than the current date which means its expired then the Row changed its color to red
And if 30 days remain in Expiration than turn yellow
private void ViewMedicine_DataGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (Dont know what to write here)
{
foreach (DataGridViewRow row in ViewMedicine_DataGrid.Rows)
{
row.DefaultCellStyle.BackColor = Color.Red;
row.DefaultCellStyle.ForeColor = Color.White;
}
}
}
In order to achieve what you're looking for, you could use two nested for, one to loop your results from the query (DataTable MrkExp), one to loop the values contained in your dataGridView.
Assuming both of your database's table and dataGridView have one Unique ID or field, you could check if they're equal.
To achieve that:
for (int i = 0; i < ViewMedicine_DataGrid.Rows.Count; i++)
{
for (int j = 0; j < MrkExp.Rows.Count; j++)
{
if (ViewMedicine_DataGrid.Rows[i].Cells[0].Value != null)
{
if (ViewMedicine_DataGrid.Rows[i].Cells[0].Value == MrkExp.Rows[j]["UNIQUE_FIELD"])
{
ViewMedicine_DataGrid.Rows[i].DefaultCellStyle.BackColor = Color.Red;
ViewMedicine_DataGrid.Rows[i].DefaultCellStyle.ForeColor = Color.White;
}
}
}
}
If you haven't got a unique field or primary, consider updating your question showing your table's structure.
Finally, after a little brainstorming into the code and research a bit I got successful in getting the desired result. Here's what I code to made it possible using a RowPrePaint Event
private void ViewMedicine_DataGrid_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
{
var ExpiryDate = Convert.ToDateTime(ViewMedicine_DataGrid.Rows[e.RowIndex].Cells[7].Value);
var TodayDate = DateTime.Today;
var Expired = ExpiryDate >= TodayDate;
var ExpiredToBe = ExpiryDate >= TodayDate.AddDays(-30);
if (Expired)
{
ViewMedicine_DataGrid.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.Red;
}
else if (ExpiredToBe)
{
ViewMedicine_DataGrid.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.Yellow;
}
}
Before I was using a CellFormatting event which was wrong and Created a RowPrePaint Event and before if statements declared variables to be used in if Statement and the code I used shows the rest. It works like a charm
My advice would be to use the event DataGridViewCell.CellFormatting for this. It is called, whenever cell [x, y] needs to be formatted.
Use DataBinding to easily access the data in your rows
I don't know what kind of items you show in your DataGridView, let's assume it's a collection of price offers. Each price offer has an expiry date:
class PriceOffer
{
public DateTime ExpiryDate {get; set;}
...
}
Somewhere you tell which columns of the PriceOffer needs to be shown. Probably in the constructor:
InitializeComponent();
this.ColumnStartDate.DataPropertyName = nameof(PriceOffer.StartDate);
this.ColumnExpiryDate.DataPropertyName = nameof(PriceOffer.ExpiryDate);
this.ColumnPrice.DataPropertyName = nameof(PriceOffer.Price);
...
You also have a property to get your initial collection of PriceOffers:
IEnumerable<PriceOffer> FetchPriceOffersToDisplay(...)
{
return ...
}
And of course a property to attach these PriceOffers to the DataGridView
BindingList<PriceOffer> DisplayedPriceOffers
{
get => return (BindingList<PriceOffer>)this.dataGridView1.DataSource;
set => this.dataGridView1.DataSource = value;
}
Initialize when the form is loaded
void OnFormLoad(object sender, ...)
{
this.DisplayedPriceOffers = new BindingList<PriceOffer>(
this.FetchPriceOffersToDisplay().ToList());
}
All changes that the operator makes: add / remove / edit PriceOffers are automatically updated in this.DisplayedPriceOffers.
Back to your question
private void dataGridView1_CellFormatting(object sender,
DataGridViewCellFormattingEventArgs e)
{
DataGridViewRow row = this.dataGridView1.Rows[e.RowIndex];
PriceOffer priceOffer = (PriceOffer)row.DataboundItem;
if (DateTime.Today > priceOffer.ExpiryDate)
{
this.ColorRowExpired(row);
}
else
{
this.ColorRowNotExpired(row);
}
}
void ColorRowExpired(DataGridViewRow row)
{
foreach (DataGridViewCell cell in row.Cells)
{
cell.BackColor = ExpiredCellBackColor;
...
}
}
I am working on access based WinForm application. I have colored cell(DateColumn) in my DataGridView. I am trying to count colored cells as shown in pic and want to reflect total No. of colored cell on a label's text. I tried below codes which are not counting my DataGridView's total No. of colored cells although counting total rows. Exact problem can be understand with the help of this image
My Codes are as follows:
private void metroGrid1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (this.metroGrid1.Columns[e.ColumnIndex].DataPropertyName == "Date 1")
try
{
var EMIDate1 = Convert.ToDateTime(metroGrid1.Rows[e.RowIndex].Cells["date1DataGridViewTextBoxColumn"].Value);
for (int i = 0; i < metroGrid1.RowCount; i++)
{
if (EMIDate1 <= DateTime.Today)
{
int countDarkRed = 0;
e.CellStyle.BackColor = Color.DarkRed;
e.CellStyle.ForeColor = Color.White;
foreach (DataGridViewRow row in this.metroGrid1.Rows)
{
if (row.Cells["date1DataGridViewTextBoxColumn"].Style.BackColor == Color.DarkRed)
{
countDarkRed++;
}
}
labelEMI.Text = "Total EMI due as on today:" + countDarkRed;
}
}
}
catch
{
}
}
Short answer
You want to set the styles in the grid's cell, not only in the current formatting session of the cell. You are colouring the wrong CellStyle. Directly access the grid cell style instead of using the event cell and your code will do what you want (tested on my machine):
if (EMIDate1 <= DateTime.Today)
{
this.metroGrid1[e.ColumnIndex, e.RowIndex].Style.BackColor = Color.DarkRed;
this.metroGrid1[e.ColumnIndex, e.RowIndex].Style.ForeColor = Color.White;
}
Long Answer
DONT DO THIS
The formatting event will be applied only on VISIBLE cells
rows or columns hidden because the control/windows is too small will not trigger this code
You are mixing formatting logic (colours) with business logic (expired dates)
try to do this check before even binding the grid, or make another call to the database
The documentation explicitly discurage to do too much process inside the cell formatting event
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.datagridview.cellformatting?view=netframework-4.7.2#remarks
This code will be executed every time there is a windows redraw, mouse over, etc, making your label one of the most expensive labels in your program
you should make that counting just once
Complete working example
Create a window form application with just one datagridview and a label
public Form1()
{
InitializeComponent();
dataGridView1.DataSource = new[] {
new {Title = "bella", Date1 = DateTime.Now.AddDays(1)},
new {Title = "ciao", Date1 = DateTime.Now.AddDays(12)},
new {Title = "bella", Date1 = DateTime.Now.AddDays(-1)},
new {Title = "ciao", Date1 = DateTime.Now.AddDays(-31)},
new {Title = "bella", Date1 = DateTime.Now.AddDays(11)},
new { Title= "ciao", Date1 = DateTime.Today} ,
new { Title= "ciao", Date1 = DateTime.Today} ,
new { Title= "ciao", Date1 = DateTime.Today.AddDays(-7)} };
}
private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (dataGridView1.Columns[e.ColumnIndex].DataPropertyName == "Date1")
{
var date = dataGridView1.Rows[e.RowIndex].Cells["Date1"].Value as DateTime?;
if (date.HasValue && date.Value <= DateTime.Today)
{
dataGridView1[e.ColumnIndex, e.RowIndex].Style.BackColor = Color.DarkRed;
dataGridView1[e.ColumnIndex, e.RowIndex].Style.ForeColor = Color.White;
}
int countDarkRed = 0;
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if (row.Cells["Date1"].Style.BackColor == Color.DarkRed)
countDarkRed++;
}
label1.Text = $"dark = {countDarkRed}";
}
}
Where you went wrong in the previous case was, you were checking only the first cell and kept incrementing the count, now you've confused my comments on your previous question. Here's the outline:
There is one if loop to check the date and change color, and one for loop to count the number of cells that have changed color
private void metroGrid1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (this.metroGrid1.Columns[e.ColumnIndex].DataPropertyName == "Date 1")
{
try
{
int countDarkRed = 0;
var EMIDate1 = Convert.ToDateTime(metroGrid1.Rows[e.RowIndex].Cells["date1DataGridViewTextBoxColumn"].Value);
//Checking whether we have to turn it red or not
if (EMIDate1 <= DateTime.Today)
{
e.CellStyle.BackColor = Color.DarkRed;
e.CellStyle.ForeColor = Color.White;
}
//Checking how many cells have turned red
foreach(DataGridViewRow row in this.metroGrid1.Rows)
{
if (row.Cells["date1DataGridViewTextBoxColumn"].Style.BackColor == Color.DarkRed)
{
countDarkRed++;
}
}
labelEMI.Text = "Total EMI due as on today:" + countDarkRed;
}
catch
{
}
}
}
I'm new to programming and I'm trying to change the value of a textbox depending of a selected value on a combo box, since the values are numerical 1 to 20, and depending on the selection it will be the number of text boxes visible. I'm using the event selected index changed.
Here is my code:
private void cbxPIN_SelectedIndexChanged(object sender, EventArgs e)
{
int pines = Convert.ToInt32(cbxPIN.SelectedItem.ToString());
if (pines == 1)
{
textbox1.visible = true;
}
else if (pines == 2)
{
textbox1.visible = true;
textbox2.visible = true;
}
...
else if (pines == n)
{
textbox1.visible = true;
textbox2.visible = true;
...
textboxn.visible = true;
}
}
since there are like 25 different numeric values on the combo box is there an easier way of doing this? asides from comparing each different value?
Something like a loop.
At the least, I'd rewrite it like this, to reduce duplication:
private void cbxPIN_SelectedIndexChanged(object sender, EventArgs e)
{
int pines = Convert.ToInt32(cbxPIN.SelectedItem.ToString());
if (pines >= 1)
textbox1.Visible = true;
if (pines >= 2)
textbox2.Visible = true;
...
if (pines >= n)
textboxn.Visible = true;
}
Actually, I'd add all the TextBox controls to a collection, possibly in the constructor:
List<TextBox> TextBoxes = new List<TextBox> { textbox1, textbox2, ... textboxn };
Then use LINQ's Take() method to grab the first xx number of controls and iterate through them:
foreach (var tb in TextBoxes.Take(pines))
textBox.Show();
You want to use a loop structure. You should validate that the number of loops to perform is > 0 and < the number of textboxes you have available, but I'll leave error handling to you.
private void cbxPIN_SelectedIndexChanged(object sender, EventArgs e)
{
int pines = Convert.ToInt32(cbxPIN.SelectedItem.ToString());
TextBox textBox;
for (int i = 1; i <= pines; i++)
{
// get the control from the form's controls collection by the control name
textBox = this.Controls["textbox" + pines.ToString()] As TextBox
textBox.Visible = true;
}
}
I've got a DataGidView that's very tight on horizontal space, and a column that has rather lengthy descriptions.
What I'd like to do is create a ComboBox column that:
Displays a shortened version of the description
Has the full description in the drop-down
Behind the scenes deals with the actual value
So for example, I have this contrived example below. The real list can have 20-30 items with text that's quite lengthy:
Code Short DropDown Text
1 BigBox Boxes larger than 6' x 6'
2 SmBox Boxes smaller than 6' x 6'
3 BigBrl Barrel 55 gallons
4 SmBrl Barrel less than 55 gallons
So what I want to show is:
When I open the dropdown I want to see:
And of course, when I query the value for the cell, I want "1".
I could split hairs and make the "short" description the first part of the longer description ("BigBx Boxes larger than 6' x 6'") but that doesn't seem right.
I'm looking for suggestions on the best way to accomplish this. No code yet to show, since I'm not quite sure where to start.
You'll want to change the DisplayMember of the combobox on the events DropDown and DropDownClosed.
On the Event DropDown, You'll want to change the DisplayMember to your Long Name. One thing I discovered is that changing the DisplayMember resets the SelectedIndex, so you'll want to save that and then reset it after you've changed the display.
int i = comboBox1.SelectedIndex;
comboBox1.DisplayMember = "LongNm";
comboBox1.SelectedIndex = i;
You'll want to repeat this process with the DropDownClosed event and the Short Name to return to your closed display.
int i = comboBox1.SelectedIndex;
comboBox1.DisplayMember = "ShortNm";
comboBox1.SelectedIndex = i;
The last problem I encountered was changing the width of the combobox dropdown. I found code to do that here.
The final code I had looks like this:
public partial class Form1 : Form
{
List<ComboData> data;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
data = new List<ComboData>();
data.Add(new ComboData(1, "BigBox", "Boxes larger than 6x6"));
data.Add(new ComboData(2, "SmBox", "Boxes small than 6x6"));
data.Add(new ComboData(3, "BirBrl", "Barrel 55 Gallons"));
data.Add(new ComboData(4, "smBrl", "Barrel less than 55 gallons"));
comboBox1.DataSource = data;
comboBox1.DisplayMember = "ShortNm";
}
private void comboBox1_DropDown(object sender, EventArgs e)
{
int i = comboBox1.SelectedIndex;
comboBox1.DisplayMember = "LongNm";
comboBox1.SelectedIndex = i;
ComboBox senderComboBox = (ComboBox)sender;
int width = senderComboBox.DropDownWidth;
Graphics g = senderComboBox.CreateGraphics();
Font font = senderComboBox.Font;
int vertScrollBarWidth =
(senderComboBox.Items.Count > senderComboBox.MaxDropDownItems)
? SystemInformation.VerticalScrollBarWidth : 0;
int newWidth;
foreach (ComboData s in ((ComboBox)sender).Items)
{
newWidth = (int)g.MeasureString(s.LongNm, font).Width
+ vertScrollBarWidth;
if (width < newWidth)
{
width = newWidth;
}
}
senderComboBox.DropDownWidth = width;
}
private void comboBox1_DropDownClosed(object sender, EventArgs e)
{
int i = comboBox1.SelectedIndex;
comboBox1.DisplayMember = "ShortNm";
comboBox1.SelectedIndex = i;
}
}
public class ComboData
{
public int Code;
public string ShortNm
{
get;
set;
}
public string LongNm
{
get;
set;
}
public ComboData(int c, string s, string l)
{
Code = c;
ShortNm = s;
LongNm = l;
}
}
The end result looks like this:
I think I have nearly the thing you want. One small compromise: Whilr you're editing the value, the combobox displays the truncated long value. Otherwise this should suit you well.
My Source code is a slightly edited version of this example. In the example they have an enum that is editable via a comboboxcolumn. My extension now changes the combobox to have a display and value member (the displaymember is the long text and the value member is the actual enum value) and when displaying the cell normally, the cell formatting event comes into play that overrides the display
These are the main changes:
private static T GetAttribute<T>(Enum value)
{
T attribute = value.GetType()
.GetMember(value.ToString())[0].GetCustomAttributes(typeof(T), false)
.Cast<T>()
.SingleOrDefault();
return attribute;
}
DataGridViewComboBoxColumn CreateComboBoxWithEnums()
{
DataGridViewComboBoxColumn combo = new DataGridViewComboBoxColumn();
//combo.DataSource = Enum.GetValues(typeof(Title));
var datatable = new DataTable(); //Setup a DataTable for your ComboBox Column
datatable.Columns.Add("col1", typeof(string));
datatable.Columns.Add("col2", typeof(Title));
foreach (Title item in Enum.GetValues(typeof(Title)))
datatable.Rows.Add(GetAttribute<DescriptionAttribute>(item).Description, item);
combo.DisplayMember = "col1";
combo.ValueMember = "col2";
combo.DataSource = datatable;
combo.DropDownWidth = 200;
combo.DataPropertyName = "Title";
combo.Name = "Title";
return combo;
}
private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (e.ColumnIndex == 0)//where Column1 is your combobox column
{
var val = dataGridView1[e.ColumnIndex, e.RowIndex].Value;
if (val != null)
{
e.Value = ((Title)val).ToString();
e.FormattingApplied = true;
}
}
}
public enum Title
{
[Description("This better be thy king!")]
King,
[Description("aka wanna-be-king")]
Sir
};
Full code: http://pastebin.com/Mp9DRvDn
You can use the click event or the dropdown event to detect when the user tries to open the combobox. Then display a list box with the longer strings right below the combobox. When the user selects a string from the listbox, set the corresponding value in the combobox.
Goal in mind, the concept is kind of like a shopping cart, so as they add items to list(Detail) it keeps the items they are adding in memory.
This works when ever I first load the list(grid) and add more rows. But if I set the first row and set the item and price and then decide to add 3 more rows then
the info I had added gets deleted instead of keeping its values and just load more lines to the list which would repopulate the gridview.
In the past I have done this with datatables but I want to be able to move from that and use this List Class
Also I have it set as viewstate so I can use it through out my page.
private ListArDocumentdetail Detail
{
get
{
ListArDocumentdetail _detail = new ListArDocumentdetail();
if (ViewState["Detail"] != null)
{
_detail = (ListArDocumentdetail)ViewState["Detail"];
}
return _detail;
}
set
{
ViewState["Detail"] = value;
}
}
protected void Page_Load(object sender, EventArgs e)
{
//creates 2 rows to start off
CreateRows(2);
}
public void CreateRows(int rowstoadd)
{
int newtotalrows = Detail.Count + rowstoadd - 1;
for (int i = Detail.Count; i <= newtotalrows; i++)
{
ArDocumentdetail detail = new ArDocumentdetail();
detail.Lineid = i;
detail.Itemid = 0;
detail.Quantity = 1;
if (Detail.Count > 0)
Detail.Insert(Detail.Count, detail);
else
Detail.Add(detail);
Detail = Detail;
}
gvInvoiceDetail.DataSource = Detail;
gvInvoiceDetail.DataBind();
GridViewRow row = gvInvoiceDetail.Rows[gvInvoiceDetail.Rows.Count - 1];
ImageButton btnAdd = (ImageButton)row.FindControl("btnAdd");
btnAdd.Visible = true;
}
protected void ibAdd_Click(object sender, ImageClickEventArgs e)
{
//user can type in how many rows they want to add on to current amount of rows
//so since grid starts off at 2 and they type 3 the grid refreshes with 5 rows.
CreateRows(Convert.ToInt32(txtRows.Text));
}
protected void UpdateRow(object sender, EventArgs e)
{
ImageButton btnUpdate = sender as ImageButton;
GridViewRow row = btnUpdate.NamingContainer as GridViewRow;
TextBox txtPrice = (TextBox)row.FindControl("txtPrice");
TextBox txtQuantity = (TextBox)row.FindControl("txtQuantity");
DropDownList ddlDescription = (DropDownList)row.FindControl("ddlDescription");
int index = Detail.FindIndex(f => f.Lineid == row.RowIndex);
Detail[index].Itemid = Convert.ToInt32(ddlDescription.SelectedValue);
Detail[index].Price = Convert.ToDecimal(txtPrice.Text);
Detail[index].Subtotal = Convert.ToDecimal(Detail[index].Price * Convert.ToInt32(txtQuantity.Text));
}
I can suggest you the logic:
Push a list into viewstate say Viewstate["List"],
Let a user chose an item. Then List list = (List)Viewstate["List"];
Add the selected item to List list. i.e. list.Add(item);
Now push the item back to viewstate. Viewstate["list"] = list;
Bind it to grid or display it on page. Whatever you want.