0

Created a custom intellisense textbox (textbox with listbox a child). As shown in below image, the listbox pops up when i enter a char which all works fine and good but when i am at the end of textbox the listbox is partially visible, is there anyway i can show the whole listbox content?

enter image description here

Tried this "Show control inside user control outside the boundaries of its parent

But when the popup window opens the text box looses focus and i cannot type anything further, my intellisense textbox keeps giving better results based on what they type but in this situation i am not able to type anymore.

FYI tried to add pParentControl.Focus() into show method defined in other article as shown below, missing something?

 public void Show(Control pParentControl)
        {
            if (pParentControl == null) return;

            // position the popup window
            var loc = pParentControl.PointToScreen(new Point(0, pParentControl.Height));
            pParentControl.Focus();
            m_tsdd.Show(loc);

        }

Here is the complete code

class TextBox_AutoComplete : TextBox
    {
        #region Class Members
        List<string> dictionary;
        ListBox listbox = new ListBox();
        #endregion

        private PopupHelper m_popup;

        #region Extern functions
        [DllImport("user32")]
        private extern static int GetCaretPos(out Point p);
        #endregion

        #region Constructors

        public TextBox_AutoComplete() : base()
        {
            this.Margin = new Padding(0, 0, 0, 0);
            this.Multiline = true;
            this.Dock = DockStyle.Fill;
            this.KeyDown += Textbox_KeyDown;
            this.KeyUp += Textbox_KeyUp;            
            listbox.Parent = this;
            listbox.KeyUp += List_OnKeyUp;
            listbox.Visible = false;
            this.dictionary = new List<string>();

        }
        #endregion

        #region Properties

        public List<string> Dictionary
        {
            get { return this.dictionary; }
            set { this.dictionary = value; }
        }
        #endregion

        #region Methods


        private static string GetLastString(string s)
        {
            Regex rgx = new Regex("[^a-zA-Z0-9_.\\[\\]]");
            s = rgx.Replace(s, " ");
            string[] strArray = s.Split(' ');
            return strArray[strArray.Length - 1];
        }

        protected override void OnTextChanged(EventArgs e)
        {
            base.OnTextChanged(e);
            Point cp;
            GetCaretPos(out cp);
            List<string> lstTemp = new List<string>();
            List<string> TempFilteredList = new List<string>();
            string LastString = GetLastString(this.Text.Substring(0, SelectionStart));

            //MessageBox.Show(LastString);

            /*seperated them so that column name matches are found first*/
            TempFilteredList.AddRange(dictionary.Where(n => n.Replace("[", "").ToUpper().Substring(n.IndexOf(".") > 0 ? n.IndexOf(".") : 0).StartsWith(LastString.ToUpper())
                                                        ).Select(r => r)
                                                        .ToList());

            TempFilteredList.AddRange(dictionary.Where(n => n.Replace("[", "").ToUpper().StartsWith(LastString.ToUpper())
                                                            || n.ToUpper().StartsWith(LastString.ToUpper()))
                                                .Select(r => r)
                                                .ToList());

            lstTemp = TempFilteredList.Distinct().Select(r => r).ToList();
            /*Getting max width*/
            int maxWidth = 0, temp = 0;
            foreach (var obj in lstTemp)
            {
                temp = TextRenderer.MeasureText(obj.ToString(), new Font("Arial", 10, FontStyle.Regular)).Width;
                if (temp > maxWidth)
                {
                    maxWidth = temp;
                }
            }
            listbox.SetBounds(cp.X + 20, cp.Y + 20, maxWidth, 60);

            if (lstTemp.Count != 0 && LastString != "")
            {
                listbox.DataSource = lstTemp;
                // listbox.Show();
                if (m_popup == null)
                    m_popup = new PopupHelper(listbox);
                m_popup.Show(this);
            }
            else if (m_popup != null)
            {
                //listbox.Hide();
                m_popup.Hide();
            }
        }

        protected void Textbox_KeyUp(object sender, KeyEventArgs e)
        {
             if (e.KeyCode == Keys.Down)
            {
                if (listbox.Visible == true)
                {
                    listbox.Focus();
                }                
                e.Handled = true;
            }
            else if (e.KeyCode == Keys.Escape)
            {
                listbox.Visible = false;
                e.Handled = true;
            }


        }

        protected void Textbox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Space && listbox.Visible == true)
            {
                listbox.Focus();
                List_OnKeyUp(listbox, new KeyEventArgs(Keys.Space));
                e.Handled = true;
            }

            if (e.KeyCode == Keys.Down && listbox.Visible == true)
            {
                listbox.Focus();
                e.Handled = true;
            }

        }

        private void List_OnKeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Space || e.KeyCode == Keys.Enter)
            {    
                int Selection_Start = this.SelectionStart;
                string StrLS = GetLastString(this.Text.Substring(0, Selection_Start));
                this.Select(Selection_Start - StrLS.Length, StrLS.Length);
                // MessageBox.Show(this.Selection_Start.ToString() + " Last string" + StrLS);
                this.SelectedText=((ListBox)sender).SelectedItem.ToString();
                listbox.Hide();
                this.Focus();

            }

        }


        #endregion

    }


 public sealed class PopupHelper : IDisposable
    {
        private readonly Control m_control;
        private readonly ToolStripDropDown m_tsdd;
        private readonly Panel m_hostPanel; // workarround - some controls don't display correctly if they are hosted directly in ToolStripControlHost

        public PopupHelper(Control pControl)
        {
            m_hostPanel = new Panel();
            m_hostPanel.Padding = Padding.Empty;
            m_hostPanel.Margin = Padding.Empty;
            m_hostPanel.TabStop = false;
            m_hostPanel.BorderStyle = BorderStyle.None;
            m_hostPanel.BackColor = Color.Transparent;

            m_tsdd = new ToolStripDropDown();
            m_tsdd.CausesValidation = false;

            m_tsdd.Padding = Padding.Empty;
            m_tsdd.Margin = Padding.Empty;
            m_tsdd.Opacity = 0.9;

            m_control = pControl;
            m_control.CausesValidation = false;
            m_control.Resize += MControlResize;
            //m_hostPanel.Controls.Add(m_control);

            m_tsdd.Padding = Padding.Empty;
            m_tsdd.Margin = Padding.Empty;

            m_tsdd.MinimumSize = m_tsdd.MaximumSize = m_tsdd.Size = pControl.Size;

            m_tsdd.Items.Add(new ToolStripControlHost(m_control));
        }

        private void ResizeWindow()
        {
            m_tsdd.MinimumSize = m_tsdd.MaximumSize = m_tsdd.Size = m_control.Size;
            m_hostPanel.MinimumSize = m_hostPanel.MaximumSize = m_hostPanel.Size = m_control.Size;
        }

        private void MControlResize(object sender, EventArgs e)
        {
            ResizeWindow();
        }

        /// <summary>
        /// Display the popup and keep the focus
        /// </summary>
        /// <param name="pParentControl"></param>
        public void Show(Control pParentControl)
        {
            if (pParentControl == null) return;

            // position the popup window
            var loc = pParentControl.PointToScreen(new Point(0, pParentControl.Height));
            pParentControl.Focus();
            m_tsdd.Show(loc);

        }


        public void Hide()
        {
            m_tsdd.Hide();
        }
        public void Close()
        {
            m_tsdd.Close();
        }

        public void Dispose()
        {
            m_control.Resize -= MControlResize;

            m_tsdd.Dispose();
            m_hostPanel.Dispose();
        }
    }
sam
  • 345
  • 2
  • 4
  • 18
  • Of course there is a way, but the "way" varies depending on your code. Consider posting your code if you want a more detailed answer. – Racil Hilan Jan 26 '18 at 15:55
  • 2
    Hard to do but you can cheat: Possible duplicate of [Show control inside user control outside the boundaries of its parent](https://stackoverflow.com/questions/12199235/show-control-inside-user-control-outside-the-boundaries-of-its-parent) – Alex K. Jan 26 '18 at 15:56
  • @RacilHilan thanks for -1, its a simple question. control inside another control do you expect me to post this? TextBox Txt = new TextBox(); ListBox listbox = new ListBox(); listbox.Parent = Txt; I think anyone with basic knowledge knows that. – sam Jan 26 '18 at 16:07
  • @alexK i think that should do the trick,let me try – sam Jan 26 '18 at 16:07
  • Sam, yes, that code is basic and not really required here. What we need is the code that shows the listbox, because changing the direction of showing the listbox is another way to solve your issue (This is how it is commonly done and better way than showing the listbox outside its parent's boundaries). Alex gave you a link that answers your question, and you could've found that answer by yourself with a quick search, so downvoted for the lack of code and research efforts. – Racil Hilan Jan 26 '18 at 16:18
  • Try [How to create drop down information box in C# Winforms?](https://stackoverflow.com/q/21288431/719186) – LarsTech Jan 26 '18 at 23:25
  • @larsTech ToolStripDropDown is working as expected but my problem is its not allowing me to type anymore into textbox after ToolStripDropDown is visible – sam Jan 27 '18 at 16:14
  • @RacilHilan added the complete code as suggested, i cant do the direction as my textbox can be single row. – sam Jan 29 '18 at 15:28
  • If the parent box is too small to fit the child box inside, you'll have to break the relationship and make them separate controls. See my answer. – Racil Hilan Jan 29 '18 at 17:23

1 Answers1

0

Firstly, I personally don't see any benefit in having a control inside another. Yes, the child control is locked inside its parent's boundaries automatically for you, but this benefit is negated by the issue that you're facing, and solving that issue requires the same work as when the two controls have no relation. In both cases, you'll have to do the calculations manually to keep the child visible inside its parent. In the second case the parent is the app's window.

Secondly, I don't recommend using hacks like the one mentioned in the comments to show the child outside its parent's boundaries. The hack creates more issues than it solves, as you found out. And what's the point of that hack anyway? If you want to show the child outside the parent, then don't make it a child control in the first place, and you don't need any hack.

The best solution is the one that you find in any well designed app, and in Windows itself. Open any app, let's say Notepad, and right-click near the upper-left corner. You'll see the context menu pulling to lower-right direction. Now right-click near the other three corners and you'll see the context menu pulling in different direction each time, so it will always be visible inside the app. Now if you resize the app window too small and right-click, the context menu will choose the best direction but some of it will be outside the app because the window is too small. That's why you need your list not to be a child, but it's up to you, and it's only about these edge cases. The solution will be similar in both cases.

You're displaying the list in this line:

listbox.SetBounds(cp.X + 20, cp.Y + 20, maxWidth, 60);

The key is cp.X and cp.Y. This is what decides where the list will appear. You need to make this point dynamic and responsive to the boundaries of the parent. You fixed the width to maxWidth and height to 60, so I will use those values in the calculation.

To make sure the list will not go beyond the bottom:

var y = this.Height < cp.Y + 60 ? this.Height - 60 : cp.Y;

To make sure the list will not go beyond the right:

var x = this.Width < cp.X + maxWidth ? this.Width - maxWidth : cp.X;

Now you can show your list at the calculated point:

listbox.SetBounds(x, y, maxWidth, 60);

Notes:

  • I didn't include the 20 gap that you used. I think it looks better without the gap and I haven't seen any app that has a gap. If you prefer the gap, add it to the calculation of x and y. Don't add it in the SetBounds() or that will screw up the calculation.

  • The calculation above doesn't take into account when the parent size is too small to show the child inside. If you want to support that edge case, you need to make the child a separate control and add some checks to the calculation.

Racil Hilan
  • 24,690
  • 13
  • 50
  • 55
  • I use this textbox in multiple places including tablelayoutpanel, so i cannot really add listbox control outside (i mean i can add a panel and then add textbox and list into it and toggle the visibility of lisbox) but i need to add it in too many places which is not viable. So how is the visual studio doing it? code page (window) where we write our .NET code is different from solution explorer window but when we are at the right most corner of code window, the intelisense shows the listbox on top of solution explore, how is that being done? – sam Jan 29 '18 at 21:04
  • In most apps, if not all, it's a completely separate control, not a child of anything. In some apps, it is even a completely separate form (i.e. window). I think in Visual Studio they did it similar to a context menu. It is definitely not a list control. You can do that if you like, create a form, add a list control on it, make it fill the entire form, make the form borderless, and now you can show the form using the same numbers we used for the `SetBounds()` function. The only thing you need to worry about is managing the focus and keeping it on the top of your form. – Racil Hilan Jan 29 '18 at 21:45
  • What are you using the list for, anyway? If it is just for autocomplete, you can do it in a simple combo-box and need not worry about all of that. – Racil Hilan Jan 29 '18 at 21:47
  • simple combo box wouldn't work,even for single row i need to allow multiple autocomplete values in that line, also i need it to be multiple line, which as far as i know is not allowed in combo box. Agree,focus is the one which is causing issue,looks like i am out of options,I guess i will have remove autocomplete feature for single line all together. – sam Jan 31 '18 at 02:01
  • The standard `TextBox` can give you multiline suggestions. [See here](http://www.c-sharpcorner.com/UploadFile/deepak.sharma00/autosuggest-textbox-from-database-column-in-windows-forms/). – Racil Hilan Jan 31 '18 at 02:13
  • Textbox gives multiline suggestions but the autocomplete feature only works for single line.i guess i can have 2 sets of text boxes, one for multiline and another for single line. i think thats the best we can do – sam Jan 31 '18 at 17:11