Search code examples
c#winformsgraphicstransparencyrichtextbox

Transparent RichTextBox with Graphic not showing Text


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. )


Solution

  • 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.