如何在允许在缩进区域上绘画的形状控件中创建缩进?
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 ); //!!
请注意,这是未经测试的!
我有一个自定义控件,我正在向其应用形状。该形状在左侧或右侧有一个指针(类似于 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 ); //!!
请注意,这是未经测试的!