New to Telerik UI for WinForms? Download free 30-day trial

RadGridView CRUD in Object Relational Hierarchy Mode

Product Version Product Author Last modified
2015.1.331 RadGridView for WinForms Hristo Merdjanov April 27, 2015

Problem

Due to performance considerations, the RadGridView bound to an object-relational hierarchy supports CRUD operations only for its first level. The following example demonstrates how this functionality can be extended and CRUD be implemented for the inner levels as well.

Solution

Keep track of the changes and update the data source object accordingly. This can be accomplished by handling the following events:

  • CellValueChanged - update the data source when a value is modified in the grid
  • UserAddingRow - update the data source when a new row is being added
  • UserAddedRow - refresh the GridViewTemplate associated with the added row
  • UserDeletedRow - update the data source when a row is deleted

Telerik UI for WinForms suite offers a RadVirtualGrid control which provides a convenient way to implement your own data management operations and optimizes the performance when interacting with large amounts of data. It may also be a good approach to follow.

Let`s first create our models according to which we will build the hierarchy:

[Serializable]
public class Artist
{
    private int id;
    private string name;
    private List<Album> albums = new List<Album>();

    public Artist()
    {
    }

    public Artist(int id, string name)
    {
        this.id = id;
        this.name = name;
    }

    public int Id
    {
        get { return id; }
        set { id = value; }
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public List<Album> Albums
    {
        get { return albums; }
    }
}

[Serializable]
public class Album
{
    private int id;
    private int artistId;
    private string title;
    private List<Track> tracks = new List<Track>();

    public Album()
    {
    }

    public Album(int id, int artistId, string title)
    {
        this.id = id;
        this.artistId = artistId;
        this.title = title;
    }

    public int Id
    {
        get { return id; }
        set { id = value; }
    }

    public int ArtistId
    {
        get { return artistId; }
        set { artistId = value; }
    }

    public string Title
    {
        get { return title; }
        set { title = value; }
    }

    public List<Track> Tracks
    {
        get { return this.tracks; }
    }
}

[Serializable]
public class Track
{
    private int id;
    private string name;
    private string mediaType;
    private string genre;
    private string size;

    public Track()
    {
    }

    public Track(int id, string name, string mediaType, string genre, string size)
    {
        this.id = id;
        this.name = name;
        this.mediaType = mediaType;
        this.genre = genre;
        this.size = size;
    }

    public int Id
    {
        get { return id; }
        set { id = value; }
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public string MediaType
    {
        get { return mediaType; }
        set { mediaType = value; }
    }

    public string Genre
    {
        get { return genre; }
        set { genre = value; }
    }

    public string Size
    {
        get { return size; }
        set { size = value; }
    }
}

We also need a data context to which we are going to bind the RadGridView:

public class DataContext
{
    private static List<Artist> artistsField = null;
    private static List<Artist> topArtistsField = null;

    protected DataContext()
    {
    }

    public static List<Artist> Artists
    {
        get
        {
            if (artistsField == null)
            {
                XmlSerializer mySerializer = new XmlSerializer(typeof(List<Artist>));
                FileStream myFileStream = new FileStream(@"..\..\artists.xml", FileMode.Open);
                artistsField = (List<Artist>)mySerializer.Deserialize(myFileStream);
            }

            return artistsField;
        }
    }

    public static List<Artist> TopArtists
    {
        get
        {
            if (topArtistsField == null)
            {
                using (MemoryStream stream = new MemoryStream(Resources.ChinookModel))
                {
                    XmlSerializer mySerializer = new XmlSerializer(typeof(List<Artist>));
                    FileStream myFileStream = new FileStream(@"..\..\artists.xml", FileMode.Open);
                    topArtistsField = (List<Artist>)mySerializer.Deserialize(myFileStream);

                    while (topArtistsField.Count > 50)
                    {
                        topArtistsField.RemoveAt(topArtistsField.Count - 1);
                    }
                }
            }

            return topArtistsField;
        }
    }
}

Back in our form, now we need to set the data source and set up the templates:

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);

    this.radGridView1.DataSource = DataContext.Artists;
    this.radGridView1.AutoGenerateHierarchy = true;

    this.SetupTemplates();
}

private void SetupTemplates()
{
    this.radGridView1.EnableFiltering = true;
    this.radGridView1.Columns["Id"].IsVisible = false;
    this.radGridView1.Columns["Albums"].IsVisible = false;
    this.radGridView1.AutoSizeColumnsMode = GridViewAutoSizeColumnsMode.Fill;

    GridViewTemplate albumsTemplate = this.radGridView1.Templates[0];
    albumsTemplate.AllowAddNewRow = true;
    albumsTemplate.Columns["Id"].IsVisible = false;
    albumsTemplate.Columns["ArtistId"].IsVisible = false;
    albumsTemplate.Columns["Tracks"].IsVisible = false;
    albumsTemplate.AutoSizeColumnsMode = GridViewAutoSizeColumnsMode.Fill;

    GridViewTemplate tracksTemplate = albumsTemplate.Templates[0];
    tracksTemplate.AllowAddNewRow = true;
    tracksTemplate.Columns["Id"].IsVisible = false;
    tracksTemplate.Columns["Size"].IsVisible = false;
    tracksTemplate.AutoSizeColumnsMode = GridViewAutoSizeColumnsMode.Fill;
}

In the handler of the CellValueChanged event we should track the changes and update the data bound object:

private void radGridView1_CellValueChanged(object sender, GridViewCellEventArgs e)
{
    if (e.Row is GridViewNewRowInfo || e.Row is GridViewFilteringRowInfo)
    {
        return;
    }

    if (e.Row.HierarchyLevel > 0)
    {
        this.SetBoundValue(e.Row.DataBoundItem, e.Column.FieldName, e.Value);
        e.Row.InvalidateRow();
    }
}

private bool SetBoundValue(object dataBoundItem, string propertyName, object value)
{
    PropertyDescriptor descriptor = TypeDescriptor.GetProperties(dataBoundItem).Find(propertyName, true);
    if (value != null)
    {
        try
        {
            Type type = Nullable.GetUnderlyingType(descriptor.PropertyType);
            if (descriptor.Converter != null && type != null && type.IsGenericType)
            {
                value = descriptor.Converter.ConvertFromInvariantString(value.ToString());
            }
            descriptor.SetValue(dataBoundItem, value);
            return true;
        }
        catch
        {
            RadMessageBox.Show(string.Concat("Invalid property value for ", propertyName), "Error", MessageBoxButtons.OK, RadMessageIcon.Error);
            return false;
        }
    }
    else
    {
        try
        {
            descriptor.SetValue(dataBoundItem, value);
        }
        catch
        {
            descriptor.SetValue(dataBoundItem, DBNull.Value);
        }
    }

    return true;
}

Now we need to handle the events concerning the addition and deletion of rows for the inner levels of our hierarchy:

private void radGridView1_UserAddingRow(object sender, GridViewRowCancelEventArgs e)
{
    GridViewRowInfo row = e.Rows[0];
    if (row.HierarchyLevel == 0)
    {
        return;
    }

    GridViewRelation relation = this.radGridView1.Relations.Find(row.ViewTemplate.Parent, row.ViewTemplate);
    GridViewRowInfo parentRow = row.Parent as GridViewRowInfo;
    PropertyDescriptorCollection parentProperties = ListBindingHelper.GetListItemProperties(parentRow.DataBoundItem);
    PropertyDescriptor childDescriptor = parentProperties.Find(relation.ChildColumnNames[0], true);
    if (childDescriptor != null)
    {
        IList children = childDescriptor.GetValue(parentRow.DataBoundItem) as IList;
        if (children != null)
        {
            object newItem = Activator.CreateInstance(ListBindingHelper.GetListItemType(children));
            bool success = true;
            foreach (GridViewColumn column in row.ViewTemplate.Columns)
            {
                if (column.IsVisible && !column.ReadOnly && row.Cells[column.FieldName].Value != null && success)
                {
                    success = success & this.SetBoundValue(newItem, column.FieldName, row.Cells[column.FieldName].Value);
                }
            }

            if (!success)
            {
                e.Cancel = true;
            }
            else
            {
                children.Add(newItem);
            }
        }
    }
}

private void radGridView1_UserAddedRow(object sender, GridViewRowEventArgs e)
{
    e.Row.ViewTemplate.Refresh();
}

private void radGridView1_UserDeletedRow(object sender, GridViewRowEventArgs e)
{
    GridViewRowInfo[] rows = e.Rows;
    for (int i = 0; i < (int)rows.Length; i++)
    {
        GridViewRowInfo row = rows[i];
        if (row.HierarchyLevel != 0)
        {
            GridViewRelation relation = this.radGridView1.Relations.Find(row.ViewTemplate.Parent, row.ViewTemplate);
            GridViewRowInfo parentRow = row.Parent as GridViewRowInfo;
            PropertyDescriptorCollection parentProperties = ListBindingHelper.GetListItemProperties(parentRow.DataBoundItem);
            PropertyDescriptor childDescriptor = parentProperties.Find(relation.ChildColumnNames[0], true);
            if (childDescriptor != null)
            {
                IList children = childDescriptor.GetValue(parentRow.DataBoundItem) as IList;
                if (children != null)
                {
                    children.Remove(row.DataBoundItem);
                    row.ViewInfo.Refresh();

                    foreach (var childRow in row.ViewInfo.ChildRows)
                    {
                        childRow.InvalidateRow();
                    }
                }
            }
        }
    }
}

You can download a VB and C# project from the following link.

In this article