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

Windows Forms 应用程序的 EasyProgressBar

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (54投票s)

2011年4月24日

CPOL

20分钟阅读

viewsIcon

107243

downloadIcon

9507

本文讨论如何使用 .NET Framework 为 Windows Forms 应用程序创建简单的 ProgressBar 控件。

Sample Image - maximum width is 600 pixels

引言

这是一个 EasyProgressBar 控件,本文还讨论了如何使用 .NET Framework 为 Windows Forms 应用程序创建一个简单的进度条控件。

  • 支持数字样式 [7 段]
  • 渐变控件的进度指示器区域及其背景
  • 进度条颜色支持(RGBA 更改器)
  • 支持终端用户的动态属性
  • 展示如何添加设计时支持,使您的自定义控件在设计时表现正常
  • 它也是一个可停靠控件。允许浮动和停靠模式
  • 为用户提供 EasyProgressBar 操作的键盘支持
  • 在系统菜单中提供额外事件
  • 序列化支持(加载以前的设置或保存当前设置)

Contents at a Glance

总之,本文将涵盖以下内容

开始

要创建提供全新功能或以独特方式组合现有用户界面元素的控件,您可以从现有控件派生以覆盖和增强其功能。此外,所有者绘制的控件使用 GDI+ 绘图例程从头开始生成其界面。因此,它们倾向于继承自控件层次结构中更基本的类,例如 System.Windows.Forms.Control。所有者绘制的控件需要最多的工作,并提供最大的灵活性。

从 .NET 的角度来看,从 Control 类和更高级别的控件类(例如 ProgressBar)派生没有区别。如果您直接从 Control 类派生,您负责在 OnPaint() 方法中手动绘制您的控件,但如果您从 ProgressBar 等类派生,您将继承一个功能齐全的控件,并且只需要添加或自定义您感兴趣的功能。我们将使用第一个。

EasyProgressBar 控件类提供了其基控件类的基本实现,但我们需要为我们的控件创建特定的类、属性、方法和进度事件。为此,我实现了自定义类并定义了一个接口,其名称为 IProgressBar

public interface IProgressBar
{
    /// <summary>
    /// Gets or Sets, the current progress value of the control.
    /// </summary>
    int Value { get; set; }

    /// <summary>
    /// Gets or Sets, the minimum progress value of the control.
    /// </summary>
    int Minimum { get; set; }

    /// <summary>
    /// Gets or Sets, the maximum progress value of the control.
    /// </summary>
    int Maximum { get; set; }

    /// <summary>
    /// Determines whether the control's border is draw or not.
    /// </summary>
    bool IsPaintBorder { get; set; }
    
    /// <summary>
    /// Determines whether the digital number drawing is enabled or not.
    /// </summary>
    bool IsDigitDrawEnabled { get; set; }

    /// <summary>
    /// Determines whether the percentage text is show or hide.
    /// </summary>
    bool ShowPercentage { get; set; }

    /// <summary>
    /// Display text formatting for progressbar value.
    /// </summary>
    string DisplayFormat { get; set; }

    /// <summary>
    /// Gets or Sets, the control's border color from here.
    /// </summary>
    Color BorderColor { get; set; }

    /// <summary>
    /// Gets or Sets, the current border style of the ProgressBar control.
    /// </summary>
    EasyProgressBarBorderStyle ControlBorderStyle { get; set; }

    /// <summary>
    /// Occurs when the progress value changed of the control.
    /// </summary>
    event EventHandler ValueChanged;
}

一些属性可以应用于您的自定义控件类声明,而不是特定属性。这些包括两个设置默认事件和属性的属性(如下表所述)。

基本控件类属性
Attribute 描述
DefaultEvent 当应用程序编程人员双击您的控件时,Visual Studio 会自动为默认事件添加一个事件处理程序。
DefaultProperty DefaultProperty 是第一次选择控件时,“属性”窗口中默认突出显示的属性。一个小提示:当您直接从 Control 类派生并且无法从控件设计器中隐藏“Text”属性时,Text 属性将始终处于活动状态。
[Designer(typeof(EasyProgressBarDesigner))]
[DefaultEvent("ValueChanged"), DefaultProperty("Value")]
public partial class EasyProgressBar : Control, IProgressBar
{
    // To do some things.
}

双缓冲

您可以通过防止控件绘制其背景来减少闪烁。如果这样做,您的代码必须首先使用 Graphics 类中的一种填充方法绘制背景。否则,原始内容将保留在新内容下方。要禁用背景绘制,您只需覆盖控件的 OnPaintBackground() 方法并什么都不做。换句话说,您不会调用基 OnPaintBackground() 方法。

protected override void OnPaintBackground
	(System.Windows.Forms.PaintEventArgs pevent)
{
    // Do nothing.
}

如果您正在使用自定义背景色填充控件,您应该始终遵循此步骤,因为它可以显著提高性能。否则,每次重绘控件时,您的控件窗口将在默认背景色和您绘制的颜色之间明显闪烁。此外,您可以不覆盖 OnPaintBackground() 方法,而是使用 SetStyle() 方法并将 AllPaintingInWmPaint 样式设置为 true。这会告诉控件窗口忽略要求它重绘背景的消息。

this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); 

禁用自动背景绘制会减少闪烁,但闪烁仍然存在。要完全消除它,您可以使用一种称为双缓冲的技术。

DoubleBuffered 属性设置为 true 等效于将 AllPaintingInWmPaintOptimizedDoubleBuffer 控件样式设置为 true。如果您在 OnPaintBackground()OnPaint() 中执行绘制,您应该将 OptimizedDoubleBuffer 属性设置为 true ,但不要设置 DoubleBuffered 属性。此外,您可能希望将 ResizeRedraw 属性设置为 true ,以便控件在大小更改时自动使自身无效。如果绘图逻辑使用依赖于控件大小的计算,这将很有用。但是,仅在需要时才使用它。

我们应该在 control 类的构造函数中将所需的 System.Windows.Forms.ControlStyles 标志设置为 true 。以下代码片段正是如此

#region Constructor

public EasyProgressBar()
{
    this.SetStyle(ControlStyles.AllPaintingInWmPaint | 
	ControlStyles.OptimizedDoubleBuffer |
        ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
}

#endregion

LinearGradientBrush

LinearGradientBrush 允许您以渐变模式混合两种颜色。您可以选择任意两种颜色,然后选择混合布局进行绘图操作。您还可以指定渐变任一侧的起始点。以下是一个在我的代码示例中用渐变填充矩形的示例

protected virtual void DrawBackground(Graphics gr)
{
    using (LinearGradientBrush brush = new LinearGradientBrush
    (Point.Empty, new Point(0, backgroundRectangle.Height), 
	backgroundGradient.ColorStart,
        backgroundGradient.ColorEnd))
    {
        if (!backgroundGradient.IsBlendedForBackground)
            gr.FillRectangle(brush, backgroundRectangle);
        else
        {
            Blend bl = new Blend(2);
            bl.Factors = new float[] { 0.3F, 1.0F };
            bl.Positions = new float[] { 0.0F, 1.0F };
            brush.Blend = bl;
            gr.FillRectangle(brush, backgroundRectangle);
        }
    }
}

您可以轻松地在继承的控件中重写基虚拟绘制方法,也可以重写基类的 OnValueChanged 方法事件。我已将所有这些都确定为虚拟的关键字。

类表

您可以在此处查看类描述

类名 描述
GradientProgress 我们使用此类的成员以特定的渐变填充进度矩形。它包含几种用于绘图操作的属性。
GradientProgressConverter 通常,在设计时无法设置 GradientProgress 类的子属性。为了解决这个问题,我们需要创建一个自定义类型转换器,这是一个派生自 ExpandableObjectConverter 的专门类。
GradientProgressEditor 为了支持缩略图视图,我们需要创建一个自定义类型编辑器。类型编辑器允许您通过在属性窗口中创建渐变的自定义缩略图来添加一些花哨的功能。
GradientBackground 我们的背景绘制类使用类成员。它还包含一些用于绘制背景操作的属性。
GradientBackgroundConverter 一个派生自 ExpandableObjectConverter 的实时 GradientBackground 对象的自定义类型转换器。
GradientBackgroundEditor 我们在属性窗口中为 BackgroundGradient 属性定义一个缩略图视图。
ColorizerProgress 用于我们进度位图的 RGBA(红、绿、蓝和 Alpha)颜色器。您可以使用此类成员轻松更改进度外观。
ColorizerProgressConverter 用于 ProgressColorizer 属性的简单 ExpandableObjectConverter
ColorizerProgressEditor 为了向 ProgressColorizer 属性添加下拉控件功能,我们需要创建一个新的 UITypeEditor
EasyProgressBarDesigner 添加设计时便利,例如设计器动词或智能标签,并从视图中删除不适当的事件或属性(或添加仅设计时事件、属性并为我们的自定义控件创建动态属性)。要创建基本的控件设计器,首先要从 ControlDesigner 派生一个类。
EasyProgressBarActionList 此类具有两个角色——它为智能标签配置 DesignerActionItem 实例的集合,并且在发出命令或进行更改时,它对链接的控件执行相应的操作。
DropDownProgress 为了为我们的进度条添加 RGBA 颜色支持,我们需要创建一个用户控件。因此,您可以轻松地从这个下拉控件中更改您的颜色组件。
DrawDigitalHelper 我们的七段显示绘制类。以 7 段格式绘制给定数字。
GradientDigitBox 您可以使用这些类成员更改 DigitBox 矩形的背景外观。
GradientDigitBoxConverter GradientDigitBox 成员的自定义类型转换器类。
GradientDigitBoxEditor 一个自定义类型编辑器类。用于在属性窗口中创建渐变的自定义缩略图。

控件边框样式

EasyProgressBar 边框样式
边框样式 预览
平面 Sample Image - maximum width is 600 pixels
凹陷 Sample Image - maximum width is 600 pixels
凸起 Sample Image - maximum width is 600 pixels

数字计数表

数字计数及其侧面
Count 数字侧面 预览
1
右侧
Sample Image - maximum width is 600 pixels
2
左侧
Sample Image - maximum width is 600 pixels
3
右侧
Sample Image - maximum width is 600 pixels
4
左侧
Sample Image - maximum width is 600 pixels
5
右侧
Sample Image - maximum width is 600 pixels
n
-
-

ExpandableObjectConverter

不幸的是,在设计时无法设置自定义对象子属性。为了解决这个问题,您需要创建一个自定义类型转换器,它是一个专门的类,可以将自定义对象转换为 string,然后再将 string 转换回实时自定义对象。如果您不使用类型转换器并在“属性”窗口中查看,您将看到一段 static 文本,其中显示了在自定义对象上调用 ToString() 的结果。

Windows Forms 控件支持许多对象属性。最好的例子是 Font,它引用一个完整的 Font 对象,具有 BoldItalicName 等属性。当您在“属性”窗口中设置 Font 属性时,您无需在单个正确格式化的 string 中输入所有这些信息。相反,您可以通过单击加号 (+) 框并单独编辑所有 Font 子属性来展开 Font 属性。

您可以使用自己的自定义对象类型启用相同类型的编辑。您实际上有两种选择——您可以直接使用 ExpandableObjectConverter,或者您可以创建一个派生自 ExpandableObjectConverter 的自定义类型转换器。如果您使用此方法,您将受益于 string 表示以及展开属性以查看子属性的能力。

第一步是创建一个派生自基类 System.ComponentModel.ExpandableObjectConverter 的自定义类,如下所示

class ColorizerProgressConverter : ExpandableObjectConverter
{
    // To do something.
}

之后,我们重写了我们所需的相应方法。

TypeConverter 可重写方法
方法 描述
CanConvertFrom() 此方法检查数据类型,如果类型转换器可以从该数据类型转换为自定义数据类型,则返回 true
ConvertFrom() 此方法执行从提供的数据类型到自定义数据类型的转换。
CanConvertTo() 此方法检查数据类型,如果类型转换器可以将自定义对象转换为此数据类型,则返回 true
ConvertTo() 此方法执行从自定义数据类型到请求数据类型的转换。
#region Override Methods

//All the CanConvertTo() method needs to is check that the target type is a string.
public override bool CanConvertTo
	(ITypeDescriptorContext context, Type destinationType)
{
    if (destinationType == typeof(string))
        return true;
    else
        return base.CanConvertTo(context, destinationType);
}

//ConvertTo() simply checks that it can indeed convert to the desired type.
public override object ConvertTo(ITypeDescriptorContext context,
    System.Globalization.CultureInfo culture, object value, Type destinationType)
{
    if (destinationType == typeof(string))
        return ToString(value);
    else
        return base.ConvertTo(context, culture, value, destinationType);
}

/* The exact same process occurs in reverse 
when converting a ColorizerProgress object to a string.
First the Properties window calls CanConvertFrom(). 
If it returns true, the next step is to call
the ConvertFrom() method. */
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
    if (sourceType == typeof(string))
        return true;
    else
        return base.CanConvertFrom(context, sourceType);
}

public override object ConvertFrom(ITypeDescriptorContext context,
	System.Globalization.CultureInfo culture, object value)
{
    if (value is string)
        return FromString(value);
    else
        return base.ConvertFrom(context, culture, value);
}

#endregion

附加类型转换器

有两种方法可以附加类型转换器。在大多数情况下您应该使用的方法是通过将 TypeConverter 属性添加到类声明来将自定义类型链接到类型转换器。

[TypeConverter(typeof(ColorizerProgressConverter))]
public class ColorizerProgress : IProgressColorizer
{ ... }

另一个选项是将 TypeConverter 属性应用于自定义控件中的属性。

/// <summary>
/// You can change the color components of the progress bitmap
/// [RGBA Colorizer for progress indicator].
/// </summary>
[Description("You can change the color components of the 
progress bitmap[RGBA Colorizer for progress indicator]")]
[TypeConverter(typeof(ColorizerProgressConverter))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Browsable(true)]
[Category("Gradient")]
public ColorizerProgress ProgressColorizer
{
    get { return progressColorizer; }
    set
    {
        try
        {
            if (!value.Equals(progressColorizer))
            {
                progressColorizer.ProgressColorizerChanged -= 
					CONTROL_INVALIDATE_UPDATE;
                progressColorizer = value;
                progressColorizer.ProgressColorizerChanged +=
                	new EventHandler(CONTROL_INVALIDATE_UPDATE);

                Invalidate();
                Update();
            }
        }
        catch (NullReferenceException)
        {
            MessageBox.Show("Value cannot be null!, please enter a valid value.");
        }
    }
}

Sample Image - maximum width is 600 pixels

编辑 ColorizerProgress 类的属性。

UITypeEditor

基类 UITypeEditor 位于 System.Drawing.Design 命名空间中。您可以从该类继承以创建自定义类型编辑器。

您可以使用 Editor 属性将属性与类型编辑器关联。与类型转换器一样,您可以将 Editor 属性应用于类声明或属性声明。

要创建自定义类型编辑器,您必须首先创建一个派生自 System.Drawing.Design.UITypeEditor 的类。然后您可以重写下表中显示的四种方法。

UITypeEditor 可重写方法
ClassMethod 描述
EditValue() 属性编辑时调用。通常,这是您将创建特殊对话框进行属性编辑的地方。
GetEditStyle() 指定类型编辑器是下拉式(提供特殊绘制选项列表)、模态式(提供用于属性选择的对话框)还是(不支持编辑)。
GetPaintValueSupported() 如果您提供了 PaintValue() 实现,请使用此方法返回 true
PaintValue() 调用此方法以绘制表示属性窗口中值的图形缩略图。

下拉式类型编辑器

下拉式类型编辑器会在属性下方的下拉框中显示一个控件。下拉框的大小适合您提供的控件的初始大小,但如果由于屏幕大小或窗口位置而无法容纳,则会调整大小。准备下拉框内容的最佳方法是创建一个用户控件。然后,类型编辑器负责在下拉框中显示该用户控件。

DropDownProgress

DropDownProgress 是一个用户控件。将其从工具箱中排除是有意义的。您可以通过将 ToolboxItem 属性添加到类声明并将其标记为 false 来实现此目的

[ToolboxItem(false)]
partial class DropDownProgress : UserControl
{
    // To do something.
}

本例中真正的诀窍是,您为编辑属性而创建的用户控件需要一种从自定义控件对象接收信息的方式。为了简化此操作,您应该向您的编辑控件添加一个构造函数,该构造函数接受它所需的所有信息。

这是构造函数代码和存储构造函数提供信息的详细信息

#region Constructor

public DropDownProgress()
{
    InitializeComponent();
}

public DropDownProgress(object value, IWindowsFormsEditorService editorService)
    : this()
{
    this.editorService = editorService;
    if (value is ColorizerProgress)
    {
        this.colorizer = value as ColorizerProgress;
    }
}

#endregion

用户可以在下拉控件中更改每个轨迹栏值。因此,我们需要为每个轨迹栏控件创建一个事件处理程序。

private void TRACKBAR_VALUE_CHANGED(object sender, EventArgs e)
{
    if (sender is TrackBar)
    {
        string result = null;
        TrackBar ctrl = (TrackBar)sender;
        if (ctrl.Value == 0)
            result = "Min";
        else if (ctrl.Value == 255)
            result = "Max";

        switch (ctrl.Name)
        {
            case "redTrackBar":
                label1.Text = String.Format("Red: {0}", 
			result ?? ctrl.Value.ToString());
                break;
            case "greenTrackBar":
                label2.Text = String.Format("Green: {0}", 
			result ?? ctrl.Value.ToString());
                break;
            case "blueTrackBar":
                label3.Text = String.Format("Blue: {0}", 
			result ?? ctrl.Value.ToString());
                break;
            default:
                label4.Text = String.Format("Alpha: {0}", 
			result ?? ctrl.Value.ToString());
                break;
        }
    }
}

下一步是开发使用此用户控件的类型编辑器。这是类声明

class ColorizerProgressEditor : UITypeEditor
{
    // To do something.
}

之后,我们需要使用 Editor 属性将此类型编辑器连接到 ColorizerProgress 类。以下代码片段展示了如何将此属性添加到适当的类声明中。如上所述,您也可以使用相同的属性将此类型编辑器附加到适当的属性。

[Editor(typeof(ColorizerProgressEditor), typeof(UITypeEditor))]
public class ColorizerProgress : IProgressColorizer
{ ... }

现在您只需填写类型编辑器代码。首先,我们为 UITypeEditor 选择下拉样式。之后,我们关闭缩略图。因为;我们的工作类(ColorizerProgress)没有实现设计时的绘图行为。最后,在 EditValue() 方法中,您获取编辑器服务,创建 DropDownProgress 用户控件的实例,并使用 IWindowsFormsEditorService.DropDownControl() 方法将其添加到属性窗口中,如下所示

#region Override Methods

public override object EditValue
(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
    if (provider != null)
    {
        IWindowsFormsEditorService editorService =
        provider.GetService(typeof(IWindowsFormsEditorService)) 
			as IWindowsFormsEditorService;
        if (editorService != null)
        {
            using (DropDownProgress dropDownProgress = 
		new DropDownProgress(value, editorService))
            {
                editorService.DropDownControl(dropDownProgress);
                value = dropDownProgress.Colorizer;
            }
        }
    }

    return value;
}

public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
    return UITypeEditorEditStyle.DropDown;  // We choose the drop-down style.
}

/// <summary>
/// Indicates whether the specified context supports 
/// painting a representation of an object's value within the specified context.
/// </summary>
/// <param name="context">An ITypeDescriptorContext that can be used 
/// to gain additional context information.</param>
/// <returns>Normally, true if PaintValue is implemented; otherwise, false.
/// But, in this scope returns false.</returns>
public override bool GetPaintValueSupported(ITypeDescriptorContext context)
{
    return false;   // We turn down thumbnails.
}

#endregion

Sample Image - maximum width is 600 pixels

在“属性”窗口中显示 DropDownProgress 控件。

绘制缩略图

类型编辑器还为您提供了在“属性”窗口中创建渐变的自定义缩略图的机会,让您可以在视觉上更具吸引力。要添加此额外的细节,您只需创建一个类型编辑器并覆盖 PaintValue() 方法。以下是 GradientBackgroundEditor 类的完整示例

#region Override Methods

public override bool GetPaintValueSupported(ITypeDescriptorContext context)
{
    return true;
}

public override void PaintValue(PaintValueEventArgs e)
{
    GradientBackground gradient = e.Value as GradientBackground;
    using (LinearGradientBrush brush = new LinearGradientBrush
    (Point.Empty, new Point(0, e.Bounds.Height), 
	gradient.ColorStart, gradient.ColorEnd))
    {
        if (!gradient.IsBlendedForBackground)
            e.Graphics.FillRectangle(brush, e.Bounds);
        else
        {
            Blend bl = new Blend(2);
            bl.Factors = new float[] { 0.3F, 1.0F };
            bl.Positions = new float[] { 0.0F, 1.0F };
            brush.Blend = bl;
            e.Graphics.FillRectangle(brush, e.Bounds);
        }
    }
}

#endregion

RGBA 颜色器

我创建了 ColorizerProgress 类,用于使用这些类成员更改您的进度外观。它实现了 IProgressColorizer 接口。

public interface IProgressColorizer : IDisposable
{
    /// <summary>
    /// Determines whether the colorizer effect is enable or not for progress bitmap.
    /// </summary>
    bool IsColorizerEnabled { get; set; }

    /// <summary>
    /// Determines whether the transparency effect is visible or not 
    /// for progress bitmap.
    /// </summary>
    bool IsTransparencyEnabled { get; set; }

    /// <summary>
    /// Gets or Sets, the red color component value of the progress bitmap.
    /// </summary>
    byte Red { get; set; }

    /// <summary>
    /// Gets or Sets, the green color component value of the progress bitmap.
    /// </summary>
    byte Green { get; set; }

    /// <summary>
    /// Gets or Sets, the blue color component value of the progress bitmap.
    /// </summary>
    byte Blue { get; set; }

    /// <summary>
    /// Gets or Sets, the alpha color component value of the progress bitmap.
    /// </summary>
    byte Alpha { get; set; }
}

GDI+ 支持应用颜色变换(包括颜色的 alpha 分量)的功能。颜色变换可以创建一些令人印象深刻的效果。例如,您可能希望使图像所有颜色的红色分量更亮,或者使图像中所有蓝色区域变为透明。

构建自定义控件通常不涉及颜色转换。然而,一个有用的操作是使图像半透明。要实现此效果,您可以使用 ImageAttributes 类,该类允许您修改 GDI+ 的功能并更改图像在渲染过程中颜色操作的方式。

您还可以使用 ColorMatrix 类。此类别使用矩阵代数来帮助实现所需的效果。GDI+ 将位图中每个像素的颜色表示为具有四个元素(alpha、红色、绿色和蓝色)的颜色向量,并且我们碰巧可以使用一个 4 X 4 矩阵的数字条目来对图像应用线性变换(例如颜色旋转和颜色缩放)。然而,4 X 4 矩阵不会为我们提供应用颜色平移的工具。如果我们添加一个“虚拟”第五列和行,我们可以在执行线性变换后使用第五列进行平移。

最简单的颜色矩阵是单位矩阵。如果您通过应用单位矩阵执行转换,结果与您开始时的图像相同。换句话说,单位矩阵不会改变任何东西。由于我们在此不深入探讨的数学原因,单位矩阵始终是一个方阵,其主对角线上为一,其他地方为零,如下图所示。尝试颜色转换时的一个好方法是,从单位矩阵开始,然后进行微小调整,直到获得所需的转换。

Sample Image - maximum width is 600 pixels

单位矩阵

对整个图像设置 alpha 分量进行转换很容易。如果您保持所有其他条目不变,您可以更改矩阵中位置 [4][4] 的条目以缩放颜色的 alpha 分量。矩阵中的每个条目都是一个单精度浮点值,因此要将 alpha 分量设置为 60%,请使用下图中显示的矩阵。

Sample Image - maximum width is 600 pixels

Alpha 缩放矩阵

让我们看看我的 DrawProgress(Graphics gr) 绘制方法中使用此技术的示例。

protected virtual void DrawProgress(Graphics gr)
{
    // To do something

    /* Create a new empty image for manipulations. If you use this constructor,
       you get a new Bitmap object that represents a bitmap in memory with
       a PixelFormat of Format32bppARGB. */
    using (Bitmap overlay = new Bitmap
	(progressRectangle.Width + 2, progressRectangle.Height + 2))
    {
        // Make an associated Graphics object.
        using (Graphics bmpGraphics = Graphics.FromImage(overlay))
        {
            // To do something
        }

        /* Create a new color matrix,
           The value Alpha in row 4, column 4 specifies the alpha value */
        float[][] jaggedMatrix = new float[][]
        {
            // Red component   [from 0.0 to 1.0 increase red color component.]
            new float[]{ progressColorizer.IsColorizerEnabled ?
            progressColorizer.Red / 255f : 1.0f , 0.0f , 0.0f , 0.0f , 0.0f },
            // Green component [from 0.0 to 1.0 increase green color component.]
            new float[]{ 0.0f , progressColorizer.IsColorizerEnabled ?
            progressColorizer.Green / 255f : 1.0f , 0.0f , 0.0f , 0.0f },
            // Blue component  [from 0.0 to 1.0 increase blue color component.]
            new float[]{ 0.0f , 0.0f , progressColorizer.IsColorizerEnabled ?
            progressColorizer.Blue / 255f : 1.0f , 0.0f , 0.0f },
            // Alpha component [from 1.0 to 0.0 increase transparency bitmap.]
            new float[]{ 0.0f , 0.0f , 0.0f , 
		progressColorizer.IsTransparencyEnabled ?
            progressColorizer.Alpha / 255f : 1.0f , 0.0f },
            // White component [0.0: goes to Original color, 1.0:
            //goes to white for all color component(Red, Green, Blue.)]
            new float[]{ progressColorizer.IsColorizerEnabled ? 0.2f : 0.0f ,
            progressColorizer.IsColorizerEnabled ? 0.2f : 0.0f ,
              progressColorizer.IsColorizerEnabled ? 0.2f : 0.0f , 0.0f , 1.0f }
        };

        ColorMatrix colorMatrix = new ColorMatrix(jaggedMatrix);

        // Create an ImageAttributes object and set its color matrix
        using (ImageAttributes attributes = new ImageAttributes())
        {
            attributes.SetColorMatrix(
                colorMatrix,
                ColorMatrixFlag.Default,
                ColorAdjustType.Bitmap);

            gr.DrawImage(overlay, new Rectangle(Point.Empty,
            new Size(overlay.Width, overlay.Height)), 0, 0, 
			overlay.Width, overlay.Height,
                  GraphicsUnit.Pixel, attributes);
        }
    }
}

系统菜单

提供额外的菜单项,用于停靠和 alpha 操作。

Sample Image - maximum width is 600 pixels

快捷键

如果 IsUserInteraction 属性已启用,它将为用户提供 EasyProgressBar 操作的键盘支持。

键盘按键
密钥 描述
End 更改为最大值
Home 更改为最小值
左侧 减小控件上的当前值
右侧 增加控件上的当前值
回车/Enter 它改变了我们控件的停靠模式
F1 未实现

EasyProgressBarDesigner

控件设计器允许您管理控件公开的设计时行为和设计时界面(属性和事件)。尽管控件设计器是 Windows Forms 基础设施中相当复杂的组件,但定制现有控件设计器以添加新功能并不困难。

您可以派生自定义控件设计器以与您的自定义控件一起使用。您为什么要创建自己的设计器?

  • 添加设计时便利,例如上下文菜单选项和智能标签
  • 从视图中删除不适当的事件或属性(或添加仅设计时事件属性并创建动态属性
  • 定制控件的设计时外观,使其与运行时外观不同(例如,在空面板周围添加边框
  • 添加对包含其他控件的控件(如工具栏)或具有特殊设计时需求的控件(如菜单)的支持

在设计时,设计器基础结构会在组件放置到窗体上时为其附加一个设计器。(如果同一个组件的多个实例添加到窗体中,Visual Studio 将为所有实例重用同一个设计器。)一旦建立此连接,控件设计器就能够参与开发人员与控件之间的交互。

要创建基本的控件设计器,首先要从 ControlDesigner 派生一个类。以下代码片段展示了如何为您的控件创建控件设计器。

public class EasyProgressBarDesigner : ControlDesigner
{
    // To do something.
}

然后,您可以通过重写内置方法向控件设计器添加功能。完成后,您需要将自定义控件设计器链接到相应的控件。为此,您将 Designer 属性应用于控件声明并指定适当的设计器类型。以下是一个将 EasyProgressBarDesigner 链接到 EasyProgressBar 控件的示例

[Designer(typeof(EasyProgressBarDesigner))]
public partial class EasyProgressBar : Control, IProgressBar
{ ... }

设计器提供了 IDesignerFilter 接口的六种方法,您可以重写这些方法来过滤属性事件特性。这些方法列在下表中

IDesignerFilter 方法
方法 描述
PostFilterAttributes 重写此方法以删除未使用或不适当的属性
PostFilterEvents 重写此方法以删除未使用或不适当的事件
PostFilterProperties 重写此方法以删除未使用或不适当的属性
PreFilterAttributes 重写此方法以添加属性
PreFilterEvents 重写此方法以添加事件
PreFilterProperties 重写此方法以添加属性

从技术上讲,过滤方法允许您修改一个 System.ComponentModel.TypeDescriptor 对象,该对象存储自定义控件的属性、特性和事件信息。Visual Studio 使用此 TypeDescriptor 中的信息来确定在设计时环境中提供哪些内容。

这是一个移除以下代码片段中指定的不适当属性的示例。

protected override void PostFilterProperties
	(System.Collections.IDictionary properties)
{
    properties.Remove("Enabled");
    properties.Remove("Padding");
    properties.Remove("RightToLeft");
    properties.Remove("BackgroundImage");
    properties.Remove("BackgroundImageLayout");

    base.PostFilterProperties(properties);
}

重要提示:通常,在 PreFilterXxx() 方法中始终先调用基方法,在 PostFilterXxx() 方法中始终后调用基方法。这样,所有设计器类都有适当的机会应用其更改。ControlDesignerComponentDesigner 使用这些方法来添加诸如 VisibleEnabledNameLocked 等属性。

动态属性

为我们的自定义控件创建动态属性。我们需要重写 PreFilterProperties 方法。此外,我们还需要在相同的方法中使用 TypeDescriptor.CreateProperty() 更改属性属性。以下代码片段展示了如何将此行为应用于您的自定义控件。

/// <summary>
/// Override this method to add some properties 
///to the control or change the properties attributes for a dynamic user interface.
/// </summary>
/// <param name="properties">Properties collection of the control
/// before than add a new property to the collection by user.</param>
protected override void PreFilterProperties

	(System.Collections.IDictionary properties)
{
    base.PreFilterProperties(properties);

    // We don't want to show the "Text" property to the end-users at the design-time.
    properties["Text"] = TypeDescriptor.CreateProperty(typeof(EasyProgressBar),
      (PropertyDescriptor)properties["Text"], BrowsableAttribute.No);

    /* After than, we don't want to see some properties at the design-time
    for general reasons(Dynamic property attributes). */
    EasyProgressBar progressBar = Control as EasyProgressBar;

    if (progressBar != null)
    {
        if (progressBar.ControlBorderStyle != EasyProgressBarBorderStyle.Flat)
        {
            properties["BorderColor"] = 
		TypeDescriptor.CreateProperty(typeof(EasyProgressBar),
              (PropertyDescriptor)properties["BorderColor"], BrowsableAttribute.No);
        }

        if (!progressBar.IsPaintBorder)
        {
            properties["BorderColor"] = 
		TypeDescriptor.CreateProperty(typeof(EasyProgressBar),
              (PropertyDescriptor)properties["BorderColor"], BrowsableAttribute.No);
            properties["ControlBorderStyle"] = 
		TypeDescriptor.CreateProperty(typeof(EasyProgressBar),
              (PropertyDescriptor)properties["ControlBorderStyle"], 
			BrowsableAttribute.No);
        }

        if (!progressBar.ShowPercentage)
        {
            properties["Font"] = 
		TypeDescriptor.CreateProperty(typeof(EasyProgressBar),
              (PropertyDescriptor)properties["Font"], BrowsableAttribute.No);
            properties["ForeColor"] = 
		TypeDescriptor.CreateProperty(typeof(EasyProgressBar),
              (PropertyDescriptor)properties["ForeColor"], BrowsableAttribute.No);
            properties["DisplayFormat"] = 
		TypeDescriptor.CreateProperty(typeof(EasyProgressBar),
              (PropertyDescriptor)properties["DisplayFormat"], 
		BrowsableAttribute.No);
        }
    }
}

您仍然需要使用自定义控件类中的 System.ComponentModel.RefreshProperties 属性将每个自定义属性链接到相应的属性。以下代码片段显示了如何将此属性添加到适当的属性声明并指定适当的标识符类型。

[RefreshProperties(RefreshProperties.All)]
public bool ShowPercentage
{ get; set; }

例如,当用户更改 EasyProgressBar.ShowPercentage 属性时。我们将向最终用户显示或隐藏 FontForeColor DisplayFormat 属性。

智能标签

最新版本的 Visual Studio 包含一个新功能,用于创建丰富的可视化设计体验——智能标签。智能标签是当您单击角落里的小箭头时,控件旁边出现的弹出窗口。

智能标签与菜单类似,它们都包含一个项目列表。然而,这些项目可以是命令(呈现为超链接),也可以是其他控件,如复选框下拉列表等。它们还可以包含静态描述性文本。通过这种方式,智能标签可以充当一个迷你属性窗口。

下图显示了自定义智能标签的示例。它允许开发人员设置 EasyProgressBar 属性的组合。

Sample Image - maximum width is 600 pixels

智能标签窗口示例

要创建此智能标签,您需要以下要素

  • DesignerActionItem 对象集合:每个 DesignerActionItem 表示智能标签中的一个单独项。
  • 动作列表类:此类别有两个角色——它为智能标签配置 DesignerActionItem 实例的集合,并在发出命令或进行更改时,对链接的控件执行相应的操作。
  • 控件设计器:这将您的动作列表连接到控件,以便智能标签在设计时出现。

动作列表

创建智能标签在概念上类似于添加 designer verbs——您重写控件设计器中的一个方法,并返回您想要支持的命令集合。(此命令列表称为 action list。)

然而,智能标签比设计器动词允许更多的选项,因此相关的代码可能更复杂。为了控制它,最好通过创建一个封装您的操作列表的自定义类来分离您的代码。此自定义类应派生自 DesignerActionList(在 System.ComponentModel.Design 命名空间中)。

以下是一个创建用于 EasyProgressBar 的动作列表的示例

public class EasyProgressBarActionList : DesignerActionList
{
    // To do something.
}

您应该为操作列表添加一个需要匹配控件类型的单个构造函数。然后,您可以将对控件的引用存储在成员变量中。这不是必需的,因为基类 ActionList 确实提供了一个 Component 属性,该属性提供对您的控件的访问。但是,通过使用此方法,您可以方便地强类型访问您的控件。

#region Constructor

// The constructor associates the control to the smart tag action list.
public EasyProgressBarActionList(EasyProgressBar control)
    : base(control)
{
    linkedControl = control;
    designerService = 
	(DesignerActionUIService)GetService(typeof(DesignerActionUIService));

    this.AutoShow = true;   // When this control will be added to the design area,
    		//the smart tag panel will open automatically.
}

#endregion

要创建您的智能标签,您需要构建一个 DesignerActionItemCollection,它将您的 DesignerActionItem 对象组组合在一起。此集合中的顺序很重要,因为 Visual Studio 将按照 DesignerActionItem 对象在智能标签中出现的顺序从上到下添加它们。

要构建操作列表,您需要重写 DesignerActionList.GetSortedActionItems() 方法,创建 DesignerActionItemCollection,将每个 DesignerActionItem 添加到其中,然后返回该集合。根据智能标签的复杂性,这可能需要几个步骤。

第一步是创建将智能标签分成独立区域的标题。然后您可以将其他项目添加到这些类别中,如下所示

public override DesignerActionItemCollection GetSortedActionItems()
{
    DesignerActionItemCollection items = new DesignerActionItemCollection();
    try
    {
        // Creating the action list static headers.
        items.Add(new DesignerActionHeaderItem("Progress"));
        items.Add(new DesignerActionHeaderItem("Appearance"));

        items.Add(new DesignerActionPropertyItem("Value", "Value", "Progress",
            "Sets, the current progress value of the control."));

        items.Add(new DesignerActionMethodItem(this,
            "IsColorizerEnabled", "Is Colorizer Enabled: " +
            (linkedControl.ProgressColorizer.IsColorizerEnabled ? 
		"ON" : "OFF"), "Appearance",
            "Determines whether the colorizer effect is enabled or not 
		for progress bitmap.", false));

        items.Add(new DesignerActionMethodItem(this,
            "IsTransparencyEnabled", "Is Transparency Enabled: " +
            (linkedControl.ProgressColorizer.IsTransparencyEnabled ? 
			"ON" : "OFF"), "Appearance",
            "Determines whether the transparency effect is
            visible or not for progress bitmap.", false));

        // Add a new static header and its items.
        items.Add(new DesignerActionHeaderItem("Information"));
        items.Add(new DesignerActionTextItem("X: " + linkedControl.Location.X +
        ", " + "Y: " + linkedControl.Location.Y, "Information"));
        items.Add(new DesignerActionTextItem("Width: " + linkedControl.Size.Width +
        ", " + "Height: " + linkedControl.Size.Height, "Information"));
    }
    catch (Exception ex)
    {
        MessageBox.Show("Exception while generating the
        action list panel for this EasyProgressBar, " + ex.Message);
    }

    return items;
}

您仍然需要将其连接到您的控件。要将此操作列表添加到您的控件,您需要在自定义设计器中重写 ActionLists 属性,创建一个新的 DesignerActionListCollection,并添加相应的 DesignerActionList 对象条目。您的控件设计器处理操作项事件,通常通过更新关联的控件。请注意,操作列表并非每次调用 ActionList 时都创建——相反,它被缓存为 private 成员变量以优化性能。

public override DesignerActionListCollection ActionLists
{
    get
    {
        if (actionLists == null)
        {
            actionLists = new DesignerActionListCollection();
            actionLists.Add(new EasyProgressBarActionList((EasyProgressBar)Control));
        }

        return actionLists;
    }
}

测试客户端

我已使用 ParameterizedThread 为我们的 EasyProgressBar 控件添加了启动/停止功能。让我们看一个使用 EasyProgressBar 的行为的示例。

// Starts or stops the current progressThread.
private void button1_Click(object sender, EventArgs e)
{
    if (!isProgressStarted)
    {
        easyProgressBar1.ValueChanged += (thrower, ea) =>
        {
            if (easyProgressBar1.Value == easyProgressBar1.Maximum)
            {
                isProgressStarted = false;

                status.Text = "Progress execution is completed";
                button1.Text = "Start New Progress";
            }
        };

        progressThread = new Thread(new ParameterizedThreadStart(ProgressChannel));
        progressThread.Name = "ProgressBar thread";
        progressThread.IsBackground = true;

        ParameterizedThreadClass pThread = new ParameterizedThreadClass
        (easyProgressBar1.Minimum, easyProgressBar1.Maximum, easyProgressBar1.Value);
        progressThread.Start(pThread);

        // Set new value.
        isProgressStarted = true;

        // Write log message.
        status.Text = String.Format("Channel Started !!!,
        The {0} is started at {1:F}", progressThread.Name, DateTime.Now);

        // Update button text.
        button1.Text = "Stop";
    }
    else
    {
        if (progressThread.IsAlive)
        {
            /* Tell the progressThread to abort itself immediately,
            raises a ThreadAbortException in the progressThread
            after calling the Thread.Join() method. */
            progressThread.Abort();

            // Wait for the progressThread to finish.
            progressThread.Join();

            isProgressStarted = false;

            // Update button text.
            button1.Text = "Resume Progress";
        }
    }
}

private void ProgressChannel(object instance)
{
    if (instance is ParameterizedThreadClass)
    {
        ParameterizedThreadClass pThread = (ParameterizedThreadClass)instance;

        try
        {
            int startingValue = pThread.ProgressValue == 
		pThread.Maximum ? pThread.Minimum : pThread.ProgressValue;
            for (int i = startingValue + 1; i <= pThread.Maximum; i++)
            {
                UpdateProgressBar(i);
                Thread.Sleep(50);
            }
        }
        catch (ThreadAbortException)
        {
            // Write log message.
            UpdateStatusBar(String.Format("Channel Aborted !!!, 
		The {0} is destroyed and stopped at {1:F}", 
		Thread.CurrentThread.Name, DateTime.Now));
        }
    }
}

参考文献

更多详情,请查阅以下书籍

  • Pro .NET 2.0 Windows Forms and Custom Controls in C#, 作者 Matthew MacDonald
  • Pro .NET 2.0 Graphics Programming, 作者 Eric White

历史

  • 2011年6月7日 - 更新
    • 当我们的控件处于浮动模式时,添加了更高级的系统菜单
    • 添加了七段数字样式
    • 为控件窗口添加了透明度支持
    • 添加了键盘用户交互
    • EasyProgressBar 控件添加了可停靠支持
    • 添加了数据加载和保存的序列化和反序列化支持(XML 和二进制序列化,您可以从 DataSaverAttribute 类中选择一种)
  • 2011年4月24日 - 首次发布
© . All rights reserved.