Back to Devexpress

Create a Custom Control Inherited From XRControl

xtrareports-1304-feature-guide-to-devexpress-reports-use-report-controls-use-custom-controls-implement-custom-control-from-scratch.md

latest18.3 KB
Original Source

Create a Custom Control Inherited From XRControl

  • Feb 18, 2026
  • 10 minutes to read

Follow the steps below to create a custom Progress Bar control.

Create a New Class

Create a new class that defines a custom control. Derive the class from the base XRControl class. Implement properties, methods, events, and other members:

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

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

Implement Methods to Render the Control

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.

CreateBrick

csharp
protected override VisualBrick CreateBrick(VisualBrick[] childrenBricks) {
    // Create and return a panel brick.
    return new PanelBrick(this);
}
vb
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.

PutStateToBrick

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

GetStateFromBrick (Optional)

csharp
protected override void GetStateFromBrick(VisualBrick brick) {
    base.GetStateFromBrick(brick);

    // Copy the current brick state to the control.
    // ...
}
vb
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.

Serialization

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.

Make Properties Serializable

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.

Serialize Collection Properties

To ensure proper serialization of collection properties, apply the XtraSerializableProperty attribute with the XtraSerializationVisibility.Collection parameter, as in the following code snippet:

csharp
[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.

Brick Serialization

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:

csharp
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 Custom Control to the Toolbox

Visual Studio Toolbox

Add the ToolboxItem attribute and set it to true. Rebuild the project.

Report Designer for WinForms

Handle the XRDesignMdiController.DesignPanelLoaded event to add a custom control to the toolbox:

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

Specify Default Property for Data Binding

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.

csharp
using DevExpress.XtraReports;

[DefaultBindableProperty("Position")]
public class ProgressBar : XRControl {
    //...
}
vb
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:

Use the Progress Bar Control

View Example: Reporting for WinForms - Create a Custom Progress Bar Control

Follow the steps below to create a report with a custom ProgressBar control:

  1. Create a new project in Visual Studio.

  2. Add a blank report to the project. For more information, review the following help topic: Create a Report in Visual Studio.

  3. Add the ProgressBar class (defined above) to the project.

  4. 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.

  5. 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 :

  6. Click the f-button next to the control to invoke the Expression Editor and bind the Position property to the data field:

  7. Switch to Preview to display the document generated from the report layout: