如何在允许在缩进区域上绘画的形状控件中创建缩进?

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

我有一个自定义控件,我正在向其应用形状。该形状在左侧或右侧有一个指针(类似于 Skype 中的消息指示器,显示消息是已接收消息还是已发送消息)。

当指示器位于左侧时,控件完全按预期绘制,但当我将指示器置于右侧时,指示器位于右侧控件边界之外。

我想要的缩进,是为了画左边的"avatar"。我还将在时间戳右侧添加另一个缩进。

为了让事情更清楚,这里是当前和 objective 行为的图像:

我使用的代码如下:

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();
    }

}

请记住,这些框的高度会根据内容而增加,并且无论控件的高度如何,指示器都应始终保持相同大小并位于第一行。此功能已经到位,但我觉得有必要提及,因为我认为缩放形状或绘画艺术会导致指针在两个位置和 width/height.

中不一致或变形

如果您注意到,该区域在左侧确实完美地剪裁,并且可以绘制,如图中第一个示例所示,但是如果我将指针向右移动,由于某种原因我无法剪裁左侧再也没有了,这是我问题的核心。

刚注意到 我标记为 'Correct' 的图像中的棕色指示器实际上只是几乎正确。消息气泡的右侧应该有圆角,如红色和橙色消息的左侧所示。

我会改变这些地方:

   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 ); //!!

    } 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 ); //!!

请注意,这是未经测试的!