PrintPage Event stays in endless loop while printing Chart Control - c#

I've found some issues on this but nothing that would help me in my logic. I have following code (showing not all because it would be too much):
public int AreaCounter { get; set; } = 0;
public PrintDocument pd { get; set; }
public void PrintCharts(DataTable dt)
{
foreach (DataRow row in dt.Rows)
{
//after a couple of rows - here is code for creating
//a new chartArea and binding the points to the series
//plus binding that series to the area
if(// two chartAreas have been created with each one having a chart)
{
PrintChartControl();
}
AreaCounter++;
}
}
private void PrintChartControl()
{
pd.PrintPage += pd_PrintPage;
pd.Print();
}
private void pd_PrintPage(object sender, PrintPageEventArgs e)
{
if (AreaCounter < 11) //12 ChartAreas have to be created
e.HasMorePages = true;
else
e.HasMorePages = false;
var myRec = e.MarginBounds;
Container.chart1.Printing.PrintPaint(e.Graphics, myRec);
}
Now what I want to do:
I loop through a DataTable. Every few lines I create a new ChartArea, binding some values from this lines to a Series and bind this to the ChartArea. I'm actually drawing to ChartAreas to my Chart Control. Every two ChartAreas I print the Control, clean it, and draw the next two ChartAreas (there are 12 at the end - so 6 times drawing to the Chart Control). This works but I want to achieve the following:
I want to add a new page for every Chart Control to my print event so that I have 6 pages at the end and then print this to one file (pdf). Somehow it gets into an infinitive loop in the printPage Event because of the e.HasMorePages property. How does this work with the chart?
Thank you very much!

You better have the PrintDocument class drive your data, instead of you trying to drive the PrintDocument. The PrintDocument class will raise a PrintPage event, it is our task to provide the data for that single page. The data goes onto the Graphics instance provided in the PrintPageEventArgs. If we want to print another page we make sure HasMorePages is true, the printdocument instance will call PrintPage again and we provide the data for the next page etc. When we have no more data and don't want more pages to be printed, we set HasMorePagesto false.
Based on your code I created the following example that I expect to be applicable to your case.
I assumed the Rows in your DataTable are the main source to determine if and how many pages you need to print. If it is not based on that, you can create an Enumerator for a different collection/array, the mechanisms remain the same.
// the reference to the enumerator for the DataRows
IEnumerator rows;
private void printDocument1_BeginPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
{
var dataTable = Load();
rows = dataTable.Rows.GetEnumerator();
}
private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
// no rows, no glory
if (rows == null) return;
// keep track of stuff
var chartsOnthisPage = 0;
var yPos = 20f;
// loop for what needs to go on this page, atm it prints 2 charts
// as long as there are rows
// the enumerator is moved to the next record ...
while ((e.HasMorePages = rows.MoveNext()))
{
// ... get hold of that datarow
var currentRow = (DataRow)rows.Current;
// print
e.Graphics.DrawString(currentRow[0].ToString(), Font, Brushes.Black, 0, yPos);
// keep track where we are
yPos = yPos + 40;
// do what ever is need to print the chart fro this row
GenerateChart(currentRow);
// print the chart
chart1.Printing.PrintPaint(e.Graphics, new Rectangle(0, (int)yPos, 200, 200));
// position tracking
yPos = yPos + 200;
// optionaly break here if we reached the end of the page
// keep track
chartsOnthisPage++;
if (chartsOnthisPage > 1) break; // HasMorePages is set
}
}
private void printDocument1_EndPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
{
// clean up;
rows = null;
}
I have two helper methods, one to create a DataTable with rows and one to generate some interesting chart.
private DataTable Load()
{
var dt = new DataTable();
dt.Columns.Add("chart");
for(int r=0; r<10; r++)
{
var rw = dt.NewRow();
rw[0] = Guid.NewGuid().ToString("N");
dt.Rows.Add(rw);
}
return dt;
}
Random rnd = new Random();
private void GenerateChart(DataRow row)
{
chart1.Series.Clear();
chart1.Series.Add("data");
var mx = rnd.Next(rnd.Next(10) + 3);
for (int x = 0; x < mx; x++)
{
chart1.Series[0].Points.Add(x, rnd.Next(100));
}
}
When I run this with a preview control this is what I get:

Related

Winforms - Position new form directly under Datagridview selected row of parent

I have a form with a DataGridView on it. I select a row (always a single row) of data. After I click a button I would like to open a new form and always position it directly under the selected row. How should I do this?
In my button click I do something like this:
private void myBtn_Click(object sender, EventArgs e)
{
if (myDGV.SelectedCells.Count > 0)
{
int i = myDGV.SelectedCells[0].RowIndex;
DataGridViewRow r = myDGV.Rows[i];
// READ SOME DATA FROM THE ROW
newForm form = new newForm();
//POPULATE THE NEW FORM WITH DATA
form.ShowDialog(this); //POSITION OF THIS FORM SHOULD BE DIRECTLY UNDER SELECTED ROW
}
}
You can use DataGridView.GetRowDisplayRectangle to obtain the row rectangle in data grid view client coordinates, then Control.RectangleToScreen to convert them to a screen coordinates needed for determining the new form location, like this:
private void myBtn_Click(object sender, EventArgs e)
{
if (myDGV.SelectedCells.Count > 0)
{
int i = myDGV.SelectedCells[0].RowIndex;
DataGridViewRow r = myDGV.Rows[i];
// READ SOME DATA FROM THE ROW
newForm form = new newForm();
//POPULATE THE NEW FORM WITH DATA ...
// Position the form under the selected row
form.StartPosition = FormStartPosition.Manual;
var rect = myDGV.RectangleToScreen(myDGV.GetRowDisplayRectangle(i, false));
form.Location = new Point(rect.Left, rect.Bottom);
form.ShowDialog(this);
}
}
While Ivans solution is 100% valid and correct, there are some minor "gripes" with it as far as aesthetics are concerned (which he could easily cater for in his elegant solution). If you want the dialog box to appear directly underneath the row, flush with both the left side of the gridview and the row bottom, you need to do it differently - the long way.
I reiterate this - Ivans solution is very appropriate and much cleaner.. The below solution gives you a little more freedom concerning form placement and alike. I threw this together in a few minutes so sorry if I missed something small.
You can always use the built in properties like Rectangle.Bottom and alike. I did this to, I guess, show the math on how to approach things.
private void myBtn_Click(object sender, EventArgs e)
{
if(dataGridView1.SelectedRows.Count > 0)
{
var rowIndex = myDGV.SelectedRows[0].Index;
var row = myDGV.Rows[rowIndex];
var formLocation = this.Location; //Form location
var gridLocation = myDGV.Location; //grid location
var rowLocation = myDGV.GetRowDisplayRectangle(rowIndex, false).Location; //row location
newForm form = new newForm();
form.StartPosition = FormStartPosition.Manual; //set to manual
//form.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
//form.BackColor = Color.Blue;
form.Location = GetPopupStartingLocation(
new Point[]
{
formLocation,
gridLocation,
rowLocation
},
row.Height);
form.Show(this);
}
}
/*
A small helper method - didn't need to put this in a method
but I did it for clarity.
*/
private Point GetPopupStartingLocation(Point[] locations, int rowHeight)
{
var yPadding = 5;
var xValues = locations.Sum(x => x.X);
var yValues = locations.Sum(x => x.Y) + ((rowHeight * 2) + yPadding);
return new Point(xValues, yValues);
}
Now Most of this is pretty much the same as Ivan's except the "longer route" but this gives you a little more control over your location using the helper function. You can add/remove adjust the values down there and find something you like. Works fine.
Hope this helps.
+1 to Ivan for the clean solution.

How can I instantiate large number of buttons in Windows Forms?

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; }

I changed some of my fields to display as other fields in other table, and now i can't update the values

Previously it worked when I display fields only on one table but now When I change the value in any fields, and clicked the update button, it gives me (Dynamic SQL generation is not supported against multiple base tables.) Help.
appointment form
Error updateing
Appointment table
nurse table
medicalcentre table
patient table
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Data.SqlClient;
using System.Configuration;
namespace GRP_02_03_SACP
{
public partial class appointment : Form
{
// Data Table to store employee data
DataTable Appointment = new DataTable();
// Keeps track of which row in Gridview
// is selected
DataGridViewRow currentRow = null;
SqlDataAdapter AppointmentAdapter;
public appointment()
{
InitializeComponent();
}
private void appointment_Load(object sender, EventArgs e)
{
LoadMedicalCentreRecords();
}
private void LoadMedicalCentreRecords()
{
//retrieve connection information info from App.config
string strConnectionString = ConfigurationManager.ConnectionStrings["sacpConnection"].ConnectionString;
//STEP 1: Create connection
SqlConnection myConnect = new SqlConnection(strConnectionString);
//STEP 2: Create command
string strCommandText = "SELECT appointmentID, aDate, aTime, aStatus, aContact, aHeight, aWeight, p.pFirstName , m.mcCentre , n.nFirstName FROM APPOINTMENT AS a LEFT OUTER JOIN Nurse AS n ON a.nurseID = n.NurseID Left outer join Patient as p on a.patientid = p.patientId left outer join medicalcentre as m on a.mcID = m.mcid";
AppointmentAdapter = new SqlDataAdapter(strCommandText, myConnect);
//command builder generates Select, update, delete and insert SQL
// statements for MedicalCentreAdapter
SqlCommandBuilder cmdBuilder = new SqlCommandBuilder(AppointmentAdapter);
// Empty Employee Table first
Appointment.Clear();
// Fill Employee Table with data retrieved by data adapter
// using SELECT statement
AppointmentAdapter.Fill(Appointment);
// if there are records, bind to Grid view & display
if (Appointment.Rows.Count > 0)
grdApp.DataSource = Appointment;
}
private void btnUpdate_Click(object sender, EventArgs e)
{
int modifiedRows = 0;
// Get changes
DataTable UpdatedTable = Appointment.GetChanges();
if (UpdatedTable != null)
{
// there are changes
// Write modified data to database
modifiedRows = AppointmentAdapter.Update(UpdatedTable);
// accept changes
Appointment.AcceptChanges();
}
else
MessageBox.Show("there are no changes to update");
if (modifiedRows > 0)
{
MessageBox.Show("There are " + modifiedRows + " records updated");
LoadMedicalCentreRecords();
}
}
private void btnPrint_Click(object sender, EventArgs e)
{
if (printDialog1.ShowDialog() == DialogResult.OK) // this displays the dialog box and performs actions dependant on which option chosen.
{
printDocument1.Print();
}
}
private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
int columnPosition = 0;
int rowPosition = 25;
// run function to draw headers
DrawHeader(new Font(this.Font, FontStyle.Bold), e.Graphics, ref columnPosition, ref rowPosition); // runs the DrawHeader function
rowPosition += 35; // sets the distance below the header text and the next black line (ruler)
// run function to draw each row
DrawGridBody(e.Graphics, ref columnPosition, ref rowPosition);
}
// DrawHeader will draw the column title, move over, draw the next column title, move over, and continue.
private int DrawHeader(Font boldFont, Graphics g, ref int columnPosition, ref int rowPosition)
{
foreach (DataGridViewColumn dc in grdApp.Columns)
{
//MessageBox.Show("dc = " + dc);
g.DrawString(dc.HeaderText, boldFont, Brushes.Black, (float)columnPosition, (float)rowPosition);
columnPosition += dc.Width + 5; // adds to colPos. value the width value of the column + 5.
}
return columnPosition;
}
/* DrawGridBody will loop though each row and draw it on the screen. It starts by drawing a solid line on the screen,
* then it moves down a row and draws the data from the first grid column, then it moves over, then draws the data from the next column,
* moves over, draws the data from the next column, and continus this pattern. When the entire row is drawn it starts over and draws
* a solid line then the row data, then the next solid line and then row data, etc.
*/
private void DrawGridBody(Graphics g, ref int columnPosition, ref int rowPosition)
{
// loop through each row and draw the data to the graphics surface.
foreach (DataRow dr in ((DataTable)grdApp.DataSource).Rows)
{
columnPosition = 0;
// draw a line to separate the rows
g.DrawLine(Pens.Black, new Point(0, rowPosition), new Point(this.Width, rowPosition));
// loop through each column in the row, and draw the individual data item
foreach (DataGridViewColumn dc in grdApp.Columns)
{
// draw string in the column
string text = dr[dc.DataPropertyName].ToString();
g.DrawString(text, this.Font, Brushes.Black, (float)columnPosition, (float)rowPosition + 10f); // the last number (10f) sets the space between the black line (ruler) and the text below it.
// go to the next column position
columnPosition += dc.Width + 5;
}
// go to the next row position
rowPosition = rowPosition + 60; // this sets the space between the row text and the black line below it (ruler).
}
}
private void btnPrintPreview_Click(object sender, EventArgs e)
{
try
{
// PrintPreviewDialog printPreviewDialog1 = new PrintPreviewDialog(); // instantiate new print preview dialog
printPreviewDialog1.Document = this.printDocument1;
if (printPreviewDialog1.ShowDialog() == DialogResult.OK) // Show the print preview dialog, uses printPage event to draw preview screen
{
printDocument1.Print();
}
}
catch (Exception exp)
{
System.Console.WriteLine(exp.Message.ToString());
}
}
}
}

How to jump to the next page in a PrintDocument?

I have an application that prints how many bar codes you want, but if the amount of bar codes is bigger than the size of the PrintDocument it doesn't jump to the next page.
I'd like to know how can I add more pages or write in the next page of a PrintDocument.
I'm using a PrintPreview to display the PrintDocument in this Windows Form.
If you hookup the OnPrintPage event you can tell the PrintDocument if it needs to add another page on the PrintPageEventArguments.
IEnumerator items;
public void StartPrint()
{
PrintDocument pd = new PrintDocument();
pd.PrintPage += new PrintPageEventHandler(this.pd_PrintPage);
items = GetEnumerator();
if (items.MoveNext())
{
pd.Print();
}
}
private void pd_PrintPage(object sender, PrintPageEventArgs ev)
{
const int neededHeight = 200;
int line =0;
// this will be called multiple times, so keep track where you are...
// do your drawings, calculating how much space you have left on one page
bool more = true;
do
{
// draw your bars for item, handle multilple columns if needed
var item = items.Current;
line++;
// in the ev.MarginBouds the width and height of this page is available
// you use that to see if a next row will fit
if ((line * neededHeight) < ev.MarginBounds.Height )
{
break;
}
more = items.MoveNext();
} while (more);
// stop if there are no more items in your Iterator
ev.HasMorePages = more;
}

how do I host a control in a DataGridViewCell for displaying as well as editing?

I've seen How to: Host Controls in Windows Forms DataGridView Cells which explains how to host a control for editing a cell in a DataGridView. But how can I host a control for displaying a cell?
I need to display a file name and a button in the same cell. Our UI designer is a graphic designer not a programmer, so I have to match the code to what he's drawn, whether it's possible - or wise - or not. We're using VS2008 and writing in C# for .NET 3.5, if that makes a difference.
UPDATE: The 'net suggests creating a custom DataGridViewCell which hosts a panel as a first step; anyone done that?
As per your "UPDATE", creating a custom DataGridViewCell is the way this is done. I've done it, and it doesn't require that much modification from the example code available from the MSDN. In my case, I needed a bunch of custom editing controls, so I ended up inheriting from DataGridViewTextBoxCell and DataGridViewColumn. I inserted into my class (the one inherited from DataGridViewTextBoxCell) a new custom control which implemented IDataGridViewEditingControl, and it all just worked.
I suppose that in your case, you could write a PanelDataGridViewCell which would contain a control MyPanelControl which would inherit from Panel and implement IDataGridViewEditingControl.
Rather than use a datagridview, how about using a TableLayoutPanel instead. Create your control that has a label and a button and events and fill your layout panel with them. Your control becomes the cell so to speak. It doesn't take much to make the table layout panel to look like a datagridview, if that is the layout style you want.
There are two ways to do this:
1). Cast a DataGridViewCell to a certain cell type that exists. For example, convert a DataGridViewTextBoxCell to DataGridViewComboBoxCell type.
2). Create a control and add it into the controls collection of DataGridView, set its location and size to fit the cell that to be host.
See Zhi-Xin Ye's sample code below which illustrates the tricks:
private void Form_Load(object sender, EventArgs e)
{
DataTable dt = new DataTable();
dt.Columns.Add("name");
for (int j = 0; j < 10; j++)
{
dt.Rows.Add("");
}
this.dataGridView1.DataSource = dt;
this.dataGridView1.Columns[0].Width = 200;
/*
* First method : Convert to an existed cell type such ComboBox cell,etc
*/
DataGridViewComboBoxCell ComboBoxCell = new DataGridViewComboBoxCell();
ComboBoxCell.Items.AddRange(new string[] { "aaa","bbb","ccc" });
this.dataGridView1[0, 0] = ComboBoxCell;
this.dataGridView1[0, 0].Value = "bbb";
DataGridViewTextBoxCell TextBoxCell = new DataGridViewTextBoxCell();
this.dataGridView1[0, 1] = TextBoxCell;
this.dataGridView1[0, 1].Value = "some text";
DataGridViewCheckBoxCell CheckBoxCell = new DataGridViewCheckBoxCell();
CheckBoxCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
this.dataGridView1[0, 2] = CheckBoxCell;
this.dataGridView1[0, 2].Value = true;
/*
* Second method : Add control to the host in the cell
*/
DateTimePicker dtp = new DateTimePicker();
dtp.Value = DateTime.Now.AddDays(-10);
//add DateTimePicker into the control collection of the DataGridView
this.dataGridView1.Controls.Add(dtp);
//set its location and size to fit the cell
dtp.Location = this.dataGridView1.GetCellDisplayRectangle(0, 3,true).Location;
dtp.Size = this.dataGridView1.GetCellDisplayRectangle(0, 3,true).Size;
}
MSDN Reference : how to host different controls in the same column in DataGridView control
Using the 1st method looks like this:
Using the 2nd method looks like this:
Additional info: Controls in the same DataGridView column dont render while initializing grid
Usually you should host controls in winfors forms datagridview cells as shown here
But if you need your control to be always visible, what you can do is make a custom column inheriting from DataGridViewImageColumn. Add the control to the datagridview. Set the DefaultCellStyle.Nullvalue of the control to a bitmap of the control you want always shown on the data gridview. Then using the cellMouseEnter event you can display and reposition the control to display over the image cell. This gives the appearance that your custom control is always visible without using as much resources as creating a new instance of your control for every row added to the datagridview. This will help performance quite a bit.
Here is what I did with my custom “AddRemove” usercontrol.
public class AddRemoveColumn : DataGridViewImageColumn
{
private AddRemove SelectionControl = null;
private Bitmap SelectionControlImage = null;
public AddRemoveColumn()
{
SelectionControl = new AddRemove();
}
#region Set Up Column
protected override void OnDataGridViewChanged()
{
base.OnDataGridViewChanged();
if (DataGridView != null)
{
Activate();
}
}
private void Activate()
{
SelectionControl.LostFocus += SelectionControl_LostFocus;
this.DataGridView.CellMouseEnter += DataGridView_CellMouseEnter;
this.DataGridView.BackgroundColorChanged += DataGridView_BackgroundColorChanged;
this.DataGridView.RowHeightChanged += DataGridView_RowHeightChanged;
SelectionControl.OnAddClicked += AddClicked;
SelectionControl.OnRemoveClicked += RemoveClicked;
this.DataGridView.Controls.Add(SelectionControl);
SelectionControl.Visible = false;
this.Width = SelectionControl.Width;
SelectionControl.BackColor = this.DataGridView.BackgroundColor;
this.DataGridView.RowTemplate.Height = SelectionControl.Height +1;
foreach (DataGridViewRow row in DataGridView.Rows)
{
row.Height = SelectionControl.Height+1;
}
SetNullImage();
}
#endregion
private void AddClicked(int RowIndex)
{
MessageBox.Show("Add clicked on index=" + RowIndex.ToString());
}
private void RemoveClicked(int RowIndex)
{
MessageBox.Show("Removed clicked on index=" + RowIndex.ToString());
}
private void SetNullImage()
{
if (SelectionControlImage != null)
{
SelectionControlImage.Dispose();
}
SelectionControlImage = new Bitmap(SelectionControl.Width, SelectionControl.Height);
SelectionControl.DrawToBitmap(SelectionControlImage, new Rectangle(0, 0, SelectionControlImage.Width, SelectionControlImage.Height));
this.DefaultCellStyle.NullValue = SelectionControlImage;
}
private void DataGridView_RowHeightChanged(object sender, DataGridViewRowEventArgs e)
{
if (e.Row.Height <= 40)
{
e.Row.Height = 40;
}
SelectionControl.Visible = false;
SetPosition(Index, e.Row.Index);
}
private void DataGridView_BackgroundColorChanged(object sender, EventArgs e)
{
SelectionControl.BackColor = this.DataGridView.BackgroundColor;
SetNullImage();
}
private void SelectionControl_LostFocus(object sender, EventArgs e)
{
SelectionControl.Visible = false;
}
private void SetPosition(int ColumnIndex, int RowIndex)
{
Rectangle celrec = this.DataGridView.GetCellDisplayRectangle(ColumnIndex, RowIndex, true);//.Rows[e.RowIndex].Cells[e.ColumnIndex].GetContentBounds();
int x_Offet = (celrec.Width - SelectionControl.Width)/ 2;
int y_Offet = (celrec.Height - SelectionControl.Height)/2;
SelectionControl.Location = new Point(celrec.X + x_Offet, celrec.Y + y_Offet);
SelectionControl.Visible = true;
SelectionControl.RowIndex = RowIndex;
}
private void DataGridView_CellMouseEnter(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == this.Index)
{
SetPosition(e.ColumnIndex, e.RowIndex);
}
}
}

Categories