命名空间:CConUI
继承:System.Windows.Forms.Control
定位:自绘图片框,支持边框、BackColor 背景色、多种图片显示模式(含 ARGB 边框色)、鼠标拖动 + 滚轮缩放、右键复位、蚂蚁线(AntLine)子控件联动,以及多种图像区域提取 / 定位方法。
依赖:CConPlus.Enum.ImageSizeMode、CConPlus.GraphicsC.DrawImageInRect、CConPlus.Rect.MoveRectangleInward、ArgbColorEditor(本程序集)、AntLine(本程序集)。
一、快速上手
using CConUI;
// 1. 创建控件并放到容器
PictureBox pb = new PictureBox();
pb.Parent = this;
pb.Dock = DockStyle.Fill;
// 2. 设置图片与显示模式
pb.Image = Image.FromFile("test.png");
pb.ImageSizeMode = CConPlus.Enum.ImageSizeMode.Zoom; // 等比缩放适应
// 3. 边框与背景
pb.BorderSize = 6;
pb.BorderColor = Color.FromArgb(255, 110, 120, 130);
pb.BackColor = Color.FromArgb(255, 50, 50, 50);
// 4. 开启鼠标拖动 + 滚轮缩放 + 右键复位
pb.MouseEnabled = true;
pb.RightRestore = true;
// 5. 监听图片变化事件
pb.Change += (s, e) =>
{
Console.WriteLine("图片已更换");
};
二、构造函数
public PictureBox()
| 项目 | 说明 |
|---|
| 参数 | 无 |
| 作用 | 创建图片框控件 |
| 默认尺寸 | 150 × 150 像素 |
| 默认背景色 | Color.FromArgb(50, 50, 50) |
| 默认边框大小 | 6 像素 |
| 默认边框颜色 | Color.FromArgb(110, 120, 130) |
| 默认图片显示模式 | CConPlus.Enum.ImageSizeMode.None |
| 默认鼠标功能 | MouseEnabled = false |
| 默认右键复位 | RightRestore = true |
| 内嵌子控件 | 自动创建并添加一个 AntLine(Dock = Fill、Visible = false、初始 AntRect = (20,20,100,100)) |
| 绘制样式 | UserPaint | AllPaintingInWmPaint | OptimizedDoubleBuffer | Selectable(可接收滚轮消息) |
| TabStop | true |
var pb = new PictureBox
{
Size = new Size(400, 300),
Dock = DockStyle.Fill
};
panel1.Controls.Add(pb);
三、公开事件
public event EventHandler Change
| 项目 | 说明 |
|---|
| 参数 | EventHandler:sender 为当前 PictureBox,e 为 EventArgs.Empty |
| 触发时机 | Image 属性被赋值时(包括赋相同值也会触发,因为 setter 中无“值相同”判断) |
| 触发顺序 | 先 Invalidate()(重绘),再触发 Change |
| 用途 | 监听图片被替换的事件;例如重置缩放、更新外部状态 |
pb.Change += (s, e) =>
{
// 图片变化后自动复位缩放/平移
pb.Redraw();
};设计器属性窗口 Category 名为 Ccon(注意小写 c)。
四、公开属性
4.1 public CConPlus.Enum.ImageSizeMode ImageSizeMode
| 项目 | 说明 |
|---|
| 类型 | CConPlus.Enum.ImageSizeMode |
| 默认 | CConPlus.Enum.ImageSizeMode.None |
| Category | CCon_图片 |
| 作用 | 图片在边框内侧区域的显示方式;由 CConPlus.GraphicsC.DrawImageInRect 解释,本控件的 CalcImageRectInInner 也按以下三种主要语义处理:
Stretch:拉伸铺满;Zoom:等比缩放适应;其他(None / Center):原始尺寸居中 |
| 副作用 | 赋值后立即 Invalidate() 重绘 |
pb.ImageSizeMode = CConPlus.Enum.ImageSizeMode.Zoom;
4.2 public System.Drawing.Image Image
| 项目 | 说明 |
|---|
| 类型 | System.Drawing.Image |
| 默认 | null |
| Category | CCon_图片 |
| 作用 | 要显示的图片。null 时不绘制图片(只画边框和背景) |
| 副作用 | 赋值后:① Invalidate() 重绘;② 触发 Change 事件 |
| 备注 | 控件**不负责** Dispose 传入的 Image;图片资源由调用方管理 |
pb.Image = Image.FromFile("photo.jpg");
// ...
// 不再使用时可自行释放原图
var old = pb.Image;
pb.Image = null;
if (old != null) old.Dispose();4.3 public int BorderSize
| 项目 | 说明 |
|---|
| 类型 | int |
| 默认 | 6 |
| Category | CCon_边框 |
| 作用 | 边框宽度(像素)。为 0 时不绘制边框,图片绘制区域铺满整个客户区 |
| 备注 | **未做上下限校验**,请自行保证非负;负数会导致 Pen 构造异常或图片区域异常 |
pb.BorderSize = 4;
4.4 public Color BorderColor
| 项目 | 说明 |
|---|
| 类型 | Color |
| 默认 | Color.FromArgb(110, 120, 130) |
| Category | CCon_边框 |
| 作用 | 边框颜色,支持 ARGB 透明度 |
| 编辑器 | ArgbColorEditor:属性面板中显示为色块,点击弹出色板可编辑 Alpha |
pb.BorderColor = Color.FromArgb(200, 0, 120, 215);
4.5 public override Color BackColor
| 项目 | 说明 |
|---|
| 类型 | Color(重写 Control.BackColor) |
| 默认 | Color.FromArgb(50, 50, 50) |
| Category | CCon_颜色 |
| 作用 | 控件背景填充色;在 OnPaintBackground 中用该颜色铺满 ClientRectangle |
| 备注 | 未使用 ARGB 编辑器(跟随普通 ColorEditor),如需透明背景请使用代码设置 Color.FromArgb(...) |
pb.BackColor = Color.Black;
4.6 public bool MouseEnabled
| 项目 | 说明 |
|---|
| 类型 | bool |
| 默认 | false |
| Category | CCon_控制 |
| 作用 | 是否启用鼠标功能: ① 左键按住拖动 → 平移图片; ② 滚轮 → 以鼠标位置为中心缩放图片(0.1× ~ 100×); ③ 右键 → 复位到初始显示(由 RightRestore 控制) |
| 副作用 | 设为 false 时会立即结束拖动、光标恢复为 Cursors.Default 并重绘 |
pb.MouseEnabled = true;
4.7 public bool RightRestore
| 项目 | 说明 |
|---|
| 类型 | bool |
| 默认 | true |
| Category | CCon_控制 |
| 作用 | 开启鼠标功能时,右键松开是否复位(调用 Redraw()) |
| 备注 | **仅在 MouseEnabled = true 时有效** |
pb.MouseEnabled = true;
pb.RightRestore = true; // 右键复位
4.8 public AntLine AntLine
| 项目 | 说明 |
|---|
| 类型 | CConUI.AntLine(本程序集的蚂蚁线控件) |
| 默认 | 非 null;构造函数中已创建并添加 |
| 作用 | 获取/替换图片框上叠加的蚂蚁线子控件;可用于设置选框矩形、联动裁剪区域 |
| setter 行为 | 把传入的 AntLine 设置为子控件(Controls.Add(value)); **注意**:不会自动从 Controls 移除旧的 AntLine,多次赋值可能导致多个 AntLine 叠加 |
| 备注 | 默认 Visible = false;需要显示时自行 pb.AntLine.Visible = true; |
// 显示蚂蚁线
pb.AntLine.Visible = true;
pb.AntLine.AntRect = new Rectangle(50, 50, 200, 150);
// 隐藏蚂蚁线
pb.AntLine.Visible = false;
// 用蚂蚁线选框获取该区域的图片内容
Bitmap crop = pb.GetImageInRectangle(pb.AntLine.AntRect);
if (crop != null) { /* 使用 crop ... */ crop.Dispose(); }
五、公开方法
5.1 public void Redraw()
| 项目 | 说明 |
|---|
| 参数 | 无 |
| 返回 | void |
| 作用 | 复位鼠标拖动与缩放效果,把 _zoom 恢复为 1、_offset 恢复为 (0,0)、清空拖动状态、光标恢复默认,并重绘 |
| 典型场景 | ① 右键复位(由 OnMouseUp 内部触发);② 更换图片后主动复位;③ 提供外部“复位按钮”的功能实现 |
pb.MouseEnabled = true;
pb.RightRestore = true; // 鼠标右键自动复位
// 外部按钮手动复位
btnReset.Click += (s, e) => pb.Redraw();
5.2 public Rectangle GetImageDisplayRectangle()
| 项目 | 说明 |
|---|
| 参数 | 无 |
| 返回 | Rectangle:图片在**控件客户区坐标系**中当前实际显示的矩形(已叠加 zoom + offset) |
| 返回空值情况 | ① Image == null;② 边框内侧无效(BorderSize 过大或控件尺寸过小) |
| 作用 | 用于获知图片当前“画在哪”,可配合蚂蚁线、命中检测等使用 |
Rectangle disp = pb.GetImageDisplayRectangle();
Console.WriteLine("当前图片显示区域:" + disp);5.3 public Rectangle GetVisibleImageRectangle()
| 项目 | 说明 |
|---|
| 参数 | 无 |
| 返回 | Rectangle:GetImageDisplayRectangle() 与边框内侧区域的**交集**(即真正在控件内可见的部分) |
| 返回空值情况 | ① Image == null;② 图片完全拖出可视区域 |
| 作用 | 计算“用户当前能看到的图片范围”,用于精确反馈(如拖动进度、裁剪显示区域) |
Rectangle visible = pb.GetVisibleImageRectangle();
if (visible.IsEmpty)
Console.WriteLine("图片已完全移出可视区域");5.4 public Bitmap GetImageInRectangle(Rectangle rect)
| 参数 | 类型 | 说明 |
|---|
rect | Rectangle | 控件**客户区**坐标系下的矩形区域 |
| 返回 | 说明 |
|---|
Bitmap | 该区域对应的原图裁剪结果。**调用方负责 Dispose()** |
null | ① Image == null;② rect 与边框内侧无交集;③ 图像内容为空白 |
| 项目 | 说明 |
|---|
| 作用 | 将“用户看到的某个屏幕/控件区域”反算回原图坐标,裁剪出对应 Bitmap |
| 核心流程 | ① rect ∩ 边框内侧 → valid;② 反变换 valid 得到 inner 坐标系中的矩形;③ 再映射回原图像素坐标 srcRect;④ srcRect ∩ 原图边界;⑤ DrawImage 输出为 Bitmap |
| 插值质量 | InterpolationMode.HighQualityBicubic + PixelOffsetMode.HighQuality |
| 典型场景 | 蚂蚁线框选裁剪、导出当前视口截图、屏幕取色前的像素读取 |
// 场景一:根据蚂蚁线选框裁剪
pb.AntLine.AntRect = new Rectangle(50, 50, 200, 150);
pb.AntLine.Visible = true;
Bitmap crop = pb.GetImageInRectangle(pb.AntLine.AntRect);
if (crop != null)
{
crop.Save("crop.png", System.Drawing.Imaging.ImageFormat.Png);
crop.Dispose(); // 必须由调用方释放
}
// 场景二:导出当前可视视口内的图片
Rectangle visible = pb.GetVisibleImageRectangle();
if (!visible.IsEmpty)
{
using (Bitmap visibleBmp = pb.GetImageInRectangle(visible))
{
if (visibleBmp != null) visibleBmp.Save("viewport.png");
}
}5.5 public void ShowImageRegion(Rectangle imageRect)
| 参数 | 类型 | 说明 |
|---|
imageRect | Rectangle | 要聚焦显示的**原图像素坐标**区域 |
| 项目 | 说明 |
|---|
| 作用 | 自动计算 _zoom 与 _offset,使指定的原图区域**居中**并**最大化**显示在边框内侧区域内 |
| 处理流程 | ① imageRect ∩ 原图;② 等比缩放比 = min(innerW/imgW, innerH/imgH);③ 限制在 [MinZoom=0.1, MaxZoom=100] 范围内;④ 计算让该区域中心映射到 inner 中心所需的 _offset;⑤ 重绘 |
| 返回前提 | 若 Image == null、参数区域无效或边框内侧为空,则不执行任何操作 |
| 典型场景 | 缩放定位到某块细节(如“定位到面部”),或与蚂蚁线联动“把框选区域放大到视口中心” |
// 把原图 (100, 200, 300, 400) 区域居中放大显示
pb.ShowImageRegion(new Rectangle(100, 200, 300, 400));
// 与蚂蚁线联动
pb.AntLine.Visible = true;
pb.AntLine.AntRect = new Rectangle(50, 50, 200, 150);
// 把蚂蚁线框出的原图区域居中显示 —— 注意:AntRect 若以控件坐标而非原图坐标表示,需先转换
pb.ShowImageRegion(pb.AntLine.AntRect);
注意:ShowImageRegion 的参数是原图像素坐标,而不是控件客户区坐标。如果从蚂蚁线(以控件坐标表示)拿到的矩形,需要先通过 GetImageInRectangle 之类的反算逻辑映射到原图坐标后再传入。
六、公开成员速查表
| 成员 | 类型 | 说明 |
|---|
PictureBox() | 构造函数 | 创建控件(150×150,默认边框 6,默认 BackColor 深灰) |
ImageSizeMode | 属性 | 图片显示方式(Stretch / Zoom / None / Center) |
Image | 属性 | 图片;赋值触发 Change 事件 |
BorderSize | 属性 | 边框粗细(像素,默认 6) |
BorderColor | 属性(ARGB) | 边框颜色 |
BackColor | 属性(重写) | 背景颜色 |
MouseEnabled | 属性 | 是否启用左键拖动 + 滚轮缩放 + 右键复位 |
RightRestore | 属性 | 右键是否复位(仅 MouseEnabled=true 有效) |
AntLine | 属性 | 蚂蚁线子控件;用于选框联动、区域裁剪 |
Change | 事件 | 图片被赋值时触发 |
Redraw() | 方法 | 复位缩放/平移,并重绘 |
GetImageDisplayRectangle() | 方法 | 返回当前图片实际绘制矩形(含缩放平移) |
GetVisibleImageRectangle() | 方法 | 返回图片在控件可视范围内的部分 |
GetImageInRectangle(Rectangle) | 方法 | 提取指定控件区域内显示的原图内容为 Bitmap(调用方 Dispose) |
ShowImageRegion(Rectangle) | 方法 | 自动计算缩放平移,使指定原图区域居中放大显示 |
七、鼠标交互一览
| 操作 | 前提 | 效果 |
|---|
| 鼠标进入控件 | MouseEnabled=true 且 CanFocus=true | 自动 Focus(),保证滚轮事件可接收 |
| 左键按下 | MouseEnabled=true 且有图片 | 进入拖动状态,光标变为 SizeAll,鼠标捕获 |
| 左键拖动 | 拖动状态中 | 更新 _offset(基于鼠标位移增量),实时重绘 |
| 左键抬起 | 拖动状态中 | 结束拖动,光标恢复 Default,释放捕获 |
| 滚轮向前(Delta>0) | MouseEnabled=true 且有图片 | 以鼠标位置为中心放大 1.1 倍(最大 100×) |
| 滚轮向后(Delta<0) | 同上 | 以鼠标位置为中心缩小 1/1.1 倍(最小 0.1×) |
| 右键抬起 | MouseEnabled=true 且 RightRestore=true | 调用 Redraw() 复位缩放与平移 |
缩放算法说明:滚轮缩放时以“鼠标所在的控件坐标”为不动点,通过公式 _offset' = (1 - k) * (mouse - center) + k * _offset(k = newZoom / oldZoom)更新偏移,保证鼠标指向的图像细节在缩放前后位置不变。
八、完整使用示例
示例 1:基础图片浏览
var pb = new PictureBox
{
Parent = this,
Dock = DockStyle.Fill,
Image = Image.FromFile("test.png"),
ImageSizeMode = CConPlus.Enum.ImageSizeMode.Zoom,
BorderSize = 4,
BorderColor = Color.FromArgb(255, 80, 80, 80),
BackColor = Color.Black
};示例 2:可拖动 / 缩放的图片查看器
var pb = new PictureBox
{
Parent = this,
Dock = DockStyle.Fill,
ImageSizeMode = CConPlus.Enum.ImageSizeMode.None,
MouseEnabled = true, // 开启鼠标拖动 + 滚轮缩放
RightRestore = true, // 右键复位
BorderSize = 2
};
pb.Image = Image.FromFile("map.png");
// 外部按钮复位
btnReset.Click += (s, e) => pb.Redraw();
// 外部按钮“聚焦到某块区域”
btnFocus.Click += (s, e) => pb.ShowImageRegion(new Rectangle(200, 200, 400, 300));示例 3:配合蚂蚁线裁剪
var pb = new PictureBox { Parent = this, Dock = DockStyle.Fill, MouseEnabled = true };
pb.Image = Image.FromFile("photo.jpg");
pb.AntLine.Visible = true;
btnCrop.Click += (s, e) =>
{
Rectangle r = pb.AntLine.AntRect;
if (r.Width > 0 && r.Height > 0)
{
using (Bitmap bmp = pb.GetImageInRectangle(r))
{
if (bmp != null)
{
bmp.Save("cropped.png", System.Drawing.Imaging.ImageFormat.Png);
MessageBox.Show("已保存 cropped.png");
}
}
}
};示例 4:监听图片变化自动复位
var pb = new PictureBox { Parent = this, Dock = DockStyle.Fill, MouseEnabled = true };
pb.Change += (s, e) =>
{
// 换图后回到初始视角
pb.Redraw();
};
// 之后每次设置 pb.Image 都会触发 Change → 自动复位
pb.Image = Image.FromFile("a.png");
pb.Image = Image.FromFile("b.png");示例 5:将鼠标控件坐标转换为原图坐标
// 通过 GetImageInRectangle + 反算,可以把一个“控件坐标”区域
// 转换成原图内容;如果只需要坐标映射,可以这样:
Rectangle disp = pb.GetImageDisplayRectangle();
Rectangle inner = CConPlus.Rect.MoveRectangleInward(pb.ClientRectangle, pb.BorderSize);
// 例:假设鼠标位置 mouseCtrl 是控件坐标,判断它是否落在图片上:
Point mouseCtrl = PointToClient(Cursor.Position);
bool onImage = disp.Contains(mouseCtrl);
// 若需要计算该点对应的原图像素坐标,可以复用 GetImageInRectangle 的裁剪结果
// 或者直接参考 GetImageInRectangle 内部的反变换公式
九、注意事项
Image 属性:控件不负责 Dispose 传入的图片;需要更换图片时,自行释放旧图,否则会内存泄漏。
BorderSize 未校验上下限:请保证非负,且不要大于控件尺寸的一半,否则会出现 MoveRectangleInward 返回空矩形、或 Pen 绘制异常。
AntLine setter 行为:_antLineControl = value; Controls.Add(value); 不检查是否已添加;多次赋值会在 Controls 中叠加多个 AntLine。建议只读用途,直接用构造时创建的那个实例。
Change 事件一定会触发:即使赋相同引用也会触发(setter 无“值相同”判断)。若你需要“仅在真正换图时才响应”,需要自己记录旧引用做比对。
滚轮缩放需要焦点:控件设置了 Selectable + TabStop=true;MouseEnabled=true 时鼠标进入会自动 Focus(),确保滚轮消息进入控件。若在容器内被其它控件抢占焦点,可能需手动 pb.Focus()。
ShowImageRegion 使用原图坐标:不是控件坐标。如果从 AntLine.AntRect(通常是控件坐标)取区域,需要先反算回原图坐标。
GetImageInRectangle 的返回 Bitmap 需要调用方 Dispose:这是常见的 WinForms 内存泄漏点。
缩放范围:硬编码为 [0.1×, 100×],不可通过属性调整。ShowImageRegion 计算出的缩放比例也会被 clamp 到该范围。
ImageSizeMode 的具体行为:绘制由 CConPlus.GraphicsC.DrawImageInRect 实现;本控件的 GetImageDisplayRectangle / GetImageInRectangle 会按照 Stretch / Zoom / 其他 三种情况近似反算。**如果 CConPlus 的 DrawImageInRect 对某些模式有额外处理**(如 Tile),需要相应调整 CalcImageRectInInner。
右键复位只针对“鼠标操作”引起的缩放/平移;如果你通过代码手动设置过 ShowImageRegion,那么右键复位会把视角还原到“无缩放、无平移”的初始状态,而不是回到那次 ShowImageRegion 的状态。
BorderColor 支持 ARGB(透明边框可出现穿透背景效果),而 BackColor 保持普通 Color 编辑。