Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Customized TabControl by Repainting Microsoft's Panels

4.73/5 (12 votes)
4 Oct 2009CPOL4 min read 72.3K   3.9K  
With Size, Icon, Color Customized & not repainting Microsoft's TabControl

Image 1

Figure 1: TabControl with Icons

Image 2

Figure 2: TabControl with customized retreat/forward card buttons

Image 3

Figure 3: TabControl with customized hover event

Image 4

Figure 4: TabControl size changing with font

Introduction

There have been a bunch of articles about how to customize(repaint) a TabControl by adding icons to it or changing its colors. I appreciate these authors because I got many useful ideas from them about how to deal with such problems. But after I tried to repaint Microsoft's TabControl for about 2 days, I finally found that it is too hard to paint what I really want. I needed more interface to change the Control's color, selectcards' size and so on. It is very difficult to maintain a proper size of SelectCard when the font changes. When the Cards' total length is bigger than the container, the two Cards' scroll buttons (retreat and forward buttons to show the selectcards which are hidden) are kind of ugly. So finally I made a Customized tabcontrol by painting the Cards on plain UserControl's panel and joining some panels together to represent the Tabpages.

By now, this Control has kind of solved my problems. I can change the colors, font, size, and Icons by giving some proper parameters.

Background

Since this is a Control inherited from panels, we have to write our own TabPages which is also inherited from panel, fire events, calculate the body's and selectcards' location and size, which is kind of complicated than repainting the tabcontrol, but it can help us to paint out what we really want: proper size, color, icons, font, etc.

Using the Control

By setting the CardFont, IconImg, and colors of the selectcards' colors, we can get our tabControl paint out.

The selectcards' height is calculated with the Cards' text font.

C#
myTabControl.CardFont = new Font("MS UI Gothic", 11F);  .......

We give the background colors and cards' colors to paint our desired Tabs...

C#
myTabControl.TabDeactiveBgColor1 = Color.YellowGreen;  .......

We set the Icon's image.

C#
MyTabPage p3 = new MyTabPage("TabPage3");
 p3.IconImg = Image.FromFile(@"D:\Icon\ark_addfile.png");
 myTabControl.Controls.Add(p3);

MyTabPage Inheriting from Plain Panel

Since we do not want to repaint the tabcontrol, we have to construct a Control which represents the TabPages. Choosing panel will help us to get a relatively light feeling when we run the control... (forgive me for my poor English). In fact, this is just a simple panel which kind of loads us down with trivial details when we paint the whole tabControl body...

C#
public class MyTabPage : Panel{
     Image imgIcon;
     int iTabIndex;

     public MyTabPage(string sName) {
         this.Text = sName;
     }
     public MyTabPage() {
     }

     public Image IconImg {
         set {
             this.imgIcon = value;
         }
         get {
             return this.imgIcon;
         }
     }
 }

We need to add two event handlers to deal with the event that user adds MyTabpage to MyTabControl.

C#
this.ControlAdded += new ControlEventHandler(MyTabControl_ControlAdded);
this.ControlRemoved += new ControlEventHandler(MyTabControl_ControlRemoved);

When mytabpage is added to our control, we will add it to a List which we maintain though the control's lifecycle.

C#
private void MyTabControl_ControlAdded(object sender, ControlEventArgs e) {
    if (e.Control is MyTabPage) {
        MyTabPage mtpAdded = (MyTabPage)e.Control;
        m_lTabPages.Add((MyTabPage)e.Control);
        Invalidate();
    }
}
C#
private void MyTabControl_ControlRemoved(object sender, ControlEventArgs e) {
      if (e.Control is MyTabPage) {
          MyTabPage mtpAdded = (MyTabPage)e.Control;
          m_lTabPages.Remove((MyTabPage)e.Control);
          Invalidate();
      }
}

Calculate MyTabControl Body's Location and Size

When we give up repainting Microsoft's tabcontrol, we have to calculate the location and size of the control's body and each selectCards...

We get our selectcards' height by measuring the font which you can set to the control...

C#
m_iCardHeight = (int)g.MeasureString("0", m_fontCard).Height + 5;

Then, we can paint the body out ...

C#
Rectangle TabArea = new Rectangle(4, m_iCardHeight + 7, 
	this.Size.Width - 8, this.Size.Height - m_iCardHeight - 11);

/*-------------------------------------------------------*/
/* Whole Control BackColor Paint                    */
/*-------------------------------------------------------*/
Brush br = new SolidBrush(SystemColors.Control);
g.FillRectangle(br, TabControlArea);
br.Dispose();

Paint the Selectcards

Image 5

This part is quite like the other articles about repainting the tabcontrols. Just one difference is that we need to set a variable to represent the start point of our cards since we're going to modify this variable to move the hiding cards out.

We get our selectcards' height by measuring the font which you can set to the control.

C#
if (nIndex == m_lTabPages.Count - 1) {
    m_iStartPos = m_iStandardCardStartPoint;
} else {
    m_iStartPos = recBounds.X + recBounds.Width;
}

And we need to mark down each selectcard's area rectangle to a dictionary so that we can respond to the select event or the cards' scroll event.

C#
tabTextArea.Width = g.MeasureString(tabPage.Text, m_fontCard).Width;
tabTextArea.Height = g.MeasureString(tabPage.Text, m_fontCard).Height + 10; 
//recBounds.Height = (int)tabTextArea.Height;
recBounds.Height = m_iCardHeight + 5;
C#
m_dicIndexRectTabs.Add(nIndex, recBounds); 

Paint our Scroll Button

Image 6

This is the hardest part of the code. Since I don't want the MouseHover or MouseMove event to load our control down, I add two small panels to deal with the hover and click event... then paint the button out at the same location where the panels are.

C#
internal void DrawScrollButton(Rectangle recCardArea, Graphics g) {

       /*---------------------------------------------------------------*/
       /* Retreat,Forward button Area               */
       /*---------------------------------------------------------------*/
       int iSBtnSideLength = recCardArea.Height / 2 + 3;
       m_recRetreat
           = new Rectangle(recCardArea.X + recCardArea.Width - iSBtnSideLength * 2,
           recCardArea.Y + recCardArea.Height - iSBtnSideLength + 3,
       iSBtnSideLength, iSBtnSideLength);
       m_recForward
           = new Rectangle(recCardArea.X + recCardArea.Width - iSBtnSideLength,
           recCardArea.Y + recCardArea.Height - iSBtnSideLength + 3,
       iSBtnSideLength, iSBtnSideLength);

       /*---------------------------------------------------------------*/
       /* Retreat,Forward panel   */
       /*---------------------------------------------------------------*/
       this.m_pnlRetreat.Size = m_recRetreat.Size;
       this.m_pnlRetreat.Location = m_recRetreat.Location;

       this.m_pnlForward.Size = m_recForward.Size;
       this.m_pnlForward.Location = m_recForward.Location;

       /*---------------------------------------------------------------*/
       /* Retreat,Forward back color              */
       /*---------------------------------------------------------------*/
       Brush br;
       /******Retreat BackColor*******/
       if (m_iStandardCardStartPoint == 5) {
           br = new SolidBrush(Color.LightGray);
       } else {
           if (m_bRetreatMouseHovered) {
               br = new SolidBrush(Color.LightCoral);
           } else {
               br = new SolidBrush(Color.FromArgb(((int)(((byte)(194)))),
       ((int)(((byte)(203)))), ((int)(((byte)(166))))));
           }
       }
       g.FillRectangle(br, m_recRetreat);

       /******Forward BackColor*******/
       foreach (int iCardIndex in m_dicIndexRectTabs.Keys) {
           if (m_dicIndexRectTabs[iCardIndex].X == 5) {
               int iNowCardsLength = 0;
               for (int iLoop = iCardIndex; iLoop < m_dicIndexRectTabs.Count; iLoop++) {
                   iNowCardsLength += m_dicIndexRectTabs[iLoop].Width;
               }
               if (iNowCardsLength < this.Width - 10) {
                   br = new SolidBrush(Color.LightGray);
               } else {
                   if (m_bForwardMouseHovered) {
                       br = new SolidBrush(Color.LightCoral);
                   } else {
                       br = new SolidBrush(Color.FromArgb(((int)(((byte)(194)))),
           ((int)(((byte)(203)))), ((int)(((byte)(166))))));
                   }
               }
               break;
           }
       }
       g.FillRectangle(br, m_recForward);
       br.Dispose();

       /*---------------------------------------------------------------*/
       /* Retreat,Forward button Border           */
       /*---------------------------------------------------------------*/
       Pen border = new Pen(Color.FromArgb(((int)(((byte)(234)))),
       ((int)(((byte)(243)))), ((int)(((byte)(206))))));
       g.DrawRectangle(border, m_recRetreat);
       g.DrawRectangle(border, m_recForward);
       border.Dispose();

       /*---------------------------------------------------------------*/
       /* Retreat,Forward button Icon            */
       /*---------------------------------------------------------------*/
       Point[] pt = new Point[3];
       pt[0] = new Point(m_recRetreat.Right - 4, m_recRetreat.Top + 4);
       pt[1] = new Point(m_recRetreat.Left + 4, m_recRetreat.Top + iSBtnSideLength / 2);
       pt[2] = new Point(m_recRetreat.Right - 4, m_recRetreat.Top + iSBtnSideLength - 4);
       g.DrawLines(new Pen(Color.White), pt);

       pt[0] = new Point(m_recForward.Left + 4, m_recForward.Top + 4);
       pt[1] = new Point(m_recForward.Right - 4, m_recForward.Top + iSBtnSideLength / 2);
       pt[2] = new Point(m_recForward.Left + 4, m_recForward.Top + iSBtnSideLength - 4);
       g.DrawLines(new Pen(Color.White), pt);
   }

Cards' Location Changed When the Scroll Button is Clicked

When we click the scroll button, the cards' location should change to move out one card which is hidden left or right when the control's size is not long enough to contain so many selectcards...

C#
private void dealCardForward() {
     foreach (int iCardIndex in m_dicIndexRectTabs.Keys) {
         if (m_dicIndexRectTabs[iCardIndex].X == 5) {
             int iNowCardsLength = 0;
             for (int iLoop = iCardIndex; iLoop < m_dicIndexRectTabs.Count; iLoop++) {
                 iNowCardsLength += m_dicIndexRectTabs[iLoop].Width;
             }
             if (iNowCardsLength < this.Width - 10) {
                 return;
             }
             
             m_iStandardCardStartPoint -= m_dicIndexRectTabs[iCardIndex].Width;
             m_iStartPos = m_iStandardCardStartPoint;
             break;
         }
     }
}
C#
private void dealCardRetreat() {
       foreach (int iCardIndex in m_dicIndexRectTabs.Keys) {
           if (m_dicIndexRectTabs[iCardIndex].X == 5) {
               if (iCardIndex == 0) {
                   return;
               }

               m_iStandardCardStartPoint += m_dicIndexRectTabs[iCardIndex - 1].Width;
               m_iStartPos = m_iStandardCardStartPoint;
               break;
           }
       }
   }

Points of Interest

Sorry for the long code with poor English explanation. Maybe you will think that it's stupid to write a control adding all the events, attribute properties by ourselves. But when we just cannot get what we really want by repainting others' controls. writing one by ourselves is also an option, isn't it? Indeed this will cost us more time (this cost me about 2 days to write it out), but it will be easy to edit by ourselves. :)

I Have a Question to Ask

If you try this code, you will find that when you add or edit the tabpages it is kind of hard to bring out the tabpage you want to edit..... since this. I believe this is not like the usual control to use... Can anyone tell me how to solve this problem?????

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)