因为公司业务原因,不能上传原始项目,这是简化版本。
临时设计的窗体和气泡样式,有需要可以重新设计。效果如下:
主要原理:一个TextBlock + 一个三角形
项目结构:
-- Form1 窗体类
-- Item 控件类(气泡)
Form1前端代码:

#region Windows 窗体设计器生成的代码 /// <summary> /// 设计器支持所需的方法 - 不要 /// 使用代码编辑器修改此方法的内容。 /// </summary> private void InitializeComponent() { this.panel1 = new System.Windows.Forms.Panel(); this.textBox1 = new System.Windows.Forms.TextBox(); this.button1 = new System.Windows.Forms.Button(); this.SuspendLayout(); // // panel1 // this.panel1.AutoScroll = true; this.panel1.Location = new System.Drawing.Point(0, 0); this.panel1.Name = "panel1"; this.panel1.Size = new System.Drawing.Size(377, 404); this.panel1.TabIndex = 0; // // textBox1 // this.textBox1.Location = new System.Drawing.Point(0, 406); this.textBox1.Multiline = true; this.textBox1.Name = "textBox1"; this.textBox1.Size = new System.Drawing.Size(377, 65); this.textBox1.TabIndex = 1; // // button1 // this.button1.Location = new System.Drawing.Point(302, 477); this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(75, 23); this.button1.TabIndex = 2; this.button1.Text = "Send"; this.button1.UseVisualStyleBackColor = true; this.button1.Click += new System.EventHandler(this.button1_Click); // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(380, 504); this.Controls.Add(this.button1); this.Controls.Add(this.textBox1); this.Controls.Add(this.panel1); this.Name = "Form1"; this.Text = "Form1"; this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.Panel panel1; private System.Windows.Forms.TextBox textBox1; private System.Windows.Forms.Button button1;
Form类后台代码:

/// <summary> /// 当前消息气泡起始位置 /// </summary> private int top = 0; /// <summary> /// 当前消息气泡高度 /// </summary> private int height = 0; private void button1_Click(object sender, EventArgs e) { AddSendMessage(textBox1.Text); AddReceiveMessage(textBox1.Text); } /// <summary> /// 显示接收消息 /// </summary> /// <param name="model"></param> private void AddReceiveMessage(string content) { Item item = new Item(); item.messageType = WindowsFormsApplication2.Item.MessageType.receive; item.SetWeChatContent(content); //计算高度 item.Top = top + height; top = item.Top; height = item.HEIGHT; //滚动条移动最上方,重新计算气泡在panel的位置 panel1.AutoScrollPosition = new Point(0, 0); panel1.Controls.Add(item); } // <summary> /// 更新界面,显示发送消息 /// </summary> private void AddSendMessage(string content) { Item item = new Item(); item.messageType = WindowsFormsApplication2.Item.MessageType.send; item.SetWeChatContent(content); item.Top = top + height; item.Left = 370 - item.WIDTH; top = item.Top; height = item.HEIGHT; panel1.AutoScrollPosition = new Point(0, 0); panel1.Controls.Add(item); }
Item类前端代码:

#region 组件设计器生成的代码 /// <summary> /// 设计器支持所需的方法 - 不要 /// 使用代码编辑器修改此方法的内容。 /// </summary> private void InitializeComponent() { this.panel1 = new System.Windows.Forms.Panel(); this.lblContent = new System.Windows.Forms.Label(); this.panel1.SuspendLayout(); this.SuspendLayout(); // // panel1 // this.panel1.AutoSize = true; this.panel1.BackColor = System.Drawing.Color.LightGray; this.panel1.Controls.Add(this.lblContent); this.panel1.Location = new System.Drawing.Point(20, 10); this.panel1.MaximumSize = new System.Drawing.Size(370, 400); this.panel1.Name = "panel1"; this.panel1.Padding = new System.Windows.Forms.Padding(10, 10, 5, 10); this.panel1.Size = new System.Drawing.Size(26, 36); this.panel1.TabIndex = 0; // // lblContent // this.lblContent.AutoSize = true; this.lblContent.Font = new System.Drawing.Font("宋体", 16F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Pixel); this.lblContent.ForeColor = System.Drawing.Color.White; this.lblContent.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.lblContent.Location = new System.Drawing.Point(5, 10); this.lblContent.Margin = new System.Windows.Forms.Padding(0); this.lblContent.MaximumSize = new System.Drawing.Size(280, 1000); this.lblContent.Name = "lblContent"; this.lblContent.Size = new System.Drawing.Size(16, 16); this.lblContent.TabIndex = 5; this.lblContent.Text = " "; this.lblContent.Visible = false; // // Item // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoSize = true; this.Controls.Add(this.panel1); this.Name = "Item"; this.Padding = new System.Windows.Forms.Padding(20, 10, 10, 5); this.Size = new System.Drawing.Size(59, 54); this.panel1.ResumeLayout(false); this.panel1.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.Panel panel1; private System.Windows.Forms.Label lblContent;
Item 类后台代码:

/// <summary> /// 本窗体总高度 /// </summary> public int HEIGHT = 40; /// <summary> /// 本窗体总宽度 /// </summary> public int WIDTH = 45; /// <summary> /// 消息类型 /// </summary> public MessageType messageType; public Item() { ///设置控件样式 SetStyle( ControlStyles.AllPaintingInWmPaint | //不闪烁 ControlStyles.OptimizedDoubleBuffer //支持双缓存 , true); InitializeComponent(); this.Paint += Item_Paint; } #region 界面重绘 /// <summary> /// 绘制气泡左上角小箭头 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Item_Paint(object sender, PaintEventArgs e) { //自己发送的消息箭头在右上角 if (messageType == MessageType.send) { Color color = System.Drawing.Color.LightGray; panel1.BackColor = color; Brush brushes = new SolidBrush(color); Point[] point = new Point[3]; point[0] = new Point(WIDTH - 5, 10); point[1] = new Point(WIDTH - 15, 10); point[2] = new Point(WIDTH - 15, 20); e.Graphics.FillPolygon(brushes, point); } else { Color color = System.Drawing.Color.LightGray; Brush brushes = new SolidBrush(color); Point[] point = new Point[3]; point[0] = new Point(10, 10); point[1] = new Point(20, 10); point[2] = new Point(20, 20); e.Graphics.FillPolygon(brushes, point); } } #endregion #region 功能操作 /// <summary> /// 设置气泡内容 /// </summary> /// <param name="type">消息类型</param> /// <param name="content">消息内容</param> public void SetWeChatContent(string content) { lblContent.Text = content; lblContent.Visible = true; HEIGHT += lblContent.Height; WIDTH += lblContent.Width; } #endregion /// <summary> /// 内部类 /// </summary> class MessageItem { public string RESPATH { get; set; } public string RESTYPE { get; set; } } /// <summary> /// 消息类型 /// </summary> public enum MessageType { send, receive }
项目中的一些坑:
1. panel控件出现滚动条后,添加控件时需要重新计算相对位置,不然每个气泡间的间距会变大。比较简单的解决方法:每次添加控件前将滚动条移到最上方,添加完控件后再将滚动条移到最下方。
2. 设置双缓冲和不闪烁
3. 计算气泡位置和绘制小箭头,这个不难但是需要时间,不知道为什么按设计稿设置位置一直出错,对winform理解不够,wpf可能会更自由一点
Github: