How to create an indent in shaped control that allows painting on indented region?

113 Views Asked by At

I have a custom control, which I am applying a shape to. The shape has a pointer (similar to the message indicator in Skype that shows if the message was a received message, or a sent message) that is on the left or right side.

When the indicator is on the left side, the control draws exactly as intended, but when I put the indicator on the right side, the indicator is placed outside of the controls bounds to the right.

The indent that I want, is for drawing the "avatar" on the left side. I will also be adding another indent on the right for the timestamp.

To help make things clearer, here an image of the current and objective behavior:

enter image description here

The code I am using is as follows:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;



/*
public enum SkyeBubbleColors as Color {
    Right=Color.FromArgb( 229, 247, 253 ),
    Left=Color.FromArgb( 199, 237, 252 )
}
*/

public enum BubbleIndicatorEnum {
    Left,
    Right
}


public class MessageRow : Control, IDisposable {


    public MessageRow() {
        SetStyle( ControlStyles.AllPaintingInWmPaint|ControlStyles.OptimizedDoubleBuffer|ControlStyles.ResizeRedraw|ControlStyles.SupportsTransparentBackColor|ControlStyles.UserPaint, true );
        UpdateStyles();
        DoubleBuffered=true;
        try {
            base.BackColor=this.Parent.BackColor;
        } catch ( Exception ee ) {
            Console.WriteLine( ee.Message );
        }

    }

}

public class Message : Control, IDisposable {


    internal class RTB : RichTextBox {

        protected override void OnLinkClicked( LinkClickedEventArgs e ) {
            System.Diagnostics.Process.Start(e.LinkText);
        }

        public RTB() {
            BorderStyle = System.Windows.Forms.BorderStyle.None;
            BackColor = Color.Orange;
            ForeColor = Color.White;
            ReadOnly = false;
            this.Font=new Font( "Segoe UI", 10 );
            Text="";
            Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top;
            ScrollBars = RichTextBoxScrollBars.None;
        }

    }

    private RTB _Text;

    private bool _TimestampVisible = false;

    private bool _AvatarVisible=false;
    private int _AvatarWidth { get; set; }
    private Image _Avatar;
    public Image Avatar { 
        get { return _Avatar; }
        set { 
            _Avatar = value;
            if ( _Avatar!=null ) {
                _Avatar = ResizeImage(value, 28, 28);
                _AvatarWidth=40;
            } else {
                _Avatar = value;
                _AvatarWidth = 0;
            }
        }
    }

    public bool AvatarVisible {
        get { return _AvatarVisible; }
        set { _AvatarVisible = value; }
    }

    public Message() {
        SetStyle( ControlStyles.AllPaintingInWmPaint|ControlStyles.OptimizedDoubleBuffer|ControlStyles.ResizeRedraw|ControlStyles.SupportsTransparentBackColor|ControlStyles.UserPaint, true );
        UpdateStyles();
        DoubleBuffered = true;
        _Text = new RTB();
        _Text.ContentsResized+=_Text_ContentsResized;
        _Text.Location = new Point(15+_AvatarWidth,5);
        _Text.Width = Width - (15+_AvatarWidth);
        this.Controls.Add( _Text );
        _Text.Visible = true;
        BubbleIndicator=BubbleIndicatorEnum.Left;
        try {
            base.BackColor=this.Parent.BackColor;
        } catch ( Exception ee ) {
            Console.WriteLine(ee.Message);
        }
        if ( _TimestampVisible ) {

        }
    }

    /// <summary>
    /// Resize the image to the specified width and height.
    /// </summary>
    /// <param name="image">The image to resize.</param>
    /// <param name="width">The width to resize to.</param>
    /// <param name="height">The height to resize to.</param>
    /// <returns>The resized image.</returns>
    private static Bitmap ResizeImage( Image image, int width, int height ) {
        var destRect=new Rectangle( 0, 0, width, height );
        var destImage=new Bitmap( width, height );

        destImage.SetResolution( image.HorizontalResolution, image.VerticalResolution );

        using ( var graphics=Graphics.FromImage( destImage ) ) {
            graphics.CompositingMode=CompositingMode.SourceCopy;
            graphics.CompositingQuality=CompositingQuality.HighQuality;
            graphics.InterpolationMode=InterpolationMode.HighQualityBicubic;
            graphics.SmoothingMode=SmoothingMode.HighQuality;
            graphics.PixelOffsetMode=PixelOffsetMode.HighQuality;

            using ( var wrapMode=new ImageAttributes() ) {
                wrapMode.SetWrapMode( WrapMode.TileFlipXY );
                graphics.DrawImage( image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode );
            }
        }

        return destImage;
    }

    void _Text_ContentsResized( object sender, ContentsResizedEventArgs e ) {
        _Text.Height = e.NewRectangle.Height;
        base.Height = _Text.Height + 10;
    }

    private BubbleIndicatorEnum _BubbleIndicator;

    public BubbleIndicatorEnum BubbleIndicator {
        get { return _BubbleIndicator; }
    set{
            _BubbleIndicator=value;
            if ( value==BubbleIndicatorEnum.Left ) {
                _Text.Location=new Point( 15+_AvatarWidth, 5 );
            } else {
                _Text.Location=new Point( 5+_AvatarWidth, 5 );
            }
            _Text.Width=Width-(20+_AvatarWidth);
            UpdateBubbleIndicator();
            _Text.Refresh();
            Invalidate();
        }
    }

    protected override void OnRegionChanged( EventArgs e ) {
        base.OnRegionChanged( e );
    }

    protected override void OnLocationChanged( EventArgs e ) {
        base.OnLocationChanged( e );
        try {
            base.BackColor=this.Parent.BackColor;
        } catch ( Exception ee ) {
            Console.WriteLine( ee.Message );
        }
    }

    public override string Text {
        get {
            return _Text.Text;
        }
        set {
            _Text.Text = value;
            //_Text.RenderEmotes();
        }
    }

    public override Color ForeColor {
        get {
            return _Text.ForeColor;
        }
        set {
            _Text.ForeColor=value;
        }
    }

    public override Font Font {
        get {
            return _Text.Font;
        }
        set {
            _Text.Font=value;
        }
    }

    private Color _BubbleColor = Color.Orange;

    public Color BubbleColor {
        get {
            return _BubbleColor;
        }
        set {
            _BubbleColor=value;
            _Text.BackColor=value;
            _Text.Refresh();
            Invalidate();
        }
    }


    private GraphicsPath Shape = new GraphicsPath();

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

    private void UpdateBubbleIndicator() {
        _Text.BackColor=_BubbleColor;
        Shape=new GraphicsPath();

        if ( BubbleIndicator==BubbleIndicatorEnum.Left ) {
            Shape.AddArc( 9+_AvatarWidth, 0, 10, 10, 180, 90 );
            Shape.AddArc( (Width+_AvatarWidth)-11, 0, 10, 10, -90, 90 );
            Shape.AddArc( ( Width+_AvatarWidth )-11, Height-11, 10, 10, 0, 90 );
            Shape.AddArc( 9+_AvatarWidth, 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.AddArc( 0+_AvatarWidth, 0, 10, 10, 180, 90 );
//              Shape.AddArc( ( Width+_AvatarWidth )-18, 0, 10, 10, -90, 90 );
//              Shape.AddArc( ( Width+_AvatarWidth )-18, Height-11, 10, 10, 0, 90 );
//              Shape.AddArc( 0+_AvatarWidth, Height-11, 10, 10, 90, 90 );
        }

        Shape.CloseAllFigures();
        _Text.Width=Width-(20+_AvatarWidth);
    }



    protected override void OnPaint( PaintEventArgs e ) {

        Point[] p;

        if ( BubbleIndicator==BubbleIndicatorEnum.Left ) {
            p= new Point[] {
                    new Point(9+_AvatarWidth, 9),
                    new Point(0+_AvatarWidth, 15),
                    new Point(9+_AvatarWidth, 20)
            };
        } else {
            p = new Point[] {
                    new Point(Width - 8, 9),
                    new Point(Width, 15),
                    new Point(Width - 8, 20)
            };
        }

        SuspendLayout();
        e.Graphics.Clear( BackColor );
        e.Graphics.SmoothingMode=SmoothingMode.HighQuality;
        e.Graphics.PixelOffsetMode=PixelOffsetMode.HighQuality;
        e.Graphics.FillPath( new SolidBrush( _BubbleColor ), Shape );
        e.Graphics.FillPolygon( new SolidBrush( _BubbleColor ), p );
        e.Graphics.DrawPolygon( new Pen( new SolidBrush( _BubbleColor ) ), p );
        e.Graphics.InterpolationMode=InterpolationMode.HighQualityBicubic;
        if ( _AvatarWidth>0&&_AvatarVisible==true&&_Avatar!=null ) {
            e.Graphics.DrawImageUnscaled( _Avatar, 0, 0);
            e.Graphics.DrawImageUnscaled( new Bitmap( this.Width-_AvatarWidth, this.Height ), _AvatarWidth, 0 );
        } else {
            e.Graphics.DrawImageUnscaled( new Bitmap( this.Width, this.Height ), 0, 0 );
        }
        base.OnPaint( e );
        ResumeLayout();
    }

}

Please keep in mind that these boxes grow in height depending on the contents, and the indicator should always be the same size and on the first row no matter the height of the control. This functionality is already in place, but I felt it necessary to mention as I believe scaling the shape or drawing art would cause the pointer to be inconsistent or misshapen in both location, and in width/height.

If you notice, the area does clip perfectly on the left, and is paintable as shown in the first example in the image, but if i move the pointer to the right, for some reason I can not clip the left anymore, and this is the heart of my problem.

JUST NOTICED The brown indicator in the image I have flagged as 'Correct' is actually only almost correct. The right side of the message bubble should have rounded corners as seen in the left side of the red and orange messages.

1

There are 1 best solutions below

3
On BEST ANSWER

I would change these spots:

   if ( BubbleIndicator==BubbleIndicatorEnum.Left ) {
        Shape.AddArc( 9+_AvatarWidth, 0, 10, 10, 180, 90 );
        Shape.AddArc( (Width/*+_AvatarWidth*/)-11, 0, 10, 10, -90, 90 ); // !!
        Shape.AddArc( (Width/*+_AvatarWidth*/ )-11, Height-11, 10, 10, 0, 90 ); //!!

and

    } else {
        Shape.AddArc( _AvatarWidth, 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(  _AvatarWidth, Height-11, 10, 10, 90, 90 ); //!!

Note that this is untested!