CellFormatting performance issue

8 posts, 0 answers
  1. Andy
    Andy avatar
    9 posts
    Member since:
    Nov 2010

    Posted 21 Jun 2011 Link to this post

    Hi,

    I'm using Version 2011.1.11.419, and have been tasked to do some performance tuning I noticed that the Cellformatting event gets called a bit more often than I would like, especially at form loading.  I found that if I place the code to set the datasource for the radGridView in the load event of the form, that it gets called much more frequently than if placed in the forms constructor.

    In the code snippets below, I'm creating a simple grid with 5 columns and 2 rows - all of type GridTextBoxColumn.  When assigning the datasource in the constructor, it generates 25 calls to the CellFormatting event. If I move the datasource assignment to the form load event, it generates 35 calls. The problem is even more pronounced when I call the radGridView.LoadLayout function.  If placed in the constructor,  it still calls the CellFormatting event 25 times, but when I move both the datasource and LoadLayout calls to the form load event, CellFormatting gets called a whopping 90 times!  Note that the xml file that I'm using is nothing crazy - simply the output from saving the contents of this grid in Visual Studio using the radGridView property builder.

    I've tried to use  radGridView.BeginInit, and radGridView.SuspendLayout to decrease the number of calls while the grid is being set up, but neither seem to have any effect.  Should I resign myself to moving radGridView code to the forms constructor, or is there a way to accomplish this in the form load event?

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
     
    using Telerik.WinControls.UI;
     
     
    namespace RadGridTest
    {
        public partial class Form1 : Form
        {
     
            private Telerik.WinControls.UI.RadGridView radGridView1;
            private int execCount = 0;
            private int beginTime;
            private int endTime;
            private BindingList<MyData> list;
     
            public Form1()
            {
               beginTime = Environment.TickCount;
               InitializeComponent();
     
               list = new BindingList<MyData>(); ;
               for (int i = 0; i <= 1; i++)
               {
                   list.Add(new MyData(100, i, i));
               }
               //radGridView1.DataSource = list;
               //radGridView1.LoadLayout("gridFormat.xml");
            }
     
            private void Form1_Load(object sender, EventArgs e)
            {
                // These commented lines cause much additional overhead in calling the CellFormatting function
                // from the form_load event rather than the constructor.
     
                radGridView1.DataSource = list;
                radGridView1.LoadLayout("gridFormat.xml");
     
     
                endTime = Environment.TickCount;
                MessageBox.Show("Load Time: " + (endTime - beginTime).ToString() + Environment.NewLine +
                                "gridformat calls: " + execCount.ToString());
            }
     
            void radGridView1_CellFormatting(object sender, Telerik.WinControls.UI.CellFormattingEventArgs e)
            {
                execCount++;
                if (e.CellElement.ColumnInfo is GridViewDataColumn)
                {
                    GridViewDataColumn dataCol = e.CellElement.ColumnInfo as GridViewDataColumn;
                    if (e.CellElement.Text != "")
                    switch (dataCol.FieldName)
                    {
                        case "B": e.CellElement.Text = Convert.ToDecimal(e.CellElement.Value).ToString("C");
                            break;
                        case "D":
                        case "E":
                            if (Convert.ToDecimal(e.CellElement.Value) % 2 == 0)
                                e.CellElement.Text = Convert.ToDecimal(e.CellElement.Value).ToString("#,##0.0000");
                            else
                                e.CellElement.Text = Convert.ToDecimal(e.CellElement.Value).ToString("#,##0.00");
                            break;
                    }
                }
            }
     
        }
     
        public class MyData
        {
            public string A { get; set; }
            public int B { get; set; }
            public string C { get; set; }
            public int D { get; set; }
            public int E { get; set; }
     
            public MyData(int b, int d, int e)
            {
                A = "Money";
                B = b;
                C = "Float";
                D = d;
                E = e;
            }
        }
    }
      
     
     
    // radGridView designer settings below
            #region Windows Form Designer generated code
     
            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
                Telerik.WinControls.UI.GridViewTextBoxColumn gridViewTextBoxColumn1 = new Telerik.WinControls.UI.GridViewTextBoxColumn();
                Telerik.WinControls.UI.GridViewTextBoxColumn gridViewTextBoxColumn2 = new Telerik.WinControls.UI.GridViewTextBoxColumn();
                Telerik.WinControls.UI.GridViewTextBoxColumn gridViewTextBoxColumn3 = new Telerik.WinControls.UI.GridViewTextBoxColumn();
                Telerik.WinControls.UI.GridViewTextBoxColumn gridViewTextBoxColumn4 = new Telerik.WinControls.UI.GridViewTextBoxColumn();
                Telerik.WinControls.UI.GridViewTextBoxColumn gridViewTextBoxColumn5 = new Telerik.WinControls.UI.GridViewTextBoxColumn();
                this.radGridView1 = new Telerik.WinControls.UI.RadGridView();
                ((System.ComponentModel.ISupportInitialize)(this.radGridView1)).BeginInit();
                ((System.ComponentModel.ISupportInitialize)(this.radGridView1.MasterTemplate)).BeginInit();
                this.SuspendLayout();
                //
                // radGridView1
                //
                this.radGridView1.BackColor = System.Drawing.SystemColors.Control;
                this.radGridView1.Cursor = System.Windows.Forms.Cursors.Default;
                this.radGridView1.Dock = System.Windows.Forms.DockStyle.Fill;
                this.radGridView1.Font = new System.Drawing.Font("Segoe UI", 8.25F);
                this.radGridView1.ForeColor = System.Drawing.SystemColors.ControlText;
                this.radGridView1.ImeMode = System.Windows.Forms.ImeMode.NoControl;
                this.radGridView1.Location = new System.Drawing.Point(0, 0);
                //
                // radGridView1
                //
                this.radGridView1.MasterTemplate.AutoGenerateColumns = false;
                gridViewTextBoxColumn1.FieldName = "A";
                gridViewTextBoxColumn1.FormatInfo = new System.Globalization.CultureInfo("");
                gridViewTextBoxColumn1.FormatString = "";
                gridViewTextBoxColumn1.HeaderText = "column1";
                gridViewTextBoxColumn1.Name = "column1";
                gridViewTextBoxColumn1.Width = 76;
                gridViewTextBoxColumn2.FieldName = "B";
                gridViewTextBoxColumn2.FormatInfo = new System.Globalization.CultureInfo("");
                gridViewTextBoxColumn2.FormatString = "";
                gridViewTextBoxColumn2.HeaderText = "column2";
                gridViewTextBoxColumn2.Name = "column2";
                gridViewTextBoxColumn2.Width = 81;
                gridViewTextBoxColumn3.FieldName = "C";
                gridViewTextBoxColumn3.FormatInfo = new System.Globalization.CultureInfo("");
                gridViewTextBoxColumn3.FormatString = "";
                gridViewTextBoxColumn3.HeaderText = "column3";
                gridViewTextBoxColumn3.Name = "column3";
                gridViewTextBoxColumn3.Width = 82;
                gridViewTextBoxColumn4.FieldName = "D";
                gridViewTextBoxColumn4.FormatInfo = new System.Globalization.CultureInfo("");
                gridViewTextBoxColumn4.FormatString = "";
                gridViewTextBoxColumn4.HeaderText = "column4";
                gridViewTextBoxColumn4.Name = "column4";
                gridViewTextBoxColumn4.Width = 90;
                gridViewTextBoxColumn5.FieldName = "E";
                gridViewTextBoxColumn5.FormatInfo = new System.Globalization.CultureInfo("");
                gridViewTextBoxColumn5.FormatString = "";
                gridViewTextBoxColumn5.HeaderText = "column5";
                gridViewTextBoxColumn5.Name = "ProgressBar";
                gridViewTextBoxColumn5.Width = 88;
                this.radGridView1.MasterTemplate.Columns.AddRange(new Telerik.WinControls.UI.GridViewDataColumn[] {
                gridViewTextBoxColumn1,
                gridViewTextBoxColumn2,
                gridViewTextBoxColumn3,
                gridViewTextBoxColumn4,
                gridViewTextBoxColumn5});
                this.radGridView1.Name = "radGridView1";
                this.radGridView1.Padding = new System.Windows.Forms.Padding(0, 0, 0, 1);
                this.radGridView1.ReadOnly = true;
                this.radGridView1.RightToLeft = System.Windows.Forms.RightToLeft.No;
                //
                //
                //
                this.radGridView1.RootElement.Padding = new System.Windows.Forms.Padding(0, 0, 0, 1);
                this.radGridView1.ShowGroupPanel = false;
                this.radGridView1.Size = new System.Drawing.Size(599, 416);
                this.radGridView1.TabIndex = 0;
                this.radGridView1.Text = "radGridView1";
                this.radGridView1.CellFormatting += new Telerik.WinControls.UI.CellFormattingEventHandler(this.radGridView1_CellFormatting);
                //
                // Form1
                //
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.ClientSize = new System.Drawing.Size(599, 416);
                this.Controls.Add(this.radGridView1);
                this.Name = "Form1";
                this.Text = "Form1";
                this.Load += new System.EventHandler(this.Form1_Load);
                ((System.ComponentModel.ISupportInitialize)(this.radGridView1.MasterTemplate)).EndInit();
                ((System.ComponentModel.ISupportInitialize)(this.radGridView1)).EndInit();
                this.ResumeLayout(false);
     
            }
     
            #endregion

  2. erwin
    erwin avatar
    358 posts
    Member since:
    Dec 2006

    Posted 22 Jun 2011 Link to this post

    I have also noticed that effect. Other telerik controls (for example the TreeView) too behave differently when methods are called in the constructor of a form than when the same method is called in the Form_Load event handler or after a form is shown.

    My guess is that with virtualization the code path is quite different depending on whether or not the objects that finally represent the display are already created. In the constructor, when the form is not visible yet, there should be no need to call the CellFormatting event handlers at all unless you do stuff like Best Fit ...

    Also I have noticed that CellFormatting gets called sometimes when it is not needed, since the Cell does not have to be redrawn. Probably it's not always easy to find out which cells actually need redrawing/reformatting and to be on the safe side CellFormatting is called more than needed.

    Anyway it's very important to keep the CellFormatting Event handler really performant. If you need to lookup some data, make sure it is in efficient memory structures. Stuff like file IO or DB access is a definite no-no in the CellFormatting event handler.

    If your code just sets some formatting based on the cell value itself like in your example, the performance impact is minimal compared to the time spent in RadGrid logic and the time needed to actually draw to the screnn.

    Erwin
  3. UI for WinForms is Visual Studio 2017 Ready
  4. Andy
    Andy avatar
    9 posts
    Member since:
    Nov 2010

    Posted 22 Jun 2011 Link to this post

    Thanks Erwin  - We've found some other odd behavior that I'm wondering if you or the forum has seen... 

    In our experimenting, we've also found that we get different results if the radGridView.LoadLayout is placed in the constructor, or the OnLoad event. In our case, we are using an editable grid for a line of business application for line items on an order. We're using the CellFormat event to format the decimal places of specifc fields.  When we instantiate/load the form, we wish to load the users last selected layout from the xml form. If we put the loadlayout call in either the constructor or OnLoad event, we get the same (correct) visual appearance of the grid (i.e. column order, column widths, column visibility).  If the loadlayout call is in the OnLoad event, all of the decimals are formatted nicely.  If, however, the loadlayout call is in the form's constructor, then when we add a row to the grid, the new row does not get the correct decimal formatting (even though the CellFormatting function is being called).

    To echo your point of CellFormatting being called when not needed - Our production code has an editable radGridView with a filter row, 34 columns (about 1/2 of which are hidden, only 15 are visible), and one row of data. The LoadLayout call for the grid is in the forms OnLoad event. When we add a row, we query an item from the database, then assign the data to each cell in the row (34 columns remember). When I click off of the row, I see that the CellFormatting event was fired 5,238 times. The way that virtualization is described in the documentation, I would expect CellFormatting to get called at most 15 * 5 = 75 times (15 visible columns * 2 data rows, 1 filter row, 1 'new' row, 1 header row).  At 5,238 calls, it has fired 87 times per visible cell on the grid. Even if it fires for all cells in the grid, that's still 32 times per cell.

    Surely I've done something wrong to generate that many calls - I would have expected the BeginInit or SuspendLayout calls to have stopped the CellFormatting event from firing, but so far, the only luck I've had (with limited success) is to add and remove the event handler in code.  I'd be interested to see if anyone else has any suggestons, but at this point I am looking at replacing the CellFormatting event entirely, and manually formatting data before it gets to the grid, or is changed. With this many calls being generated there's no way to optimize the code any further - the simple overhead of making the call starts to become noticeable.
  5. erwin
    erwin avatar
    358 posts
    Member since:
    Dec 2006

    Posted 23 Jun 2011 Link to this post

    Are you loading the grid explicitly in code, manually assigning values to each Cell?

    That could explain the behaviour, since probably at least the whole row has to redraw (probably adjacent rows too) and therefore you have multiple calls to CellFormatting) on each individual assignment.  If you trace out what cells actually call CellFormatting when you assign a value to an individual cell you might see a pattern.

    I assume the grid performance is optimized for cases where you bind to a data source. You might be better off constructing a DataTable with all your values and then binding that to the grid in one call.

    Virtualization pays out when you load thousands of rows (through databinding) in you scenario with only a few rows you might have to pay a price in overhead for the additional complexities of virtualization.

    Regards
    Erwin

  6. Andy
    Andy avatar
    9 posts
    Member since:
    Nov 2010

    Posted 23 Jun 2011 Link to this post

    That was exactly the tip that I needed to resolve this (that and a little luck experimenting ;-)  ).

    I am loading the initial grid as in the original example - by setting the datasource to a bindinglist data type. This portion completes with a high, but acceptable number of calls to CellFormatting.

    The real problem was when the user added a single row to the grid, when performance is particularly important.  After the user has selected an item, I am assigning the new values to the grid using statements similar to these.  With a huge number of breakpoints set, I was able to determine that each cell assignment statement was firing the CellFormatEvent for the entire grid, which is why I was seeing such a large number of calls.
    gvOrderLineRow = radGridOrderLines.CurrentRow;
     
     if (gvOrderLineRow.RowElementType == typeof(GridDataRowElement)
           ||    gvOrderLineRow.RowElementType == typeof(GridNewRowElement))
         {
            gvOrderLineRow.Cells["somecolumn"].Value = someValue;
            gvOrderLineRow.Cells["someothercolumn".Value = someOtherValue;
         }

    With a little more experimentation, I was able to find that the statements below woud suspend most (but not all) of the calls to the CellFormattingEvent.   A notable exception was a call to set the ReadOnly property of the GridViewColumn - some still fire the CellFormatting Event, and some do not.
    radGridOrderLines.MasterTemplate.BeginUpdate();
     
     
    radGridOrderLines.MasterTemplate.EndUpdate();

    Hey Telerik guys - This would be a great tip to add to the gridview documentation under Gridview | Populating with Data |   Tips when binding to Custom Collections......  It managed to shave off about 1.5 seconds from each call
  7. Jack
    Admin
    Jack avatar
    2335 posts

    Posted 27 Jun 2011 Link to this post

    Hi guys,

    Thank you for your feedback. Indeed, there are cases when CellFormatting fires more times. We constantly try to optimize our controls in terms of speed and productivity and we will research this case further. When setting a cell value, we should cover the case where you want to apply some custom formatting or the theme has to apply another cell appearance, that is why you get so many calls to CellFormatting event.

    Jeff, your suggestion regarding MasterTemplate update methods is correct. However, you will get the best effect by calling BeginUpdate/EndUpdate methods of GridTableElement. This will suspend all UI updates. Here is a sample:
    private void Form1_Load(object sender, EventArgs e)
    {
        this.radGridView1.Columns.Add(new GridViewDecimalColumn("ID"));
        this.radGridView1.Columns.Add(new GridViewTextBoxColumn("Name"));
        this.radGridView1.Columns.Add(new GridViewDecimalColumn("Value"));
     
        this.radGridView1.TableElement.BeginUpdate();
        Random r = new Random();
        for (int i = 0; i < 5; i++)
        {
            this.radGridView1.Rows.Add(i, "Row " + i, r.Next(10));
        }
        this.radGridView1.TableElement.EndUpdate(false);
    }

    We will update our documentation accordingly and I updated your Telerik points for this suggestion. Should you have any further questions, do not hesitate to ask.
     
    All the best,
    Jack
    the Telerik team
    Q1’11 SP1 of RadControls for WinForms is available for download; also available is the Q2'11 Roadmap for Telerik Windows Forms controls.
  8. Patrick
    Patrick avatar
    7 posts
    Member since:
    Jul 2011

    Posted 20 Jun 2014 in reply to Jack Link to this post

    I know this is an old post to which I'm responding, but I haven't found one that more closely addresses my issue.  I have a bound grid that provides detail to a master record on a form.  In this case, it's an invoice header & invoice detail.  I'm using entity framework for data access/management.  I assign the binding source's DataSource in the form's load event.  Given the advice in Jack's reply, I called the BeginUpdate & EndUpdate methods at the start/end of the load event.  It reduced the frequency of the CellFormatting event firing from 25 times to 9 when the grid has 2 rows, 4 when there was one row.

    One reason I am using the CellFormatting event is because I have a calculated column that I want formatted.  I discovered that if I set the column's Expression property to perform the calculation, the FormatString then has no effect on the column.

    So I have two questions:

    1) Is there a way to further minimize the firing of the CellFormatting event?

    2) Is the code below the best way to address my way of calculating & formatting a column? (and is CellFormatting the best event to use to perform this?)

            switch (e.Column.Name)
            {
              case "SatisfiedImg":
                //Set the cell image based on the cell value
                  .......
                  break;

              case "TotalAmount":

                  decimal interest = (decimal)rgvInvoiceDetails.Rows[e.RowIndex].Cells[4].Value;
                  decimal principal = (decimal)rgvInvoiceDetails.Rows[e.RowIndex].Cells[5].Value;

                  e.CellElement.Text = (interest + principal).ToString("N", System.Globalization.CultureInfo.InvariantCulture);
                  break;
  9. Dess
    Admin
    Dess avatar
    1601 posts

    Posted 24 Jun 2014 Link to this post

    Hello Patrick,

    Thank you for writing.

    BeginUpdate and EndUpdate methods are a suitable solution to reduce the time when loading data. Another approach is to use Virtual mode. With virtual mode, you can implement your own data management operations. The primary use of virtual mode, however, is to optimize the performance when interacting with large amounts of data.

    As to the question related to the FormatString property for Calculated Columns (Column Expressions), please find attached my sample project, which contains a GridViewDecimalColumn with Expression and it successfully uses the applied FormatString. If you are still experiencing any difficulties with the FormatString property, I would kindly ask you to open a new thread and provide a sample project, which reproduces the undesired behavior. Thus, we would be able to investigate the precise case and suggest a suitable solution. Thank you in advance.

    I hope this information helps. Should you have further questions, I would be glad to help.

    Regards,
    Desislava
    Telerik
     
    Check out Telerik Analytics, the service which allows developers to discover app usage patterns, analyze user data, log exceptions, solve problems and profile application performance at run time. Watch the videos and start improving your app based on facts, not hunches.
     
Back to Top
UI for WinForms is Visual Studio 2017 Ready