介绍
本文讨论如何使用.NET框架为windows窗体应用程序定制选项卡控件。示例应用程序在演示程序和源项目zip文件中可用。
- 记忆的支持
- 支持键盘导航
- 支持拖拽选项卡页面从一个容器转移到另一个标签改变也在同一个容器中
- 添加标签页显示/隐藏下拉菜单的功能
- 梯度选中的选项卡项及其背景
- 彩色化支持控制标题(RGBA改变)
- 支持终端用户的动态属性
- 展示如何添加设计时支持你的自定义控件在设计时行为正当
- 自定义msctls_updown32照片卷轴。 支持6种不同风格
- 为KRBTabControl提供键盘支持用户操作
- 支持透明背景的控制标题和选定的选项卡项目
- 序列化支持(加载以前的设置或保存当前设置。)
- 事件通知(图纸更改,更改下拉菜单,选项卡页面选择和关闭操作)
内容一目了然
不管怎样,我要在这篇文章如下:
- 开始
- ExpandableObjectConverter
- UITypeEditor
- RGBA再着色
- 用户交互的键盘快捷键
- 先进的设计时支持
- 拖放支持
- 选项卡标题样式
- 下拉菜单定制
- 防止标签页选择或标签页关闭用户的行动
类表
KRBTabControl
类提供了一个基本的控制基类的实现,但我们需要创建特定的类,属性,方法和事件对我们的控制。 要做到这一点,我已经实现了定制类。
在这里你可以检查类描述。
类名 | 描述 |
GradientTab |
我们选中的选项卡项目矩形及其边界填充特定梯度通过使用这个类成员。 它包括几种绘图操作的属性。 |
GradientTabConverter |
通常情况下,没有办法的subproperties设置 GradientTab 在设计时类。 为了解决这个问题,我们需要创建一个自定义类型转换器是一个来源于专业类 ExpandableObjectConverter 。
|
GradientTabEditor |
支持缩略图,我们需要创建一个自定义类型编辑器。 类型编辑给你改变一点幻想通过创建一个自定义缩略图的梯度属性窗口。 |
GradientTabEditor | 这个类,我们填选项卡页面头部区域的各种模式。 它包含四个属性为绘画操作。 |
HatcherConverter |
自定义类型转换器 孵卵器 对象,来源于 ExpandableObjectConverter 。
|
HatcherEditor |
我们定义一个缩略图视图 BackgroundHatcher 属性在属性窗口。
|
HatchStyleConverter |
错误! 枚举的 HatchStyle ,多个值时调用的结果 HatchStyle 枚举。 因此,我们提供的列表 HatchStyle 标准的价值观。
|
CaptionGradient |
您可以确定一个新的标题梯度风格选项卡控件通过使用这些类成员。 |
CaptionGradientConverter |
一个自定义类型转换器类 CaptionGradient 成员。
|
CaptionGradientEditor |
一个自定义类型编辑器类。 创建一个自定义缩略图的梯度属性窗口。 |
ButtonsCaption |
你可以改变你的活跃或者不活跃的标题按钮的颜色通过使用这些类成员。 |
ButtonsCaptionConverter |
一个自定义类型转换器类 ButtonsCaption 成员。
|
ButtonsCaptionEditor |
一个自定义类型编辑器类。 创建一个自定义缩略图的梯度属性窗口。 |
RandomizerCaption |
RGBA(红,绿,蓝和α)为控制标题再着色。 你可以很容易地改变你的标题出现通过使用这个类成员。 |
RandomizerCaptionConverter |
一个简单的 ExpandableObjectConverter 为 CaptionRandomizer 财产。
|
CaptionColorChooserEditor |
添加一个模态对话框的能力 CaptionRandomizer 属性,我们需要创建一个新的 UITypeEditor 。
|
TabpageExCollectionEditor |
我们需要创建一个自定义集合编辑器创建的一个新实例 TabPageEx 类或破坏现有的 TabPageEx 在我们TabPages集合编辑器。
|
TabPageExPool |
这个集合类包含我们的隐藏选项卡页面。 你在设计时不能隐藏选项卡页面。 它只在运行时工作模式。 |
Custom3DBorder |
我们的分离器抽屉班。 它画一条垂直的线连接指定的两个点坐标对。 |
ArrowWindow |
似乎只有当我们拖着一个标签项项目另一个选项卡。 |
UpDown32 |
隐藏或破坏系统” msctls-updown32” 照片卷轴按钮从容器中,我们需要创建一个来源于原生窗口类 NativeWindow 。
|
Scroller 照片卷轴 |
照片卷轴类包括左和右 RolloverUpDown 控制。 你可以改变照片卷轴样式的属性对话框。
|
RolloverUpDown |
与翻转上下按钮控制,很少与GDI +执行工作。 相反,上下按钮图像的四个州准备在一个单独的程序,并导入到应用程序资源。 这些图像无缝地分配给它们之间的控制开关(通过派生一个类 UpDownBase )。
|
UpDownBase |
让一个自定义的上下按钮按钮照片卷轴,最有意义的获得直接从控制类,因为你需要实现自己所有的绘图操作。 的 UpDownBase 控制是声明为抽象类不能直接实例化。
|
KRBTabControlDesigner |
添加设计时方便,如设计器谓词或智能标记,也删除不适当的事件或属性视图(或添加design-time-only事件、属性和为我们的自定义控件)创建动态属性。 创建一个基本的控制设计,通过派生类从开始 ParentControlDesigner 。
|
KRBTabControlActionList |
这个类有两个roles-it配置的集合 DesignerActionItem 实例智能标记和当一个命令或改变,它执行相应的操作与控制。
|
TabPageEx |
我们的自定义选项卡页面类。 我们需要改变和添加一些属性。 |
CaptionColorChooser |
在标题更改RGBA值位图。 我们需要使用一个模态对话框改变颜色属性。 |
ContextMenuShownEventArgs |
这门课是我们的下拉菜单项参数。 |
SelectedIndexChangingEventArgs |
为了防止标签页选择或关闭,我们需要创建一个定制的 EventArgs 类。
|
控制边界样式
边框样式 | 预览 |
固体 |
|
虚线 |
|
Dashed |
控制属性表
财产 | 描述 |
|
获取或设置选中的选项卡页面风格。 |
|
获取或设置边框颜色的控制。 |
|
获取或设置图标的颜色选择选项卡。 |
|
获取或设置距离像素,第一个选项卡页面的左边缘与容器的左边缘的客户区,该值必须在2至5像素大小的范围。 |
|
获取或设置的背景风格选项卡页眉。 |
|
获取或设置渐变颜色的选定的选项卡页项目。 |
|
您可以确定一个新的选项卡控件标题梯度的风格和颜色。 |
|
你可以改变你的活跃或者不活跃的标题按钮的颜色。 |
|
你可以改变你的活跃的和不活跃的标题颜色组件(红,绿,蓝,α)。 |
|
获取或设置照片卷轴(上/下)选项卡控件的样式。 |
|
确定活动选项卡是否延伸到它的父容器。 |
|
决定是否选项卡标题背景画。 |
|
决定是否选项卡控件的标题是可见的。 |
|
确定选项卡控件的边界是否画,你必须设置IsCaptionVisible的值为false此更改生效。 |
|
向用户提供键盘支持选项卡控制操作。 |
|
确定标签之间的分隔线是否可见选项卡页面。 |
|
确定选项卡控件像文档标签样式。 |
选项卡页面风格
选项卡页面风格 | 预览 |
KRBStyle |
|
OfficeXP |
|
VS2010 |
控制事件
我实现了几个事件控制设计和最终用户通知。
事件名称 | 描述 |
|
事件提出当IsDrawHeader属性的值改变。 |
|
事件提出当IsCaptionVisible属性的值改变。 |
|
事件引发的价值HeaderVisibility(StretchToParent)属性发生了改变。 |
|
发生在一个标签页面被关闭。 |
|
发生在一个标签页面被选中。 |
|
当用户点击下拉图标标题如果是可见的。 |
透明背景的支持
预览 | |
CaptionBar |
|
TabPages |
|
空集装箱 |
如果选项卡页面数小于1,你会看到一个空的容器标签如下。 |
的ExpandableObjectConverter
不幸的是,没有办法在设计时subproperties设置一个自定义对象。 为了解决这个问题,您需要创建一个自定义类型转换器,它是一个专门的类,可以转换到一个自定义对象 字符串
,然后把 字符串
回到一个自定义对象。 如果你不使用一个类型转换器,在属性窗口中,你会看到一块 静态
文本显示调用的结果 ToString()
自定义对象。
许多对象属性支持Windows窗体控件。 最好的例子是 字体
,指的是一个成熟的 字体
对象的属性如 大胆的
, 斜体
, 的名字
,等等。 当你设置 字体
属性在属性窗口中,您不需要所有这些信息在一个类型,正确格式化 字符串
。 相反,你可以扩大 字体
属性通过点击加号(+)框和编辑所有的 字体
单独subproperties。
您可以启用相同类型的编辑您自己的自定义对象类型。 你有两个选择可以使用 ExpandableObjectConverter
直接,或者您可以创建一个自定义类型转换器,来源于ExpandableObjectConverter
。 如果您使用这种方法,你会有好处的 字符串
表示和扩大房地产看到subproperties的能力。
第一步是创建一个自定义类,它来自基类 System.ComponentModel.ExpandableObjectConverter
,如下所示:
class RandomizerCaptionConverter : ExpandableObjectConverter
{
// To do something. }
比后,我们使用覆盖所需的方法。
方法 | 描述 |
CanConvertFrom() |
该方法检查数据类型,并返回 真正的 如果类型转换器可以从这个数据类型转换到自定义数据类型。
|
ConvertFrom() |
此方法执行从所提供的数据类型转换到自定义数据类型。 |
CanConvertTo() |
该方法检查数据类型,并返回 真正的 如果从自定义对象类型转换器可以转换数据类型。
|
ConvertTo() |
这个方法执行自定义数据类型的转换请求的数据类型。 |
1 #region Override Methods 2 3 //所有的canconvertto()方法需要检查目标的类型是一个字符串。 4 public override bool CanConvertTo 5 (ITypeDescriptorContext context, Type destinationType) 6 { 7 if (destinationType == typeof(string)) 8 return true; 9 else 10 return base.CanConvertTo(context, destinationType); 11 } 12 13 //ConvertTo() simply checks that it can indeed convert to the desired type. 14 public override object ConvertTo(ITypeDescriptorContext context, 15 System.Globalization.CultureInfo culture, object value, Type destinationType) 16 { 17 if (destinationType == typeof(string)) 18 return ToString(value); 19 else 20 return base.ConvertTo(context, culture, value, destinationType); 21 } 22 23 /* 完全相同的过程发生在相反的过程中。 24 将对象转换为字符串时randomizercaption。 25 第一个属性窗口调用canconvertfrom()。 26 如果返回true,则下一步是调用 27 convertfrom()方法。 */ 28 public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) 29 { 30 if (sourceType == typeof(string)) 31 return true; 32 else 33 return base.CanConvertFrom(context, sourceType); 34 } 35 36 public override object ConvertFrom(ITypeDescriptorContext context, 37 System.Globalization.CultureInfo culture, object value) 38 { 39 if (value is string) 40 return FromString(value); 41 else 42 return base.ConvertFrom(context, culture, value); 43 } 44 45 #endregion
附加一个类型转换器
有两种方法可以附加一个类型转换器。 你应该在大多数情况下使用的方法是将自定义类型转换器通过添加类型 TypeConverter
属性类声明。
1 [TypeConverter(typeof(RandomizerCaptionConverter))] 2 public class RandomizerCaption : ICaptionRandomizer 3 { ... }
和另一个选择是应用 TypeConverter
您的自定义属性的属性控制。
1 /// <summary> 2 /// You can change your active and inactive caption color components 3 /// (Red, Green, Blue, Alpha). 4 /// </summary> 5 [Description("You can change your active and inactive caption color components (Red, Green, Blue, Alpha)")] 6 [TypeConverter(typeof(RandomizerCaptionConverter))] 7 [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] 8 [Browsable(true)] 9 public RandomizerCaption CaptionRandomizer 10 { 11 get { return _captionRandomizer; } 12 set 13 { 14 try 15 { 16 if (!value.Equals(_captionRandomizer)) 17 { 18 _captionRandomizer.CaptionRandomizerChanged -= 19 CONTROL_INVALIDATE_UPDATE; 20 _captionRandomizer = value; 21 _captionRandomizer.CaptionRandomizerChanged += 22 new EventHandler(CONTROL_INVALIDATE_UPDATE); 23 24 Invalidate(); 25 Update(); 26 } 27 } 28 catch (NullReferenceException) 29 { 30 MessageBox.Show("Value cannot be null!, please enter a valid value."); 31 } 32 } 33 }
的UITypeEditor
基地 UITypeEditor
类中 System.Drawing.Design
名称空间。 您可以从这个类继承来创建您的自定义类型编辑器。
你将一个属性与类型编辑器使用 编辑器
属性。 同类型转换器,您可以应用 编辑器
属性类声明或声明一个属性。
创建一个自定义类型编辑器,您必须首先创建一个类,它来源于 System.Drawing.Design.UITypeEditor
。 然后您可以覆盖以下表所示的四种方法。
ClassMethod | 描述 |
EditValue() |
当调用属性编辑。 一般来说,在这里您将创建一个特殊的对话框属性编辑。 |
GetEditStyle() |
指定编辑器是一个类型 下拉 (提供了专门绘制的列表选择), 模态 (为属性提供了一个对话框中选择),或 没有一个 (没有编辑支持)。 |
GetPaintValueSupported() |
使用此返回 真正的 如果你提供一个 PaintValue() 实现。
|
PaintValue() |
画一个图形调用缩略图表示属性窗口中的值。 |
模态类型编辑器
模态类型编辑器显示了属性值旁边的省略号(…)按钮。 单击此按钮时,出现一个对话框,允许开发人员改变属性值。
CaptionColorChooser
的 CaptionColorChooser
是一种模态形式。 是有意义的代码编辑器。 你可以做到这一点通过添加 内部类声明的关键字。
internal partial class CaptionColorChooser : Form { // To do something. }
真正的诀窍在本例中,您创建的模态形式编辑属性需要一种方法来接收信息的自定义控件对象。 要做到这一点,你应该创建一些公共实例编辑控制接收所需的所有信息。
存储instance-supplied信息的详细信息:
#region Instance Members
public KRBTabControl contextInstance; public ICaptionRandomizer Randomizer; #endregion
每一个 NumericUpDown
值可以改变用户在模态对话框。 所以我们需要为每个NumericUpDown控制创建一个事件处理程序,然后我们应该更新当前视图的形式。
private void numeric_ValueChanged(object sender, EventArgs e) { // Update current view on the control. Captions.Invalidate(); Captions.Update(); }
下一步是开发类型编辑器,使用该模态形式。 类声明的是:
class CaptionColorChooserEditor : UITypeEditor
{
// To do something. }
比后,我们需要这种类型编辑器来连接 RandomizerCaption
类使用 编辑器
属性。 下面的代码片段显示了如何将这个属性添加到您合适的类声明。 正如上面提到的,你也可以把这种类型编辑器使用适当的属性 相同 属性。
[Editor(typeof(CaptionColorChooserEditor), typeof(UITypeEditor))] public class RandomizerCaption : ICaptionRandomizer { ... }
所有您需要做的就是填写类型编辑器代码。 首先,我们选择的模态风格 UITypeEditor
。 比后,我们拒绝缩略图。 因为,我们的工人类( RandomizerCaption
)没有实现绘图设计时的行为。 最后,在 EditValue()
方法 创建一个实例 的 CaptionColorChooser
形式,设置初始属性的使用范围,如下所示:
1 #region Override Methods 2 3 public override object EditValue 4 (ITypeDescriptorContext context, IServiceProvider provider, object value) 5 { 6 ICaptionRandomizer current; 7 using (CaptionColorChooser frm = new CaptionColorChooser()) 8 { 9 // Set currently objects to the form. 10 frm.Randomizer = (ICaptionRandomizer)value; 11 frm.contextInstance = context.Instance as KRBTabControl; 12 13 if (frm.ShowDialog() == DialogResult.OK) 14 current = frm.Randomizer; 15 else 16 current = (ICaptionRandomizer)value; 17 } 18 19 return current; 20 } 21 22 public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) 23 { 24 // We will use a window for property editing. 25 return UITypeEditorEditStyle.Modal; 26 } 27 28 public override bool GetPaintValueSupported(ITypeDescriptorContext context) 29 { 30 // No special thumbnail will be shown for the grid. 31 return false; 32 } 33 34 #endregion
画一个缩略图
类型编辑也给你机会有点幻想通过创建一个自定义缩略图的梯度属性窗口。 添加额外的技巧,所有您需要做的是创建一个类型编辑器和覆盖 PaintValue()
方法。 这里的完整的示例 GradientTabEditor
类:
#region Override Methods public override bool GetPaintValueSupported(ITypeDescriptorContext context) { return true; } public override void PaintValue(PaintValueEventArgs e) { GradientTab gradient = e.Value as GradientTab; using (LinearGradientBrush brush = new LinearGradientBrush(e.Bounds, gradient.ColorStart, gradient.ColorEnd, gradient.GradientStyle)) { e.Graphics.FillRectangle(brush, e.Bounds); } } #endregion
RGBA再着色
我已经创建了 RandomizerCaption
类来改变你的标题栏外观通过使用这些类成员。 它实现了 ICaptionRandomizer
接口。
1 public interface ICaptionRandomizer : IDisposable 2 { 3 /// <summary> 4 /// Determines whether the randomizer effect is enable or not for tab control caption. 5 /// </summary> 6 bool IsRandomizerEnabled { get; set; } 7 8 /// <summary> 9 /// Determines whether the transparency effect is visible or not 10 /// for tab control caption. 11 /// </summary> 12 bool IsTransparencyEnabled { get; set; } 13 14 /// <summary> 15 /// Gets or Sets, the red color component value of the caption bitmap. 16 /// </summary> 17 byte Red { get; set; } 18 19 /// <summary> 20 /// Gets or Sets, the green color component value of the caption bitmap. 21 /// </summary> 22 byte Green { get; set; } 23 24 /// <summary> 25 /// Gets or Sets, the blue color component value of the caption bitmap. 26 /// </summary> 27 byte Blue { get; set; } 28 29 /// <summary> 30 /// Gets or Sets, the alpha color component value of the caption bitmap. 31 /// </summary> 32 byte Transparency { get; set; } 33 }
更多细节,请看看我的 EasyProgressBar Windows窗体应用程序 篇文章。 你可以操作你的标题栏外观如果它是可见的,如下面所示的例子:
操作色表——在不活跃的模式 | ||||||||
|
快捷键
如果启用了IsUserInteraction财产,提供用户键盘支持选项卡控件操作。
键 | 描述 |
结束 |
选择最后一个选项卡页面容器。 |
家 |
选择第一个选项卡页面容器。 |
左 |
选择选项卡左边当前选中的标签页的容器。 |
正确的 |
选择标签右侧的当前选中的标签页的容器。 |
插入 |
当用户按“插入”键,一个问题对话框,允许开发者插入一个新标签页。 如以下图片所示; |
删除 |
从容器中删除选中的标签页。 这个键被按下时,出现一个对话框,允许开发人员从容器中删除当前选中选项卡页面如下所示。 |
的KRBTabControlDesigner
控制设计师允许您管理设计时行为和设计时界面(属性和事件)暴露你的控制。 虽然控制设计相当复杂的Windows窗体的基础设施,不难定制现有的控制设计添加新特性。
你可以得到一个定制的控制设计师使用与您的自定义控件。 你为什么要创建自己的设计师?
- 添加设计时方便 上下文菜单 选项和 智能标记 。
- 删除不适当的事件或属性视图(或添加design-time-only 事件 , 属性 并创建 动态属性 )。
- 裁缝设计时的控制,不同于运行时出现(例如,添加 一个边境 在一个空面板)。
- 添加支持包含其他控件(如控制 工具栏 )或控制有特殊设计时需要(如 菜单 )。
在设计时,设计师基础设施高度设计师每个组件,因为它是坐落在一个形式。 (如果多个实例相同的组件被添加到一个表单, Visual Studio 将为所有实例重用相同的设计师。) 一旦建立连接,控制设计师有能力参与开发人员之间的交互和控制。
创建一个定制的设计师等容器的控制,通过派生类从开始 ParentControlDesigner
。 下面的代码片段显示了如何创建一个控制设计师为你控制。
public class KRBTabControlDesigner : System.Windows.Forms.Design.ParentControlDesigner { // To do something. }
然后,您可以将功能添加到您的设计师通过覆盖内置的控制方法。 当你完成后,你需要链接自定义控制设计师到适当的控制。 要做到这一点,你应用 设计师
属性来控制声明和指定适当的设计类型。 这是一个连接的例子 KRBTabControlDesigner
到 KRBTabControl
控制:
[Designer(typeof(KRBTabControlDesigner))] public partial class KRBTabControl : TabControl { ... }
设计师提供的六个方法 IDesignerFilter
界面,您可以覆盖过滤器 属性 , 事件 , 属性 。 下面的表中列出了这些方法。
方法 | 描述 |
PostFilterAttributes |
重写这个方法来删除未使用的或不适当的属性。 |
PostFilterEvents |
重写这个方法来删除未使用的或不适当的事件。 |
PostFilterProperties |
重写这个方法来删除未使用的或不适当的属性。 |
PreFilterAttributes |
重写这个方法添加属性。 |
PreFilterEvents |
重写这个方法添加事件。 |
PreFilterProperties |
重写这个方法添加属性。 |
从技术上讲,允许你修改的过滤方法 System.ComponentModel.TypeDescriptor
对象存储属性,属性,为您的自定义控件和事件信息。 Visual Studio使用信息TypeDescriptor
来确定设计时环境中可用。
这里有一个例子,删除不适当的属性中指定以下代码转储。
protected override void PostFilterProperties (System.Collections.IDictionary properties) { properties.Remove("Margin"); properties.Remove("ImeMode"); properties.Remove("Padding"); properties.Remove("Enabled"); properties.Remove("RightToLeft"); properties.Remove("RightToLeftLayout"); properties.Remove("ApplicationSettings"); properties.Remove("DataBindings"); base.PostFilterProperties(properties); }
重要提示 :作为一般规则,总是先调用基方法 PreFilterXxx()
最后的方法 PostFilterXxx()
方法。 这样,所有设计类应用他们的变化给出适当的机会。 的ControlDesigner
和 ComponentDesigner
使用这些方法来添加属性 可见
, 启用
, 的名字
, 锁着的
。
动态属性
创建自定义控件的动态属性。 我们需要覆盖 PreFilterProperties
方法。 此外,我们需要改变使用properties属性 TypeDescriptor.CreateProperty()
在相同的方法。 下面的代码片段显示了如何应用这种行为你的自定义控件。
1 /// <summary> 2 /// Override this method to add some properties 3 /// to the control or change the properties attributes for a dynamic user interface. 4 /// </summary> 5 /// <param name="properties">Properties collection of the control 6 /// before than add a new property to the collection by user.</param> 7 protected override void PreFilterProperties(System.Collections.IDictionary properties) 8 { 9 base.PreFilterProperties(properties); 10 11 // We don't want to show the "Location" and "ShowToolTips" properties for our control at the design-time. 12 properties["Location"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 13 (PropertyDescriptor)properties["Location"], BrowsableAttribute.No); 14 properties["ShowToolTips"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 15 (PropertyDescriptor)properties["ShowToolTips"], BrowsableAttribute.No); 16 17 /* After than, we don't want to see some properties at design-time for general reasons 18 (Dynamic property attributes). */ 19 KRBTabControl parentControl = Control as KRBTabControl; 20 21 if (parentControl != null) 22 { 23 if (parentControl.HeaderVisibility) 24 { 25 properties["ItemSize"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 26 (PropertyDescriptor)properties["ItemSize"], BrowsableAttribute.No); 27 properties["TabStyles"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 28 (PropertyDescriptor)properties["TabStyles"], BrowsableAttribute.No); 29 properties["Alignments"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 30 (PropertyDescriptor)properties["Alignments"], BrowsableAttribute.No); 31 properties["UpDownStyle"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 32 (PropertyDescriptor)properties["UpDownStyle"], BrowsableAttribute.No); 33 properties["HeaderStyle"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 34 (PropertyDescriptor)properties["HeaderStyle"], BrowsableAttribute.No); 35 properties["TabGradient"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 36 (PropertyDescriptor)properties["TabGradient"], BrowsableAttribute.No); 37 properties["IsDrawHeader"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 38 (PropertyDescriptor)properties["IsDrawHeader"], BrowsableAttribute.No); 39 properties["CaptionButtons"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 40 (PropertyDescriptor)properties["CaptionButtons"], BrowsableAttribute.No); 41 properties["TabBorderColor"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 42 (PropertyDescriptor)properties["TabBorderColor"], BrowsableAttribute.No); 43 properties["GradientCaption"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 44 (PropertyDescriptor)properties["GradientCaption"], BrowsableAttribute.No); 45 properties["BackgroundColor"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 46 (PropertyDescriptor)properties["BackgroundColor"], BrowsableAttribute.No); 47 properties["BackgroundImage"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 48 (PropertyDescriptor)properties["BackgroundImage"], BrowsableAttribute.No); 49 properties["BackgroundHatcher"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 50 (PropertyDescriptor)properties["BackgroundHatcher"], BrowsableAttribute.No); 51 properties["CaptionRandomizer"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 52 (PropertyDescriptor)properties["CaptionRandomizer"], BrowsableAttribute.No); 53 properties["IsDrawTabSeparator"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 54 (PropertyDescriptor)properties["IsDrawTabSeparator"], BrowsableAttribute.No); 55 56 return; 57 } 58 59 if (!parentControl.IsCaptionVisible) 60 { 61 properties["CaptionButtons"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 62 (PropertyDescriptor)properties["CaptionButtons"], BrowsableAttribute.No); 63 properties["CaptionRandomizer"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 64 (PropertyDescriptor)properties["CaptionRandomizer"], BrowsableAttribute.No); 65 properties["GradientCaption"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 66 (PropertyDescriptor)properties["GradientCaption"], BrowsableAttribute.No); 67 } 68 69 if (parentControl.IsDrawHeader) 70 { 71 switch (parentControl.HeaderStyle) 72 { 73 case KRBTabControl.TabHeaderStyle.Hatch: 74 properties["BackgroundColor"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 75 (PropertyDescriptor)properties["BackgroundColor"], BrowsableAttribute.No); 76 properties["BackgroundImage"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 77 (PropertyDescriptor)properties["BackgroundImage"], BrowsableAttribute.No); 78 break; 79 case KRBTabControl.TabHeaderStyle.Solid: 80 properties["BackgroundImage"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 81 (PropertyDescriptor)properties["BackgroundImage"], BrowsableAttribute.No); 82 properties["BackgroundHatcher"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 83 (PropertyDescriptor)properties["BackgroundHatcher"], BrowsableAttribute.No); 84 break; 85 default: 86 properties["BackgroundColor"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 87 (PropertyDescriptor)properties["BackgroundColor"], BrowsableAttribute.No); 88 properties["BackgroundHatcher"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 89 (PropertyDescriptor)properties["BackgroundHatcher"], BrowsableAttribute.No); 90 break; 91 } 92 } 93 else 94 { 95 properties["HeaderStyle"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 96 (PropertyDescriptor)properties["HeaderStyle"], BrowsableAttribute.No); 97 properties["BackgroundColor"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 98 (PropertyDescriptor)properties["BackgroundColor"], BrowsableAttribute.No); 99 properties["BackgroundImage"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 100 (PropertyDescriptor)properties["BackgroundImage"], BrowsableAttribute.No); 101 properties["BackgroundHatcher"] = TypeDescriptor.CreateProperty(typeof(KRBTabControl), 102 (PropertyDescriptor)properties["BackgroundHatcher"], BrowsableAttribute.No); 103 } 104 } 105 }
你仍然需要每个自定义属性链接到适当的属性使用 System.ComponentModel.RefreshProperties
在您的自定义控件类属性。 下面的代码片段显示了如何将这个属性添加到您合适的属性声明和指定适当的标识符类型。
[RefreshProperties(RefreshProperties.All)] public TabHeaderStyle HeaderStyle { get; set; }
例如,当 KRBTabControl.HeaderStyle
属性由用户改变了。 我们显示或隐藏 写成BackgroundColor <代码>
, 分辨率的
和 BackgroundHatcher
最终用户属性。
智能标记
最新版本的Visual Studio包含一个新特性来创建丰富的设计时experience-smart标签。 智能标记的弹出窗口出现在控制当你点击旁边的小箭头。
智能标记菜单,因为它们有一个相似的项目列表。 然而,这些项目 命令 (呈现超链接等),或其他控件 复选框 , 下拉列表 ,等等。 他们还可以包含静态的描述性文本。 通过这种方式,智能标记可以像一个迷你属性窗口。
下图显示了一个定制的智能标记的例子。 它允许开发人员设置的组合 KRBTabControl
属性。
要创建这个智能标记,需要以下材料:
-
DesignerActionItem对象的集合 :每个
DesignerActionItem
代表一个单项智能标记。 -
一个动作列表类 :这个类有两个roles-it配置的集合
DesignerActionItem
实例的智能标记,当一个命令或改变,它执行相应的操作与控制。 - 一个控制设计 :此钩你的动作列表控制的智能标记出现在设计时。
动作列表
创建智能标记是在概念上类似于添加 设计器谓词
你覆盖在你的控制设计方法,并返回一个命令,您想要支持的集合。 (这被称为的命令列表 动作列表
)。
然而,智能标记允许更多的选择比设计器谓词,所以相关的代码可能是更复杂的。 控制这一切,这是一个好主意单独的代码创建一个自定义类,封装了你的动作列表。 这个自定义类应该来自 DesignerActionList
(在 System.ComponentModel.Design
名称空间)。
这里有一个例子,它创建了一个动作列表,供使用 KRBTabControl
:
public class KRBTabControlActionList : DesignerActionList { // To do something. }
你应该向动作列表添加一个构造函数,需要匹配的控制类型。 然后您可以参考控制存储在一个成员变量。 这不是必需的,因为基础 ActionList
类提供了一个 组件
属性提供了访问控制。 然而,通过使用这种方法,您将获得便利的强类型访问控制。
#region Constructor
// The constructor associates the control to the smart tag action list. public KRBTabControlActionList(KRBTabControl control) : base(control) { _linkedControl = control; _host = (IDesignerHost)GetService(typeof(IDesignerHost)); _changeService = (IComponentChangeService)GetService(typeof(IComponentChangeService)); _designerService = (DesignerActionUIService)GetService(typeof(DesignerActionUIService)); // When this control will be added to the design area, the smart tag panel will open automatically. this.AutoShow = true; } #endregion
创建智能标记,你需要建立一个 DesignerActionItemCollection
结合你的群 DesignerActionItem
对象。 顺序很重要 在这个集合,因为Visual Studio将增加DesignerActionItem
对象的智能标记 从上到下 它们出现的顺序。
建立你的动作列表,你覆盖 DesignerActionList.GetSortedActionItems()
方法,创建 DesignerActionItemCollection
,添加每个 DesignerActionItem
,然后返回集合。 根据你的智能标记的复杂性,这可能需要几个步骤。
第一步是创建一个标题,将智能标记划分为单独的区域。 然后您可以将其他物品添加到这些类别,如下所示:
1 public override DesignerActionItemCollection GetSortedActionItems() 2 { 3 DesignerActionItemCollection items = new DesignerActionItemCollection(); 4 try 5 { 6 // Creating the action list static headers. 7 items.Add(new DesignerActionHeaderItem("Commands")); 8 items.Add(new DesignerActionHeaderItem("Appearance")); 9 10 if (!_linkedControl.HeaderVisibility) 11 { 12 // Creates other action list headers. 13 items.Add(new DesignerActionHeaderItem("Tab Item Appearance")); 14 15 items.Add(new DesignerActionPropertyItem("TabStyles", "Tab Styles", "Appearance", 16 "Tab Style")); 17 18 items.Add(new DesignerActionPropertyItem("Alignments", "Tab Alignments", "Appearance", 19 "Tab Alignment")); 20 21 items.Add(new DesignerActionPropertyItem("FirstColor", "First Color", "Tab Item Appearance", 22 "First TabItem Color")); 23 24 items.Add(new DesignerActionPropertyItem("SecondColor", "Second Color", "Tab Item Appearance", 25 "Second TabItem Color")); 26 27 items.Add(new DesignerActionPropertyItem("GradientMode", "Gradient Mode", "Tab Item Appearance", 28 "Gradient Style")); 29 30 items.Add(new DesignerActionPropertyItem("IsSupportedAlphaColor", "Support Alpha Color", 31 "Tab Item Appearance", "Supports alpha component for tab item background colors")); 32 33 items.Add(new DesignerActionMethodItem(this, 34 "RandomizeColors", "Randomize Colors", "Tab Item Appearance", 35 "Randomize TabItem Colors", false)); 36 } 37 38 items.Add(new DesignerActionMethodItem(this, 39 "HeaderVisibility", "StretchToParent " + (_linkedControl.HeaderVisibility ? "ON" : "OFF"), 40 "Appearance", "Determines whether the active tab is stretched to its parent container or not", 41 false)); 42 43 items.Add(new DesignerActionMethodItem(this, 44 "AddTab", "Add Tab", "Commands", 45 "Add a new tab page to the container", false)); 46 47 if (_linkedControl.TabCount > 0) 48 { 49 DesignerActionMethodItem methodRemove = new DesignerActionMethodItem(this, "RemoveTab", 50 "Remove Tab", "Commands", "Removes the selected tab page from the container", false); 51 52 items.Add(methodRemove); 53 } 54 55 // Add a new static header and its items. 56 items.Add(new DesignerActionHeaderItem("Information")); 57 items.Add(new DesignerActionTextItem("X: " + _linkedControl.Location.X + ", " + "Y: " 58 + _linkedControl.Location.Y, "Information")); 59 items.Add(new DesignerActionTextItem("Width: " + _linkedControl.Size.Width + ", " + "Height: " 60 + _linkedControl.Size.Height, "Information")); 61 } 62 catch (Exception ex) 63 { 64 MessageBox.Show("Exception while generating the action list panel for this KRBTabControl, " + 65 ex.Message); 66 } 67 68 return items; 69 }
你还需要连接到你的控制。 把这个动作列表添加到你的控制,你需要覆盖 ActionLists
房地产在您的自定义设计器中,创建一个新的DesignerActionListCollection
,并添加适当的 DesignerActionList
对象条目。 你控制设计师处理行动项目事件,一般通过更新相关的控制。 注意,每次动作列表不是创建的 ActionList
被称为相反,缓存它吗 私有成员变量来优化性能。
public override DesignerActionListCollection ActionLists { get { if (_actionLists == null) { _actionLists = new DesignerActionListCollection(); _actionLists.Add(new KRBTabControlActionList((KRBTabControl)Control)); } return _actionLists; } }
拖放支持
如果启用AllowDrop属性,您可以拖动当前选中选项卡页面在其他选项卡页面。 而且你可以拖拽选项卡页面标签容器从一个到另一个地方。
选项卡标题样式
标题样式 | 预览 |
固体 |
|
孵化 |
|
纹理 |
下拉菜单定制
有两种方法来定制你的下拉菜单。 你应该在大多数情况下使用的方法来处理 ContextMenuShown
事件。 它的一个实例 ContextMenuShownEventArgs
为菜单定制方法。
#region Customize ContextMenuStrip private void krbTabControl1_ContextMenuShown(object sender, ContextMenuShownEventArgs e) { ToolStripMenuItem menuItem = new ToolStripMenuItem() { Text = "Another menu item", ShortcutKeys = Keys.Control | Keys.N }; menuItem.Click += (thrower, ea) => { MessageBox.Show("Hello World!!!"); }; e.ContextMenu.Items.Insert(0, new ToolStripSeparator()); e.ContextMenu.Items.Insert(0, menuItem); menuItem = new ToolStripMenuItem() { Text = "Open a new tab", Image = this.ımageList1.Images[2], ShortcutKeys = Keys.Control | Keys.O }; menuItem.Click += (thrower, ea) => { KRBTabControl.TabPageEx newTabPage = new KRBTabControl.TabPageEx(); newTabPage.ImageIndex = 2; krbTabControl1.TabPages.Add(newTabPage); Label newLabel = new Label(); newLabel.AutoSize = true; newLabel.Text = String.Format("I've created by the drop-down menu, {0}", newTabPage.Text); newLabel.Font = new Font("Tahoma", 9.75f, FontStyle.Bold); newLabel.Location = new Point(10, 10); newTabPage.Controls.Add(newLabel); }; e.ContextMenu.Items.Insert(0, new ToolStripSeparator()); e.ContextMenu.Items.Insert(0, menuItem); e.ContextMenu.Items.Add(new ToolStripSeparator()); menuItem = new ToolStripMenuItem("My Menu Item"); if (krbTabControl1.SelectedTab != null) { menuItem.Click += (thrower, ea) => { MessageBox.Show(String.Format("Selected Tab Page: {0}", krbTabControl1.SelectedTab)); }; } e.ContextMenu.Items.Add(menuItem); } #endregion
另一个选择是,当创建一个继承控制不同的视觉外观,您必须提供自定义下拉菜单的代码覆盖 OnContextMenuShown
方法。 你也可以隐藏或显示选项卡页面相同的菜单。 你会看到可用的选项卡页在“ 可用的选项卡页面 ”项,如下所示:
下拉菜单 | 预览 |
自定义菜单 |
防止标签页选择或标签页关闭用户的行动
行动 | 代码转储 |
TabPage选择 |
private void krbTabControl1_SelectedIndexChanging(object sender, SelectedIndexChangingEventArgs e) { if (e.TabPage.Text == "IsSelectable?") e.Cancel = true; } |
TabPage关闭 |
private void krbTabControl1_TabPageClosing(object sender, SelectedIndexChangingEventArgs e) { if (e.TabPage.Text == "Schedules") { if (MessageBox.Show("Do you want to remove the Schedules tab page?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) { e.Cancel = true; } } } |
引用
欲知详情,请看以下书籍:
- Pro .NET 2.0 Windows Forms and Custom Controls in C#, by Matthew MacDonald
- Pro .NET 2.0 Graphics Programming, by Eric White
历史
- August 01, 2011 - Updated
- Fixed drag and drop operation.
- Fixed texture drawing for bottom alignment.
- Added dynamic properties support.
- Added mnemonic support.
- Added show/hide capability to the tab pages by the drop down menu.
- Added transparency support to the control caption bar and tab pages background.
- Added keyboard user interaction.
- Added serialize and deserialize support for data loading and saving(Xml and Binary serialization, you can choose one from the
DataSaverAttribute
class). - July 11, 2009 - First release
许可
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)