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

RadTextBoxControl Block Creation and Selection

3 Answers 230 Views
TextBox
This is a migrated thread and some comments may be shown as answers.
Holger
Top achievements
Rank 1
Holger asked on 10 Dec 2018, 12:38 PM

     I want to let the user to color some words or phrases in a Textbox.

I thought the features of RadTextBoxControl would be good for that. (In between RadTextBox and RichTextEditor)

First handicap, I seem to have no influence on Block Creation, meaning on how the text is split into Blocks.

Than I assign a text like 'Dvoraks "New World" Symphony'

I get Blocks like

  • Dvoraks
  • "New
  • World"
  • Symphony

Not really what I wanted.

I found no other way than this:

radTextBoxControl1.TextBoxElement.ViewElement.Children.Add(new TextBlockElement() {Text = "Dvoraks"});
radTextBoxControl1.TextBoxElement.ViewElement.Children.Add(new TextBlockElement() {Text = "\"New World\""});
radTextBoxControl1.TextBoxElement.ViewElement.Children.Add(new TextBlockElement() {Text = "Symphony"});

Is this a good way, the recommanded way, to do custom splitting ?

Next: I could not attach a click event to a text block.

I found your Demo with a Button inside a TextBox (implementing ITextBlock on RadButtonElement) this works fine.

But something like this, is just never called (replacing the "new TextBoxElement" with "new TBE" in the above snippet of course)

public class TBE : TextBlockElement
    {
        protected override void OnClick(EventArgs e)
        {
            base.OnClick(e);
        }
    }

 

Than I tried to subscribe to the Click-Event directly. (The Click Event of the TextBlockElement). No catched event either.

Another try:

radTextBoxControl1.TextBoxElement.MouseDown += TextBoxElement_MouseDown;
 
private void TextBoxElement_MouseDown(object sender, MouseEventArgs e)
        {
            var clickedElement = radTextBoxControl1.ElementTree.GetElementAtPoint(e.Location) as ITextBlock;
            if (clickedElement != null)
            {
            }
        }

 

No success either: The GetElementAtPoint always returns the TextBoxElement, which is not wrong, but I want a children of it, a TextBlock.

 

So I need help on how to catch a click on a TextBlock,... than finally I can color my Block on user click (with setting textblock.ForeColor = Red f.e.).

 

Finally, what I'am missing also is Selecting a TextBlock. The Select Functions of TextBoxControl are all character based.

But has the TextBlock information about it's own position in the whole Text ? Is this the Offset/Length Property  ? Documentation don't gives me any clue, if this is something measured in pixel, number of characters, or whatever.

So how do I something like textBoxControl1.Select(textblock) ?

 

 

 

 

3 Answers, 1 is accepted

Sort by
0
Hristo
Telerik team
answered on 11 Dec 2018, 10:47 AM
Hi Holger,

The text is separated into blocks when the input contains new lines or when some special symbols are appended.
  • whitespace
  • tab - '\t'
  • line feed - '\n'
  • carriage return - '\r'

Custom splitting is not currently supported.  Please also note that the Children collection is manipulated internally by the control. In fact, your first code snippet will ultimately create a single text block with the following text: "New World "Symphony.

About the click event, you can handle the MouseDown event of RadTextBoxControl and depending on the location extract the text block element under the mouse: 
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
         
        this.radTextBoxControl1.MouseDown += TextBoxElement_MouseDown;
    }
 
    private void TextBoxElement_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Middle)
        {
            RadTextBoxControlElement element = ((RadTextBoxControl)sender).TextBoxElement;
            TextPosition position = element.Navigator.GetPositionFromPoint(e.Location);
            LineInfo line = position.Line;
            TextBlockElement textBlock = position.TextBlock as TextBlockElement;
            if (textBlock != null)
            {
                // Change the fore color depending on the textBloxk.Text value
                textBlock.ForeColor = Color.Red;
            }
        }
    }
}

Regarding the selection, it will be necessary to calculate the start and end TextPosition of the text block element you would like to select. You can check my code snippet below with a possible implementation: 
private void button1_Click(object sender, EventArgs e)
{
    this.radTextBoxControl1.Focus();
 
    TextBlockElement textBlock = this.radTextBoxControl1.TextBoxElement.ViewElement.Children[2] as TextBlockElement;
    TextPosition start = this.GetWordStartPosition(textBlock);
    TextPosition end = this.GetWordEndPosition(textBlock);
    this.radTextBoxControl1.TextBoxElement.Navigator.Select(start, end);
}
 
private TextPosition GetWordStartPosition(ITextBlock currentBlock)
{
    TextBoxViewElement viewElement = this.radTextBoxControl1.TextBoxElement.ViewElement;
    bool isTabOrWhitespace = TextBoxViewElement.IsTabOrWhitespace(currentBlock.Text);
    bool hasWord = false;
 
    while (currentBlock.Index > 0)
    {
        ITextBlock prevBlock = viewElement.Children[currentBlock.Index - 1] as ITextBlock;
        bool isPrevTabOrWhitespace = TextBoxViewElement.IsTabOrWhitespace(prevBlock.Text);
        bool isCarrigeReturn = TextBoxViewElement.IsCarriageReturn(prevBlock.Text);
        bool isLineFeed = TextBoxViewElement.IsLineFeed(prevBlock.Text);
 
        if (isCarrigeReturn || isLineFeed)
        {
            break;
        }
 
        if (isTabOrWhitespace)
        {
            if (hasWord && isPrevTabOrWhitespace)
            {
                break;
            }
 
            if (!hasWord)
            {
                hasWord = !isPrevTabOrWhitespace;
            }
        }
        else
        {
            if (isPrevTabOrWhitespace)
            {
                break;
            }
        }
 
        currentBlock = prevBlock;
    }
 
    LineInfo currentLine = viewElement.Lines.BinarySearchByBlockIndex(currentBlock.Index);
    return new TextPosition(currentLine, currentBlock, 0);
}
 
private TextPosition GetWordEndPosition(ITextBlock currentBlock)
{
    TextBoxViewElement viewElement = this.radTextBoxControl1.TextBoxElement.ViewElement;
    bool isTabOrWhitespace = TextBoxViewElement.IsTabOrWhitespace(currentBlock.Text);
    bool hasWhitespace = false;
 
    while (currentBlock.Index < viewElement.Children.Count - 1)
    {
        ITextBlock nextBlock = viewElement.Children[currentBlock.Index + 1] as ITextBlock;
        bool isNextTabOrWhitespace = TextBoxViewElement.IsTabOrWhitespace(nextBlock.Text);
        bool isCarrigeReturn = TextBoxViewElement.IsCarriageReturn(nextBlock.Text);
        bool isLineFeed = TextBoxViewElement.IsLineFeed(nextBlock.Text);
 
        if (isCarrigeReturn || isLineFeed)
        {
            break;
        }
 
        if (isTabOrWhitespace)
        {
            if (!isNextTabOrWhitespace)
            {
                break;
            }
        }
        else
        {
            if (hasWhitespace && !isNextTabOrWhitespace)
            {
                break;
            }
 
            if (!hasWhitespace)
            {
                hasWhitespace = isNextTabOrWhitespace;
            }
        }
 
        currentBlock = nextBlock;
    }
 
    LineInfo currentLine = viewElement.Lines.BinarySearchByBlockIndex(currentBlock.Index);
    return new TextPosition(currentLine, currentBlock, currentBlock.Length);
}

Alternatively, to using the RadTextBoxControl, you can consider RadRichTextEditor which supports rich text and it has an API allowing easier navigation: https://docs.telerik.com/devtools/winforms/controls/richtexteditor/overview.

I hope this will help. Let me know if you need further assistance.

Regards,
Hristo
Progress Telerik
Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
0
Holger
Top achievements
Rank 1
answered on 11 Dec 2018, 11:19 AM

Thanks for your effort, it helps  understand the internals,

but if I don't have any control on what is considered a TextBlock, this might be the wrong approach for my problem.

Somewhere else I found the hint to use a LinkLabel, since this has the feature to highlight several character sequences in color, and has the click handling already included.

RichTextEditor is universal, but might be kind of overkill, just to assign information to a word, like "this is a noun" or "this is a string"

Actually, something like a colored source code Editor, would be perfect. Still Ascii, but with very little highlights.

(I just write to give other readers, ideas what to consider. This is not a question, unless you got new ideas, by my comment).

 

 

 

0
Hristo
Telerik team
answered on 12 Dec 2018, 08:36 AM
Hi Holger,

RadLabel with HTML-like formatted text could also be a possible solution. You can try handling the MouseDown event to extract the formatted text block under the mouse. Then you can easily change its fore and back colors: 
public partial class RadForm1 : Telerik.WinControls.UI.RadForm
{
    public RadForm1()
    {
        InitializeComponent();
 
        this.radLabel1.Text = "<html><size=12>This is RadLabel <br><b><font=Arial>Arial, Bold</b><br><i><color= Red><font=Times New Roman>Times, Italic <u>Underline</u><br><size=9>Size = 9<br><color= 0, 0, 255>Sample Text";
 
        this.radLabel1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.radLabel1_MouseDown);
    }
 
    private FormattedText IsMouseOverBlock(FormattedTextBlock textBlock, MouseEventArgs e)
    {
        Point elementAtPoint = this.radLabel1.LabelElement.PointFromControl(e.Location);
        int linesCount = textBlock.Lines.Count;
        for (int i = 0; i < linesCount; ++i)
        {
            TextLine textLine = textBlock.Lines[i];
            int textLineCount = textLine.List.Count;
            for (int j = 0; j < textLineCount; ++j)
            {
                FormattedText formattedText = textLine.List[j];
                if (!string.IsNullOrEmpty(formattedText.Text) && formattedText.DrawingRectangle.Contains(elementAtPoint))
                {
                 
                    return formattedText;//found link under mouse
                }
            }
        }
 
        return null;//notfound
    }
 
    private void radLabel1_MouseDown(object sender, MouseEventArgs e)
    {
        FieldInfo pi = this.radLabel1.LabelElement.Children[2].Children[1].GetType().GetField("textPrimitiveImpl", BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.NonPublic);
        TextPrimitiveHtmlImpl text = (TextPrimitiveHtmlImpl)pi.GetValue(this.radLabel1.LabelElement.Children[2].Children[1]);
 
 
        FormattedTextBlock textBlock = text.TextBlock;
        FormattedText formattedText = IsMouseOverBlock(textBlock, e);
        if (formattedText != null)
        {
            formattedText.FontColor = Color.Yellow;
            formattedText.BgColor = Color.FromArgb(50, Color.Blue);
        }
    }
}

I am also attaching a short video showing the result on my end. I hope this will help.

Regards,
Hristo
Progress Telerik
Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
Tags
TextBox
Asked by
Holger
Top achievements
Rank 1
Answers by
Hristo
Telerik team
Holger
Top achievements
Rank 1
Share this question
or