I have a RichTextBox control in my view. I'm using code-behind (UI logic only) to format the RTF within my RichTextBox which is working from a 'Format' button click event which instantiates a TextRange:
private void _btnFormat_Click(object sender, RoutedEventArgs e)
{
TextRange rangeOfText = new TextRange(richTextBoxArticleBody.Document.ContentStart, richTextBoxArticleBody.Document.ContentEnd);
rangeOfText.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Black);
rangeOfText.ApplyPropertyValue(TextElement.FontSizeProperty, "12");
rangeOfText.ApplyPropertyValue(TextElement.FontFamilyProperty, "Arial");
rangeOfText.ApplyPropertyValue(TextElement.FontStyleProperty, "Normal");
rangeOfText.ApplyPropertyValue(Inline.TextDecorationsProperty, null);
}
I want to also remove any tables within the RTF. Can I use the same approach maybe from the Table class to remove tables from my RichTextBox? Thanks
You'll have to climb down the Blocks and get the descendants
of the FlowDocument and get all the Tables and then remove them from the its Parent.
Ok, if anyone is trying to achieve this I don't think it's possible. Maybe you can iterate over a simple table in an Rtf string and remove the tags but if you can't determine user input the Rtf is by far too complex. Therefore here's my solution (of sorts...)
private void _btnFormat_Click(object sender, RoutedEventArgs e)
{
TextRange rangeOfText = new TextRange(richTextBoxArticleBody.Document.ContentStart, richTextBoxArticleBody.Document.ContentEnd);
rangeOfText.ApplyPropertyValue(Table.BorderThicknessProperty, "3");
rangeOfText.ApplyPropertyValue(Table.BorderBrushProperty, Brushes.Red);
}
In the 'Format' button click event I've set table borders to Red. On my save back to the database method I've used this simple if statement:
private void SaveToDbCommandAction()
{
if(PastedText.Contains("trowd"))
{
Xceed.Wpf.Toolkit.MessageBox.Show("Cannot save Article. Please remove pasted tables");
}
else
{
SaveToDb(RTBText);
}
}
Therefore when the user pastes in a table they are warned via the red cell borders. This is particularly useful if they paste a table with invisible borders and can't actually see the table. The If statement then determines whether the Rtf string contains a 'trowd' tag therefore preventing the save.
Related
I have a custom WPF control that inherits from RichTextBox. I'd like to be able to modify the FlowDocument of the richtextbox whenever the text is changed. For demonstration, let's say that we have this:
<MyCustomRichTextbox>
<FlowDocument>
<Paragraph>This is the first paragraph</Paragraph>
<Paragraph>And this is the second</Paragraph>
</FlowDocument>
</MyCustomRichTextbox>
and whenever the text is changed (e.g. someone types in the control), the entire containing paragraph is colored red.
It seems to me that two things need to happen in order to achieve this:
I need to somehow get the block that contains the changed text out of MyCustomRichTextbox.Document.Blocks
I need to modify that block
Unfortunately, the OnTextChanged method doesn't provide a way to get the Block that changed. I was able to use LINQ and the TextChangedEventArgs.Offset to get the block, but I'm concerned that this approach will yield unacceptable slowdowns with larger documents (since it must enumerate each block every time a character is typed). Is there a better way to get the containing paragraph?
I know I could cache a reference to the "Last modified block" and check if it's still the one being modified, but that wouldn't really help in a random access scenario.
If I understand your problem correctly, you want to highlight the containing Paragraph of the current selection (the current position of the caret). So it's obviously that you have to get the containing Paragraph each time the Selection changes. Then you can just change the Foreground to Brushes.Red. Fortunately that the containing Paragraph seems to be referenced by the TextPointer and the process of finding it is nearly immediate. (a TextPointer has a property called Paragraph). Here is the detailed code:
Paragraph p = null;
//Suppose rtb is the name of your RichtTextBox
private void UpdateContainingBlockState() {
if (p != rtb.Selection.Start.Paragraph){
if (p != null) p.Foreground = Brushes.Black;
p = rtb.Selection.Start.Paragraph;
if (p != null) p.Foreground = Brushes.Red;
}
}
//The SelectionChanged event handler for your RichTextBox
private void selectionChangedHandler(object sender, RoutedEventArgs e){
UpdateContainingBlockState();
}
The frequency of changing the Selection is fairly high (each time you press almost keys which can cause the selection changing). So if your document is large and you realize some poor performance while typing, it's time to switch to the next more complex code. You can also try using Threading approach (or using Task) to put the UpdateContainingBlockState() call in another thread but be careful about cross-thread access. Here I use a different approach, the idea is call the UpdateContainingBlockState() at the right time, that is when the actual selection change can jump between paragraphs. While typing normal printable characters, the selection will be always in the current paragraph (so we don't need to call UpdateContainingBlockState()) unless when you type the Enter key. Generally we will call the method when user typing a control key (arrow keys, home, end, Enter, ...). We should also call the method if the RichTextBox gets focused and if user clicks mouse on the RichTextBox. You can see that almost the typed characters won't trigger calling the method so it will improve the performance much more than the code above (of course it may be realizable only when the document is large). Here is the detailed code:
//should add this using at the beginning
using System.Runtime.InteropServices;
[DllImport("user32")]
private static extern int MapVirtualKey(int ucode, int mapType);
//The KeyUp event handler for your RichTextBox
private void keyUp_Handler(object sender, KeyEventArgs e){
if (char.IsControl((char) MapVirtualKey(KeyInterop.VirtualKeyFromKey(e.Key),0x2)))
UpdateContainingBlockState();
}
//The PreviewMouseUp event handler for your RichTextBox
private void previewMouseUp_Handler(object sender, MouseButtonEventArgs e){
//UpdateContainingBlockState();
//Calling UpdateContainingBlockState() directly will cause a small issue
//So we use this instead
Task.Run(() => Dispatcher.Invoke( () => UpdateContainingBlockState()));
}
//The GotKeyboardFocus event handler for your RichTextBox
private void gotKeyboardFocus_Handler(object sender,
KeyboardFocusChangedEventArgs e){
UpdateContainingBlockState();
}
I am trying to create a WPF application that has a richtextbox which accepts dictations from the user as its input.
I want to use this code for the SpeechRecognized event for an an object of the SpeechRecognitionEngine class.
private void speechRecognizer_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
{
e.Result.Text = rtb.Text; //rtb is an object of the RichTextBox class
}
The problem is that there is no Text property for the RichTextBox class. Is there any way of fixing this? Thanks in advance
Try this:
private void speechRecognizer_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
{
rtb.AppendText(e.Result.Text);
}
This is a well documented problem and there are many different solutions for it. You can find a large number of possible solutions in the answers to the RichTextBox (WPF) does not have string property “Text” question here on Stack Overflow.
However, my preferred method would be to declare a Text Attached Property for the RichTextBox class. Please see the Attached Properties Overview page on MSDN to find out more about Attached Properties. You can find out what code to use in your Text Attached Property for the RichTextBox class in the RichTextBox Text property where are you hiding? tutorial on C# Disciples website.
RichTextBox contents can be set via Document property. You can find more details on MSDN
Here is an example from the link provided above. It creates a new document and a paragraph for it, then assigns the document to RichTextBox:
// Create a simple FlowDocument to serve as content.
FlowDocument flowDoc = new FlowDocument(new Paragraph(new Run("Simple FlowDocument")));
// Create an empty, default RichTextBox.
RichTextBox rtb = new RichTextBox();
// This call sets the contents of the RichTextBox to the specified FlowDocument.
rtb.Document = flowDoc;
// This call gets a FlowDocument representing the contents of the RichTextBox.
FlowDocument rtbContents = rtb.Document;
I'm using a 'Paste' button command in my view Model to copy RTF from the clipboard. PastedText is my string property that a RichTextBox is bound to in my view:
private void FormatPastedTextCommandAction()
{
PastedText += Clipboard.GetText(TextDataFormat.Rtf);
}
This works and the text is pasted on pressing the Paste button. However, I want to lock down the formatting on the paste function and remove all formatting from the pasted RTF string (colour, italics, set to black Arial 12).
I would just use PastedText += Clipboard.GetText();
to get the plain text but it pastes in at a different font size and I need it in RTF format. I've looked at iterating over the RTF string and doing a find/replace on font size, colour etc. but the RTF is very complex even for a couple of words.
Is there any way around this? Thanks
In the end I used code behind in the view to strip formatting from the RichTextBox itself using a 'Format' button:
private void _btnFormat_Click(object sender, RoutedEventArgs e)
{
TextRange rangeOfText = new TextRange(richTextBoxArticleBody.Document.ContentStart, richTextBoxArticleBody.Document.ContentEnd);
rangeOfText.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Black);
rangeOfText.ApplyPropertyValue(TextElement.FontSizeProperty, "12");
rangeOfText.ApplyPropertyValue(TextElement.FontFamilyProperty, "Arial");
rangeOfText.ApplyPropertyValue(TextElement.FontStyleProperty, "Normal");
rangeOfText.ApplyPropertyValue(Inline.TextDecorationsProperty, null);
rangeOfText.ApplyPropertyValue(Paragraph.MarginProperty, new Thickness(0));
}
This does a good job and doesn't really break the MVVM pattern as the code is UI logic only.
If I copy some text with different format and paste it to my richtextbox it is not plain I mean its format will be copied as well.
Is there anyway I can copy-paste as a plain text?
By the way my program is on WinForm
thanks for any answer
you must use WinForm RichTextBox (not in UI, just in code), even if you are on WPF, in order to convert RTF to plain text. Use this method in your Copy event.
C# code :
private String ConvertRtfToText()
{
System.Windows.Forms.RichTextBox rtfBox = new System.Windows.Forms.RichTextBox();
rtfBox.Rtf = this.rtfData;
return rtfBox.Text;
}
VB.Net Code :
Private Function ConvertRtfToText() As String
Dim rtfBox As RichTextBox = New RichTextBox()
rtfBox.Rtf = Me.rtfData
Return rtfBox.Text
End Function
source : http://msdn.microsoft.com/en-US/en-en/library/vstudio/cc488002.aspx
I recently had the same issue. I did want to retain some of the formatting, i.e. paragraphs and line feeds, but I required all the addition text format to be removed.
I'm working in WPF but the RichTextBox interface is the same. I have created a button that will allow users to select some text and remove the formatting. It is very simple, you just need to use the ClearAllProperties() method on the TextSelection object.
C# Code (WPF):
private void ClearFormat_Click(object sender, RoutedEventArgs e)
{
rtbText.Selection.ClearAllProperties();
}
This is a super easy solution but perhaps not super elegant...
1) Add a plain textbox to your form and make it hidden
2) Create a button to remove the formatting (or you can do this
automatically when the text is pasted)
3) In the OnClick (or OnPaste) code just copy the text from the rich
textbox control to the plain textbox control then copy the text
from the plain textbox back to the rich textbox control (see example
below)
private void btnRemoveFormatting_Click(object sender, EventArgs e)
{
txtPlainText.Text = txtRTF.Text;
txtRTF.Text = ""; // Required - this makes sure all formatting is gone
txtRTF.Text = txtPlainText.Text;
}
How would you prevent the user from adding or removing lines in a TextBox? By that I mean, if I set the text in a textbox to 7 lines with some text, how can I make sure that it will always be 7 lines of text there? The user must be able to edit those lines like usual, but not remove a line entirely, and not add any new ones.
Would have to take into account both keyboard input and also things like cut and paste, etc.
Any good ideas?
Reason: I want to make a file renamer kind of like Oscar's Renamer. You give it a folder, and it loads the filenames into a textbox where you can do changes pretty much like you do in a text editor. When you are happy with your changes, you write them back. Reason for constant n lines in textbox is then of course that line n is the name of file n. Adding a new line shouldn't be allowed since you only have those files in that folder. Removing a line should also not be allowed since you would then be missing a name for a file.
Why go through the trouble of making something like this when it already exists? Well, I am curious to see if I could do it, and thought it could be a nice excercise to learn a few things along the way. Since it has some interesting problems that needs to be solved. Like this one :) There are also some features I think are lacking in that Oscar's Renamer. So... to sum up: I'm doing it to learn and to try make an even better version of it. I know fully well that I may just as well fail completely though, or just never finish it :p But that is another story. I want to learn
You have the wrong interface then for the data. In this case, you should have a fixed number of textboxes, one for each line of data. This would allow the user to modify the contents of each line, but not remove a line or add a line.
Trying to make a multi-line textbox do this will be maddening at best, since you will have to determine when a new line is added/removed and then kill the change.
Why not use a Listbox instead?
I'd probably let the user do what they want on the textbox, then add validation to show the user there is an error when they go above 7 lines (e.g. red-outline and tooltip or something). If they are under 7 lines, no problem, add them when you come to process that data.
What is the specific reason you want the always 7 lines? Like casperOne said, maybe the interface you're using isn't ideal to your needs.
One possible way of doing this is to sub-class the Textbox control and override the winProc method. This method handles all window messages pumped to the windowed control (Textbox in your case). You could monitor the use of the backspace and delete keys and carat position and discard the key strokes that attempt to remove carriage return line feed sequences. And provide the user with an interactive alert that tells them why they cannot remove entire lines.
Doing it this way gives you complete control and is the lowest level way to see all input that is coming into your Textbox control. You can intercept certain messages and discard them, the ones that you want to allow just pass them through into the base class method. Such as if the user highlights all lines and hits the delete key. There is other event handlers that you can use to intercept keyboard input but they have some limitations, the winProc will allow you to check any message directed to the control including delete, backspace copy and paste etc, mouse clicks etc.
Sample:
public class myCustomTextBox : TextBox
{
protected override void WndProc(ref Message m)
{
if (m.Msg == 770) // window paste message id
{
string clipBoardData = Clipboard.GetDataObject().GetData(DataFormats.Text).ToString();
handlePasteEvent(clipBoardData);
}
else
{
base.WndProc(ref m);
}
}
private void handlePasteEvent(string pasteData)
{
// process pasted data
}
}
Since you already have stated that this is a learning project, a see if I can do it-project, I think you should throw in some WPF into it. A listbox with a itemtemplate would solve this very nicely.
If that's out of the way, I would consider using a datagrid instead of a textbox.
Load your data in a editable DataGrid instead of a TextBox, that should make things much more simple, and also you can pick which columns are editable and which are not.
Here's an example that uses a DataGridView and simulates your textbox:
The grid lines are hidden.
The headers of columns and rows are hidden.
The background color is the same color a TexBox has.
Add a DataGridView to a form and use the following code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
namespace Test
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
this.dataGridView1.AllowUserToResizeRows = false;
this.dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
this.dataGridView1.BackgroundColor = SystemColors.Window;
this.dataGridView1.BorderStyle = BorderStyle.None;
this.dataGridView1.CellBorderStyle = DataGridViewCellBorderStyle.None;
this.dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView1.ColumnHeadersVisible = false;
this.dataGridView1.EditMode = DataGridViewEditMode.EditProgrammatically;
this.dataGridView1.MultiSelect = false;
this.dataGridView1.RowHeadersVisible = false;
this.dataGridView1.SelectionChanged += new System.EventHandler(this.dataGridView1_SelectionChanged);
dataGridView1.DataSource = Directory.GetFiles(Environment.CurrentDirectory).Select(f => new FileEdit { FileName = Path.GetFileName(f) }).ToList();
}
private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
dataGridView1.BeginEdit(true);
}
}
class FileEdit
{
public string FileName { get; set; }
}
}