|
Hi Steve,
I just wanted to let you know about a small bug I ran across.
Set the DGVPrinter object's ProportionalColumns property to false.
Set TitleAlignment, SubTitleAlignment and FooterAlignment to Center.
When the graphics are rendered in the private "printsection" routine, it appears to use the width of the dgv object using ProportionalColumns value of true. The dgv is centered, but the Title, SubTitle and Footer all appear to be centered on an object that would be further to the right. My guess is they are positioned relative to the dgv's graphic instead of the page's graphic.
If I understood graphics a little better, I'd get it fixed and show you my solution. But I am *not* very good with graphics right now.
Joe Pool
|
|
|
|
|
Hmmmm .... I'm not able to reproduce anything like this. The centering of the Title, SubTitle and Footer are relative to the current page's margins and the calculated PrintWidth . You can see the calls in printDoc_PrintPage. Here's the Title print as an example:
printsection(e.Graphics, ref printpos, title, titlefont,
titlecolor, titleformat, overridetitleformat,
pagesets[currentpageset].margins);
The last parameter sends the current page's margins. The margins are used as the "left" position and the global "PrintWidth " value is used for the area within which to center the text. Whether or not "porportional columns" is turned on or off is irrelevant at this point.
There is one thing that might be affecting this - in printRow, the area to print in is adjusted to the smaller of PrintWidth or the width of the columns for that pageset (pageset.coltotalwidth ) so the area that the columns are printed in can actually be *different* from the area that the header and footer text is centered in.
At this point, my guess is that something else is going on. Can you give me more information on what you're trying to do and what's actually coming out the other end?
Steve G.
|
|
|
|
|
I'll follow up this with a private message.
I'd be happy to work with you on this or even help you with it in any way that I can.
I can email you the PDF output of my printout.
Joe
|
|
|
|
|
Sure - I'd like to see the PDF, along with your settings for the DGVPrinter object. My email id is aureolin on the hotmail mail service.
Steve G.
|
|
|
|
|
Just fyi, your XML code comments are throwing some compile-time warnings.
DGVPrinter.cs(932,26): error CS1572: Warning as Error: XML comment on 'DGVPrinterHelper.DGVPrinter.SetupPrint()' has a param tag for 'pd', but there is no parameter by that name
DGVPrinter.cs(1145,26): error CS1572: Warning as Error: XML comment on 'DGVPrinterHelper.DGVPrinter.buildstringformat(ref System.Drawing.StringFormat, System.Windows.Forms.DataGridViewCellStyle, System.Drawing.StringAlignment, System.Drawing.StringAlignment, System.Drawing.StringFormatFlags, System.Drawing.StringTrimming)' has a param tag for 'overrideformat', but there is no parameter by that name
DGVPrinter.cs(1147,57): error CS1573: Warning as Error: Parameter 'format' has no matching param tag in the XML comment for 'DGVPrinterHelper.DGVPrinter.buildstringformat(ref System.Drawing.StringFormat, System.Windows.Forms.DataGridViewCellStyle, System.Drawing.StringAlignment, System.Drawing.StringAlignment, System.Drawing.StringFormatFlags, System.Drawing.StringTrimming)' (but other parameters do)
DGVPrinter.cs(1148,56): error CS1573: Warning as Error: Parameter 'linealignment' has no matching param tag in the XML comment for 'DGVPrinterHelper.DGVPrinter.buildstringformat(ref System.Drawing.StringFormat, System.Windows.Forms.DataGridViewCellStyle, System.Drawing.StringAlignment, System.Drawing.StringAlignment, System.Drawing.StringFormatFlags, System.Drawing.StringTrimming)' (but other parameters do)
DGVPrinter.cs(1149,28): error CS1573: Warning as Error: Parameter 'trim' has no matching param tag in the XML comment for 'DGVPrinterHelper.DGVPrinter.buildstringformat(ref System.Drawing.StringFormat, System.Windows.Forms.DataGridViewCellStyle, System.Drawing.StringAlignment, System.Drawing.StringAlignment, System.Drawing.StringFormatFlags, System.Drawing.StringTrimming)' (but other parameters do)
DGVPrinter.cs(1726,26): error CS1572: Warning as Error: XML comment on 'DGVPrinterHelper.DGVPrinter.printsection(System.Drawing.Graphics, ref float, string, System.Drawing.Font, System.Drawing.Color, System.Drawing.StringFormat, bool, System.Drawing.Printing.Margins)' has a param tag for 'alignment', but there is no parameter by that name
DGVPrinter.cs(1727,26): error CS1572: Warning as Error: XML comment on 'DGVPrinterHelper.DGVPrinter.printsection(System.Drawing.Graphics, ref float, string, System.Drawing.Font, System.Drawing.Color, System.Drawing.StringFormat, bool, System.Drawing.Printing.Margins)' has a param tag for 'flags', but there is no parameter by that name
DGVPrinter.cs(1731,50): error CS1573: Warning as Error: Parameter 'format' has no matching param tag in the XML comment for 'DGVPrinterHelper.DGVPrinter.printsection(System.Drawing.Graphics, ref float, string, System.Drawing.Font, System.Drawing.Color, System.Drawing.StringFormat, bool, System.Drawing.Printing.Margins)' (but other parameters do)
|
|
|
|
|
Thanks for the note - I've fixed these, they'll be included in the next update.
Steve G.
|
|
|
|
|
First, I just want to say thanks for this.
There are, however, a couple of suggestions I would like to make for future releases.
First, could you add a field for the owning form, with the default set to null?
public DGVPrinter(Form owner)
{
this.owner = owner;
}
Second, could you add the ability to set the default zoom on the preview? On my desktop (with a screen resolution of 1600 by 1200 pixels), the preview always comes up too small to see the report.
public void PrintPreviewDataGridView(DataGridView dgv)
{
this.dgv = dgv;
if (DialogResult.OK == DisplayPrintDialog())
{
SetupPrint();
PrintPreviewDialog ppdialog = new PrintPreviewDialog();
ppdialog.UseAntiAlias = true;
ppdialog.PringPreviewControl.Zoom = 1.0;
if (owner == null)
{
ppdialog.ShowDialog();
} else
{
ppdialog.MdiParent = owner;
ppdialog.Show();
}
}
}
I make these modifications for the copies that I keep on my system, but it makes it more work to update to your newer versions.
I thought I'd share what I use with you. Use it if you like; ditch it if you don't.
Thanks for the tool!
|
|
|
|
|
It took a day or two to get the changes through CodeProject's editing process, but the requested changes are in place. (It also took me a day or two to send this notice as my wife and no. 2 son were sick with the flu!)
Anyway, I've added both requested properties, and updated the print preview display to size itself more appropriately. DGVPrinter now attempts to size the print preview display dialog so that the full page is visible, whether in portrait or landscape mode.
Have fun!
Steve G.
|
|
|
|
|
Thanks Steve!
I feel good knowing that I was able to contribute.
I wish the best for your family's speedy recovery. Chicken soup, saltine crackers, and 7-Up are all this Daddy serves to his sick family.
Regards,
Joe
|
|
|
|
|
Thanks, aureolin, for making this available. It has fit in quite nicely with a project I'm working on.
One thing that was driving me crazy, though, was I could not get the Titles & subtitles to be aligned correctly on the page. The were, instead, somehow tied to the table location which caused the Titles to look bad and even cut-off. Changing this code:
if (!String.IsNullOrEmpty(title))
printsection(e.Graphics, ref printpos, title, titlefont,
titlecolor, titleformat, overridetitleformat,
pagesets[currentpageset].margins);
if (!String.IsNullOrEmpty(subtitle))
printsection(e.Graphics, ref printpos, subtitle, subtitlefont,
subtitlecolor, subtitleformat, overridesubtitleformat,
pagesets[currentpageset].margins);
to this:
if (!String.IsNullOrEmpty(title))
printsection(e.Graphics, ref printpos, title, titlefont,
titlecolor, titleformat, overridetitleformat,
if (!String.IsNullOrEmpty(subtitle))
printsection(e.Graphics, ref printpos, subtitle, subtitlefont,
subtitlecolor, subtitleformat, overridesubtitleformat,
solved that problem and now my partners & I are happy with the output.
Thanks again!
Jeff M
|
|
|
|
|
Hi and thanks a mil for this code.
I would like to know how to fit this dgv in the desired position on a page.
I would like to print about 10 lines of text and a small graphic and then at about mid page the DGV.
Thanks in advance for any tips on how to use your class.
|
|
|
|
|
I think you may need to revise how you're approaching the problem. If you look at the PrintDoc_PrintPage routine and trace it down into the printsection routine you can see that the DGV object actually prints to a Graphics object acquired from the PrintEventArgs. What this means is that you can't just imbed the printout into another process.
The DGV object does have a way to set title lines, etc. in the header and footer and print them properly, but it doesn't have a way to imbed a graphic. To print things the way you're wanting to you can try overriding the "print header" section in PrintDoc_PrintPage. You'll also have to handle tracking the vertical space that your printing uses (that's the printpos variable) so that you don't have the printout running off the bottom of the page.
Hope this helps!
Steve G.
|
|
|
|
|
Hi and thanks for your reply.
I have only been programming for 4 months so what you write is way over my league.
I have looked at the sections you refer to and see the section that prints to a Graphics object.
Prior to writing to you I had tried to feed the TITLE and SUBTITLE properties some extra text, it works but the spacing makes my result look cramped.
I am so close to getting my app's printpage sorted out, I have the text and image printing fine in the top half of a page...if only I could get the DGV to print in the right spot I would be set....even willing to give priority to the DGV and let it print where it wants to but then I need to figure out how to get the text in....the image would be a bonus.
Any spoonfeeding would be greatly appreciated.
Using basic printdocument and printpage with some dumb code :
writer.WriteLine("About ten lines");
e.Graphics.DrawImage(pictureBox1.Image, 450, 250);
Thanks again
|
|
|
|
|
>> Only been programming for 4 months ...
Yikes!! You're pretty ambitious for a beginning programmer - and you seem to have come a long long way in short period of time.
What I'm talking about is you putting e.Graphics.DrawString() and e.Graphics.DrawImage() calls to print your stuff in the "print header" section, near where (instead of?) the title and subtitle are printed. The one thing you'll need to do is update the printpos variable to 'consume' the vertical space used for your header, both print and white space.
FYI, you can't use Writer.WriteLine to print to the e.Graphics object, that's why I'm suggesting the DrawString call. The DrawString method will give you the control that you need to get things looking good on the page.
You'll also need to create a couple of custom properties to pass the text and image(s) you want to print into the DGV object.
Hope this helps!!
Steve G.
|
|
|
|
|
..."seem to have come a long long way"..."seem to" is the correct way of putting it, cos in reality I am far from understanding what custom properties and updating the printpos variable are all about !
But nevertheless thanks for your time and once again thanks for the code.
Hope to get out of my newbie clothes soon cos I am getting fustrated by my lack of understanding things however every small step till now has been a matter of celebration.
Ciao
|
|
|
|
|
Good for you! You have come a long way - farther than you think.
Anyway, I can answer two things for you:
1) What does that "printpos" variable do? If you look at a sheet of paper, the printing process happens from the top down. In order to print a lines that are spaced nicely and don't simply print one on top of another, you have to move the "print position" (ergo 'printpos' for the variable name) down the page. Remember, during the print process DGVPrinter is actually 'drawing' everything on a blank slate, and it has complete control. DGVPrinter has to account for whitespace, left and right positioning, scaling to fit items in a page, leaving room for header and footer items, etc. So, the printpos variable is my 'where am I' indicator, counting vertical space used as the drawing of items moves down the page.
2) Custom properties. This one is a bit simpler. Take a look at the top of the DGVPrinter code and you'll find a lot of things that look like this:
private String printerName;
public String PrinterName
{
get { return printerName; }
set { printerName = value; }
}
This is how you define a "property" in C# (but you knew that). If you wanted to print a graphic in the header, you would need to add a property that would allow you to set a bitmap object (rather than a String in this example). This will make your image available to the code down inside DGVPrinter so that it can draw it into the header using the DrawImage call to write the bitmap into the output.
Hope this helps!
Steve G.
|
|
|
|
|
Hi aureolin,
Great work! One thing I would like to get it to work is to fit in one page when there are more than 15 or 20 columns, of course the rows don't have to be in one page, just columns. is this possible? Thanks very much for your time and help.
Kind regards,
Pete
|
|
|
|
|
Hi there, and Thanks!
You can set the widths of the columns to specific values. So, to make everything fit on one page, simply set the columns widths until you get the look you're trying for.
Steve G.
|
|
|
|
|
First, nice work! I don't know why MS would think that printing data would not be a common practice....
The only problem I'm having is that my DGV has too many columns to fit on a page (100 total columns).
Is there a way to print multiple pages to accommodate the columns like there is for multiple rows?
Thanks again for the code!
John
|
|
|
|
|
DGV Printer will automatically handle the case where there are too many columns (or the columns widths are too wide) to fit on one page. The overflow will print on a separate page. Try it in print preview and see!
Steve G.
|
|
|
|
|
Steve,
I tried that, but they are just cut off. I am using the VB version of your code... thanks for any help you can provide... Here's my instantiating code:
Dim dgvprint As New DGVPrinterHelper.DGVPrinter
dgvprint.Title = "RECORDS SEARCH RESULTS"
dgvprint.SubTitle = "BIKES"
dgvprint.PrintMargins.Bottom = 4
dgvprint.PrintMargins.Top = 4
dgvprint.PrintMargins.Left = 4
dgvprint.PrintMargins.Right = 4
dgvprint.PageSettings.Landscape = True
dgvprint.PrintPreviewDataGridView(dgvResults)
|
|
|
|
|
Remember that the multi-page layout is printed as follows
page 1 of 100 - part 1
page 2 of 100 - part 1
page 3 of 100 - part 1
...
page 1 of 100 - part 2
page 2 of 100 - part 2
page 3 of 100 - part 2
The best and easiest way to quickly see if you're getting the multi-page layout is to look at the page numbers. If you see "Part 1" in the page number, the DGV printer has formatted your columns across multiple pages.
Steve G.
|
|
|
|
|
I think I found the issue....
I don't see anything about Part1 in the page number. I included totalpagenumber on the report and it says "Page 1 of 1". I also tried directly printing it to the printer and that didn't work.
I stepped through the code and the PageDef gets the correct number of columns (169).
In the MeasurePrintArea method, there's a for loop that seems to be causing the issue. Here's your code:
' split columns into page sets
Dim columnwidth As Single
For i = 0 To colstoprint.Count - 1
' get initial column width
If colwidthsoverride(i) >= 0 Then columnwidth = colwidthsoverride(i)
' See if the column width takes us off the page - Except for the
' first column. This will prevent printing an empty page!! Otherwise,
' columns longer than the page width are printed on their own page
If printWidth < (pagesets(pset).coltotalwidth + columnwidth) AndAlso i <> 0 Then
pagesets.Add(New PageDef(m_printmargins, colstoprint.Count))
pset += 1
' Account for row headers
pagesets(pset).coltotalwidth = rowheaderwidth
End If
' update page set definition
pagesets(pset).colstoprint.Add(colstoprint(i))
pagesets(pset).colwidths.Add(colwidths(i))
pagesets(pset).colwidthsoverride.Add(colwidthsoverride(i))
pagesets(pset).coltotalwidth += columnwidth
Next
The If statement:
If printWidth < (pagesets(pset).coltotalwidth + columnwidth) AndAlso i <> 0 Then
pagesets.Add(New PageDef(m_printmargins, colstoprint.Count))
pset += 1
' Account for row headers
pagesets(pset).coltotalwidth = rowheaderwidth
End If
Never evaluates to true. It looks like the columnwidth is always 0. I don't see where it's being set. I see the declaration as a single, but then it's never set. I changed the following lines:
'FROM
If printWidth < (pagesets(pset).coltotalwidth + columnwidth) AndAlso i <> 0 Then
'TO
If printWidth < (pagesets(pset).coltotalwidth + dgv.Columns(i).Width) AndAlso i <> 0 Then
'FROM
pagesets(pset).coltotalwidth += columnwidth
'TO
pagesets(pset).coltotalwidth += dgv.Columns(i).Width
It may have to do with the override, but I don't see where the columnwidth property is set without an override.
After the changes it works fine? Any ideas where the columnwidth was being set? BTW, after that small change, it works fine. Thanks again for any advice....
John
|
|
|
|
|
Interesting - if you look at the C# version (above) and find that section of 'measureprintarea' you'll find that the columnwidth value is set in the statement immediately preceeding the "if" - but that statement is missing from the fragment you've posted. Here's the relevant code:
float columnwidth;
for (i = 0; i < colstoprint.Count; i++)
{
columnwidth = (colwidthsoverride[i] >= 0)
? colwidthsoverride[i] : colwidths[i];
if (printWidth < (pagesets[pset].coltotalwidth + columnwidth) && i != 0)
{
pagesets.Add(new PageDef(printmargins, colstoprint.Count));
pset++;
pagesets[pset].coltotalwidth = rowheaderwidth;
}
You can see that the columnwidth is set to the width of the current column (or it's override value) just prior to the 'if' statement. The change you're proposing will work as long as you're not using any column width overrides.
Steve G.
|
|
|
|
|
If it helps anyone, I re-worked the MeasurePrintArea function in VB.NET to allow for multiple column printing (and overrides). Thanks for the advice Steve....
Private Sub measureprintarea(ByVal g As Graphics)
Dim i As Integer, j As Integer
rowheights = New List(Of Single)(rowstoprint.Count)
colwidths = New List(Of Single)(colstoprint.Count)
headerHeight = 0
footerHeight = 0
' temp variables
Dim col As DataGridViewColumn
Dim row As DataGridViewRow
'-----------------------------------------------------------------
' measure the page headers and footers, including the grid column header cells
'-----------------------------------------------------------------
' measure the column headers
Dim headerfont As Font = dgv.ColumnHeadersDefaultCellStyle.Font
If headerfont Is Nothing Then
headerfont = dgv.DefaultCellStyle.Font
End If
' set initial column sizes based on column titles
For i = 0 To colstoprint.Count - 1
col = DirectCast(colstoprint(i), DataGridViewColumn)
' deal with overridden col widths
Dim usewidth As Single = 0
If 0 < colwidthsoverride(i) Then
usewidth = colwidthsoverride(i)
Else
usewidth = printWidth
End If
' measure the title for each column, keep widths and biggest height
Dim size As SizeF = g.MeasureString(col.HeaderText, headerfont, New SizeF(usewidth, 2147483647), headercellformat)
colwidths.Add(size.Width)
If colheaderheight < size.Height Then colheaderheight = size.Height
Next
'-----------------------------------------------------------------
' measure the page number
'-----------------------------------------------------------------
If pageno Then
pagenumberHeight = (g.MeasureString("Page", pagenofont, printWidth, m_pagenumberformat)).Height
End If
'-----------------------------------------------------------------
' Calc height of header.
' Header height is height of page number, title, subtitle and height of column headers
'-----------------------------------------------------------------
' note that we dont count the page number height if it's not on a separate line
If pagenumberontop AndAlso Not m_pagenumberonseparateline Then
headerHeight += pagenumberHeight
End If
If Not [String].IsNullOrEmpty(m_title) Then
headerHeight += (g.MeasureString(m_title, m_titlefont, printWidth, m_titleformat)).Height
End If
If Not [String].IsNullOrEmpty(m_subtitle) Then
headerHeight += (g.MeasureString(m_subtitle, m_subtitlefont, printWidth, m_subtitleformat)).Height
End If
headerHeight += colheaderheight
'-----------------------------------------------------------------
' measure the footer, if one is provided. Include the page number if we're printing
' it on the bottom
'-----------------------------------------------------------------
If Not [String].IsNullOrEmpty(m_footer) Then
footerHeight += (g.MeasureString(m_footer, m_footerfont, printWidth, m_footerformat)).Height
End If
' note we don't count the page number height if it's not on a separate line
If Not pagenumberontop AndAlso m_pagenumberonseparateline Then
footerHeight += pagenumberHeight
End If
footerHeight += m_footerspacing
'-----------------------------------------------------------------
' measure the grid to be printed ... this gets us all the row heights
' and an accurate measure of column widths for the printed area
'-----------------------------------------------------------------
For i = 0 To rowstoprint.Count - 1
row = DirectCast(rowstoprint(i), DataGridViewRow)
rowheights.Add(0)
' add row headers if they're visible
If dgv.RowHeadersVisible Then
Dim rhsize As SizeF = g.MeasureString(row.HeaderCell.EditedFormattedValue.ToString(), headerfont)
If rowheaderwidth < rhsize.Width Then rowheaderwidth = rhsize.Width
End If
' calculate widths for each column. We're looking for the largest width needed for
' all the rows of data.
For j = 0 To colstoprint.Count - 1
col = DirectCast(colstoprint(j), DataGridViewColumn)
' access the data to be printed - weird bug: had to move this up here since
' doing this access actually changes the cell's style. ???
Dim datastr As String = row.Cells(col.Index).EditedFormattedValue.ToString()
' get gridview style, and override if we have a set style for this column
Dim currentformat As StringFormat = Nothing
Dim colstyle As DataGridViewCellStyle = Nothing
If ColumnStyles.ContainsKey(col.Name) Then
colstyle = colstyles(col.Name)
' build the cell style and font
buildstringformat(currentformat, colstyle, cellformat.Alignment, cellformat.LineAlignment, cellformat.FormatFlags, cellformat.Trimming)
ElseIf (col.HasDefaultCellStyle) OrElse (row.Cells(col.Index).HasStyle) Then
colstyle = row.Cells(col.Index).InheritedStyle
' build the cell style and font
buildstringformat(currentformat, colstyle, cellformat.Alignment, cellformat.LineAlignment, cellformat.FormatFlags, cellformat.Trimming)
Else
currentformat = cellformat
colstyle = dgv.DefaultCellStyle
End If
' get the raw size of the string.
Dim size As SizeF = g.MeasureString(datastr, colstyle.Font)
' Handle fixed size cells and > printwidth cells where the width of the
' data won't fit. (I.E. need to stretch the row down the page)
If (0 < colwidthsoverride(j)) OrElse (size.Width > printWidth) Then
' set column width
If 0 < colwidthsoverride(j) Then
colwidths(j) = colwidthsoverride(j)
ElseIf size.Width > printWidth Then
colwidths(j) = printWidth
End If
' remeasure the string with the new limits and proper formatting for wrapping.
' Use an absurd height value so that we can get the real number of lines printed
Dim chars As Integer, lines As Integer
size = g.MeasureString(datastr, colstyle.Font, New SizeF(colwidths(j), 2147483647), currentformat, chars, lines)
' set row height
Dim tempheight As Single = size.Height
' lines * size.Height;
If rowheights(i) < tempheight Then rowheights(i) = tempheight
Else
If colwidths(j) < size.Width Then colwidths(j) = size.Width
If rowheights(i) < size.Height Then rowheights(i) = size.Height
End If
Next
Next
'-----------------------------------------------------------------
' Break the columns accross page sets. This is the key to printing
' where the total width is wider than one page.
'-----------------------------------------------------------------
' assume everything will fit on one page
pagesets = New List(Of PageDef)()
pagesets.Add(New PageDef(m_printmargins, colstoprint.Count))
Dim pset As Integer = 0
' Account for row headers
pagesets(pset).coltotalwidth = rowheaderwidth
' split columns into page sets
Dim columnwidth As Single
For i = 0 To colstoprint.Count - 1
' get initial column width
If colwidthsoverride(i) >= 0 Then
columnwidth = colwidthsoverride(i)
Else
columnwidth = colwidths(i)
End If
' See if the column width takes us off the page - Except for the
' first column. This will prevent printing an empty page!! Otherwise,
' columns longer than the page width are printed on their own page
If printWidth < (pagesets(pset).coltotalwidth + columnwidth) AndAlso i <> 0 Then
pagesets.Add(New PageDef(m_printmargins, colstoprint.Count))
pset += 1
' Account for row headers
pagesets(pset).coltotalwidth = rowheaderwidth
End If
' update page set definition
pagesets(pset).colstoprint.Add(colstoprint(i))
pagesets(pset).colwidths.Add(colwidths(i))
pagesets(pset).colwidthsoverride.Add(colwidthsoverride(i))
pagesets(pset).coltotalwidth += columnwidth
Next
'-----------------------------------------------------------------
' Adjust column widths and table margins for each page
'-----------------------------------------------------------------
For i = 0 To pagesets.Count - 1
AdjustPageSets(g, pagesets(i))
Next
End Sub
|
|
|
|
|