I tried making it using DataGridView. If there are 1000 items, it will take some time to adjust the height, so some kind of ingenuity will be necessary. I fixed it a little.
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 static Padding cellPadding = new Padding(8);
private static Padding bubblePadding = new Padding(4);
private const int bubbleRadius = 8;
private readonly BindingList<Comment> comments
= new BindingList<Comment>();
class Comment
{
public string Text { get; set; }
public bool IsMine { get; set; }
[Browsable(false)] public Size TextSize;
[Browsable(false)] public int ProposeWidth;
[Browsable(false)] public Font ProposeFont;
public Comment(string text, bool isMine) {
Text = text;
IsMine = isMine;
}
public bool IsChanged(int width, Font font) {
if (ProposeWidth != width) {
Save(width, font);
return true;
}
if (ProposeFont == null || ProposeFont != font) {
Save(width, font);
return true;
}
return false;
}
private void Save(int width, Font font) {
ProposeWidth = width;
ProposeFont = font;
}
}
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;
}
dataGridView1.Columns[1].Visible = false;
}
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
dataGridView1.Columns[0].DefaultCellStyle.Font ??
dataGridView1.DefaultCellStyle.Font ??
dataGridView1.Font;
}
}
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];
if (comment.IsChanged(ProposeWidth, ProposeFont)) {
comment.TextSize =
TextRenderer.MeasureText(
g, comment.Text, ProposeFont,
new Size(ProposeWidth, 1), textFormatFlags);
}
int height =
comment.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,
e.CellBounds.Top + cellPadding.Top);
textColor = Color.White;
bubbleColor = Color.DodgerBlue;
} else {
bubbleLocation = new Point(e.CellBounds.Left,
e.CellBounds.Top + cellPadding.Top);
textColor = Color.Black;
bubbleColor = SystemColors.Window;
}
Rectangle bubbleArea = new Rectangle(bubbleLocation, bubbleSize);
FillRoundRectangle(e.Graphics, bubbleArea, bubbleRadius, 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, int radius, Color color) {
using (var path = GetRoundedRectangle(bounds, radius))
using (var brush = new SolidBrush(color)) {
g.FillPath(brush, path);
}
}
private static GraphicsPath GetRoundedRectangle(Rectangle baseRect, int radius) {
GraphicsPath path = new GraphicsPath();
path.AddArc(baseRect.X, baseRect.Y, radius * 2, radius * 2, 180, 90);
path.AddArc(baseRect.Right - radius * 2, baseRect.Y, radius * 2, radius * 2, 270, 90);
path.AddArc(baseRect.Right - radius * 2, baseRect.Bottom - radius * 2, radius * 2, radius * 2, 0, 90);
path.AddArc(baseRect.X, baseRect.Bottom - radius * 2, radius * 2, radius * 2, 90, 90);
path.CloseFigure();
return path;
}
}