Skip to content

Instantly share code, notes, and snippets.

@KOZ60
Created October 22, 2023 05:51
Show Gist options
  • Save KOZ60/d007bc8216c2e046471abd08ed47e61a to your computer and use it in GitHub Desktop.
Save KOZ60/d007bc8216c2e046471abd08ed47e61a to your computer and use it in GitHub Desktop.
ChatDataGridView.cs
namespace AutoSizeGridDemo
{
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Reflection;
using System.Windows.Forms;
public partial class Form2 : Form
{
private const TextFormatFlags textFormatFlags =
TextFormatFlags.TextBoxControl |
TextFormatFlags.WordBreak;
private const int chatIndent = 48;
private const int borderSize = 1;
private readonly Padding cellPadding = new Padding(8);
private readonly Padding bubblePadding = new Padding(4);
private const int bubbleRadius = 8;
private readonly Color mineTextColor = Color.White;
private readonly Color mineBubbleColor = Color.DodgerBlue;
private readonly Color otherTextColor = Color.Black;
private readonly Color otherBubbleColor = Color.White;
private readonly Font proposeFont = new Font("Calibri", 12F, FontStyle.Regular);
private readonly BindingList<Comment> comments
= new BindingList<Comment>();
class Comment
{
public string Text { get; private set; }
[Browsable(false)] public bool IsMine { get; private set; }
[Browsable(false)] public Size TextSize { get; private set; }
private Font ProposeFont;
private int ProposeWidth;
public Comment(string text, bool isMine) {
Text = text;
IsMine = isMine;
}
private bool IsChanged(Font font, int width) {
if (ProposeFont == null || ProposeFont != font) {
return true;
}
if (ProposeWidth != width) {
return true;
}
return false;
}
public Size MeasureTextSize(Graphics g, Font font, int width) {
if (IsChanged(font, width)) {
TextSize = TextRenderer.MeasureText(
g, Text, font,
new Size(width, 1), textFormatFlags);
ProposeWidth = width;
ProposeFont = font;
}
return TextSize;
}
}
public Form2() {
InitializeComponent();
}
private void Form2_Load(object sender, EventArgs e) {
var pi = typeof(DataGridView).GetProperty("DoubleBuffered",
BindingFlags.NonPublic | BindingFlags.Instance);
if (pi != null) {
pi.SetValue(dataGridView1, true);
}
int index = 1;
for (int i = 0; i < 250; i++) {
comments.Add(new Comment($"{index++}: I am creating a chat application using C# window form. But I have a challenge on how to load chat messages using pagination like what skype does that is assuming a chat has 1000 chats I need to first load the last like 50 messages on the TableLayoutPanel.", false));
comments.Add(new Comment($"{index++}: Wait, I was expecting at a few dozen controls. Creating 1000 controls causes performance issues.", true));
comments.Add(new Comment($"{index++}: Sorry, I should have suggested another method.", true));
comments.Add(new Comment($"{index++}: Hmm, It is trouble. DataGridView and ListView are suitable for displaying from the top, but not for displaying from the bottom.", true));
}
dataGridView1.DataSource = comments;
dataGridView1.ScrollBars = ScrollBars.Vertical;
dataGridView1.AllowUserToAddRows = false;
dataGridView1.RowHeadersVisible = false;
dataGridView1.ColumnHeadersVisible = false;
var column0 = dataGridView1.Columns[0];
if (column0 != null) {
column0.Resizable = DataGridViewTriState.False;
column0.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
column0.ReadOnly = true;
}
}
private void Form2_Shown(object sender, EventArgs e) {
AdjustHeight(dataGridView1.RowCount - 50);
dataGridView1.FirstDisplayedScrollingRowIndex
= dataGridView1.RowCount - 1;
}
private int ProposeWidth {
get {
return
dataGridView1.ClientSize.Width -
SystemInformation.VerticalScrollBarWidth -
chatIndent -
cellPadding.Horizontal;
}
}
private Font ProposeFont {
get {
return proposeFont;
}
}
private void DataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) {
if (e.RowIndex > -1 && e.ColumnIndex == 0) {
DrawCell(e, comments[e.RowIndex]);
e.Handled = true;
}
}
private void DataGridView1_SizeChanged(object sender, EventArgs e) {
AdjustDisplay();
}
private void DataGridView1_Scroll(object sender, ScrollEventArgs e) {
AdjustDisplay();
}
private void AdjustDisplay() {
int start = dataGridView1.FirstDisplayedScrollingRowIndex;
int end = dataGridView1.Rows.GetLastRow(DataGridViewElementStates.Displayed);
AdjustHeight(start - 10, end + 50);
}
private void AdjustHeight(int start) {
AdjustHeight(start, dataGridView1.RowCount - 1);
}
private void AdjustHeight(int start, int end) {
start = Math.Max(start, 0);
end = Math.Min(end, dataGridView1.RowCount - 1);
using (var g = dataGridView1.CreateGraphics()) {
for (int rowIndex = start; rowIndex <= end; rowIndex++) {
var comment = comments[rowIndex];
var row = dataGridView1.Rows[rowIndex];
var textSize = comment.MeasureTextSize(g, ProposeFont, ProposeWidth);
int height = textSize.Height +
cellPadding.Vertical +
bubblePadding.Vertical +
borderSize * 2;
if (row.Height != height) {
row.Height = height;
}
}
}
}
private void DrawCell(DataGridViewCellPaintingEventArgs e, Comment comment) {
var text = comment.Text;
var isMine = comment.IsMine;
using (var brush = new SolidBrush(dataGridView1.BackgroundColor)) {
e.Graphics.FillRectangle(brush, e.CellBounds);
}
var textSize = comment.TextSize;
var bubbleSize = new Size(textSize.Width + bubblePadding.Horizontal,
textSize.Height + bubblePadding.Vertical);
Point bubbleLocation;
Color bubbleColor;
Color textColor;
if (isMine) {
bubbleLocation = new Point(e.CellBounds.Right - bubbleSize.Width - cellPadding.Right,
e.CellBounds.Top + cellPadding.Top);
textColor = mineTextColor;
bubbleColor = mineBubbleColor;
} else {
bubbleLocation = new Point(e.CellBounds.Left + cellPadding.Left,
e.CellBounds.Top + cellPadding.Top);
textColor = otherTextColor;
bubbleColor = otherBubbleColor;
}
Rectangle bubbleArea = new Rectangle(bubbleLocation, bubbleSize);
FillRoundRectangle(e.Graphics, bubbleArea, bubbleColor);
Point textLocation = new Point(bubbleLocation.X + bubblePadding.Left,
bubbleLocation.Y + bubblePadding.Top);
Rectangle textArea = new Rectangle(textLocation, textSize);
TextRenderer.DrawText(
e.Graphics, text, ProposeFont,
textArea, textColor, textFormatFlags);
}
private static void FillRoundRectangle(Graphics g, Rectangle bounds, Color color) {
using (var path = GetRoundedRectangle(bounds))
using (var brush = new SolidBrush(color)) {
g.FillPath(brush, path);
}
}
private static GraphicsPath GetRoundedRectangle(Rectangle bounds) {
GraphicsPath path = new GraphicsPath();
path.AddArc(bounds.X, bounds.Y, bubbleRadius * 2, bubbleRadius * 2, 180, 90);
path.AddArc(bounds.Right - bubbleRadius * 2, bounds.Y, bubbleRadius * 2, bubbleRadius * 2, 270, 90);
path.AddArc(bounds.Right - bubbleRadius * 2, bounds.Bottom - bubbleRadius * 2, bubbleRadius * 2, bubbleRadius * 2, 0, 90);
path.AddArc(bounds.X, bounds.Bottom - bubbleRadius * 2, bubbleRadius * 2, bubbleRadius * 2, 90, 90);
path.CloseFigure();
return path;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment