A grid without editors can not be called a grid. Convenient data editing feature is one of the most important advantages of Dapfor’s product over other grids.

In developing the editing system programmers considered that the grid should support standard .Net framework editors, enable easy use of new editors and controls and use any third-party editors and graphical controls.

Standard Microsoft DataGridView control for data editing supports different column types: DataGridViewTextBoxColumn, DataGridViewComboBoxColumn, etc. Many third-party grid developers use similar approach. However, we think that editor type shouldn’t depend on column type as this approach is too bulky and limits available editors, while the programmer often has to write a lot of code to implement a new editor. Besides, we think that a header column shouldn’t contain information of data types. Below we shall provide some examples of this.

NetGrid editing system is based on well-known proven data editing principle used in PropertyGrid. This approach is based on UITypeEditor class and supports use of almost any controls in a dropdown combo box.

grid-intro 13

Let’s say a few words about UITypeEditor. Many .Net classes have their own editors. They can be declared very easily:

C# Copy imageCopy
class MyEditor : UITypeEditor
{
    ...
}


[Editor(typeof(MyEditor), typeof(UITypeEditor))]
class MyClass
{
    ...
}

To get an editor object the following code can be used.

C# Copy imageCopy
//Custom editor
UITypeEditor myEditor = (UITypeEditor)TypeDescriptor.GetEditor(typeof(MyClass), typeof(UITypeEditor));

//Standard editor to edit Color objects
UITypeEditor colorEditor = (UITypeEditor)TypeDescriptor.GetEditor(typeof(Color), typeof(UITypeEditor));

By the way, we see editors of this kind when we edit properties in VisualStudio. This is how Control.Dock property editing looks in IDE.

Editors in PropertyGrid

UITypeEditor has several styles determined by UITypeEditorEditStyle enum. Data editing in different styles looks as follows.

Editor styles

Data editing starts on left-click over PropertyGrid cell. The grid reads current value of data object property and calls UITypeEditor.EditValue() method. Further implementation of editor completely depends on developer. With modal call the programmer just has to call Form.ShowModal() from of any user form. As the call is modal, the developer has to return a new value after closing the form. This is how font picker editor works.

C# Copy imageCopy
class MyEditor : UITypeEditor
{
    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.Modal;
    }

    public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
    {
        Font font = value as Font;
        FontDialog dlg =  new FontDialog();
        dlg.Font = font;
        if(dlg.ShowDialog() == DialogResult.OK)
        {
            value = dlg.Font;
        }
        return value;
    }
}

Usage of user controls in a dropdown combo box is not much more complicated. Implementation of IWindowsFormsEditorService interface ensures modal display of user control in a dropdown window. IWindowsFormsEditorService.DropDownControl() method is equivalent to Form.ShowModal() and to close control the programmer only has to call IWindowsFormsEditorService.CloseDropDown() method.

C# Copy imageCopy
class MyEditor : UITypeEditor
{
    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.DropDown;
    }

    public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
    {
        //Get the editor service
        IWindowsFormsEditorService service = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;

        if(service != null)
        {
            //Create a control
            using(Button someControl = new Button())
            {
                //Add a reaction on user click
                someControl.Click += delegate
                {
                    //User has clicked on the button inside of the dropdown box. Close the control.
                    service.CloseDropDown();

                    //Set a desired value
                    //value = _new_value_;
                };

                //Start editing with specified control. 
                //This call opens a modal dropdown box
                service.DropDownControl(someControl);        
            }
        }

        return value;
    }
}

.Net Grid uses similar approach and implements IWindowsFormsEditorService interface in the same way. This enables it to use standard controls and third-party controls for data editing.

Data editing in cells

As it has been previously said, UITypeEditor may have only 3 styles: (None, Modal, DropDown). If None style is applied, PropertyGrid uses TextBox. We think that it is a significant limitation as the programmer may desire to place his own control inside a cell and not in a dropbox (e.g. a slider). Dapfor NetGrid expands its features with UITypeEditorEx class. This class has an enhanced set of methods that can be used to create an arbitrary control directly over data cell and to use it for editing.

C# Copy imageCopy
//This editor creates a clickable button inside the cell
class MyEditor : UITypeEditorEx
{
    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.None;
    }

    public override StopEditReason EditCell(IGridEditorService service, Cell cell, StartEditReason reason)
    {
        using(Button button = new Button())
        {
            //Add a reaction on user click
            button.Click += delegate
            {
                //Close control with some reason
                service.CloseCellControl(StopEditReason.UserStop);

                //Set a new value if needed
                cell.Value = _newvalue_;
            };

            //Start editing in cell
            return service.CellEditControl(button, cell.VirtualBounds, reason);
        }
    }
}

Let’s note that user control should be created only for the time of data editing. It is especially important when the grid operates a lot of rows. Controls cannot be created for each row as Windows limits maximum number of controls created by the application. For this purpose UITypeEditorEx provides a feature of drawing control directly inside a cell. This way user feels comfortable when editing data as if he was working with actual controls. However, in reality the grid creates controls only when the user clicks a cell.

C# Copy imageCopy
public class TrackBarEditor : UITypeEditorEx
{
    private readonly int _minValue;
    private readonly int _maxValue;

    //Constructor    
    public TrackBarEditor(int minValue, int maxValue)
    {
        _minValue = minValue;
        _maxValue = maxValue;
    }

    //Indicates that the editor draws the entire cell
    public override bool GetPaintCellSupported()
    {
        return true;
    }

    //Painting routine
    public override void PaintCell(PaintCellEventArgs e)
    {
        //Do basic painting without text
        e.Text = string.Empty;
        e.PaintAll();
        e.Handled = true;


        Rectangle r = e.Cell.VisibleBounds;
        r.Inflate(-4, 0);
        int value = (int) e.Cell.Value;

        int range = _maxValue - _minValue;
        if(range > 0)
        {
            Rectangle channel = new Rectangle(r.X, r.Y + r.Height / 2 - 3, r.Width, 4);
            int offset = (value*(r.Width - 4)/range);
            int x = r.X + offset;
            if(e.Grid._impl.IsRightToLeft)
            {
                x = r.Right - offset - 4;
            }

            Rectangle button = new Rectangle(x, r.Y + 3, 5, r.Height - 6);

            //Draw the track channel
            ControlPaint.DrawBorder3D(e.Graphics, channel, Border3DStyle.SunkenInner);

            //Draw the thumb
            if (Application.RenderWithVisualStyles)
            {
                VisualStyleRenderer renderer = new VisualStyleRenderer(VisualStyleElement.TrackBar.ThumbBottom.Hot);
                renderer.DrawBackground(e.Graphics, button);
            }
            else
            {
                ControlPaint.DrawButton(e.Graphics, button, ButtonState.Normal);
            }
        }
    }

    public override StopEditReason EditCell(IGridEditorService service, Cell cell, StartEditReason reason)
    {
        StopEditReason stopReason = StopEditReason.Undefined;
        //Edit the cell if the user has clicked on the cell with the left button
        if (cell.Row != null && Equals(StartEditReason.LButtonClick, reason))
        {
            cell.EnsureVisible();
            //Create a real control
            using (TrackBar control = new TrackBar())
            {
                control.RightToLeft = cell.Row.Grid._impl.IsRightToLeft ? RightToLeft.Yes : RightToLeft.No;

                //Initialize the control
                control.TabStop = true;
                control.BackColor = cell.Appearance.BackColor;
                control.TickStyle = TickStyle.None;
                control.Minimum = _minValue;
                control.Maximum = _maxValue;
                int value = Convert.ToInt32(cell.Value);
                value = Math.Max(control.Minimum, value);
                value = Math.Min(control.Maximum, value);
                control.Value = value; 

                //Handle the MouseUp events
                control.MouseUp += delegate
                {
                    //Stop edit in place
                    service.CloseCellControl(StopEditReason.UserStop);
                };

                //Start Edit in place. 
                stopReason = service.CellEditControl(control, cell.VirtualBounds, reason);

                //Set a new value to Cell
                cell.Value = Convert.ChangeType(control.Value, cell.DataField.FieldType); 
            }
        }
        return stopReason;
    }
}
Trackbar editor

Specific case – data editing without graphical controls.

In some cases it is not necessary to create controls. This may happen when a data field contains bool value or a star rating assigned by user. Such values don’t require control creation but only painting current state and reacting on user click on a cell.

C# Copy imageCopy
public class RatingEditor : UITypeEditorEx
{
    public override bool GetPaintCellSupported()
    {
        //The cell should be repainted in the editor
        return true;
    }

    public override void PaintCell(PaintCellEventArgs e)
    {
        //Do basic painting without text
        e.Text = string.Empty;
        e.PaintAll();
        e.Handled = true;

        //Draw the stars over the cell
        Rectangle bounds = e.Cell.VirtualBounds;
        bounds.Width = Resources.star_grey.Width;
        for (int i = 0; i < 5; i++)
        {
            //Select image
            if (e.Cell.Value != null && e.Cell.Value is int)
            {
                int curRating = (int) e.Cell.Value;
                Image image = i >= curRating ? Resources.star_grey : Resources.star_yellow;
                e.Graphics.DrawImage(image, bounds);
                bounds.X += bounds.Width;
            }
        }
    }

    public override StopEditReason EditCell(IGridEditorService service, Cell cell, StartEditReason reason)
    {
        //Edit the cell if the user has clicked on the cell with the left button
        if (cell.Row != null && Equals(StartEditReason.LButtonClick, reason))
        {
            Point pt = cell.Row.Grid.PointToClient(Cursor.Position);
            Rectangle bounds = cell.VirtualBounds;

            int imageWidth = Resources.star_grey.Width;
            if (bounds.Contains(pt) && imageWidth > 0)
            {
                //Set a new rating to the data object
                cell.Value = ((pt.X - bounds.X)/imageWidth) + 1;
                cell.Invalidate();
            }
        }

        return StopEditReason.UserStop;
    }
}
Rating editor

Enabling editing

The following conditions should be met for data editing by user:

  • Property of the edited object should have a setter.

  • Column.Editable property should be set to true

Editor setup

There are several methods that can be used for editor setup.

  • As it has been previously said, editors can be set only for object types

    C# Copy imageCopy
    [Editor(typeof(MyEditor), typeof(UITypeEditor))]
    class MyClass
    {
        ...
    }
  • Dapfor has expanded use of these attributes for class properties. For example, the following code can be used for data editing with a rating editor:

    C# Copy imageCopy
    class SomeProduct
    {
        private int _rating;
    
        [Editor(typeof(RatingEditor), typeof(UITypeEditor))]
        public int Rating
        {
            get { return _rating; }
            set { _rating = value; }
        }
    }

    If grid rows contain objects of different types, the grid may use different editors depending on class properties.

  • Editors can be set in columns:
    C# Copy imageCopy
    Header header = grid.Headers[0];
    header["rating"].Editor = new RatingEditor();
  • Editors can also be set directly when the user clicks a cell. This feature can be used to disable editing of some cells:
    C# Copy imageCopy
    grid.CellBeginEdit += delegate(object sender, GridCellBeginEditEventArgs e)
    {
        if (e.Cell.Column != null && e.Cell.Column.Id == "rating")
        {
            e.Editor = new RatingEditor();    
        }
    };

Validation

Users may enter wrong data when editing. A programmer can use validation features to transmit only correct values to data objects. With editors based on UITypeEditorEx this is quite simple as the programmer has full control over data editing and therefore controls the validation process and transmission of new value to a data object.

C# Copy imageCopy
public override StopEditReason EditCell(IGridEditorService service, Cell cell, StartEditReason reason)
{
    using(SomeControl control = new SomeControl())
    {
        button.Click += delegate
        {
            //validate user's input. If data is correct, close the editor
            if(control.IsValid)
            {
                service.CloseCellControl(StopEditReason.UserStop);    
            }
        };

        //Start editing in cell
        return service.CellEditControl(control, cell.VirtualBounds, reason);
    }
}

A similar approach can be used for editors based on UITypeEditor. Besides, the grid provides an interface that controls grid behavior in data editing and validation. The following example shows how to display a tooltip error message upon incorrect data entry.

C# Copy imageCopy
grid.ValidateCell += delegate(object sender, ValidateCellEventArgs e)
{
    e.ErrorText = "Invalid data";
    e.Action = ValidateCellAction.CancelValue;
};
Data validation

Navigation

The grid provides editor navigation feature to make data editing easier for users. It is possible to set the reason for stopping cell editing, such as Esc, Enter, Tab keys, Shift+Tab shortcut, etc. Depending on the reason the grid decides whether editing should be continued or ended. Grid reactions on completion of data sorting in a cell are shown in the table below.

Reason

Reaction

StopEditReason.SelectValue

Navigation to the next editable cell

StopEditReason.Enter

Navigation to the next editable cell

StopEditReason.Tab

Navigation to the next editable cell

StopEditReason.ShiftTab

Navigation to the previous editable cell

StopEditReason.LButtonOutsideClick

Editing a new cell, over which the cursor is

StopEditReason.Escape

End of editing the current cell

StopEditReason.Exception

End of editing the current cell

The grid provides the following 2 callbacks to determine next or previous cell for editing. Therefore, the programmer may specify any desired cell for this purpose.

C# Copy imageCopy
grid.PrevEditableCell += delegate(object sender, GridEditableCellEventArgs e)
{
    e.NewEditableCell = _a_new_editable_cell;
};

grid.NextEditableCell += delegate(object sender, GridEditableCellEventArgs e)
{
    e.NewEditableCell = _a_new_editable_cell;
};

We hope that reading this tutorial will make data editing in NetGrid easy and clear for you. We also hope that efforts of Dapfor’s programmers will result in creation of beautiful and compact applications.