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();