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

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

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