65.9K
CodeProject 正在变化。 阅读更多。
Home

默认 TrackBar 的一个变种

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.27/5 (9投票s)

2004 年 8 月 12 日

4分钟阅读

viewsIcon

114446

downloadIcon

3578

多色、多方向的 TrackBar。

Sample Image - ColorTrackBar.jpg

引言

我当时在寻找一个更好看的 TrackBar 控件,偶然发现了 Alan Zhao 的 ColorProgressBar 代码,发现创建一个自己的 TrackBar 控件会多么简单。于是,我构思出了 ColorTrackBar。首先,让我们为这个控件的各个部分或区域建立一些基本的命名约定。

  • Bar - 这是控件的背景区域。
  • Tracker - 这是 ColorTrackBar 的用户控件部分,它会移动来设置值。

控件属性

  • BarBorderColor - 设置/获取绘制条形边框时使用的颜色。
  • BarColor - 设置/获取控件 Bar 的颜色。
  • TrackerBorderColor - 设置/获取 Tracker 的边框颜色。
  • TrackerColor - 设置/获取 Tracker 的颜色。
  • Minimum - 设置/获取 ColorTrackBar 控件可能返回的最小值。
  • Maximum - 设置/获取控件可能返回的最大值。
  • Value - 设置/获取 Tracker 相对于最大值和最小值的位置。
  • MaximumValueSide - 此属性允许用户决定控件的哪个方向会增加 Value 属性,反之则会减小 Value。可能的值有 LeftRightTopBottom
  • BarOrientation - 选择 Horizontal(水平)或 Vertical(垂直)。
  • ControlCornerStyle - 选择 Square(方形)或 Rounded(圆角)的角落。
  • TrackerSize - 根据 BarOrientation 的选择指定 tracker 的宽度或高度。注意:如果您选择了 Rounded(圆角)的角落,则无法设置 Tracker 的大小。

控制事件

  • Scroll - 当 Tracker 的位置改变时,会触发此事件。
  • ValueChanged - 当 Value 属性的数值改变时,会触发此事件。

使用控件

  1. 下载源代码并解压。
  2. 打开 VS.NET 的工具菜单,选择“添加/删除工具箱项”。
  3. 点击“浏览”,然后导航到源代码的 bin\Release 目录下的 ColorTrackBar.dll
  4. 点击打开,然后点击确定,ColorTrackBar 控件现在就会出现在您的工具箱中,只需将控件拖到您的窗体上即可。
  5. 设置控件的属性,并连接事件处理程序,您就可以开始使用了。

代码内部

此控件的源代码中可能包含三个有趣的部分,第一个是绘制圆角

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() 方法中。

我处理三个消息

  1. WM_LBUTTONDOWN (0x0201)
  2. WM_LBUTTONUP (0x0202)
  3. 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 矩形计算 ValuetrackerValue)。

WM_LBUTTONUP
if(m.Msg==0x0202)
{
  leftbuttonDown=false;
}

当接收到左键抬起消息时,我只需将我的状态变量设置为 false 以停止任何移动,并且 reset 允许下次用户单击控件时重置 mousestartPos

结论

我使用 ControlDesigner 类从设计器中移除了许多标准的控件事件。通过编辑 ColorTrackBar 源代码,可以非常容易地恢复它们。

我希望其他人也能发现这个控件很有用,请随时发送任何建议或评论。

© . All rights reserved.