Adding support for #-tagging with popup menu

4 posts, 0 answers
  1. Thomas Bargholz
    Thomas Bargholz avatar
    7 posts
    Member since:
    Oct 2006

    Posted 16 Oct 2013 Link to this post

    Hi,

    I'm trying to add a twitter style #-tagging feature to a Winforms RadRichTextBox, where, when the user enters "#", my code must automatically detect it, and convert the entered text to a link when the user ends the word, by entering a periode/comma/semicolon, a Space a line feed or switches caret position away from the entered word.

    I would also like to add support for a popup menu/list, which displays the best matches to what the user have entered, based on previously used #-tags. The user can then select a tag from the popup list and have it inserted as a #-tag link, or continue typing, if none of the displayed #-tags are valid.

    Checking the caret position and getting when the user enters "#" is easy enough, but detecting when the user then enters a " " to get the tag, I can't get that to work. I've also tried some popup context menu for displaying the list of available at the current caret position, but that opens with focus, so if the user keeps typing, the typing is ignored.

    I really need some pointers and samples to get me in the right direction, so any help is much appriciated.

    Best regards,
    Thomas

  2. George
    Admin
    George avatar
    500 posts

    Posted 21 Oct 2013 Link to this post

    Hi Thomas,

    Thank you for contacting us.

    For this purpose I have created a class which can manage the hash tags and the menu. The class can be found below:
    public class HashTagBuilder
    {
        private StringBuilder sb = new StringBuilder();
        private RadRichTextBox richTextBox;
        private bool hashTagEntered;
        private List<string> hashTagsHistory = new List<string>();
        private RadDropDownMenu menu = new RadDropDownMenu();
        private bool menuPopupShown;
     
        public HashTagBuilder(RadRichTextBox richTextBox)
            {
                this.richTextBox = richTextBox;
            }
     
        public void StartListening()
            {
                sb.Clear();
                this.richTextBox.KeyPress += RichTextBox_KeyPress;
                this.richTextBox.KeyDown += richTextBox_KeyDown;
            }
     
        public void StopListening()
            {
                this.richTextBox.KeyDown -= richTextBox_KeyDown;
                this.richTextBox.KeyPress -= RichTextBox_KeyPress;
            }
     
        private void richTextBox_KeyDown(object sender, KeyEventArgs e)
            {
                if (this.hashTagEntered)
                {
                    if (e.KeyData == Keys.Enter)
                    {
                        this.RemoveLastWord();
                        this.InsertLink();
                        e.SuppressKeyPress = true;
                        this.ClosePopup();
                    }
                    else if (e.KeyData == Keys.Space)
                    {
                        e.SuppressKeyPress = true;
                        this.menu.Items.Clear();
                        if (this.hashTagsHistory.Count == 0)
                        {
                            return;
                        }
     
                        for (int i = this.hashTagsHistory.Count - 1; i > this.hashTagsHistory.Count - 5 && i >= 0; i--)
                        {
                            menu.Items.Add(new RadMenuItem
                            {
                                Text = this.hashTagsHistory[i]
                            });
                            menu.Items.Last().Click += OldTagClick;
                        }
     
                        menu.Show(this.richTextBox,
                            new Point((int)this.richTextBox.Document.CaretPosition.Location.X, (int)this.richTextBox.Document.CaretPosition.Location.Y));
                        this.menuPopupShown = true;
                        this.richTextBox.Focus();
                    }
                }
            }
     
        private void OldTagClick(object sender, EventArgs e)
        {
            this.RemoveLastWord();
            this.InsertLink(((RadMenuItem)sender).Text);
        }
     
        private void ClosePopup()
            {
                if (this.menuPopupShown)
                {
                    this.menu.ClosePopup(RadPopupCloseReason.Keyboard);
                    this.menuPopupShown = false;
                }
            }
     
        private void RemoveLastWord()
            {
                this.richTextBox.Document.CaretPosition.MoveToCurrentWordStart();
                this.richTextBox.Document.Selection.SetSelectionStart(this.richTextBox.Document.CaretPosition);
                this.richTextBox.Document.CaretPosition.MoveToCurrentWordEnd();
                this.richTextBox.Document.Selection.AddSelectionEnd(this.richTextBox.Document.CaretPosition);
                this.richTextBox.Delete(false);
            }
     
        private void RichTextBox_KeyPress(object sender, KeyPressEventArgs e)
            {
                if (this.hashTagEntered)
                {
                    e.Handled = true;
                    if (e.KeyChar == '.' || e.KeyChar == ',' || e.KeyChar == ';')
                    {
                        this.RemoveLastWord();
                        this.InsertLink();
                    }
                    else
                    {
                        sb.Append(e.KeyChar);
                    }
                }
                else if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift && e.KeyChar == '#')
                {
                    this.hashTagEntered = true;
                    this.sb.Append(e.KeyChar);
                }
     
                this.ClosePopup();
            }
     
        protected virtual void InsertLink(string link = null)
            {
                this.hashTagEntered = false;
                string text = link ?? sb.ToString();
                sb.Clear();
     
                HyperlinkInfo info = new HyperlinkInfo
                {
                    IsAnchor = false,
                    NavigateUri = text,
                    Target = HyperlinkTargets.Blank
                };
     
                this.richTextBox.InsertHyperlink(info, text);
                this.hashTagsHistory.Add(text);
            }
    }

    Its constructor requires an instance of RadRichTextBox which will be managed. Hashtags will be inserted on the keys you mentioned. The history menu will be shown if there is history on Space press after a hashtag has been entered. Please note that none of the above functionality will work if a hashtag has not been entered. In order to get the class running just call the StartListening method.

    You can further customize it according to your needs as this is the base functionality which I think covers all the things you mentioned.

    I hope this will help.

    Regards,
    George
    Telerik
    TRY TELERIK'S NEWEST PRODUCT - EQATEC APPLICATION ANALYTICS for WINFORMS.
    Learn what features your users use (or don't use) in your application. Know your audience. Target it better. Develop wisely.
    Sign up for Free application insights >>
  3. UI for WinForms is Visual Studio 2017 Ready
  4. Thomas Bargholz
    Thomas Bargholz avatar
    7 posts
    Member since:
    Oct 2006

    Posted 21 Oct 2013 Link to this post

    Hi George,
    that is absolutely brilliant - that was exactly the feature I was looking for, and it's so easy to customize to my purpose.
    Thanks you very much! Perfect support :-)
     Regards,
    Thomas
  5. George
    Admin
    George avatar
    500 posts

    Posted 23 Oct 2013 Link to this post

    Hi Thomas,

    I am glad that I could help.

    Do not hesitate to contact us again in case you need any additional assistance.

    Regards,
    George
    Telerik
    TRY TELERIK'S NEWEST PRODUCT - EQATEC APPLICATION ANALYTICS for WINFORMS.
    Learn what features your users use (or don't use) in your application. Know your audience. Target it better. Develop wisely.
    Sign up for Free application insights >>
Back to Top