Back to Devexpress

How to: Create a Printable ListView Descendant Implementing the IPrintable Interface

windowsforms-3245-controls-and-libraries-printing-exporting-examples-using-printing-links-how-to-create-a-printable-listview-descendant-implementing-the-iprintable-interface.md

latest23.4 KB
Original Source

How to: Create a Printable ListView Descendant Implementing the IPrintable Interface

  • Sep 26, 2023
  • 13 minutes to read

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:

Base Declaration

The class declaration and variable definitions are as follows:

csharp
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;
    // ...

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

Interfaces Implementation

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.

csharp
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();
}
vb
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.

csharp
// 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() {
}
vb
' 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

Drawing Methods

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.

csharp
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);
}
vb
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.

csharp
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;
}
vb
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.

csharp
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);
}
vb
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.

csharp
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);
    }
}
vb
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

Get the Result

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

How to: Create a Custom Link to Print a ListView Control