在用C#做WinForm开发时,如果要支持查看图片时显示滚动条通常按照如下方式去做:
第一步,拖一个Panel控件到窗口上并设置Dock = DockStyle.Fill,AutoScroll=true.
第二步,拖一个PictureBox控件到Panel控件中,设置其Position为(0, 0),SizeMode为AutoSize
这样在查看的图片的宽高大于窗口的宽高时就会出现滚动条。
如果按照上述步骤封装成一个UserControl,并定义一些列的属性和事件下次使用直接从Toolbox拖到窗口上就可以了。
最终效果如下图所示:
如果要在查看时支持缩放图片该怎么办呢?你可以通过改变PictureBox的SizeMode属性为Zoom并动态计算PictureBox的Position和Size来实现。
但本文将采取另外一种方式实现支持滚动条的图片查看控件。
UserControl和Control
UserControl和Control是开发WinForm控件时常用的两个基类。继承自UserControl的控件通常叫做用户自定义控件,常用于组合一系列的控件成一个新的控件。而继承自Control的控件通常叫做完全自定义控件,通常用于从头开发一个新的控件。所有的WinForm的控件都是继承自Control的,包括UserControl。UserControl和Control的关系如下图所示(忽略了其他接口):
从图上可以看出,UserControl继承自容器控件类ContainerControl,ContainerControl类继承自支持滚动的ScrollableControl类,ScrollableControl类继承自Control类。支持滚动的Panel控件同样继承自ScrollableControl,是ScrollableControl的一个泛化。
本文的目的不是讲解UserControl和Control的区别、属性、事件、方法之类的,想要详细了解请参阅MSDN。
在控件上显示图像
要在控件上显示图像,需要用到Graphics画布对象。你可以在控件的任意方法或属性中创建Graphics的一个实例:
也可以在OnPaint事件的参数中获取:
2 {
3 base.OnPaint(pe);
4 var g = pe.Graphics;
5 DrawImage(pe);
6 }
获取到Graphics对象后,你就可以通过调用Graphics.DrawImage方法在控件上绘制图像了。Graphics.DrawImage方法有很多重载,支持部分绘制图像、对图像进行处理等。本文只使用DrawImage(Image image, Rectangle rect)把图像绘制到rect参数定义的矩形区域,关于Graphics和DrawImage的更详细的说明请参见MSDN。
简单的设计一下
看完上面的内容,你应该对如何开发支持滚动和缩放的PictureViewer有了一定的了解了吧?是否摩拳擦掌准备动手写代码了?不着急,我们先想一想需要支持哪些功能。我们简单的列一个功能清单,如下:
1. 支持图片超出显示区域时,通过滚动条查看图像;
2. 支持自动适应窗口大小或窗口的宽度;
3. 支持Ctrl+鼠标滚轮进行任意比例的缩放;
4. 支持通过设置图像文件或Image对象显示图像;
5. 支持对图像的缩放范围进行限制;
在明确需要支持的功能清单后,我们可以开始编写代码了。
开始编码
选择基类
为了使控件支持滚动条,我们选择ScrollableControl作为控件的基类,并把控件的AutoScroll设置为true。启用了双缓冲可以解决闪动的问题。
{
/// <summary>
/// 创建PictureViewer的一个实例
/// </summary>
public PictureViewer()
{
this.AutoScroll = true;
this.DoubleBuffered = true;
InitializeComponent();
}
}
定义属性
接下来定义一些公共属性,用来配置控件显示的图像,缩放步进、缩放比率等。
2 /// 获取或设置图片文件
3 /// </summary>
4 [Description("如果同时设置了Image属性和ImageFile属性,使用Image属性设置的图像。")]
5 public string ImageFile
6 {
7 get;
8 set;
9 }
10
11 /// <summary>
12 /// 图像
13 /// </summary>
14 private Image _image = null;
15 /// <summary>
16 /// 获取或设置图像。
17 /// </summary>
18 [Description("如果同时设置了Image属性和ImageFile属性,使用Image属性设置的图像。")]
19 public Image Image
20 {
21 get
22 {
23 if (_image != null)
24 {
25 return _image;
26 }
27 else
28 {
29 if (!string.IsNullOrEmpty(ImageFile))
30 {
31 _image = Image.FromFile(ImageFile);
32 }
33 }
34 return _image;
35 }
36 set
37 {
38 _image = value;
39 }
40 }
41
42 /// <summary>
43 /// 缩放步进
44 /// </summary>
45 private int _zoomStep = 10;
46 /// <summary>
47 /// 获取或设置缩放步进,1至100之间
48 /// </summary>
49 public int ZoomStep
50 {
51 get { return _zoomStep; }
52 set
53 {
54 if (value < 1)
55 {
56 _zoomStep = 1;
57 }
58 else if (value > 100)
59 {
60 _zoomStep = 100;
61 }
62 else
63 {
64 _zoomStep = value;
65 }
66 }
67 }
68
69 /// <summary>
70 /// 最大缩放比率,表示为百分之几。
71 /// </summary>
72 private int _maxZoomPercent = 1000;
73 /// <summary>
74 /// 获取或设置最大缩放比率,表示为百分之几。
75 /// </summary>
76 public int MaxZoomPercent
77 {
78 get { return _maxZoomPercent; }
79 set { _maxZoomPercent = value; }
80 }
81
82 /// <summary>
83 /// 当前缩放比例
84 /// </summary>
85 private int _zoomPercent = 100;
86 /// <summary>
87 /// 获取或设置当前缩放比例
88 /// </summary>
89 public int ZoomPercent
90 {
91 get { return _zoomPercent; }
92 set
93 {
94 if (_zoomPercent < 1)
95 {
96 _zoomPercent = 1;
97 }
98 else if (_zoomPercent > MaxZoomPercent)
99 {
100 _zoomPercent = MaxZoomPercent;
101 }
102 else
103 {
104 _zoomPercent = value;
105 }
106 this.Invalidate();
107 }
108 }
109
110 /// <summary>
111 /// 适应模式
112 /// </summary>
113 private FitMode _fitMode = FitMode.FitAll;
114 /// <summary>
115 /// 获取或设置初始适应模式
116 /// </summary>
117 public FitMode FitMode
118 {
119 get { return _fitMode; }
120 set
121 {
122 _fitMode = value;
123 if (_fitMode == ControlsLibrary.FitMode.FitPercent)
124 {
125 ZoomPercent = 100;
126 }
127 else
128 {
129 this.Invalidate();
130 }
131 }
132 }
上面的FitMode是一个枚举类型,其定义如下。
2 /// 图像适应模式
3 /// </summary>
4 public enum FitMode
5 {
6 /// <summary>
7 /// 全部适应
8 /// </summary>
9 FitAll,
10 /// <summary>
11 /// 适应宽度
12 /// </summary>
13 FitWidth,
14 /// <summary>
15 /// 适应到百分比
16 /// </summary>
17 FitPercent
18 }
注意,在为FitMode和ZoomPercent属性赋值时,调用了Invalidate()方法。这个方法会通知Windows重新绘制这个控件。通常情况下只在OnPaint事件中绘制,其他地方通过调用Invalidate方法通知Windows重绘重绘。Invalidate方法有很多重载,这里不一一表述,如想进一步了解请参考MSDN相关文档。
计算图像大小和位置
下面我们要计算绘制图像的大小。根据设计,控件要支持图像自适应控件大小,图像自适应控件宽度以及指定百分比。我们分别编写三个方法,用来计算三种模式下的图像大小:
2 /// 适应工作区
3 /// </summary>
4 /// <returns>图像大小</returns>
5 private Size FitAll()
6 {
7int width = 0;
8int height = 0;
9
10var imageRatio = (float)Image.Width / (float)Image.Height;
11// 较宽
12 if (imageRatio > 1)
13 {
14 width = Math.Min(Image.Width, this.ClientSize.Width);
15 height = (int)(width / imageRatio);
16if (height > this.ClientSize.Height)
17 {
18 height = this.ClientSize.Height;
19 width = (int)(height * imageRatio);
20 }
21 }
22else
23 {
24 height = Math.Min(Image.Height, this.ClientSize.Height);
25 width = (int)(height * imageRatio);
26if (width > this.ClientSize.Width)
27 {
28 width = this.ClientSize.Width;
29 height = (int)(height / imageRatio);
30 }
31 }
32return new Size(width, height);
33 }
34
35/// <summary>
36 /// 自适应宽度
37 /// </summary>
38 /// <returns>图像大小</returns>
39 private Size FitWidth()
40 {
41int width = this.ClientSize.Width;
42int height = (int)(Image.Height * (float)width / (float)Image.Width);
43return new Size(width, height);
44 }
45
46/// <summary>
47 /// 适应到指定百分比
48 /// </summary>
49 /// <param name="percent">百分比,默认100</param>
50 /// <returns>图像大小</returns>
51 private Size FitPercent(int percent = 100)
52 {
53if (percent == 100)
54 {
55return Image.Size;
56 }
57
58if (percent < 1)
59 {
60 percent = 1;
61 }
62else if (percent > MaxZoomPercent)
63 {
64 percent = MaxZoomPercent;
65 }
66
67int width = (int)(Image.Width * ((float)percent / 100f));
68int height = (int)(Image.Height * ((float)percent / 100f));
69return new Size(width, height);
70 }