C# TreeView OnDrawNode is working slow and creating artifact - c#

I am working on a custom C# TreeView and I would like to do some custom draw to highlight the keywords appear in the name of nodes.
I did :
DrawMode = TreeViewDrawMode.OwnerDrawText;
in the constructor of the custom TreeView and override the OnDrawNode:
protected override void OnDrawNode(DrawTreeNodeEventArgs e)
{
if (!e.Node.IsVisible) { return; }
if (e.Node.Bounds.IsEmpty) { return; }
e.DrawDefault = false;
...draw calls...
But it worked strangely after I coded like this, the perceived behaviors include:
OnDrawNode being call on the child nodes which is not expanded and invisible
When the content of the TreeView updates, the user would see the old content and new content at the same time overlapping with each other. The old content would disappear not until about half second or longer.
The rendering speed is much slower than the original draw call.
Another modification I did is the code snippet I found here to suppress the flickering happens when the TreeView is updating:
http://dev.nomad-net.info/articles/double-buffered-tree-and-list-views
But it seems not directly related to the problem since I can still see the text overlapping after removing it.
I wonder if anyone have any idea about this issue?
Any thought would be appreciated.
Thank you.
edit:
The content of the OnDrawNode is like:
string pattern = keyword;
if (!string.IsNullOrWhiteSpace(pattern))
{
Regex regularExpressionnew = Regex(pattern);
Match match = regularExpression.Match(e.Node.Text);
while (match.Success)
{
CaptureCollection captureCollection = match.Groups[0].Captures;
foreach (Capture capture in captureCollection)
{
int highlightStartIndex = capture.Index;
int highlightEndIndex = capture.Index + pattern.Length;
e.Graphics.FillRectangle(nodeHightLightColor, GetTextBoundsBetweenIndex(e.Graphics, e.Node.Text, highlightStartIndex, highlightEndIndex, e.Bounds));
}
match = match.NextMatch();
}
Brush drawBrush = new SolidBrush(Color.Black);
e.Graphics.DrawString(e.Node.Text, Font, drawBrush, e.Bounds);
GetTextBoundsBetweenIndex is essentially calculating the square area covering the characters between highlightStartIndex and highlightEndIndex.
But the lag and overlap would happen event the regular expression is commented out and only the text rendering left.

Related

Searching and Highlighting words in Richtextbox is very slow

I have a RichTextBox rtbADB, I have to find the words which in richtextbox, which are either "SDM" or "SurfaceFlinger, and highlight( change fore color or change background color) them with any color. I am using the below code to do the same. There are two problems with my code:
While highlighting, it scrolls through the content of richtextbox , I want the highlight to happen without scrolling through the content.
The highlighting is very slow. I want it to happen as soon as function is called.
public void HighLightKeyWords()
{
string keywords = #"SDM|SurfaceFlinger";
MatchCollection keywordMatches = Regex.Matches(rtbADB.Text, keywords);
foreach (Match m in keywordMatches)
{
//rtbADB.Select(m.Index, m.Length);
rtbADB.SelectionStart = m.Index;
rtbADB.SelectionLength = m.Length;
rtbADB.SelectionColor = Color.Red;
}
}

WPF- Horizontal and Vertical position of a Run Element in a FlowDocument

Is there anyway to get the horizontal position(pixel) and vertical position(pixel) of a Run element in a FlowDocument?
Edit:
All i need to do is scroll to that position and make it the top line of the FlowDocument.
To Answer Your Question
The code needed to get the position of a content element in a document is all internal to .NET and not publically exposed. You would need access to an IContentHost implementation, which the built-in document viewers do not publically expose. So, there is no supported way to do what you are asking.
To Solve Your Actual Problem
There is a way to achieve your desired result of scrolling the element to the top of the view. What you want to do is scroll to the end of the document, then call BringIntoView on the element you want to have at the top.
There are multiple ways a FlowDocument can be displayed in an application. How you handle the scrolling depends on which control you are using to present the FlowDocument.
In a RichTextBox, use the ScrollToEnd method.
In a FlowDocumentScrollViewer, you will need to get its internal ScrollViewer and call ScrollToBottom on it. (You have to wait until the control is loaded before you can get a template part from it.)
private void MyControl_Loaded(object sender, RoutedEventArgs e)
{
mScrollViewer = mViewer.Template.FindName("PART_ContentHost", mViewer) as ScrollViewer;
}
In a FlowDocumentReader, the process is a bit more complex.
When the control is loaded, register for changes to the ViewingMode property and run the handler once to account for the starting value:
private void MyControl_Loaded(object sender, RoutedEventArgs e)
{
var descriptor = DependencyPropertyDescriptor.FromProperty(FlowDocumentReader.ViewingModeProperty, typeof(FlowDocumentReader));
descriptor.AddValueChanged(mReader, (s, a) => Reader_ViewModeChanged());
Reader_ViewModeChanged();
}
In the handler, dig in to find the ScrollViewer. It will only be present when the ViewingMode is set to Scroll:
private void Reader_ViewModeChanged()
{
mScrollViewer = null;
if (mReader.ViewingMode == FlowDocumentReaderViewingMode.Scroll)
{
var contentHost = mReader.Template.FindName("PART_ContentHost", mReader) as DependencyObject;
if (contentHost != null && VisualTreeHelper.GetChildrenCount(contentHost) > 0)
{
var documentScrollViewer = VisualTreeHelper.GetChild(contentHost, 0) as FlowDocumentScrollViewer;
if (documentScrollViewer != null)
{
documentScrollViewer.ApplyTemplate();
mScrollViewer = documentScrollViewer.Template.FindName("PART_ContentHost", documentScrollViewer) as ScrollViewer;
}
}
}
}
Once you have the ScrollViewer, you can call ScrollToBottom on it when desired.
Now, scroll to the bottom of the document, then call BringIntoView on your Run, and it should be at the top of the view.
Does not bring it to the top but just call BringIntoView on the Run. Save a reference to the Run.
It may be late but i still want to share the way i DID it in WPF.
You need an offset to do so.
As the above said: Flow gave you:
flow.ScrollToHome(); // Bottom
But also gave: ScrollToVerticalOffset (get from Rect)
if you have index (offset of the char/line) - you can find it in you saved data or get the TextPointer with flow.Selection.Start/End
TextPointer t_st = flow.Selection.Start;
double offset = flow.Document.ContentStart.GetOffsetToPosition(t_st);
private void gotoOffset(double offset)
{
TextPointer myTextPointer1 = flow.Document.ContentStart.GetPositionAtOffset((int)offset);
flow.Selection.Select(myTextPointer1, myTextPointer1);
flow.Focus();
Rect screenPos2 = myTextPointer1.GetCharacterRect(LogicalDirection.Forward);
double offset2 = screenPos2.Top;
Thread.Sleep(100);
flow.ScrollToVerticalOffset(offset2);
flow.Focus();
}
As the code above, We get the Rect from TextPointer, the Textpointer and get from Offset.
The focus just to make sure to place the cursor in right place.
Sometime the issue happen when you jump to many offset.
I recomment to trigger flow.ScrollToHome(); Before jump (because this ScrollToVerticalOffset true from the start, not any line)

DataGridView - Suppress all word wrapping

I'm trying to implement a DataGridView that has smaller cell width than autosize does.
If you look really close at a autosized cell you'll notice that there is still some space that is not used to actually display the cell's content.
That's why I started to measure the content's width by myself via TextRenderer and then manually set the column's width.
The initial problem was that a "A" was displayed as "A..." long before the cell was actually "filled". The reason for that was cell.Style.WrapMode set to "nonSet". I was quite happy that DataGridViewTriState.True did work for that "A"-example.
But now I just noticed that if the String has multiple words ("A, B") the DataGridView tries to display the content to several lines long before the cell is actually "filled".
What I'm now looking for is either a way to delete that "padding" of the cell's content or fully suppress the word wrapping on a certain cell/column (= single line without the String's cut off).
Additionally I should admit that there are no hidden blanks on that Strings so trim has no effect at all.
Edit:
I randomly stumbled upon some colleagues code that seems to do kind of what I'm searching.
StringFormat format = new StringFormat(StringFormatFlags.NoClip);
The documentation says that strings have some rectangle around them that is bigger than the string itself. If the rectangle sticks out of the writeable area the string is wrapped. That code snippet suppresses that (default) behaviour.
The only problem is that this solution only seems to work for drawing strings. I didn't find a possibility to assign a stringformat object to a string.
Try This Code
dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells;
dataGridView1.DefaultCellStyle.WrapMode to DataGridView1TriState.True
Hope it helps you
Can you Try this code. That is working in my condition.
private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.Value == null)
return;
var s = e.Graphics.MeasureString(e.Value.ToString(), dataGridView1.Font);
if (s.Width > dataGridView1.Columns[e.ColumnIndex].Width)
{
using (
Brush gridBrush = new SolidBrush(this.dataGridView1.GridColor),
backColorBrush = new SolidBrush(e.CellStyle.BackColor))
{
e.Graphics.FillRectangle(backColorBrush, e.CellBounds);
e.Graphics.DrawString(e.Value.ToString(), dataGridView1.Font, Brushes.Black, e.CellBounds, StringFormat.GenericDefault);
dataGridView1.Rows[e.RowIndex].Height = (int)(s.Height * Math.Ceiling(s.Width / dataGridView1.Columns[e.ColumnIndex].Width));
e.Handled = true;
}
}
}

How do I reclaim the space from the "Grip"

I've got a StatusStrip with a single ToolStripStatusLabel, Spring=true and a background color for notifications.
The problem is that there's an ugly gray square on the right side of the status strip. After fiddling for a while, I realized this is the sizing grip (I had is set to SizingGrip=false, GripStyle=Hidden). Yet even with it hidden, it still hogs the space. I can't get any content on the status strip to extend all the way to the right.
How would you work around this? Note I can't just set the backcolor of the StatusStrip because the Status Label changes colors and has some fading effects.
The StatusStrip.Padding property is borked, it returns the wrong value for Padding.Right if the sizing grip is disabled. You can fix it in your form constructor, like this:
public Form1() {
InitializeComponent();
statusStrip1.Padding = new Padding(statusStrip1.Padding.Left,
statusStrip1.Padding.Top, statusStrip1.Padding.Left, statusStrip1.Padding.Bottom);
}
Using the Left property to specify Right is the fix. Don't bother submitting this bug to Connect, they won't fix it.
Have a look at this blog entry on MSDN. The question was about changing the size of the sizing grip manually, and I think using the ToolStrip Renderer as suggested could work for you also.
The problem I have so far, is that it removes the background color on a status label in the StatusStrip, so it's not a solution yet, but it's a start.
public MyForm()
{
InitializeComponent();
statusStrip1.Renderer = new MyRenderer();
}
private class MyRenderer : ToolStripProfessionalRenderer
{
protected override void OnRenderStatusStripSizingGrip(ToolStripRenderEventArgs e)
{
// don't draw at all
}
}
I had following problem: when I set tsslSeparator.Spring = true, my right label disappeared immediately after tsslSeparator lost focus. The issue appeared when sizing grip was enabled. When it was disabled, everything worked just fine.
The solution was to set right margin for right label to something different than 0.
tsslLogging.Margin = new Padding(0, 3, 2, 2); // this is necessary for right alignment of status bar label
Hope this helps somebody.
If Microsoft isn't interesting in fixing it, it seems like a proper fix should handle all orientations, and ideally fix all Status Strips (see my answer to Get All Children for definition of GetAllChildren)
public static StatusStrip FixPadding(this StatusStrip ss) {
if (!ss.SizingGrip) {
var fixpad = ss.Padding;
if (ss.Orientation == Orientation.Horizontal) {
if (ss.RightToLeft == RightToLeft.No)
fixpad.Right = fixpad.Left;
else
fixpad.Left = fixpad.Right;
}
else
fixpad.Bottom = fixpad.Top;
ss.Padding = fixpad;
}
return ss;
}
public static void FixStatusStripPadding(this Form f) {
foreach (var ss in f.GetAllChildren().OfType<StatusStrip>())
ss.FixPadding();
}

C# Winforms bold treeview node doesn't show whole text

I'm using the following code to make my treenodes bold:
Font font = new Font(tvQuestionSequence.Font, FontStyle.Bold);
foreach (QuestionnaireBuilder_Category cat in categories)
{
TreeNode node = new TreeNode();
node.Text = cat.Description;
node.Name = cat.Id.ToString();
node.NodeFont = font;
tvQuestionSequence.Nodes.Add(node);
}
But the text of the bold nodes is not displayed correctly. The last letter(s) are not shown. How come? And how to solve this problem?
I found this Post when searching through the web because I am facing the exact same problem.
However, appending a white space to the end of the node was not an option, and I found an alternative way that seems to fix the issue.
After setting my node font Bold, all I need to do is reset the node text with the same value.
Here is the Code Sample:
Font boldFont = new Font(treeview.Font, FontStyle.Bold);
node.NodeFont = boldFont;
node.Text = node.Text;
It seems that the node is redrawn after changing the text, which is exactly what I wanted in the first place.
I've found that this is a Windows issue. A workaround for this problem is this:
In the form constructor set the font of the treeview to bold. When adding nodes which must not be bold, change the font to regular:
// Constructor of your form
public Form()
{
InitializeComponent();
Font font = new Font(tvQuestionSequence.Font, FontStyle.Bold);
tvQuestionSequence.Font = font;
}
// Add regular nodes (not bold)
Font font = new Font(tvQuestionSequence.Font, FontStyle.Regular);
TreeNode treeNode = new TreeNode();
treeNode.Text = "Foo";
treeNode.NodeFont = font;
TreeNode parent = tvQuestionSequence.Nodes.Find("parent", true);
parent.Nodes.Add(treeNode);
Simply use treeView.BeginUpdate() before you bold the node then treeView.EndUpdate() after you've bolded the node.
This is a known Windows bug.
The simple solution is just to append an extra space character at the end of your strings. The space character will not be visible, but it will increase the number of pixels needed to draw the string, so the entire string will be visible.
This is all not helping for me.
What DID the trick is making the font a little bigger and bold at DESIGN time.
(In the Properties window)
So make sure you define the treeview with big enough font, then later you can add nodes with smaller font. They will fit.
I do agree with the solution provided. I just want to add to it to shed a little more light on what the problem is.
The treeview has its own font which determines the width of items at the root level. That compensates for the fact that there is only an item height property available and no item width property.
The solution to your problem is to determine what the font of your root node should be, then set the tree to that same font. You can do that at design time also.
Hope that helps someone.
A workaround for this problem is this:
Set the defaul font of treeview to bold in the properties.
And chnage to not bold when you need.
I do the following, I set the DrawNode Event to call, it sets the node to bold and removes the highlighted colour.
You can set any colour you like using the first parameter of the e.Graphics.FillRectangle function.
private void SetNodeBoldWhenSelected(object sender, DrawTreeNodeEventArgs e)
{
if (e.Node == null) return;
var font = e.Node.NodeFont ?? e.Node.TreeView.Font;
if (e.Node.IsSelected)
{
font = new Font(font, FontStyle.Bold);
}
var bounds = new Rectangle( e.Bounds.X,e.Bounds.Y,e.Bounds.Width+20,e.Bounds.Height);
e.Graphics.FillRectangle(SystemBrushes.ControlDarkDark, bounds);
TextRenderer.DrawText(e.Graphics, e.Node.Text, font, bounds, SystemColors.HighlightText, TextFormatFlags.GlyphOverhangPadding);
}
Now when I select a node I get 20 pixels more space, for my font, this works well, one can calculate the "real" size needed but there is no specification stating it needs to do this but you can use Graphics.MeasureString if you feel you need to do that.
Very easy and works fine
treeView1.SelectedNode.NodeFont = new System.Drawing.Font(treeView1.SelectedNode.TreeView.Font, treeView1.SelectedNode.TreeView.Font.Style | FontStyle.Bold);
this.treeView1.SelectedNode.Text += string.Empty;
I realize this is an old thread and it may have been answered. I just ran across this problem as I'm learning to use TreeViews. What worked for me was changing the font size for the entire TreeView to the same size, or bigger than the font of the level you want to bold. The default font size is 8.something. I changed mine to 10, which was the size I wanted my nodes, and the truncating was gone.
What worked for me: Hooking into the load event in the Control's constructor and tweaking the node as explained in BlunT's answer.
public MyControl()
{
InitializeComponent();
_head = new TreeNode();
this.Load += (s, e) =>
{
trvParts.Nodes.Clear();
_head.NodeFont = new Font(trvParts.Font, FontStyle.Bold);
trvParts.Nodes.Add(_head);
_head.Text = "Node Name";
};
}
It's in vb.Net however the solution to re-enter the value of the TEXT field gets around this nicely. As in:
With myNode
Dim myText As String = .Text 'capture the text
.NodeFont = New Font(<name of your treeview>.Font, FontStyle.Bold)
.Text = myText 'reset the text to the original value
End With
Based on MSDN Library, try change your code to:
Font font = new Font(tvQuestionSequence.Font, FontStyle.Bold);
foreach (QuestionnaireBuilder_Category cat in categories)
{
TreeNode node = new TreeNode();
node.Text = cat.Description;
node.Name = cat.Id.ToString();
node.NodeFont = font;
tvQuestionSequence.BeginUpdate(); //added newline here <--
tvQuestionSequence.Nodes.Add(node);
tvQuestionSequence.EndUpdate(); //added newline here <--
}
It work for me

Categories

Resources