Figure 1-1. NeoTabWindow Control
Introduction
The NeoTabControlLibrary is a custom .NET tab control for the Windows Forms Applications. It's coming with add-in renderer support. So you can change
your control's appearance dynamically. I'll provide step-by-step exercises along with a thorough explanation of how you can create your control renderer class.
So you can easily create your own control appearance in a few steps (this subject will be described later).
At the same time, you can use designs that you've created without recompiling the control library.
- Mnemonic support.
- Supports keyboard navigation.
- Drag and drop support with a few drag effect style.
- Supports the 'close' and 'drop-down' smart buttons.
- Supports the 'drop-down' menu customizations.
- Design time support.
- Allows you to create your own tooltip customization.
- Allows you to show or hide an existing tab page items.
- Add-in renderer support.
- Allows a developer to receive or handle some important messages for the application life cycle. Ex: tab page selections, removing a tab page item from
the control container or handling some mouse messages.
- Supports transparent background color.
- Allows you to add your tab page items to any of the four sides of the control.
- Deep clone support.
Backgrounds of the NeoTabWindow and NeoTabPage controls
The NeoTabWindow
control derives directly from the Control
class, but the NeoTabPage
control inherits
from the Panel
class. At design time, we can do the following to ensure that they behave as we want. At this point, a few attributes
can be applied to our class declaration, rather than a specific property. Figure 1-2 shows the NeoTabWindow
and NeoTabPage
class diagrams.
[Designer(typeof(NeoTabControlLibrary.Design.NeoTabWindowDesigner))]
[DefaultEvent("SelectedIndexChanged"), DefaultProperty("TabPages")]
[ToolboxItem(typeof(NeoTabControlLibrary.Design.NeoTabWindowToolboxItem))]
public class NeoTabWindow : Control, ISupportInitialize, ICloneable
{
}
Figure 1-2. NeoTabWindow and NeoTabPage class diagrams
And the other control: NeoTabPage
control class declaration.
[ToolboxItem(false)]
[DesignTimeVisible(false)]
[Designer(typeof(NeoTabControlLibrary.Design.NeoTabPageDesigner))]
[DefaultEvent("Click"), DefaultProperty("Text")]
public class NeoTabPage : Panel, IFormattable, IComparable<NeoTabPage>
{
}
Tab Page Alignments
You can add your tab page items to any of the four sides of the control. To do this, you must override NeoTabPageItemsSide
property and specify
its side in your renderer class. You can specify one of these values (Top, Left, Bottom, Right). For example;
public override TabPageLayout NeoTabPageItemsSide
{
get { return TabPageLayout.Left; }
}
You can find a few sample screenshots below for it.
Figure 2. left-aligned NeoTabWindow control, example from the CCleaner renderer assembly.
And the other example shown in Figure 3.
Figure 3. right-aligned NeoTabWindow control, example from the SizDotNET renderer assembly.
In the article files, you can find more examples for this.
To start working with it.
You have created a new project and want to work with this control library. When you're in the design mode and if you drop
a new NeoTabWindow
control to your form designer using the Toolbox window, you will see the model dialog box shown
in Figure 4 where you can specify the executable directory of your application. After selecting your application directory,
click the OK button in this window. If you don't want to work with a custom renderer, you can skip this step by clicking the Cancel button.
Figure 4. Selecting the application directory
After clicking the OK button, you will see the Add-in Manager form shown in Figure 5 where you can use the renderer assemblies
that you've created previously. After selecting a renderer assembly in the table you want to work with, click the Apply button. As mentioned above,
you can skip this step by clicking the Cancel button if you don't want to work with a custom renderer class.
Figure 5. Exploring the Add-in Manager
You can also access this add-in manager form by using the RendererName
property in the properties window. This property is associated with
a modal type editor (NeoTabControlLibrary.Design.RendererNameEditor
). A modal type editor shows an ellipsis (...) button next to the property value.
When this button is clicked, a modal dialog box appears that allows the developer to change the property value.
Figure 6 shows the use cases for the NeoTabWindow
control case study.
Figure 6. Use cases for NeoTabWindow control
NeoTabWindow Properties
Table 1. Custom properties from the NeoTabWindow control
Property | Description |
| Gets the currently selected tab page. |
| Gets or sets the index of the currently selected tab page. |
| Determines whether the tooltip effect is enable or not for this tab control. |
| Gets or sets the drag and drop style for this control. |
| Gets or sets the type name of the renderer class for starting control with a custom renderer. |
| Gets or sets the renderer class of the NeoTabWindow control. |
| Gets or sets the tooltip renderer of the control. |
NeoTabWindow Events
Table 2. Events from the NeoTabWindow control
Event | Description |
| Event raised when the current renderer is updated/edited. |
| Occurs when the value of the Renderer property changes. |
| Occurs when the value of the SelectedIndex property changes. |
| Occurs when a NeoTabPage control is being selected. |
| Event raised when a NeoTabPage control is removed from this control. |
| Occurs when a NeoTabPage control is being removed. |
| Event raised when the smart drop-down button is clicked on the control. |
NeoTabWindow Methods
Table 3. General Methods from the NeoTabWindow control
Method | Description |
| Removes the specified NeoTabPage control from the control collection. |
| Removes a NeoTabPage control from the control collection at the specified indexed location if it supports removing. |
| Gets the NeoTabPage control at the specified point if it exists in the collection. |
| Allows you to show or hide an existing tab page items. |
| Shows Add-in Manager Form. |
| Shows the editor form of the current renderer, if it supports. |
| The result value is: 0 ( SmartCloseButton ), 1 ( SmartDropDownButton ), -1 ( Not Found ). |
NeoTabPage Properties
Table 4. Properties from the NeoTabPage control
Property | Description |
| Determines whether this NeoTabPage control is closeable or not. |
| Determines whether this NeoTabPage control is selectable or not. |
| Gets or sets to be displayed text for this page. |
| The text that is shown when the mouse hovers over this tab. |
NeoTabPage Events
Table 5. Events from the NeoTabPage control
Event | Description |
| Event raised when the value of the Text property is changed on control. |
RendererBase Abstract Class
The RendererBase
class allows the developer to create a custom renderer assembly by using this class abstract members. Figure 7 shows
the CommonObjects
diagrams.
Figure 7. CommonObjects diagrams
Here’s the class declaration:
public abstract class RendererBase : IDisposable
{
#region Event
public event EventHandler RendererUpdated;
#endregion
#region Destructor
~RendererBase()
{
GC.SuppressFinalize(this);
}
#endregion
#region Protected Methods
protected void OnRendererUpdated()
{
if (RendererUpdated != null)
RendererUpdated(this, EventArgs.Empty);
}
#endregion
#region Virtual Methods
public virtual void OnDrawSmartCloseButton(Graphics gfx, Rectangle closeButtonRct, ButtonState btnState)
{
if (!IsSupportSmartCloseButton)
return;
Pen pen = null;
Pen borderPen = null;
Brush brush = null;
switch (btnState)
{
case ButtonState.Normal:
pen = new Pen(Color.Black);
break;
case ButtonState.Hover:
pen = new Pen(Color.Black);
borderPen = new Pen(Color.FromArgb(49, 106, 197));
brush = new SolidBrush(Color.FromArgb(195, 211, 237));
break;
case ButtonState.Pressed:
pen = new Pen(Color.White);
borderPen = new Pen(Color.LightGray);
brush = new SolidBrush(Color.FromArgb(41, 57, 85));
break;
case ButtonState.Disabled:
pen = new Pen(SystemColors.GrayText);
break;
}
if (brush != null)
{
gfx.FillRectangle(brush, closeButtonRct);
brush.Dispose();
if (borderPen != null)
{
gfx.DrawRectangle(borderPen, closeButtonRct.Left, closeButtonRct.Top,
closeButtonRct.Width - 1, closeButtonRct.Height - 1);
borderPen.Dispose();
}
}
if (pen != null)
{
gfx.DrawLine(pen, closeButtonRct.Left + 3, closeButtonRct.Top + 4,
closeButtonRct.Right - 5, closeButtonRct.Bottom - 3);
gfx.DrawLine(pen, closeButtonRct.Left + 4, closeButtonRct.Top + 4,
closeButtonRct.Right - 4, closeButtonRct.Bottom - 3);
gfx.DrawLine(pen, closeButtonRct.Right - 4, closeButtonRct.Top + 4,
closeButtonRct.Left + 4, closeButtonRct.Bottom - 3);
gfx.DrawLine(pen, closeButtonRct.Right - 5, closeButtonRct.Top + 4,
closeButtonRct.Left + 3, closeButtonRct.Bottom - 3);
pen.Dispose();
}
}
public virtual void OnDrawSmartDropDownButton(Graphics gfx, Rectangle dropdownButtonRct, ButtonState btnState)
{
if (!IsSupportSmartDropDownButton)
return;
Pen pen = null;
Pen borderPen = null;
Brush brush = null;
switch (btnState)
{
case ButtonState.Normal:
pen = new Pen(Color.Black);
break;
case ButtonState.Hover:
pen = new Pen(Color.Black);
borderPen = new Pen(Color.FromArgb(49, 106, 197));
brush = new SolidBrush(Color.FromArgb(195, 211, 237));
break;
case ButtonState.Pressed:
pen = new Pen(Color.White);
borderPen = new Pen(Color.LightGray);
brush = new SolidBrush(Color.FromArgb(41, 57, 85));
break;
case ButtonState.Disabled:
pen = new Pen(SystemColors.GrayText);
break;
}
if (brush != null)
{
gfx.FillRectangle(brush, dropdownButtonRct);
brush.Dispose();
if (borderPen != null)
{
gfx.DrawRectangle(borderPen, dropdownButtonRct.Left, dropdownButtonRct.Top,
dropdownButtonRct.Width - 1, dropdownButtonRct.Height - 1);
borderPen.Dispose();
}
}
if (pen != null)
{
using (Brush fill = new SolidBrush(pen.Color))
{
gfx.FillPolygon(fill, new Point[]
{
new Point(dropdownButtonRct.Left + 3, dropdownButtonRct.Top + 6),
new Point(dropdownButtonRct.Right - 3, dropdownButtonRct.Top + 6),
new Point(dropdownButtonRct.Left + dropdownButtonRct.Width / 2, dropdownButtonRct.Bottom - 3)});
}
pen.Dispose();
}
}
#endregion
#region Abstract Methods
public abstract void InvokeEditor();
public abstract void OnRendererBackground(Graphics gfx, Rectangle clientRct);
public abstract void OnRendererTabPageArea(Graphics gfx, Rectangle tabPageAreaRct);
public abstract void OnRendererTabPageItem(Graphics gfx, Rectangle tabPageItemRct,
string tabPageText, int index, ButtonState btnState);
#endregion
#region Virtual Properties
public virtual int SmartButtonsBetweenSpacing { get { return 2; } }
public virtual Size SmartButtonsSize { get { return new Size(14, 13); } }
public virtual bool IsSupportSmartCloseButton { get { return false; } }
public virtual bool IsSupportSmartDropDownButton { get { return false; } }
public virtual DrawingOffset SmartButtonsAreaOffset
{
get { return new DrawingOffset(0, 0, 3, 6); }
}
#endregion
#region Abstract Properties
public abstract int ItemObjectsDrawingMargin { get; }
public abstract int TabPageItemsBetweenSpacing { get; }
public abstract Color BackColor { get; }
public abstract Color TabPageItemForeColor { get; }
public abstract Color SelectedTabPageItemForeColor { get; }
public abstract Color DisabledTabPageItemForeColor { get; }
public abstract Color MouseOverTabPageItemForeColor { get; }
public abstract Font NeoTabPageItemsFont { get; }
public abstract DrawingOffset TabPageAreaCornerOffset { get; }
public abstract DrawingOffset TabPageItemsAreaOffset { get; }
public abstract TabPageLayout NeoTabPageItemsSide { get; }
public abstract TabPageItemStyle NeoTabPageItemsStyle { get; }
#endregion
#region IDisposable Members
public abstract void Dispose();
#endregion
}
How can I create my own custom renderer class
In the following exercise, you will create the first renderer assembly for your control. When creating subsequent control renderers for the NeoTabWindow
control,
use this exercise as the model.
As mentioned, you can use this exercise to create your first renderer assembly and then use it as the base steps to create other assemblies. Without further ado,
create the first renderer assembly by following these steps:
- The first order of business is to open a new instance of Visual Studio. Once Visual Studio is open,
you can see the Start Page screen and any recent projects you have created, as shown in Figure 8.
Figure 8. Opening Visual Studio
- Select File -> New -> Project, as shown in Figure 9.
Figure 9. Creating a new project
- After selecting 'Project', you will see the New Project dialog box.
- Navigate to the Visual C# list within the Project Types tree, and choose Windows. Select Class Library from the Templates
section located on the right side of the dialog box, as shown in Figure 10.
Figure 10. New Project dialog box
- Now you need to enter two pieces of information. The first piece of information is the name of the solution; enter NeoTabControlLibrary.Renderer.VS2008.
For the second piece of information, browse to the location on your hard drive where you want to set up your solution file and the subsequent source code.
Finally, notice that the option 'Create directory for solution' is checked. This is the desired option. The New Project dialog box will now look like Figure 11.
Figure 11. Finalized New Project dialog box
- Click the OK button to execute and save the new solution file.
- You can now view the new solution within Solution Explorer in Visual Studio, as shown in Figure 12. Notice that the new class library automatically
created a new class named Class1. You can keep this class for the time being; however, the following exercises, you will either delete or rename this class.
Figure 12. The new control renderer solution
You have finished setting up and organizing the Visual Studio solution for your control renderer application; however, it doesn't
contain any drawing implementations at this time. You're now prepared to establish the overall drawing architecture and how this architecture
will be implemented in the Visual Studio solution.
- Since the solution is now set up, you can add the next vital piece to the overall puzzle; in the following exercise,
you'll implement the drawing methods and properties of the
RendererBase
class. - Return to the Visual Studio solution you created in the previous exercise. Continue to Solution Explorer, and right-click
on the Class1.cs name that automatically created by Visual Studio in the previous exercise. Then select Rename, as shown in Figure 13.
Figure 13. Renaming the class name
Change the name of this class to VS2008LikeRenderer.cs, as shown in Figure 14.
Figure 14. The VS2008LikeRenderer class
- You are able to view the class that was renamed; however, you still need to do some additional work within the class file. At the moment,
the class simply shows the name of class. Change the code within the class to resemble the following:
using System;
namespace NeoTabControlLibrary.Renderer.VS2008
{
public sealed class VS2008LikeRenderer
{
#region Static Constructor
static VS2008LikeRenderer()
{
}
#endregion
}
}
You have changed the class to that of a public sealed class, and you have added the subsequent static constructor. When creating subsequent
your control renderer assemblies, use this exercise as the model. Now you're ready to move along with the implementation of the properties and drawing
methods contained in the VS2008LikeRenderer
class.
- Before you add the abstract methods and properties of the
RendererBase
class, you need to add a reference to the class library project.
The reference you need is the NeoTabControlLibrary.CommonObjects.RendererBase
class from the NeoTabControlLibrary.CommonObjects library.
To add this reference, right-click the References folder within the project, and select the Add Reference menu item, as shown in Figure 15.
Figure 15. Adding a reference
After selecting the Add Reference option, within a few seconds you will see the Add Reference dialog box. Firstly, you select the System.Drawing reference
located on the .NET tab, as shown in Figure 16, and then click the OK button.
Figure 16. Add Reference dialog box
This adds the reference to System.Drawing to your class library project. You can now complete this step by adding the NeoTabControlLibrary.CommonObjects
reference.
Right-click again on the References folder within the same project and then select Add Reference menu item. You will see the Add Reference dialog box again.
Navigate to the Browse tab within this screen, and choose the location of the NeoTabControlLibrary.CommonObjects.dll library in this tab, as shown
in Figure 17, and then click the OK button.
Figure 17. Adding an existing item reference
After clicking the OK button, you will see the reference assemblies under the References folder that you have added. After this point,
you have to declare the new namespaces being used at the top of the code. And also, import the System.Drawing.Drawing2D
namespace at this section:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using NeoTabControlLibrary.CommonObjects;
namespace NeoTabControlLibrary.Renderer.VS2008
{
}
- You can now implement the abstract methods and properties of the
RendererBase
class. For the VS2008LikeRenderer
class,
have it inherit from the RendererBase
class, as shown here:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using NeoTabControlLibrary.CommonObjects;
namespace NeoTabControlLibrary.Renderer.VS2008
{
public sealed class VS2008LikeRenderer : RendererBase
{
#region Static Constructor
static VS2008LikeRenderer()
{
}
#endregion
}
}
- You have now indicated that the
VS2008LikeRenderer
class will implement the RendererBase
class, but now you have to add the actual implementation.
After you finish typing the name of the base class, the Visual Studio integrated development environment (IDE) will give you a shortcut where you can complete the implementation.
Figure 18 shows the indicator just below the letter 'R' in the word RendererBase.
Figure 18. The abstract class indicator
- Move your cursor over the indicator, and click the down arrow to display the options shown in Figure 19.
Figure 19. Choosing the implementation
- Click on the 'Implement abstract class RendererBase' item, and then override the drawing methods and properties within this class,
the following code is for the complete
VS2008LikeRenderer
class:
public sealed class VS2008LikeRenderer : RendererBase
{
#region Symbolic Constants
private static readonly Font MY_FONT;
private static readonly DrawingOffset[] OFFSETS;
private static readonly Color[] COLORS;
private static readonly int[] INTEGERARRAY;
#endregion
#region Static Constructor
static VS2008LikeRenderer()
{
MY_FONT = new Font("Tahoma", 8.25f, FontStyle.Bold);
OFFSETS = new DrawingOffset[] {
new DrawingOffset(5, 5, 5, 5),
new DrawingOffset(0, 5, 0, 0) };
COLORS = new Color[]{
SystemColors.Control,
Color.Black,
Color.Black,
SystemColors.GrayText,
Color.Black
};
INTEGERARRAY = new int[] { 3, 0 };
}
#endregion
#region Helper Methods
private GraphicsPath CreateRoundRect(Rectangle rect, int radius)
{
GraphicsPath gp = new GraphicsPath();
int x = rect.X;
int y = rect.Y;
int width = rect.Width;
int height = rect.Height;
if (radius > 0)
{
radius = Math.Min(radius, height / 2 - 1);
radius = Math.Min(radius, width / 2 - 1);
gp.AddLine(x + radius, y, x + width - (radius * 2), y);
gp.AddArc(x + width - (radius * 2), y, radius * 2, radius * 2, 270, 90);
gp.AddLine(x + width, y + radius, x + width, y + height - (radius * 2));
gp.AddArc(x + width - (radius * 2), y + height - (radius * 2), radius * 2, radius * 2, 0, 90);
gp.AddLine(x + width - (radius * 2), y + height, x + radius, y + height);
gp.AddArc(x, y + height - (radius * 2), radius * 2, radius * 2, 90, 90);
gp.AddLine(x, y + height - (radius * 2), x, y + radius);
gp.AddArc(x, y, radius * 2, radius * 2, 180, 90);
}
else
{
gp.AddRectangle(rect);
}
gp.CloseFigure();
return gp;
}
#endregion
public override void InvokeEditor()
{
throw new NotImplementedException();
}
public override void OnRendererBackground(Graphics gfx, Rectangle clientRct)
{
}
public override void OnRendererTabPageArea(Graphics gfx, Rectangle tabPageAreaRct)
{
Rectangle rct = tabPageAreaRct;
SmoothingMode mode = gfx.SmoothingMode;
gfx.SmoothingMode = SmoothingMode.AntiAlias;
rct.Inflate(-2, -2);
rct.Width -= 1;
rct.Height -= 1;
using (Brush brush = new SolidBrush(Color.FromArgb(194, 207, 229)))
{
using (Pen pen = new Pen(brush, 4))
gfx.DrawRectangle(pen, rct);
}
rct = tabPageAreaRct;
rct.Inflate(-4, -4);
rct.Width -= 1;
rct.Height -= 1;
using (Pen pen = new Pen(Color.FromArgb(161, 180, 214)))
gfx.DrawRectangle(pen, rct);
rct = tabPageAreaRct;
rct.Width -= 1;
rct.Height -= 1;
using (GraphicsPath path = CreateRoundRect(rct, 4))
{
using (LinearGradientBrush brush =
new LinearGradientBrush(Point.Empty, new Point(0, 1),
Color.FromArgb(157, 177, 212),
Color.FromArgb(153, 175, 212)))
{
Blend bl = new Blend(2);
bl.Factors = new float[] { 0.3F, 1.0F };
bl.Positions = new float[] { 0.0F, 1.0F };
brush.Blend = bl;
using (Pen pen = new Pen(brush))
gfx.DrawPath(pen, path);
}
}
rct.Inflate(-1, -1);
using (GraphicsPath path = CreateRoundRect(rct, 4))
{
using (Pen pen = new Pen(Color.FromArgb(225, 230, 232)))
gfx.DrawPath(pen, path);
}
gfx.SmoothingMode = mode;
}
public override void OnRendererTabPageItem(Graphics gfx, Rectangle tabPageItemRct, string tabPageText,
int index, CommonObjects.ButtonState btnState)
{
Rectangle itemRct = tabPageItemRct;
itemRct.Y += 2;
itemRct.Height -= 2;
SmoothingMode mode = gfx.SmoothingMode;
gfx.SmoothingMode = SmoothingMode.AntiAlias;
using (StringFormat format = new StringFormat(StringFormatFlags.LineLimit))
{
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
format.HotkeyPrefix = System.Drawing.Text.HotkeyPrefix.Show;
using (GraphicsPath path = new GraphicsPath())
{
int xOffset;
bool isSelected = false;
Color textColor = DisabledTabPageItemForeColor;
switch (btnState)
{
case CommonObjects.ButtonState.Normal:
textColor = TabPageItemForeColor;
goto case CommonObjects.ButtonState.Disabled;
case CommonObjects.ButtonState.Pressed:
isSelected = true;
itemRct.Y -= 2;
itemRct.Height += 2;
textColor = SelectedTabPageItemForeColor;
using (Brush brush = new SolidBrush(BackColor))
gfx.FillRectangle(brush, itemRct);
goto case CommonObjects.ButtonState.Disabled;
case CommonObjects.ButtonState.Disabled:
if (index == 0)
{
xOffset = itemRct.Left + 10 + (itemRct.Height / 2);
Point[] points = new Point[]
{
new Point(itemRct.Left, itemRct.Bottom),
new Point(itemRct.Left, itemRct.Bottom - 3),
new Point(itemRct.Left + 8, itemRct.Bottom - 17),
new Point(xOffset, itemRct.Top),
};
path.AddBeziers(points);
}
else if (isSelected)
{
xOffset = itemRct.Left + (itemRct.Height / 2);
Point[] points = new Point[]
{
new Point(itemRct.Left - 10, itemRct.Bottom),
new Point(itemRct.Left - 10, itemRct.Bottom - 3),
new Point(itemRct.Left - 2, itemRct.Bottom - 17),
new Point(xOffset, itemRct.Top),
};
path.AddBeziers(points);
}
else
{
xOffset = itemRct.Left + (itemRct.Height / 2);
path.AddLine(itemRct.Left, itemRct.Bottom, itemRct.Left,
itemRct.Top + (itemRct.Height / 2) - 3);
Point[] points = new Point[]
{
new Point(itemRct.Left, itemRct.Top + (itemRct.Height / 2) - 4),
new Point(itemRct.Left, itemRct.Top + (itemRct.Height / 2) - 5),
new Point(itemRct.Left + 2, itemRct.Top + 2),
new Point(xOffset, itemRct.Top),
};
path.AddBeziers(points);
}
path.AddLine(xOffset + 1, itemRct.Top, itemRct.Right - 4, itemRct.Top);
path.AddLine(itemRct.Right - 1, itemRct.Top + 2, itemRct.Right - 1, itemRct.Bottom);
path.CloseFigure();
using (LinearGradientBrush brush = new LinearGradientBrush(itemRct, Color.White,
isSelected ? Color.FromArgb(194, 207, 229) : Color.FromArgb(238, 236, 221),
LinearGradientMode.Vertical))
{
Blend bl = new Blend(2);
bl.Factors = new float[] { 0.4F, 1.0F };
bl.Positions = new float[] { 0.0F, 1.0F };
brush.Blend = bl;
gfx.FillPath(brush, path);
}
using (Pen pen = new Pen(isSelected ? Color.FromArgb(153, 175, 212) : Color.FromArgb(172, 168, 153)))
{
gfx.DrawPath(pen, path);
if (isSelected)
{
pen.Color = Color.FromArgb(194, 207, 229);
gfx.DrawLine(pen, index == 0 ? itemRct.Left + 1 : itemRct.Left - 9, itemRct.Bottom,
itemRct.Right - 2, itemRct.Bottom);
gfx.DrawLine(pen, index == 0 ? itemRct.Left + 1 : itemRct.Left - 9, itemRct.Bottom + 1,
itemRct.Right - 2, itemRct.Bottom + 1);
}
else
{
pen.Color = Color.FromArgb(156, 176, 212);
gfx.DrawLine(pen, itemRct.Left, itemRct.Bottom, itemRct.Right - 1, itemRct.Bottom);
}
}
using (Font font = new Font(NeoTabPageItemsFont, isSelected ? FontStyle.Bold : FontStyle.Regular))
{
itemRct.X += 2;
itemRct.Width -= 2;
itemRct.Y += 2;
itemRct.Height -= 2;
if (index == 0)
{
itemRct.X += 6;
itemRct.Width -= 6;
}
using (Brush brush = new SolidBrush(textColor))
gfx.DrawString(tabPageText, font, brush, itemRct, format);
}
break;
case CommonObjects.ButtonState.Hover:
if (index == 0)
{
xOffset = itemRct.Left + 10 + (itemRct.Height / 2);
Point[] points = new Point[]
{
new Point(itemRct.Left, itemRct.Bottom),
new Point(itemRct.Left, itemRct.Bottom - 3),
new Point(itemRct.Left + 8, itemRct.Bottom - 17),
new Point(xOffset, itemRct.Top),
};
path.AddBeziers(points);
}
else
{
xOffset = itemRct.Left + (itemRct.Height / 2);
path.AddLine(itemRct.Left, itemRct.Bottom, itemRct.Left,
itemRct.Top + (itemRct.Height / 2) - 3);
Point[] points = new Point[]
{
new Point(itemRct.Left, itemRct.Top + (itemRct.Height / 2) - 4),
new Point(itemRct.Left, itemRct.Top + (itemRct.Height / 2) - 5),
new Point(itemRct.Left + 2, itemRct.Top + 2),
new Point(xOffset, itemRct.Top),
};
path.AddBeziers(points);
}
path.AddLine(xOffset + 1, itemRct.Top, itemRct.Right - 4, itemRct.Top);
path.AddLine(itemRct.Right - 1, itemRct.Top + 2, itemRct.Right - 1, itemRct.Bottom);
path.CloseFigure();
using (LinearGradientBrush brush = new LinearGradientBrush(itemRct, Color.FromArgb(220,226,231),
Color.FromArgb(162, 187, 226), LinearGradientMode.Vertical))
{
Blend bl = new Blend(2);
bl.Factors = new float[] { 0.3F, 1.0F };
bl.Positions = new float[] { 0.0F, 1.0F };
brush.Blend = bl;
gfx.FillPath(brush, path);
}
using (Pen pen = new Pen(Color.FromArgb(153, 175, 212)))
{
gfx.DrawPath(pen, path);
pen.Color = Color.FromArgb(194, 207, 229);
gfx.DrawLine(pen, itemRct.Left, itemRct.Bottom, itemRct.Right - 1, itemRct.Bottom);
}
using (Font font = new Font(NeoTabPageItemsFont, FontStyle.Regular))
{
itemRct.X += 2;
itemRct.Width -= 2;
itemRct.Y += 2;
itemRct.Height -= 2;
if (index == 0)
{
itemRct.X += 6;
itemRct.Width -= 6;
}
using (Brush brush = new SolidBrush(MouseOverTabPageItemForeColor))
gfx.DrawString(tabPageText, font, brush, itemRct, format);
}
break;
}
}
}
gfx.SmoothingMode = mode;
}
public override int ItemObjectsDrawingMargin
{
get { return INTEGERARRAY[0]; }
}
public override int TabPageItemsBetweenSpacing
{
get { return INTEGERARRAY[1]; }
}
public override Color BackColor
{
get { return COLORS[0]; }
}
public override Color TabPageItemForeColor
{
get { return COLORS[1]; }
}
public override Color SelectedTabPageItemForeColor
{
get { return COLORS[2]; }
}
public override Color DisabledTabPageItemForeColor
{
get { return COLORS[3]; }
}
public override Color MouseOverTabPageItemForeColor
{
get { return COLORS[4]; }
}
public override Font NeoTabPageItemsFont
{
get { return MY_FONT; }
}
public override DrawingOffset TabPageAreaCornerOffset
{
get { return OFFSETS[0]; }
}
public override DrawingOffset TabPageItemsAreaOffset
{
get { return OFFSETS[1]; }
}
public override TabPageLayout NeoTabPageItemsSide
{
get { return TabPageLayout.Top; }
}
public override TabPageItemStyle NeoTabPageItemsStyle
{
get { return TabPageItemStyle.OnlyText; }
}
public override void Dispose()
{
GC.SuppressFinalize(this);
}
}
- Now you need to enter some additional class information being used at the top of the class declaration. To do this, you need to connect your renderer
class with the
AddInRenderer
attribute. After you apply this attribute to the class declaration,
enter some descriptive information (version number, developer name, display name...) (If your control renderer supports editor, set IsSupportEditor = true, in this example it is false) within the AddInRenderer
attribute, as shown here:
[AddInRenderer("VS2008Like",
"VS2008Like renderer class, TabPageLayout: Top, TabPageItemStyle: OnlyText.",
DeveloperName = "Burak Özdiken", VersionNumber = "1.0.0.0")]
public sealed class VS2008LikeRenderer : RendererBase
{
}
- Finally, to use this renderer assembly for your applications, you'll create a subdirectory named VS2008 under
the 'Add-ins' directory. And then, add your assembly file into the folder that you've created, as shown in Figure 20.
Figure 20. Creating a new renderer subdirectory
Add your assembly file, as shown in Figure 21.
Figure 21. Adding a assembly file
- The exercise is now complete, and you have successfully created your first renderer assembly. The final product will now look like Figure 22-1.
Figure 22-1. The final product
- To support smart 'close' and 'drop-down' buttons, you need to override
IsSupportSmartCloseButton
and IsSupportSmartDropDownButton
properties, as shown here:
public override bool IsSupportSmartCloseButton
{
get { return true; }
}
public override bool IsSupportSmartDropDownButton
{
get { return true; }
}
Figure 22-2. The smart 'close' and 'drop-down' buttons;
How can I load a custom renderer in runtime
You can load a renderer assembly using the AddInRendererManager.LoadRenderer(string typeName)
static method. This static method loads a specific
control renderer from a given renderer's type name. It returns a renderer class from a specified type name, as shown here:
private void button1_Click(object sender, EventArgs e)
{
neoTabWindow1.Renderer = AddInRendererManager.LoadRenderer("MYNETRendererVS2");
}
How can I show a renderer editor for the specified control renderer
If it supports, you can show a renderer editor using the ShowAddInRendererEditor()
method of the NeoTabWindow
control, as shown here:
private void button2_Click(object sender, EventArgs e)
{
neoTabWindow1.ShowAddInRendererEditor();
}
RendererChanged Event
You can handle this event for each control renderer changes. For example; you might want to change the background color of the form, controls padding, margin ex...
private void neoTabWindow1_RendererChanged(object sender, EventArgs e)
{
this.BackColor = neoTabWindow1.BackColor;
string typeName = neoTabWindow1.Renderer.GetType().Name;
if (typeName.StartsWith("CCleanerRenderer"))
{
foreach (NeoTabControlLibrary.NeoTabPage tp in neoTabWindow1.Controls)
tp.BackColor = Color.White;
}
else if (typeName.StartsWith("NeoTabStripRenderer"))
{
foreach (NeoTabControlLibrary.NeoTabPage tp in neoTabWindow1.Controls)
tp.BackColor = Color.White;
}
else if (typeName.StartsWith("VS2010LikeRenderer"))
{
foreach (NeoTabControlLibrary.NeoTabPage tp in neoTabWindow1.Controls)
tp.BackColor = Color.White;
}
else
{
foreach (NeoTabControlLibrary.NeoTabPage tp in neoTabWindow1.Controls)
tp.BackColor = neoTabWindow1.BackColor;
}
}
Customizing the drop-down menu
You can customize your drop-down menu items using the DropDownButtonClicked
event, as shown here:
Figure 22-3. Customizing drop-down menu
private void neoTabWindow1_DropDownButtonClicked(object sender,
NeoTabControlLibrary.DropDownButtonClickedEventArgs e)
{
for (int i = 0; i < e.ContextMenu.Items.Count; i++)
{
ToolStripItem item = e.ContextMenu.Items[i];
switch (i)
{
case 0:
item.Image = NeoTabControlClient.Properties.Resources.Close;
break;
case 1:
break;
default:
if (item.Enabled)
item.Image = NeoTabControlClient.Properties.Resources.InsertTabControlHS;
else
item.Image = NeoTabControlClient.Properties.Resources.Locked;
break;
}
}
}
How can I add my own tab page controls to the control container in runtime
To add a new tab page control, you can use the following code lines, as shown here:
NeoTabPage myTabPage = new NeoTabPage();
myTabPage.Text = "My tab page control";
neoTabWindow1.Controls.Add(myTabPage);
You can also add your tab page items using the NeoTabWindow.TabPages
property, as shown here:
NeoTabPage[] myTabPageItems =
new NeoTabPage[]{
new NeoTabPage(),
new NeoTabPage(),
new NeoTabPage()
};
neoTabWindow1.TabPages.AddRange(myTabPageItems);
How can I get or set a tab page control in the control container
You can access the selected tab page control using the SelectedTab
property. If it returns a null value, your tab control does not contain any tab pages control.
NeoTabPage selectedTab = neoTabWindow1.SelectedTab;
MessageBox.Show(selectedTab.Text);
To change the selected index of the control, you can use the following code lines.
neoTabWindow1.SelectedIndex = 2;
How can I remove a tab page item from my tab control
To remove a specific tab page control from the control container, you can use the Remove(NeoTabPage toBeRemoved)
method, as shown here:
NeoTabPage myTabPage = new NeoTabPage();
myTabPage.Text = "My tab page control";
neoTabWindow1.Controls.Add(myTabPage);
NeoTabPage toBeRemoved = myTabPage;
neoTabWindow1.Remove(toBeRemoved);
If you want to remove a tab page item from the control collection at the specified indexed location, you can use the RemoveAt(int toBeRemovedIndex)
method, as shown here:
neoTabWindow1.RemoveAt(2);
You can also use the TabPages.Remove()
, Controls.Remove()
and TabPages.RemoveAt()
, Controls.RemoveAt()
methods;
However, if you use this methods for a control removing, you cannot use the TabPageRemoving
, TabPageRemoved
,
SelectedIndexChanging
, SelectedIndexChanged
events.
How can I show the tab page manager
you can show the tab manager using the ShowTabManager()
method of the NeoTabWindow
control, as shown here:
private void button3_Click(object sender, EventArgs e)
{
neoTabWindow1.ShowTabManager();
}
Figure 22-4. Show / Hide Tab Manager
Note: If your tab page items are not closeable, you cannot use the tab manager for these items.
Accessing tab page items from the control mouse events
To access a tab page item from a control mouse event, You can use the GetHitTest()
method, as shown here:
private void neoTabWindow1_MouseMove(object sender, MouseEventArgs e)
{
int itemIndex = -1;
Rectangle itemRectangle;
NeoTabControlLibrary.CommonObjects.ButtonState itemState;
NeoTabControlLibrary.NeoTabPage tp = neoTabWindow1.GetHitTest(e.Location,
out itemRectangle, out itemState, out itemIndex);
if (tp != null && tp.IsSelectable)
{
this.Text = tp.Text;
}
}
Figure 23. Mouse move activity diagram
Drag & Drop support
If the value of the AllowDrop
property is true for your tab control, you can drag and drop a tab page item onto the other tab page items,
as shown in Figure 24.
- TabPageItemEffect
Figure 24. DragDropStyle: TabPageItemEffect
- You can also use the 'PageEffect' style, as shown in Figure 25.
Figure 25. DragDropStyle: PageEffect
Prevent the selection of a tab item
To prevent selection, you can use the following code lines, as shown here:
The first easy way:
neoTabPage5.IsSelectable = false;
The second way is:
private void neoTabWindow1_SelectedIndexChanging(object sender,
NeoTabControlLibrary.SelectedIndexChangingEventArgs e)
{
if (e.TabPage == neoTabPage5)
e.Cancel = true;
}
To prevent removing:
The first easy way:
neoTabPage3.IsCloseable = false;
The second way is:
private void neoTabWindow1_TabPageRemoving(object sender,
NeoTabControlLibrary.TabPageRemovingEventArgs e)
{
if (e.TabPage == neoTabPage3)
e.Cancel = true;
}
Keyboard Support and Navigation
Figure 26. Keyboard support
You can navigate between the tab page items using the keyboard shortcuts.
Keys | Description |
End | Selects last tab. |
Home | Selects first tab. |
Left and Tab+Control+Shift keys | Selects the tab on the left side of the currently selected tab. |
Right and Tab+Control keys | Selects the tab on the right side of the currently selected tab. |
Tooltips
If the value of the IsTooltipEnabled
property is true, the tooltip text that is shown when the mouse cursor is over a tab page item, as shown in Figure 27.
Figure 27. Working with tooltips
You can customize your own tooltips using the TooltipRenderer
property, as shown in Figure 28.
Figure 28. Using the TooltipRenderer property
After the customizing, it will now look like Figure 29.
Figure 29. Customizing the tooltips
Clone Support
Figure 30. Dolly, copy clone sheep
To clone a NeoTabWindow
control from an existing NeoTabWindow
control, you can use the following example, as shown here:
NeoTabPage[] newTabPageItems =
new NeoTabPage[]{
new NeoTabPage(){ Text = "Mario Andretti" },
new NeoTabPage(){ Text = "Graham Hill" },
new NeoTabPage(){ Text = "Emerson Fittipaldi" },
new NeoTabPage(){ Text = "Michael Schumacher" }
};
NeoTabWindow myClonedControl = neoTabWindow1.Clone() as NeoTabWindow;
myClonedControl.Controls.AddRange(newTabPageItems);
myClonedControl.Dock = DockStyle.Fill;
Form newFrm = new Form();
newFrm.ShowIcon = false;
newFrm.ShowInTaskbar = false;
newFrm.Text = "Formula-1 Racers";
newFrm.Controls.Add(myClonedControl);
newFrm.Show();
Figure 31. Control cloning
History
- Sept 28, 2012 - Updated
- Added an example for the clone support.
- Created a new renderer assembly named
VS2005LikeRenderer
.
- Sept 27, 2012 - Updated
- Created a new collection class named
NeoTabPageHidedMembersCollection
. - Added the ability to show or hide an existing tab page items.
- Inserted a new menu item within the drop-down menu to showing the tab manager.
- Added a new interface to the
NeoTabPage
class, named IFormattable
. - Added a new interface to the
NeoTabPage
class, named IComparable<NeoTabPage>
. - Updated
NeoTabPage
class. - Updated
NeoTabWindow
class.
- Sept 25, 2012 - Updated
- Created a new renderer assembly named
TelerikRenderer
.
- Sept 18, 2012 - Updated
- Added smart 'close' and 'drop-down' button support.
- Created a new event named
DropDownButtonClicked
within the NeoTabWindow
class. - Updated
NeoTabWindow
class. - Created a new renderer assembly named
OrderedListRenderer
. - Updated
RendererBase
class.
- Sept 14, 2012 - Updated
- Fixed
InvalidOperationException
and cross-threading errors. - Added clone support descriptions.
- Sept 13, 2012 - Updated
- Added editor support for control renderer assemblies.
- Created a new event named
RendererUpdated
within the NeoTabWindow
class. - Created a new renderer assembly named
VS2012LikeRenderer
with editor support. - Created a new
DataGridViewEditorButtonColumn
for the Add-in Manager table. - Updated
AddInRendererManager
class. - Updated
RendererBase
class. - Fixed names of the CCleaner renderer resources
- Sept 10, 2012 - First release.