Best practice to implement Shift+F3 behavior (change word case like MSWord does)

8 posts, 0 answers
  1. Grigory
    Grigory avatar
    7 posts
    Member since:
    Jul 2014

    Posted 25 Jul 2014 Link to this post

    I need to implement "ChangeCase" keyboard shortcut (like Shift+F3 in MS Word) for RadRichTextBox, which changes the text either selected by mouse, or the last word before caret's position. So i can't find best way how to do this. May be i should use the way with selections like:

    DocumentPosition pos = new DocumentPosition(this.radRichTextBox.Document.CaretPosition);
      
    pos.MoveToCurrentWordStart();
    this.radRichTextBox.Document.Selection.SetSelectionStart(pos);
      
    pos.MoveToCurrentWordEnd();
    this.radRichTextBox.Document.Selection.AddSelectionEnd(pos);

    And then work with selected word. Or just replace word in position. 

    The result should be (3 consecutive Shift+F3):
    word --(Shift+F3)-> Word --(Shift+F3)-> WORD --(Shift+F3)-> word
  2. Grigory
    Grigory avatar
    7 posts
    Member since:
    Jul 2014

    Posted 25 Jul 2014 in reply to Grigory Link to this post

    And  i have some questions about entry point for this behavior. Should i use RadRichTextBox.RegisterCommand or just handle OnPreviewKeyDown
  3. UI for WPF is Visual Studio 2017 Ready
  4. Boby
    Admin
    Boby avatar
    595 posts

    Posted 30 Jul 2014 Link to this post

    Hello Grigoy,
    You could use both of the approaches - registering custom command or handling PreviewEditorKeyDown event, for example:
    public partial class MainDemoControl : UserControl
    {
        public static RoutedCommand ToggleCaseRoutedCommand = new RoutedCommand();
     
        public MainDemoControl()
        {
            RadRichTextBox.RegisterCommand(MainDemoControl.ToggleCaseRoutedCommand, Key.F3, ModifierKeys.Shift);
     
            InitializeComponent();
     
            this.radRichTextBox.CommandBindings.Add(new CommandBinding(MainDemoControl.ToggleCaseRoutedCommand, Executed, CanExecute));
        }
     
        private void Executed(object sender, ExecutedRoutedEventArgs e)
        {
            this.radRichTextBox.BeginUndoGroup();
     
            if (this.radRichTextBox.Document.Selection.IsEmpty)
            {
                DocumentPosition pos = new DocumentPosition(this.radRichTextBox.Document.CaretPosition);
     
                pos.MoveToCurrentWordStart();
                this.radRichTextBox.Document.Selection.SetSelectionStart(pos);
     
                pos.MoveToCurrentWordEnd();
                this.radRichTextBox.Document.Selection.AddSelectionEnd(pos);
            }
            else if (this.radRichTextBox.Document.Selection.Ranges.Count > 1)
            {
                // If multirange selection is present, use only the first range.
                DocumentPosition start = new DocumentPosition(this.radRichTextBox.Document.Selection.Ranges.First.StartPosition);
                DocumentPosition end = new DocumentPosition(this.radRichTextBox.Document.Selection.Ranges.First.EndPosition);
     
                this.radRichTextBox.Document.Selection.SetSelectionStart(start);
                this.radRichTextBox.Document.Selection.AddSelectionEnd(end);
            }
     
            string selectedText = this.radRichTextBox.Document.Selection.GetSelectedText();
     
            this.radRichTextBox.Insert(selectedText.ToUpper());
     
            this.radRichTextBox.EndUndoGroup();
        }
     
        private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

    Don't hesitate to contact us if you have other questions.


    Regards,
    Boby
    Telerik
     
    Check out Telerik Analytics, the service which allows developers to discover app usage patterns, analyze user data, log exceptions, solve problems and profile application performance at run time. Watch the videos and start improving your app based on facts, not hunches.
     
  5. Grigory
    Grigory avatar
    7 posts
    Member since:
    Jul 2014

    Posted 31 Jul 2014 in reply to Boby Link to this post

    Thank you. I've already implemented this behavior. Glad to see that my code is quite similar to your version. Telerik controls have friendly API :)
  6. Daniel
    Daniel avatar
    5 posts
    Member since:
    Apr 2015

    Posted 23 Nov 2015 in reply to Boby Link to this post

    Hi,

    This solution works only partially. It works only if you use a shortcut like Shift F3 and it clears the formatting.

    The toggle functionality should change not only to upper but also from all upper to all lower and from lower to first upper. Fortunately I don't need toggle.

    I need just to upper and to lower but not from shortcut but from context menu. Unfortunately if you use context menu the selection is lost. (it is visible, but the statement Document.Selection.IsEmpty returns true). I call it a bug.

    Then there is the lost of formatting if you use

    string selectedText = this.radRichTextBox.Document.Selection.GetSelectedText();
    this.radRichTextBox.Insert(selectedText.ToUpper());

     I replaced it with

    foreach (var box in Document.Selection.GetSelectedBoxes().ToList())
    {
         if (!box.IsFormattingSymbol)
             box.Text = box.Text.ToUpper();
    }

    And it almost works (from shortcut not from context menu). You need to scroll up to hide affected text and then scroll down to redraw it. I tried with Invalidate but no luck. Also undo doesn't work.

     

    So my questions are:

    Is there a way to force document to be redrawn?

    Is there a way to use context menu and have access to selection?

    Regards,

    Daniel

  7. Boby
    Admin
    Boby avatar
    595 posts

    Posted 24 Nov 2015 Link to this post

    Hi Daniel,

    You are right, the solution I proposes was only a workaround, and is not considering formatting of the text.

    The best approach would be to get the selected spans from the selected span layout boxes, modify their text with ToUpper/ToLower where needed and then re-insert the spans. This could be tricky tough, as you would need mapping between the spans and the plain text representation, moreover some of the spans may be only partially selected.

    Using the RadRichTextBox.InsertInine method to insert spans and BeginUndoGroup/EndUndoGroup methods will ensure that the history will be preserved, and Undo/Redo would work as expected. 

    There is a public item in the feedback portal regarding the change case command here:
    Change Case Command
    where you can vote for this feature and track eventual implementation.

    For the context menu, you can customize it (add custom elements) using one of the approaches described in the Context Menu help article. I have performed some tests and the selection seems correctly reported as not empty (for example in a ContextMenu.Showing event handler).

    Regards,
    Boby
    Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
  8. Daniel
    Daniel avatar
    5 posts
    Member since:
    Apr 2015

    Posted 24 Nov 2015 in reply to Boby Link to this post

    Thanks,

    I've fixed the issue with selection.

    I tried to use InsertInline but it works only if you select one whole span.

    As I asked before. IS there a way to force document to be redrawn?

    Regards,

    Daniel

  9. Boby
    Admin
    Boby avatar
    595 posts

    Posted 26 Nov 2015 Link to this post

    Hi Daniel,

    You could, but I would not recommend using the approach with setting the text directly on the layout boxes, because of the following reasons:
    - This relies on an implementation detail - namely that setting text on a layout box would affect the underlying span. For historical reasons the API of the layout model is too "open", exposing properties (like set_Text) which are not very user-friendly and should not be used in normal circumstances.
    - The layout would be only partially invalidated - for example the last words could be trimmed as the parent paragraph layout box is not notified for the change.
    - Setting properties directly on the document model elements (and not through RadRichTextBox or RadDocumentEditor) is not recommended as it could be easy to broke the underlying model (some validation checks are skipped) and moreover it resets the document history.
    - Without more complex logic, you would get a behavior similar to the approach suggested by me (with using InsertInline) - if only parts of the span box are selected, you would change the case of the whole span box.

    Otherwise, the most easiest way to simulate what happens on scroll is to invoke the RecreateUI method of the active document presenter:
    this.radRichTextBox.ActiveEditorPresenter.RecreateUI();

    Regards,
    Boby
    Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
Back to Top
UI for WPF is Visual Studio 2017 Ready