Back to Devexpress

How to: Create a Range Control Client that Paints Custom Information within Range Control

windowsforms-11760-controls-and-libraries-editors-and-simple-controls-range-control-examples-how-to-create-a-range-control-client-that-paints-custom-information-within-range-control.md

latest24.2 KB
Original Source

How to: Create a Range Control Client that Paints Custom Information within Range Control

  • Nov 13, 2018
  • 13 minutes to read

This example shows how to implement a custom Range Control Client to paint any information within the RangeControl.

The Range Control Client created in this example paints a graph.

csharp
using DevExpress.Utils.Drawing;
using DevExpress.XtraEditors;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace CustomRangeControlClient {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e) {
            RangeControl rangeControl1 = new RangeControl();
            rangeControl1.Dock = DockStyle.Fill;
            this.Controls.Add(rangeControl1);

            rangeControl1.Client = new CustomRangeClient(101, -100, 100);
            rangeControl1.SelectedRange.Maximum = 30;
            rangeControl1.SelectedRange.Minimum = 10;
        }
    }

    public class CustomRangeClient : IRangeControlClient {

        const int rulerDeltaConst = 2;

        int[] data;
        int minValue;
        int maxValue;

        public CustomRangeClient(int dataCount, int minDataValue, int maxDataValue) {
            InitData(dataCount, minDataValue, maxDataValue);

            ruler = new List<object>(dataCount / rulerDeltaConst + 1);
            for (int i = 0; i < ruler.Count; i++) {
                ruler.Add(i);
            }
        }

        void InitData(int dataCount, int minDataValue, int maxDataValue) {
            Data = new int[dataCount];
            MinValue = minDataValue;
            MaxValue = maxDataValue;
            Random r = new Random();
            for (int i = 0; i < Data.Length; i++) {
                Data[i] = minDataValue + r.Next(maxDataValue - minDataValue);
            }
        }

        public int[] Data {
            get {
                return data;
            }
            private set {
                data = value;
            }
        }
        public int MaxValue {
            get {
                return maxValue;
            }
            private set {
                maxValue = value;
            }
        }

        public int MinValue {
            get {
                return minValue;
            }
            private set {
                minValue = value;
            }
        }

        EventHandlerList events;
        protected EventHandlerList Events {
            get {
                if (events == null)
                    events = new EventHandlerList();
                return events;
            }
        }

        private static readonly object rangeChanged = new object();

        // Fires the RangeChanged event.
        protected void RaiseRangeChanged() {
            RangeChangedEventHandler handler = Events[rangeChanged] as RangeChangedEventHandler;
            if (handler != null) {
                RangeControlRangeEventArgs e = new RangeControlRangeEventArgs();
                e.Range = new RangeControlRange();
                handler(this, e);
            }
        }

        #region IRangeControlClient Members
        // Checks if the specified type of the ruler values is supported.
        // This method is called when a new value is set to the Minimum and Maximum properties.
        bool IRangeControlClient.IsValidType(Type type) {
            return true;
        }

        //This method fires when you move the mouse cursor over the viewport.
        void IRangeControlClient.UpdateHotInfo(RangeControlHitInfo hitInfo) {
        }

        //This method fires when you press with the mouse within the viewport (without releasing the mouse button).
        void IRangeControlClient.UpdatePressedInfo(RangeControlHitInfo hitInfo) {
        }

        //This method fires when you click within the viewport.
        void IRangeControlClient.OnClick(RangeControlHitInfo hitInfo) {
        }

        // Returns true if the Client's state is valid and the Client should render itself within the viewport;
        // Returns false if a message specified by the InvalidText property should be painted instead of the Client.
        bool IRangeControlClient.IsValid {
            get {
                return true;
            }
        }
        // Specifies text painted when the Client's state is invalid.
        string IRangeControlClient.InvalidText {
            get {
                return "i n v a l i d";
            }
        }

        // Return the object that will be accessible via the RangeControl.ClientOptions property.
        object IRangeControlClient.GetOptions() {
            return this;
        }

        //The event that fires when the range has been changed via the Client.
        event ClientRangeChangedEventHandler IRangeControlClient.RangeChanged {
            add { Events.AddHandler(rangeChanged, value); }
            remove { Events.RemoveHandler(rangeChanged, value); }
        }
        // Fires when the range is changed via the RangeControl.
        void IRangeControlClient.OnRangeChanged(object rangeMinimum, object rangeMaximum) {
        }

        // Return true for a specific orientation if the Client supports this orientation.
        bool IRangeControlClient.SupportOrientation(RangeControlClientOrientation orientation) {
            return (orientation != RangeControlClientOrientation.Vertical);
        }

        // Return true if the Client draws the ruler itself.
        bool IRangeControlClient.DrawRuler(RangeControlPaintEventArgs e) {
            return false;
        }

        //Returns false if the RangeControl should reserve drawing space for the ruler.
        bool IRangeControlClient.IsCustomRuler {
            get {
                return false;
            }
        }

        // Returns text representation of the ruler values
        string IRangeControlClient.RulerToString(int index) {
            return (index * (int)(rulerDeltaConst)).ToString();
        }

        List<object> ruler;
        // If ruler values are not equally spaced, return custom ruler values; 
        // If the ruler has equally spaced increments specified by the RulerDelta property, return null.
        List<object> IRangeControlClient.GetRuler(RulerInfoArgs e) {
            return null;
            //return ruler;
        }

        // Returns a ruler increment (when values are equally distributed).
        object IRangeControlClient.RulerDelta {
            get { return rulerDeltaConst; }
        }

        // Returns a normalized ruler increment.
        double IRangeControlClient.NormalizedRulerDelta {
            get {
                return (double)rulerDeltaConst / BarCount;
            }
        }

        //Gets a ruler value (between Minimum and Maximum) from a normalized value (between 0 and 1).
        object IRangeControlClient.GetValue(double normalizedValue) {
            int index = (int)(normalizedValue * BarCount);
            return index;
        }
        // Performs the opposite conversion.
        double IRangeControlClient.GetNormalizedValue(object value) {
            int index = (int)value;
            return ((double)index) / BarCount;
        }

        string IRangeControlClient.ValueToString(double normalizedValue) {
            return string.Empty;
        }

        // Renders the Range Control's viewport.
        void IRangeControlClient.DrawContent(RangeControlPaintEventArgs e) {
            Rectangle rect = e.ContentBounds;
            rect.Inflate(0, -3);
            rect.Height -= ((IRangeControlClient)this).RangeBoxBottomIndent;
            DrawGraph(e, rect);
            DrawZeroLine(e, rect);
        }

        protected virtual void DrawZeroLine(RangeControlPaintEventArgs e, Rectangle contentBounds) {
            double zeroLine = (double)(MaxValue - 0) / (MaxValue - MinValue);
            if (zeroLine < 0.0 || zeroLine >= 1.0f)
                return;
            int y = (int)(contentBounds.Y + zeroLine * contentBounds.Height);
            e.Cache.DrawLine(new Point(contentBounds.X, y), new Point(contentBounds.Right, y), Color.Pink, 1);
        }

        protected virtual void DrawGraph(RangeControlPaintEventArgs e, Rectangle contentBounds) {
            GraphicsCache gCache = e.Cache as GraphicsCache;
            gCache.FillRectangle(Brushes.WhiteSmoke, contentBounds);
            gCache.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
            int start = Math.Max(0, (int)(e.RangeControl.VisibleRangeStartPosition * BarCount) - 2);
            int end = Math.Min(Data.Length, start + ((int)(e.RangeControl.VisibleRangeWidth * BarCount) + 4));
            Point? prevPoint = null;

            for (int i = start; i < end; i++) {
                int y = contentBounds.Y + contentBounds.Height - (int)((double)(Data[i] - MinValue) / (MaxValue - MinValue) * contentBounds.Height);
                int x = e.CalcX((double)i / BarCount);
                if (prevPoint.HasValue) {
                    gCache.DrawLine(new Point(prevPoint.Value.X, prevPoint.Value.Y), new Point(x, y), Color.DarkCyan, 1);
                }
                prevPoint = new Point(x, y);
            }
            gCache.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.Default;
        }

        int BarCount { get { return Data.Length - 1; } }

        // The top and bottom indents for the selection area within the viewport.
        // These limit the bounds of the selection thumb lines that mark the current selection. 
        int IRangeControlClient.RangeBoxTopIndent { get { return 0; } }
        int IRangeControlClient.RangeBoxBottomIndent { get { return 0; } }

        // Validates a range when it is changed.
        void IRangeControlClient.ValidateRange(NormalizedRangeInfo info) {
            int start = (int)(info.Range.Minimum * BarCount);
            int end = (int)(info.Range.Maximum * BarCount);
            if (end == start) end = start + 2;

            info.Range.Minimum = (double)start / BarCount;
            info.Range.Maximum = (double)end / BarCount;
        }
        //This method fires when a client is added to or removed from the RangeControl.
        void IRangeControlClient.OnRangeControlChanged(IRangeControl rangeControl) { }

        //This method fires when the RangeControl is resized.
        void IRangeControlClient.OnResize() { }

        //This method fires when the RangeControl's state or settings are changed
        void IRangeControlClient.Calculate(Rectangle contentRect) { }

        //Validates a scale factor
        double IRangeControlClient.ValidateScale(double newScale) {
            // Limit the maximum scale factor to 10:
            return Math.Min(10, newScale);
        }
        // Allows you to override the selected region's bounds.
        Rectangle IRangeControlClient.CalculateSelectionBounds(RangeControlPaintEventArgs e, Rectangle rect) {
            return rect;
        }
        // Allows you to custom paint the selected region.
        void IRangeControlClient.DrawSelection(RangeControlPaintEventArgs e) {
        }

        #endregion
    }
}
vb
Imports System.ComponentModel
Imports System.Text
Imports DevExpress.Utils.Drawing
Imports DevExpress.XtraEditors

Partial Public Class Form1
    Public Sub New()
        InitializeComponent()
        Dim rangeControl1 As New DevExpress.XtraEditors.RangeControl
        rangeControl1.Dock = DockStyle.Fill
        Controls.Add(rangeControl1)

        rangeControl1.Client = New CustomRangeClient(101, -100, 100)
        rangeControl1.SelectedRange.Maximum = 30
        rangeControl1.SelectedRange.Minimum = 10
    End Sub
End Class

Public Class CustomRangeClient
    Implements IRangeControlClient

    Private Const rulerDeltaConst As Integer = 2

    Private data_Renamed() As Integer

    Private minValue_Renamed As Integer

    Private maxValue_Renamed As Integer

    Public Sub New(ByVal dataCount As Integer, ByVal minDataValue As Integer, ByVal maxDataValue As Integer)
        InitData(dataCount, minDataValue, maxDataValue)

        ruler = New List(Of Object)(dataCount \ rulerDeltaConst + 1)
        Dim i As Integer = 0
        Do While i < ruler.Count
            ruler.Add(i)
            i += 1
        Loop
    End Sub

    Private Sub InitData(ByVal dataCount As Integer, ByVal minDataValue As Integer, ByVal maxDataValue As Integer)
        Data = New Integer(dataCount - 1) {}
        MinValue = minDataValue
        MaxValue = maxDataValue
        Dim r As New Random()
        For i As Integer = 0 To Data.Length - 1
            Data(i) = minDataValue + r.Next(maxDataValue - minDataValue)
        Next i
    End Sub

    Public Property Data() As Integer()
        Get
            Return data_Renamed
        End Get
        Private Set(ByVal value As Integer())
            data_Renamed = value
        End Set
    End Property
    Public Property MaxValue() As Integer
        Get
            Return maxValue_Renamed
        End Get
        Private Set(ByVal value As Integer)
            maxValue_Renamed = value
        End Set
    End Property

    Public Property MinValue() As Integer
        Get
            Return minValue_Renamed
        End Get
        Private Set(ByVal value As Integer)
            minValue_Renamed = value
        End Set
    End Property

    Private events_Renamed As EventHandlerList
    Protected ReadOnly Property Events() As EventHandlerList
        Get
            If events_Renamed Is Nothing Then
                events_Renamed = New EventHandlerList()
            End If
            Return events_Renamed
        End Get
    End Property

    Private Shared ReadOnly rangeChanged_Renamed As New Object()

    ' Fires the RangeChanged event.
    Protected Sub RaiseRangeChanged()
        Dim handler As RangeChangedEventHandler = TryCast(Events(rangeChanged_Renamed), RangeChangedEventHandler)
        If handler IsNot Nothing Then
            Dim e As New RangeControlRangeEventArgs()
            e.Range = New RangeControlRange()
            handler(Me, e)
        End If
    End Sub

#Region "IRangeControlClient Members"
    ' Checks if the specified type of the ruler values is supported.
    ' This method is called when a new value is set to the Minimum and Maximum properties.
    Private Function IRangeControlClient_IsValidType(ByVal type As Type) As Boolean Implements IRangeControlClient.IsValidType
        Return True
    End Function

    'This method fires when you move the mouse cursor over the viewport.
    Private Sub IRangeControlClient_UpdateHotInfo(ByVal hitInfo As RangeControlHitInfo) Implements IRangeControlClient.UpdateHotInfo
    End Sub

    'This method fires when you press with the mouse within the viewport (without releasing the mouse button).
    Private Sub IRangeControlClient_UpdatePressedInfo(ByVal hitInfo As RangeControlHitInfo) Implements IRangeControlClient.UpdatePressedInfo
    End Sub

    'This method fires when you click within the viewport.
    Private Sub IRangeControlClient_OnClick(ByVal hitInfo As RangeControlHitInfo) Implements IRangeControlClient.OnClick
    End Sub

    ' Returns true if the Client's state is valid and the Client should render itself within the viewport;
    ' Returns false if a message specified by the InvalidText property should be painted instead of the Client.
    Private ReadOnly Property IRangeControlClient_IsValid() As Boolean Implements IRangeControlClient.IsValid
        Get
            Return True
        End Get
    End Property

    ' Specifies text painted when the Client's state is invalid.
    Private ReadOnly Property IRangeControlClient_InvalidText() As String Implements IRangeControlClient.InvalidText
        Get
            Return "i n v a l i d"
        End Get
    End Property

    ' Return the object that will be accessible via the RangeControl.ClientOptions property.
    Private Function IRangeControlClient_GetOptions() As Object Implements IRangeControlClient.GetOptions
        Return Me
    End Function

    'The event that fires when the range has been changed via the Client.
    Private Custom Event RangeChanged As ClientRangeChangedEventHandler Implements IRangeControlClient.RangeChanged
        AddHandler(ByVal value As ClientRangeChangedEventHandler)
            Events.AddHandler(rangeChanged_Renamed, value)
        End AddHandler
        RemoveHandler(ByVal value As ClientRangeChangedEventHandler)
            Events.RemoveHandler(rangeChanged_Renamed, value)
        End RemoveHandler
        RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
        End RaiseEvent
    End Event

    ' Fires when the range is changed via the RangeControl.
    Private Sub IRangeControlClient_OnRangeChanged(ByVal rangeMinimum As Object, ByVal rangeMaximum As Object) Implements IRangeControlClient.OnRangeChanged
    End Sub

    ' Return true for a specific orientation if the Client supports this orientation.
    Private Function IRangeControlClient_SupportOrientation(ByVal orientation As RangeControlClientOrientation) As Boolean Implements IRangeControlClient.SupportOrientation
        Return (orientation <> RangeControlClientOrientation.Vertical)
    End Function

    ' Return true if the Client draws the ruler itself.
    Private Function IRangeControlClient_DrawRuler(ByVal e As RangeControlPaintEventArgs) As Boolean Implements IRangeControlClient.DrawRuler
        Return False
    End Function

    'Returns false if the RangeControl should reserve drawing space for the ruler.
    Private ReadOnly Property IRangeControlClient_IsCustomRuler() As Boolean Implements IRangeControlClient.IsCustomRuler
        Get
            Return False
        End Get
    End Property

    ' Returns text representation of the ruler values
    Private Function IRangeControlClient_RulerToString(ByVal index As Integer) As String Implements IRangeControlClient.RulerToString
        Return (index * CInt(rulerDeltaConst)).ToString()
    End Function

    Private ruler As List(Of Object)
    ' If ruler values are not equally spaced, return custom ruler values; 
    ' If the ruler has equally spaced increments specified by the RulerDelta property, return null.
    Private Function IRangeControlClient_GetRuler(ByVal e As RulerInfoArgs) As List(Of Object) Implements IRangeControlClient.GetRuler
        Return Nothing
        'return ruler;
    End Function

    ' Returns a ruler increment (when values are equally distributed).
    Private ReadOnly Property IRangeControlClient_RulerDelta() As Object Implements IRangeControlClient.RulerDelta
        Get
            Return rulerDeltaConst
        End Get
    End Property

    ' Returns a normalized ruler increment.
    Private ReadOnly Property IRangeControlClient_NormalizedRulerDelta() As Double Implements IRangeControlClient.NormalizedRulerDelta
        Get
            Return CDbl(rulerDeltaConst) / BarCount
        End Get
    End Property

    'Gets a ruler value (between Minimum and Maximum) from a normalized value (between 0 and 1).
    Private Function IRangeControlClient_GetValue(ByVal normalizedValue As Double) As Object Implements IRangeControlClient.GetValue
        Dim index As Integer = CInt((normalizedValue * BarCount))
        Return index
    End Function

    ' Performs the opposite conversion.
    Private Function IRangeControlClient_GetNormalizedValue(ByVal value As Object) As Double Implements IRangeControlClient.GetNormalizedValue
        Dim index As Integer = DirectCast(value, Integer)
        Return (CDbl(index)) / BarCount
    End Function

    Private Function IRangeControlClient_ValueToString(ByVal normalizedValue As Double) As String Implements IRangeControlClient.ValueToString
        Return String.Empty
    End Function

    ' Renders the Range Control's viewport.
    Private Sub IRangeControlClient_DrawContent(ByVal e As RangeControlPaintEventArgs) Implements IRangeControlClient.DrawContent
        Dim rect As Rectangle = e.ContentBounds
        rect.Inflate(0, -3)
        rect.Height -= DirectCast(Me, IRangeControlClient).RangeBoxBottomIndent
        DrawGraph(e, rect)
        DrawZeroLine(e, rect)
    End Sub

    Protected Overridable Sub DrawZeroLine(ByVal e As RangeControlPaintEventArgs, ByVal contentBounds As Rectangle)
        Dim zeroLine As Double = CDbl(MaxValue - 0) / (MaxValue - MinValue)
        If zeroLine < 0.0 OrElse zeroLine >= 1.0F Then
            Return
        End If
        Dim y As Integer = CInt((contentBounds.Y + zeroLine * contentBounds.Height))
        e.Cache.DrawLine(New Point(contentBounds.X, y), New Point(contentBounds.Right, y), Color.Pink, 1)
    End Sub

    Protected Overridable Sub DrawGraph(ByVal e As RangeControlPaintEventArgs, ByVal contentBounds As Rectangle)
        Dim gCache As GraphicsCache = CType(e.Cache, GraphicsCache)
        gCache.FillRectangle(Brushes.WhiteSmoke, contentBounds)
        gCache.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality
        Dim start As Integer = Math.Max(0, CInt((e.RangeControl.VisibleRangeStartPosition * BarCount)) - 2)
        Dim [end] As Integer = Math.Min(Data.Length, start + (CInt((e.RangeControl.VisibleRangeWidth * BarCount)) + 4))
        Dim prevPoint? As Point = Nothing
        Using pen As New Pen(Color.Blue, 1)
            For i As Integer = start To [end] - 1
                Dim y As Integer = contentBounds.Y + contentBounds.Height - CInt((CDbl(Data(i) - MinValue) / (MaxValue - MinValue) * contentBounds.Height))
                Dim x As Integer = e.CalcX(CDbl(i) / BarCount)
                If prevPoint.HasValue Then
                    gCache.DrawLine(New Point(prevPoint.Value.X, prevPoint.Value.Y), New Point(x, y), Color.DarkCyan, 1)
                End If
                prevPoint = New Point(x, y)
            Next i
        End Using
        gCache.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.Default
    End Sub

    Private ReadOnly Property BarCount() As Integer
        Get
            Return Data.Length - 1
        End Get
    End Property

    ' The top and bottom indents for the selection area within the viewport.
    ' These limit the bounds of the selection thumb lines that mark the current selection. 
    Private ReadOnly Property IRangeControlClient_RangeBoxTopIndent() As Integer Implements IRangeControlClient.RangeBoxTopIndent
        Get
            Return 0
        End Get
    End Property

    Private ReadOnly Property IRangeControlClient_RangeBoxBottomIndent() As Integer Implements IRangeControlClient.RangeBoxBottomIndent
        Get
            Return 0
        End Get
    End Property

    ' Validates a range when it is changed.
    Private Sub IRangeControlClient_ValidateRange(ByVal info As NormalizedRangeInfo) Implements IRangeControlClient.ValidateRange
        Dim start As Integer = CInt((info.Range.Minimum * BarCount))
        Dim [end] As Integer = CInt((info.Range.Maximum * BarCount))
        If [end] = start Then
            [end] = start + 2
        End If

        info.Range.Minimum = CDbl(start) / BarCount
        info.Range.Maximum = CDbl([end]) / BarCount
    End Sub

    'This method fires when a client is added to or removed from the RangeControl.
    Private Sub IRangeControlClient_OnRangeControlChanged(ByVal rangeControl As IRangeControl) Implements IRangeControlClient.OnRangeControlChanged
    End Sub

    'This method fires when the RangeControl is resized.
    Private Sub IRangeControlClient_OnResize() Implements IRangeControlClient.OnResize
    End Sub

    'This method fires when the RangeControl's state or settings are changed
    Private Sub IRangeControlClient_Calculate(ByVal contentRect As Rectangle) Implements IRangeControlClient.Calculate
    End Sub

    'Validates a scale factor
    Private Function IRangeControlClient_ValidateScale(ByVal newScale As Double) As Double Implements IRangeControlClient.ValidateScale
        ' Limit the maximum scale factor to 10:
        Return Math.Min(10, newScale)
    End Function

    ' Allows you to override the selected region's bounds.
    Public Function CalculateSelectionBounds(e As RangeControlPaintEventArgs, rect As Rectangle) As Rectangle Implements IRangeControlClient.CalculateSelectionBounds
        Return rect
    End Function

    ' Allows you to custom paint the selected region.
    Public Sub DrawSelection(e As RangeControlPaintEventArgs) Implements IRangeControlClient.DrawSelection
    End Sub

#End Region
End Class