This is a migrated thread and some comments may be shown as answers.

Adding support for #-tagging with popup menu

3 Answers 60 Views
RichTextBox (obsolete as of Q3 2014 SP1)
This is a migrated thread and some comments may be shown as answers.
Thomas Bargholz
Top achievements
Rank 1
Iron
Thomas Bargholz asked on 16 Oct 2013, 11:38 AM
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

3 Answers, 1 is accepted

Sort by
0
George
Telerik team
answered on 21 Oct 2013, 10:52 AM
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 >>
0
Thomas Bargholz
Top achievements
Rank 1
Iron
answered on 21 Oct 2013, 01:52 PM
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
0
George
Telerik team
answered on 23 Oct 2013, 11:33 AM
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 >>
Tags
RichTextBox (obsolete as of Q3 2014 SP1)
Asked by
Thomas Bargholz
Top achievements
Rank 1
Iron
Answers by
George
Telerik team
Thomas Bargholz
Top achievements
Rank 1
Iron
Share this question
or