因為公司業務原因,不能上傳原始項目,這是簡化版本。
臨時設計的窗體和氣泡樣式,有需要可以重新設計。效果如下:
主要原理:一個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: