i need help on a maze game that i'm trying to build. I know there is a lot written on that topic, but i can't get it right. So, on the problem - i want the user to escape from the maze with arrow keys. I build the maze from 2-d array of rectangle cells from the tutorial here.
And that is the method.
public static Grid DFS(Grid grid)
{
Stack<Cell> cellStack = new Stack<Cell>();
int totalCells = grid.Cells.GetLength(0) * grid.Cells.GetLength(1);
Cell currentCell = grid.Cells[0, 0];
currentCell.IsVisited = true;
grid.VisitedCells = 1;
while (grid.VisitedCells < totalCells)
{
List<Cell> neighbours = GetNeighbours(grid, currentCell);
if (neighbours.Count > 0)
{
Cell newCell = GetRandomNeighbour(neighbours);
CrushWall(currentCell, newCell);
newCell.IsVisited = true;
cellStack.Push(currentCell);
currentCell = newCell;
grid.VisitedCells++;
}
else
{
currentCell = cellStack.Pop();
}
}
return grid;
}
Then i add it on a panel in the main form:
private void mediumToolStripMenuItem_Click(object sender, EventArgs e)
{
panel1.Controls.Clear();
Grid grid = new Grid(30, 30);
panel1.Size = grid.Size;
grid.BorderStyle = BorderStyle.FixedSingle;
panel1.Controls.Add(grid);
maze = Maze.DFS(grid);
}
So far so good. Now i want to escape from the maze. Start point is Cell[0,0] and the goal is at the last Cell, let's say Cell[30,30]. Here is my attempt for that. It's not finished yet and covers just when we go right and down. What it happens is that i go just one cell right or down and that's it. Noting more! Please tell me where i'm wrong!
void frmMain_KeyDown(object sender, KeyEventArgs e)
{
for (int i = 0; i < maze.Rows; i++)
{
for (int j = 0; j < maze.Columns; j++)
{
maze.Cells[i, j].IsVisited = false;
}
}
Cell currentCell = maze.Cells[0, 0];
if (currentCell == maze.Cells[maze.Rows - 1, maze.Columns - 1])
{
MessageBox.Show("Well Done! You made it!");
}
else
{
switch (e.KeyCode)
{
case Keys.Right:
currentCell = Maze.GoRight(maze, currentCell);
currentCell.IsOnPath = true;
maze.Invalidate();
break;
case Keys.Down:
currentCell = Maze.GoDown(maze, currentCell);
currentCell.IsOnPath = true;
maze.Invalidate();
break;
}
}
}
And here is the going right for example:
public static Cell GoRight(Grid maze, Cell currentCell)
{
List<Cell> neighbours = GetMazeNeighbours(maze, currentCell);
Cell rightNeighbour = new Cell();
if (neighbours.Any())
{
foreach (var item in neighbours)
{
if (item.LeftWall == 1)
{
rightNeighbour = item;
}
}
}
return rightNeighbour;
}
Two potential problems I can see straight away. Firstly, most seriously, you're initialising currentCell to the cell at [0,0] every time you press a key. So, every time a key is pressed currentCell goes back to [0,0]! That can't be right.
Secondly, you need to break out or return from your foreach as soon as you assign rightNeighbour, or any other cells in the list of neighbours that are subsequently found matching the item.LeftWall == 1 criteria will overwrite the assignment.(Plus it's inefficient to iterate through a list when you've already found what you're looking for).
e.g.
foreach (var item in neighbours)
{
if (item.LeftWall == 1)
{
rightNeighbour = item;
break;
}
}
Finally i was able to locate the problem and fix it. Here it is on the image:
The black cell is the current cell that we`re on. The yellow ones are the neighbours. The problem is that the two neighbours have the same properties. For example:
neighbour1.TopWall = 1;
neighbour2.TopWall = 1;
This means that the top wall of both neighbours is crushed.
Let`s say i want to go down. In the KeyDown event the condition for going down is true for both of the neighbours, so it is pure luck that the correct neighbour is the first met here:
foreach (var item in neighbours)
{
if (item.TopWall == 1)
{
rightNeighbour = item;
break;
}
}
And i solved it like this:
if current cell has neighbours
check if bottom cell is not null
if false, check if its top wall is crushed
if true, make bottom cell the current cell
I hope this will help to someone! Cheers!
Related
I'm creating simple game which board is based on Grid layout. I make f.e. 20rows x 20columns grid, some of them are filled by some FrameworkElement. I want to make drag&drop mechanism for them and I wonder how to find row&column of grid by mouse position.
Any idea?
Ok i found such function:
public Cell GetCell(Point position)
{
Cell cell = new Cell();
cell.Column = -1;
double total = 0;
foreach (var column in boardGrid.ColumnDefinitions)
{
if(position.X < total)
{
break;
}
cell.Column++;
total += column.ActualWidth;
}
cell.Row = -1;
total = 0;
foreach (var row in boardGrid.RowDefinitions)
{
if(position.Y < total)
{
break;
}
cell.Row++;
total += row.ActualHeight;
}
return cell;
}
I'm developing a theatre reservation software. I'm using Windows Forms, the seats is represented by a 2-dimensioned array. And I draw the buttons as following:
public void DrawSeats()
{
// pnl_seats is a Panel
pnl_seats.Controls.Clear();
// Here I store all Buttons instance, to later add all buttons in one call (AddRange) to the Panel
var btns = new List<Control>();
// Suspend layout to avoid undesired Redraw/Refresh
this.SuspendLayout();
for (int y = 0; y < _seatZone.VerticalSize; y++)
{
for (int x = 0; x < _seatZone.HorizontalSize; x++)
{
// Check if this seat exists
if (IsException(x, y))
continue;
// Construct the button with desired properties. SeatSize is a common value for every button
var btn = new Button
{
Width = SeatSize,
Height = SeatSize,
Left = (x * SeatSize),
Top = (y * SeatSize),
Text = y + "" + x,
Tag = y + ";" + x, // When the button clicks, the purpose of this is to remember which seat this button is.
Font = new Font(new FontFamily("Microsoft Sans Serif"), 6.5f)
};
// Check if it is already reserved
if (ExistsReservation(x, y))
btn.Enabled = false;
else
btn.Click += btn_seat_Click; // Add click event
btns.Add(btn);
}
}
// As said before, add all buttons in one call
pnl_seats.Controls.AddRange(btns.ToArray());
// Resume the layout
this.ResumeLayout();
}
But already with a seat zone of 20 by 20 (400 buttons), it spent almost 1 minute to draw it, and in debug I checked that the lack of performance, is the instantiation of the buttons.
There is a way to make it faster? Perhaps disable all events during the instatiation or another lightweight Control that has the Click event too?
UPDATE:
lbl was a test, the correct is btn, sorry.
UPDATE 2:
Here is the IsException and ExistsReservations methods:
private bool IsException(int x, int y)
{
for (var k = 0; k < _seatsExceptions.GetLength(0); k++)
if (_seatsExceptions[k, 0] == x && _seatsExceptions[k, 1] == y)
return true;
return false;
}
private bool ExistsReservation(int x, int y)
{
for (var k = 0; k < _seatsReservations.GetLength(0); k++)
if (_seatsReservations[k, 0] == x && _seatsReservations[k, 1] == y)
return true;
return false;
}
Suppose that you change your arrays for reservations and exclusions to
public List<string> _seatsExceptions = new List<string>();
public List<string> _seatsReservations = new List<string>();
you add your exclusions and reservations in the list with something like
_seatsExceptions.Add("1;10");
_seatsExceptions.Add("4;19");
_seatsReservations.Add("2;5");
_seatsReservations.Add("5;5");
your checks for exclusions and reservations could be changed to
bool IsException(int x, int y)
{
string key = x.ToString() + ";" + y.ToString();
return _seatsExceptions.Contains(key);
}
bool ExistsReservation(int x, int y)
{
string key = x.ToString() + ";" + y.ToString();
return _seatsReservations.Contains(key);
}
of course I don't know if you are able to make this change or not in your program. However consider to change the search on your array sooner or later.
EDIT I have made some tests, and while a virtual grid of 20x20 buttons works acceptably well (31 millisecs 0.775ms on average), a bigger one slows down noticeably. At 200x50 the timing jumps to 10 seconds (1,0675 on average). So perhaps a different approach is needed. A bound DataGridView could be a simpler solution and will be relatively easy to handle.
I also won't use such a myriad of controls to implement such a thing. Instead you should maybe create your own UserControl, which will paint all the seats as images and reacts on a click event.
To make it a little easier for you i created such a simple UserControl, that will draw all the seats and reacts on a mouse click for changing of the state. Here it is:
public enum SeatState
{
Empty,
Selected,
Full
}
public partial class Seats : UserControl
{
private int _Columns;
private int _Rows;
private List<List<SeatState>> _SeatStates;
public Seats()
{
InitializeComponent();
this.DoubleBuffered = true;
_SeatStates = new List<List<SeatState>>();
_Rows = 40;
_Columns = 40;
ReDimSeatStates();
MouseUp += OnMouseUp;
Paint += OnPaint;
Resize += OnResize;
}
public int Columns
{
get { return _Columns; }
set
{
_Columns = Math.Min(1, value);
ReDimSeatStates();
}
}
public int Rows
{
get { return _Rows; }
set
{
_Rows = Math.Min(1, value);
ReDimSeatStates();
}
}
private Image GetPictureForSeat(int row, int column)
{
var seatState = _SeatStates[row][column];
switch (seatState)
{
case SeatState.Empty:
return Properties.Resources.emptySeat;
case SeatState.Selected:
return Properties.Resources.choosenSeat;
default:
case SeatState.Full:
return Properties.Resources.fullSeat;
}
}
private void OnMouseUp(object sender, MouseEventArgs e)
{
var heightPerSeat = Height / (float)Rows;
var widthPerSeat = Width / (float)Columns;
var row = (int)(e.X / widthPerSeat);
var column = (int)(e.Y / heightPerSeat);
var seatState = _SeatStates[row][column];
switch (seatState)
{
case SeatState.Empty:
_SeatStates[row][column] = SeatState.Selected;
break;
case SeatState.Selected:
_SeatStates[row][column] = SeatState.Empty;
break;
}
Invalidate();
}
private void OnPaint(object sender, PaintEventArgs e)
{
var heightPerSeat = Height / (float)Rows;
var widthPerSeat = Width / (float)Columns;
e.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
for (int row = 0; row < Rows; row++)
{
for (int column = 0; column < Columns; column++)
{
var seatImage = GetPictureForSeat(row, column);
e.Graphics.DrawImage(seatImage, row * widthPerSeat, column * heightPerSeat, widthPerSeat, heightPerSeat);
}
}
}
private void OnResize(object sender, System.EventArgs e)
{
Invalidate();
}
private void ReDimSeatStates()
{
while (_SeatStates.Count < Rows)
_SeatStates.Add(new List<SeatState>());
if (_SeatStates.First().Count < Columns)
foreach (var columnList in _SeatStates)
while (columnList.Count < Columns)
columnList.Add(SeatState.Empty);
while (_SeatStates.Count > Rows)
_SeatStates.RemoveAt(_SeatStates.Count - 1);
if (_SeatStates.First().Count > Columns)
foreach (var columnList in _SeatStates)
while (columnList.Count > Columns)
columnList.RemoveAt(columnList.Count - 1);
}
}
This will currently draw forty rows and columns (so there are 800 seats) and you can click on each seat to change its state.
Here are the images i used:
EmtpySeat:
ChoosenSeat:
FullSeat:
If you anchor this control and resize it or you click on a seat to change its state there can be some minor lacking for the repainting if you further increase the number of rows or columns, but that is still somewhere far below one second. If this still hurts you, you have to improve the paint method and maybe check the ClipRectangle property of the paint event and only paint the really needed parts, but that's another story.
Rather than using actual button controls, just draw the image of the seats then when the user clicks on a seat translate the mouse X,Y coordinates to determine which seat was clicked. This will be more efficient. Of course, the drawback is that you have to write the method to translate x,y coordinates to a seat, but that really isn't that difficult.
EDIT; it has been pointed out to me this will not work in Windows Forms!
Well, you are Sequentially working through it.
if one iteration costs 1 sec, the full process will take 400*1 in time.
Perhaps you should try and make a collection of your objects, and process it 'parallel'.
try the .Net framework (4 and above) 'parallel foreach' method:
http://msdn.microsoft.com/en-s/library/system.threading.tasks.parallel.foreach(v=vs.110).aspx
edit: so, if you have a list buttonnames, you can say
buttonNames.ForEach(x=>CreateButton(x));
while your CreateButton() method is as following:
private Button CreateButton(string nameOfButton) { Button b = new
Button(); b.Text = nameOfButton; //do whatever you want...
return b; }
When i drag one picturebox, it drags both of mine. Because in the pbxMap_DragDrop method i have to call both of the methods that should fire when i drag one.
private void pbxMap_DragDrop(object sender, DragEventArgs e)
{
myDetectMouse.setMinotaur(e, myMap.myCells);
myDetectMouse.setTheseus(e, myMap.myCells);
}
SetTheseus:
public void setTheseus(DragEventArgs e, List<Cell> cells)
{
for (int i = 0; i < cells.Count; i++)
{
int[] mapData = myMapController.getMapData(i, cells);
int column = mapData[0];
int row = mapData[1];
int right = mapData[2];
int bottom = mapData[3];
Point RelativeMouseLoc = myMapController.myMap.myForm.pbxMap.PointToClient(Cursor.Position);
if (RelativeMouseLoc.X > column &&
RelativeMouseLoc.X < column + myMapController.myMap.myCellSize
&& RelativeMouseLoc.Y > row && RelativeMouseLoc.Y <
row + myMapController.myMap.myCellSize)
{
myMapController.myMap.myCells[i].hasTheseus = true;
}
else
{
myMapController.myMap.myCells[i].hasTheseus = false;
}
}
}
SetMinotaur is much the same but replace hasTheseus with hasMinotaur. As soon as a cell "hasTheseus" or "hasMinotaur" it will be drawn to the cell.
So it draws them both when i drag one because they both get set in pbxMap_DragDrop.
I thought i could have multiple event handlers for pbxMap_DragDrop depending on which picturebox was dragged.
You can check the sender parameter to determine whether you want to move the minotaur or Theseus. It would look something like this:
var pic = (PictureBox)sender;
if (pic.Name == "minotaur")
{
myDetectMouse.setMinotaur(e, myMap.myCells);
}
else
{
myDetectMouse.setTheseus(e, myMap.myCells);
}
If you don't want to use the Name property, you can use something else like the Tag property - just make sure you set it for each of the PictureBox objects.
I have a DataGridView in C# (.NET 2.0) with a few read-only cells (3 out of 25). I'm using the method outlined in the top answer here to set ReadOnly on those specific cells in the Form_Load handler. The linked question states that
some cells has to be ReadOnly and also when the user navigates with TAB or ENTER between cells, the ReadOnly cells should be bypassed
so I assumed setting the flag would cause the cells to be skipped.
Long story short, it's not. The read-only cells get tabbed to and selected even though they cannot be edited. I'm combing over the properties of the DataGridView looking for some kind of TabMode or TabSkipsReadOnlyCells property, but so far nothing. Is there a property to set for this behavior, or do I have to write some tab event handling code of some kind?
This seems like it should be the default behavior, so I'm kind of annoyed to even have to find a property for it, much less have to write code to do it...
EDIT: I should clarify, I'm not interested in only handling navigation with the Tab key. I want to implement reasonable navigation with the arrow keys and possibly the mouse as well. That means if I have to write code I need direct control over where the selection moves when I bounce it out of a read-only cell, probably by setting CurrentCell on the DataGridView. This is so that if the user up-arrows into a read-only cell I can redirect to the cell above, not always to the cell to the right.
EDIT 2: Here's my final solution, handling arrow key navigation as well as tab navigation, based on Sean Griffiths' code (also linked in the accepted answer):
private void GridForm_Load(object sender, EventArgs e)
{
dataGridView1.CellEnter += dataGridView1_CellEnter;
}
//a delegate is needed to avoid a circular loop when selecting a cell when in a cell selection event
private delegate void SetColumnAndRowOnGrid(DataGridView grid, int columnIndex, int rowIndex);
static SetColumnAndRowOnGrid setCellMethod = new SetColumnAndRowOnGrid(setGridCell);
// Method pointed to by the delegate
private static void setGridCell(DataGridView grid, int columnIndex, int rowIndex)
{
grid.CurrentCell = grid.Rows[rowIndex].Cells[columnIndex];
grid.BeginEdit(true);
}
// Track the cell we leave so we can determine direction of "travel"
int _lastRow = 0, _lastCol = 0;
private void dataGridView1_CellLeave(object sender, DataGridViewCellEventArgs e)
{
_lastRow = e.RowIndex;
_lastCol = e.ColumnIndex;
}
enum Direction { Up, Down, Left, Right }
// When we enter a read only cell, determine direction
// of "travel" and keep going that way
private void dataGridView1_CellEnter(object sender, DataGridViewCellEventArgs e)
{
int currRow = e.RowIndex;
int currCol = e.ColumnIndex;
if (dataGridView1.Rows[currRow].Cells[currCol].ReadOnly)
{
Direction direction = Direction.Right;
if ((currRow != _lastRow) && (currCol == _lastCol))
{
// moving vertically
if (currRow < _lastRow) direction = Direction.Up;
else direction = Direction.Down;
}
else
{
// moving horizontally
if (currCol == 0 &&
_lastCol == dataGridView1.Columns.Count - 1 &&
currRow == _lastRow + 1)
{
// Special case - probably just tabbed from end of row
direction = Direction.Right;
}
else if (currCol == dataGridView1.Columns.Count - 1 &&
_lastCol == 0 &&
currRow == _lastRow - 1)
{
// Special case - probably just shift-tabbed from start of row
direction = Direction.Left;
}
else if (currCol < _lastCol) { direction = Direction.Left; }
}
//this cell is readonly, find the next tabable cell
if (!SetNextTabableCell(dataGridView1, currCol, currRow, direction))
{
// All the cells in the grid have been tried, none could be tabbed
// to so move onto the next control
bool tabForward = direction == Direction.Right || direction == Direction.Down;
SelectNextControl(this, tabForward, true, true, true);
}
}
}
// Find the next cell that we want to be selectable
private static bool SetNextTabableCell(DataGridView grid, int nextColumn, int nextRow, Direction direction)
{
//keep selecting each next cell until one is found that isn't either readonly or invisible
int maxMoves = grid.ColumnCount * grid.RowCount;
int moves = 0;
do
{
if (!GetNextCell(grid, ref nextColumn, ref nextRow, ref direction)) return false;
// Prevent infinite loop - I managed to get in one when this function
// wound up in a readonly column with a direction of Down (if we've moved
// to another cell more times than there are cells in the grid, just give up)
if (++moves > maxMoves) return false;
}
while (grid.Rows[nextRow].Cells[nextColumn].ReadOnly == true ||
grid.Rows[nextRow].Cells[nextColumn].Visible == false);
//a cell has been found that can be entered, use the delegate to select it
grid.BeginInvoke(setCellMethod, grid, nextColumn, nextRow);
return true;
}
// Get the next cell in the indicated direction
// Wrap around if going left-right
// Bounce at the edge if going up/down
private static bool GetNextCell(DataGridView grid, ref int nextColumn, ref int nextRow, ref Direction direction)
{
switch (direction)
{
case Direction.Right:
if (nextColumn < grid.Columns.Count - 1)
{
// Nominal case - move right one cell
nextColumn = nextColumn + 1;
}
else // at the last column
{
// go the the first column
nextColumn = 0;
if (nextRow < grid.Rows.Count - 1)
{
// Nominal case - move down one row
nextRow = nextRow + 1;
}
// at the last row and last column exit this method, no cell can be selected
else { return false; }
}
break;
case Direction.Left:
if (nextColumn > 0)
{
// Nominal case - move left one cell
nextColumn = nextColumn - 1;
}
else // at the first column
{
// go the the last column
nextColumn = grid.Columns.Count - 1;
if (nextRow > 0)
{
// Nominal case - move up one row
nextRow = nextRow - 1;
}
// at the first row and first column exit this method, no cell can be selected
else { return false; }
}
break;
case Direction.Down:
if (nextRow < grid.Rows.Count - 1)
{
// Nominal case - move down one cell
nextRow = nextRow + 1;
}
else // at the last row
{
// turn around
nextRow = nextRow - 1;
direction = Direction.Up;
}
break;
case Direction.Up:
if (nextRow > 0)
{
// Nominal case - move up one cell
nextRow = nextRow - 1;
}
else // at the first row
{
// turn around
nextRow = nextRow + 1;
direction = Direction.Down;
}
break;
default: return false;
}
return true;
}
If anyone uses this and finds cases where it behaves badly, I'd like to hear about it so I can hopefully update this with a fix.
EDIT 3: Added a safety counter after the code managed to get itself in an infinite-loop state today. All cells in column zero were set to read-only, and the first click into the grid control was in column zero, so it tried to move down, then up, then down....
You will have put in some code for that
One way of doing it is
void grd_CellEnter(object sender, DataGridViewCellEventArgs e)
{
if(grd[e.ColumnIndex,e.RowIndex].ReadOnly)
SendKeys.Send("{TAB}");
}
I had a similar problem using a datagridview - there is a write up on my solution here http://codemumbler.blogspot.com/2011/02/one-aspect-of-datagridviews-that-you.html
It uses the CellEnter eventhandler and hunts for the next available usable sell in the grid and uses a delegate to avoid a Reentrant exception.
Hope this helps - Sean
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;
}
}