Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The Archive Base

The Archive Base Logo The Archive Base Logo

The Archive Base Navigation

  • SEARCH
  • Home
  • About Us
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • Add group
  • Groups page
  • Feed
  • User Profile
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Buy Points
  • Users
  • Help
  • Buy Theme
  • SEARCH
Home/ Questions/Q 6689889
In Process

The Archive Base Latest Questions

Editorial Team
  • 0
Editorial Team
Asked: May 26, 20262026-05-26T05:34:19+00:00 2026-05-26T05:34:19+00:00

Please refer to my question asked at tek-tips.com: http://tek-tips.com/viewthread.cfm?qid=1663735&page=1 As I mentioned in a

  • 0

Please refer to my question asked at tek-tips.com:
http://tek-tips.com/viewthread.cfm?qid=1663735&page=1

As I mentioned in a couple of my other threads, I’m building a control to pretty much replicate the SMS text messaging on the iPhone. This consists of simply a bubble on either side of the control containing text. I already have a working version, but need to re-build it from scratch. I’d like some advice on some things…

What do you think is the best method to store the list of message data? I was thinking using a TCollection, but that could be way too heavy. Currently I’m using a TStringList containing raw text data which is parsed out and translated appropriately. This works great because I don’t have to create any extra objects with loads of unnecessary properties. It’s just…

data syntax:
<user_size><deliminator><user><message_size><deliminator><message>

which could look like:
9|djjd4713023|This is a test message!

characters:
SDTTTTTTTTTSSDTTTTTTTTTTTTTTTTTTTTTTT

user_size = 9
deliminator = |
user = djjd47130
etc.......

Anyway, I expect possibly thousands of messages in this control. Which brings me to my next question. The best way to draw it. Currently, I’m using a TDrawGrid, and am in the process of converting it to a TStringGrid so I can contain the text directly in the grid rather than the TStringList. However that’s where I stopped because I’m wondering if there’s another better way than to use a grid. It’s easy because it automatically manages storing the rect of each cell, etc.

How about using a TImage instead? There’s another concern about the largest possible control size. This control automatically grows higher with the more messages, so again, if there’s for example 1,000 messages, with an average message bubble height of about 80 pixels, that would mean the grid control needs to be 80,000 pixels high. Using a TImage though could be tough, because I would then have to manually calculate the position on that canvas to draw each balloon, similar to how grids internally keep track of that.

By the way, this grid (or otherwise canvas) is inside of a TScrollBox (final control will inherit from a TScrollingWinControl). This is how it can scroll, while the actual canvas its self is much larger than the control, big enough to draw all the message balloons. Scrolling in the control is actually moving up and down in the TScrollBox to see portions of the control canvas displaying the messages.

To summarize the pieces I need to perfect:
– Light-weight method of storing message items in a list (inside grid, string list, collection, or other list?)
– Scrollable canvas with list items of variable height (grid, image, or other list?)
– Allowing maximum number of messages to be kept with variable heights?
– Ability to customize how the control reacts to user actions to automatically scroll up or down

I’m not necessarily asking for a fix for anything, but rather advice to make it the best possible way.

  • 1 1 Answer
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

1 Answer

  • Voted
  • Oldest
  • Recent
  • Random
  1. Editorial Team
    Editorial Team
    2026-05-26T05:34:19+00:00Added an answer on May 26, 2026 at 5:34 am

    If I were you, I’d do something like this:

    unit ChatControl;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Classes, Controls, Graphics;
    
    type
      TUser = (User1 = 0, User2 = 1);
    
      TChatControl = class(TCustomControl)
      private
        FColor1, FColor2: TColor;
        FStrings: TStringList;
        FScrollPos: integer;
        FOldScrollPos: integer;
        FBottomPos: integer;
        FBoxTops: array of integer;
        FInvalidateCache: boolean;
        procedure StringsChanged(Sender: TObject);
        procedure SetColor1(Color1: TColor);
        procedure SetColor2(Color2: TColor);
        procedure SetStringList(Strings: TStringList);
        procedure ScrollPosUpdated;
        procedure InvalidateCache;
      protected
        procedure Paint; override;
        procedure Resize; override;
        procedure CreateParams(var Params: TCreateParams); override;
        procedure WndProc(var Message: TMessage); override;
        function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
          MousePos: TPoint): Boolean; override;
        procedure Click; override;
      public
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
        function Say(const User: TUser; const S: String): Integer;
        procedure ScrollToBottom;
      published
        property Align;
        property Anchors;
        property Cursor;
        property Font;
        property Color1: TColor read FColor1 write SetColor1 default clSkyBlue;
        property Color2: TColor read FColor2 write SetColor2 default clMoneyGreen;
        property Strings: TStringList read FStrings write SetStringList;
        property TabOrder;
        property TabStop;
      end;
    
    procedure Register;
    
    implementation
    
    uses Math;
    
    procedure Register;
    begin
      RegisterComponents('Rejbrand 2009', [TChatControl]);
    end;
    
    { TChatControl }
    
    procedure TChatControl.Click;
    begin
      inherited;
      if CanFocus and TabStop then
        SetFocus;
    end;
    
    constructor TChatControl.Create(AOwner: TComponent);
    begin
      inherited;
    
      DoubleBuffered := true;
    
      FScrollPos := 0;
      FBoxTops := nil;
      InvalidateCache;
    
      FStrings := TStringList.Create;
      FStrings.OnChange := StringsChanged;
      FColor1 := clSkyBlue;
      FColor2 := clMoneyGreen;
    
      FOldScrollPos := MaxInt;
    end;
    
    procedure TChatControl.CreateParams(var Params: TCreateParams);
    begin
      inherited;
      Params.Style := Params.Style or WS_VSCROLL;
    end;
    
    destructor TChatControl.Destroy;
    begin
      FStrings.Free;
      inherited;
    end;
    
    function TChatControl.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
      MousePos: TPoint): Boolean;
    begin
      dec(FScrollPos, WheelDelta);
      ScrollPosUpdated;
    end;
    
    procedure TChatControl.InvalidateCache;
    begin
      FInvalidateCache := true;
    end;
    
    procedure TChatControl.Paint;
    const
      Aligns: array[TUser] of integer = (DT_RIGHT, DT_LEFT);
    var
      Colors: array[TUser] of TColor;
    var
      User: TUser;
      i, y, MaxWidth, RectWidth: integer;
      r, r2: TRect;
      SI: TScrollInfo;
    begin
    
      inherited;
    
      Colors[User1] := FColor1;
      Colors[User2] := FColor2;
    
      y := 10 - FScrollPos;
      MaxWidth := ClientWidth div 2;
    
      Canvas.Font.Assign(Font);
    
      if FInvalidateCache then
        SetLength(FBoxTops, FStrings.Count);
    
      for i := 0 to FStrings.Count - 1 do
      begin
    
        if FInvalidateCache then
          FBoxTops[i] := y + FScrollPos
        else
        begin
          if (i < (FStrings.Count - 1)) and (FBoxTops[i + 1] - FScrollPos < 0) then
            Continue;
          if FBoxTops[i] - FScrollPos > ClientHeight then
            Break;
          y := FBoxTops[i] - FScrollPos;
        end;
    
        User := TUser(FStrings.Objects[i]);
    
        Canvas.Brush.Color := Colors[User];
    
        r := Rect(10, y, MaxWidth, 16);
        DrawText(Canvas.Handle,
          PChar(FStrings[i]),
          Length(FStrings[i]),
          r,
          Aligns[User] or DT_WORDBREAK or DT_CALCRECT);
    
        if User = User2 then
        begin
          RectWidth := r.Right - r.Left;
          r.Right := ClientWidth - 10;
          r.Left := r.Right - RectWidth;
        end;
    
        r2 := Rect(r.Left - 4, r.Top - 4, r.Right + 4, r.Bottom + 4);
        Canvas.RoundRect(r2, 5, 5);
    
        DrawText(Canvas.Handle,
          PChar(FStrings[i]),
          Length(FStrings[i]),
          r,
          Aligns[User] or DT_WORDBREAK);
    
        if FInvalidateCache then
        begin
          y := r.Bottom + 10;
          FBottomPos := y + FScrollPos;
        end;
    
      end;
    
      SI.cbSize := sizeof(SI);
      SI.fMask := SIF_ALL;
      SI.nMin := 0;
      SI.nMax := FBottomPos;
      SI.nPage := ClientHeight;
      SI.nPos := FScrollPos;
      SI.nTrackPos := SI.nPos;
    
      SetScrollInfo(Handle, SB_VERT, SI, true);
    
      if FInvalidateCache then
        ScrollToBottom;
    
      FInvalidateCache := false;
    
    end;
    
    procedure TChatControl.Resize;
    begin
      inherited;
      InvalidateCache;
      Invalidate;
    end;
    
    function TChatControl.Say(const User: TUser; const S: String): Integer;
    begin
      result := FStrings.AddObject(S, TObject(User));
    end;
    
    procedure TChatControl.ScrollToBottom;
    begin
      Perform(WM_VSCROLL, SB_BOTTOM, 0);
    end;
    
    procedure TChatControl.SetColor1(Color1: TColor);
    begin
      if FColor1 <> Color1 then
      begin
        FColor1 := Color1;
        Invalidate;
      end;
    end;
    
    procedure TChatControl.SetColor2(Color2: TColor);
    begin
      if FColor2 <> Color2 then
      begin
        FColor2 := Color2;
        Invalidate;
      end;
    end;
    
    procedure TChatControl.SetStringList(Strings: TStringList);
    begin
      FStrings.Assign(Strings);
      InvalidateCache;
      Invalidate;
    end;
    
    procedure TChatControl.StringsChanged(Sender: TObject);
    begin
      InvalidateCache;
      Invalidate;
    end;
    
    procedure TChatControl.WndProc(var Message: TMessage);
    var
      SI: TScrollInfo;
    begin
      inherited;
      case Message.Msg of
        WM_GETDLGCODE:
          Message.Result := Message.Result or DLGC_WANTARROWS;
        WM_KEYDOWN:
          case Message.wParam of
            VK_UP:
              Perform(WM_VSCROLL, SB_LINEUP, 0);
            VK_DOWN:
              Perform(WM_VSCROLL, SB_LINEDOWN, 0);
            VK_PRIOR:
              Perform(WM_VSCROLL, SB_PAGEUP, 0);
            VK_NEXT:
              Perform(WM_VSCROLL, SB_PAGEDOWN, 0);
            VK_HOME:
              Perform(WM_VSCROLL, SB_TOP, 0);
            VK_END:
              Perform(WM_VSCROLL, SB_BOTTOM, 0);
          end;
        WM_VSCROLL:
          begin
            case Message.WParamLo of
              SB_TOP:
                begin
                  FScrollPos := 0;
                  ScrollPosUpdated;
                end;
              SB_BOTTOM:
                begin
                  FScrollPos := FBottomPos - ClientHeight;
                  ScrollPosUpdated;
                end;
              SB_LINEUP:
                begin
                  dec(FScrollPos);
                  ScrollPosUpdated;
                end;
              SB_LINEDOWN:
                begin
                  inc(FScrollPos);
                  ScrollPosUpdated;
                end;
              SB_PAGEUP:
                begin
                  dec(FScrollPos, ClientHeight);
                  ScrollPosUpdated;
                end;
              SB_PAGEDOWN:
                begin
                  inc(FScrollPos, ClientHeight);
                  ScrollPosUpdated;
                end;
              SB_THUMBTRACK:
                begin
                  ZeroMemory(@SI, sizeof(SI));
                  SI.cbSize := sizeof(SI);
                  SI.fMask := SIF_TRACKPOS;
                  if GetScrollInfo(Handle, SB_VERT, SI) then
                  begin
                    FScrollPos := SI.nTrackPos;
                    ScrollPosUpdated;
                  end;
                end;
            end;
            Message.Result := 0;
          end;
      end;
    end;
    
    procedure TChatControl.ScrollPosUpdated;
    begin
      FScrollPos := EnsureRange(FScrollPos, 0, FBottomPos - ClientHeight);
      if FOldScrollPos <> FScrollPos then
        Invalidate;
      FOldScrollPos := FScrollPos;
    end;
    
    end.
    

    This is ultra-fast even with 10 000 messages.

    Screenshot

    To test it, do something like

    procedure TForm4.Button1Click(Sender: TObject);
    var
      i: integer;
    begin
      ChatControl1.Strings.Clear;
      for i := 0 to StrToInt(LabeledEdit1.Text) - 1 do
        ChatControl1.Say(TUser(Random(2)), RandomString(2, 80));
    end;
    
    procedure TForm4.Edit2KeyPress(Sender: TObject; var Key: Char);
    begin
      Assert(Sender is TEdit);
      if ord(Key) = VK_RETURN then
      begin
        ChatControl1.Say(TUser(TEdit(Sender).Tag), TEdit(Sender).TExt);
        Key := #0;
        TEdit(Sender).Clear;
      end;
    end;
    

    Full source and compiled demo: ChatControlDemo.zip

    Still, there is certainly room for further improvements. For example, it is pretty stupid to recompute the entire cache array when you add a single message to the end of the string list. Clearly, it suffices to simply append the position of this newly added message to the cache array. But I leave that up to you.

    • 0
    • Reply
    • Share
      Share
      • Share on Facebook
      • Share on Twitter
      • Share on LinkedIn
      • Share on WhatsApp
      • Report

Sidebar

Related Questions

Please refer to the topic http://www.codeproject.com/KB/viewstate/SaveViewState.aspx . The topic demonstrates how you can save
Please refer to this background question. After constructing this COUNT, how would I then
action method is not called Please refer to this question - , One of
Firstly, if you're not using 9.1+, please refer to this question . How do
Please refer to my previous question here: Link Here Will this project type be
I have question please refer the following code to understand the question. (I removed
First question here. Please excuse noob errors. I want to be able to refer
Code for the below test: http://jsfiddle.net/fXdjm/ Questions about line heights: Please refer first box.
Please refer this question How to assign multiple names simultaneously to a C# class
Please refer my previous question for code sample Sockets: sometimes (rarely) packets are lost

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help
  • SEARCH

Footer

© 2021 The Archive Base. All Rights Reserved
With Love by The Archive Base

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.