Tutorial: Custom Drawing

  • 5 minutes to read

This walkthrough is a transcript of the Custom Drawing video available on the DevExpress YouTube Channel.

In this tutorial, you will learn how to use the grid View's CustomDraw… events. You'll start with a grid displaying plain task data. First, you'll use an event to customize the appearance of individual cells. Then, the same event handler will be modified to alter the displayed text in individual cells depending on grid data. You'll then extend the handler even further to draw images within specific cells. Finally, you'll see how to custom paint column headers while keeping their interactive elements, such as filter dropdown buttons and sort glyphs.

GridView_Appearance_CustomDrawingFinalResult

Starting Point

Start with an application that has a GridControl displaying task data.

GridView_Appearance_CustomDrawingInitialGrid

Custom Drawing Individual Cells

Choose the grid View and handle the GridView.CustomDrawCell event. This event provides you with the RowCellCustomDrawEventArgs.RowHandle and RowCellCustomDrawEventArgs.Column parameters that identify the cell being painted.

  • Customizing Cell Appearance

    If a cell is in the focused row, custom draw is not applied to ensure the focused style priority. In this first version of the event handler, change the background style of the UnitPrice column's cells whose values are greater than 30. The default painting mechanism will be invoked after event handler execution, and it will automatically apply the specified background.

    
    private void gridView_CustomDrawCell(object sender, DevExpress.XtraGrid.Views.Base.RowCellCustomDrawEventArgs e) {
        GridView view = sender as GridView;
        if (e.RowHandle == view.FocusedRowHandle) return;
        if (e.Column.FieldName != "UnitPrice") return;
        // Fill a cell's background if its value is greater than 30.
        if (Convert.ToInt32(e.CellValue) > 30)
            e.Appearance.BackColor = Color.FromArgb(60, Color.Salmon);
    }
    

    Run the application to see the result. As you see, cells that conform to the specified condition have a different appearance, and in that way the CustomDraw… event allow you to implement conditional formatting of any complexity.

    GridView_Appearance_CustomDrawingCellBackColor

  • Changing Cell Display Text

    Close the application and further modify the custom draw event handler. Change the event's RowCellCustomDrawEventArgs.DisplayText parameter if a cell resides within a row that has a discount assigned. The specified text will also be painted by the default rendering mechanism.

    
    private void gridView_CustomDrawCell(object sender, DevExpress.XtraGrid.Views.Base.RowCellCustomDrawEventArgs e) {
        // ...
        // Specify the cell's display text. 
        double discount = Convert.ToDouble(view.GetRowCellValue(e.RowHandle, view.Columns["Discount"]));
        if (discount > 0) {
            double unitPrice = Convert.ToDouble(view.GetRowCellValue(e.RowHandle, view.Columns["UnitPrice"]));
            double discountPrice = unitPrice * (1 - discount);
            e.DisplayText = "Discount price: " + discountPrice.ToString("c2");
        }
    }
    

    Run the application again. Notice that certain column cells display the discounted price instead of the unit price. In essence, this allows you to do what you could do using Unbound Column expressions and Display Text Formatting, but with CustomDraw... events, you can apply these changes to individual cells, rather than to entire columns.

    GridView_Appearance_CustomDrawingCellDisplayText

  • Displaying Custom Images within Cells

    Draw custom images over the default rendering for cells containing discount prices. Paint a custom image using the e.Cache.DrawImage method. To invoke the default rendering mechanism prior to this, call the CustomDrawEventArgs.DefaultDraw method. This method prevents default painting from being re-invoked after event execution.

    
    private void gridView_CustomDrawCell(object sender, DevExpress.XtraGrid.Views.Base.RowCellCustomDrawEventArgs e) {
        // ...
        e.DefaultDraw();
        // Paint images in cells if discounts > 0.
        Image image = Image.FromFile("c:\\important.png");
        if (discount > 0)
            e.Cache.DrawImage(image, e.Bounds.Location);
    }
    

    Run the application. Custom images are displayed for certain cells.

    GridView_Appearance_CustomDrawingCellImages

Custom Drawing Column Headers

To custom paint column headers, subscribe to the GridView.CustomDrawColumnHeader event. Use the gradient from 'Salmon' to 'White' to fill the background of column headers, and after that, display header captions. Set the event's CustomDrawEventArgs.Handled parameter to true to prevent the default painting mechanism from being invoked when the event handler is completed.


private void gridView_CustomDrawColumnHeader(object sender, ColumnHeaderCustomDrawEventArgs e) {
    Rectangle rect = e.Bounds;
    // Create a new brush
    Brush brush = new LinearGradientBrush(e.Bounds, Color.Salmon, Color.White, LinearGradientMode.ForwardDiagonal);
    // Fill a column's background
    e.Cache.FillRectangle(brush, rect);
    // Draw the column's caption
    e.Appearance.DrawString(e.Cache, e.Info.Caption, e.Info.CaptionRect);
    // Draw the filter and sort buttons.
    e.Handled = true;
}

Now run the application. Column headers are painted with the 'Salmon' to 'White' gradient.

GridView_Appearance_CustomDrawingColumnHeaders

However, the usual sort and filter glyphs are not displayed. There is a way to fix this and display standard interactive elements within column headers. You need to enumerate the ColumnHeaderCustomDrawEventArgs.Info.InnerElements collection and use a specially designed method to paint them if their settings indicate that they are currently visible.


private void gridView_CustomDrawColumnHeader(object sender, ColumnHeaderCustomDrawEventArgs e) {
    // ...
    // Draw the filter and sort buttons.
    foreach (DevExpress.Utils.Drawing.DrawElementInfo info in e.Info.InnerElements) {
        if (!info.Visible) continue;
        DevExpress.Utils.Drawing.ObjectPainter.DrawObject(e.Cache, info.ElementPainter, info.ElementInfo);
    }           
}

Run the application again to see the result. Now, the headers have a custom appearance while keeping the standard elements so end-users can still interact with column headers in the usual manner.

GridView_Appearance_CustomDrawingFinalResult

Complete Code


private void gridView_CustomDrawCell(object sender, DevExpress.XtraGrid.Views.Base.RowCellCustomDrawEventArgs e) {
    GridView view = sender as GridView;
    if (e.RowHandle == view.FocusedRowHandle) return;
    if (e.Column.FieldName != "UnitPrice") return;
    // Fill a cell's background if its value is greater than 30.
    if (Convert.ToInt32(e.CellValue) > 30)
        e.Appearance.BackColor = Color.FromArgb(60, Color.Salmon);
    // Specify the cell's display text. 
    double discount = Convert.ToDouble(view.GetRowCellValue(e.RowHandle, view.Columns["Discount"]));
    if (discount > 0) {
        double unitPrice = Convert.ToDouble(view.GetRowCellValue(e.RowHandle, view.Columns["UnitPrice"]));
        double discountPrice = unitPrice * (1 - discount);
        e.DisplayText = "Discount price: " + discountPrice.ToString("c2");
    }
    e.DefaultDraw();
    // Paint images in cells if discounts > 0.
    Image image = Image.FromFile("c:\\important.png");
    if (discount > 0)
        e.Cache.DrawImage(image, e.Bounds.Location);
}

private void gridView_CustomDrawColumnHeader(object sender, ColumnHeaderCustomDrawEventArgs e) {
    Rectangle rect = e.Bounds;
    // Create a new brush
    Brush brush = new LinearGradientBrush(e.Bounds, Color.Salmon, Color.White, LinearGradientMode.ForwardDiagonal);
    // Fill a column's background
    e.Cache.FillRectangle(brush, rect);
    // Draw the column's caption
    e.Appearance.DrawString(e.Cache, e.Info.Caption, e.Info.CaptionRect);
    e.Handled = true;
    // Draw the filter and sort buttons.
    foreach (DevExpress.Utils.Drawing.DrawElementInfo info in e.Info.InnerElements) {
        if (!info.Visible) continue;
        DevExpress.Utils.Drawing.ObjectPainter.DrawObject(e.Cache, info.ElementPainter, info.ElementInfo);
    }           
}
See Also