I have a c# DataGridView with some columns. I want to add an Action Column as the last Cell in each Row.
There should be buttons or icons like View,Delete,Update in each Row.
Here are a few options I can think of:
By far the simplest is to add one Column for each Button. (Recommended!)
Of you could create a custom cell type, maybe a Panel subclass with the Buttons you want. This involves implementing IDataGridViewEditingControl iterface with at least a dozen fields and methods and also two custom classes derived from DataGridViewColumn and DataGridViewCell with many more things to do. In short a huge amount of work! For a sophisticated editing control maybe worth it. For a few Buttons certainly not! (Not recommended!)
Or you could fake the Buttons by a little owner-drawing magic in the CellPainting event. See below..!
Or you could add one sopisticated control outside the DataGridView that acts on the current row. The usual way!
Here is a fun little example, that owner-draws the four 'SIDU' commands in the 4th Column of a DatagGridView DGV:
private void Form1_Load(object sender, EventArgs e)
{
DGV.Rows.Add(12);
for( int i = 0; i< DGV.Rows.Count; i++)
{
DGV[0, i].Value = i;
DGV[1, i].Value = R.Next(1000);
DGV[2, i].Value = rights[R.Next(rights.Count)];
DGV[3, i].ReadOnly = true;
}
}
List<string> rights = new List<string>
{ "SIDU", "SID-", "SI-U", "S-DU", "SI--", "S--U", "S-D-", "S---" };
Dictionary<char, string> rightsTexts = new Dictionary<char, string>
{ { 'S', "Select" }, { 'I', "Insert" }, { 'D', "Delete" }, { 'U', "Update" } };
Random R = new Random(1);
private void DGV_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.ColumnIndex == 3 && e.RowIndex >= 0)
{
string r = DGV[2,e.RowIndex].Value.ToString();
StringFormat format = new StringFormat()
{ LineAlignment = StringAlignment.Center, Alignment = StringAlignment.Center};
int w = e.CellBounds.Width / 4;
int y = e.CellBounds.Y + 1;
int h = e.CellBounds.Height - 2;
e.PaintBackground(e.CellBounds, false);
for (int i = 0; i < 4; i++)
{
int x = e.CellBounds.X + i * w;
Rectangle rect = new Rectangle(x, y, w, h);
ControlPaint.DrawButton(e.Graphics, rect, ButtonState.Normal);
if (rightsTexts.ContainsKey(r[i]))
e.Graphics.DrawString(rightsTexts[r[i]], DGV.Font,
SystemBrushes.WindowText, rect ,format );
}
e.Handled = true;
}
}
private void DGV_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.ColumnIndex == 3 && e.RowIndex >= 0)
{
DataGridViewCell cell = DGV[e.ColumnIndex, e.RowIndex];
int w = cell.Size.Width;
int buttonIndex = e.X * 4 / w;
Text = rightsTexts.ElementAt(buttonIndex).Value;
}
}
The drawing stuff was thrown up rather q&d, so you could invest a lot more care to fine-tune it..
I have chosen to display the rights in a visible cell for demostration. For production the action cell's value would be the obvious place.
Related
I have a dictionary of buttons and also an null variable button. When you click on an button from the dictionary, we get the values that should be sent to an empty variable.
public static Button selectedfigure=null;
public Dictionary<(int x, int y), Button> _Panels = new Dictionary<(int x, int y), Button>();
public void Fillboard()
{
for (int x = 0; x < 8; x++)
{
for (int y = 0; y < 8; y++)
{
_Panels[(x, y)] = new Button()
{
Height = 64,
Width = 64,
Location = new Point(x * 64, y * 64),
BackColor = CalculateBackColor(x,y),
BackgroundImage = CalculateBackimage(x,y)
};
Controls.Add(_Panels[(x, y)]);
}
}
}
Your question is about how to interact with Mouse events on your "board". The answer is to subscribe to Mouse events like Click for each control you add. Your code is using a normal Button but will be much simpler if you make a custom class that inherits Button or preferably PictureBox and this way each instance keeps track of its own X-Y, color, and image.
Also, instead of using a Dictionary lookup and setting Location properties yourself, you might try an 8x8 TableLayoutPanel for the board, then iterate all its 8 columns and rows with methods that take column and row as arguments.
The mouse events can be subscribed to in the addSquare method.
private void addSquare(int column, int row)
{
var color = ((column + row) % 2) == 0 ? Color.White : Color.Black;
var square = new Square
{
BackColor = color,
Column = column,
Row = row,
Size = new Size(80, 80),
Margin = new Padding(0),
Padding = new Padding(10),
Anchor = (AnchorStyles)0xf,
SizeMode = PictureBoxSizeMode.StretchImage,
};
tableLayoutPanel.Controls.Add(square, column, row);
// Hook the mouse events here
square.Click += onSquareClicked;
square.MouseHover += onSquareMouseHover;
}
Before and after iterating the TableLayoutPanel with addSquare
Changing the value of the Square.Piece property will choose an image from a resource file.
private void initImage(int column, int row)
{
var square = (Square)tableLayoutPanel.GetControlFromPosition(column, row);
if (square.BackColor == Color.Black)
{
switch (row)
{
case 0:
case 1:
case 2:
square.Piece = Piece.Black;
break;
case 5:
case 6:
case 7:
square.Piece = Piece.Red;
break;
default:
square.Piece = Piece.None;
break;
}
}
}
Before and after iterating the TableLayoutPanel with initImage
Mouse event handlers are simple now:
private void onSquareMouseHover(object sender, EventArgs e)
{
var square = (Square)sender;
_tt.SetToolTip(square, square.ToString());
}
private void onSquareClicked(object sender, EventArgs e)
{
var square = (Square)sender;
MessageBox.Show($"Clicked: {square}");
}
ToolTip _tt = new ToolTip();
The Square class is uncomplicated, just a few lines of code.
class Square : PictureBox // Gives more visual control than Button
{
public int Column { get; internal set; }
public int Row { get; internal set; }
Piece _piece = Piece.None;
public Piece Piece
{
get => _piece;
set
{
if(!Equals(_piece, value))
{
_piece = value;
switch (_piece)
{
case Piece.None:
Image = null;
break;
case Piece.Black:
Image = Resources.black;
break;
case Piece.Red:
Image = Resources.red;
break;
}
}
}
}
public override string ToString() =>
Piece == Piece.None ?
$"Empty {BackColor.Name} square [column:{Column} row:{Row}]" :
$"{Piece} piece [column:{Column} row:{Row}]";
}
enum Piece { None, Black, Red };
Edited (in response to Jeremy's excellent suggestion)
Where and how to call the methods to initialize the board:
public MainForm()
{
InitializeComponent();
// Board is a TableLayoutPanel 8 x 8
tableLayoutPanel.AutoSize = true;
tableLayoutPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
// Add squares
IterateBoard(addSquare);
// Add pieces
IterateBoard(initImage);
}
void IterateBoard(Action<int, int> action)
{
for (int column = 0; column < 8; column++)
{
for (int row = 0; row < 8; row++)
{
action(column, row);
}
}
}
I don't know exactly which is your problem. I think you need know which button is pressed in your matrix.
You can add the controls in the same way but instead the use of _Panels dictionary, you can use Tag property of button to store a Point with the x,y coordinates:
for (int x = 0; x < 8; x++)
{
for (int y = 0; y < 8; y++)
{
var button = new Button
{
Height = 64,
Width = 64,
Location = new Point(x * 64, y * 64),
BackColor = CalculateBackColor(x,y),
BackgroundImage = CalculateBackimage(x,y),
Tag = new Point(x, y)
};
button.Click += OnButtonClick;
Controls.Add(button);
}
}
In the click event handler of the button:
private void OnButtonClick(object sender, EventArgs e)
{
var button = (Button)sender;
var point = (Point)button.Tag;
// Here you have x,y coordinates of clicked button
}
In my WinForm project I have a DataGridView control.
I achieved to merge the Header cells of 2 Columns. It is working fine but there is a problem when scrolling. The image below should explain it.
Can anyone help?
This is the code I use to merging the Column Header cells.
The indexes of the merged Columns are 20 and 21.
private void loadEvents()
{
this.dgv_db_door.Paint += new PaintEventHandler(dgv_db_door_Paint);
this.dgv_db_door.Scroll += new ScrollEventHandler(dgv_db_door_Scroll);
this.dgv_db_door.ColumnWidthChanged += DataGridView1_ColumnWidthChanged;
this.dgv_db_door.Resize += DataGridView1_Resize;
}
private void dgv_db_door_Paint(object sender, PaintEventArgs e)
{
string doorCloser = "DOOR CLOSER";
//Index numbers of merged columns ara 20 and 21;
int mergedColumn1 = 20;
int mergedColumn2 = 21;
Rectangle r1 = dgv_db_door.GetCellDisplayRectangle(mergedColumn1, -1, true);
int w2 = dgv_db_door.GetCellDisplayRectangle(mergedColumn2, -1, true).Width;
r1.X += 1;
r1.Y += 1;
r1.Width = r1.Width + w2 - 2;
r1.Height = r1.Height / 2 - 2;
e.Graphics.FillRectangle(new SolidBrush(dgv_db_door.ColumnHeadersDefaultCellStyle.BackColor), r1);
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
e.Graphics.DrawString(doorCloser, dgv_db_door.ColumnHeadersDefaultCellStyle.Font,
new SolidBrush(dgv_db_door.ColumnHeadersDefaultCellStyle.ForeColor), r1, format);
}
private void dgv_db_door_Scroll(object sender, ScrollEventArgs e)
{
if (e.OldValue > e.NewValue)
{
}
else
{
this.InvalidateHeader();
}
}
private void DataGridView1_Resize(object sender, EventArgs e)
{
this.InvalidateHeader();
}
private void DataGridView1_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
{
this.InvalidateHeader();
}
private void InvalidateHeader()
{
Rectangle rtHeader = this.dgv_db_door.DisplayRectangle;
rtHeader.Height = this.dgv_db_door.ColumnHeadersHeight / 2;
this.dgv_db_door.Invalidate(rtHeader);
}
A few modifications to the drawing procedure:
The custom Header drawing Rectangle is sized calculating the Width of all the Columns in the specified Range.
The position of the drawing area is calculated using the left-most Column's position, the RowHeadersWidth and the current DataGridView.HorizontalScrollingOffset, which defines the horizontal scroll position of the client rectangle.
To draw the custom Header, a clipping region is created - using Graphics.SetClip() - to exclude the Row Header from the drawing area.
The custom Header is only rendered when visible on screen.
I'm using TextRenderer.DrawText instead of Graphics.DrawString(), since this is the method used by this control to render its content.
Hence, I'm calculating the height of the text bounds using TextRenderer.MeasureText()
TextFormatFlags.PreserveGraphicsClipping is used to instruct TextRenderer not to draw the text outside the clipping region of the Graphics object.
Note 1: I've added Double-Buffering to the DataGridView, to avoid any flickering when clicking the Header Cells. It may have an impact when the grid needs to render a high number of Rows.
Note 2: You can remove all those InvalidateHeader() calls, not needed here.
► This new behavior allows to reset the range of Columns to include in the custom Header and the Header's Text at any time (as shown in the visual example).
This is how it looks now:
using System.Reflection;
TextFormatFlags centerTopflags =
TextFormatFlags.HorizontalCenter | TextFormatFlags.Top | TextFormatFlags.PreserveGraphicsClipping;
string mergedHeaderText = string.Empty;
int[] mergedColumns = null;
public void SomeForm()
{
this.InitializeComponent();
var flags = BindingFlags.Instance | BindingFlags.NonPublic;
dgvTest.GetType().GetProperty("DoubleBuffered", flags).SetValue(dgvTest, true);
mergedColumns = new int[] { 20, 21 };
mergedHeaderText = "DOOR CLOSER"
}
private void dgv_db_door_Paint(object sender, PaintEventArgs e)
{
var dgv = sender as DataGridView;
var headerStyle = dgv.ColumnHeadersDefaultCellStyle;
int colsWidth = -1;
int colsLeft = 1;
// Absolute Width of the merged Column range
for (int i = 0; i < mergedColumns.Length; i++) {
var col = dgv.Columns[mergedColumns[i]];
colsWidth += col.Visible ? col.Width : 0;
}
// Absolute Left position of the first Column to merge
if (mergedColumns[0] > 0) {
colsLeft += dgv.Columns.OfType<DataGridViewColumn>()
.Where(c => c.Visible).Take(mergedColumns[0]).Sum(c => c.Width);
}
// Merged Headers raw drawing Rectangle
var r = new Rectangle(
dgv.RowHeadersWidth + colsLeft - dgv.HorizontalScrollingOffset, 2,
colsWidth, dgv.ColumnHeadersHeight);
// Measure the Height of the text to render - no wrapping
r.Height = TextRenderer.MeasureText(e.Graphics, mergedHeaderText, headerStyle.Font, r.Size, centerTopflags).Height;
// Draw the merged Headers only if visible on screen
if (r.Right > dgv.RowHeadersWidth || r.X < dgv.DisplayRectangle.Right) {
// Clip the drawing Region to exclude the Row Header
var clipRect = new Rectangle(
dgv.RowHeadersWidth + 1, 0,
dgv.DisplayRectangle.Width - dgv.RowHeadersWidth, dgv.ColumnHeadersHeight);
e.Graphics.SetClip(clipRect);
using (var brush = new SolidBrush(headerStyle.BackColor)) e.Graphics.FillRectangle(brush, r);
TextRenderer.DrawText(e.Graphics, mergedHeaderText, headerStyle.Font, r, headerStyle.ForeColor, centerTopflags);
e.Graphics.ResetClip();
}
}
I would like to be able to create X x Y number of boxes / circles / buttons inside a picturebox. EXACTLY like Windows Defrag tool.
I tried to create a layout and keep adding buttons or pictureboxes to it, but its extremely slow and after 200 or so pictureboxes it crashes, runs out of window handles or memory.
What's the alternative? Can you show me a simple piece of code to add boxes just like Defrag tool where I can easily access them like
box[x,y].Color = Green as my app makes progress?
Currently I have this:
private void ResetTableStyles()
{
boardPanel.Controls.Clear();
boardPanel.RowStyles.Clear();
boardPanel.ColumnStyles.Clear();
boardPanel.RowCount = Rows;
boardPanel.ColumnCount = Columns;
for (int i = 0; i < Rows; i++)
{
boardPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100f));
}
for (int j = 0; j < Columns; j++)
{
boardPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100f));
}
}
private void CreateButtons()
{
for (int i = 0; i < Rows; i++)
{
for (int j = 0; j < Columns; j++)
{
var button = new PictureBox
{
BackColor = Color.White,
Dock = DockStyle.Fill,
Margin = Padding.Empty,
Tag = new Point(i, j),
BackgroundImageLayout = ImageLayout.Stretch
};
//button.MouseDown += button_MouseDown;
boardPanel.Controls.Add(button, j, i);
}
}
}
which as I said doesn't work, after sometime it just crashes and it takes very long time.
Perhaps someone has a better answer, but you could use a Panel as the canvas and handle the Paint event to draw colored rectangles onto the Panel. You could then use the mouse events, such as MouseMove, to figure out which cell you're on.
Here is a super-simple proof of concept.
// Data.cs
namespace WindowsFormsApplication
{
public class Data
{
public int State { get; set; }
public string Tip { get; set; }
}
}
// Form1.cs
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var rng = new Random();
_data = new Data[30 * 30];
for (int i = 0; i < _data.Length; i++)
{
_data[i] = new Data
{
State = rng.Next(0, 3),
Tip = $"Data at index: {i}"
};
}
}
private Data[] _data;
private void panel1_Paint(object sender, PaintEventArgs e)
{
using (var brush = new SolidBrush(Color.Gray))
using (var buffer = BufferedGraphicsManager.Current.Allocate(e.Graphics,
new Rectangle(0, 0, 450, 450)))
{
for (int x = 0; x < 30; x++)
{
for (int y = 0; y < 30; y++)
{
int dataIdx = (y * 30) + x;
Data data = _data[dataIdx];
if (data.State == 1)
{
brush.Color = Color.Blue;
}
else if (data.State == 2)
{
brush.Color = Color.Red;
}
else
{
brush.Color = Color.Gray;
}
buffer.Graphics.FillRectangle(brush, x * 15, y * 15, 15, 15);
buffer.Graphics.DrawLine(Pens.Black, 0, y * 15, 450, y * 15); //Gridline
}
buffer.Graphics.DrawLine(Pens.Black, x * 15, 0, x * 15, 450); //Gridline
}
buffer.Render(e.Graphics);
}
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
var point = e.Location;
int x = point.X / 15;
int y = point.Y / 15;
int dataIdx = (y * 30) + x;
System.Diagnostics.Debug.WriteLine(_data[dataIdx].Tip);
}
}
}
The Data class represents the model behind each segment on the panel and is pretty straight forward.
Form1 has a single control placed on it: a Panel named panel1 with a size of 450 x 450. Each cell on the panel will be 15 x 15, so we have 30 columns and 30 rows. In Form1's constructor, we initialize the Data that will be used to draw the panel.
Inside of Form1.panel1_Paint we iterate through the cells and look up the instance of Data for the given cell. Then, based on the State, set a color for the cell and draw a rectangle of that color. This is done on a buffer so the panel doesn't flicker when painting.
Inside of Form1.panel1_MouseMove we use the location of the mouse pointer relative to panel1 to figure out which cell the mouse is over, get the instance of Data for that cell, and display the Tip in the debug window.
You might be able to take this and build upon it, perhaps with a custom UserControl so you can easily access each individual cell with the column and row... your box[x, y].Color = Colors.Green example above.
Initially I display data in cells as user scrolls I need to load more data in DataGridView.
I am using DataGridView CellPainting for drawing lines.
When I start scrolling in datagridview the cells get overlapped and it completely changes the output.
public partial class Display : Form
{
public Display()
{
InitializeComponent();
LoadData();
}
// To create the rows and columns and fill some data
private void LoadData()
{
int columnSize = 10;
DataGridViewColumn[] columnName = new DataGridViewColumn[columnSize];
for (int index = 0; index < columnSize; index++)
{
columnName[index] = new DataGridViewTextBoxColumn();
if (index == 0)
{
columnName[index].Name = "Name";
columnName[index].HeaderText = "Name";
}
else
{
columnName[index].Name = (index).ToString();
columnName[index].HeaderText = (index).ToString();
}
columnName[index].FillWeight = 0.00001f;
columnName[index].AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
dataGridView1.Columns.Add(columnName[index]);
}
for (int rowIndex = 0; rowIndex < columnSize; rowIndex++)
{
dataGridView1.Rows.Add((rowIndex + 1).ToString());
dataGridView1.Rows[rowIndex].HeaderCell.Value = (rowIndex + 1).ToString();
}
}
private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
Rectangle rectPos1 = this.dataGridView1.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false);
Pen graphPen = new Pen(Color.Red, 1);
Graphics graphics = this.dataGridView1.CreateGraphics();
Point[] points =
{
new Point(rectPos1.Left , rectPos1.Bottom),
new Point(rectPos1.Right, rectPos1.Bottom),
new Point(rectPos1.Right, rectPos1.Top)
};
graphics.DrawLines(graphPen, points);
e.PaintContent(rectPos1);
e.Handled = true;
}
}
Sample Download Link
Which I have shown in the below image
How can i avoid it please help me solve this issue.
A couple of issues. First and foremost, you should almost always use the provided Graphics object you get from the PaintEventArgs. CreateGraphics is a temporary canvas that gets easily erased. One of the parameters you get is the CellBounds rectangle, so you can use that. Your lines are actually drawing outside of the rectangle, and you aren't clearing the previous contents, so your code should look something like this:
Rectangle rectPos1 = e.CellBounds;
e.Graphics.FillRectangle(Brushes.White, rectPos1);
Graphics graphics = e.Graphics; // this.dataGridView1.CreateGraphics();
Point[] points =
{
new Point(rectPos1.Left , rectPos1.Bottom - 1),
new Point(rectPos1.Right - 1, rectPos1.Bottom - 1),
new Point(rectPos1.Right - 1, rectPos1.Top)
};
graphics.DrawLines(Pens.Red, points);
e.PaintContent(rectPos1);
e.Handled = true;
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
How can I check that whether a usercontrol exists in the cell where the user has clicked. I have created a user control Rack.cs , just want to know that how can I check that the Rack() is present in that location or not?
If yes then do something
private void tableLayoutPanel1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
click++;
var pt = new Point(e.X, e.Y);
var colWidths = this.tableLayoutPanel1.GetColumnWidths();
var rowHeights = this.tableLayoutPanel1.GetRowHeights();
//tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 150));
//tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 150F));
int col = -1, row = -1;
int offset = 0;
for (int iCol = 0; iCol < this.tableLayoutPanel1.ColumnCount; ++iCol)
{
if (pt.X >= offset && pt.X <= (offset + colWidths[iCol]))
{
col = iCol;
break;
}
offset += colWidths[iCol];
}
offset = 0;
for (int iRow = 0; iRow < this.tableLayoutPanel1.RowCount; ++iRow)
{
if (pt.Y >= offset && pt.Y <= (offset + rowHeights[iRow]))
{
row = iRow;
break;
}
offset += rowHeights[iRow];
}
tableLayoutPanel1.Controls.Add(racking[click], col, row);
racking[click].setposition(row, col);
racking[click].SetChannel(click.ToString());
tableLayoutPanel1.ColumnStyles[col].SizeType = SizeType.AutoSize;
Adapter.insertposition(RackID, row, col, click);
}
else if (e.Button == MouseButtons.Right)
{
int[] colWidths = tableLayoutPanel1.GetColumnWidths();
int[] rowHeights = tableLayoutPanel1.GetRowHeights();
int top = tableLayoutPanel1.Parent.PointToScreen(tableLayoutPanel1.Location).Y;
for (int y = 0; y < rowHeights.Length; ++y)
{
int left = tableLayoutPanel1.Parent.PointToScreen(tableLayoutPanel1.Location).X;
for (int x = 0; x < colWidths.Length; ++x)
{
if (new Rectangle(left, top, colWidths[x], rowHeights[y])
.Contains(MousePosition))
{
Control c = tableLayoutPanel1.GetControlFromPosition(x, y);
if (c != null)
{
MessageBox.Show("Yes");
}
}
left += colWidths[x];
}
top += rowHeights[y];
}
}
}
Now I want to check my Rack control instead of the Rectangle control that where it is present or not? And my Rack control is a mixture of text boxes and button
After seeing some code it is still not entirely clear what you are doing (for example, what is racking?), but this should help anyway...
TableLayoutPanel has a method called GetControlFromPosition which will get the control within a given cell (column and row), so firstly you can get that control like so:
var myCellControl = tableLayoutPanel1.GetControlFromPosition(col, row);
What you do next will depend on how you are adding your controls. If you are directly adding a Rack control to each cell, then you can just test it like so:
if(myCellControl is Rack)
{
//is Rack control, so do someting
}
otherwise, if the Rack control is nested within a container control (e.g. a Panel), then you should loop the children controls and test for a Rack control:
bool hasRack = false;
foreach(Control child in myCellControl.Controls)
{
if(child is Rack)
{
//Rack control found
hasRack = true;
break;
}
}
Hope that helps