8

I want to add a close button to TabPages of a TabControl. I try this code and it works fine with a Left To Right TabControl:

private Point _imageLocation = new Point(13, 5);
private Point _imgHitArea = new Point(13, 2);

this.tabControl2.DrawMode = System.Windows.Forms.TabDrawMode.OwnerDrawFixed;

tabControl2.DrawItem += TabControl2_DrawItem;

private void TabControl2_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
    try
    {
        Image img = new Bitmap(GestionP.Properties.Resources.Close);
        Rectangle r = e.Bounds;
        r = this.tabControl2.GetTabRect(e.Index);
        r.Offset(2, 2);
        Brush TitleBrush = new SolidBrush(Color.Black);
        Font f = this.Font;
        string title = this.tabControl2.TabPages[e.Index].Text;
        e.Graphics.DrawString(title, f, TitleBrush, new PointF(r.X, r.Y));

        if (tabControl2.SelectedIndex >= 1)
        {
            e.Graphics.DrawImage(img, new Point(r.X + (this.tabControl2.GetTabRect(e.Index).Width - _imageLocation.X), _imageLocation.Y));
        }

    }
        catch (Exception) { }
}

    private void tabControl2_MouseClick(object sender, MouseEventArgs e)
    {
        TabControl tc = (TabControl)sender;
        Point p = e.Location;
        int _tabWidth = 0;
        _tabWidth = this.tabControl2.GetTabRect(tc.SelectedIndex).Width - (_imgHitArea.X);
        Rectangle r = this.tabControl2.GetTabRect(tc.SelectedIndex);
        r.Offset(_tabWidth, _imgHitArea.Y);
        r.Width = 16;
        r.Height = 16;
        if (tabControl2.SelectedIndex >= 1)
        {
            if (r.Contains(p))
            {
                TabPage TabP = (TabPage)tc.TabPages[tc.SelectedIndex];
                tc.TabPages.Remove(TabP);

            }
        }
    }

But when I set the property RightToLeftLayout = true and RightToLeft = true it doesn't work, TabPage titles don't appear and also close button.

So how to fix the code in a way that accepts RightToLeft property?

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
user4340666
  • 1,453
  • 15
  • 36

2 Answers2

7

You can create a function to translate coordinates of a rectangle to RTL coordinates in a container:

public static Rectangle GetRTLCoordinates(Rectangle container, Rectangle drawRectangle)
{
    return new Rectangle(
        container.Width - drawRectangle.Width - drawRectangle.X,
        drawRectangle.Y,
        drawRectangle.Width,
        drawRectangle.Height);
}

And when painting in RTL mode, calculate coordinates this way:

tabRect = GetRTLCoordinates(this.tabControl2.ClientRectangle, tabRect);

Also you should draw your strings using an StringFormat and set it to use StringFormatFlags.DirectionRightToLeft when you are in RTL mode and draw string in the translated rectangle using the string format:

e.Graphics.DrawString(this.tabControl2.TabPages[e.Index].Text, 
                      this.Font, Brushes.Black, tabRect, sf);

You can encapsulate all codes in a CustomTabControl inheriting TabControl.

Screenshot

enter image description here enter image description here

The whole code could be:

I suppose you have a close image somewhere like Properties.Default.Close and assign it to this.CloseImage. Here is the image I used: enter image description here

I also set the this.tabControl2.Padding = new Point(10, 3); to provide additional free space for drawing the image.

Also you can simply add your criteria of not closing first tab.

Image CloseImage;

private void Form1_Load(object sender, EventArgs e)
{
    this.tabControl2.DrawMode = System.Windows.Forms.TabDrawMode.OwnerDrawFixed;
    tabControl2.DrawItem += TabControl2_DrawItem;
    tabControl2.MouseClick += tabControl2_MouseClick;
    CloseImage = Properties.Resources.Close;
    this.tabControl2.Padding = new Point(10, 3);
}


private void TabControl2_DrawItem(object sender, 
                                  System.Windows.Forms.DrawItemEventArgs e)
{
    try
    {
        var tabRect = this.tabControl2.GetTabRect(e.Index);
        tabRect.Inflate(-2, -2);
        var imageRect = new Rectangle(tabRect.Right - CloseImage.Width,
                                 tabRect.Top + (tabRect.Height - CloseImage.Height) / 2,
                                 CloseImage.Width,
                                 CloseImage.Height);

        var sf = new StringFormat(StringFormat.GenericDefault);
        if (this.tabControl2.RightToLeft == System.Windows.Forms.RightToLeft.Yes &&
            this.tabControl2.RightToLeftLayout == true)
        {
            tabRect = GetRTLCoordinates(this.tabControl2.ClientRectangle, tabRect);
            imageRect = GetRTLCoordinates(this.tabControl2.ClientRectangle, imageRect);
            sf.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
        }

        e.Graphics.DrawString(this.tabControl2.TabPages[e.Index].Text,
                              this.Font, Brushes.Black, tabRect, sf);
        e.Graphics.DrawImage(CloseImage, imageRect.Location);

    }
    catch (Exception) { }
}

private void tabControl2_MouseClick(object sender, MouseEventArgs e)
{

    for (var i = 0; i < this.tabControl2.TabPages.Count; i++)
    {
        var tabRect = this.tabControl2.GetTabRect(i);
        tabRect.Inflate(-2, -2);
        var imageRect = new Rectangle(tabRect.Right - CloseImage.Width,
                                 tabRect.Top + (tabRect.Height - CloseImage.Height) / 2,
                                 CloseImage.Width,
                                 CloseImage.Height);
        if (imageRect.Contains(e.Location))
        {
            this.tabControl2.TabPages.RemoveAt(i);
            break;
        }
    }
}

public static Rectangle GetRTLCoordinates(Rectangle container, Rectangle drawRectangle)
{
    return new Rectangle(
        container.Width - drawRectangle.Width - drawRectangle.X,
        drawRectangle.Y,
        drawRectangle.Width,
        drawRectangle.Height);
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • @Dotnet Here is the [image](https://i.stack.imgur.com/8kuxe.png). You can play with `this.tabControl2.Padding = new Point(10, 3);`. – Reza Aghaei Sep 25 '17 at 13:43
  • 1
    I made the `Padding` to **4**, because for some reason the text was lower than the **X**... Thanks for this :) – Momoro Jan 31 '20 at 08:06
  • Is there a way to add a hover image for the X? e.g. if I hover the mouse over the X button, it will turn into the image I specify? Thanks :) – Momoro May 14 '20 at 05:55
  • @Momoro the logic for paint is in `DrawItem` event handler. The logic for checking the mouse position to see if it's over the close button can be in `MouseMove`. To force the repaint, you need to call `Invalidate` method of the control. Give it a try and ask a linked question if you needed some help. All the community users can help you, however feel free to tag me here in the comments, so I may also share more idea if I have anything in mind – Reza Aghaei May 14 '20 at 07:36
  • But, I am not attempting to re-draw the **entire** control, I just need to re-draw the tabpage. Is this possible? Thanks :D – Momoro May 14 '20 at 07:52
  • @Momoro `Invalidate` has an overload which invalidates a specific rectangle of the control. The basic idea of paint is: do all paintings in paint event, when you need to repaint, for example because of a click, because of a mouse move, etc, call invalidate. – Reza Aghaei May 14 '20 at 10:34
  • I tried what you suggested, and it just makes the X image disappear.. Shouldn't it just change the image, or am I doing something wrong? I can send you my code through another comment if it's needed – Momoro May 15 '20 at 00:38
0

there is no need to override drawing:
1. select tab control.
2. set RightToLeft property to yes AND RightToLeftLayout property to true.

now all tab pages are aligned right to left (the trick is that RightToLeft and RightToLeftLayout properties must be set to changed, dont know why Microsoft did it that way...)

Jonathan Applebaum
  • 5,738
  • 4
  • 33
  • 52