在 C# WinForms 中创建自定义 TabControl - 设计时拖放问题

问题描述 投票:0回答:1

在创建自定义 TabControl(名为 MainTabControl)时,我遇到 2 个问题:

  1. 无法从设计器工具箱拖放控件,因为当鼠标悬停时,会自动选择 MainTabControl 而不是当前 TabPage。当我释放拖动时,出现错误。

“无法将‘Control’添加到TabControl中。只有TabPages可以直接 添加到 TabControls”。

  1. 当 MainTabControl 没有选项卡时,无法通过鼠标单击进行选择,只能使用套索选择。

设计师

using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Security.Permissions;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace ThunderbirdLibrary.CustomControls
{
    [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
    public class MainTabControlDesigner : ParentControlDesigner
    {
        DesignerActionListCollection _actionLists;
        DesignerVerbCollection _verbs;
        IDesignerHost _designerHost;
        IComponentChangeService _changeService;
        bool clicked = false;

        public override DesignerActionListCollection ActionLists
        {
            get
            {
                if (_actionLists == null)
                {
                    _actionLists = new DesignerActionListCollection
                    {
                        new MainTabControlActionList(this.Component)
                    };
                }

                return _actionLists;
            }
        }

        public override DesignerVerbCollection Verbs
        {
            get
            {
                if(_verbs == null)
                {
                    _verbs = new DesignerVerbCollection()
                    {
                        new DesignerVerb("Add Tab", new EventHandler(OnAddTab)),
                        new DesignerVerb("Remove Tab", new EventHandler(OnRemoveTab))
                    };

                    MainTabControl mainTabControl = Control as MainTabControl;
                    if (mainTabControl != null)
                    {
                        if (mainTabControl.TabPages.Count == 0) _verbs[1].Enabled = false;

                        else _verbs[1].Enabled = true;
                    }
                }

                return _verbs;
            }
        }

        public override void Initialize(IComponent component)
        {
            base.Initialize(component);

            _designerHost = (IDesignerHost)GetService(typeof(IDesignerHost));
            _changeService = (IComponentChangeService)GetService(typeof(IComponentChangeService));

            if (_changeService != null)
                _changeService.ComponentChanged += new ComponentChangedEventHandler(OnComponentChanged);
        }

        public override void InitializeNewComponent(IDictionary defaultValues)
        {
            base.InitializeNewComponent(defaultValues);
            
            try
            {
                MainTabControl control = Control as MainTabControl;

                OnAddTab(this, EventArgs.Empty);

                control.SelectedIndex = 0;
                ((MainTabPage)control.TabPages[0]).ImageName = "AccountSetup";
            }
            catch
            {
                throw new Exception("Couldn't initialize MainTabControl with MainTabPage");
            }
        }


        // Properties Window by default is updated only after select and deselect the control
        // Here "ComponentChanging" and "ComponentChanged" comes in play and notify IDE designer about the changes
        public void OnAddTab(object sender, EventArgs e)
        {
            MainTabControl parentControl = Control as MainTabControl;

            DesignerTransaction transaction = null;
            try
            {
                transaction = _designerHost.CreateTransaction("Add Tab");

                RaiseComponentChanging(TypeDescriptor.GetProperties(parentControl)["TabPages"]);

                MainTabPage newPage = (MainTabPage)_designerHost.CreateComponent(typeof(MainTabPage));
                newPage.Text = newPage.Name;
                parentControl.TabPages.Add(newPage);
                parentControl.SelectedTab = newPage;

                RaiseComponentChanged(TypeDescriptor.GetProperties(parentControl)["TabPages"], null, null);

                transaction.Commit();
            }
            catch
            {
                MessageBox.Show("Exception occured during adding the tab");
                transaction?.Cancel();
            }

        }

        public void OnRemoveTab(object sender, EventArgs e)
        {
            MainTabControl parentControl = Control as MainTabControl;

            DesignerTransaction transaction = null;
            try
            {
                transaction = _designerHost.CreateTransaction("Remove Tab");

                RaiseComponentChanging(TypeDescriptor.GetProperties(parentControl)["TabPages"]);

                _designerHost.DestroyComponent(parentControl.SelectedTab);

                RaiseComponentChanged(TypeDescriptor.GetProperties(parentControl)["TabPages"], null, null);
                
                transaction.Commit();
            }
            catch
            {
                MessageBox.Show("Exception occured during removing the tab");
                transaction?.Cancel();
            }
        }

        public void OnComponentChanged(object sender, ComponentChangedEventArgs e)
        {
            MainTabControl parentControl = e.Component as MainTabControl;
            
            if(parentControl != null && e.Member.Name == "TabPages")
            {
                foreach (DesignerVerb verb in Verbs)
                {
                    if(verb.Text == "Remove Tab")
                    {
                        if (parentControl.TabPages.Count == 0) verb.Enabled = false;

                        else verb.Enabled = true;
                    }
                }
            }
        }

        // Determine whether to pass click to the control
        protected override bool GetHitTest(Point point)
        {
            ISelectionService selectionService = (ISelectionService)GetService(typeof(ISelectionService));

            object selectedObject = selectionService.PrimarySelection;

            return selectedObject != null && selectedObject.Equals(Control);
        }
    }
}

MainTabControl.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Design;
using System.Linq;
using System.Resources;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Media.Animation;

namespace ThunderbirdLibrary.CustomControls;

[Designer(typeof(MainTabControlDesigner))]
public partial class MainTabControl : TabControl
{
    #region Private Members

    private readonly Size DefaultItemSize;
    //private int mouseAreaTriggerOffset = 3;
    private readonly int standardOffset;

    private Rectangle imageRec;
    private Rectangle textRec;
    private Rectangle closeButtonRec;
    private Rectangle closeAreaRec;
    
    private int ellipsisWidth;
    private bool isRecalculatingTabSize;

    private Point mouseDownLocation;

    #endregion

    #region Properties

    [Editor(typeof(MainTabPageCollectionEditor), typeof(UITypeEditor))]
    public new TabPageCollection TabPages
    {
        get
        {
            return base.TabPages;
        }
    }

    #endregion

    #region Constructor
    public MainTabControl()
    {
        InitializeComponent();

        this.DrawMode = TabDrawMode.OwnerDrawFixed;
        this.SizeMode = TabSizeMode.Fixed;
        SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true);

        isRecalculatingTabSize = false;

        // Set Sizes of header parts that (probably) won't be recalculated
        DefaultItemSize = new Size(250, 33);

        standardOffset = (int)(this.DefaultItemSize.Width * 0.02);

        imageRec = new Rectangle(
            standardOffset,
            standardOffset,
            DefaultItemSize.Height / 2,
            DefaultItemSize.Height / 2);

        closeAreaRec = new Rectangle(
            DefaultItemSize.Width - standardOffset - DefaultItemSize.Height / 2,
            (DefaultItemSize.Height - DefaultItemSize.Height / 2) / 2,
            DefaultItemSize.Height / 2,
            DefaultItemSize.Height / 2);

        using (Graphics g = this.CreateGraphics())
        {
            closeButtonRec = new Rectangle(
                new Point(
                    DefaultItemSize.Width - closeAreaRec.Width - standardOffset + closeButtonRec.Width / 4,
                    (DefaultItemSize.Height - closeAreaRec.Height) / 2
                ),
                g.MeasureString("x", Font).ToSize());

            ellipsisWidth = (int)g.MeasureString("...", Font).Width;


            textRec = new Rectangle(
                2 * standardOffset + imageRec.Width,
                (int)((DefaultItemSize.Height - g.MeasureString("Test String", Font).Height) / 2),
                0, 
                0);
        }
    }

    #endregion

    #region EventHandler

    protected override void OnControlRemoved(ControlEventArgs e)
    {
        base.OnControlRemoved(e);

        if (e.Control is MainTabPage)
        {
            RecalculateTabSize();
        }
    }

    protected override void OnControlAdded(ControlEventArgs e)
    {
        base.OnControlAdded(e);

        if(e.Control is MainTabPage)
        {
            RecalculateTabSize();
        }

    }

    protected override void OnSizeChanged(EventArgs e)
    {
        if (!isRecalculatingTabSize)
        {
            base.OnSizeChanged(e);

            isRecalculatingTabSize = true;
            RecalculateTabSize();
            isRecalculatingTabSize = false;
        }
    }

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

        mouseDownLocation = e.Location;
    }

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

        for (int i = 0; i < this.TabCount; i++)
        {
            Rectangle tabRec = this.GetTabRect(i);
            Rectangle absoluteCloseArea = new Rectangle()
            {
                Size = closeAreaRec.Size,
                Location = new Point()
                {
                    X = tabRec.X + closeAreaRec.X,
                    Y = tabRec.Y + ItemSize.Height / 4
                }
            };

            MainTabPage page = TabPages[i] as MainTabPage;

            if (absoluteCloseArea.Contains(e.Location))
            {
                page.isMouseOverCloseArea = true;
                Invalidate(absoluteCloseArea);
            }
            else if (page.isMouseOverCloseArea)
            {
                page.isMouseOverCloseArea = false;
                Invalidate(absoluteCloseArea);
            }
        }
    }

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

        for (int i = 0; i < this.TabCount; i++)
        {
            Rectangle tabRec = this.GetTabRect(i);
            Rectangle absoluteCloseArea = new Rectangle()
            {
                Size = closeAreaRec.Size,
                Location = new Point()
                {
                    X = tabRec.X + closeAreaRec.X,
                    Y = tabRec.Y + ItemSize.Height / 4
                }
            };

            if (absoluteCloseArea.Contains(e.Location) && absoluteCloseArea.Contains(mouseDownLocation))
            {
                TabPages.RemoveAt(i);
            }
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        for(int i = 0; i < TabCount; i++)
        {
            MainTabPage mainTabPage = TabPages[i] as MainTabPage;

            if(mainTabPage != null)
            {
                Rectangle tabRec = this.GetTabRect(i);

                // Background
                using (Brush recBrush = new SolidBrush(i == SelectedIndex ?
                    mainTabPage.SelectedTabBackColor : mainTabPage.TabBackColor))
                using (Pen recPen = new Pen(Brushes.Black))
                {
                    e.Graphics.FillRectangle(recBrush, tabRec);
                    e.Graphics.DrawRectangle(recPen, tabRec);
                }


                // Image
                if (mainTabPage.HasImage)
                {
                    using(Image image = (Image)Resource.ResourceManager.GetObject(mainTabPage.ImageName))
                    {
                        e.Graphics.DrawImage(image, new Rectangle(
                            tabRec.Left + imageRec.X,
                            tabRec.Top + imageRec.Y,
                            imageRec.Width,
                            imageRec.Height));
                    }
                }

                // Text
                bool isTabSelected = i == this.SelectedIndex;
                var trimmingResult = TrimString(textRec.Size, Font, mainTabPage.Text, isTabSelected, e.Graphics);

                e.Graphics.DrawString(trimmingResult.text, Font, Brushes.White,
                    tabRec.Left + textRec.X, tabRec.Top + textRec.Y);

                // Close button AND Close Area
                if (mainTabPage.IsClosable && trimmingResult.isCloseButton)
                {
                    if (mainTabPage.isMouseOverCloseArea)
                    {
                        SolidBrush closeAreaColor = (i == this.SelectedIndex)
                            ? new SolidBrush(mainTabPage.SelectedtabCloseAreaColor)
                            : new SolidBrush(mainTabPage.TabCloseAreaColor);

                        e.Graphics.FillRectangle(closeAreaColor, new Rectangle(
                            tabRec.Left + closeAreaRec.X,
                            tabRec.Top + closeAreaRec.Y,
                            closeAreaRec.Width,
                            closeAreaRec.Height));

                        closeAreaColor.Dispose();
                    }

                    e.Graphics.DrawString("x", Font, Brushes.White,
                        tabRec.Left + closeButtonRec.X, tabRec.Top + closeButtonRec.Y);
                }

                // Upperline
                if (mainTabPage.IsUpperLine && isTabSelected)
                {
                    using (Pen pen = new Pen(mainTabPage.SelectedTabUpperLine, 3))
                    {
                        e.Graphics.DrawLine(pen, tabRec.Left + standardOffset, 4, tabRec.Right - standardOffset, 4);
                    }
                }
            }
        }
    }

    #endregion

    #region Helper Methods

    private (string text, bool isCloseButton) TrimString(Size maxSize, Font font, string text, bool isSelected, Graphics g)
    {
        int currentWidth = (int)g.MeasureString(text, font).Width;

        if (currentWidth < textRec.Width)
        {
            return (text, true);
        }
        else
        {
            // Decide if text area merge with close button area size
            int availableWidthForText = isSelected ? textRec.Width : textRec.Width + standardOffset + closeAreaRec.Width;

            while (currentWidth + ellipsisWidth >= availableWidthForText && text.Length > 0)
            {
                text = text.Substring(0, text.Length - 1);
                currentWidth = (int)g.MeasureString(text, font).Width;
            }

            return (string.Concat(text, "..."), false || isSelected);
        }
    }

    public void RecalculateTabSize()
    {
        if (TabPages.Count == 0)
            return;

        int averageTabWidth = this.Size.Width / this.TabPages.Count;

        if (averageTabWidth <= 0) 
            return;

        this.ItemSize = averageTabWidth > DefaultItemSize.Width ?
                    DefaultItemSize : new Size(averageTabWidth - 1, DefaultItemSize.Height);

        // Recalculate text, closeArea and close Button Rectangles
        textRec = new Rectangle(
            textRec.X,
            textRec.Y,
            this.ItemSize.Width - 4 * standardOffset - imageRec.Width - closeAreaRec.Width,
            this.ItemSize.Height / 2);

        closeAreaRec = new Rectangle(
            ItemSize.Width - standardOffset - ItemSize.Height / 2,
            (ItemSize.Height - ItemSize.Height / 2) / 2,
            ItemSize.Height / 2,
            ItemSize.Height / 2);

        closeButtonRec = new Rectangle(
            new Point(
                ItemSize.Width - closeAreaRec.Width - standardOffset + closeButtonRec.Width / 4,
                (ItemSize.Height - closeAreaRec.Height) / 2),
            closeAreaRec.Size);
    }

    #endregion
}

c# winforms drag designer
1个回答
0
投票

我找到了解决方法。

© www.soinside.com 2019 - 2024. All rights reserved.