You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PictureBox中可移动FrameControl效果异常问题排查

问题分析

你遇到的问题主要源于两个核心点:

  1. 拖动Frame时未触发PictureBox全量重绘:当FrameControl移动时,PictureBox没有收到重绘通知,导致旧的半透明背景区域未被更新,只有拖动轨迹处重新绘制了新的半透明区域,留下明显的残留痕迹。
  2. PictureBox默认无缓冲导致闪烁:PictureBox默认未开启双缓冲,重绘过程中容易出现视觉闪烁,进一步放大了显示异常的问题。
解决方案

我们可以通过三步修复问题,同时优化显示效果:

1. 让FrameControl主动触发父控件重绘

在FrameControl的位置或大小发生变化时,主动通知父控件(PictureBox)进行全区域重绘,确保旧的半透明背景被清除,重新绘制当前Frame之外的区域。

2. 开启PictureBox的双缓冲

通过反射开启PictureBox的保护属性DoubleBuffered,解决重绘过程中的闪烁问题,提升视觉流畅度。

3. 优化Paint事件的绘制逻辑

确保每次绘制都正确排除当前Frame的位置,覆盖PictureBox的整个客户区域(如果需要仅覆盖图像显示区域,可额外添加坐标计算逻辑)。

修改后的完整代码
using System;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using DevExpress.XtraEditors;

namespace DXApplication5
{
    public partial class Form1 : XtraForm
    {
        public Form1()
        {
            InitializeComponent();
            // 开启PictureBox双缓冲,解决重绘闪烁问题
            typeof(PictureBox).InvokeMember("DoubleBuffered", 
                BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic, 
                null, pictureBox1, new object[] { true });
            pictureBox1.Paint += PictureBox1_Paint;
        }

        private void PictureBox1_Paint(object sender, PaintEventArgs e)
        {
            if (pictureBox1.Controls.Count > 0)
            {
                var frame = pictureBox1.Controls[0];
                // 排除Frame所在区域,只绘制Frame之外的部分
                e.Graphics.ExcludeClip(frame.Bounds);
                // 用半透明黑色填充整个PictureBox客户区
                using (var brush = new SolidBrush(Color.FromArgb(100, Color.Black)))
                {
                    e.Graphics.FillRectangle(brush, pictureBox1.ClientRectangle);
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            var size = 300;
            var frame = new FrameControl();
            frame.Size = new Size(size, size);
            frame.Location = new Point((pictureBox1.Width - size) / 2, (pictureBox1.Height - size) / 2);
            pictureBox1.Controls.Add(frame);
        }
    }

    public class FrameControl : Control
    {
        public FrameControl()
        {
            SetStyle(ControlStyles.SupportsTransparentBackColor, true);
            DoubleBuffered = true;
            ResizeRedraw = true;
            BackColor = Color.Transparent;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            using (var pen = new Pen(Color.Black, 4))
            {
                pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
                e.Graphics.DrawRectangle(pen, 0, 0, Width - 1, Height - 1);
            }
        }

        // 移动时触发父控件重绘
        protected override void OnMove(EventArgs e)
        {
            base.OnMove(e);
            Parent?.Invalidate();
        }

        // 调整大小时触发父控件重绘
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            Parent?.Invalidate();
        }

        const int WM_NCHITTEST = 0x84;
        const int WM_SETCURSOR = 0x20;
        const int WM_NCLBUTTONDBLCLK = 0xA3;

        protected override void WndProc(ref Message m)
        {
            int borderWidth = 10;
            if (m.Msg == WM_SETCURSOR) /*设置拖动光标为SizeAll*/
            {
                if ((m.LParam.ToInt32() & 0xffff) == 0x2 /*Move状态*/)
                {
                    Cursor.Current = Cursors.SizeAll;
                    m.Result = (IntPtr)1;
                    return;
                }
            }
            if ((m.Msg == WM_NCLBUTTONDBLCLK)) /*禁用双击最大化*/
            {
                m.Result = (IntPtr)1;
                return;
            }
            base.WndProc(ref m);
            if (m.Msg == WM_NCHITTEST)
            {
                var pos = PointToClient(new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16));
                if (pos.X <= ClientRectangle.Left + borderWidth && pos.Y <= ClientRectangle.Top + borderWidth)
                    m.Result = new IntPtr(13); //TOPLEFT
                else if (pos.X >= ClientRectangle.Right - borderWidth && pos.Y <= ClientRectangle.Top + borderWidth)
                    m.Result = new IntPtr(14); //TOPRIGHT
                else if (pos.X <= ClientRectangle.Left + borderWidth && pos.Y >= ClientRectangle.Bottom - borderWidth)
                    m.Result = new IntPtr(16); //BOTTOMLEFT
                else if (pos.X >= ClientRectangle.Right - borderWidth && pos.Y >= ClientRectangle.Bottom - borderWidth)
                    m.Result = new IntPtr(17); //BOTTOMRIGHT
                else if (pos.X <= ClientRectangle.Left + borderWidth)
                    m.Result = new IntPtr(10); //LEFT
                else if (pos.Y <= ClientRectangle.Top + borderWidth)
                    m.Result = new IntPtr(12); //TOP
                else if (pos.X >= ClientRectangle.Right - borderWidth)
                    m.Result = new IntPtr(11); //RIGHT
                else if (pos.Y >= ClientRectangle.Bottom - borderWidth)
                    m.Result = new IntPtr(15); //Bottom
                else
                    m.Result = new IntPtr(2); //Move
            }
        }
    }
}
额外优化(可选)

如果你的需求是半透明背景仅覆盖图像实际显示区域(而非PictureBox整个客户区,因为Zoom模式下可能存在空白边),可以添加以下方法计算图像显示矩形,替换Paint事件中的pictureBox1.ClientRectangle:

private Rectangle GetImageDisplayRectangle(PictureBox pb)
{
    if (pb.Image == null) return pb.ClientRectangle;
    float imageAspect = (float)pb.Image.Width / pb.Image.Height;
    float controlAspect = (float)pb.ClientSize.Width / pb.ClientSize.Height;
    Rectangle rect;

    if (imageAspect > controlAspect)
    {
        int width = pb.ClientSize.Width;
        int height = (int)(pb.ClientSize.Width / imageAspect);
        int y = (pb.ClientSize.Height - height) / 2;
        rect = new Rectangle(0, y, width, height);
    }
    else
    {
        int height = pb.ClientSize.Height;
        int width = (int)(pb.ClientSize.Height * imageAspect);
        int x = (pb.ClientSize.Width - width) / 2;
        rect = new Rectangle(x, 0, width, height);
    }
    return rect;
}

内容的提问来源于stack exchange,提问作者techno

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:41:09