xtrareports-1304-feature-guide-to-devexpress-reports-use-report-controls-use-custom-controls-implement-custom-control-from-scratch.md
Follow the steps below to create a custom Progress Bar control.
Create a new class that defines a custom control. Derive the class from the base XRControl class. Implement properties, methods, events, and other members:
using DevExpress.XtraPrinting;
using DevExpress.XtraReports;
using DevExpress.XtraReports.UI;
using System.ComponentModel;
using System.Drawing;
using DevExpress.Utils.Serializing;
using DevExpress.XtraReports.Expressions;
// ...
[ToolboxItem(true)]
[DefaultBindableProperty("Position")]
public class ProgressBar : XRControl {
// Implement a static constructor as shown below to add the
// "Position" property to the property grid's "Expressions" tab.
static ProgressBar() {
// Specify an array of events in which the property should be available.
string[] eventNames = new string[] { "BeforePrint" };
// Specify the property position in the property grid's "Expressions" tab.
// 0 - first, 1000 - last.
int position = 0;
// Specify an array of the property's inner properties.
string[] nestedBindableProperties = null;
// Specify the property's category in the property grid's "Expressions" tab.
// The empty string corresponds to the root category.
string scopeName = "";
// Create and set a description for the "Position" property.
ExpressionBindingDescription description = new ExpressionBindingDescription(
eventNames, position, nestedBindableProperties, scopeName
);
ExpressionBindingDescriptor.SetPropertyDescription(
typeof(ProgressBar), nameof(Position), description
);
}
private float position = 0;
private float maxValue = 100;
public ProgressBar() {
this.ForeColor = SystemColors.Highlight;
}
[DefaultValue(100)]
[Description("The maximum value of the bar position.")]
[DisplayName("Max Value")]
[Category("Parameters")]
[XtraSerializableProperty]
public float MaxValue {
get { return this.maxValue; }
set {
if (value <= 0) return;
this.maxValue = value;
}
}
[DefaultValue(0)]
[Description("The current bar position.")]
[DisplayName("Position")]
[Category("Parameters")]
[XtraSerializableProperty]
public float Position {
get { return this.position; }
set {
if (value < 0 || value > maxValue)
return;
this.position = value;
}
}
// ...
}
Imports DevExpress.XtraPrinting
Imports DevExpress.XtraReports
Imports DevExpress.XtraReports.UI
Imports System.ComponentModel
Imports System.Drawing
Imports DevExpress.Utils.Serializing
Imports DevExpress.XtraReports.Expressions
' ...
<ToolboxItem(True), DefaultBindableProperty("Position")>
Public Class ProgressBar
Inherits XRControl
' Implement a static constructor as shown below to add the
' "Position" property to the property grid's "Expressions" tab.
Shared Sub New()
' Specify an array of events in which the property should be available.
Dim eventNames() As String = { "BeforePrint" }
' Specify the property position in the property grid's "Expressions" tab.
' 0 - first, 1000 - last.
Dim position_Renamed As Integer = 0
' Specify an array of the property's inner properties.
Dim nestedBindableProperties() As String = Nothing
' Specify the property's category in the property grid's "Expressions" tab.
' The empty string corresponds to the root category.
Dim scopeName As String = ""
' Create and set a description for the "Position" property.
Dim description As New ExpressionBindingDescription(eventNames, position_Renamed, nestedBindableProperties, scopeName)
ExpressionBindingDescriptor.SetPropertyDescription(GetType(ProgressBar), nameof(Position), description)
End Sub
Private position_Renamed As Single = 0
Private maxValue_Renamed As Single = 100
Public Sub New()
Me.ForeColor = SystemColors.Highlight
End Sub
<DefaultValue(100), Description("The maximum value of the bar position."), DisplayName("Max Value"), Category("Parameters"), XtraSerializableProperty>
Public Property MaxValue() As Single
Get
Return Me.maxValue_Renamed
End Get
Set(ByVal value As Single)
If value <= 0 Then
Return
End If
Me.maxValue_Renamed = value
End Set
End Property
<DefaultValue(0), Description("The current bar position."), DisplayName("Position"), Category("Parameters"), XtraSerializableProperty>
Public Property Position() As Single
Get
Return Me.position_Renamed
End Get
Set(ByVal value As Single)
If value < 0 OrElse value > maxValue_Renamed Then
Return
End If
Me.position_Renamed = value
End Set
End Property
' ...
End Class
Bricks are a document’s basic elements. When you open a Preview window, report controls are rendered into bricks. For more information, review the following help topic: Bricks.
In a custom control, override the CreateBrick, PutStateToBrick, and GetStateFromBrick (optional) methods.
protected override VisualBrick CreateBrick(VisualBrick[] childrenBricks) {
// Create and return a panel brick.
return new PanelBrick(this);
}
Protected Overrides Function CreateBrick(ByVal childrenBricks() As VisualBrick) As VisualBrick
' Create and return a panel brick.
Return New PanelBrick(Me)
End Function
Use this method to create and return a new brick. Refer to the following article for information on different brick types: Classes Hierarchy: Bricks.
If the control is a container for other controls, the bricks for the contained controls are generated before calling the CreateBrick method for the container. Use the childrenBricks array to access bricks of the contained controls.
protected override void PutStateToBrick(VisualBrick brick, PrintingSystemBase ps) {
base.PutStateToBrick(brick, ps);
// Cast the "brick" variable to the "PanelBrick" type (the type of a brick
// created in the "CreateBrick" method).
PanelBrick panel = (PanelBrick)brick;
// Create a new visual brick to represent a bar.
VisualBrick progressBar = new VisualBrick(this);
// Hide the brick's borders.
progressBar.Sides = BorderSide.None;
// Set the foreground color for the bar.
progressBar.BackColor = panel.Style.ForeColor;
// Calculate the width of the progress bar's filled area.
float filledAreaWidth = panel.Rect.Width * (Position / MaxValue);
// Create a rectangle to be filled by the foreground color.
progressBar.Rect = new RectangleF(0, 0, filledAreaWidth, panel.Rect.Height);
// Add the visual brick to the panel brick.
panel.Bricks.Add(progressBar);
}
Protected Overrides Sub PutStateToBrick(ByVal brick As VisualBrick, ByVal ps As PrintingSystemBase)
MyBase.PutStateToBrick(brick, ps)
' Cast the "brick" variable to the "PanelBrick" type (the type of a brick
' created in the "CreateBrick" method).
Dim panel As PanelBrick = CType(brick, PanelBrick)
' Create a new visual brick to represent a bar.
Dim progressBar As New VisualBrick(Me)
' Hide the brick's borders.
progressBar.Sides = BorderSide.None
' Set the foreground color for the bar.
progressBar.BackColor = panel.Style.ForeColor
' Calculate the width of the progress bar's filled area.
Dim filledAreaWidth As Single = panel.Rect.Width * (Position / MaxValue)
' Create a rectangle to be filled by the foreground color.
progressBar.Rect = New RectangleF(0, 0, filledAreaWidth, panel.Rect.Height)
' Add the visual brick to the panel brick.
panel.Bricks.Add(progressBar)
End Sub
End Class
The method takes a brick created in the CreateBrick method as the first argument. Use this method to set up brick properties.
protected override void GetStateFromBrick(VisualBrick brick) {
base.GetStateFromBrick(brick);
// Copy the current brick state to the control.
// ...
}
Protected Overrides Sub GetStateFromBrick(ByVal brick As VisualBrick)
MyBase.GetStateFromBrick(brick)
' Copy the current brick state to the control.
' ...
End Sub
The method takes a brick modified in the PutStateToBrick method as an argument. Use this method to copy the current brick state to the control before you access control properties in the PrintOnPage event.
Both the custom control and the brick require serialization. The brick only uses XML serialization, and the control implements XML serialization and supports CodeDom serialization - which is mandatory for the Visual Studio Designer.
Use the XtraSerializableProperty attribute to serialize custom properties.
The XtraSerializableProperty attribute is responsible for serializing the property in XML. The attribute should be specified to serialize a property that returns a simple type. Complex types require a constructor with the XtraSerializationVisibility argument type (the most commonly used values are Hidden, Collection, Reference, Content).
The DesignerSerializationVisibility attribute is responsible for serializing the CodeDOM in Visual Studio Designer. It has three options - Hidden, Visible, and Content. You should apply Visible to collections or references.
The DefaultValue attribute determines whether the property value is serialized.
Refer to the following help topic for more information: XML Serialization.
To ensure proper serialization of collection properties, apply the XtraSerializableProperty attribute with the XtraSerializationVisibility.Collection parameter, as in the following code snippet:
[XtraSerializableProperty(XtraSerializationVisibility.Collection)]
public List<CustomParameter> CustomParameters { get; set; }
You should implement the IXtraSupportDeserializeCollectionItem interface to define how new elements of the collection are created. All required properties of the CustomParameter class must also be marked with the XtraSerializableProperty attribute.
For more information, review the following article: K18435 - How to serialize a custom property of the DevExpress control’s descendant.
Only XML serialization is necessary. For correct deserialization, map the brick’s text type (the overridden BrickType property at the Brick level) to the real type.
If the CreateBrick method of your custom control returns a known VisualBrick descendant, no action is necessary. An example is the Progress Bar custom control described in this help topic.
If the CreateBrick method returns a custom brick type, you should handle the BrickFactory.BrickResolve event to map the custom control type. The following code snippet maps the RoundLabelBrick and RoundPanelBrick custom bricks:
using DevExpress.XtraPrinting;
using DevExpress.XtraPrinting.Native;
using CustomControls.RoundBordersControls;
namespace DevExpress.XtraReports.CustomControls {
public static class RoundedCustomControl {
public static void EnsureCustomBrick() {
BrickFactory.BrickResolve += OnBrickResolve;
}
private static void OnBrickResolve(object sender, BrickResolveEventArgs args) {
if(args.Brick != null)
return;
CreateBrick<RoundLabelBrick>(args);
CreateBrick<RoundPanelBrick>(args);
}
static void CreateBrick<T>(DevExpress.XtraPrinting.BrickResolveEventArgs args) where T : class, new() {
if(args.Name == typeof(T).Name) {
args.Brick = new T() as Brick;
}
}
}
}
View Example: How to Create Controls with Rounded Corners
Add the ToolboxItem attribute and set it to true. Rebuild the project.
Handle the XRDesignMdiController.DesignPanelLoaded event to add a custom control to the toolbox:
designForm.DesignMdiController.DesignPanelLoaded +=
// ...
}
void DesignMdiController_DesignPanelLoaded(object sender, DesignerLoadedEventArgs e) {
// Get the Toolbox service from the Designer host.
IToolboxService ts =
(IToolboxService)e.DesignerHost.GetService(typeof(IToolboxService));
// Add a new category to the Toolbox,
// and place a custom control into it.
ToolboxItem item = new ToolboxItem(typeof(ProgressBar));
ts.AddToolboxItem(item, "New Category");
}
AddHandler designForm.DesignMdiController.DesignPanelLoaded, AddressOf DesignMdiController_DesignPanelLoaded
' ...
Private Sub DesignMdiController_DesignPanelLoaded(ByVal sender As Object, ByVal e As DesignerLoadedEventArgs)
' Get the Toolbox service from the Designer host.
Dim ts As IToolboxService = DirectCast(e.DesignerHost.GetService(GetType(IToolboxService)), IToolboxService)
' Add a new category to the Toolbox,
' and place a custom control into it.
Dim item As New ToolboxItem(GetType(ProgressBar))
ts.AddToolboxItem(item, "New Category")
End Sub
Build the solution. The ProgressBar control appears in the toolbox and you can drop the control onto the report band:
The DefaultBindableProperty attribute for the Position property specifies that when you drop a field from the Field List onto the control, the Position property is bound to that field. For more information on data binding, review the following help topic: Bind Report Controls to Data.
using DevExpress.XtraReports;
[DefaultBindableProperty("Position")]
public class ProgressBar : XRControl {
//...
}
Imports DevExpress.XtraReports
<DefaultBindableProperty("Position")>
Public Class ProgressBar
Inherits XRControl
'...
End Class
After you add the DefaultBindableProperty attribute, rebuild the solution. The Position property appears in the Properties window:
View Example: Reporting for WinForms - Create a Custom Progress Bar Control
Follow the steps below to create a report with a custom ProgressBar control:
Create a new project in Visual Studio.
Add a blank report to the project. For more information, review the following help topic: Create a Report in Visual Studio.
Add the ProgressBar class (defined above) to the project.
Bind the report to the JSON data source defined in the code snippet below. For more information, review the following help topic: Bind a Report to JSON Data.
Design the report layout as shown in the image that follows. Use the XRLabel control to display countries and areas. Set the BackColor property to LightBlue. Set the MaxValue property to 17100 :
Click the f-button next to the control to invoke the Expression Editor and bind the Position property to the data field:
Switch to Preview to display the document generated from the report layout: