WinForms自定义垂直滚动条内存泄漏问题排查求助
WinForms自定义垂直滚动条内存泄漏问题
问题描述
开发WinForms自定义垂直滚动条控件时,拖动滑块上下移动,应用内存占用持续增长;停止交互后内存也不回落,疑似内存泄漏,但无法定位代码问题。
已尝试操作
- 合理解绑并重新绑定事件处理程序
- 在不需要时尝试释放滑块控件
- 使用
Invalidate()方法刷新控件,问题仍存在 - 手动管理自定义资源(如滑块控件)以防止泄漏
预期行为
交互滚动条时内存占用不应持续增长,停止交互后应恢复正常水平。
代码实现
CustonVScrollBar控件代码
using Chef_s_Codex.CustomUIElements; using System; using System.Drawing; using System.Windows.Forms; namespace Test.Custom { [DefaultEvent("VelChanged")] public partial class CustonVScrollBar : UserControl { public delegate void VelChangedEventHandler(object sender, EventArgs e); public event VelChangedEventHandler VelChanged; private Color chennelColor = Color.Transparent; private Color slederColor = Color.LightGray; private Color focusChennelColor = Color.Transparent; private Color focusSlederColor = Color.Gray; private int chennelWidth = 15; private int slederWidht = 15; private int slederHeight = 75; private int cornerradius = 7; private int maxValue = 100; private int minValue = 0; private int vel = 0; private int scaleValue = 5; private int locYSlider = 0; private bool isMove = false; private int locYLast = 0; private bool isFocus = false; private int deltaY; private RoundedPanel slider; #region Properties [Category("Scrollbar")] [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Color ChennelColor { get { return chennelColor; } set { chennelColor = value; this.BackColor = value; } } [Category("Scrollbar")] [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Color SlederColor { get { return slederColor; } set { slederColor = value; if (slider != null) slider.BackColor = value; } } [Category("Scrollbar")] [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Color FocusChennelColor { get { return focusChennelColor; } set { focusChennelColor = value; } } [Category("Scrollbar")] [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Color FocusSlederColor { get { return focusSlederColor; } set { focusSlederColor = value; } } [Category("Scrollbar")] [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int ChennelWidth { get { return chennelWidth; } set { chennelWidth = value; Invalidate(); } } [Category("Scrollbar")] [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int SlederWidht { get { return slederWidht; } set { slederWidht = value; if (slider != null) slider.Width = value; } } [Category("Scrollbar")] [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int MaxValue { get { return maxValue; } set { maxValue = value; } } [Category("Scrollbar")] [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int MinValue { get { return minValue; } set { minValue = value; } } [Category("Scrollbar")] [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int Vel { get { return vel; } set { if (value > maxValue) vel = maxValue; else if (value < minValue) vel = minValue; else vel = value; locYSlider = UpdateLocYSlider(vel); } } [Category("Scrollbar")] [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int ScaleValue { get { return scaleValue; } set { scaleValue = value; } } [Category("Scrollbar")] [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int SlederHeight { get { return slederHeight; } set { slederHeight = value; if (slider != null) slider.Height = slederHeight; } } [Category("Scrollbar")] [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int CornerRadius { get { return cornerradius; } set { cornerradius = value; } } #endregion public CustonVScrollBar() { InitializeComponent(); this.SetStyle(ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true); roundedPanel1.MouseDown -= CustomScrollbar_MouseDown; roundedPanel1.MouseUp -= CustomScrollbar_MouseUp; roundedPanel1.MouseMove -= CustomScrollbar_MouseMove; roundedPanel1.MouseDown += CustomScrollbar_MouseDown; roundedPanel1.MouseUp += CustomScrollbar_MouseUp; roundedPanel1.MouseMove += CustomScrollbar_MouseMove; if (slederHeight < roundedPanel1.Height) { if (slider != null) { roundedPanel1.Controls.Remove(slider); slider.Dispose(); slider = null; } SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true); slider = new RoundedPanel(); roundedPanel1.Controls.Add(slider); slider.BorderStyle = BorderStyle.None; slider.RadiusBottomLeft = 5; slider.RadiusBottomRight = 5; slider.RadiusTopLeft = 5; slider.RadiusTopRight = 5; slider.Height = slederHeight; slider.Width = slederWidht; slider.BackColor = slederColor; slider.Location = new Point((roundedPanel1.Width - slederWidht) / 2, 20); slider.Enabled = false; } locYSlider = 1; locYLast = 1; vel = UpdateValue(); } protected override void CreateHandle() { base.CreateHandle(); roundedPanel1.BackColor = chennelColor; this.BackColor = this.Parent.BackColor; } #region Callbacks private void CustomScrollbar_MouseMove(object? sender, MouseEventArgs e) { if (slederHeight >= roundedPanel1.Height || !isMove) return; int allowedTop = 1; int allowedBottom = roundedPanel1.Height - 1; deltaY = e.Y - locYLast; if (deltaY == 0) return; int newLocYSlider = locYSlider + deltaY; newLocYSlider = Math.Clamp(newLocYSlider, allowedTop, allowedBottom - slederHeight); if (newLocYSlider == locYSlider) return; // Prevent unnecessary updates locYSlider = newLocYSlider; locYLast = e.Y; vel = UpdateValue(); OnVelChanged(); } protected virtual void OnVelChanged() { VelChanged?.Invoke(this, EventArgs.Empty); } private int UpdateValue() { int availableHeight = this.Height - slederHeight; if (availableHeight <= 0) return minValue; // Prevent division by zero double ratio = (double)(locYSlider) / availableHeight; int value = minValue + (int)((maxValue - minValue) * ratio); slider.Location = new Point(slider.Location.X, locYSlider); return Math.Clamp(value, minValue, maxValue); } private void CustomScrollbar_MouseUp(object? sender, MouseEventArgs e) { slider.BackColor = slederColor; if (slederHeight >= roundedPanel1.Height) return; isMove = false; isFocus = false; } private void CustomScrollbar_MouseDown(object? sender, MouseEventArgs e) { slider.BackColor = focusSlederColor; if (slederHeight >= roundedPanel1.Height) return; isFocus = true; locYLast = e.Y; if (e.Button == MouseButtons.Left) { locYSlider = e.Y - (slederHeight / 2); if (locYSlider < 0) locYSlider = 0; if (locYSlider > roundedPanel1.Height - slederHeight) locYSlider = roundedPanel1.Height - slederHeight; vel = UpdateValue(); OnVelChanged(); isMove = true; } } private int UpdateLocYSlider(int valueSlider) { double d = (double)roundedPanel1.Height / (double)(maxValue - minValue); int y = (int)(valueSlider * d); if (y > roundedPanel1.Height - slederHeight) y -= slederHeight; return y; } #endregion } }
RoundedPanel控件代码
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing.Drawing2D; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Test.Custom { public class RoundedPanel : Panel { private int radiusTopLeft = 0; private int radiusTopRight = 0; private int radiusBottomLeft = 0; private int radiusBottomRight = 0; public bool draw; public bool isTest; Graphics graphics; [Browsable(true)] [Category("Corner Radius")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int RadiusTopLeft { get => radiusTopLeft; set { radiusTopLeft = Math.Max(0, value); Invalidate(); } } [Browsable(true)] [Category("Corner Radius")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int RadiusTopRight { get => radiusTopRight; set { radiusTopRight = Math.Max(0, value); Invalidate(); } } [Browsable(true)] [Category("Corner Radius")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int RadiusBottomLeft { get => radiusBottomLeft; set { radiusBottomLeft = Math.Max(0, value); Invalidate(); } } [Browsable(true)] [Category("Corner Radius")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int RadiusBottomRight { get => radiusBottomRight; set { radiusBottomRight = Math.Max(0, value); Invalidate(); } } public RoundedPanel() { this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true); this.BorderStyle = BorderStyle.None; graphics = this.CreateGraphics(); graphics.SmoothingMode = SmoothingMode.AntiAlias; graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; } private GraphicsPath GetFigurePath(RectangleF rect) { GraphicsPath path = new GraphicsPath(); path.StartFigure(); // Top-left corner if (RadiusTopLeft > 0) path.AddArc(rect.X, rect.Y, RadiusTopLeft * 2, RadiusTopLeft * 2, 180, 90); else path.AddLine(rect.X, rect.Y, rect.X + 1, rect.Y); // Top-right corner if (RadiusTopRight > 0) path.AddArc(rect.Width - RadiusTopRight * 2, rect.Y, RadiusTopRight * 2, RadiusTopRight * 2, 270, 90); else path.AddLine(rect.Width - 1, rect.Y, rect.Width, rect.Y + 1); // Bottom-right corner if (RadiusBottomRight > 0) path.AddArc(rect.Width - RadiusBottomRight * 2, rect.Height - RadiusBottomRight * 2, RadiusBottomRight * 2, RadiusBottomRight * 2, 0, 90); else path.AddLine(rect.Width, rect.Height - 1, rect.Width - 1, rect.Height); // Bottom-left corner if (RadiusBottomLeft > 0) path.AddArc(rect.X, rect.Height - RadiusBottomLeft * 2, RadiusBottomLeft * 2, RadiusBottomLeft * 2, 90, 90); else path.AddLine(rect.X + 1, rect.Height, rect.X, rect.Height - 1); path.CloseFigure(); return path; } protected override void OnResize(EventArgs eventargs) { base.OnResize(eventargs); RectangleF rectangleF = new RectangleF(0, 0, this.Width, this.Height + 1); if (this.Region != null) { this.Region.Dispose(); } using (GraphicsPath path = GetFigurePath(rectangleF)) using (Pen pen = new Pen(this.Parent != null ? this.Parent.BackColor : this.BackColor, 2)) { this.Region = new Region(path); graphics.DrawPath(pen, path); } } protected override void Dispose(bool disposing) { if (disposing) { if (this.Region != null) { this.Region.Dispose(); this.Region = null; } } base.Dispose(disposing); } } }
问题分析与修复方案
内存泄漏根源
RoundedPanel中graphics对象未释放:构造函数中直接创建Graphics实例,但未在Dispose方法中释放,每次创建RoundedPanel都会导致该对象无法被回收。OnResize方法中图形资源重复创建且管理不当:拖动滑块时会间接触发RoundedPanel的Resize事件,频繁创建Region和GraphicsPath,部分资源未被及时回收。- 事件绑定冗余:
CustonVScrollBar构造函数中先解绑再绑定事件,虽不直接泄漏,但可能引发潜在的事件引用问题。
修复步骤
1. 修复RoundedPanel的Graphics资源泄漏
在Dispose方法中添加graphics释放逻辑,同时移除构造函数中预创建Graphics的代码,将绘制逻辑移至OnPaint方法:
public RoundedPanel() { this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true); this.BorderStyle = BorderStyle.None; } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; e.Graphics.CompositingQuality = CompositingQuality.HighQuality; e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; RectangleF rectangleF = new RectangleF(0, 0, this.Width, this.Height + 1); using (GraphicsPath path = GetFigurePath(rectangleF)) using (Pen pen = new Pen(this.Parent != null ? this.Parent.BackColor : this.BackColor, 2)) { this.Region = new Region(path); e.Graphics.DrawPath(pen, path); } } protected override void OnResize(EventArgs eventargs) { base.OnResize(eventargs); Invalidate(); // 触发OnPaint重绘,避免在Resize中直接操作图形资源 } protected override void Dispose(bool disposing) {
相关产品推荐
相关产品推荐

