windowsforms-3245-controls-and-libraries-printing-exporting-examples-using-printing-links-how-to-create-a-printable-listview-descendant-implementing-the-iprintable-interface.md
The following example demonstrates how a standard control can be modified to enable its use with the PrintableComponentLink. To accomplish this, the control should implement the IPrintable interface. In this topic, we create a printable descendant of the ListView control, that implements the IPrintable interface.
This tutorial consists of the following steps:
The class declaration and variable definitions are as follows:
public class PrintableListView : System.Windows.Forms.ListView, IPrintable {
private Container components = null;
private IPrintingSystem ps;
private IBrickGraphics graph;
private int offsetx = 0;
private ImageList imageList = null;
// ...
}
Public Class PrintableListView
Inherits System.Windows.Forms.ListView
Implements IPrintable
Private components As Container = Nothing
Private ps As IPrintingSystem
Private graph As IBrickGraphics
Private offsetx As Integer = 0
Private imageList As ImageList = Nothing
' ...
End Class
It is necessary to provide the methods of the IBasePrintable interface. The new control must be initialized. The code also defines which image view, and which image list are used.
The following code implements the IBasePrintable interface, which is the base interface for the IPrintable. It defines the procedures for printing initialization, page area creation and printing completion.
void IBasePrintable.Initialize(IPrintingSystem ps, ILink link) {
this.ps = ps;
imageList = (View == View.SmallIcon || View == View.List || View == View.Details)
? SmallImageList : (View == View.LargeIcon) ? LargeImageList : null;
offsetx = (imageList == null) ? 0 : imageList.ImageSize.Height;
}
void IBasePrintable.Finalize(IPrintingSystem ps, ILink link) {
}
// Constructs different bricks based on section type and information provided by the control.
void IBasePrintable.CreateArea(string areaName, IBrickGraphics graph) {
this.graph = graph;
if (areaName.Equals("PageFooter"))
CreatePageFooter();
else if (areaName.Equals("DetailHeader"))
CreateDetailHeader();
else if (areaName.Equals("Detail"))
CreateDetail();
}
Private Sub Initialize(ByVal ps As IPrintingSystem, ByVal link As ILink) _
Implements IBasePrintable.Initialize
Me.ps = ps
If (View = View.SmallIcon OrElse View = View.List OrElse View = View.Details) Then
imageList = SmallImageList
Else
If (View = View.LargeIcon) Then
imageList = LargeImageList
Else
imageList = Nothing
End If
End If
If (imageList Is Nothing) Then
offsetx = 0
Else
offsetx = imageList.ImageSize.Height
End If
End Sub
Private Overloads Sub Finalize(ByVal ps As IPrintingSystem, ByVal link As ILink) _
Implements IBasePrintable.Finalize
End Sub
' Constructs different bricks based on section type and information provided by the control.
Private Sub CreateArea(ByVal areaName As String, ByVal graph As IBrickGraphics) _
Implements IBasePrintable.CreateArea
Me.graph = graph
If areaName.Equals("PageFooter") Then
CreatePageFooter()
ElseIf areaName.Equals("DetailHeader") Then
CreateDetailHeader()
ElseIf areaName.Equals("Detail") Then
CreateDetail()
End If
End Sub
Then, we have to provide methods for invoking and using the Printable Component Property Editor form and the corresponding help system.
The following code contains procedures which define and invoke the printable component Property Editor form and help system.
// Enables the Property Editor form.
bool IPrintable.HasPropertyEditor() {
return true;
}
// Overrides the corresponding property and specifies the Property Editor to display.
UserControl IPrintable.PropertyEditorControl {
get {
UserControl ctrl = new UserControl();
return ctrl;
}
}
// Enables the help system for the Property Editor.
bool IPrintable.SupportsHelp() {
return false;
}
// Invokes the help system for the Property editor.
void IPrintable.ShowHelp() {
}
// Determines whether intersected bricks are created by this link.
bool IPrintable.CreatesIntersectedBricks {
get { return true; }
}
// Applies all changes made by the Property Editor.
void IPrintable.AcceptChanges() {
}
// Cancels changes made by the user in the Property editor.
void IPrintable.RejectChanges() {
}
' Enables the Property Editor form.
Private Function HasPropertyEditor() As Boolean Implements IPrintable.HasPropertyEditor
Return True
End Function
' Overrides the corresponding property and specifies the Property Editor to display.
Private ReadOnly Property PropertyEditorControl() As UserControl _
Implements IPrintable.PropertyEditorControl
Get
Dim ctrl As New UserControl()
Return ctrl
End Get
End Property
' Enables the help system for the Property Editor.
Private Function SupportsHelp() As Boolean Implements IPrintable.SupportsHelp
Return False
End Function
' Invokes the help system for the Property editor.
Private Sub ShowHelp() Implements IPrintable.ShowHelp
End Sub
' Determines whether intersected bricks are created by this link.
Private ReadOnly Property CreatesIntersectedBricks() As Boolean _
Implements IPrintable.CreatesIntersectedBricks
Get
Return True
End Get
End Property
' Applies all changes made by the Property Editor.
Private Sub AcceptChanges() Implements IPrintable.AcceptChanges
End Sub
' Cancels changes made by the user in the Property editor.
Private Sub RejectChanges() Implements IPrintable.RejectChanges
End Sub
The code for drawing the bricks.
The following code implements methods for drawing bricks. The ps variable represents the current PrintingSystem. The IBrick.SetProperties method is used to specify the brick properties via the IBrick interface.
private IBrick DrawBrick(string typeName, RectangleF rect) {
IBrick brick = ps.CreateBrick(typeName);
return graph.DrawBrick(brick, rect);
}
private IBrick DrawBrick(string typeName, object[,] properties, RectangleF rect) {
IBrick brick = ps.CreateBrick(typeName);
brick.SetProperties(properties);
return graph.DrawBrick(brick, rect);
}
Private Function DrawBrick(ByVal typeName As String, ByVal rect As RectangleF) As IBrick
Dim brick As IBrick = ps.CreateBrick(typeName)
Return graph.DrawBrick(brick, rect)
End Function
Private Function DrawBrick(ByVal typeName As String, ByVal properties(,) As Object, _
ByVal rect As RectangleF) As IBrick
Dim brick As IBrick = ps.CreateBrick(typeName)
brick.SetProperties(properties)
Return graph.DrawBrick(brick, rect)
End Function
And, the code for the main method of report generation, which creates the report’s contents.
The following code implements the CreateDetail method for the printable descendant of the ListView control. It populates the detail section of a report with data obtained from the control.
It calls the CreateDetails method, if the printable ListView control is in Detail mode. The code for other modes is omitted to shorten and simplify the example.
The brickGraph variable represents the IBrickGraphics interface. The code draws TextBrick objects for text items in the List View and ImageBrick objects for list view icons. Brick properties are accessed via the IBrick.SetProperties method provided by the IBrick interface.
private void CreateDetail() {
if (View == View.Details) CreateDetails();
else if (View == View.LargeIcon || View == View.SmallIcon || View == View.List)
CreateIcons();
}
private void CreateDetails() {
Rectangle r = Rectangle.Empty;
StringFormat sf = new StringFormat(StringFormatFlags.NoWrap | StringFormatFlags.LineLimit);
sf.LineAlignment = StringAlignment.Near;
graph.DefaultBrickStyle.StringFormat = new BrickStringFormat(sf);
graph.DefaultBrickStyle.BackColor = SystemColors.Window;
graph.DefaultBrickStyle.BorderColor = SystemColors.Control;
graph.DefaultBrickStyle.Sides = GridLines ? BorderSide.All : BorderSide.None;
Point pt = Point.Empty;
if (Items.Count > 0) {
Rectangle bounds = Items[0].Bounds;
pt = bounds.Location;
pt.Y -= 3;
}
for (int i = 0; i < Items.Count; i++) {
ListViewItem item = Items[i];
graph.DefaultBrickStyle.Font = item.Font;
graph.DefaultBrickStyle.BackColor = item.BackColor;
graph.DefaultBrickStyle.ForeColor = item.ForeColor;
r = item.Bounds;
r.Offset(-pt.X, -pt.Y);
for (int j = 0; j < Columns.Count; j++) {
ColumnHeader column = Columns[j];
r.Width = column.Width;
if (j == 0 && imageList != null) {
DrawBrick("VisualBrick", r);
DrawDetailImage(item, r);
DrawDetailText(item, r);
} else {
TextBrick brick = (TextBrick)DrawBrick("TextBrick", r);
brick.Text = item.SubItems[j].Text;
}
r.Offset(r.Width, 0);
}
}
}
private void CreateIcons() {
graph.DefaultBrickStyle.BackColor = Color.Transparent;
graph.DefaultBrickStyle.BorderColor = Color.Black;
graph.DefaultBrickStyle.Sides = BorderSide.None;
Size imageSize = Size.Empty;
if (offsetx != 0) {
for (int i = 0; i < Items.Count; i++) {
ListViewItem item = Items[i];
int index = item.ImageIndex;
Image image = imageList.Images[index];
Rectangle r = item.Bounds;
imageSize = imageList.ImageSize;
r.Size = imageSize;
if (index < 0)
DrawBrick("VisualBrick", r);
else {
ImageBrick brick = (ImageBrick)DrawBrick("ImageBrick", r);
brick.Image = image;
}
}
offsetx += 3;
}
graph.DefaultBrickStyle.StringFormat = new BrickStringFormat(StringFormatFlags.LineLimit,
StringAlignment.Near, StringAlignment.Near);
for (int i = 0; i < Items.Count; i++) {
ListViewItem item = Items[i];
graph.DefaultBrickStyle.Font = item.Font;
graph.DefaultBrickStyle.BackColor = (item.BackColor == SystemColors.Window)
? Color.Transparent : item.BackColor;
graph.DefaultBrickStyle.ForeColor = item.ForeColor;
RectangleF r = RectangleF.Empty;
r.Size = MeasureString(item.Text);
if (r.Width > 59) {
r.Width = 59;
r.Height = 29;
}
r.X = item.Bounds.Left;
if (item.Bounds.Width > item.Bounds.Height) r.X += imageSize.Width;
r.Y = item.Bounds.Bottom - r.Height;
TextBrick brick = (TextBrick)DrawBrick("TextBrick", r);
brick.Text = item.Text;
}
}
private void DrawDetailImage(ListViewItem item, Rectangle bounds) {
int index = item.ImageIndex;
if (index < 0) return;
Rectangle r = bounds;
r.Size = imageList.ImageSize;
r.Offset(2, (bounds.Height - r.Height) / 2);
IBrick brick = DrawBrick("ImageBrick", r);
brick.SetProperties(new object[,] { { "Image", imageList.Images[index] },
{ "Sides", BorderSide.None }, { "BackColor", Color.Transparent } });
}
private void DrawDetailText(ListViewItem item, Rectangle bounds) {
Rectangle r = bounds;
r.Width = bounds.Width - (imageList.ImageSize.Width + 2);
r.Offset(bounds.Width - r.Width, 0);
IBrick brick = DrawBrick("TextBrick", r);
brick.SetProperties(new object[,] { { "Text", item.SubItems[0].Text },
{ "Sides", BorderSide.None }, { "BackColor", Color.Transparent } });
}
private Rectangle GetCellBounds(int pi, int pj) {
Rectangle r = Rectangle.Empty;
for (int i = 0; i < pi; i++)
r.X += Columns[i].Width;
r.Y += Font.Height + 4;
for (int i = 0; i < pj; i++)
r.Y += Items[i].Bounds.Y;
r.Width = Columns[pi].Width;
r.Height = Items[pj].Bounds.Height;
return r;
}
private SizeF MeasureString(string text) {
Graphics graphics = Graphics.FromHwnd(new IntPtr(0));
SizeF size = graphics.MeasureString(text, Font);
size.Width += 2; // Border size
size.Height += 2; // Border size
graphics.Dispose();
return size;
}
Private Sub CreateDetail()
If View = View.Details Then
CreateDetails()
ElseIf View = View.LargeIcon OrElse View = View.SmallIcon OrElse View = View.List Then
CreateIcons()
End If
End Sub
Private Sub CreateDetails()
Dim r As Rectangle = Rectangle.Empty
Dim sf As New StringFormat(StringFormatFlags.NoWrap Or StringFormatFlags.LineLimit)
sf.LineAlignment = StringAlignment.Near
graph.DefaultBrickStyle.StringFormat = New BrickStringFormat(sf)
graph.DefaultBrickStyle.BackColor = SystemColors.Window
graph.DefaultBrickStyle.BorderColor = SystemColors.Control
If GridLines Then
graph.DefaultBrickStyle.Sides = BorderSide.All
Else
graph.DefaultBrickStyle.Sides = BorderSide.None
End If
Dim pt As Point = Point.Empty
If Items.Count > 0 Then
Dim bounds As Rectangle = Items(0).Bounds
pt = bounds.Location
pt.Y -= 3
End If
For i As Integer = 0 To Items.Count - 1
Dim item As ListViewItem = Items(i)
graph.DefaultBrickStyle.Font = item.Font
graph.DefaultBrickStyle.BackColor = item.BackColor
graph.DefaultBrickStyle.ForeColor = item.ForeColor
r = item.Bounds
r.Offset(-pt.X, -pt.Y)
For j As Integer = 0 To Columns.Count - 1
Dim column As ColumnHeader = Columns(j)
r.Width = column.Width
If j = 0 AndAlso imageList IsNot Nothing Then
DrawBrick("VisualBrick", r)
DrawDetailImage(item, r)
DrawDetailText(item, r)
Else
Dim brick As TextBrick = CType(DrawBrick("TextBrick", r), TextBrick)
brick.Text = item.SubItems(j).Text
End If
r.Offset(r.Width, 0)
Next j
Next i
End Sub
Private Sub CreateIcons()
graph.DefaultBrickStyle.BackColor = Color.Transparent
graph.DefaultBrickStyle.BorderColor = Color.Black
graph.DefaultBrickStyle.Sides = BorderSide.None
Dim imageSize As Size = Size.Empty
If offsetx <> 0 Then
For i As Integer = 0 To Items.Count - 1
Dim item As ListViewItem = Items(i)
Dim index As Integer = item.ImageIndex
Dim image As Image = imageList.Images(index)
Dim r As Rectangle = item.Bounds
imageSize = imageList.ImageSize
r.Size = imageSize
If index < 0 Then
DrawBrick("VisualBrick", r)
Else
Dim brick As ImageBrick = CType(DrawBrick("ImageBrick", r), ImageBrick)
brick.Image = image
End If
Next i
offsetx += 3
End If
graph.DefaultBrickStyle.StringFormat = New BrickStringFormat(StringFormatFlags.LineLimit, _
StringAlignment.Near, StringAlignment.Near)
For i As Integer = 0 To Items.Count - 1
Dim item As ListViewItem = Items(i)
graph.DefaultBrickStyle.Font = item.Font
If (item.BackColor = SystemColors.Window) Then
graph.DefaultBrickStyle.BackColor = Color.Transparent
Else
graph.DefaultBrickStyle.BackColor = item.BackColor
End If
graph.DefaultBrickStyle.ForeColor = item.ForeColor
Dim r As RectangleF = RectangleF.Empty
r.Size = MeasureString(item.Text)
If r.Width > 59 Then
r.Width = 59
r.Height = 29
End If
r.X = item.Bounds.Left
If item.Bounds.Width > item.Bounds.Height Then
r.X += imageSize.Width
End If
r.Y = item.Bounds.Bottom - r.Height
Dim brick As TextBrick = CType(DrawBrick("TextBrick", r), TextBrick)
brick.Text = item.Text
Next i
End Sub
Private Sub DrawDetailImage(ByVal item As ListViewItem, ByVal bounds As Rectangle)
Dim index As Integer = item.ImageIndex
If index < 0 Then
Return
End If
Dim r As Rectangle = bounds
r.Size = imageList.ImageSize
r.Offset(2, (bounds.Height - r.Height) Mod 2)
Dim brick As IBrick = DrawBrick("ImageBrick", r)
brick.SetProperties(New Object(,) {{"Image", imageList.Images(index)}, _
{"Sides", BorderSide.None}, {"BackColor", Color.Transparent}})
End Sub
Private Sub DrawDetailText(ByVal item As ListViewItem, ByVal bounds As Rectangle)
Dim r As Rectangle = bounds
r.Width = bounds.Width - (imageList.ImageSize.Width + 2)
r.Offset(bounds.Width - r.Width, 0)
Dim brick As IBrick = DrawBrick("TextBrick", r)
brick.SetProperties(New Object(,) {{"Text", item.SubItems(0).Text}, _
{"Sides", BorderSide.None}, {"BackColor", Color.Transparent}})
End Sub
Private Function GetCellBounds(ByVal pi As Integer, ByVal pj As Integer) As Rectangle
Dim r As Rectangle = Rectangle.Empty
For i As Integer = 0 To pi - 1
r.X += Columns(i).Width
Next i
r.Y += Font.Height + 4
For i As Integer = 0 To pj - 1
r.Y += Items(i).Bounds.Y
Next i
r.Width = Columns(pi).Width
r.Height = Items(pj).Bounds.Height
Return r
End Function
Private Function MeasureString(ByVal text As String) As SizeF
Dim graphics As Graphics = graphics.FromHwnd(New IntPtr(0))
Dim size As SizeF = graphics.MeasureString(text, Font)
size.Width += 2 ' Border size
size.Height += 2 ' Border size
graphics.Dispose()
Return size
End Function
In order to print the date and page number at the bottom of each page, the code for the page footer generation should be added.
The following code implements the CreatePageFooter method, which is used to generate a report’s page footer. The resulting MarginalFooter is shown in the picture.
private void CreatePageFooter() {
string format = "Page {0} of {1}";
Font font = new Font("Arial", 9);
graph.DefaultBrickStyle = new BrickStyle(BorderSide.None, 1,
Color.Black, Color.Transparent, Color.Black, font,
new BrickStringFormat(StringAlignment.Center, StringAlignment.Center));
float height = font.Height + 2;
RectangleF r = new RectangleF(0, 0, 0, height);
DrawBrick("PageInfoBrick", new object[,] { {"PageInfo",PageInfo.NumberOfTotal},
{"Format",format}, {"Alignment",BrickAlignment.Far}, {"AutoWidth",true} }, r);
DrawBrick("PageInfoBrick", new object[,] { {"Alignment",BrickAlignment.Near},
{"AutoWidth",true}, {"PageInfo",PageInfo.DateTime} }, r);
}
Private Sub CreatePageFooter()
Dim format As String = "Page {0} of {1}"
Dim font As New Font("Arial", 9)
graph.DefaultBrickStyle = New BrickStyle(BorderSide.None, 1, Color.Black, Color.Transparent, _
Color.Black, font, New BrickStringFormat(StringAlignment.Center, StringAlignment.Center))
Dim height As Single = font.Height + 2
Dim r As New RectangleF(0, 0, 0, height)
DrawBrick("PageInfoBrick", New Object(,) {{"PageInfo", PageInfo.NumberOfTotal}, _
{"Format", format}, {"Alignment", BrickAlignment.Far}, {"AutoWidth", True}}, r)
DrawBrick("PageInfoBrick", New Object(,) {{"Alignment", BrickAlignment.Near}, _
{"AutoWidth", True}, {"PageInfo", PageInfo.DateTime}}, r)
End Sub
The next step is to create the detail header. This section is repeated on each page, and is similar to the table header. In this implementation, it is drawn only if the list view is equal to the Details.
The following code constructs a header for the report’s detail area. It contains column headings for the list of items, representing the Detail view mode of the ListView control.
private void CreateDetailHeader() {
if (View != View.Details) return;
StringFormat sf = new StringFormat(StringFormatFlags.NoWrap);
sf.LineAlignment = StringAlignment.Near;
graph.DefaultBrickStyle = new BrickStyle(BorderSide.All, 1, Color.Black,
SystemColors.Control, SystemColors.ControlText, this.Font, new BrickStringFormat(sf));
Rectangle r = Rectangle.Empty;
r.Y = 1;
for (int i = 0; i < Columns.Count; i++) {
r.Width = Columns[i].Width;
r.Height = Font.Height + 4;
TextBrick brick = (TextBrick)DrawBrick("TextBrick", r);
brick.Text = Columns[i].Text;
r.Offset(Columns[i].Width, 0);
}
}
Private Sub CreateDetailHeader()
If View <> View.Details Then
Return
End If
Dim sf As New StringFormat(StringFormatFlags.NoWrap)
sf.LineAlignment = StringAlignment.Near
graph.DefaultBrickStyle = New BrickStyle(BorderSide.All, 1, Color.Black, _
SystemColors.Control, SystemColors.ControlText, Me.Font, New BrickStringFormat(sf))
Dim r As Rectangle = Rectangle.Empty
r.Y = 1
For i As Integer = 0 To Columns.Count - 1
r.Width = Columns(i).Width
r.Height = Font.Height + 4
Dim brick As TextBrick = CType(DrawBrick("TextBrick", r), TextBrick)
brick.Text = Columns(i).Text
r.Offset(Columns(i).Width, 0)
Next i
End Sub
And, that’s all! Finally, the control, which can be printed via the XtraPrinting Library , is created.
The original ListView control and a report, generated via the XtraPrinting Library , are illustrated below.
See Also