RichTextBox - UI Resize causes huge CPU Load - c#

I've recently been developing an RTF Editor which is only a simple UserControl that has a RichTextBox with a couple Events like PreviewTextInput and PreviewMouseUp.
I noticed something slightly annoying though.
The Performance of the RichTextBox is absolutely terrible whenever the UI is being resized and the RichTextBox has a lot of Text to cause its Wrapping Algorithm to fire.
This gives the Application a really sloppy feel, as if it is poorly optimized (even though it isn't).
At first I noticed this performance hit while selecting Text, so instead of using the SelectionChanged event, I decided to use the PreviewMouseUp event and then fetch the Selection.
Then after further testing I found out that the resize also caused huge loads.
And I'm talking about loads ranging between 5% -> 30% with a Quad-Core CPU running at 3.8GHz!
To further test it out, I decided to comment out my RichTextBox and only include a new RichTextBox with no defined Property
<RichTextBox/>
Inserting this into a Window, filling with Text, and then resizing the Window to cause the Wrapping Algorithm did the same again, up to 30% usage!
I tried to research about this matter, and most people ended up recommending setting the PageWidth to high values in order to prevent Wrapping:
richTextBox1.HorizontalScrollBarVisibility = ScrollBarVisibility.Visible;
richTextBox1.Document.PageWidth = 1000;
Which I do not want, since the previous Version of the Editor I wrote was made with WinForms and could do Wrapping effortlessly, and I also want it in the new WPF Version.
Did anyone else ever face this issue?
If yes, could you please point me into the right direction to remove this huge strain on the hardware?
I'm a bit sad because I love WPF, but I did find one or the other Object that is really unoptimized and/or not practical in comparison to the WinForms counterpart, the RichTextBox seems to be another one of those cases :(
Sorry for the huge amount of Text, but I really wanted to Document this neatly in case some other poor soul faces this issue and for you guys to see what I've tried so far.

One way to overcome this issue might be to switch to "no wrap" mode when window is being resized, but when user finished with resizing - switch back to normal mode. Then wrapping algorithm will be executed just once at the end and users should still have smooth feeling about your application. Sample code:
public partial class MainWindow : Window
{
public MainWindow() {
InitializeComponent();
this.SizeChanged += OnSizeChanged;
}
private Timer _timer;
private void OnSizeChanged(object sender, SizeChangedEventArgs e) {
// user started resizing - set large page width
textBox.Document.PageWidth = 1000;
// if we already setup timer - stop it and start all over
if (_timer != null) {
_timer.Dispose();
_timer = null;
}
_timer = new Timer(_ => {
// this code will run 100ms after user _stopped_ resizing
Dispatcher.Invoke(() =>
{
// reset page width back to allow wrapping algorithm to execute
textBox.Document.PageWidth = double.NaN;
});
}, null, 100, Timeout.Infinite);
}
}

Related

Best way to model block diagrams in WPF

I want to write a block diagram design tool (something similar to Simulink or Modelica). I have done something like that already in C++/Qt and C#/WinForms/GDI+, but now I want to go WPF.
Take a block, which can be a rectangle or a square that contains several other smaller shapes as "input/output/bidirectional ports" probably labelled or not, some text and probably a bitmap or vector image. It should provide context menus, basic mouse or drag events (for moving the block, pulling the connections between blocks, etc.) and should allow manual rearrangement of its graphical constituents, maybe in a different editor.
Now imagine a diagram with say 1000 of such blocks (I am exaggerating a bit to allow enough headroom in the future) and corresponding connections. Given that scale, I wonder if I should fall back on to the visual level of WPF and model the interaction part manually or if it is sufficient to use Drawings, Shapes or even Controls for it (like a block being a button).
I am getting a little nervous when I see the ~50 event types a Button supplies and multiply this with the number of blocks times the average number of ports per block. Many elements will just point to the same event handlers or context menus, so these handlers could also be redirected to a management class.
I read through the respective WPF chapters in the book "Pro C# 5.0" and that actually did not allay my fears.
So what level of WPF (visual, drawing, shape, control) is advisable when it comes to speed and memory performance under these requirements?
Sidenote: I am just starting with WPF, so I am a bit stunned about its versatility. It makes strategic decisions a bit difficult for me, which is why I am asking before comprehensively researching.
You could try to create a custom layout with virtualization or use an existing one VirtualizationCanvas.
You need a custom panel for placing your scheme items at correct places in your scheme.
Also, you should create a custom control, based on ItemsControl with custom ItemsControlItems for handling items creating and so on.
Then apply your layout as ItemPanleTemplate for ItemsControl or ListView, where ItemsSource would be bounded to your viewModel with scheme collection.
That's the easiest option, as for me.
So now I have made a small feasibility study on the topic. Add 1800 Buttons to a Canvas, add two mouse events to each of them individually and further add a MouseWheel event to the window to allow basic zooming of the "scene". Observations are on my 10-year old Intel Core2Quad Q6600 + NVidia GTX 460 based machine.
Application starts up fast. Exe is very small (as expected), 8k. Resizing the window (to see more or less of the docked canvas' contents) feels quite snappy. However near fullscreen (all Buttons visible) redraw becomes a little heavy (like ~5Hz refresh rate). App grabs itself 20M of memory instead of 10M with only 1 Button. Zooming is fast enough. Moreover, when zoomed in, the rendering gets noticeably faster (so clipping and such works well). This is good because being in the closely zoomed-in state is the most frequent case when working with larger diagrams. Response to button events is unimpaired in every respect.
Conclusion: using Buttons for diagramming seems doable. It is certainly suboptimal as a graphics app gauged against the capabilities of my machine, but it is still fast enough for the job. Maybe using Shapes instead of Buttons gets a little more out of it. But definitely no case for falling back on low-level graphics.
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="wpf1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="wpf1"
Width="513"
Height="385"
x:Name="window1"
MouseWheel="window1_MouseWheel">
<Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch" x:Name="canvas1" Background="#FFFFE8E8"/>
</Window>
...and...
public partial class Window1 : Window
{
double totalScale = 1;
public Window1()
{
InitializeComponent();
for (int i=0; i<60; i++)
{
for (int j=0; j<30; j++)
{
Button newButton = new Button();
canvas1.Children.Add(newButton);
newButton.Width = 25;
newButton.Height = 25;
Canvas.SetTop(newButton, j*30);
Canvas.SetLeft(newButton, i*30);
newButton.Click += new System.Windows.RoutedEventHandler(button1_Click);
newButton.MouseMove += new System.Windows.Input.MouseEventHandler(button1_MouseMove);
}
}
}
Button lastButton;
void button1_Click(object sender, RoutedEventArgs e)
{
lastButton = sender as Button;
lastButton.Background = Brushes.Blue;
}
void button1_MouseMove(object sender, MouseEventArgs e)
{
Button butt = sender as Button;
if (lastButton != butt) butt.Background = Brushes.Yellow;
}
void window1_MouseWheel(object sender, MouseWheelEventArgs e)
{
double scaleFactor = Math.Pow(1.0005,e.Delta);
totalScale *= scaleFactor;
canvas1.LayoutTransform = new ScaleTransform(totalScale, totalScale);
}
}

Why does it take longer to toggle graphics after a selection is made from a combo box?

I have a WPF app that draws a compass. There is a large ring with tick marks and labels. I have a checkbox that toggles the compass graphics on and off. When I first start up the app, the compass turns on and off instantly.
Meanwhile, I have a combo box that grabs some data from a local database and uses that to render some overlay graphics. After using this combo box, the compass graphics no longer toggle quickly. In fact, the UI completely freezes for about 4 seconds whenever I click the checkbox.
I attempted to profile my app using Window Performance Profiling Tool for WPF. When I activated the checkbox, not only did my app freeze, so did the profiler. The graphs "catched up" afterward, but this tells me something must be seriously wrong.
I've managed to nail down that the problem graphics are the tick marks (not the numeric labels). If I eliminate them, the freezing problem stops. If I cut them down from 360 to, say, 36, the app still freezes, but for less time. Again, no matter how many tick marks I have, they toggle instantly when the app first starts.
My question is, How do I figure out why the toggle for my compass graphics goes from instant to horribly slow? I've tried extensive profiling and debugging, and I just can't come up with any reason why setting the Visibility on some tick marks should ever cause the app to freeze.
Edit
Okay, I've stripped everything out of my app to just the bare essentials, zipped it up, and uploaded it to Sendspace. Here is the link (it's about 143K):
http://www.sendspace.com/file/n1u3yg
[Note: don't accidentally click the banner ad, the real download link is much smaller and lower on the page.]
Two requests:
Do you experience the problem on your machine? Try opening Compass.exe (in bin\Release) and clicking the check box rapidly. The compass tick marks should turn on and off with no delay. Then, select an item from the combo box and try rapidly clicking the check box again. On my machine, it's very laggy, and after I stop rapid-fire clicking, it takes a few seconds for the graphics to catch up.
If you do experience the lag, do you see anything in the code that could be causing this odd behavior? The combo box is not connected to anything, so why should selecting an item from it affect the future performance of other graphics on the window?
Although ANTS didn't indicate a particular performance 'hotspot', I think that your technique is slightly flawed as it seems that every tick has a ViewModel that is responsible for handling an individual tick, and you are individually binding those ticks to the view. You end up creating 720 view models for these ticks that fire the a similar event each time the entire compass is shown or hidden. You also create a new LineGeometry every time this field is accessed.
The recommended approach for WPF in a custom drawn situation like this is to use a DrawingVisual and embrace the retained mode aspect of WPF's rendering system. There are several googleable resources that talk about this technique, but the gist is to declare a compass class inherits from FrameworkElement, and some smaller classes that inherit from DrawingVisual and use that to render the compass. With this technique, you can still have a ViewModel drive the compass behavior, but you wouldn't have individual viewmodels for each part of the compass. I'd be inclined to decompose the compass into parts such as bezel, arrow, sight, etc... but your problem may require a different approach.
class Compass : FrameworkElement
{
private readonly List<ICompassPart> _children = new List<ICompassPart>();
public void AddVisualChild(ICompassPart currentObject)
{
_children.Add(currentObject);
AddVisualChild((Visual)currentObject);
}
override protected int VisualChildrenCount { get { return _children.Count; } }
override protected Visual GetVisualChild(int index)
{
if (index < 0 || index >= _children.Count) throw new ArgumentOutOfRangeException();
return _children[index] as Visual;
}
override protected void OnRender(DrawingContext dc)
{
//The control automatically renders its children based on their RenderContext.
//There's really nothing to do here.
dc.DrawRectangle(Background, null, new Rect(RenderSize));
}
}
class Bezel : DrawingVisual
{
private bool _visible;
public bool Visible {
{
get { return _visible; }
set
{
_visible = value;
Update();
}
}
private void Update()
{
var dc = this.RenderOpen().DrawingContext;
dc.DrawLine(/*blah*/);
dc.Close();
}
}

Showing and hiding parts of winforms (extending?) C# .NET

I have a question about forms and controls. I want to add the ability to sort of make a part of my form only show when something is clicked. For example I have form1 and on the form i have a button and when that button is clicked the form grows or extends (slides out?) to show other controls that werent there before the button was clicked. I have no idea what this is called so I don't know what to look for but Ive seen it used in many other applications. Any information on this would be greatly appreciated.
You'd probably have to roll your own animation, increasing the size dimensions of your form (or panel, or whatever) on a timer, thereby exposing the previously hidden controls.
Timer T = new Timer();
T.Interval = 10;
T.Tick += (s, e) =>
{
myForm.Size = new System.Drawing.Size(myForm.Width + 10, myForm.Height);
if (myForm.Size.Width >= FormWidthThreashold)
T.Stop();
};
T.Start();
At the risk of stating the obvious, I don't suppose there's any way to switch the WPF? This stuff is built in, and quite easy for WPF. If not though, something like this should get you started.
I've done this before. Start by organising your form into logical sections. Don't leave all your controls on the form, place them inside panels. At Design-time you'll need to have the panels "fully expanded", but then at runtime you manipulate the panels' left, top, width, height, and maybe even the alignment and anchors properties, through code. You could use a timer as suggested by #Adam Rackis.. or you could change the increment value to alter the speed of the animation. The animation itself is just a loop that starts with x = x1 and ends with x = x2, where x = x + increment_value inside the loop. As the value of "x" changes, the component will be automatically redrawn. To get a smoother effect you might need to repaint the control (or the entire panel) on each iteration. If it runs too fast, you can either insert a delay or try to make the loop rely on a timer. I've had problems with timers for this kind of stuff, but admittedly I wasn't using C#.NET at the time (I did it in Delphi). It takes a lot of fiddling with the fine details to get this working nicely, so be patient, it's not Flash! Good luck.

Paint event handler stops executing after a few iterations

I've got a Windows Form that circulates through images which are displayed on the form as a slideshow. The way I'm doing this is to have a Panel control the size of the form it resides in, and add an event handler that draws an Image object that exists in memory.
void panel_Paint(object sender, PaintEventArgs e)
{
if (_bShowImage)
{
Point leftCorner = new Point((this.Bounds.Width / 2) - (_image.Width / 2), (this.Bounds.Height / 2) - (_image.Height / 2));
e.Graphics.DrawImage(_image, leftCorner);
_bShowImage = false;
}
}
When a new Image is loaded and referenced by _image, I'm forcing the Panel to redraw:
_bShowImage = true;
_panel.Refresh();
Immediately afterwards, the image is disposed and the dereferenced from the global variable:
_image.Dispose();
_image = null;
I've seen that it works for a while, say 5 iterations, then the panel_Paint() handler is not being called. I'm using 2-3 JPG's for the display and I know they're not corrupted as they are shown fine for the first x times. I've put debug lines around the Refresh() method of the panel which execute fine. It's as if the call to the handler has been dropped. Has anyone encountered this problem before?
This is so completely backwards. Either you use a paint event handler like now. It's just fine (I say it's better than a picturebox) but then you need to drop that _bShowImage and _image.Dispose stuff. You should instead dispose the _image before you power it up with a new one. But not until that.
Or, if you absolutley must dispose the _image right after it's painted, then you should instead use Panel.CreateGraphics to get a Graphichs object you can use to immediately draw the _image and drop the event.
As it stands - it is just darn confusing. Also: .Invalidate() is what you almost always want -not .Refresh(). That's just something that got stuck in many minds since the VB6 era.
Wouldn't it be smarter to put your pictures in a picture box and loop through them in that way, so that you don't force a repaint on the whole window each time?
just a thought...
Tony

How to force a redraw of my application's entry in the taskbar?

I have a Windows form application written in C#. I update the title of the form frequently, but there's a substantial lag between the title changing and the title dislayed in the taskbar being updated.
What's a clean way to force an update / redraw of the task bar's entry for my program? Failing that, how can I force a redraw of the entire task bar?
Elaboration: It turns out that the delay in updating the taskbar is fixed at about 100ms, however this seems to be a delay based on when the Form.Text was last modified. If you modify the text faster then that - say, every 10ms, the taskbar is not updated until the Form.Text has been left unchanged for at least ~100ms.
OS: Vista 32.
Did you try to call Form.Refresh() after updating the title?
Edit:
If you are doing the title updates in a loop you might have to do something along the line of:
this.Invalidate();
this.Update();
Application.DoEvents();
A task bar update more than every 100ms is going to be too fast for the user to resolve anyway. Presumably you're showing some sort of progress or status indicator to the user?
If so, you're crippling the app needlessly doing so many UI updates. That processing time is better used getting the customer's job done.
I think you need to revisit the UI design aspects of what you're trying to do.
I just did a simple test. The changes are quite instantaneous. From the look of it, it's definitely less than 500ms. If you need to update the title at a higher rate, I won't really recommend it. Generally I've seen the fastest update rate of twice per second.
EDIT:
I tested using keypress event. When I hold down the key for a fast repeat, it won't update until I've release my key. Thus, same scenario as your setup.
Btw, why do you need to update every 10ms? Just keep in mind that Thread.Sleep(timeout) with timeout of less than 50ms is not accurate. Also, 10ms timeout will equal to 100Hz, unless you're using high end display, you'll have miss a few frame. Most general LCD have a refresh rate of 60Hz. And our eye can't differentiate anything faster than 25Hz. Thus 40ms delay is more than enough, if you want to animate. Generally I would recommend 15Hz (67ms) for simple animation. If just want to scroll some text, 2Hz is more than enough. Anything faster will make the user dizzy.
Are you using code similar to this in your form?:
private void Form1_Load(object sender, EventArgs e)
{
Timer t = new Timer();
t.Interval = 10;
t.Tick += new EventHandler(t_Tick);
t.Start();
}
int aa = 0;
void t_Tick(object sender, EventArgs e)
{
this.Text = aa++.ToString();
}
Works fine for me - no lag between form and taskbar at all.
Are you sure you aren't locking up the GUI thread and not calling Application.DoEvents on your loop?
I'm using the new Windows 7 beta, so it's a small chance that it's different behavior.

Categories

Resources