The reason behind slow performance in WPF - c#

I'm creating a large number of texts in WPF using DrawText and then adding them to a single Canvas.
I need to redraw the screen in each MouseWheel event and I realized that the performance is a bit slow, so I measured the time the objects are created and it was less than 1 milliseconds!
So what could be the problem? A long time ago I guess I read somewhere that it actually is the Rendering that takes the time, not creating and adding the visuals.
Here is the code I'm using to create the text objects, I've only included the essential parts:
public class ColumnIdsInPlan : UIElement
{
private readonly VisualCollection _visuals;
public ColumnIdsInPlan(BaseWorkspace space)
{
_visuals = new VisualCollection(this);
foreach (var column in Building.ModelColumnsInTheElevation)
{
var drawingVisual = new DrawingVisual();
using (var dc = drawingVisual.RenderOpen())
{
var text = "C" + Convert.ToString(column.GroupId);
var ft = new FormattedText(text, cultureinfo, flowdirection,
typeface, columntextsize, columntextcolor,
null, TextFormattingMode.Display)
{
TextAlignment = TextAlignment.Left
};
// Apply Transforms
var st = new ScaleTransform(1 / scale, 1 / scale, x, space.FlipYAxis(y));
dc.PushTransform(st);
// Draw Text
dc.DrawText(ft, space.FlipYAxis(x, y));
}
_visuals.Add(drawingVisual);
}
}
protected override Visual GetVisualChild(int index)
{
return _visuals[index];
}
protected override int VisualChildrenCount
{
get
{
return _visuals.Count;
}
}
}
And this code is run each time the MouseWheel event is fired:
var columnsGroupIds = new ColumnIdsInPlan(this);
MyCanvas.Children.Clear();
FixedLayer.Children.Add(columnsGroupIds);
What could be the culprit?
I'm also having trouble while panning:
private void Workspace_MouseMove(object sender, MouseEventArgs e)
{
MousePos.Current = e.GetPosition(Window);
if (!Window.IsMouseCaptured) return;
var tt = GetTranslateTransform(Window);
var v = Start - e.GetPosition(this);
tt.X = Origin.X - v.X;
tt.Y = Origin.Y - v.Y;
}

I'm currently dealing with what is likely the same issue and I've discovered something quite unexpected. I'm rendering to a WriteableBitmap and allowing the user to scroll (zoom) and pan to change what is rendered. The movement seemed choppy for both the zooming and panning, so I naturally figured the rendering was taking too long. After some instrumentation, I verified that I'm rendering at 30-60 fps. There is no increase in render time regardless of how the user is zooming or panning, so the choppiness must be coming from somewhere else.
I looked instead at the OnMouseMove event handler. While the WriteableBitmap updates 30-60 times per second, the MouseMove event is only fired 1-2 times per second. If I decrease the size of the WriteableBitmap, the MouseMove event fires more often and the pan operation appears smoother. So the choppiness is actually a result of the MouseMove event being choppy, not the rendering (e.g. the WriteableBitmap is rendering 7-10 frames that look the same, a MouseMove event fires, then the WriteableBitmap renders 7-10 frames of the newly panned image, etc).
I tried keeping track of the pan operation by polling the mouse position every time the WriteableBitmap updates using Mouse.GetPosition(this). That had the same result, however, because the returned mouse position would be the same for 7-10 frames before changing to a new value.
I then tried polling the mouse position using the PInvoke service GetCursorPos like in this SO answer eg:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out POINT lpPoint);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
and this actually did the trick. GetCursorPos returns a new position each time it is called (when the mouse is moving), so each frame is rendered at a slightly different position while the user is panning. The same sort of choppiness seems to be affecting the MouseWheel event, and I have no idea how to work around that one.
So, while all of the above advice about efficiently maintaining your visual tree is good practice, I suspect that your performance issues may be a result of something interfering with the mouse event frequency. In my case, it appears that for some reason the rendering is causing the Mouse events to update and fire much slower than usual. I'll update this if I find a true solution rather than this partial work-around.
Edit: Ok, I dug into this a little more and I think I now understand what is going on. I'll explain with more detailed code samples:
I am rendering to my bitmap on a per-frame basis by registering to handle the CompositionTarget.Rendering event as described in this MSDN article. Basically, it means that every time the UI is rendered my code will be called so I can update my bitmap. This is essentially equivalent to the rendering that you are doing, it's just that your rendering code gets called behind the scenes depending on how you've set up your visual elements and my rendering code is where I can see it. I override the OnMouseMove event to update some variable depending on the position of the mouse.
public class MainWindow : Window
{
private System.Windows.Point _mousePos;
public Window()
{
InitializeComponent();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
// Update my WriteableBitmap here using the _mousePos variable
}
protected override void OnMouseMove(MouseEventArgs e)
{
_mousePos = e.GetPosition(this);
base.OnMouseMove(e);
}
}
The problem is that, as the rendering takes more time, the MouseMove event (and all mouse events, really) gets called much less frequently. When the rendering code takes 15ms, the MouseMove event gets called every few ms. When the rendering code takes 30ms, the MouseMove event gets called every few hundred milliseconds. My theory on why this happens is that the rendering is happening on the same thread where the WPF mouse system updates its values and fires mouse events. The WPF loop on this thread must have some conditional logic where if the rendering takes too long during one frame it skips doing the mouse updates. The problem arises when my rendering code takes "too long" on every single frame. Then, instead of the interface appearing to slow down a little bit because the rendering is taking 15 extra ms per frame, the interface stutters greatly because that extra 15ms of render time introduces hundreds of milliseconds of lag between mouse updates.
The PInvoke workaround I mentioned before essentially bypasses the WPF mouse input system. Every time the rendering happens it goes straight to the source, so starving the WPF mouse input system no longer prevents my bitmap from updating correctly.
public class MainWindow : Window
{
private System.Windows.Point _mousePos;
public Window()
{
InitializeComponent();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
POINT screenSpacePoint;
GetCursorPos(out screenSpacePoint);
// note that screenSpacePoint is in screen-space pixel coordinates,
// not the same WPF Units you get from the MouseMove event.
// You may want to convert to WPF units when using GetCursorPos.
_mousePos = new System.Windows.Point(screenSpacePoint.X,
screenSpacePoint.Y);
// Update my WriteableBitmap here using the _mousePos variable
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out POINT lpPoint);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
}
This approach didn't fix the rest of my mouse events (MouseDown, MouseWheel, etc), however, and I wasn't keen on taking this PInvoke approach for all of my mouse input, so I decided I better just stop starving the WPF mouse input system. What I ended up doing was only updating the WriteableBitmap when it really needed to be updated. It only needs to be updated when some mouse input has affected it. So the result is that I receive mouse input one frame, update the bitmap on the next frame but do not receive more mouse input on the same frame because the update takes a few milliseconds too long, and then the next frame I'll receive more mouse input because the bitmap didn't need to be updated again. This produces a much more linear (and reasonable) performance degradation as my rendering time increases because the variable length frame times just sort of average out.
public class MainWindow : Window
{
private System.Windows.Point _mousePos;
private bool _bitmapNeedsUpdate;
public Window()
{
InitializeComponent();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
if (!_bitmapNeedsUpdate) return;
_bitmapNeedsUpdate = false;
// Update my WriteableBitmap here using the _mousePos variable
}
protected override void OnMouseMove(MouseEventArgs e)
{
_mousePos = e.GetPosition(this);
_bitmapNeedsUpdate = true;
base.OnMouseMove(e);
}
}
Translating this same knowledge to your own particular situation: for your complex geometries that lead to performance issues I would try some type of caching. For example, if the geometries themselves never change or if they don't change often, try rendering them to a RenderTargetBitmap and then add the RenderTargetBitmap to your visual tree instead of adding the geometries themselves. That way, when WPF is performing it's rendering path, all it needs to do is blit those bitmaps rather than reconstruct the pixel data from the raw geometric data.

#Vahid: the WPF system is using [retained graphics]. What you eventually should do, is devise a system where you only send "what has changed compared to previous frame" - nothing more, nothing less, you should not be creating new objects at all. It's not about "creating objects takes zero seconds", it's about how it affects rendering and the time. It's about letting the WPF do it's job using caching.
Sending new objects to the GPU for rendering=slow. Sending only updates to the GPU which tells what objects moved=fast.
Also, it's possible to create Visuals in an arbitrary thread to improve the performance (Multithreaded UI: HostVisual - Dwayne Need). That all said, if your project is pretty complex in 3D wise - there's good chance that WPF won't just cut it. Using DirectX.. directly, is much, much, more performant!
Some of the articles I suggest you to read & understand:
[Writing More Efficient ItemsControls -
Charles Petzold] - understand the process how one achieves better drawing rate in WPF.
As for why your UI is lagging, Dan answer seems to be spot on. If you are trying to render more than WPF can handle, the input system will suffer.

The likely culprit is the fact that you are clearing out and rebuilding your visual tree on each wheel event. According to your own post, that tree includes a "large number" of text elements. For each event that comes in, each of those text elements must be recreated, reformatted, measured, and eventually rendered. That is not the way to accomplish simple text scaling.
Rather than setting a ScaleTransform on each FormattedText element, set one on the element containing the text. Depending on your needs, you can set a RenderTransform or LayoutTransform. Then, when you receive wheel events, adjust the Scale property accordingly. Don't rebuild the text on each event.
I would also do what other have recommended and bind an ItemsControl to the list of columns and generate the text that way. There is no reason you should need to do this by hand.

Related

How to set initial color of owner drawn control

Scenario
Having a Windows Forms Form-derived form that contains a Panel-derived control:
The form gets a black background color set:
public MyForm()
{
InitializeComponent();
base.BackColor = Color.Black;
}
And the panel control is configured to be double buffered and the like, as described here, here and here:
public MyPanel()
{
base.DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
UpdateStyles();
}
The actual drawing is done inside these overrides in MyPanel:
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
Wrong behaviour
Every now and then, when the form is initially shown, my owner drawn panel is shortly drawn as white, before my own drawing code is actually called:
After my drawing code is called, everything is drawn correctly and I never can reproduce the white background again:
Even resizing the window and panel does not make it flicker in white.
Enforcing the wrong behavior
I can enforce the initial white-drawn of my panel, if I put a sleep in the Shown event handler of my form:
private void MyForm_Shown(object sender, EventArgs e)
{
Thread.Sleep(1000);
}
The panel is shown in white for 1000 ms just before my owner-drawn paint code is being called.
My question
How can I avoid the initial white displaying when having an owner drawn/custom drawn panel?
My goal is to have the control "know" its initial background color (black in my example) right from the start, not after it is initially shown.
Some thoughts
I've tried to play with all kind of things including the CreateParams property, but with no visible success.
My initial idea was to provide some initial background color through the WNDCLASSEX structure, but after digging through the Reference Source, I still have no clue whether this is possible and would help.
Whole code
Just to be safe, following is my whole code.
MyForm.cs:
public partial class MyForm : Form
{
public MyForm()
{
InitializeComponent();
base.BackColor = Color.Black;
}
private void MyForm_Shown(object sender, EventArgs e)
{
Thread.Sleep(1000);
}
}
MyPanel.cs:
public class MyPanel : Panel
{
public MyPanel()
{
base.DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
UpdateStyles();
}
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
}
I had a custom control, pretty much completely self-drawn, that was dynamically getting added to a Form in the dropdown mechanism in PropertyGrid.
If I read your problem right, it's basically the same overall issue.
I had BackColor set and was fine. But with DoubleBuffer set, it seems to just ignore it for a bit.
Taking in all of the existing comments on the question, I was able to have this solution, and hope the details help someone else make their own.
Problem 1
My control flickered unless I repainted whole control every OnPaint.
If did proper painting only attempting to paint things that intersected the e.ClipRectangle, then it would flicker in real-time, like effects from invalidates that had to do when the mouse was moved. Attempt to Paint whole thing, no problem.
I watched trace output real-time and watched all of my draws and invalidates print, and never a time where should be introducing flicker myself, on myself directly.
Problem 2
If I turn on DoubleBuffered, then instead it flickered badly as the control was shown every time opening the dropdown. From white background only for 100-200 ms, at least, then to black and rendered foreground suddenly in one step.
Problem 3
I never actually needed double buffer. Both the problem 1 and 2 were always related to WM_ERASEBKGND.
The actual original flicker seems to be caused by WM_ERASEBKGND very briefly visibly whacking my already painted thing, right before I painted it again. Did not really need actual double buffering in my case. For some reason when I was blindly painting the whole list maybe the timing was different and was painting over the erase before could see it.
All that said, if I turn DoubleBuffered on which removes WM_ERASEBKGND via turning on AllPaintingInWmPaint, then initial background won't be painted until I suppose the double buffer and paint process works its way, all the way through the first time.
Problem 4
If I let the WM_ERASEBKGND "just happen", then it's still double painting, and I don't know if or when it might end up flicking anyway for someone else.
If I only turn on SetStyle(OptimizedDoubleBuffer, then I now know I'll be letting the initial background paint and not flicker on show. But I also know I'm using double buffer to mask the WM_ERASEBKGND for the entirety of the life of the control after it is shown.
So....
I did something like this:
Part 1
if the user of the control sees a need to double buffer doing something that might flicker, create a way for them to easily enable it, without forcing AllPaintingInWmPaint. Like if they want to use Paint or a DrawXXX event and doing something that animates or something related to mouse movement.
bool _isDoubleBuffer;
[Category("Behavior")]
public virtual bool DoubleBuffer
{
get { return _isDoubleBuffer; } // dont care about real SetStyle state
set
{
if (value != DoubleBuffer)
{
_isDoubleBuffer = value;
SetStyle(ControlStyles.OptimizedDoubleBuffer, value);
}
}
}
Part 2
Manage WM_ERASEBKGND yourself, as the choice is otherwise 1) always off with AllPaintingInWmPaint, and no background paint on show, or 2) violating what double buffer expects where it would be always masking the WM_ERASEBKGND.
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_ERASEBKGND:
if (_hasPaintForeground && _isDoubleBuffer)
return;
}
base.WndProc(ref m);
}
Part 3
You are now your own decider of what AllPaintingInWmPaint means.
In this case would want the initial messages to process like normal. When we knew for sure the .Net and DoubleBuffer side was finally kicking it, by seeing our first real paint happen, then turn WM_ERASEBKGND off for the duration.
bool _hasPaintForeground;
protected override void OnPaint(PaintEventArgs e)
{
// your paint code here, if any
base.OnPaint(e);
if (!_hasPaintForeground) // read cheaper than write every time
{
_hasPaintForeground = true;
}
}
Part 4
In my case, I also had originally gimped the OnBackground draw, which works if you are opaque drawing each element yourself in OnPaint. This allowed me to not have double buffer on for so long until I started following the clip and changed the timing so that I started also seeing the other WM_ERASEBKGND side effects and issues.
protected override void OnPaintBackground(PaintEventArgs e)
{ // Paint default background until first time paint foreground.
if (!_hasPaintForeground) // This will kill background image but stop background flicker
{ // and eliminate a lot of code, and need for double buffer.
base.OnPaintBackground(e);
} // PaintBackground is needed on first show so no white background
}
I may not need this part anymore, but if my DoubleBuffer is off then I would. So long as I'm always painting opaque in OnPaint covering the whole draw Clip area.
Addendum:
In addition to all of that....
Separate issue with text render.
It looks like if I render only 250 x 42, like two rows and two text renders, which all occur in one OnPaint, verified with Diagnostics.Trace.WriteLine, then the text renders at least one monitor frame later, every single time. Making it look like the text is flashing. Is just 2x paint background single color then 2x paint text each for rows.
However, if I attempt to paint the whole client area of like 250 x 512 or whatever, like 17 rows, even though the e.Clip is exactly those two rows, because I'm the one that invalidated it, then no flicker of the text, 0 flicker.
There is either some timing issue or other side effect. But that's 17 chances instead of two, for at least one row to flicker text where the whole background is shown before the text renders, and it never happens. If I try to only render rows that are in the clip area it happens every time.
There is def something going with .Net or Windows. I tried with both g.DrawString and TextRenderer.DrawText and they both do it. Flicker if draw 2, not flicker if attempt to draw 17. Every time.
Maybe has something to do with drawing text near the mouse pointer, when OnPaint comes back too quickly?
Maybe if I draw enough things or OnPaint takes longer to come back, it's doing double buffer anyway? Dunno
So....
It's a good thing I went through this exercise with the original question.
I may choose to just render the whole client every time, but I'll never be able to do it the "right way" without something like my example code above.

How to redraw quickly on a canvas. C# WINFORM

For my software, I am using a Timer from Systems.timer library, every time my timer ticks, it calls a method for repainting my screen. I do not want to clear the screen, then to repaint on it. I just want to paint the new areas on it directly.
At the beginning, I did this:
Constructor{
...
this.timer = new Timer
{
Interval = 10,
};
this.timer.Elapsed += OnPaint;
this.timer.start();
}
public void OnPaint(Object sender, EventArgs e)
{
This.Parent.OnPaintLoadingCircle();
This.Parent.OnPaintReadyToBePaintedAreas();
}
Then I noticed it was much faster for painting when the OnPaint method contains this:
public void OnPaint(Object sender, EventArgs e)
{
This.Parent.Invalidate();
}
So I have two questions:
QUESTION 1 :
Why is it faster???
Because when I call invalidate():
The UI thread clears the screen.
Then UI thread redraws the old areas
Then UI thread draws the loading circle
Then UI thread draws the new areas.
And when I call my two methods OnPaintLoadingCircle() and OnPaintReadyToBePaintedArea():
The timer thread draws the loading circle
Then the timer thread draws the new areas
QUESTION 2 :
I would like to know if it exists a way for asking a controller to draw it surface without clearing it. ( I tried this.Parent.Update(), this.Parent.Refresh(), both of them first clear the screen as well).
Thank you very much for helping me.
Why is it faster???
For the simplest of reasons: because when you call Invalidate() in the OnPaint() method, it forces re-painting of the window immediately, which is much more quickly than a timer could.
The timers in .NET are not suited for high-frequency operations. They only guarantee the time between intervals will be at least what you specify. The actual interval can and often is longer than what you specify, especially if you are using a very short interval (e.g. on the order of less than 10-20ms). This necessarily limits how often you can re-paint the window when using a timer, to a much greater degree than just re-painting the window as fast as you can.
I would like to know if it exists a way for asking a controller to draw it surface without clearing it.
Not easily, no. At the most basic level, you can override OnPaintBackground() and not call the base implementation. But this approach only works if you are prepared to redraw everything, because the system counts on you covering up stale pixels with the correct pixels when you draw.
In fact, a much more common approach is to use double-buffering. The most basic form is to just set the DoubleBuffered property in the control constructor. But you can also combine not clearing the window with maintaining your own offscreen Bitmap object into which you draw your content. Then when a Paint event happens, you just copy the Bitmap to the window.
A much more complicated approach involves hosting a Direct2D surface in your window. Not for the faint of heart, but should offer the best possible performance in a Winforms program.

Does setting the image of a PictureBox to the same image force a redraw?

I have a background thread that checks the communication status of a hardware device (a camera) and updates an image on my form accordingly with a green circle or red X.
public bool cameraStatus;
public MainForm()
{
InitializeComponent();
// add the UpdateDisplay function to a dictionary that will be periodically called
updateMethods.Add(new EventHandler(UpdateDisplay));
}
public void UpdateDisplay()
{
if (cameraStatus)
imgCameraStatus.Image = Properties.Resources.camera_good;
else
imgCameraStatus.Image = Properties.Resources.camera_bad;
}
The UpdateDisplay function gets called quite often, ever 50 ms or so. The majority of the time cameraStatus does not change so imgCameraStatus.Image keeps being set to the same value.
The image does not flicker and I am wondering if the image actually gets redrawn by the window each time or not, since the source does not change. I am not sure how to get to the lower-level painting function calls in the WinForms paint cycle.
Looking at the source code, setting the Image property calls a private method called InstallNewImage. This method will always call Invalidate which will schedule a redraw of the PictureBox in the message queue.

.NET UserControl: Size property gives incorrect value on Resize event

Excuse the code dump, these are functions within a UserControl
private void PNGQuantPreviewControl_Resize(object sender, EventArgs e)
{
createOffScreenBm();
draw();
}
private void createOffScreenBm()
{
offScreenBm = new Bitmap(this.Size.Width, this.Size.Height);
offScreenGfx = Graphics.FromImage(offScreenBm);
}
private void draw()
{
// draw background
offScreenGfx.FillRectangle(transTexture, 0, 0, offScreenBm.Width, offScreenBm.Height);
// draw image preview
offScreenGfx.DrawImage(pngQuantPreview, getTopLeftPosition());
// apply to picture box
this.CreateGraphics().DrawImage(offScreenBm, 0, 0);
}
So, when the control changes size, it recreates the offscreen bitmap to reflect the new size and redraws the image.
However, if I quickly resize the control the bitmap doesn't fill it, there's a gap left at the right and/or bottom.
I'm fairly new to C#, so there's probably something obvious I'm doing wrong, or I'm reading the size values at the wrong time. Any ideas?
First of all you need to overwrite OnPaint method, or subscribe to Paint event and draw everything there.
Second you do not need to create offscreen bitmap for double buffering, because in .net already exist class for such purposes BufferedGraphics.
And third, it is much better to create UserControl descedant and enable internal .net double buffering, something like this:
public UserControl2
{
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
}
Using this approach you will get double-buffering, and all you need is to draw your graphics in OnPaint method. You can read more about this control styles in Msdn.
Have you considered overriding the OnPaint method and placing the code within that method? This would result in your drawing code being executed any time the control needs to be redrawn, regardless of the reason.
A resize event does not necessarily wait until you are finished resizing the parent container. When the resize event is raised it needs to wait until the code exits before it can capture a new resize event so when the window/control is resized quickly, it can't keep up all that well and what you get is the last time it was able to capture the event, not necessarily the final state of the control ... if that makes any sense.
Do you have anything like a splitter on your control, or a MinSize or MaxSize declared?

Form opacity animation in C# with a BackgroundWorker

With the help of the BackgroundWorker, I created an opacity animation for some form.
There's only one tiny issue with this approach but I can't understand where is the problem. The animation speed is configurable and even if the speed value is very high, sometimes the animations is very, very slow, for some odd reason...
The "slow animation" I'm talking about it's not stutter, the animation is actually very smooth, it just takes more time to perform the whole animation (from 0% to 100%, or vice-verse). This only happens from time to time. It seems (not sure) that it happens when the computer is doing some other, somewhat intensive, background action.
I need to fix that of course but I also would like to know if there's anyway you would improve this code or if you would do it differently and/or better.
Here's my code:
private const int TOGGLE_EFFECT_SPEED = 10;
private void blendWorker_DoWork(object sender, DoWorkEventArgs e) {
bool blendIn = (bool)e.Argument;
// Loop through all opacity values
for(double value = 1; value <= 100; value += 1) {
// Report the current progress on the worker
blendWorker.ReportProgress(0, blendIn ? value : 100 - value);
// Suspends the current thread by the specified blend speed
System.Threading.Thread.Sleep(11 - TOGGLE_EFFECT_SPEED);
}
// Set the worker result as the inverse tag value
e.Result = !blendIn;
}
private void blendWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) {
double opValue = (double)e.UserState;
// Show and repaint the whole main notes window?
if(opValue == 1.0) {
Show();
Invalidate(true);
}
// Set the main notes window opacity value
Opacity = (double)e.UserState / 100;
}
private void blendWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
bool tagFlag = (bool)e.Result;
// Hide the main notes window?
if(tagFlag) {
Hide();
}
// Set the main notes window tag value
Tag = tagFlag;
}
/*
THE FOLLOWING METHOD IS PART OF A DIFFERENT CLASS.
ACTUALLY, IT'S THE "PROGRAM" CLASS WHERE MAIN()
IS LOCATED. THIS METHOD IS CALLED TO SHOW/HIDE
THE MAIN APPLICATION FORM WITH AN OPACITY ANIMATION
*/
internal static void ToggleNotesWindow() {
// Get the tag value converted to boolean type
bool tagFlag = Convert.ToBoolean(NotesWindow.Tag, CultureInfo.InvariantCulture);
// Bring the main notes window to front?
if(tagFlag) Program.NotesWindow.BringToFront();
// Run the blend effect if it's not already running
if(!NotesWindow.blendWorker.IsBusy) {
NotesWindow.blendWorker.RunWorkerAsync(tagFlag);
}
// Activate and focus the main notes window?
if(tagFlag) Program.NotesWindow.Activate();
}
Each time you change the opacity of the form, Windows has to redraw all the windows below it, the window itself and then apply opacity (Vista does this much faster and buffered). Since you're stepping through each opacity state from 1...100 this process has to complete 100 times. Sometimes redrawing your window or a window below it will be slow.
The Thread.Sleep method with a value > 0 will sleep from 0...~10ms no matter what value you pass. The thread scheduler timer resolution on Windows is approx 10ms (Again, Vista and other OS change and optimize so it's not exact) so you can't schedule a time slice smaller than that. 100x10ms + the time to actually render might take 2 whole seconds to fade in/out.
The way to speed it up is to reduce the number of re-draws. Rather than stepping +1 for opacity, step +5, +10, etc. That reduces the total number of re-draws required and would result in a form fading in 100ms instead.
Overall I don't see much that would warrant a change at least at first glance. If you are seeing some performance bottlenecks you might try having a look at Ants Profiler or a similar code profiling tool to see if you can pinpoint any slow sections.

Categories