-
Notifications
You must be signed in to change notification settings - Fork 1.4k
TextToolbar Control for Editing RichEditBox #1116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TextToolbar Control for Editing RichEditBox #1116
Conversation
…ould now not overwrite my other PR.
|
@WilliamABradley, |
|
Things to remember with formatting is it's more than just formatting selection. If I hit the bold button or Ctrl-B it should make all of the new text I type bold until I turn it off. Is this handled? |
| } | ||
|
|
||
| private void EditZone_TextChanged(object sender, RoutedEventArgs e) | ||
| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to change the preview text every time the text changes, only need to change when the pivot changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@skendrot fair enough
|
|
||
| namespace Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats | ||
| { | ||
| public enum Format |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think tying the control to these types makes sense. I would rather see it fire events for the buttons like BoldActivated/BoldDeactivated or FormatActivated event with a Type parameter of "Bold". Then controls could be updated to support the toolbar. Of course we cannot change the RichEditBox, so maybe it can support that out-of-the box
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@skendrot the Reason for the Formatter class, is that different formats require different sets of buttons. E.g. RichText supports Underline, while Markdown doesn't. Therefore, RichText has an underline button and Markdown doesn't. This also allows users to define their own Formatter class with their own styling and Default Button map.
Perhaps the abstract methods can be removed, and attached to button directly, when someone adds a CommonButton to their ButtonMap?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood. All the more reason not to tie the toolbar to any particular controls and instead allow for buttons to removed and/or added and fire events when they are activated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would mean that the user would have to add their own code to achieve the same functionality, which would defeat the point of having a control that is universal as per discussion at #1107. If not using a formatter class, how do you propose supporting both Rich Text and Markdown? With what you are saying, the user might as well just use a CommandBar and create their own from scratch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There isn't a way to create one control to be "universal" for any text entry. What happens if I want this toolbar for a regular Textbox and not a RichTextbox? TextBox still supports bold, italics, hyperlinks, colors, etc.
Having code paths like
if(Format == RichText)
{
DoRichText();
}
else if(Format == Markdown)
{
DoMarkdown();
}
else if(Format == Textbox)
{
DoTextBox()
}
else
{
DoSomethingElse();
}
Is a perfect example of violating the Open/Closed principle of software development.
If I'm the only person who thinks it should be "more generic" then throw my suggestion away
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you actually look at the formatter implementation, you will see that my code isn't like that. If a user wants to implement their own experience, they can either inherit the Formatter class, defining their own Default buttons, how to deal with Bold, etc. They can also insert additional custom buttons via XAML or code behind, remove default buttons via XAML or code behind.
The only purpose the Format enum has, is to Instantiate the RichText Formatter or the Markdown formatter. Set it to Custom, and then you can provide it your own inherited Formatter.
I have ensured that are plenty of places for the user to be able to adapt the code to suit their needs.
It is generic, yet you can still achieve similar functionality.
|
@skendrot This was just the initial functionality I was setting up, considering things such as Shortcut keys, after the initial code was setup. Again, Shortcut keys would probably be of better use in the Formatter class, so that Different default Buttons can receive their own key. Such as the Code Button, which wouldn't exist in the Rich Text version of the Toolbar, could get CTRL + "[", or something like that. Also if you test in the GitHub Comments section, which I have tested against for implementation, you will see that Ctrl-B only changes selection, and pressing it again will remove the Bold formatting, as if the button was pressed. |
-Added ShiftActivation Action to ToolbarButton, for holding shift when clicking or doing a keyboard combination. Made new Action called Click to hide the AppBarButton Click Handler, allowing for cleaner code, and a way to activate ShiftActivation when Shift-Clicking, such as pressing CTRL + SHIFT + K will add the Link template, instead of Opening the Link Creator. -Added Code Block support, now just have to implement Inline Code Selection, however, It requires a check to see if it is a single line selection. -You no longer have to manually iterate the ListLineIterator, it also starts at 1. -Added bool to determine if the ListLineIterator has reached the last line. -Added option to Wrap selected Lines for a list, with Empty Lines (For Line Chars). -Added Logic for ObservableCollections in ButtonMaps, and Remove Buttons, so that Buttons can be dynamically added and removed at anytime during operation. -Moved simple methods out of TextToolbar.Events into TextToolbar.Methods.
|
@skendrot I have implemented Rudimentary Shortcut keys, however, I need to use reflection to disable the RichEditBox's internal KeyDown interactions, so that Markdown can use combinations such as CTRL + R & CTRL + L, as it doesn't support Right to Left Text, Or I could just stray away from using those keys. I still like having a Formatter class to format the text for specific formats like Markdown, as RichEditBox, doesn't have the support built-in, but the code split could be moved, so two Controls can be made, an Inherited RichEditBox that uses the Formatter Class, and then the TextToolbar could attach to this Inherited RichEditBox, and trigger it's methods. Which is a more natural location for a Formatter Style class, however, doing this manipulation in the TextToolbar is a simpler implementation, and it is easier for a user to Implement. |
-TextToolbar now properly updates the XAML Designer, as the Dependency property are handled with PropertyChanged Events. (If Project code is disabled, nothing shows). -Added ToolbarButton as a parameter to every Formatter method, in order to do things such as enabling Toggling, and to handle the ToggleEnded Event. -Added Headers menu for MarkDown Formatter, to create different Headers. -Added Inline Check for Code Formatting. -All MD SetList methods are now Toggleable, so that they automatically add the List Char after going to a new line. -Removed Dependency on FontAwesome, by Designing List Icons using XAML. -Fixed Sample Page, Fleshed out Page, and added Binded Properties, so that users can see both the Markdown Formatter and RichText Formatter, and what it would look like in the dark theme. -Sample Page also demonstrates Adding/Removing Default Buttons, Custom Buttons with Keyboard shortcuts. -Added some basic implementation or RichText Formatting.
…t of translatable/replacable strings. -Fixed Formatter not loading Default Buttons into the TextToolbar Default Buttons map. Meaning that Default buttons can now be manually be modified before the UI appears.
…ate. -Added Back IsEnabled Property to Sample Page. -Added DependencyPropertyWatcher, to track changes in Dependency Properties.
|
Love the control. I'm slowly reviewing the PR. In the meantime, I keep running into a marshaling exception that you should probably take a look. It happens almost every time I play with the markdown format, switch to rich format and click on to view the xaml code, (or go back between xaml code and properties) |
|
We should not be changing the behavior of REB, so CTRL+R/L, should continue to support right to left. Strike-through does not usually have a keyboard shortcut, so we can leave it without, or assign some other key. It seems like you've implemented quite a bit of the rich text formatter, why not finish it ;) |
|
Please take a look at keyboard/controller navigation as well. Tab is working, but it looks like separators are also capturing focus. The controller doesn't work at all, the entire toolbar captures focus instead. |
|
@nmetulev Whoops, I had only just added the DependencyWatcher in order to grey out the Custom List Icons. I'll fix that right away, I'll patch the separators capturing focus too. As for Controller Navigation, I can't imagine in it's current state that this control would be very helpful anyway. I would finish Implementing RTF, however, I just don't have time. I have 5 days till Uni starts again, and I want to finish updating my app, TVPLAN. :) |
…e previous button. -Hopefully fixed the Threading Exception for disposal of DependencyWatcher in the Symbol deconstructor. -Disabled Tab Stops around the place. The Toolbar is now more Keyboard/Controller friendly, requiring no confusing middle tabs. -Tooltip now fills out the Automation.Name field. -Separators shouldn't steal focus anymore.
|
@nmetulev All issues fixed in my latest Commit. I even threw in Automation.Name for the Toolbar Buttons. |
…Continuous Integration :( -Removed Shortcut key for Strikethrough. -Fixed Automation.Name crash if Tooltip is null, e.g. switching Formatters.
|
@WilliamABradley I'm not too worried about shortcuts keys, more that the toolbar handles more than changing selection. When a control uses this toolbar, and a tool is activated like Bold, then any new text entered from the location that bold was activated becomes bold. |
|
@skendrot That experience is achieved when using Rich Text format on the TextToolbar, however, due to the design of Markdown formatting, it is not as feasible. |
|
Thanks, I'll try it out. BTW, you should be able to grab the nugets from appveyor as well, in the artifacts section. Here are the ones from the last commit. |
|
I feel silly now, Its because they had a pre-release flag on them. :( |
|
Ok, it works without a specified Format now, but I seem to have lost Design Time viewing. |
|
@nmetulev I fixed Design Time, it should all be working now :) Currently Hyperlinking with Rich Text, ApplyDisplayUpdates don't seem to update the UI, it will return plain text, however, if you try to Open the Link creator on this text, it will turn blue in the Link Creator in the Label Field. One fix for this would be to Copy the whole RTF Document to a string, and then restore it (This might take time on a big document). |
|
I noticed that about the link, the link still seems to work though. It seems like a Document bug. I'd recommend creating an issue for us to track once this PR is merged. I have another request for you. I was playing with the API and the RemoveDefaultButtons property doesn't seem natural, kinda counterintuitive to add in order to remove. Wondering if there is another way? Maybe a property on the button. Kinda like |
|
Yea that makes more sense, the sole purpose, was that it allowed you to remove buttons in XAML. The same effect can probably be achieved this way. |
… to change the visibility of a default button from XAML, with IsVisible. -Added method to get Default Button instance from TextToolbar in Enum. -Made RemoveToolbarItem public. -Updated Doc.
|
@nmetulev I have made the changes, |
|
That works much better :) What is the purpose of ButtonModifications? |
|
@nmetulev It allows you to do the same thing from XAML. <controls:TextToolbar x:Name="Toolbar" Editor="{x:Bind Editor}">
<controls:TextToolbar.ButtonModifications>
<buttons:DefaultButton Type="Headers" IsVisible="False"/>
</controls:TextToolbar.ButtonModifications>and var button = Toolbar.GetDefaultButton(ButtonType.Headers);
button.Visibility = Windows.UI.Xaml.Visibility.Collapsed;Are Equivalent, XAML reflects it in the designer too. |
|
So now we need a second review right? |
|
Yeah, we need two approvals before we merge |
|
I'm not sure if @skendrot is available to do this, is there someone else? |
|
I'll start reviewing this :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love this control! Thanks for taking the time to build this. I have requested some changes which would be great to put in. Happy to discuss further if you disagree with anything.
| { | ||
| public object Convert(object value, Type targetType, object parameter, string language) | ||
| { | ||
| return (Format)value == Format; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could get an invalid cast exception here. Add some defense against that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a quick converter in the Sample App, but I could make it available for use with the toolbar.
|
|
||
| public object ConvertBack(object value, Type targetType, object parameter, string language) | ||
| { | ||
| throw new NotImplementedException(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a chance this code could ever get called? Would people want to write back to a backing property? If so I'd add the convert back code too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't exactly infer the type of Format from a Boolean, it Could RichText, Markdown or Custom
| var linkOpen = Task.Run(() => Launcher.LaunchUriAsync(new Uri(e.Link))); | ||
| } | ||
| catch | ||
| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure this will not be reachable. Check out : https://msdn.microsoft.com/en-us/magazine/jj991977.aspx
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The link works, do you mean the catch if the link isn't a valid Uri?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup, as it was running in the Task.
| public void FormatUnderline(ToolbarButton button) | ||
| { | ||
| var format = Formatter.SelectionFormat; | ||
| if (!button.IsToggled) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of repetitive logic. Consider a high order function.- not a must though, just makes reading easier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of the properties on these a very slightly different though, such as Underline and list types.
| } | ||
| }; | ||
|
|
||
| Toolbar.CustomButtons.Add(demoButton); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found a bug:
- Switch to dark
- IsEnabled = Off
- Format: RichText
- Click Add Custom Button
Bullet points and numbering icons change colour
| } | ||
| }; | ||
|
|
||
| Toolbar.CustomButtons.Add(demoButton); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the toolbar is wider than the parent hosting it, it would be great to have something that indicates there is more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't that what the scrollbar is for? Something like that sounds difficult to implement.
…from SampleApp to be usable for anyone. Added safer conversion and ConvertBack. -Fixed List Button colors when IsEnabled=false, and changing the theme. -Moved Uri creation out of Task for Previewer.
|
@shenchauhan I have fixed the bug with the List Icons, the Link creation, and the Value converter, perhaps the More buttons indicator could be something to be added in a future PR? |
|
@WilliamABradley Agreed, approved and merging!! Nice work! 👍 |
|
Congrats @WilliamABradley 🥇 |
Uh oh!
There was an error while loading. Please reload this page.