默认 TrackBar 的一个变种






4.27/5 (9投票s)
2004 年 8 月 12 日
4分钟阅读

114446

3578
多色、多方向的 TrackBar。
引言
我当时在寻找一个更好看的 TrackBar 控件,偶然发现了 Alan Zhao 的 ColorProgressBar 代码,发现创建一个自己的 TrackBar 控件会多么简单。于是,我构思出了 ColorTrackBar
。首先,让我们为这个控件的各个部分或区域建立一些基本的命名约定。
- Bar - 这是控件的背景区域。
- Tracker - 这是
ColorTrackBar
的用户控件部分,它会移动来设置值。
控件属性
BarBorderColor
- 设置/获取绘制条形边框时使用的颜色。BarColor
- 设置/获取控件 Bar 的颜色。TrackerBorderColor
- 设置/获取 Tracker 的边框颜色。TrackerColor
- 设置/获取 Tracker 的颜色。Minimum
- 设置/获取ColorTrackBar
控件可能返回的最小值。Maximum
- 设置/获取控件可能返回的最大值。Value
- 设置/获取 Tracker 相对于最大值和最小值的位置。MaximumValueSide
- 此属性允许用户决定控件的哪个方向会增加Value
属性,反之则会减小Value
。可能的值有Left
、Right
、Top
和Bottom
。BarOrientation
- 选择Horizontal
(水平)或Vertical
(垂直)。ControlCornerStyle
- 选择Square
(方形)或Rounded
(圆角)的角落。TrackerSize
- 根据BarOrientation
的选择指定 tracker 的宽度或高度。注意:如果您选择了Rounded
(圆角)的角落,则无法设置 Tracker 的大小。
控制事件
Scroll
- 当 Tracker 的位置改变时,会触发此事件。ValueChanged
- 当Value
属性的数值改变时,会触发此事件。
使用控件
- 下载源代码并解压。
- 打开 VS.NET 的工具菜单,选择“添加/删除工具箱项”。
- 点击“浏览”,然后导航到源代码的 bin\Release 目录下的 ColorTrackBar.dll。
- 点击打开,然后点击确定,
ColorTrackBar
控件现在就会出现在您的工具箱中,只需将控件拖到您的窗体上即可。 - 设置控件的属性,并连接事件处理程序,您就可以开始使用了。
代码内部
此控件的源代码中可能包含三个有趣的部分,第一个是绘制圆角
protected GraphicsPath DrawRoundedCorners(Rectangle Rect,
Color BorderColor,Graphics g)
{
GraphicsPath gPath = new GraphicsPath();
try
{
Pen LinePen = new Pen(BorderColor,borderWidth+1);
switch(barOrientation)
{
case Orientations.Horizontal:
Rectangle LeftRect,RightRect;
LeftRect=new Rectangle(Rect.X,Rect.Y+1,
Rect.Height-1,Rect.Height-2);
RightRect = new Rectangle(Rect.X+
(Rect.Width-Rect.Height),Rect.Y+1,
Rect.Height-1,Rect.Height-2);
//build shape
gPath.AddArc(LeftRect,90,180);
gPath.AddLine(LeftRect.X+LeftRect.Width/2+2,
LeftRect.Top+1,RightRect.X+(RightRect.Width/2)-1,
RightRect.Top+1);
gPath.AddArc(RightRect,270,180);
gPath.AddLine(RightRect.X+(RightRect.Width/2),
RightRect.Bottom, LeftRect.X+(LeftRect.Width/2),
LeftRect.Bottom);
gPath.CloseFigure();
g.DrawPath(LinePen,gPath);
break;
case Orientations.Vertical:
Rectangle TopRect,BotRect;
TopRect=new Rectangle(Rect.X+1,Rect.Y,
Rect.Width-2,Rect.Width-1);
BotRect = new Rectangle(Rect.X+1,Rect.Y+
(Rect.Height-Rect.Width),
Rect.Width-2,Rect.Width-1);
//build shape
gPath.AddArc(TopRect,180,180);
gPath.AddLine(TopRect.Right,
TopRect.Y+TopRect.Height/2,
BotRect.Right,
BotRect.Y+BotRect.Height/2+1);
gPath.AddArc(BotRect,0,180);
gPath.AddLine(BotRect.Left+1,
BotRect.Y+BotRect.Height/2-1,
TopRect.Left+1,TopRect.Y+TopRect.Height/2+2);
gPath.CloseFigure();
g.DrawPath(LinePen,gPath);
break;
default:
break;
}
}
catch(Exception Err)
{
throw new Exception("DrawRoundedCornersException: "
+Err.Message);
}
return gPath;
}
圆角是通过绘制两个基于控件高度的半圆来创建的。首先,我从 ClientRectangle
中计算出两个子矩形来绘制圆,然后连接半圆的开口端以形成圆角控件。
第二个有趣的部分是绘制圆角控件,这是我借鉴 Alan Zhao 的方法,使用 GradientBrush
来赋予它 3D 外观。
protected void PaintPath(GraphicsPath PaintPath,
Color PathColor,Graphics g)
{
Region FirstRegion,SecondRegion;
FirstRegion = new Region(PaintPath);
SecondRegion= new Region(PaintPath);
//
// Fill background
//
SolidBrush bgBrush = new SolidBrush(ControlPaint.Dark(PathColor));
g.FillRegion(bgBrush, new Region(PaintPath));
bgBrush.Dispose();
//
// The gradient brush
//
LinearGradientBrush brush;
Rectangle FirstRect,SecondRect;
Rectangle RegionRect = Rectangle.Truncate(PaintPath.GetBounds());
switch(barOrientation)
{
case Orientations.Horizontal:
FirstRect= new Rectangle(RegionRect.X,RegionRect.Y,
RegionRect.Width, RegionRect.Height / 2);
SecondRect=new Rectangle(RegionRect.X,
RegionRect.Height / 2, RegionRect.Width,
RegionRect.Height / 2);
//only get the bar region
FirstRegion.Intersect(FirstRect);
SecondRegion.Intersect(SecondRect);
// Paint upper half
brush = new LinearGradientBrush(
new Point(FirstRect.Width/2,FirstRect.Top),
new Point(FirstRect.Width/2,FirstRect.Bottom),
ControlPaint.Dark(PathColor),
PathColor);
g.FillRegion(brush, FirstRegion);
brush.Dispose();
// Paint lower half
// (SecondRect.Y - 1 because there would be
// a dark line in the middle of the bar)
brush = new LinearGradientBrush(
new Point(SecondRect.Width/2,
SecondRect.Top-1),
new Point(SecondRect.Width/2,
SecondRect.Bottom),
PathColor,
ControlPaint.Dark(PathColor));
g.FillRegion(brush, SecondRegion);
brush.Dispose();
break;
case Orientations.Vertical:
FirstRect= new Rectangle(RegionRect.X,RegionRect.Y,
RegionRect.Width/2, RegionRect.Height);
SecondRect=new Rectangle(RegionRect.Width / 2,
RegionRect.Y, RegionRect.Width/2,
RegionRect.Height);
//only get the bar region
FirstRegion.Intersect(FirstRect);
SecondRegion.Intersect(SecondRect);
// Paint left half
brush = new LinearGradientBrush(
new Point(FirstRect.Left, FirstRect.Height/2),
new Point(FirstRect.Right,FirstRect.Height/2),
ControlPaint.Dark(PathColor),
PathColor);
g.FillRegion(brush, FirstRegion);
brush.Dispose();
// Paint right half
// (SecondRect.X - 1 because there would be a dark line
// in the middle of the bar)
brush = new LinearGradientBrush(
new Point(SecondRect.Left - 1,SecondRect.Height/2),
new Point(SecondRect.Right,SecondRect.Height/2),
PathColor,
ControlPaint.Dark(PathColor));
g.FillRegion(brush, SecondRegion);
brush.Dispose();
break;
default:
break;
}
}
首先,我使用 GraphicsPath.GetBounds
获取一个 RectangleF
对象,然后将其截断为常规的 Rectangle
对象。然后,我计算控件的上/下半部分或左/右半部分。为了“过滤掉”矩形的角落,我然后获取与 GraphicPath
的矩形和 GraphicsPath
的形状(圆角)相交的 Region
。然后我用背景色填充这两个半部分。
最后,我认为 Tracker 的移动需要一些解释。我以前从未创建过像这个控件这样移动的控件,所以请原谅我的笨拙的解决方案。所有关于移动的重要代码都在 WndProc()
方法中。
我处理三个消息
WM_LBUTTONDOWN
(0x0201
)WM_LBUTTONUP
(0x0202
)WM_MOUSEMOVE
(0x0200
)
if(m.Msg==0x0201)
{
Point CurPoint=new Point(LowWord((uint)m.LParam),
HighWord((uint)m.LParam));
if(trackRect.Contains(CurPoint))
{
if(!leftbuttonDown)
{
leftbuttonDown=true;
switch(this.barOrientation)
{
case Orientations.Horizontal:
mousestartPos= CurPoint.X-trackRect.X;
break;
case Orientations.Vertical:
mousestartPos= CurPoint.Y-trackRect.Y;
break;
}
}
}
else
{
int OffSet=0;
switch(this.barOrientation)
{
case Orientations.Horizontal:
if(trackRect.Right+(CurPoint.X-trackRect.X
-(trackRect.Width/2))>=this.Width)
OffSet=this.Width-trackRect.Right-1;
else if(trackRect.Left+(CurPoint.X-
trackRect.X-(trackRect.Width/2))<=0)
OffSet=(trackRect.Left-1)*-1;
else
OffSet=CurPoint.X-trackRect.X-(trackRect.Width/2);
trackRect.Offset(OffSet,0);
trackerValue=(int)( ((trackRect.X-1) *
(barMaximum-barMinimum))/(this.Width-trackSize-2));
if(maxSide==Poles.Left)
trackerValue=(trackerValue-(barMaximum-barMinimum))*-1;
break;
case Orientations.Vertical:
if(trackRect.Bottom+(CurPoint.Y-trackRect.Y-
(trackRect.Height/2))>=this.Height)
OffSet=this.Height-trackRect.Bottom-1;
else if(trackRect.Top+(CurPoint.Y-
trackRect.Y-(trackRect.Height/2))<=0)
OffSet=(trackRect.Top-1)*-1;
else
OffSet=CurPoint.Y-trackRect.Y-(trackRect.Height/2);
trackRect.Offset(0,OffSet);
trackerValue=(int)( ((trackRect.Y-1) *
(barMaximum-barMinimum))/(this.Height-trackSize-2));
if(maxSide==Poles.Top)
trackerValue=(trackerValue-(barMaximum-barMinimum))*-1;
break;
default:
break;
}
trackerValue+=barMinimum;
this.Invalidate();
if(OffSet!=0)
{
OnScroll();
OnValueChanged();
}
}
this.Focus();
}
//WM_MOUSEMOVE
if(m.Msg==0x0200)
{
int OldValue=trackerValue;
Point CurPoint=new Point(LowWord((uint)m.LParam),
HighWord((uint)m.LParam));
if(leftbuttonDown && ClientRectangle.Contains(CurPoint))
{
int OffSet=0;
try
{
switch(this.barOrientation)
{
case Orientations.Horizontal:
if(trackRect.Right+(CurPoint.X-trackRect.X
-mousestartPos)>=this.Width)
OffSet=this.Width-trackRect.Right-1;
else if(trackRect.Left+(CurPoint.X-
trackRect.X-mousestartPos)<=0)
OffSet=(trackRect.Left-1)*-1;
else
OffSet=CurPoint.X-trackRect.X-mousestartPos;
trackRect.Offset(OffSet,0);
trackerValue=(int)( ((trackRect.X-1) *
(barMaximum-barMinimum))/(this.Width-trackSize-2));
if(maxSide==Poles.Left)
trackerValue=(trackerValue-(barMaximum-barMinimum))*-1;
break;
case Orientations.Vertical:
if(trackRect.Bottom+(CurPoint.Y-
trackRect.Y-mousestartPos)>=this.Height)
OffSet=this.Height-trackRect.Bottom-1;
else if(trackRect.Top+(CurPoint.Y-
trackRect.Y-mousestartPos)<=0)
OffSet=(trackRect.Top-1)*-1;
else
OffSet=CurPoint.Y-trackRect.Y-mousestartPos;
trackRect.Offset(0,OffSet);
trackerValue=(int)( ((trackRect.Y-1) *
(barMaximum-barMinimum))/(this.Height-trackSize-2));
if(maxSide==Poles.Top)
trackerValue=(trackerValue-(barMaximum-barMinimum))*-1;
break;
}
}
catch(Exception){}
finally
{
//force redraw
trackerValue+=barMinimum;
this.Invalidate();
if(OffSet!=0)
{
OnScroll();
OnValueChanged();
}
}
}
}
WM_LEFTBUTTONDOWN
基本上,如果用户点击 Tracker 的区域内,我会将初始点保存在 mousestartPos
中,当用户拖动 Tracker 时,我将使用它来计算 Tracker 的矩形或区域的偏移量。如果初始点不在 Tracker 的区域内,但仍在 Bar 的区域内,我会将 Tracker 的中心跳转到鼠标点击的位置。
WM_MOUSEMOVE
当我收到 MOVE 消息时,我会通过我的 leftbuttonDown
状态变量检查左键是否按下,并根据初始开始位置、当前鼠标位置和当前 Tracker 矩形来偏移 Tracker 矩形。然后,我根据新的 Tracker 矩形计算 Value
(trackerValue
)。
WM_LBUTTONUP
if(m.Msg==0x0202)
{
leftbuttonDown=false;
}
当接收到左键抬起消息时,我只需将我的状态变量设置为 false
以停止任何移动,并且 reset
允许下次用户单击控件时重置 mousestartPos
。
结论
我使用 ControlDesigner
类从设计器中移除了许多标准的控件事件。通过编辑 ColorTrackBar
源代码,可以非常容易地恢复它们。
我希望其他人也能发现这个控件很有用,请随时发送任何建议或评论。