Charles Jenkins
2021-09-27T20:28:35Z
For best performance, I'd like to use cell styles everywhere, but cache them so that the number of styles kept in memory and referenced by the cells is as small as possible.

To do that I'd need a cache that's like a dictionary where each entry has a unique key based on the actual settings in a style. It occurred to me that if only the style settings object were stored as a structure instead of a class, the style could be its own cache key.

Here's what I came up with. This first draft looks good in my testing: I created a table of 10,000 rows that only ended up having 10 cell styles, and the scrolling performance is awesome, even over Remote Desktop!

But I'd like to hear what others think, in case my code has some sort of fatal flaw...

The first file defines a struct which can contain the properties of a cell style:

// File: TenTecCellStylePattern.cs

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

using TenTec.Windows.iGridLib;

namespace VirtualGrid.Infrastructure {

  public struct TenTecCellStylePattern : IEquatable<TenTecCellStylePattern> {

    //--------------------------------------
    #region Properties
    //--------------------------------------

    public Color BackColor { get; set; }

    public iGIndent ContentIndent { get; set; }

    public iGCustomDrawFlags CustomDrawFlags { get; set; }

    public iGCellEditorBase CustomEditor { get; set; }

    public IiGDropDownControl DropDownControl { get; set; }

    public iGEmptyStringAs EmptyStringAs { get; set; }

    public iGBool Enabled { get; set; }

    public iGCellFitContentsInViewport FitContentsInViewport { get; set; }

    public iGCellFlags Flags { get; set; }

    public Font Font { get; set; }

    public Color ForeColor { get; set; }

    public IFormatProvider FormatProvider { get; set; }

    public string FormatString { get; set; }

    public iGContentAlignment ImageAlign { get; set; }

    public ImageList ImageList { get; set; }

    public int MaxInputLength { get; set; }

    public iGBool ReadOnly { get; set; }

    public iGBool Selectable { get; set; }

    public iGBool SingleClickEdit { get; set; }

    public ISite Site { get; set; }

    public iGContentAlignment TextAlign { get; set; }

    public iGStringFormatFlags TextFormatFlags { get; set; }

    public iGTextPosToImage TextPosToImage { get; set; }

    public iGStringTrimming TextTrimming { get; set; }

    public iGCellType Type { get; set; }

    public iGCellTypeFlags TypeFlags { get; set; }

    public Type ValueType { get; set; }

    #endregion Properties

    //--------------------------------------
    #region Public Methods
    //--------------------------------------

    public static TenTecCellStylePattern Create( iGCellStyle style ) {
      return new TenTecCellStylePattern {
        BackColor = style.BackColor,
        ContentIndent = style.ContentIndent,
        CustomDrawFlags = style.CustomDrawFlags,
        CustomEditor = style.CustomEditor,
        DropDownControl = style.DropDownControl,
        EmptyStringAs = style.EmptyStringAs,
        Enabled = style.Enabled,
        FitContentsInViewport = style.FitContentsInViewport,
        Flags = style.Flags,
        Font = style.Font,
        ForeColor = style.ForeColor,
        FormatProvider = style.FormatProvider,
        FormatString = style.FormatString,
        ImageAlign = style.ImageAlign,
        ImageList = style.ImageList,
        MaxInputLength = style.MaxInputLength,
        ReadOnly = style.ReadOnly,
        Selectable = style.Selectable,
        SingleClickEdit = style.SingleClickEdit,
        Site = style.Site,
        TextAlign = style.TextAlign,
        TextFormatFlags = style.TextFormatFlags,
        TextPosToImage = style.TextPosToImage,
        TextTrimming = style.TextTrimming,
        Type = style.Type,
        TypeFlags = style.TypeFlags,
        ValueType = style.ValueType
      };
    }

    /// <summary>
    /// We override GetHashCode() because we're supposed to, in case the cache dictionary
    /// calls it to short-circuit calling Equals()
    /// </summary>

    public override int GetHashCode() {
      var n = 43270662;
      n = n * -1521134295 + BackColor.GetHashCode();
      n = n * -1521134295 + ContentIndent.GetHashCode();
      n = n * -1521134295 + CustomDrawFlags.GetHashCode();
      n = n * -1521134295 + CustomEditor?.GetHashCode() ?? 0;
      n = n * -1521134295 + DropDownControl?.GetHashCode() ?? 0;
      n = n * -1521134295 + EmptyStringAs.GetHashCode();
      n = n * -1521134295 + Enabled.GetHashCode();
      n = n * -1521134295 + FitContentsInViewport.GetHashCode();
      n = n * -1521134295 + Flags.GetHashCode();
      n = n * -1521134295 + Font?.GetHashCode() ?? 0;
      n = n * -1521134295 + ForeColor.GetHashCode();
      n = n * -1521134295 + FormatProvider?.GetHashCode() ?? 0;
      n = n * -1521134295 + FormatString?.GetHashCode() ?? 0;
      n = n * -1521134295 + ImageAlign.GetHashCode();
      n = n * -1521134295 + ImageList?.GetHashCode() ?? 0;
      n = n * -1521134295 + MaxInputLength.GetHashCode();
      n = n * -1521134295 + ReadOnly.GetHashCode();
      n = n * -1521134295 + Selectable.GetHashCode();
      n = n * -1521134295 + SingleClickEdit.GetHashCode();
      n = n * -1521134295 + Site?.GetHashCode() ?? 0;
      n = n * -1521134295 + TextAlign.GetHashCode();
      n = n * -1521134295 + TextFormatFlags.GetHashCode();
      n = n * -1521134295 + TextPosToImage.GetHashCode();
      n = n * -1521134295 + TextTrimming.GetHashCode();
      n = n * -1521134295 + Type.GetHashCode();
      n = n * -1521134295 + TypeFlags.GetHashCode();
      n = n * -1521134295 + ValueType?.GetHashCode() ?? 0;
      return n;
    }

    public override bool Equals( object obj ) =>
      ( obj is TenTecCellStylePattern other && this.Equals( other ) );

    bool IEquatable<TenTecCellStylePattern>.Equals( TenTecCellStylePattern other ) {
      return (
        BackColor == other.BackColor
        && ContentIndent.EqualsTo( ContentIndent )
        && CustomDrawFlags == other.CustomDrawFlags
        && CustomEditor == other.CustomEditor
        && DropDownControl == other.DropDownControl
        && EmptyStringAs == other.EmptyStringAs
        && Enabled == other.Enabled
        && FitContentsInViewport == other.FitContentsInViewport
        && Flags == other.Flags
        && Font.Equals( Font, Font )
        && ForeColor == other.ForeColor
        && FormatProvider == other.FormatProvider
        && FormatString == other.FormatString
        && ImageAlign == other.ImageAlign
        && ImageList == other.ImageList
        && MaxInputLength == other.MaxInputLength
        && ReadOnly == other.ReadOnly
        && Selectable == other.Selectable
        && SingleClickEdit == other.SingleClickEdit
        && Site == other.Site
        && TextAlign == other.TextAlign
        && TextFormatFlags == other.TextFormatFlags
        && TextPosToImage == other.TextPosToImage
        && TextTrimming == other.TextTrimming
        && Type == other.Type
        && TypeFlags == other.TypeFlags
        && ValueType == other.ValueType
      );
    }

    #endregion Public Methods

  }
}

The second file is the cache:

// File: TenTecCellStyleCache.cs

using System;
using System.Collections.Generic;

using TenTec.Windows.iGridLib;

namespace VirtualGrid.Infrastructure {

  public class TenTecCellStyleCache {

    //--------------------------------------
    #region Public Methods
    //--------------------------------------

    public int Count =>
      m_cache.Count;

    public void Clear() =>
      m_cache.Clear();

    public iGCellStyle FindOrAdd( iGCellStyle originalStyle, Action<iGCellStyle> modifier = null ) {

      // Determine the exact style characteristics we're looking up

      iGCellStyle candidateStyle;
      if ( modifier != null ) {
        // Clone original style and call modifier() to make changes to the new copy
        candidateStyle = originalStyle.Clone();
        modifier( candidateStyle );
      } else {
        // No changes; caller meant for the original style to be our candidate
        candidateStyle = originalStyle;
      }

      // Turn those characteristics into a single key for the cache dictionary

      var key = TenTecCellStylePattern.Create( candidateStyle );

      // If a copy is found in the dictionary, return it so our candidate can possibly be garbage collected

      if ( m_cache.TryGetValue( key, out iGCellStyle cachedStyle ) ) {
        return cachedStyle;
      }

      // No cached copy found, so we add our candidate to the cache and return it

      m_cache[ key ] = candidateStyle;
      return candidateStyle;
    }

    #endregion Public Methods

    //--------------------------------------
    #region Fields
    //--------------------------------------

    private readonly Dictionary<TenTecCellStylePattern, iGCellStyle> m_cache = new Dictionary<TenTecCellStylePattern, iGCellStyle>();

    #endregion Fields

  }
}

And the way you make use of it is:

// Code from whatever method gets called to load data into the grid named "Grid"
      var styleCache = new TenTecCellStyleCache();

      var g = Grid;
      g.BeginUpdate();

      var cols = g.Cols;
      var rows = g.Rows;

      rows.Clear();

      foreach ( var myDataObject in myDataObjectCollection ) {

        // Add the row

        var row = rows.Add();

        // Fill in cell values

        var cells = row.Cells;

        // Whatever code is necessary to set cells[ index ].Value = one of myDataObject's properties for each column in the grid

        // For this example, give the each cell in the row a style based on it's column's style, but with colors defined by the represented object's properties

        for ( int index = 0; index < cols.Count; ++index ) {
          var col = cols[ index ];
          var cell = cells[ index ];
          var newStyle = styleCache.FindOrAdd(
            col.CellStyle,
            style => {
              var colors = // code to get colors from the proper data object, e.g. myDataObject.GetCellColors()
              style.ForeColor = colors.ForeColor;
              style.BackColor = colors.BackColor;
            }
          );
          if ( cell.Style != newStyle ) {
            cell.Style = newStyle;
          }
        }
      }
      g.EndUpdate();
Igor/10Tec
2021-09-28T07:11:16Z
A nice idea and implementation. And, I think the Knowledge Base section is a better place for this. Moving it to this section.

Do you really save much RAM with this approach? What are other benefits else?

In a real-world app, when the app has many forms with iGrids, the cell style cache must be an object on the app level. Then all forms containing grids can use this cache without duplicating same styles in every form. Have you tried to use your cache this way?
Charles Jenkins
2021-09-28T12:15:12Z
Thank you for the kind words, Igor!

I believe I read in the documentation that re-using cell styles would help the grid operate faster than it would when manipulating each cell's individual properties. That's what I was going for, but since my method still involves creating and releasing style objects, I don't know if it truly acheives that benefit. But at least this way you can use styles exclusively, for every cell, without eating up RAM by creating thousands of CellStyle objects that are all in fact identical.

I never thought about making the cache global. I think that might be a good approach. I'm going to try it in my app.
Charles Jenkins
2021-09-28T15:12:19Z
Igor, is there a way to get the base cell style that would be used in the grid, even before columns are created?

I'm trying the global cache idea, and I need some style as a starting point before creating columns, in order that the columns' cell styles can also be cached as we create each column.
Igor/10Tec
2021-09-28T15:14:49Z
Originally Posted by: Charles Jenkins 

I believe I read in the documentation that re-using cell styles would help the grid operate faster than it would when manipulating each cell's individual properties.


Yes. This is a question of grid design. If you definitely know beforehand you are going to apply a similar formatting to a huge number of cells, then it's much better to create a cell style with the corresponding settings and use it in those cells instead of setting the corresponding properties of every cell to the same values.

If you do not know what formatting you will apply, but expect the formatting options may repeat for several cells - then this is the case when your approach saves memory.