Search code examples
c#winformscombobox

Prevent dropdown of Combo Box collapse before item selected


I want when the button in Pagination is clicked, the dropdown of the Combo Box is still opened, so I can see the result of the pagination in the items of the Combo Box and the dropdown closed when I selected item of Combo Box.

My problem is when I click the buttons in Pagination, the dropdown of the Combo Box closed, so I can't see the result of the pagination.

How to prevent dropdown of Combo Box collapse before item selected in C# Win Form?

CtechComboBoxPagination.cs

using static DiscountCard.View.CustomControl.CtechPagination;

namespace DiscountCard.View.CustomControl
{
    public partial class CtechComboBoxPagination : ComboBox
    {
        public CtechPagination? Pagination { get; set; }

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
                return cp;
            }
        }

        public CtechComboBoxPagination()
        {
            InitializeComponent();

            DoubleBuffered = true;
            DropDownStyle = ComboBoxStyle.DropDownList;
            DrawMode = DrawMode.OwnerDrawFixed;
            Font = new Font("Consolas", 11F, FontStyle.Regular, GraphicsUnit.Point);

            Pagination = new CtechPagination();
            Pagination.SetInitialize(Enumerable.Range(0, 100).AsQueryable(), newContent: true, PaginationPosition.LastPage);
            Pagination.FirstPaginationButton_Click += Pagination_FirstPaginationButton_Click;
            Pagination.PrevPaginationButton_Click += Pagination_PrevPaginationButton_Click;
            Pagination.NextPaginationButton_Click += Pagination_NextPaginationButton_Click;
            Pagination.LastPaginationButton_Click += Pagination_LastPaginationButton_Click;
        }

        private void Pagination_FirstPaginationButton_Click(object? sender, EventArgs e)
        {
            DroppedDown = true;
        }
        private void Pagination_PrevPaginationButton_Click(object? sender, EventArgs e)
        {
            DroppedDown = true;
        }
        private void Pagination_NextPaginationButton_Click(object? sender, EventArgs e)
        {
            DroppedDown = true;
        }
        private void Pagination_LastPaginationButton_Click(object? sender, EventArgs e)
        {
            DroppedDown = true;
        }

        protected override void OnDrawItem(DrawItemEventArgs e)
        {
            e.DrawBackground();

            if (e.Index < 0 || e.Index >= Items.Count)
            {
                return;
            }
            var item = Items[e.Index];
            String? text = item == null ? "(null)" : item.ToString();

            TextRenderer.DrawText(e.Graphics, text, e.Font, e.Bounds, e.ForeColor, TextFormatFlags.Left | TextFormatFlags.VerticalCenter);

            base.OnDrawItem(e);
        }

        protected override void OnDropDown(EventArgs e)
        {
            base.OnDropDown(e);

            if (Pagination == null)
            {
                return;
            }

            // for test
            if (Pagination.CurrentPage != null)
            {
                Items.Clear();
                for (int i = Pagination.CurrentPage.MinIndex; i <= Pagination.CurrentPage.MaxIndex; i++)
                {
                    Items.Add(i);
                }
            }

            Pagination.Margin = new Padding(0);
            Pagination.MinimumSize = new Size(this.Width, 30);

            ToolStripControlHost host = new ToolStripControlHost(Pagination);
            host.Padding = new Padding(0);

            ToolStripDropDown dropdown = new ToolStripDropDown();
            dropdown.Padding = new Padding(0);
            dropdown.Items.Add(host);
            if (this.Items.Count > 0)
            {
                dropdown.Show(this, 0, 3 + this.Height + (this.Items.Count * this.ItemHeight));
            }
        }
    }
}

CtechPagination.cs

using CardData.Ext.Mod.Logger;
using DiscountCard.Ext.Mod.General;
using DiscountCard.View.CustomControl.Pagination;

namespace DiscountCard.View.CustomControl
{
    public partial class CtechPagination : UserControl
    {
        public enum PaginationPosition
        {
            FirstPage,
            PreviousPage,
            NextPage,
            LastPage,
            CurrrentPage
        }

        public CtechPagination()
        {
            InitializeComponent();

            FirstPaginationButton.Click += OnFirstPaginationButton_Click;
            PrevPaginationButton.Click += OnPrevPaginationButton_Click;
            NextPaginationButton.Click += OnNextPaginationButton_Click;
            LastPaginationButton.Click += OnLastPaginationButton_Click;
        }

        public event EventHandler? FirstPaginationButton_Click;
        public event EventHandler? PrevPaginationButton_Click;
        public event EventHandler? NextPaginationButton_Click;
        public event EventHandler? LastPaginationButton_Click;
        protected virtual void OnFirstPaginationButton_Click(object? sender, EventArgs e)
        {
            try
            {
                if (Pages == null)
                {
                    return;
                }
                if (Pages.Count == 0)
                {
                    return;
                }

                PageIndex = 0;

                Page? firstPage = Pages[PageIndex];
                if (firstPage != null)
                {
                    PagesLabel.Text = String.Format("{0:N0} of {1:N0}", firstPage.MinIndex, firstPage.MaxIndex);
                    FirstPage = firstPage;

                    CurrentPage = firstPage;

                    FirstPaginationButton_Click?.Invoke(this, e);
                }
            }
            catch (Exception ex)
            {
                ExLogger.LogException(ex, "");
            }
        }
        protected virtual void OnPrevPaginationButton_Click(object? sender, EventArgs e)
        {
            try
            {
                if (Pages == null)
                {
                    return;
                }
                if (Pages.Count == 0)
                {
                    return;
                }

                --PageIndex;
                if (PageIndex < 0)
                {
                    PageIndex = 0;
                }

                Page? prevPage = Pages[PageIndex];
                if (prevPage != null)
                {
                    PagesLabel.Text = String.Format("{0:N0} of {1:N0}", prevPage.MinIndex, prevPage.MaxIndex);
                    PreviousPage = prevPage;

                    CurrentPage = prevPage;

                    PrevPaginationButton_Click?.Invoke(this, e);
                }
            }
            catch (Exception ex)
            {
                ExLogger.LogException(ex, "");
            }
        }
        protected virtual void OnNextPaginationButton_Click(object? sender, EventArgs e)
        {
            try
            {
                if (Pages == null)
                {
                    return;
                }
                if (Pages.Count == 0)
                {
                    return;
                }

                ++PageIndex;
                if (PageIndex > Pages.Count - 1)
                {
                    PageIndex = Pages.Count - 1;
                }

                Page? nextPage = Pages[PageIndex];
                if (nextPage != null)
                {
                    PagesLabel.Text = String.Format("{0:N0} of {1:N0}", nextPage.MinIndex, nextPage.MaxIndex);
                    NextPage = nextPage;

                    CurrentPage = nextPage;

                    NextPaginationButton_Click?.Invoke(this, e);
                }
            }
            catch (Exception ex)
            {
                ExLogger.LogException(ex, "");
            }
        }
        protected virtual void OnLastPaginationButton_Click(object? sender, EventArgs e)
        {
            try
            {
                if (Pages == null)
                {
                    return;
                }
                if (Pages.Count == 0)
                {
                    return;
                }

                PageIndex = Pages.Count - 1;

                Page? lastPage = Pages[PageIndex];
                if (lastPage != null)
                {
                    PagesLabel.Text = String.Format("{0:N0} of {1:N0}", lastPage.MinIndex, lastPage.MaxIndex);
                    LastPage = lastPage;

                    CurrentPage = lastPage;

                    LastPaginationButton_Click?.Invoke(this, e);
                }
            }
            catch (Exception ex)
            {
                ExLogger.LogException(ex, "");
            }
        }
        public void SetInitialize(IQueryable<int> source, bool newContent, PaginationPosition selectedPagination)
        {
            PageSize = Common.DEFAULT_PAGE_SIZE;

            TotalCount = source.Count();
            if (TotalCount <= 0)
            {
                return;
            }

            TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize);
            if (TotalPages <= 0)
            {
                return;
            }

            if (newContent)
            {
                Pages = new List<Page>();
                for (int i = 0; i < TotalPages; i++)
                {
                    IQueryable<int> indexes = (IQueryable<int>)source.Skip(i * PageSize).Take(PageSize);
                    if (indexes != null)
                    {
                        Page page = new Page();
                        page.MinIndex = 1 + indexes.Min();
                        page.MaxIndex = 1 + indexes.Max();
                        Pages.Add(page);
                    }
                }
            }

            if (Pages != null)
            {
                switch (selectedPagination)
                {
                    case PaginationPosition.FirstPage:
                        if (newContent)
                        {
                            PageIndex = 0;
                        }

                        Page? firstPage = Pages[PageIndex];
                        if (firstPage != null)
                        {
                            PagesLabel.Text = String.Format("{0:N0} of {1:N0}", firstPage.MinIndex, firstPage.MaxIndex);
                            FirstPage = firstPage;

                            CurrentPage = firstPage;
                        }
                        break;

                    case PaginationPosition.PreviousPage:
                        if (newContent)
                        {
                            --PageIndex;
                        }
                        if (PageIndex < 0)
                        {
                            PageIndex = 0;
                        }

                        Page? prevPage = Pages[PageIndex];
                        if (prevPage != null)
                        {
                            PagesLabel.Text = String.Format("{0:N0} of {1:N0}", prevPage.MinIndex, prevPage.MaxIndex);
                            PreviousPage = prevPage;

                            CurrentPage = prevPage;
                        }
                        break;

                    case PaginationPosition.NextPage:
                        if (newContent)
                        {
                            ++PageIndex;
                        }
                        if (PageIndex > Pages.Count - 1)
                        {
                            PageIndex = Pages.Count - 1;
                        }

                        Page? nextPage = Pages[PageIndex];
                        if (nextPage != null)
                        {
                            PagesLabel.Text = String.Format("{0:N0} of {1:N0}", nextPage.MinIndex, nextPage.MaxIndex);
                            NextPage = nextPage;

                            CurrentPage = nextPage;
                        }
                        break;

                    case PaginationPosition.LastPage:
                        if (newContent)
                        {
                            PageIndex = Pages.Count - 1;
                        }
                        if (PageIndex < 0)
                        {
                            PageIndex = 0;
                        }

                        Page? lastPage = Pages[PageIndex];
                        if (lastPage != null)
                        {
                            PagesLabel.Text = String.Format("{0:N0} of {1:N0}", lastPage.MinIndex, lastPage.MaxIndex);
                            LastPage = lastPage;

                            CurrentPage = lastPage;
                        }
                        break;

                    case PaginationPosition.CurrrentPage:
                        if (PageIndex < 0)
                        {
                            PageIndex = 0;
                        }
                        if (PageIndex > Pages.Count - 1)
                        {
                            PageIndex = Pages.Count - 1;
                        }

                        Page? curentPage = Pages[PageIndex];
                        if (curentPage != null)
                        {
                            PagesLabel.Text = String.Format("{0:N0} of {1:N0}", curentPage.MinIndex, curentPage.MaxIndex);
                            CurrentPage = curentPage;
                        }
                        break;
                }
            }
        }

        public int PageIndex { get; private set; }
        public int PageSize { get; private set; }
        public int TotalCount { get; private set; }
        public int TotalPages { get; private set; }
        public List<Page>? Pages { get; private set; }

        public Page? CurrentPage { get; private set; }
        public Page? FirstPage { get; private set; }
        public Page? PreviousPage { get; private set; }
        public Page? NextPage { get; private set; }
        public Page? LastPage { get; private set; }

    }
}

CtechPagination.Designer.cs

namespace DiscountCard.View.CustomControl
{
    partial class CtechPagination
    {
        /// <summary> 
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Component Designer generated code

        /// <summary> 
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.PaginationTableLayoutPanel = new DiscountCard.View.CustomControl.CtechTableLayoutPanel();
            this.FirstPaginationButton = new System.Windows.Forms.Button();
            this.PrevPaginationButton = new System.Windows.Forms.Button();
            this.NextPaginationButton = new System.Windows.Forms.Button();
            this.LastPaginationButton = new System.Windows.Forms.Button();
            this.PagesLabel = new System.Windows.Forms.Label();
            this.PaginationTableLayoutPanel.SuspendLayout();
            this.SuspendLayout();
            // 
            // PaginationTableLayoutPanel
            // 
            this.PaginationTableLayoutPanel.ColumnCount = 9;
            this.PaginationTableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 5F));
            this.PaginationTableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 50F));
            this.PaginationTableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 5F));
            this.PaginationTableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 50F));
            this.PaginationTableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
            this.PaginationTableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 50F));
            this.PaginationTableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 5F));
            this.PaginationTableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 50F));
            this.PaginationTableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 5F));
            this.PaginationTableLayoutPanel.Controls.Add(this.FirstPaginationButton, 1, 0);
            this.PaginationTableLayoutPanel.Controls.Add(this.PrevPaginationButton, 3, 0);
            this.PaginationTableLayoutPanel.Controls.Add(this.NextPaginationButton, 5, 0);
            this.PaginationTableLayoutPanel.Controls.Add(this.LastPaginationButton, 7, 0);
            this.PaginationTableLayoutPanel.Controls.Add(this.PagesLabel, 4, 0);
            this.PaginationTableLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
            this.PaginationTableLayoutPanel.Location = new System.Drawing.Point(0, 0);
            this.PaginationTableLayoutPanel.Name = "PaginationTableLayoutPanel";
            this.PaginationTableLayoutPanel.RowCount = 1;
            this.PaginationTableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
            this.PaginationTableLayoutPanel.Size = new System.Drawing.Size(816, 30);
            this.PaginationTableLayoutPanel.TabIndex = 0;
            // 
            // FirstPaginationButton
            // 
            this.FirstPaginationButton.Dock = System.Windows.Forms.DockStyle.Fill;
            this.FirstPaginationButton.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
            this.FirstPaginationButton.Location = new System.Drawing.Point(8, 3);
            this.FirstPaginationButton.Name = "FirstPaginationButton";
            this.FirstPaginationButton.Size = new System.Drawing.Size(44, 24);
            this.FirstPaginationButton.TabIndex = 0;
            this.FirstPaginationButton.Text = "⟨⟨";
            this.FirstPaginationButton.UseVisualStyleBackColor = true;
            // 
            // PrevPaginationButton
            // 
            this.PrevPaginationButton.Dock = System.Windows.Forms.DockStyle.Fill;
            this.PrevPaginationButton.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
            this.PrevPaginationButton.Location = new System.Drawing.Point(63, 3);
            this.PrevPaginationButton.Name = "PrevPaginationButton";
            this.PrevPaginationButton.Size = new System.Drawing.Size(44, 24);
            this.PrevPaginationButton.TabIndex = 1;
            this.PrevPaginationButton.Text = "⟨";
            this.PrevPaginationButton.UseVisualStyleBackColor = true;
            // 
            // NextPaginationButton
            // 
            this.NextPaginationButton.Dock = System.Windows.Forms.DockStyle.Fill;
            this.NextPaginationButton.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
            this.NextPaginationButton.Location = new System.Drawing.Point(709, 3);
            this.NextPaginationButton.Name = "NextPaginationButton";
            this.NextPaginationButton.Size = new System.Drawing.Size(44, 24);
            this.NextPaginationButton.TabIndex = 2;
            this.NextPaginationButton.Text = "⟩";
            this.NextPaginationButton.UseVisualStyleBackColor = true;
            // 
            // LastPaginationButton
            // 
            this.LastPaginationButton.Dock = System.Windows.Forms.DockStyle.Fill;
            this.LastPaginationButton.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
            this.LastPaginationButton.Location = new System.Drawing.Point(764, 3);
            this.LastPaginationButton.Name = "LastPaginationButton";
            this.LastPaginationButton.Size = new System.Drawing.Size(44, 24);
            this.LastPaginationButton.TabIndex = 3;
            this.LastPaginationButton.Text = "⟩⟩";
            this.LastPaginationButton.UseVisualStyleBackColor = true;
            // 
            // PagesLabel
            // 
            this.PagesLabel.AutoSize = true;
            this.PagesLabel.Dock = System.Windows.Forms.DockStyle.Fill;
            this.PagesLabel.Location = new System.Drawing.Point(113, 0);
            this.PagesLabel.Name = "PagesLabel";
            this.PagesLabel.Size = new System.Drawing.Size(590, 30);
            this.PagesLabel.TabIndex = 4;
            this.PagesLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
            // 
            // CtechPagination
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Controls.Add(this.PaginationTableLayoutPanel);
            this.Margin = new System.Windows.Forms.Padding(0);
            this.Name = "CtechPagination";
            this.Size = new System.Drawing.Size(816, 30);
            this.PaginationTableLayoutPanel.ResumeLayout(false);
            this.PaginationTableLayoutPanel.PerformLayout();
            this.ResumeLayout(false);

        }

        #endregion

        private CtechTableLayoutPanel PaginationTableLayoutPanel;
        private Button FirstPaginationButton;
        private Button PrevPaginationButton;
        private Button NextPaginationButton;
        private Button LastPaginationButton;
        private Label PagesLabel;
    }
}

enter image description here

enter image description here


Solution

  • As I understand it, you've made a UserControl for the pagination and when you click on one of the nav buttons it results in an unwanted close of the drop down.

    user-control

    I was able to reproduce the behavior you're describing and then was able to suppress it by implementing IMessageFilter for the user control in order to intercept the mouse events and mark them as "handled" in the filter (if they occur inside the client rectangle of the UC).

    nav

    public partial class Pagination : UserControl, IMessageFilter
    {
        public Pagination()
        {
            InitializeComponent();
            Application.AddMessageFilter(this);
            Disposed += (sender, e) =>Application.RemoveMessageFilter(this);
            PagesLabel.Text = $"Page {Page} of {Pages}";
        }
    
        const int WM_LBUTTONDOWN = 0x0201;
        const int WM_LBUTTONUP = 0x0202;
        public bool PreFilterMessage(ref Message m)
        {
            var client = PointToClient(MousePosition);
            switch (m.Msg)
            {
                case WM_LBUTTONDOWN:
                    if (ClientRectangle.Contains(client))
                    {
                        IterateControlTree((control) => hitTest(control, client));
                        return true;
                    }
                    break;
                case WM_LBUTTONUP:
                    if(ClientRectangle.Contains(client))
                    {
                        return true;
                    }
                    break;
            }
            return false;
        }
        .
        .
        .
    }
    

    To be clear, this means no Button.Click events are going to take place either! For this reason it's necessary to iterate the Control tree of the user control to perform a hit test on the button controls it contains.

    bool IterateControlTree(Func<Control, bool> action, Control control = null)
    {
        if (control == null) control = this;
        if(action(control)) return true;
        foreach (Control child in control.Controls)
        {
            if(IterateControlTree(action, child))
            {
                return true;
            }
        }
        return false;
    }
    

    For example, detecting a click in the client rectangle of one of the buttons could modify a Page property which in turn fires an event for the combo box to react.

    private bool hitTest(Control control, Point client)
    {
        if (control.Bounds.Contains(client))
        {
            switch (control.Name)
            {
                case nameof(FirstPaginationButton): Page = 1; return true;
                case nameof(PrevPaginationButton): Page --; return true;
                case nameof(NextPaginationButton): Page ++; return true;
                case nameof(LastPaginationButton): Page = Pages; return true;
                default: break;
            }
        }
        return false;
    }
    

    Feel free to browse or clone the code I wrote to test this answer if that would be helpful.