Flickering ListView in virtual mode - c#

I found a not so funny bug in the default ListView (not owner drawed!). It flickers heavily when items are added constantly into it (by using Timer to example) and user is trying to see items slightly away from selected item (scrolled either up or down).
Am I doing something wrong here?
Here is some code to reproduce it:
Create WindowsFormsApplication1;
set form WindowState to Maximized;
put on form timer1, set Enabled to true;
put on form listView1:
this.listView1.Dock = System.Windows.Forms.DockStyle.Fill;
this.listView1.View = System.Windows.Forms.View.Details;
this.listView1.VirtualMode = true;
add one column;
add event
this.listView1.RetrieveVirtualItem += new System.Windows.Forms.RetrieveVirtualItemEventHandler(this.listView1_RetrieveVirtualItem);
and finally
private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
e.Item = new ListViewItem(e.ItemIndex.ToString());
}
private void timer1_Tick(object sender, EventArgs e)
{
listView1.VirtualListSize++;
}
Now run it and wait until scrollbar on listview will appears (as timer will add enough items), then:
Select one of the first items in the listview (with mouse or keys), then scroll down by using scrollbar or mouse wheel, so that selected item will go outside of current view (up). The more you scroll down, the heavier flickering will become! And look at what scrollbar is doing ?!?!?
Similar effect appears if scrolling selected item down.
Question
How do I deal with it? Idea is to have sort of constantly updating log window with possibility to stop auto-scrolling and go up/down to investigate events in close proximity. But with that kek-effect it is just not possible!

It looks like problem is related to Selected / Focused combo (perhaps someone from Microsoft can confirm).
Here is a possible workaround (it's dirty and I liek it!):
private void timer1_Tick(object sender, EventArgs e)
{
// before adding
if (listView1.SelectedIndices.Count > 0)
{
if (!listView1.Items[listView1.SelectedIndices[0]].Bounds.IntersectsWith(listView1.ClientRectangle))
listView1.TopItem.Focused = true;
else
listView1.Items[listView1.SelectedIndices[0]].Focused = true;
}
// add item
listView1.VirtualListSize++;
}
Trick is to check before adding new item whenever currently selected item is away (here is the topic of how to check). And if item is away, then set focus to the current TopItem temporarily (until user scroll back, so that selected item will be again "visible" and this is when it gets focus back).

After some finding for a work-around, I realized that you can't prevent flicker once you select an item. I've tried using some ListView messages but fail. If you want to research more on this, I think you should pay some attention at LVM_SETITEMSTATE and maybe some other messages. After all, I thought of this idea, we have to prevent the user from selecting an item. So to fake a selected item, we have to do some custom drawing and faking like this:
public class CustomListView : ListView
{
public CustomListView(){
SelectedIndices = new List<int>();
OwnerDraw = true;
DoubleBuffered = true;
}
public new List<int> SelectedIndices {get;set;}
public int SelectedIndex { get; set; }
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x1000 + 43) return;//LVM_SETITEMSTATE
else if (m.Msg == 0x201 || m.Msg == 0x202)//WM_LBUTTONDOWN and WM_LBUTTONUP
{
int x = m.LParam.ToInt32() & 0x00ff;
int y = m.LParam.ToInt32() >> 16;
ListViewItem item = GetItemAt(x, y);
if (item != null)
{
if (ModifierKeys == Keys.Control)
{
if (!SelectedIndices.Contains(item.Index)) SelectedIndices.Add(item.Index);
}
else if (ModifierKeys == Keys.Shift)
{
for (int i = Math.Min(SelectedIndex, item.Index); i <= Math.Max(SelectedIndex, item.Index); i++)
{
if (!SelectedIndices.Contains(i)) SelectedIndices.Add(i);
}
}
else
{
SelectedIndices.Clear();
SelectedIndices.Add(item.Index);
}
SelectedIndex = item.Index;
return;
}
}
else if (m.Msg == 0x100)//WM_KEYDOWN
{
Keys key = ((Keys)m.WParam.ToInt32() & Keys.KeyCode);
if (key == Keys.Down || key == Keys.Right)
{
SelectedIndex++;
SelectedIndices.Clear();
SelectedIndices.Add(SelectedIndex);
}
else if (key == Keys.Up || key == Keys.Left)
{
SelectedIndex--;
SelectedIndices.Clear();
SelectedIndices.Add(SelectedIndex);
}
if (SelectedIndex == VirtualListSize) SelectedIndex = VirtualListSize - 1;
if (SelectedIndex < 0) SelectedIndex = 0;
return;
}
base.WndProc(ref m);
}
protected override void OnDrawColumnHeader(DrawListViewColumnHeaderEventArgs e)
{
e.DrawDefault = true;
base.OnDrawColumnHeader(e);
}
protected override void OnDrawItem(DrawListViewItemEventArgs e)
{
i = 0;
base.OnDrawItem(e);
}
int i;
protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e)
{
if (!SelectedIndices.Contains(e.ItemIndex)) e.DrawDefault = true;
else
{
bool isItem = i == 0;
Rectangle iBound = FullRowSelect ? e.Bounds : isItem ? e.Item.GetBounds(ItemBoundsPortion.ItemOnly) : e.SubItem.Bounds;
Color iColor = FullRowSelect || isItem ? SystemColors.HighlightText : e.SubItem.ForeColor;
Rectangle focusBound = FullRowSelect ? e.Item.GetBounds(ItemBoundsPortion.Entire) : iBound;
if(FullRowSelect || isItem) e.Graphics.FillRectangle(SystemBrushes.Highlight, iBound);
TextRenderer.DrawText(e.Graphics, isItem ? e.Item.Text : e.SubItem.Text,
isItem ? e.Item.Font : e.SubItem.Font, iBound, iColor,
TextFormatFlags.LeftAndRightPadding | TextFormatFlags.VerticalCenter);
if(FullRowSelect || isItem)
ControlPaint.DrawFocusRectangle(e.Graphics, focusBound);
}
i++;
base.OnDrawSubItem(e);
}
}
NOTE: This code above will disable MouseDown, MouseUp (for Left button) and KeyDown event (for arrow keys), if you want to handle these events outside of your CustomListView, you may want to raise these events yourself. (By default, these events are raised by some code in or after base.WndProc).
There is still one case in which the user can select the item by holding mouse down and drag to select. To disable this, I think we have to catch the message WM_NCHITTEST but we have to catch and filter it on right condition. I've tried dealing with this but no luck. I hope you can do it. This is just a demo. However as I said, we seem unable to go another way. I think your problem is some kind of BUG in the ListView control.
UPDATE
In fact I thought of Focused and Selected before but that's when I've tried accessing the SelectedItem with ListView.SelectedItems (That's wrong). So I didn't trying that approach. However after finding out that we can access the SelectedItem of a ListView in virtual mode via the ListView.SelectedIndices and ListView.Items, I think this solution is the most efficient and simple one:
int selected = -1;
bool suppressSelectedIndexChanged;
private void timer1_Tick(object sender, EventArgs e)
{
listView1.SuspendLayout();
if (selected > -1){
ListViewItem item = listView1.Items[selected];
Rectangle rect = listView1.GetItemRect(item.Index);
suppressSelectedIndexChanged = true;
item.Selected = item.Focused = !(rect.Top <= 2 || rect.Bottom >= listView1.ClientSize.Height-2);
suppressSelectedIndexChanged = false;
}
listView1.VirtualListSize++;
listView1.ResumeLayout(true);
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e){
if (suppressSelectedIndexChanged) return;
selected = listView1.SelectedIndices.Count > 0 ? listView1.SelectedIndices[0] : -1;
}
NOTE: The code is just a demo for the case user selects just 1 item, you can add more code to deal with the case user selects more than 1 item.

I had the same problem and the code of #Sinatr almost works perfect, however when the selected item is right on the top border of the listview, it starts jumping between the selected and the next item on each update.
I had to include the height of the column headers to the visibility test which solved the problem for me:
if (lstLogMessages.SelectedIndices.Count > 0)
{
Rectangle selectedItemArea = lstLogMessages.Items[lstLogMessages.SelectedIndices[0]].Bounds;
Rectangle listviewClientArea = lstLogMessages.ClientRectangle;
int headerHeight = lstLogMessages.TopItem.Bounds.Top;
if (selectedItemArea.Y + selectedItemArea.Height > headerHeight && selectedItemArea.Y + selectedItemArea.Height < listviewClientArea.Height) // if the selected item is in the visible region
{
lstLogMessages.Items[lstLogMessages.SelectedIndices[0]].Focused = true;
}
else
{
lstLogMessages.TopItem.Focused = true;
}
}
lstLogMessages.VirtualListSize = currentView.MessageCount;

i know this is old post and [King King] has already given a double buffer example but still posting a simple code if it helps some one & this also removes flickering even if you have a item selected, but you need to Inherit ListView to use this cause SetStyle is not accessible from outside
C# Code
public class ListViewEX : ListView
{
public ListViewEX()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
}
}
VB.NET
Public Class ListViewEX
Inherits ListView
Public Sub New()
SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.OptimizedDoubleBuffer, True)
End Sub
End Class

Related

problems updating picture box control

I have two controls on the same form. Both controls contain an ObjectListView control. The one listview was created with the graphical editor in visual studio. This one is not causing any issues. The other listview is created programmatically at run-time. I have defined an event handler for each control that gets called when the hot item changes and they are both firing when they should. Both event handlers call the same code to update a picturebox control. The problem is that the picturebox does not get updated when the programmatically defined listview is asking it to. I am positive the event handler is getting called because my code writes to a text file as well as updating the picture box. The text file gets updated but the picture box does not. I have tried updating, invalidating, and refreshing the PicutureBox as well as the parent form, but I just can not get it to update.
I am not sure if this is an ObjectListView issue or a standard WinForms problem. I realize my question is very vague but I am not sure how to clarify it without posting all my code. Any advice would be appreciated.
Here is the code that the event handler calls:
public void ShowBitmap(object sender, HotItemChangedEventArgs e, ObjectListView lv, string type)
{
ObjectListView olv = sender as ObjectListView;
if (sender == null)
{
return;
}
switch (e.HotCellHitLocation)
{
case HitTestLocation.Nothing:
break;
case HitTestLocation.Group:
break;
case HitTestLocation.GroupExpander:
break;
default:
if (e.HotColumnIndex == 0)
{
pictureBox1.Hide();
pictureBox1.BorderStyle = BorderStyle.FixedSingle;
int rowIndex = e.HotRowIndex;
string text = "";
if (type == "Main Parts")
{
TypedObjectListView<MainRadanProjectPartsPart> tlist = new TypedObjectListView<MainRadanProjectPartsPart>(lv);
text = tlist.Objects[rowIndex].Symbol;
}
else if (type == "Parts")
{
TypedObjectListView<RadanProjectPartsPart> tlist = new TypedObjectListView<RadanProjectPartsPart>(lv);
text = tlist.Objects[rowIndex].Symbol;
}
else if (type == "Nests")
{
TypedObjectListView<MainRadanProjectNestsNest> tlist = new TypedObjectListView<MainRadanProjectNestsNest>(lv);
text = tlist.Objects[rowIndex].FileName;
}
if (text != null)
{
Point screenCoords = Cursor.Position;
Point controlRelatedCoords = lv.PointToClient(screenCoords);
if (controlRelatedCoords.Y < oldCursorPosition.Y)
{
pictureBox1.Location = controlRelatedCoords;
int xPos = controlRelatedCoords.X;
int yPos = controlRelatedCoords.Y + 60;
pictureBox1.Location = new Point(xPos, yPos);
}
else if (controlRelatedCoords.Y > oldCursorPosition.Y)
{
pictureBox1.Location = controlRelatedCoords;
int xPos = controlRelatedCoords.X;
//int yPos = controlRelatedCoords.Y - pictureBox1.Height;
int yPos = controlRelatedCoords.Y - pictureBox1.Height + 30;
pictureBox1.Location = new Point(xPos, yPos);
}
pictureBox1.Show();
pictureBox1.BringToFront();
olvTreeViewMainParts.Focus();
lv.Focus();
pictureBox1.Visible = true;
DrawSymbol(text);
oldCursorPosition = controlRelatedCoords; // save the cursor position to track cursor direction between calls
}
else
{
DrawSymbol("");
}
}
else
{
pictureBox1.Hide();
}
break;
}
}
Here is the event handler for the programmaticaly defined listview:
// track the cursor as it moves over the items in the listview
private void olvPartsListView_HotItemChanged(object sender, HotItemChangedEventArgs e)
{
ShowBitmap(sender, e, olvPartsListView, "Parts");
}

Switching Panels via Index Methods

I've been trying to solve my issue for quite a while and to be honest am getting nowhere. What i would like is when the user clicks the 'top' button on my panel it automatically goes to the top( and swaps with the one there.) and when they click the bottom button it automatically goes to the bottom. I'm setting the index panel manually but of course this doesnt work because its only viable for one panel (i have ten). Greatly appreciate some help in finding a method that can send the panel to the top of the stack regardless of its position.
Here is a image (basic) to help understand
Control ctrlToMove = (Control)this.bookControls[bookName];
int ctrlToMoveIndex = bookPanel.Controls.IndexOf(ctrlToMove);
int ctrlToSwapIndex = ctrlToMoveIndex - 5;
Control ctrlToSwap = bookPanel.Controls[ctrlToSwapIndex];
this.bookPanel.Controls.SetChildIndex(ctrlToMove, ctrlToSwapIndex);
this.bookPanel.Controls.SetChildIndex(ctrlToSwap, ctrlToMoveIndex);
Based on your drawing, I made a UserControl with a button on it:
void uc_ButtonClicked(object sender, EventArgs e) {
UserControl1 uc = sender as UserControl1;
if (uc != null) {
int childIndex = flowLayoutPanel1.Controls.GetChildIndex(uc);
if (childIndex > 0) {
UserControl1 ucTop = flowLayoutPanel1.Controls[0] as UserControl1;
flowLayoutPanel1.Controls.SetChildIndex(uc, 0);
flowLayoutPanel1.Controls.SetChildIndex(ucTop, childIndex);
}
}
}
According to your picture you have one control per row in panel. Thus I suggest you to use TableLayoutPanel instead of FlowLayoutPanel. Also I'd create user control for items in panel. E.g. it will have name PriorityUserControl and four buttons to increase, decrease, maximize, minimize it's 'priority' (I placed buttons horizontally just to save place on screen):
Next, create four events in this user control:
public event EventHandler PriorityMaximized;
public event EventHandler PriorityIncreased;
public event EventHandler PriorityDecreased;
public event EventHandler PriorityMinimized;
And rise appropriate event when button clicked:
private void topButton_Click(object sender, EventArgs e)
{
if (PriorityMaximized != null)
PriorityMaximized(this, EventArgs.Empty);
}
That's it. We have user control which tells whether it want to move up or down. Now add user controls to TableLayoutPanel (either manually or dynamically) and subscribe same event handlers of these four events to ALL user controls. Something like:
// create user control and attach event handlers
PriorityUserControl control = new PriorityUserControl();
control.PriorityMaximized += priorityUserControl_PriorityMaximized;
control.PriorityMinimized += priorityUserControl_PriorityMinimized;
control.PriorityIncreased += priorityUserControl_PriorityIncreased;
control.PriorityDecreased += priorityUserControl_PriorityDecreased;
// add another row to table
panel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
panel.RowCount = panel.RowStyles.Count;
// add control table layout panel
panel.Controls.Add(control);
panel.SetRow(control, panel.RowCount - 1);
Good. All you should do now is implement these event handlers. It's simple. E.g. decreasing priority (i.e. moving down):
private void priorityUserControl_PriorityDecreased(object sender, EventArgs e)
{
// sender is a control where you clicked Down button
Control currentControl = (Control)sender;
// get position in panel
var position = panel.GetPositionFromControl(currentControl);
// just to be sure control is not one at the bottom
if (position.Row == panel.RowCount - 1)
return;
// we want to switch with control beneath current
Control controlToSwitch = panel.GetControlFromPosition(0, position.Row + 1);
// move both controls
panel.SetRow(currentControl, position.Row + 1);
panel.SetRow(controlToSwitch, position.Row);
}
Now implementation of maximizing priority (i.e. moving to top):
private void priorityUserControl_PriorityMaximized(object sender, EventArgs e)
{
Control currentControl = (Control)sender;
var position = panel.GetPositionFromControl(currentControl);
if (position.Row == 0 || panel.RowCount < 2)
return;
Control topControl = panel.GetControlFromPosition(0, 0);
panel.SetRow(currentControl, 0);
panel.SetRow(topControl, position.Row);
}
I believe you will create rest two handlers by yourself.
The key of what you want is setting up a clear and extendable algorithm capable to deal with the different positions of the Panels. Here you have a simple code showing certain approach to this problem:
public partial class Form1 : Form
{
int[] panelLocations;
Point[] pointLocations;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
panelLocations = new int[5];
pointLocations = new Point[5];
panelLocations[1] = 1;
panelLocations[2] = 2;
panelLocations[3] = 3;
pointLocations[1] = new Point(panel1.Left, panel1.Top);
pointLocations[2] = new Point(panel2.Left, panel2.Top);
pointLocations[3] = new Point(panel3.Left, panel3.Top);
}
private void relocate(int curPanel, bool goTop)
{
int curLoc = panelLocations[curPanel];
int newLoc = curLoc - 1;
if (!goTop)
{
newLoc = curLoc + 1;
}
if (newLoc < 1) newLoc = 3;
if (newLoc > 3) newLoc = 1;
if (newLoc != curLoc)
{
int otherIndex = Array.IndexOf(panelLocations, newLoc);
panelLocations[curPanel] = newLoc;
relocatePanel(curPanel);
panelLocations[otherIndex] = curLoc;
relocatePanel(otherIndex);
}
}
private void relocatePanel(int curIndex)
{
if (curIndex == 1)
{
panel1.Location = pointLocations[panelLocations[1]];
}
else if (curIndex == 2)
{
panel2.Location = pointLocations[panelLocations[2]];
}
else if (curIndex == 3)
{
panel3.Location = pointLocations[panelLocations[3]];
}
}
private void buttonTop1_Click(object sender, EventArgs e)
{
relocate(1, true);
}
private void buttonBottom1_Click(object sender, EventArgs e)
{
relocate(1, false);
}
}
Open a new project, add 3 panels (Panel1, Panel2 and Panel3... better put different background colors) and include two buttons (buttonUp and buttonDown). This code will make the Panel1 to go up and down (by changing its position with the other panels).
The idea is pretty simple: at the start you store the positions of all the Panels in an array. In another array, you store where each panel is located every time (1 is the original position of Panel1, etc.).
It is a quite simple code which you can improve and extend as much as required, but the idea is pretty reliable and you can use it in any case: a set of fixed positions through which the panels will be moving.

Leaving the dropdown enabled

I want to disable a combobox, but at the same time I want to let the users see the other options available (that is, I want to enable the dropdown).
By default, when ComboBox.Enabled = false, the dropdown is also disabled (nothing happens when we click on the combobox).
My first thought is to leave it enabled and handle the ComboBox.SelectedIndex event to set it back to the default value (I will just need to gray it out in some way.)
I am wondering if there is any native functionality like this that I am missing, or if there would be other way of doing it.
Don't use a Combobox if you don't want the Combobox functionality. Use a ListView instead.
A "What You See Is What You Can't Get" Combobox seems a bad idea.
I suggest using ListBox instead.
It's a hacky workaround, but it should accomplish something similar to your request:
public partial class Form1 : Form
{
ComboBox _dummy;
public Form1()
{
InitializeComponent();
// set the style
comboBox1.DropDownStyle =
System.Windows.Forms.ComboBoxStyle.DropDownList;
// disable the combobox
comboBox1.Enabled = false;
// add the dummy combobox
_dummy = new ComboBox();
_dummy.Visible = false;
_dummy.Enabled = true;
_dummy.DropDownStyle = ComboBoxStyle.DropDownList;
this.Controls.Add(_dummy);
// add the event handler
MouseMove += Form1_MouseMove;
}
void Form1_MouseMove(object sender, MouseEventArgs e)
{
var child = this.GetChildAtPoint(e.Location);
if (child == comboBox1)
{
if (!comboBox1.Enabled)
{
// copy the items
_dummy.Items.Clear();
object[] items = new object[comboBox1.Items.Count];
comboBox1.Items.CopyTo(items, 0);
_dummy.Items.AddRange(items);
// set the size and position
_dummy.Left = comboBox1.Left;
_dummy.Top = comboBox1.Top;
_dummy.Height = comboBox1.Height;
_dummy.Width = comboBox1.Width;
// switch visibility
comboBox1.Visible = !(_dummy.Visible = true);
}
}
else if (child != _dummy)
{
// switch visibility
comboBox1.Visible = !(_dummy.Visible = false);
}
}
}
If using a ListBox as other answers suggested is not convenient. There is a way by creating a custom combobox and adding a ReadOnly property. Try this code :
class MyCombo : System.Windows.Forms.ComboBox
{
public bool ReadOnly { get; set; }
public int currentIndex;
public MyCombo()
{
currentIndex = SelectedIndex ;
}
protected override void OnSelectedIndexChanged(EventArgs e)
{
if (ReadOnly && Focused)
SelectedIndex = currentIndex;
currentIndex = SelectedIndex;
base.OnSelectedIndexChanged(e);
}
}
Usually the background color of read-only controls should not change, so I haven't done that part.

vertical scroll bars events not raised in datagrid view

I have a datagrid view like this....in below image well thats works fine...
I need to hook up an event in vertical side bar ..
i mean if i click on upper arrow in the scroll bar i want to do something ...
To be more specific i want to get the id of first upper record id when i click on upper arrow in vertical scroll bar..
using System.Reflection;
using System.Windows.Forms;
bool addScrollListener(DataGridView dgv)
{
bool ret = false;
Type t = dgv.GetType();
PropertyInfo pi = t.GetProperty("VerticalScrollBar", BindingFlags.Instance | BindingFlags.NonPublic);
ScrollBar s = null;
if (pi != null)
s = pi.GetValue(dgv, null) as ScrollBar;
if (s != null)
{
s.Scroll += new ScrollEventHandler(s_Scroll);
ret = true;
}
return ret;
}
private void s_Scroll(object sender, ScrollEventArgs e)
{
if (e.ScrollOrientation == ScrollOrientation.VerticalScroll)
{
if (e.Type == ScrollEventType.ThumbPosition)
{
if (e.Type == ScrollEventType.SmallIncrement)
{
int i = dgvMembers.FirstDisplayedScrollingRowIndex;
int idemebers =Convert.ToInt32(dgvMembers.Rows[i].Cells["Id"].Value.ToString());
getMemberInfo(i, idemebers); // i want to the details of selected record into text boxes
}
if (e.Type == ScrollEventType.SmallDecrement)
{
int i = dgvMembers.FirstDisplayedScrollingRowIndex;
int idemebers = Convert.ToInt32(dgvMembers.Rows[i].Cells["Id"].Value.ToString());
getMemberInfo(i, idemebers);
}
}
}
}
but this event does not fire
s.Scroll += new ScrollEventHandler(s_Scroll);
it does not goes into the this event ...
would any one pls help on this...
Many thanks in advance
Try using the DataGridView.Scroll event.
To be more specific i want to get the id of first upper record id when i click on upper arrow in vertical scroll bar
In your DataGridView.Scroll event handler, you can do this (the upper arrow is considered a small decrement:
if (e.ScrollOrientation == ScrollOrientation.VerticalScroll
&& e.Type == ScrollEventType.SmallDecrement)
{
int i = dgvMembers.FirstDisplayedScrollingRowIndex;
// your code to process the first displayed row here
}

C# How to change row color in datagridview? [duplicate]

I would like to change the color of a particular row in my datagridview. The row should be changed to red when the value of columncell 7 is less than the value in columncell 10. Any suggestions on how to accomplish this?
You need to loop through the rows in the datagridview and then compare values of columns 7 and 10 on each row.
Try this:
foreach (DataGridViewRow row in vendorsDataGridView.Rows)
if (Convert.ToInt32(row.Cells[7].Value) < Convert.ToInt32(row.Cells[10].Value))
{
row.DefaultCellStyle.BackColor = Color.Red;
}
I was just investigating this issue (so I know this question was published almost 3 years ago, but maybe it will help someone... ) but it seems that a better option is to place the code inside the RowPrePaint event so that you don't have to traverse every row, only those that get painted (so it will perform much better on large amount of data:
Attach to the event
this.dataGridView1.RowPrePaint
+= new System.Windows.Forms.DataGridViewRowPrePaintEventHandler(
this.dataGridView1_RowPrePaint);
The event code
private void dataGridView1_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
{
if (Convert.ToInt32(dataGridView1.Rows[e.RowIndex].Cells[7].Text) < Convert.ToInt32(dataGridView1.Rows[e.RowIndex].Cells[10].Text))
{
dataGridView1.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.Beige;
}
}
You're looking for the CellFormatting event.
Here is an example.
I had trouble changing the text color as well - I never saw the color change.
Until I added the code to change the text color to the event DataBindingsComplete for DataGridView. After that it worked.
I hope this will help people who face the same problem.
Something like the following... assuming the values in the cells are Integers.
foreach (DataGridViewRow dgvr in myDGV.Rows)
{
if (dgvr.Cells[7].Value < dgvr.Cells[10].Value)
{
dgvr.DefaultCellStyle.ForeColor = Color.Red;
}
}
untested, so apologies for any error.
If you know the particular row, you can skip the iteration:
if (myDGV.Rows[theRowIndex].Cells[7].Value < myDGV.Rows[theRowIndex].Cells[10].Value)
{
dgvr.DefaultCellStyle.ForeColor = Color.Red;
}
Some people like to use the Paint, CellPainting or CellFormatting events, but note that changing a style in these events causes recursive calls. If you use DataBindingComplete it will execute only once. The argument for CellFormatting is that it is called only on visible cells, so you don't have to format non-visible cells, but you format them multiple times.
You can Change Backcolor row by row using your condition.and this function call after applying Datasource of DatagridView.
Here Is the function for that.
Simply copy that and put it after Databind
private void ChangeRowColor()
{
for (int i = 0; i < gvItem.Rows.Count; i++)
{
if (BindList[i].MainID == 0 && !BindList[i].SchemeID.HasValue)
gvItem.Rows[i].DefaultCellStyle.BackColor = ColorTranslator.FromHtml("#C9CADD");
else if (BindList[i].MainID > 0 && !BindList[i].SchemeID.HasValue)
gvItem.Rows[i].DefaultCellStyle.BackColor = ColorTranslator.FromHtml("#DDC9C9");
else if (BindList[i].MainID > 0)
gvItem.Rows[i].DefaultCellStyle.BackColor = ColorTranslator.FromHtml("#D5E8D7");
else
gvItem.Rows[i].DefaultCellStyle.BackColor = Color.White;
}
}
private void dtGrdVwRFIDTags_DataSourceChanged(object sender, EventArgs e)
{
dtGrdVwRFIDTags.Refresh();
this.dtGrdVwRFIDTags.Columns[1].Visible = false;
foreach (DataGridViewRow row in this.dtGrdVwRFIDTags.Rows)
{
if (row.Cells["TagStatus"].Value != null
&& row.Cells["TagStatus"].Value.ToString() == "Lost"
|| row.Cells["TagStatus"].Value != null
&& row.Cells["TagStatus"].Value.ToString() == "Damaged"
|| row.Cells["TagStatus"].Value != null
&& row.Cells["TagStatus"].Value.ToString() == "Discarded")
{
row.DefaultCellStyle.BackColor = Color.LightGray;
row.DefaultCellStyle.Font = new Font("Tahoma", 8, FontStyle.Bold);
}
else
{
row.DefaultCellStyle.BackColor = Color.Ivory;
}
}
//for (int i= 0 ; i<dtGrdVwRFIDTags.Rows.Count - 1; i++)
//{
// if (dtGrdVwRFIDTags.Rows[i].Cells[3].Value.ToString() == "Damaged")
// {
// dtGrdVwRFIDTags.Rows[i].Cells["TagStatus"].Style.BackColor = Color.Red;
// }
//}
}
This is my solution to change color to dataGridView with bindingDataSource:
private void dataGridViewECO_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
if (e.ListChangedType != ListChangedType.ItemDeleted)
{
DataGridViewCellStyle green = this.dataGridViewECO.DefaultCellStyle.Clone();
green.BackColor = Color.Green;
DataGridViewCellStyle gray = this.dataGridViewECO.DefaultCellStyle.Clone();
gray.BackColor = Color.LightGray;
foreach (DataGridViewRow r in this.dataGridViewECO.Rows)
{
if (r.Cells[8].Value != null)
{
String stato = r.Cells[8].Value.ToString();
if (!" Open ".Equals(stato))
{
r.DefaultCellStyle = gray;
}
else
{
r.DefaultCellStyle = green;
}
}
}
}
}
If you bind to a (collection) of concrete objects, you can get the that concrete object via the DataBoundItem property of the row. (To avoid check for magic strings in the cell and using "real" properties of the object)
Skeleton example below:
DTO/POCO
public class Employee
{
public int EmployeeKey {get;set;}
public string LastName {get;set;}
public string FirstName {get;set;}
public bool IsActive {get;set;}
}
Binding to the datagridview
private void BindData(ICollection<Employee> emps)
{
System.ComponentModel.BindingList<Employee> bindList = new System.ComponentModel.BindingList<Employee>(emps.OrderBy(emp => emp.LastName).ThenBy(emp => emp.FirstName).ToList());
this.dgvMyDataGridView.DataSource = bindList;
}
then the event handler and getting the concrete object (instead of a DataGridRow and/or cells)
private void dgvMyDataGridView_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
{
Employee concreteSelectedRowItem = this.dgvMyDataGridView.Rows[e.RowIndex].DataBoundItem as Employee;
if (null != concreteSelectedRowItem && !concreteSelectedRowItem.IsActive)
{
dgvMyDataGridView.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.LightGray;
}
}
I typically Like to use the GridView.RowDataBound Event event for this.
protected void OrdersGridView_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
e.Row.ForeColor = System.Drawing.Color.Red;
}
}
Works on Visual Studio 2010. (I tried it and it works!)
It will paint your entire row.
Create a button for the datagridview.
Create a CellClick event and put the next line of code inside of it.
if (dataGridView3.Columns[e.ColumnIndex].Index.Equals(0)
{
dataGridView3.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.Beige;
}
You have not mentioned how value is changed. I have used similar functionality when user is entering value. i.e. entering and leaving edit mode.
Using CellEndEdit event of datagridview.
private void dgMapTable_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
double newInteger;
if (double.TryParse(dgMapTable[e.ColumnIndex,e.RowIndex].Value.ToString(), out newInteger)
{
if (newInteger < 0 || newInteger > 50)
{
dgMapTable[e.ColumnIndex, e.RowIndex].Style.BackColor = Color.Red;
dgMapTable[e.ColumnIndex, e.RowIndex].ErrorText
= "Keep value in Range:" + "0 to " + "50";
}
}
}
You may add logic for clearing error notification in a similar way.
if in your case, if data is loaded programmatically, then CellLeave event can be used with same code.
With this code, you only change rows backcolor where columname value is null other rows color still the default one.
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if (row.Cells["columnname"].Value != null)
{
dataGridView1.AlternatingRowsDefaultCellStyle.BackColor = Color.MistyRose;
}
}
Just a note about setting DefaultCellStyle.BackColor...you can't set it to any transparent value except Color.Empty. That's the default value. That falsely implies (to me, anyway) that transparent colors are OK. They're not. Every row I set to a transparent color just draws the color of selected-rows.
I spent entirely too much time beating my head against the wall over this issue.
I landed here looking for a solution for the case where I dont use data binding. Nothing worked for me but I got it in the end with:
dataGridView.Columns.Clear();
dataGridView.Rows.Clear();
dataGridView.Refresh();
If you are the second dumbest developer on the planet (me being the dumbest), all of the above solutions seem to work: CellFormatting, DataSourceChanged, and RowPrePaint. I prefer RowPrePaint.
I struggled with this (for way too long) because I needed to override my SelectionBackColor and SelectionForeColor instead of BackColor and ForeColor as I was changing the selected row.
int counter = gridEstimateSales.Rows.Count;
for (int i = 0; i < counter; i++)
{
if (i == counter-1)
{
//this is where your LAST LINE code goes
//row.DefaultCellStyle.BackColor = Color.Yellow;
gridEstimateSales.Rows[i].DefaultCellStyle.BackColor = Color.Red;
}
else
{
//this is your normal code NOT LAST LINE
//row.DefaultCellStyle.BackColor = Color.Red;
gridEstimateSales.Rows[i].DefaultCellStyle.BackColor = Color.White;
}
}
dataGridView1.Rows[1].Cells[1].Style.BackColor = Color.Yellow;
if (this.dgblista.Columns[e.ColumnIndex].Name == "TOTAL PAGADO")
{
if ((dgblista.Columns[e.ColumnIndex].Name == "COSTO DEL CURSO") == (dgblista.Columns[e.ColumnIndex].Name == "TOTAL PAGADO"))
{
e.CellStyle.ForeColor = Color.White;
e.CellStyle.BackColor = Color.Red;
}
}

Categories