Created
October 22, 2023 05:51
-
-
Save KOZ60/d007bc8216c2e046471abd08ed47e61a to your computer and use it in GitHub Desktop.
ChatDataGridView.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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