Hi: I am trying to implement programmatically selected text formatting in a WPF RichTextBox.
The following code from How to select text from the RichTextBox and then color it? is exactly what i am trying to do. However, as far as i can tell this code is for a WinForms RichTextBox:
public void ColourRrbText(RichTextBox rtb)
{
Regex regExp = new Regex("\b(For|Next|If|Then)\b");
foreach (Match match in regExp.Matches(rtb.Text))
{
rtb.Select(match.Index, match.Length);
rtb.SelectionColor = Color.Blue;
}
}
However, i am stuck on how to fully convert the above code to code that WPF RichTextBox knows how to handle. The following code from wpf richtextbox selection with regex provides some very helpful guidance. So this is where i am:
public static void ColorSpecificText(RichTextBox richTextBox)
{
var newRun = new Run(); // <-- I THINK I NEED TO RELATE THIS CODE TO MY textRange OR TO MY richTextBox, BUT NOT SURE / DON'T KNOW HOW TO DO IT
TextRange textRange = new TextRange(richTextBox.Document.ContentEnd, richTextBox.Document.ContentEnd);
Regex regex = new Regex(#"\b(For|Next|If|Then)\b");
foreach (Match match in regex.Matches(textRange.Text))
{
TextPointer start = newRun.ContentStart.GetPositionAtOffset(match.Index, LogicalDirection.Forward);
TextPointer end = newRun.ContentStart.GetPositionAtOffset(match.Index + match.Length, LogicalDirection.Backward);
if (start != null && end != null)
{
richTextBox.Selection.Select(start, end);
richTextBox.Selection.ApplyPropertyValue(Run.BackgroundProperty, "Red");
}
}
}
For example, when applying this code to a WPF RichTextBox with the string "Next Hello World", the above code doesn't generate any errors, but doesn't work either ("Next" doesn't get highlighted). I don't have reason to believe that the regex would be the issue as i tested it at regex101.com. My guess is i am missing something in declaring newRun and how to relate it either to textRange or richTextBox. Thanks for any guidance on how to resolve this.
Related
How to add checkbox to all the header columns (Like in the image) and get the checked value, here autogeneratecolumns is true. Pls suggest programmatically or Clientside. I tried telerik demos but not much of help from there.
enter image description here
Correction in Img: For all the columns, Including Article Type.
There are multiple ways to get the desired result but here is the first solution I could come up with. I'd need to know a lot more about the exact requirements in order to find the best solution for your situation.
Based on the current information you've provided, this is the best I could come up with:
First you'll want to implement the OnAutoGeneratingColumn event of your GridView.
<telerik:RadGridView x:Name="MyGridView" AutoGenerateColumns="True" AutoGeneratingColumn="MyGridView_OnAutoGeneratingColumn" ItemsSource="{Binding MyData}" />
In this event you can customize the columns being generated. You could for example escape the generation of certain columns or customize anything you want really. In your situation you'll want to add a CheckBox to the Header of course.
private void MyGridView_OnAutoGeneratingColumn(object sender, GridViewAutoGeneratingColumnEventArgs e)
{
//Extra1: Ignore this event for certain columns
if (e.Column.UniqueName.Contains("extra1"))
{
return;
}
//Extra2: Disable the generation of a column entirely
if (e.Column.UniqueName.Equals("extra2"))
{
e.Cancel = true;
return;
}
//Place a CheckBox inside the header
e.Column.Header = new StackPanel()
{
Orientation = Orientation.Vertical,
Children =
{
new TextBlock()
{
Text = e.Column.UniqueName,
Margin = new Thickness(2),
HorizontalAlignment = HorizontalAlignment.Center
},
new CheckBox()
{
Margin = new Thickness(2),
HorizontalAlignment = HorizontalAlignment.Center
}
}
};
}
This should now give you the desired result as shown on your screenshot.
The second part is to get the list of checked columns. For this part I don't really know what exactly you're looking for but I'll just give you something to get you started.
Consider the following to be the OnClick event of a button:
private void MyButton_OnClick(object sender, RoutedEventArgs e)
{
var cols = new List<GridViewColumn>();
foreach (var col in MyGridView.Columns)
{
var hc = MyGridView.ChildrenOfType<GridViewHeaderCell>().FirstOrDefault(q => q.Column != null && q.Column.UniqueName == col.UniqueName && q.Column.DisplayIndex == col.DisplayIndex);
if (hc == null) continue;
var cb = hc.FindChildByType<CheckBox>();
if (cb != null && cb.IsChecked == true)
cols.Add(col);
}
MessageBox.Show(string.Join(", ", cols.Select(q => q.UniqueName)));
}
If you have any more questions or need any more help, just leave a comment.
Update: I was using the WPF version of telerik which is why I used the telerik:RadGridView. I didn't realize you were using the ASP.NET AJAX version which makes yours a telerik:RadGrid.
I believe the equivalent of my OnAutoGeneratingColumn event would be OnColumnCreated in your version, here is the documentation of telerik
I'd also appreciate it if you could mark this as the answer if this solved your problem, or at least give this an up-vote if this helped you in any way.
I have seen several posts on various forms varying in times from 2006 to now about how to add hyperlinks to RichTextBox, but they all seem overly complex for what I want. I am creating a desktop chat client, and I receive input as strings, now among these strings may be some urls, I need those urls to be clickable. Which from what I gather means they need to be HyperLink objects.
Navigating through the RichTextBox and replacing the urls with HyperLinks seems to be no small feat. Does anyone have a relatively simple solution for this?
In my web client it's a simple one liner
value = value.replace(/(http:\/\/[^\s]+)/gi, '$1');
Never thought I'd see the day where C# actually makes it harder.
If you want to do an equivalent of value.replace(/(http:\/\/[^\s]+)/gi, '$1') in WPF:
<RichTextBox x:Name="MyRichTextBox" IsDocumentEnabled="True" IsReadOnly="True" />
And the code that converts the string is the following:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var htmlText = "Google's website is http://www.google.com";
MyRichTextBox.Document = ConvertToFlowDocument(htmlText);
}
private FlowDocument ConvertToFlowDocument(string text)
{
var flowDocument = new FlowDocument();
var regex = new Regex(#"(http:\/\/[^\s]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
var matches = regex.Matches(text).Cast<Match>().Select(m => m.Value).ToList();
var paragraph = new Paragraph();
flowDocument.Blocks.Add(paragraph);
foreach (var segment in regex.Split(text))
{
if (matches.Contains(segment))
{
var hyperlink = new Hyperlink(new Run(segment))
{
NavigateUri = new Uri(segment),
};
hyperlink.RequestNavigate += (sender, args) => Process.Start(segment);
paragraph.Inlines.Add(hyperlink);
}
else
{
paragraph.Inlines.Add(new Run(segment));
}
}
return flowDocument;
}
}
It uses the same regular expression you provided, which is lacking if you properly want to recognize URLs with a regular expression. This one doesn't recognize https ones and the last dot in the following sentence would be a part of the URL: "This is a URL: http://www.google.com/."
What the code does is to split the text based on the regular expression, iterate it and adds the correct elements to the FlowDocument constructed on the fly.
Clicking the Hyperlink should open your default browser.
Result:
That said, this is only good for read only usage of the RichTextBox (as indicated by the question in the comment).
Need help finding a TableLayoutPanel added at run time to a tab control of a winform. Please find example code below. Any help would be appreciated.
private void GenerateControls()
{
TableLayoutPanel tp = new TableLayoutPanel();
tp.Name = "tpName";
tab1.Controls.Add(tp);
}
private void findTablePanelControl()
{
TableLayoutPanel tp = (TableLayoutPanel)this.Controls.Find("tpName", true)[0];
string name = tp.Name;
}
I receive the follow error message: Index was outside the bounds of the array.
I also tried the following code, but get this error (Object reference not set to an instance of an object) on the "string name =" line:
TableLayoutPanel tpParseSchema = (TableLayoutPanel)this.Controls.Find("tpParseSchema", true).FirstOrDefault();
I found the problem. The provided example code actually will work. The problem with my real code was that I mistakenly keyed in the wrong name value for the TableLayoutPanel. I ended up figuring this out by recursively stepping through all of the child controls of the tab control. Here is example code of how I did that.
foreach (Control ctrl in tab1.Controls)
{
string ctrlname = ctrl.Name;
}
The more I read about the subject the less I understand so apologies in advance if the below seems completely off the wall.
I have a usercontrol that contains a flowdocument - a view with a corresponding viewmodel. The aim is to send this to a preview window where the user can view the document and also print it.
I lifted some code from an example at http://www.eggheadcafe.com/tutorials/aspnet/9cbb4841-8677-49e9-a3a8-46031e699b2e/wpf-printing-and-print-pr.aspx
When the below is called
Public Shared Sub PrintPreview(owner As Window, data As FormData)
Using xpsStream As New MemoryStream()
Using package__1 As Package = Package.Open(xpsStream, FileMode.Create, FileAccess.ReadWrite)
Dim packageUriString As String = "memorystream://data.xps"
Dim packageUri As New Uri(packageUriString)
PackageStore.AddPackage(packageUri, package__1)
Dim xpsDocument__2 As New XpsDocument(package__1, CompressionOption.Maximum, packageUriString)
Dim writer As XpsDocumentWriter = XpsDocument.CreateXpsDocumentWriter(xpsDocument__2)
Dim visual As New Form(data)
Dim printTicket As New PrintTicket()
printTicket.PageMediaSize = A4PaperSize
writer.Write(visual, printTicket)
Dim document As FixedDocumentSequence = xpsDocument__2.GetFixedDocumentSequence()
xpsDocument__2.Close()
Dim printPreviewWnd As New PrintPreviewWindow(document)
printPreviewWnd.Owner = owner
printPreviewWnd.ShowDialog()
PackageStore.RemovePackage(packageUri)
End Using
End Using
This brings up the print preview window and shows the user control that holds the flowdocument. However, it only shows the first of what should be multiple pages. I was under the assumption the whole point of writing the xps and then reading it back again in this window was to work around the problem of printing a visual but I'm obviously misunderstanding the whole process. Any help in getting through my thick head what I need to do to be able to view all of the pages in the document would be much appreciated.
Cheers
I thought the above with using xpsdocument and getfixeddocumentsequence would convert the flowdocument to a fixeddocument but seeing it doesn't, am I perhaps writing it wrongly??
You can print a visual to an XPS. However, as I understand it, it is your job to manage pages. If your visual is too big to fit on a page, you need to find a way to split it onto multiple pages.
The source code I posted here prints a list of items over many pages:
https://bitbucket.org/paulstovell/samples/src/a323f0c65ea4/XPS%20Report%20Generator/
If you can find a way to split your visuals (perhaps create 3 forms, with 15 items per form) into pages, you can then use this:
using (var doc = new XpsDocument("P:\\Test2.xps", FileAccess.Write))
{
var writer = XpsDocument.CreateXpsDocumentWriter(doc);
var collator = writer.CreateVisualsCollator();
collator.BeginBatchWrite();
collator.Write(form1);
collator.Write(form2);
collator.Write(form3);
collator.EndBatchWrite();
}
var doc2 = new XpsDocument("P:\\Test2.xps", FileAccess.Read);
var seq = doc2.GetFixedDocumentSequence();
var window = new Window();
window.Content = new DocumentViewer {Document = seq};
window.ShowDialog();
Also, note that if you're newing up a visual and printing it, you'll need to size it first, otherwise you may get an empty screen. Here's an example of generating a page of data (of course you'd change the sizes to fit an A4 sheet).
private StackPanel CreatePage()
{
var panel = new StackPanel();
panel.Width = 1000;
panel.Height = 1000;
for (var i = 0; i < 10; i++)
{
panel.Children.Add(new TextBlock() {Text = "Item " + i});
}
panel.Measure(new Size(1000, 1000));
panel.Arrange(new Rect(0, 0, 1000, 1000));
return panel;
}
I want to allow some simple formatting commands within a WPF RichTextBox but not others.
I've created a toolbar that allows users to apply bold or italics, and use bulleted or numbered lists. (Basically, I only want to support the formatting commands that would be appropriate for a blog or wiki.)
The problem is that users can perform cut and paste operations that insert text with foreground and background colors, among other kinds of disallowed formatting. This can lead to nasty usability issues like users pasting white text onto a white background.
Is there any way to turn these advanced formatting features off? If not, is there a way I can intercept the paste operation and strip out the formatting I don't want?
You can intercept the paste operation like this:
void AddPasteHandler()
{
DataObject.AddPastingHandler(richTextBox, new DataObjectPastingEventHandler(OnPaste));
}
void OnPaste(object sender, DataObjectPastingEventArgs e)
{
if (!e.SourceDataObject.GetDataPresent(DataFormats.Rtf, true)) return;
var rtf = e.SourceDataObject.GetData(DataFormats.Rtf) as string;
// Change e.SourceDataObject to strip non-basic formatting...
}
and the messy part is keeping some but not all of the formatting. The rtf variable will be a string in RTF format that you can use a third-party libary to parse, walk the tree using a DOM-like pattern, and emit new RTF with just text, bold and italics. Then cram that back into e.SourceDataObject or a number of other options (see the docs below).
Here are PastingHandler docs:
DataObject.AddPastingHandler Method
Here is one of many RTF parsers:
NRTFTree - A class library for RTF processing in C#
Here is the code if you wanted to strip all formatting from pasted content (Not what you asked, but may be usefull to someone):
void OnPaste(object sender, DataObjectPastingEventArgs e)
{
if (!e.SourceDataObject.GetDataPresent(DataFormats.Rtf, true)) return;
var rtf = e.SourceDataObject.GetData(DataFormats.Rtf) as string;
FlowDocument document = new FlowDocument();
document.SetValue(FlowDocument.TextAlignmentProperty, TextAlignment.Left);
TextRange content = new TextRange(document.ContentStart, document.ContentEnd);
if (content.CanLoad(DataFormats.Rtf) && string.IsNullOrEmpty(rtf) == false)
{
// If so then load it with RTF
byte[] valueArray = Encoding.ASCII.GetBytes(rtf);
using (MemoryStream stream = new MemoryStream(valueArray))
{
content.Load(stream, DataFormats.Rtf);
}
}
DataObject d = new DataObject();
d.SetData(DataFormats.Text, content.Text.Replace(Environment.NewLine, "\n"));
e.DataObject = d;
}
}