-1

I have the following code working, and commented out the Text being drawn onto the control as I started out using this as a graphic only control. Moving on, I have switched the control to be RichTextBox instead of Control so that I can have all the goodies that come with it such as clickable hyperlinks, selectable text, and so on.

The problem I face, is the text can be selected, and even copied, but it is invisible, and is also not positioned exactly where I would like it (uncomment the text print lines in the Messages.OnPaint() method to see how I wish the text to appear)

PRETTY PICTURE OF PROBLEM enter image description here

PRETTY PICTURE OF WHAT I AM TRYING TO ACCOMPLISH enter image description here

PRETTY PICTURE OF WHAT IT LOOKS LIKE WITH THE TEXT PRINT CODE UNCOMMENTED enter image description here

THE CODE

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Runtime.InteropServices;

using System.Diagnostics;

public class MessageControl : ScrollableControl {

    public List<Message> Messages { get; private set; }

    private Color _LeftBubbleColor=Color.FromArgb(217,217,217);
    private Color _RightBubbleColor=Color.FromArgb(192,206,215);
    private Color _LeftBubbleTextColor=Color.FromArgb(52,52,52);
    private Color _RightBubbleTextColor=Color.FromArgb( 52, 52, 52 );
    private bool _DrawArrow=true;
    private int _BubbleIndent=40;
    private int _BubbleSpacing=10;

    private int _TruncateHistory=0;

    public enum BubblePositionEnum { Left, Right }

    public Color LeftBubbleColor { get { return _LeftBubbleColor; } set {_LeftBubbleColor = value; } }
    public Color RightBubbleColor { get { return _RightBubbleColor; } set { _RightBubbleColor=value; } }
    public Color LeftBubbleTextColor { get { return _LeftBubbleTextColor; } set { _LeftBubbleTextColor=value; } }
    public Color RightBubbleTextColor { get { return _RightBubbleTextColor; } set { _RightBubbleTextColor=value; } }
    public int BubbleIndent { get { return _BubbleIndent; } set { _BubbleIndent = value; } }
    public int BubbleSpacing { get { return _BubbleSpacing; } set { _BubbleSpacing=value; } }
    public bool DrawArrow { get { return _DrawArrow; } set { _DrawArrow = value; } }
    public int TruncatHistory { get { return _TruncateHistory; } set { _TruncateHistory = value; } }

    public MessageControl() {
        Messages = new List<Message>();
        SetStyle( ControlStyles.AllPaintingInWmPaint|ControlStyles.OptimizedDoubleBuffer|ControlStyles.ResizeRedraw|ControlStyles.SupportsTransparentBackColor|ControlStyles.UserPaint, true );
        DoubleBuffered=true;
        BackColor=Color.Orange;
        Anchor=AnchorStyles.Top|AnchorStyles.Left|AnchorStyles.Right|AnchorStyles.Bottom;
        AutoScroll=true;
    }

    public void Remove( Message message ) {
        this.Invalidate();
        Messages.Remove( message );
        RedrawControls();
    }

    public void Remove( Message[] messages ) {
        foreach ( Message m in messages ) {
            Messages.Remove( m );
        }
        RedrawControls();
        this.Invalidate();
    }

    public void Add( string Message, BubblePositionEnum Position ) {
        if ( Messages.Count>0 ) {
            Message m = Messages[Messages.Count-1];
            if ( m.BubblePosition==Position ) {
                Message=m.Text+"\n"+Message;
                Remove(m);
            }
        }

        Message b = new Message(Position);
        if ( Messages.Count>0 ) {
            b.Top=Messages[Messages.Count-1].Top+Messages[Messages.Count-1].Height+_BubbleSpacing;
        } else {
            b.Top=_BubbleSpacing;
        }

        b.Text = Message;
        b.DrawBubbleArrow=_DrawArrow;

        if ( VerticalScroll.Visible ) {
            b.Width=Width-( _BubbleIndent+_BubbleSpacing+SystemInformation.VerticalScrollBarWidth );
        } else {
            b.Width=Width-( _BubbleIndent+_BubbleSpacing );
        }
        if ( Position==BubblePositionEnum.Right ) {
            b.Left = _BubbleIndent;
            b.BubbleColor = _RightBubbleColor;
            b.ForeColor = _RightBubbleTextColor;
        } else {
            b.Left = _BubbleSpacing;
            b.BubbleColor=_LeftBubbleColor;
            b.ForeColor=_LeftBubbleTextColor;
        }

        this.Messages.Add(b);
        this.Controls.Add(b);

        if ( Messages.Count>_TruncateHistory&&_TruncateHistory>0 ) {
            Remove( Messages[0] );
            this.Invalidate();
        }
        base.ScrollControlIntoView(this.Controls[Controls.Count-1]);
    }

    public void Truncate( int count ) {
        if ( count>=( Controls.Count-1 ) ) {
//          Controls.Clear();
            Messages.Clear();
        } else if ( count>1 ) {
            int x=0;
            while ( x<count&&x<=Controls.Count ) {
                Messages.RemoveAt( 0 );
                x++;
            }
        } else {
            Messages.RemoveAt(0);
        }
        this.Invalidate();
        RedrawControls();

    }

    protected override void OnResize( System.EventArgs e ) {
        RedrawControls();
        base.OnResize( e );
    }

    private void RedrawControls() {
        int count=0;
        Message last=null;
        int new_width=this.Width;
        this.Controls.Clear();
        VerticalScroll.Visible = false;
        this.SuspendLayout();
        foreach ( Message m in this.Messages ) {
            if ( count>0 ) {
                m.Top=last.Top+last.Height+_BubbleSpacing;
                if ( VerticalScroll.Visible ) {
                    m.Width=new_width-( _BubbleIndent+_BubbleSpacing+SystemInformation.VerticalScrollBarWidth );
                } else {
                    m.Width=new_width-( _BubbleIndent+_BubbleSpacing );
                }
            } else {
                m.Top=_BubbleSpacing;
                if ( VerticalScroll.Visible ) {
                    m.Width=new_width-( _BubbleIndent+_BubbleSpacing+SystemInformation.VerticalScrollBarWidth );
                } else {
                    m.Width=new_width-( _BubbleIndent+_BubbleSpacing );
                }
            }
            last=m;
            count++;
        }
        this.Controls.AddRange(Messages.ToArray<Message>());
        if ( this.Controls.Count>0 ) {
            base.ScrollControlIntoView( this.Controls[Controls.Count-1] );
        }
        this.ResumeLayout();
    }

    public class Message : RichTextBox {
        private GraphicsPath Shape;
        private Color _TextColor=Color.FromArgb( 52, 52, 52 );
        private Color _BubbleColor=Color.FromArgb( 217, 217, 217 );
        private bool _DrawBubbleArrow=true;
        private BubblePositionEnum _BubblePosition = BubblePositionEnum.Left;

        public override Color ForeColor { get { return this._TextColor; } set { this._TextColor=value; this.Invalidate(); } }
        public BubblePositionEnum BubblePosition { get { return this._BubblePosition; } set { this._BubblePosition=value; this.Invalidate(); } }
        public Color BubbleColor { get { return this._BubbleColor; } set { this._BubbleColor=value; this.Invalidate(); } }
        public bool DrawBubbleArrow { get { return _DrawBubbleArrow; } set { _DrawBubbleArrow=value; Invalidate(); } }
        public Message(BubblePositionEnum Position) {
            _BubblePosition=Position;
            SetStyle( ControlStyles.AllPaintingInWmPaint|ControlStyles.OptimizedDoubleBuffer|ControlStyles.ResizeRedraw|ControlStyles.SupportsTransparentBackColor|ControlStyles.UserPaint, true );
            DoubleBuffered=true;
            Size=new Size( 152, 38 );
            BackColor=Color.Transparent;
            ForeColor=Color.FromArgb( 52, 52, 52 );
            Font=new Font( "Segoe UI", 10 );
            Anchor=AnchorStyles.Top|AnchorStyles.Left|AnchorStyles.Right;
            BorderStyle=System.Windows.Forms.BorderStyle.None;
            ScrollBars = RichTextBoxScrollBars.None;
        }

        [DllImport( "kernel32.dll", CharSet=CharSet.Auto )]
        static extern IntPtr LoadLibrary( string lpFileName );

        protected override CreateParams CreateParams {
            get {
                CreateParams prams=base.CreateParams;
                if ( LoadLibrary( "msftedit.dll" )!=IntPtr.Zero ) {
                    prams.ExStyle|=0x020; // transparent  
                    prams.ClassName="RICHEDIT50W";
                }
                return prams;
            }
        }

        protected override void OnResize( System.EventArgs e ) {
            SuspendLayout();
            Shape=new GraphicsPath();

            var _Shape=Shape;
            if ( BubblePosition==BubblePositionEnum.Left ) {
                _Shape.AddArc( 9, 0, 10, 10, 180, 90 );
                _Shape.AddArc( Width-11, 0, 10, 10, -90, 90 );
                _Shape.AddArc( Width-11, Height-11, 10, 10, 0, 90 );
                _Shape.AddArc( 9, Height-11, 10, 10, 90, 90 );
            } else {
                _Shape.AddArc( 0, 0, 10, 10, 180, 90 );
                _Shape.AddArc( Width-18, 0, 10, 10, -90, 90 );
                _Shape.AddArc( Width-18, Height-11, 10, 10, 0, 90 );
                _Shape.AddArc( 0, Height-11, 10, 10, 90, 90 );
            }
            _Shape.CloseAllFigures();

            Bitmap B=new Bitmap( this.Width, this.Height );
            Graphics G=Graphics.FromImage( B );

            SizeF s=G.MeasureString( Text, Font, Width-25 );
            this.Height=(int)( Math.Floor( s.Height )+10 );

            ResumeLayout();
            //Invalidate();
            base.OnResize( e );
        }

        protected override void OnClick( EventArgs e ) {
            //MessageBox.Show( base.Height.ToString() );
            base.OnClick( e );
        }

        protected override void OnPaint( PaintEventArgs e ) {
            base.OnPaint( e );
            Bitmap B=new Bitmap( this.Width, this.Height );
            Graphics G=Graphics.FromImage( B );

            B=new Bitmap( this.Width, this.Height );
            G=Graphics.FromImage( B );
            var _G=G;

            _G.SmoothingMode=SmoothingMode.HighQuality;
            _G.PixelOffsetMode=PixelOffsetMode.HighQuality;
            _G.Clear( BackColor );

            // Fill the body of the bubble with the specified color
            _G.FillPath( new SolidBrush( _BubbleColor ), Shape );
            // Draw the string specified in 'Text' property
//          if ( _BubblePosition==BubblePositionEnum.Left ) {
//              _G.DrawString( Text, Font, new SolidBrush( ForeColor ), new Rectangle( 13, 4, Width-19, Height-5 ) );
//          } else {
//              _G.DrawString( Text, Font, new SolidBrush( ForeColor ), new Rectangle( 5, 4, Width-19, Height-5 ) );
//          }

            // Draw a polygon on the right side of the bubble
            if ( _DrawBubbleArrow==true ) {
                if(_BubblePosition == BubblePositionEnum.Left) {
                    Point[] p = {
                        new Point(9, 9),
                        new Point(0, 15),
                        new Point(9, 20)
                   };
                    _G.FillPolygon( new SolidBrush( _BubbleColor ), p );
                    _G.DrawPolygon( new Pen( new SolidBrush( _BubbleColor ) ), p );
                } else {
                    Point[] p = {
                        new Point(Width - 8, 9),
                        new Point(Width, 15),
                        new Point(Width - 8, 20)
                    };
                    _G.FillPolygon( new SolidBrush( _BubbleColor ), p );
                    _G.DrawPolygon( new Pen( new SolidBrush( _BubbleColor ) ), p );
                }
            }
            G.Dispose();
            e.Graphics.InterpolationMode=InterpolationMode.HighQualityBicubic;
            e.Graphics.DrawImageUnscaled( B, 0, 0 );
            B.Dispose();
        }
    }
}

Just in case it wasn't clear, I am not looking to paint the text on as you can see I have no problems with this. I wish to bring the natural text forward over the graphic while maintaining all the capabilities of using a RichTextBox control such as

  • Clickable Links
  • Inline font-style changes
  • Possibility of embedding emotes (images)
  • Fully selectable text
  • Right-click options
  • other things not listed, but are super cool to have in a chat/message control.

(for those of you bearing with this as I bring this to life, due to community support, I will be releasing the final version of this control back into the community -- highly dependant on progression ^^ -- as you can tell, the brunt of the work is done, so aside from this minute detail, everything should be in order to add all the other goodies. )

Kraang Prime
  • 9,981
  • 10
  • 58
  • 124
  • Can you try appending the text to the RichText box after Graphic is added? – aspiring Jun 11 '15 at 08:36
  • The text is inside the box already, but not visible. You can select it and copy it out. Also, setting the Text property after the graphic is added creates and endless flicker as it attempts to draw the control over and over again, and still, no visible text – Kraang Prime Jun 11 '15 at 08:39
  • No idea why you get a down vote here. Anyone why is the downvote? @SanuelJackson do you use any `backgroundworker/threads`? It did help me when using heavy graphics. – aspiring Jun 11 '15 at 08:44
  • @aspiring - not sure why the random downvote -- didn't even notice tbh :) . No need for worker threads when using suspend/resume . The control I built also supports auto-truncating so use of that feature should also reduce it's memory and refresh footprint. – Kraang Prime Jun 11 '15 at 08:49
  • Good one. Does this help you? http://stackoverflow.com/questions/6132487/drawing-transparent-text-via-c-sharp-graphics-but-in-a-way-that-it-turns-the?rq=1 – aspiring Jun 11 '15 at 08:52
  • @aspiring - unfortunately no. I have no problems printing text --- I could do that with or without the richtextbox control. My problem is having the richtextbox controls text come forward over the graphic. e.Graphics.Text ... is not selectable, nor does it allow for individual clicky hyperlinks and such – Kraang Prime Jun 11 '15 at 08:56
  • If you grab the above code, and uncomment the lines in Message.OnPaint(), you will understand that printing the text, isn't the issue. I did add an image, so you can see, the text is in the box but not visible, nor does the selection color show up when selecting the text. Look at skype for example, you can select the text OVER the image. – Kraang Prime Jun 11 '15 at 08:58
  • Added another photo so that the issue is illustrated better as well as what I am trying to achieve. Selectable clickable text with hyperlinks and eventually image support (aka, emotes). – Kraang Prime Jun 11 '15 at 09:06
  • 2
    Maybe you should make `Message` a user control that does not derive from `RichTextBox`, but does contain one, and also draws a message bubble's border around it. – Dialecticus Jun 11 '15 at 10:00
  • Have it partially sorted. I just need to know what Style flags to set to show I only want to control painting of the background, and not the foreground. – Kraang Prime Jun 11 '15 at 16:49
  • @Dialecticus - You are correct. In my tests, any attempt to clip or adjust even the background of the RichTextBox have resulted in non-selectable text. What I have done now, is a new Control, and then in the constructor I add the RichTextBox to that control, and scale it according to contents. This seems to work fairly well, and I am now on the next problem here >> http://stackoverflow.com/questions/30794352/how-to-swap-text-in-a-richtextbox-with-images-without-using-the-clipboard – Kraang Prime Jun 12 '15 at 02:36

1 Answers1

0

Instead of drawing stuff in OnPaint of control derived from RichTextBox it's better to draw stuff around the text box, on user control that just contains the text box, and let the text box draws itself.

Dialecticus
  • 16,400
  • 7
  • 43
  • 103
  • I disagree with it being better, but it is definitely much easier. The richtextbox is not friendly for wrapping in any case, and this is one example of that. Another example would be programming margins inside the box for all 4 directions, or replacing text with a png image that has a transparent background - without using a hack (the copy/paste trick). To date, this is the only control that when making transparent, buggers up the contents and causes wacky indescribable bad behaviour while drawing, limiting this to solid color backgrounds only. – Kraang Prime Jun 14 '15 at 11:27
  • I have succumbed to the ease of this method for lack of a more direct approach. After some time travelling through the rabbit hole, it just keeps getting deeper (adjusting for scrolling, etc), and really ends up closer to me just making a RichTextBox control from scratch using Graphics, so .... onto a parent control it goes adding padding. I really wanted to avoid this. – Kraang Prime Jun 17 '15 at 17:30