2

I am not really sure where I am going wrong but before I added my custom backcolor to my tabControl1 everything worked fine, I saw the X on other tabs and the + to add tabs

enter image description here

After I added this source of code my Tabs I add are not there but the + button functions and going on other tabs functions aswell making me think that the backColor is just hiding them behind.

            SolidBrush backColor = new SolidBrush(Color.FromArgb(40, 40, 40)); // change back color
            e.Graphics.FillRectangle(backColor, rec);

enter image description here

Is there like any way that the Tab Pages display fully with the DrawItems I have put in with the backColor?

The code source:

        private void tabControl1_DrawItem_1(object sender, DrawItemEventArgs e)
        {
            Rectangle rec = tabControl1.ClientRectangle;

            SolidBrush backColor = new SolidBrush(Color.FromArgb(40, 40, 40)); // change back color
            e.Graphics.FillRectangle(backColor, rec);

            StringFormat StrFormat = new StringFormat();
            StrFormat.LineAlignment = StringAlignment.Center;
            StrFormat.Alignment = StringAlignment.Center;

            Brush bshBack = new SolidBrush(Color.FromArgb(55, 55, 55)); // change tab color

            var tabPage = this.tabControl1.TabPages[e.Index];
            var tabRect = this.tabControl1.GetTabRect(e.Index);
            tabRect.Inflate(-2, -2);
            if (e.Index == this.tabControl1.TabCount - 1)
            {
                // Add
                var addImage = Properties.Resources.Add;
                e.Graphics.FillRectangle(bshBack, e.Bounds);
                e.Graphics.DrawImage(addImage,
                    tabRect.Left + (tabRect.Width - addImage.Width) / 2,
                    tabRect.Top + (tabRect.Height - addImage.Height) / 2);
            }
            else
            {
                // Close
                var closeImage = Properties.Resources.Close;
                e.Graphics.FillRectangle(bshBack, e.Bounds);
                e.Graphics.DrawImage(closeImage,
                    (tabRect.Right - closeImage.Width),
                    tabRect.Top + (tabRect.Height - closeImage.Height) / 2);
                TextRenderer.DrawText(e.Graphics, tabPage.Text, tabPage.Font,
                    tabRect, tabPage.ForeColor, TextFormatFlags.Left);
            }
        }
VWWE
  • 57
  • 5
  • [Workaround](https://stackoverflow.com/questions/11822748/how-to-change-the-background-color-of-unused-space-tab-in-c-sharp-winforms). – dr.null May 09 '22 at 19:32
  • yes it does work but whenever one of my tab pages is selected it makes this like white outline any way I could disable that? @dr.null – VWWE May 09 '22 at 19:53
  • You won't get a nice result through `TabDrawMode.OwnerDrawFixed`. For a better outcome, you need to takeover and paint the whole thing yourself. You can find many [examples](https://www.codeproject.com/Articles/91387/Painting-Your-Own-Tabs-Second-Edition-2) to do so. I'll try to find something close. – dr.null May 09 '22 at 20:33
  • Yea if you could find something close to this https://prnt.sc/Vw13tXrb-KfG that would be great. ik its like a ton of work put into them custom ones but im trying my best to find everything. – VWWE May 09 '22 at 21:11

1 Answers1

0

To extend the TabControl with Close and Add Button solution. Create a new class and inherit from the TabControl, set the control styles shown below in the constructor to mainly draw the control and the tabs the way you want it.

using System;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;

namespace YourProjectNamespace
{
    [DesignerCategory("Code")]
    [ToolboxBitmap(typeof(TabControl))]
    public class TabControlEx : TabControl
    {
        public TabControlEx() : base()
        {
            SetStyle(ControlStyles.AllPaintingInWmPaint
                | ControlStyles.ResizeRedraw
                | ControlStyles.OptimizedDoubleBuffer
                | ControlStyles.UserPaint, true);
            UpdateStyles();
            Padding = new Point(14, 4);
        }

        private Color backColor = Color.FromArgb(40, 40, 40);
        /// <inheritdoc cref="Control.BackColor"/>
        [DefaultValue(typeof(Color), "40, 40, 40"),
            Browsable(true),
            EditorBrowsable(EditorBrowsableState.Always),
            DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public new Color BackColor
        {
            get => backColor;
            set
            {
                if (backColor != value)
                {
                    backColor = value;
                    Invalidate();
                }
            }
        }

        private Image addImage;
        [DefaultValue(null)]
        public Image AddButtonImage
        {
            get => addImage;
            set
            {
                addImage = value;
                Invalidate();
            }
        }

        private Image closeImage;
        [DefaultValue(null)]
        public Image CloseButtonImage
        {
            get => closeImage;
            set
            {
                closeImage = value;
                Invalidate();
            }
        }

        protected override void OnHandleCreated(EventArgs e)
        {
            base.OnHandleCreated(e);
            SendMessage(Handle, TCM_SETMINTABWIDTH, IntPtr.Zero, (IntPtr)16);
            if (TabCount > 0) TabPages[TabCount - 1].Text = "";
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            var g = e.Graphics;

            g.Clear(BackColor);

            if (TabCount == 0) return;

            for (int i = 0; i < TabCount; i++)
            {
                var tp = TabPages[i];
                var r = Rectangle.Inflate(GetTabRect(i), -4, 0);

                if (i == TabCount - 1)
                {
                    if (AddButtonImage != null)
                        g.DrawImage(AddButtonImage,
                            r.X + (r.Width - AddButtonImage.Width) / 2,
                            r.Y + (r.Height - AddButtonImage.Height) / 2,
                            AddButtonImage.Width, AddButtonImage.Height);
                }
                else
                {
                    if (i == SelectedIndex)
                        using (var brSel = new SolidBrush(Color.FromArgb(105, 105, 105)))
                            g.FillRectangle(brSel, Rectangle.Inflate(r, 4, 1));

                    if (ImageList != null && tp.ImageIndex >= 0)
                        ImageList.Draw(g, 
                            r.X, r.Y + (r.Height - ImageList.ImageSize.Height) / 2, 
                            tp.ImageIndex);

                    if (CloseButtonImage != null)
                    {
                        var closeRect = new Rectangle(new Point(
                            r.Right - CloseButtonImage.Width,
                            r.Y + (r.Height - CloseButtonImage.Height) / 2),
                            CloseButtonImage.Size);

                        g.DrawImage(CloseButtonImage, closeRect,
                            0, 0, CloseButtonImage.Width, CloseButtonImage.Height,
                            GraphicsUnit.Pixel);
                    }

                    TextRenderer.DrawText(g, tp.Text, tp.Font, r, Color.White,
                        TextFormatFlags.HorizontalCenter | 
                        TextFormatFlags.VerticalCenter);
                }
            }
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);

            var lastIndex = TabCount - 1;
            if (GetTabRect(lastIndex).Contains(e.Location))
            {
                var args = new AddingTabEventArgs();
                OnAddingTab(args);
                if (args.Cancel) return;

                var newTp = new TabPage
                {
                    Text = "New Tab",
                    BackColor = BackColor
                };
                TabPages.Insert(lastIndex, newTp);
                SelectedIndex = lastIndex;
            }
            else
            {
                for (var i = 0; i < TabPages.Count; i++)
                {
                    var r = Rectangle.Inflate(GetTabRect(i), -4, 0);

                    if (new Rectangle(r.Right - 16, r.Y + (r.Height - 16), 16, 16)
                        .Contains(e.Location))
                    {
                        var args = new ClosingTabEventArgs(TabPages[i]);
                        OnClosingTab(args);
                        if (args.Cancel) return;

                        TabPages[i].Dispose();
                        break;
                    }
                }
            }
        }

        protected override void OnSelecting(TabControlCancelEventArgs e)
        {
            if (e.TabPageIndex == TabCount - 1)
                e.Cancel = true;
            else
                base.OnSelecting(e);
        }

        public event EventHandler<ClosingTabEventArgs> ClosingTab;

        protected virtual void OnClosingTab(ClosingTabEventArgs e) => 
            ClosingTab?.Invoke(this, e);

        public event EventHandler<AddingTabEventArgs> AddingTab;

        protected virtual void OnAddingTab(AddingTabEventArgs e) =>
            AddingTab?.Invoke(this, e);

        const int TCM_SETMINTABWIDTH = 0x1300 + 49;

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
    }

    public class ClosingTabEventArgs : EventArgs
    {
        public ClosingTabEventArgs(TabPage tp) => TabPage = tp;

        public TabPage TabPage { get; }

        public bool Cancel { get; set; }
    }

    public class AddingTabEventArgs : EventArgs
    {
        public AddingTabEventArgs() { }

        public bool Cancel { get; set; }
    }
}

Note, I've added two more events, AddingTab and ClosingTab to trap them in your implementation and do whatever you need, including cancelation.

private void tabControlEx1_AddingTab(object sender, AddingTabEventArgs e)
{
    if (MessageBox.Show("Add new TabPage?", "Alert", 
        MessageBoxButtons.YesNo) == DialogResult.No)
        e.Cancel = true;
}

private void tabControlEx1_ClosingTab(object sender, ClosingTabEventArgs e)
{
    if (MessageBox.Show($"Remove {e.TabPage.Text}?", "Alert", 
        MessageBoxButtons.YesNo) == DialogResult.No)
        e.Cancel = true;
}

Of course, you can extend the control more and add more drawing properties like the back/foreground colors of the normal and selected tabs. Also, you can try the LinearGradientBrush to fill the backgrounds instead of the solid brushes.

Rebuild, on the top of the ToolBox window, find and drop an instance. Try and go back to the code to add your touch.

SOQ72179300

dr.null
  • 4,032
  • 3
  • 9
  • 12
  • if i may ask how do i use it? like how do i call it – VWWE May 10 '22 at 15:27
  • i pasted the code in and this is what i got: https://prnt.sc/g5H0CYwStNwN – VWWE May 10 '22 at 15:33
  • @VWWE See the edit. You need to replace the old TabControl with this one. Either, drop a new instance into the designer **or** edit the `Form.Designer.cs` to change the type of the existing `TabControl` to `TabControlEx` or whatever you name this custom control. – dr.null May 10 '22 at 23:55
  • Thanks for the help but like this is just confusign me too much when I paste in the code the other code I use for like Draw and MouseDown underlines tabControl1 and I'm just beyond confused but I'll just keep trying with the code you sent me and I hope I get somewhere with it, since I'm not gonna bother you much – VWWE May 11 '22 at 13:25
  • Could you send a Visual Studio Project, if you can @dr.null – VWWE May 11 '22 at 19:14
  • Not bothering, you're welcome. Just follow these steps: 1) Forget your old `TabControl`. 2) Select your project. 3) Press `Shift+Alt+C` keys to open the add item dialog. 3) Rename the class to `TabControlEx` and hit Add. 4) In the new class and within the namespace, replace the `class TabControlEx { }` part with the code above. 5) Rebuild. 6) Open a Form in your project. 7) From the top of the `ToolBox` window, find `TabControlEx` and drop an instance in the designer. That's all. – dr.null May 11 '22 at 23:11
  • 1
    Yea alright I got to the point where I can put in my TabControlEx and now how do I add the images and whenever I press close or add it says "System.NullReferenceException: 'Object reference not set to an instance of an object.'" line 175 for close and 180 for add @dr.null – VWWE May 12 '22 at 16:14
  • Right, you found a bug. Forgot a `null` check in the events invokers. Just append `?` to the events. `ClosingTab?.Invoke(this, e);` and `AddingTab?.Invoke(this, e);`. As for the images, if you mean the Add and Close images. then select the control and press `F4` to activate the Properties window, Find the `AddButtonImage` and `CloseButtonImage` to set them. Click the ` . ` button and select an image for each from the select resource dialog. If you mean the other images on the left, then you need to follow [this](https://stackoverflow.com/a/4019948/14171304) answer. – dr.null May 13 '22 at 03:59
  • Alright thanks you so much for the help appreciate it. Also if I was to add like a WebBrowser everytime I add a tab would I put it in the TabControlEx.cs or the `tabControlEx1_AddingTab`? – VWWE May 13 '22 at 19:52
  • Never mind found out how, thanks again. – VWWE May 13 '22 at 19:56
  • The anchor doesn't seem to be working when I resize on the TabControlEx? @dr.null – VWWE May 13 '22 at 20:18
  • @VWWE Anchor of which? Make sure you select the right control before you set the Anchor property. Just checked again and everything works as it should. – dr.null May 14 '22 at 01:10
  • Ah it works now probably put the anchor on my tabpage or something. Thanks again – VWWE May 14 '22 at 10:28