UserControl (SliderControl) 剖析






4.87/5 (41投票s)
本文将分步介绍如何实现一个名为 SliderControl 的 UserControl。
简介 [^]

本文分步介绍了如何实现一个名为 SliderControl 的 UserControl。
在以下讨论中,由开发人员指定的属性将以粗体混合大小写文本显示。软件内部使用的变量将以斜体小写文本显示。
用户视角
该控件允许用户拖动箭头(管的右侧),并将其向上或向下移动。垂直移动仅限于管的顶部和底部。箭头的填充颜色会随箭头所指管中的颜色而变化。当用户移动箭头时,箭头右侧的文本会随之改变,以指示当前值。
开发者视角
对于开发人员来说,滑块控件可以集成到 Visual Studio 工具箱中,并放置在 Windows 窗体表面。控件最重要的属性是this.Height,在内部称为control_height。所有其他维度都由此维度派生。
每当用户更改当前值时,都会引发SliderValueChanged 事件。请注意,这与检测箭头的移动不同。current_value是一个整数。因此,箭头位置的微小变化可能会导致一个小数。该小数(使用四舍五入)四舍五入到最接近的整数,并与之前的current_value进行比较。只有当它们不同时,才会引发 SliderValueChanged 事件。新的current_value将在 SliderValueEventArgs 中返回。
开发控件
在开发控件的过程中,遵循某种形式的需求至关重要。对于 SliderControl,这是通过 Microsoft PowerPoint 演示文稿实现的。该演示文稿中的幻灯片作为 PNG 图像并入本文。完整的演示文稿作为可下载文件提供。
如果读者未安装 Microsoft PowerPoint,可免费下载 PowerPoint Viewer。
目录
符号 [^] 将读者返回到目录顶部。
视觉属性 [^]

滑块控件具有许多构成用户视觉图像的属性。在右侧的图中,控件的边界以红色绘制。
开发人员通过将控件从工具箱拖到窗体上的位置来指定控件的左上角。该位置在控件的图形环境中变为 ( 0, 0 )。开发人员通过拖动控件的调整大小手柄来指定control_height。control_width计算为
control_width = ( int ) ( ( float ) control_height / 4.0F + 0.5F );
请注意,control_width由control_height决定。改变两者的唯一方法是使用调整大小手柄。
控件边界和每个控件组件之间有一个边框。该边框根据control_height计算为
offset = ( int ) ( ( float ) control_height / 100.0F + 0.5F );
一旦计算出offset,tube_height就可以计算为
tube_height = control_height - 2 * offset;
管的宽度可以指定或计算。
- 指定。
为了在窗体上实现多个滑块控件的共同视觉呈现,开发人员可能希望跳过tube_width的计算(例如在滑块控件演示中)。这可以通过将ForceTubeWidth设置为true并提供TubeWidth的值来实现。如果ForceTubeWidth未设置为true,则指定TubeWidth的值将被忽略。
- 计算。
如果ForceTubeWidth未设置为true,则计算tube_width的值。为了计算tube_width,使用常数 HEIGHT_TO_WIDTH
HEIGHT_TO_WIDTH = 0.030612244897959;
并且tube_width计算为
tube_width = ( int ) ( ( double ) tube_height * HEIGHT_TO_WIDTH + 0.5 );
管子使用线性渐变画笔着色,并使用由MaximumColor、MidpointColor和MinimumColor指定的颜色混合。
在彩色管的左侧,是一组垂直的标签,范围从MaximumValue到MinimumValue。标签值由Increment分隔。标签与控件的左边界和彩色管之间由offset分隔。MaximumValue必须大于MinimumValue,并且MinimumValue必须大于或等于零。
实现 [^]

滑块控件是用户绘制控件。控件的某些部分在调用控件的OnPaint方法时绘制。
控件的图形,其轮廓以红色绘制,由两个不同的图形图像组成:背景图形,以蓝色勾勒并填充蓝色图案;以及指示器图形,以绿色勾勒并填充绿色图案。
绘制后,除非ControlBackgroundColor、ForceTubeWidth和TubeWidth、MaximumColor、MidpointColor、MinimumColor、MaximumValue、MinimumValue或Increment发生变化或控件大小调整,否则背景图形无需重新绘制。当背景图形重新绘制或用户更改当前值箭头的位置时,指示器图形必须重新绘制。
背景图形 [^]

背景图形由彩色管和管左侧的一组标签组成。管本身由两个大小相等、由一个矩形连接的端点圆组成。
请注意,端点圆的半径是tube_width/2。绘制后,端点圆会分别填充MaximumColor和MinimumColor。
矩形使用线性渐变画笔填充,该画笔通过MidpointColor从MaximumColor到MinimumColor定义颜色混合。
标签源自MaximumValue、MinimumValue和Increment的值。生成标签后,使用MeasureText计算所有标签的最大尺寸label_size。
有五个点P0、P1、P2、P3和P4是固定的,基于属性值。这些点完全定义了背景图形的内容。回想一下之前,所有图形对象的原点是控件的左上角。
P0 | 管的左上角以及MaximumValue端点圆的边界矩形的左上角。 | |
P1 | 管子的左下角。 | |
P2 | 彩色矩形的上部中心。 | |
P3 | 彩色矩形的下部中心。 | |
P4 | MinimumValue端点圆的边界矩形的左上角。 |
这些点中最重要的是P0,因为所有其他点都源自它。用于计算这些值的序列是
- 更新管尺寸。
- 更新背景标签。
- 更新背景几何图形。
尽管名称略有不当,update_tube_dimensions 是
// ************************************ update_tube_dimensions
void update_tube_dimensions ( )
{
offset = round ( ( float ) control_height / 100.0F );
tube_height = control_height - 2 * offset;
if ( !force_tube_width )
{
tube_width = round ( ( double ) tube_height *
HEIGHT_TO_WIDTH );
}
arrow_width = round ( 1.5 * ( double ) tube_width );
}
辅助函数round返回其参数的整数值,使用四舍五入到最接近的整数。round被重载以接受float和double类型的参数。
当定义背景的属性之一发生变化时,必须更新背景标签。
// ********************************** update_background_labels
void update_background_labels ( )
{
int available_height = 0;
float font_size = FONT_SIZE;
Font new_font = new Font ( FONT_FAMILY, FONT_SIZE );
int needed_height = 0;
new_font = new Font ( FONT_FAMILY, font_size );
labels = create_background_labels ( MaximumValue,
MinimumValue,
Increment);
label_size = determine_maximum_label_size ( labels,
new_font );
// force vertical fit
update_tube_dimensions ( );
available_height = control_height -
( 2 * offset ) - tube_width;
needed_height = labels.Length * label_size.Height;
while ( needed_height > available_height )
{
font_size -= 0.1F;
new_font = new Font ( FONT_FAMILY, font_size );
label_size = determine_maximum_label_size (
labels,
new_font );
needed_height = labels.Length * label_size.Height;
}
label_font = new_font;
}
两个辅助函数create_background_labels和determine_maximum_label_size执行其名称所示的功能。一旦标签创建并初步调整大小,update_background_labels会继续测试以确保标签在available_height内垂直适配。如果标签不适配,则会减小字体大小直到标签适配,
最后,计算点P0、P1、P2、P3和P4。
// ******************************** update_background_geometry
void update_background_geometry ( )
{
update_tube_dimensions ( );
P0.X = offset + label_size.Width + offset;
P0.Y = offset;
P1.X = P0.X;
P1.Y = control_height - offset;
P2.X = P0.X + round ( ( double ) tube_width / 2.0 );
P2.Y = P0.Y + round ( ( double ) tube_width / 2.0 );
P3.X = P2.X;
P3.Y = P1.Y - round ( ( double ) tube_width / 2.0 );
P4.X = P0.X;
P4.Y = P1.Y - tube_width;
}
此时,背景图形的几何形状已定义。绘制背景的顺序是
- 用ControlBackgroundColor填充控件。
- 绘制背景标签。
- 绘制端点圆(先MaximumValue,然后MinimumValue)。
- 绘制彩色管。
这个序列可以在draw_background_graphic中找到
// *********************************** draw_background_graphic
void draw_background_graphic ( Graphics graphics )
{
Brush brush;
Rectangle end_point_rectangle =
new Rectangle ( );
LinearGradientBrush linear_gradient_brush;
Rectangle rectangle;
// background labels
draw_background_labels ( graphics );
// endpoints
end_point_rectangle.Size = new Size ( tube_width,
tube_width );
// maximum endpoint
brush = new SolidBrush ( MaximumColor );
end_point_rectangle.Location = P0;
graphics.FillEllipse ( brush, end_point_rectangle );
brush.Dispose ( );
// minimum endpoint
brush = new SolidBrush ( MinimumColor );
end_point_rectangle.Location = P4;
graphics.FillEllipse ( brush, end_point_rectangle );
brush.Dispose ( );
// gradient tube
rectangle = new Rectangle ( new Point ( P0.X, P2.Y ),
new Size ( tube_width,
P3.Y - P2.Y ) );
// inflate to account for the
// right and bottom off by one
// value in Rectangles
rectangle.Inflate ( 1, 1 );
linear_gradient_brush = create_linear_gradient_brush (
MaximumColor,
MidpointColor,
MinimumColor );
graphics.FillRectangle ( linear_gradient_brush,
rectangle );
linear_gradient_brush.Dispose ( );
}
辅助函数draw_background_labels是
// ************************************ draw_background_labels
void draw_background_labels ( Graphics graphics )
{
Point location = new Point ( 0, 0 );
TextFormatFlags text_format_flags;
int vertical_offset = 0;
text_format_flags = ( TextFormatFlags.Right |
TextFormatFlags.VerticalCenter );
vertical_offset = ( P3.Y - P2.Y ) / ( labels.Length - 1 );
location.X = P0.X - ( offset + label_size.Width );
location.Y = P2.Y - ( label_size.Height / 2 );
foreach ( string label in labels )
{
Rectangle rectangle = new Rectangle ( location,
label_size );
TextRenderer.DrawText ( graphics,
label,
label_font,
rectangle,
Color.Black,
text_format_flags );
location.Y += vertical_offset;
}
}
辅助函数create_linear_gradient_brush是
// ****************************** create_linear_gradient_brush
LinearGradientBrush create_linear_gradient_brush (
Color maximum_color,
Color midpoint_color,
Color minimum_color )
{
LinearGradientBrush brush;
ColorBlend color_blend = new ColorBlend ( );
Rectangle rectangle;
rectangle = new Rectangle ( new Point ( P0.X, P2.Y ),
new Size ( tube_width,
tube_height ) );
rectangle.Inflate ( 1, 1 );
brush = new LinearGradientBrush (
this.ClientRectangle,
maximum_color,
minimum_color,
LinearGradientMode.Vertical );
color_blend.Positions = new float [ ]
{
0.0F,
0.5F,
1.0F
};
color_blend.Colors = new Color [ ]
{
maximum_color,
midpoint_color,
minimum_color
};
brush.InterpolationColors = color_blend;
return ( brush );
}
此时,背景图形已绘制完毕,并可传输到OnPaint PaintEventArgs中提供的Graphics对象。除非背景图形属性发生变化,否则我们不需要执行背景图形计算。
指示器图形 [^]

指示器图形由当前值箭头和箭头右侧的包含current_value的标签组成。P2在背景图形中的值固定了指示器图形的左侧。
用户通过将光标放置在箭头的边界内、按下并按住鼠标按钮之一、向上或向下拖动光标(以及箭头),最后释放鼠标按钮来与箭头交互。如果光标位于箭头内,控件会通过响应OnMouseDown、OnMouseMove和OnMouseUp事件来响应这些用户操作。

指示器图形由draw_indicator_graphic绘制
// ************************************ draw_indicator_graphic
void draw_indicator_graphic ( Graphics graphics )
{
update_indicator_geometry ( );
draw_current_value_string ( graphics );
draw_arrow ( graphics );
}
在绘制指示器图形之前,必须更新指示器图形的几何形状。
// ********************************* update_indicator_geometry
void update_indicator_geometry ( )
{
int dy = 0;
float percent = 0.0F;
float percent_down = 0.0F;
int pixels = 0;
int pixels_down = 0;
double theta = INTERIOR_ANGLE * Math.PI / 180.0;
update_tube_dimensions ( );
// solve initialization problem
if ( CurrentValue < MinimumValue )
{
CurrentValue = MaximumValue - Increment;
}
P5.X = P2.X + round ( ( double ) tube_width / 2.0 ) +
offset;
pixels = round ( ( float ) ( P3.Y - P2.Y ) );
percent = Math.Abs ( ( float ) ( current_value -
MinimumValue ) /
( float ) ( MaximumValue -
MinimumValue ) );
percent_down = 1.0F - percent;
pixels_down = round ( ( percent_down *
( float ) pixels ) );
P5.Y = P2.Y + pixels_down;
dy = round ( ( double ) arrow_width *
Math.Sin ( theta ) );
P6.X = P5.X + arrow_width;
P6.Y = P5.Y - dy;
P7.X = P6.X;
P7.Y = P5.Y + dy;
}
常数 INTERIOR_ANGLE(20 度)的值是通过实验获得的。我做的第一件事就是画箭头。随着时间的推移,我发现 20 度是平衡的、令人愉悦的,并且尺寸足够大,允许用户捕捉箭头(用于拖动)。
修改指示器几何图形后,绘制箭头右侧的标签。
// ********************************* draw_current_value_string
void draw_current_value_string ( Graphics graphics )
{
Brush brush;
string current_value_string;
TextFormatFlags flags;
Point location;
Rectangle rectangle;
brush = new SolidBrush ( Color.Black );
current_value_string = current_value.ToString ( );
flags = ( TextFormatFlags.Left |
TextFormatFlags.VerticalCenter );
location = new Point ( P6.X + offset,
P5.Y - ( label_size.Height / 2 ) );
rectangle = new Rectangle ( location, label_size );
TextRenderer.DrawText ( graphics,
current_value_string,
label_font,
rectangle,
Color.Black,
flags );
}
TextRenderer和TextFormatFlags用于在边界矩形左侧和垂直居中绘制标签。
最后我们可以画箭头了。
// ************************************************ draw_arrow
void draw_arrow ( Graphics graphics )
{
Point [ ] arrow_outline = new Point [ 3 ];
GraphicsPath arrow_path = null;
Brush brush;
int i = 0;
Pen pen;
Color value_color;
update_geometry ( );
value_color = background.ColorAtLocation (
new Point ( P5.X - ( tube_width / 2 ),
P5.Y ) );
brush = new SolidBrush ( value_color );
pen = new Pen ( Color.Black, 1.0F );
arrow_outline [ i++ ] = P5;
arrow_outline [ i++ ] = P6;
arrow_outline [ i++ ] = P7;
arrow_path = new GraphicsPath ( FillMode.Alternate );
arrow_path.AddLines ( arrow_outline );
arrow_path.CloseFigure ( );
// arrow_region is used for
// hit testing
arrow_region = new Region ( arrow_path );
// draw arrow outline
graphics.DrawPolygon ( pen, arrow_outline );
// fill arrow outline
graphics.FillPolygon ( brush, arrow_outline );
arrow_path.Dispose ( );
brush.Dispose ( );
pen.Dispose ( );
}
箭头的轮廓由P5、P6和P7定义。数组arrow_outline存储轮廓。从该数组定义一个GraphicsPath,并从该路径定义一个Region。该区域的IsVisible方法将用于命中测试。箭头的轮廓使用DrawPolygon绘制。
箭头使用FillPolygon填充。
此时,指示器图形已绘制完毕,并可传输到OnPaint PaintEventArgs中提供的 Graphics 对象。当背景图形重新绘制或用户移动箭头时,指示器图形会重新绘制。
事件处理 [^]
大多数编程语言中一个比较隐晦的特性是感兴趣的操作的信号传递方式。许多语言都有这种机制。但由于 SliderControl 是用 C# 实现的,因此本讨论将仅限于该语言。
SliderValueChanged [^]
除非 SliderControl 能够告知(发出信号)其父级用户更改了current_value(通过向上或向下拖动箭头),否则它将毫无用处。为了发出此事件的信号,SliderControl 包含SliderValueChanged事件的声明。
// ******************************** control delegate and event
public delegate void SliderValueChangedHandler (
Object sender,
SliderValueChangedEventArgs e );
public event SliderValueChangedHandler SliderValueChanged;
委托SliderValueChangedHandler定义了将由SliderValueChanged事件调用的方法的签名。该事件有两个参数:发送者和自定义 EventArgs,SliderValueChangedEventArgs。
// ***************************** class SliderValueChangedEventArgs
public class SliderValueChangedEventArgs : EventArgs
{
public int SliderValue;
// ******************************* SliderValueChangedEventArgs
public SliderValueChangedEventArgs ( int slider_value )
{
SliderValue = slider_value;
}
} // class SliderValueChangedEventArgs
SliderValueChangedEventArgs返回当前的SliderValue。每当 SliderControl 检测到current_value的值发生变化时,它就会调用trigger_slider_value_changed_event。
// ************************ trigger_slider_value_changed_event
void trigger_slider_value_changed_event ( int current_value )
{
if ( SliderValueChanged != null )
{
SliderValueChanged ( this,
new SliderValueChangedEventArgs (
current_value ) );
}
}
SliderValueChanged事件可以有零个或多个订阅者。测试
if ( SliderValueChanged != null )
是为了确保至少有一个订阅者订阅了SliderValueChanged事件。未能进行此测试可能会导致异常,这是用户控件中应避免的情况。
如果一个类希望收到 SliderControl 的current_value值变化的通知,它必须注册一个事件处理程序。在TestSliderControl 演示项目中,有三个 SliderControl 实例。每个实例都连接到事件处理程序。这通过首先声明方法slider_SC_SliderValueChanged将用于捕获事件来实现
heater_AC_SC.SliderValueChanged +=
new SliderControl.SliderControl.
SliderValueChangedHandler (
SliderValueChanged );
spa_SC.SliderValueChanged +=
new SliderControl.SliderControl.
SliderValueChangedHandler (
SliderValueChanged );
cruise_control_SC.SliderValueChanged +=
new SliderControl.SliderControl.
SliderValueChangedHandler (
SliderValueChanged );
slider_SC_SliderValueChanged方法声明如下
// ****************************** slider_SC_SliderValueChanged
void SliderValueChanged (
object sender,
SliderControl.SliderValueChangedEventArgs e )
{
string new_value;
SliderControl.SliderControl slider_control;
slider_control = ( SliderControl.SliderControl ) sender;
new_value = e.SliderValue.ToString ( );
switch ( slider_control.Tag.ToString ( ).ToLower ( ) )
{
case "heater_ac":
heater_ac_value_TB.Text = new_value;
break;
case "spa":
spa_value_TB.Text = new_value;
break;
case "cruise_control":
cruise_control_value_TB.Text = new_value;
break;
default:
break;
}
}
在设计时,SliderControl 的每个实例都被赋予一个唯一的Tag值。slider_SC_SliderValueChanged方法使用此值来区分 SliderControl 的多个实例。在此示例中,SliderValue的新值放置在 TextBox 中。可以使用SliderValue中返回的值执行任意数量的其他操作。
滑动箭头 [^]
用户通过如前述方式上下移动箭头来与 SliderControl 交互。用户的交互通过OnMouseDown、OnMouseMove和OnMouseUp事件的事件处理程序检测到。
OnMouseDown 事件处理程序 [^]
// *********************************************** OnMouseDown
protected override void OnMouseDown ( MouseEventArgs e )
{
base.OnMouseDown(e);
if ( arrow_region.IsVisible ( new Point ( e.X, e.Y ) ) )
{
// cursor is in the arrow
arrow_being_dragged = true;
if ( current_value_changed ( e.Y ) )
{
trigger_slider_value_changed_event (
current_value );
this.Invalidate ( );
}
}
}
SliderControl 的OnMouseDown事件处理程序是经典的。
- 调用基类的OnMouseDown方法,以便所有注册的委托都接收到该事件。
- 测试以确保光标在draw_arrow方法中定义的arrow_region中。
- 如果光标在arrow_region中,则将变量arrow_being_dragged设置为true,并测试以确保与箭头关联的值与current_value不同。判断新值是否与current_value不同的测试在current_value_changed方法中执行。
- 如果current_value_changed返回true,则调用trigger_slider_value_changed_event,控件将重新绘制自身。
current_value_changed实现为
// ************************************* current_value_changed
bool current_value_changed ( int y )
{
int old_current_value = current_value;
float percent;
float value_down;
int pixels;
int pixels_down;
bool value_changed = false;
if ( P5.Y != y )
{
if ( y > P3.Y )
{
y = P3.Y;
}
if ( y < P2.Y )
{
y = P2.Y;
}
pixels = P3.Y - P2.Y;
pixels_down = y - P2.Y;
percent = ( float ) pixels_down / ( float ) pixels;
value_down = percent * ( float ) ( MaximumValue -
MinimumValue );
current_value = round ( ( float ) MaximumValue -
value_down );
value_changed = ( old_current_value !=
current_value );
}
return ( value_changed );
}
请注意,current_value在此方法中进行了修改。
OnMouseMove 事件处理程序 [^]
// *********************************************** OnMouseMove
protected override void OnMouseMove ( MouseEventArgs e )
{
base.OnMouseMove ( e );
if ( arrow_being_dragged )
{
if ( current_value_changed ( e.Y ) )
{
trigger_slider_value_changed_event (
current_value );
this.Invalidate ( );
}
}
}
SliderControl 的OnMouseMove事件处理程序很简单。如果arrow_being_dragged为true,则表示用户仍按住鼠标按钮并正在上下拖动箭头。在这种情况下,会调用current_value_changed,如果它返回true,则会调用trigger_slider_value_changed_event,并且控件会重新绘制自身。
OnMouseUp 事件处理程序 [^]
// ************************************************* OnMouseUp
protected override void OnMouseUp ( MouseEventArgs e )
{
base.OnMouseUp ( e );
arrow_being_dragged = false;
}
当用户释放鼠标按钮时,会调用OnMouseUp事件处理程序。反过来,此处理程序只是将arrow_being_dragged设置为false。这会导致在用户再次按下鼠标按钮之前,所有未来的鼠标移动都将被忽略。请注意,SliderControl 不需要重新绘制。
OnPaint 事件处理程序 [^]
// *************************************************** OnPaint
protected override void OnPaint ( PaintEventArgs e )
{
base.OnPaint ( e );
e.Graphics.Clear ( ControlBackgroundColor );
if ( ( this.Height != control_height ) ||
( this.Width != control_width ) )
{
int width;
revise_background_graphic = true;
control_height = this.Height;
control_width = round ( ( float ) control_height /
4.0F );
update_geometry ( );
width = offset + label_size.Width + offset +
tube_width + offset + arrow_width + offset +
label_size.Width + offset;
if ( width > control_width )
{
control_width = width;
}
this.Size = new Size ( control_width,
control_height );
}
if ( ( background == null ) || revise_background_graphic )
{
if ( revise_background_graphic )
{
revise_background_graphic = false;
}
create_background_graphic ( );
draw_background_graphic ( background.Graphic );
}
background.RenderGraphicsBuffer ( e.Graphics );
create_indicator_graphic ( );
draw_indicator_graphic ( indicator.Graphic );
indicator.RenderGraphicsBuffer ( e.Graphics );
}
每当系统识别出某个控件需要重新绘制时,它就会通过引发 OnPaint 事件来通知控件。OnPaint 事件可能因多种原因而引发,其中一些原因包括
- 光标在控件表面移动。
- 控件大小调整。
- 控件的隐藏区域变得可见。
- 控件自身请求。
例如,OnMouseDown和OnMouseMove事件处理程序都通过调用Invalidate来触发控件的重绘。此外,当ControlBackgroundColor、CurrentValue、ForceTubeWidth、Increment、MaximumColor、MaximumValue、MidpointColor、MinimumColor、MinimumValue或TubeWidth中的任何一个发生更改时,也会调用 Invalidate。
被调用时,SliderControl 的 OnPaint 事件处理程序会执行以下操作
- 调用基类的OnPaint方法,以便所有注册的委托都接收到该事件。
- 彻底清除控件上现有的图形。
- 确定控件的高度或宽度是否已更改。如果是,则首先将revise_background_graphic设置为true,从而强制重新绘制背景图形,然后计算并设置控件的新大小。
- 如果背景为null或revise_background_graphic为true,则重新创建并重绘背景。请注意,如果这些条件都不是true,则背景保持不变。
- 将背景绘制到屏幕上。
- 创建并重新绘制指示器图形。
- 将指示器绘制到屏幕上。
图形缓冲区 [^]
GraphicsBuffer 类包含一个屏幕外位图,用于无闪烁地绘制图形对象。尽管 .Net 提供了双缓冲图形功能,但对于 SliderControl 而言,这有些大材小用。
通常我将 GraphicsBuffer 包含在我正在实现的控件中。但是,由于该类在 SliderControl 软件之外也很有用,因此我将其作为单独的源文件 (GraphicsBuffer.cs) 包含在下载中。
成员 [^]
GraphicsBuffer 的成员是
Bitmap bitmap;
Graphics graphics;
int height;
int width;
bitmap是用于保存最终显示在屏幕上的图像的屏幕外对象。绘制通过graphics执行,其在 GraphicsBuffer 内部定义为
graphics = Graphics.FromImage ( bitmap );
以及height和width包含bitmap维度。
构造函数 [^]
// ******************************************** GraphicsBuffer
public GraphicsBuffer ( )
{
width = 0;
height = 0;
}
GraphicsBuffer 构造函数创建一个新的空 GraphicsBuffer 对象。例如
background = new GraphicsBuffer ( );
或
indicator = new GraphicsBuffer ( );
方法 [^]
一旦构造了 GraphicsBuffer,就可以使用方法和属性来完善和使用它。
创建图形缓冲区 [^]
// ************************************** CreateGraphicsBuffer
public bool CreateGraphicsBuffer ( int width,
int height )
{
bool success = true;
if ( graphics != null )
{
graphics.Dispose ( );
graphics = null;
}
if ( bitmap != null )
{
bitmap.Dispose ( );
bitmap = null;
}
this.width = 0;
this.height = 0;
if ( ( width == 0 ) || ( height == 0 ) )
{
success = false;
}
else
{
this.width = width;
this.height = height;
bitmap = new Bitmap ( this.width, this.height );
graphics = Graphics.FromImage ( bitmap );
success = true;
}
return ( success );
}
CreateGraphicsBuffer是GraphicsBuffer构造函数之后要调用的第一个方法。此方法通过删除之前调用CreateGraphicsBuffer的任何残余物来完成创建过程;从指定的height和width创建内存中的位图;并将图形与位图关联起来。
删除图形缓冲区 [^]
// ************************************** DeleteGraphicsBuffer
public GraphicsBuffer DeleteGraphicsBuffer ( )
{
if ( graphics != null )
{
graphics.Dispose ( );
graphics = null;
}
if ( bitmap != null )
{
bitmap.Dispose ( );
bitmap = null;
}
width = 0;
height = 0;
return ( null );
}
在 SliderControl 中,每当要重新绘制背景图形时,都会执行以下代码
// ********************************* create_background_graphic
void create_background_graphic ( )
{
if ( background != null )
{
background = background.DeleteGraphicsBuffer ( );
}
background = new GraphicsBuffer ( );
background.CreateGraphicsBuffer ( control_width,
control_height );
background.Graphic.SmoothingMode = SmoothingMode.
HighQuality;
background.ClearGraphics ( ControlBackgroundColor );
}
类似的重绘指示器图形的代码也会执行。
// ********************************** create_indicator_graphic
void create_indicator_graphic ( )
{
if ( indicator != null )
{
indicator = indicator.DeleteGraphicsBuffer ( );
}
indicator = new GraphicsBuffer ( );
indicator.CreateGraphicsBuffer ( control_width,
control_height );
indicator.Graphic.SmoothingMode = SmoothingMode.
HighQuality;
}
当调用 SliderControl memory_cleanup事件处理程序时,也会执行DeleteGraphicsBuffer。
// ******************************************** memory_cleanup
void memory_cleanup ( object sender,
EventArgs e )
{
if ( arrow_region != null )
{
arrow_region.Dispose ( );
arrow_region = null;
}
// DeleteGraphicsBuffer
// returns null
if ( background != null )
{
background = background.DeleteGraphicsBuffer ( );
}
if ( indicator != null )
{
indicator = indicator.DeleteGraphicsBuffer ( );
}
}
memory_cleanup连接到 ApplicationExit 事件,并在 SliderControl 构建时执行。
Application.ApplicationExit +=
new EventHandler ( memory_cleanup );
memory_cleanup ( this, EventArgs.Empty );
渲染图形缓冲区 [^]
// ************************************** RenderGraphicsBuffer
public void RenderGraphicsBuffer ( Graphics graphic )
{
if ( bitmap != null )
{
graphic.DrawImage (
bitmap,
new Rectangle ( 0, 0, width, height ),
new Rectangle ( 0, 0, width, height ),
GraphicsUnit.Pixel );
}
}
RenderGraphicsBuffer将 GraphicsBuffer 位图绘制到其参数中指定的图形。它使用 .Net Graphics 类的DrawImage方法,并在 SliderControl OnPaint 事件处理程序中为背景和指示器图形调用。
background.RenderGraphicsBuffer ( e.Graphics );
:
:
indicator.RenderGraphicsBuffer ( e.Graphics );
此处e是 OnPaint 事件处理程序的PaintEventArgs参数。当调用RenderGraphicsBuffer时,屏幕图像会更新。
清除图形 [^]
// ********************************************* ClearGraphics
public void ClearGraphics ( Color background_color )
{
Graphic.Clear ( background_color );
}
ClearGraphics清除整个绘图表面并用指定的background_color填充。
ColorAtLocation [^]
// ******************************************* ColorAtLocation
public Color ColorAtLocation ( Point location )
{
Color color = Color.Black;
if ( ( location.X <= this.width ) &&
( location.Y <= this.height ) )
{
color = this.bitmap.GetPixel ( location.X,
location.Y );
}
return ( color );
}
ColorAtLocation返回图形位图中给定location处的 GDI Color。如果location在图形位图之外,则返回黑色。
GraphicsBufferExists [^]
// ************************************** GraphicsBufferExists
public bool GraphicsBufferExists
{
get
{
return ( graphics != null );
}
}
如果图形对象存在,GraphicsBufferExists返回true;否则返回false。
属性 [^]
GraphicsBuffer 只有一个属性。
图形 [^]
// *************************************************** Graphic
public Graphics Graphic
{
get
{
return ( graphics );
}
}
Graphic返回当前的 Graphics 对象。此对象用于在位图上绘制图形。
滑块控件演示 [^]

回想前面,管子的宽度可以指定或计算。
在右侧的图中,三个管子需要其中两个指定管子的宽度。在 spa 和巡航控制 SliderControls 的属性页上,ForceTubeWidth设置为true,TubeWidth设置为 16。该值 (16) 来自加热器/空调 SliderControl 的TubeWidth。这些设置的效果是确保所有三个管子在窗体上显示时具有相同的宽度。
结论 [^]
本文介绍了滑块控件的逐步实现指南。
参考文献 [^]
- 将项目添加到工具箱
- Bitmap
- Color
- ColorBlend
- 将事件处理程序方法连接到事件
- 委托
- 双缓冲图形
- DrawImage
- 事件和委托
- 图形
- 实现事件
- Invalidate
- LinearGradientBrush
- MeasureText
- OnMouseDown
- OnMouseMove
- OnMouseUp
- OnPaint
- PaintEventArgs
- 引发事件
- Register
- 舍入
- Tag
- TextFormatFlags
- TextRenderer
- 用户绘制控件
开发环境 [^]
SliderControl 在以下环境中开发
Microsoft Windows 7 Professional Service Pack 1 | |
Microsoft Visual Studio 2008 Professional | |
Microsoft .Net Framework Version 3.5 SP1 | |
Microsoft Visual C# 2008 |
历史 [^]
05/08/2013 | 原始文章 | |
05/14/2013 | 继承自 UserControl 更改为 Control,版本为 1.2 |