.NET Framework - Draw Line is to slow, there is anything else ?

Asked By RobertoA
07-Mar-11 03:03 AM
We use this code for draw an x,y chart
-----------------------------------------------------
for (int count = 1; count <= Width; count = count + 1)
{
int punto_shiftato_nella_serie = count + (int.Parse(txtOffsetX.Text) * 25);
int punto_shiftato_nella_serie_piu_uno = punto_shiftato_nella_serie + 1;

// Disegno linea grafico sensore 2
if (chkSegnale2.Checked)
{
float X1 = count * valore_zoom_in_X;
float Y1 = offset_in_Y + Serie2[punto_shiftato_nella_serie] *
valore_zoom_in_Y;
float X2 = (count + 1) * valore_zoom_in_X;
float Y2 = offset_in_Y + Serie2[punto_shiftato_nella_serie_piu_uno] *
valore_zoom_in_Y;
formGraphics.DrawLine(myPen, X1, Y1, X2, Y2);
}
}
-----------------------------------------------------

Is all ok but very slow, a screen redraw (there are 8 cycle like this) take
1,5 - 2 sec
There is alternative way, too fast,  to draw chart like sinusoidal curve
(api, opengl, ...) ?
RobertoA
Windows XP
(1)
Windows Media
(1)
BufferedGraphicsManager
(1)
Silverlight
(1)
CompositionTarget
(1)
BufferedGraphics
(1)
DrawingContext
(1)
DrawingVisual
(1)
  kndg replied to RobertoA
07-Mar-11 03:40 AM
If you change your code to below, is there an improvement?

if (chkSegnale2.Checked)
{
int offsetX = Int32.Parse(txtOffsetX.Text);

for (int count = 1; count <= Width; count++)
{
int punto_shiftato_nella_serie = count + offsetX * 25;
int punto_shiftato_nella_serie_piu_uno = punto_shiftato_nella_serie
+ 1;

float X1 = ...
float Y1 = ...
float X2 = ...
float Y2 = ...

formGraphics.DrawLine(myPen, X1, Y1, X2, Y2);
}
}

If you still find the GDI is slow to your need, maybe a migrate to WPF?
  RobertoA replied to kndg
07-Mar-11 06:23 AM
Thank for your reply
No speed increment, with code correction, i think that compiler optimization
work for me and modify where is possibile to obtain the max speed of
execution
What mean, by code point of view, migrate to Wpf ?
RobertoA
  Rick Lones replied to RobertoA
07-Mar-11 10:19 AM
it is a long time since I have done Windows graphics, but:
Assuming you are drawing directly to a visible screen control, have you tried
double buffering?  I.e., make your updates to a second invisible control and
then display that only after the update loop is complete?  The hope would be
that in that case you will not be slowed down by multiple screen refreshes.

HTH,
-rick-
  Peter Duniho replied to RobertoA
07-Mar-11 11:32 AM
You must have a huge number of lines if it takes 2 seconds to draw all
of them.

For sure, you should pull the call to int.Parse() and the "if" statement
out of the loop as "kndg" suggested.  Whether you notice an improvement
or not, if you care about speed there is no sense in starting with
handicapped code.  :)

Also, Rick's suggestion to double-buffer ??? i.e. draw into an off-screen
Bitmap instance first (whenever the underlying data changes???_not_ on
every call to OnPaint()), and then use that to update the control when
needed (i.e. when OnPaint() is called) ??? is another good one for
improving speed.

You also should use the DrawLines() method, calling it _once_, where you
pass a list of points that are all connected, rather than making "Width"
calls to the DrawLine() method.

However, the fact remains that for a normal-sized control, with a number
of lines that would fit in a plain graph shown on screen, there is no way
it would take 2 seconds to draw the whole thing, even calling DrawLine()
once for each line, unless you are running on a 15-year-old computer.  So
really, the best solution is either to upgrade your computer to
something more recent, or to fix your graph-drawing so that it is
generating only a number of lines to draw that is appropriate to the size
of the control.

If the "Width" in your loop is really the width of the control in
pixels, and your computer is a recent model, then there is something else
going on here that you are not telling us.  For example, some very
expensive operation hidden in the "Serie2" indexer.  If that is the case,
then to get any useful help you need to post a concise-but-complete code
example that reliably reproduces the problem.

Pete
  RobertoA replied to Rick Lones
07-Mar-11 12:08 PM
Yes, may be a video refresh that take major of time
What is the right manner to use double buffer ?
  RobertoA replied to Peter Duniho
07-Mar-11 12:13 PM
I think that compiler optimization do it for us


Yes, is probable, but i dont know how do 'double buffering'


My pc is an Athlon 6000 (3 GHz), not very new but not very very old


Thank very much
RobertoA
  Peter Duniho replied to RobertoA
07-Mar-11 12:19 PM
I doubt it.  The compiler has no way to know that the input to either
int.Parse() or the "if" statement is invariant.

But more to the point: while I am certainly against premature,
code written the way you have it.  It does not make it any easier to read
or maintain, and only puts you behind from the outset.

For sure, if you have a performance problem, you should not "think that
the compiler optimization do it".  If you are hoping for an optimization
and refusing to rearrange the code so that it is better-optimized in the
C# itself, then you _must_ look at the native code generated at run-time
to verify that the expected optimization has been made.

You can do that of course, and who knows?  Maybe I am wrong and the
compiler does optimize those invariants out of the loop.  But it is a
serious programming error to just _assume_ that the compiler will make
those optimizations (and I really do doubt that it does).


All your drawing code, just draw into a Bitmap instance instead of the
Graphics instance you got from the PaintEventArgs.  You can get a
Graphics instance from the Bitmap instance by using the
Graphics.FromImage() method.  Draw into that Graphics instance instead
of the one from the Paint event.

Do that whenever the data being rendered has changed.  Then when you are
handling the Paint event (e.g. in your OnPaint() override), just draw
the Bitmap instance instead of all the lines.


That's plenty fast for this.  There is really nothing in the code you
posted that would explain why it should take 2 seconds to draw your
graphics.  So if you _really_ want to fix this issue, you need to heed
my last paragraph and respond to it:
  RobertoA replied to Peter Duniho
07-Mar-11 12:40 PM
Yes, it is right


Now i try


Thank very much
  kndg replied to RobertoA
08-Mar-11 08:40 PM
If you did not see an improvement, then probably the slow down is at the
other code that you did not show. As others had pointed out, drawing a
simple chart does not take that long and maybe a complete-but-consice
code example would help others point out the problem.

WPF is a new API for developing a windows application in which the
rendering is accelarated by the graphics hardware (through DirectX). If
you had done all the optimization you could and you feel it is the limit
of GDI, then moving to WPF might help.
  Peter Duniho replied to kndg
08-Mar-11 09:03 PM
For what it is worth, I am not aware of any research that suggests that
WPF's advantages lie in the area of performance.  In fact, a number of
sources indicate that at least for simple graphics, WPF is significantly
slower, due to its more complex layout engine.

Remember: while WPF is using 3D hardware acceleration for rendering,
GDI+ does use hardware acceleration as well, just a different kind.  And
not all of the time spent in presenting graphics is actually spent in
the rendering itself.

There are certain kinds of rendering for which WPF is well-suited,
especially in the area of animation and transformations, but for just
drawing plain lines on the screen, it is unlikely using WPF will improve
things.  If anything, it is more likely to worsen whatever problem
already exists in the code.

One area in which WPF is _much_ worse than Forms is in library size and
startup time.  Library size may not matter much, as long as you have got
lots of RAM (i.e. any computer made within the last five years or so),
but the extra time spent on app startup will be noticeable always.
Whether it is a worthwhile trade-off will depend on one's needs; for many
programs, the extra startup time just will not matter at all.

Here are a couple of interesting URLs:
http://stackoverflow.com/questions/202079/wpf-versus-winforms
http://msdn.microsoft.com/en-us/library/aa970683.aspx

Pete
  kndg replied to Peter Duniho
09-Mar-11 01:42 AM
I did below simple tests (drawing horizontal & vertical line) to measure
the performance of both APIs.

1. Winform (on my system, each rendering takes about 2000ms)

using System;
using System.Drawing;
using System.Diagnostics;
using System.Windows.Forms;

namespace MyNamespace
{
public class MyForm : Form
{
private Stopwatch stopwatch = new Stopwatch();
private Timer timer = new Timer();

private const int LINE_WIDTH = 800;

public MyForm()
{
Width = LINE_WIDTH;
Height = LINE_WIDTH;
StartPosition = FormStartPosition.CenterScreen;

timer.Interval = 5000;
timer.Tick += (o, e) => Invalidate();
timer.Start();
}

protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
stopwatch.Restart();

for (int i = 0; i < 50; i++)
{
for (int x = 0; x < LINE_WIDTH; x += 3)
{
g.DrawLine(Pens.Blue, x, 0, x, LINE_WIDTH);
}

for (int y = 0; y < LINE_WIDTH; y += 3)
{
g.DrawLine(Pens.Red, 0, y, LINE_WIDTH, y);
}
}

stopwatch.Stop();
Console.WriteLine(String.Format("Elapsed: {0}",
stopwatch.ElapsedMilliseconds));
}
}

public class MyClass
{
[STAThread]
public static void Main(string[] args)
{
Application.Run(new MyForm());
}
}
}

2. WPF using basic shape (on my system, it takes around 800ms)

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Threading;

namespace MyNamespace
{
public class MyWindow : Window
{
private Canvas canvas;
private Stopwatch stopwatch = new Stopwatch();
private DispatcherTimer timer = new DispatcherTimer();

private const int LINE_WIDTH = 800;

public MyWindow()
{
Width = LINE_WIDTH;
Height = LINE_WIDTH;
WindowStartupLocation = WindowStartupLocation.CenterScreen;

canvas = new Canvas();
Content = canvas;

timer.Interval = new TimeSpan(0, 0, 5);
timer.Tick += (o, e) => Render();
timer.Start();
}

public void Render()
{
canvas.Children.Clear();
stopwatch.Restart();

for (int i = 0; i < 50; i++)
{
for (int x = 0; x < LINE_WIDTH; x += 3)
{
AddLine(Brushes.Blue, x, 0, x, LINE_WIDTH);
}

for (int y = 0; y < LINE_WIDTH; y += 3)
{
AddLine(Brushes.Red, 0, y, LINE_WIDTH, y);
}
}

stopwatch.Stop();
Console.WriteLine(String.Format("Elapsed: {0}",
stopwatch.ElapsedMilliseconds));
}

public void AddLine(Brush brush, int x1, int y1, int x2, int y2)
{
var line = new Line();
line.Stroke = brush;
line.X1 = x1;
line.Y1 = y1;
line.X2 = x2;
line.Y2 = y2;
canvas.Children.Add(line);
}
}

public class MyClass
{
[STAThread]
public static void Main(string[] args)
{
Application app = new Application();
app.Run(new MyWindow());
}
}
}

3. WPF raw rendering (on my system, it takes less than 20ms)

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;

namespace MyNamespace
{
public class MyWindow : Window
{
private DispatcherTimer timer = new DispatcherTimer();
private const int LINE_WIDTH = 800;

public MyWindow()
{
Width = LINE_WIDTH;
Height = LINE_WIDTH;
WindowStartupLocation = WindowStartupLocation.CenterScreen;

var drawing = new MyDrawingElement(LINE_WIDTH);
Content = drawing;

timer.Interval = new TimeSpan(0, 0, 5);
timer.Tick += (o, e) => drawing.InvalidateVisual();
timer.Start();
}
}

public class MyDrawingElement : FrameworkElement
{
private static Pen bluePen = new Pen(Brushes.Blue, 1.0);
private static Pen redPen = new Pen(Brushes.Red, 1.0);

private Stopwatch stopwatch = new Stopwatch();
private int lineWidth;

public MyDrawingElement(int lineWidth)
{
this.lineWidth = lineWidth;
}

protected override void OnRender(DrawingContext drawingContext)
{
stopwatch.Restart();

for (int i = 0; i < 50; i++)
{
for (int x = 0; x < lineWidth; x += 3)
{
drawingContext.DrawLine(bluePen, new Point(x, 0), new
Point(x, lineWidth));
}

for (int y = 0; y < lineWidth; y += 3)
{
drawingContext.DrawLine(redPen, new Point(0, y), new
Point(lineWidth, y));
}
}

stopwatch.Stop();
Console.WriteLine(String.Format("Elapsed: {0}",
stopwatch.ElapsedMilliseconds));
}
}

public class MyClass
{
[STAThread]
public static void Main(string[] args)
{
Application app = new Application();
app.Run(new MyWindow());
}
}
}

(I test the above using command line, so if using the IDE, someone may
replace the Console.WriteLine statement with the Trace.WriteLine)

From the above test, I still found that the WPF is rendering way much
faster (even using shape) compared to WinForm. But the wierd thing is,
although the rendering is fast, but the time to display it to the screen
is very slow (even slow compare to WinForm) and on test #3, the windows
simply goes to unresponsive state. I am no expert in WPF and probably I
code it wrong, but hopefully someone can explain this behavior.


Thanks. I have not go through it yet but I find the discussion on
stackoverflow interesting.
  Peter Duniho replied to kndg
09-Mar-11 10:46 AM
The main issue is that the tests are not valid.

First, in the Forms case, you are not drawing in an optimized way, so
it is not a fair test of Forms.  If you set the DoubleBuffered property
to "true", you will find that the time it takes to actually draw drops
dramatically.

it is easy to make one program win a benchmark if you hobble the other
program.  :p

On my own computer, with the window full maximized (i.e. no clipping of
the drawing), it can take as long as 5 seconds to draw the ~25,000 lines
without double-buffering.  But if I enable double-buffering, the drawing
itself takes around 50-60ms.

Second, in your first WPF program you are not measuring the time it takes
to draw; you are measuring the time it takes to add the lines to the
layout.  The drawing happens later.  So you are not even comparing apples
to apples.

The second WPF program is theoretically more correct, but since WPF does
double-buffering by default, it is still not an apples-to-apples
comparison unless you fix your Forms program to do the same.  More
problematic is the issue of the program not being responsive.  I am just
copying-and-pasting the code you wrote, not being a WPF expert myself
and I do not have time at the moment the look into it.

I note that the program does eventually respond; it is just very slow.
This suggests that it is actually doing quite a lot of work, and that
that work is related to the drawing.  In other words, I think it is
entirely likely that in spite of what the API looks like, most of the
work is not in fact being done in the FrameworkElement's OnRender()
override.

The fact that the app remains sluggish even if I do not start the timer
and set the loop in OnRender() to execute only one iteration strongly
suggests that is in fact the case, because the measured time is in that
case small enough that my framerate would exceed the refresh rate of my
monitor (60hz???the measured time actually allows for a frame rate of
500-1000 fps), and yet the window is responding at a frame rate of maybe
10-20 fps or so (rough guess based on extensive experience with
sluggishly-rendering programs).

Unfortunately, the documentation is not very specific about how a
DrawingContext works or what really happens when you call DrawLine().
But it appears to me that it is not simply rendering immediately a line
into some buffer (off-screen or otherwise).

A much better test would be to look at actual framerates for repeating
rendering.  I suspect that you would get a more realistic comparison
doing that.  And I doubt that, assuming you enable double-buffering for
the Forms program, you would  see WPF being genuinely faster than Forms.

Pete
  kndg replied to Peter Duniho
10-Mar-11 03:25 AM
Ah yes, I miss that. I enable double-buffering and the time improve to
600ms.


Okay.


I made a second version of the tests below which count the frame rates.
On Winform, I get a nice 70 fps while the WPF version is somewhat lower
at 35 fps. What still mysterious is, while the Winform version showing
the nice sequence of rendering work, I cannot see that in the WPF
version. It just show the last rendering sequence when the time is out.
It is maybe due to the different rendering model between the two APIs
(Winform - immediate mode, WPF - retained mode). But, do you have any
idea how to force it to behave like the Winform version. It may becomes
useful in situation where you need to simulate a waveform - for example.

1. Winform

using System;
using System.Drawing;
using System.Diagnostics;
using System.Windows.Forms;
using System.Threading;

namespace MyNamespace
{
public class MyForm : Form
{
private static readonly Pen[] pens = new[] {
Pens.Red,
Pens.Green,
Pens.Blue,
Pens.Cyan,
Pens.Yellow,
Pens.Magenta,
};

private static readonly Random random = new Random();

private const int LINE_WIDTH = 800;
private volatile bool isRendering = true;
private int frameCount = 0;

private Graphics graphics = null;
private BufferedGraphics gfx = null;

public MyForm()
{
Width = LINE_WIDTH;
Height = LINE_WIDTH;
StartPosition = FormStartPosition.CenterScreen;

gfx = BufferedGraphicsManager.Current.Allocate(CreateGraphics(),
new Rectangle(0, 0, LINE_WIDTH, LINE_WIDTH));
graphics = gfx.Graphics;

Shown += (o, e) => TestRenderingSpeed();
}

private void TestRenderingSpeed()
{
Console.Write(String.Format("Run for 10 seconds... "));

new Thread(() =>
{
Thread.Sleep(10000);
isRendering = false;
}).Start();

int totalPens = pens.Length;

while (isRendering)
{
DrawLines(graphics, pens[random.Next(totalPens)],
pens[random.Next(totalPens)]);
gfx.Render();
}

Console.WriteLine(String.Format("Finished.\nFrame count: {0}
Rate: {1} fps", frameCount, frameCount / 10));
}

private void DrawLines(Graphics g, Pen pen1, Pen pen2)
{
g.FillRectangle(Brushes.Black, 0, 0, LINE_WIDTH, LINE_WIDTH);

for (int x = 0; x < LINE_WIDTH; x += 3)
{
g.DrawLine(pen1, x, 0, x, LINE_WIDTH);
}

for (int y = 0; y < LINE_WIDTH; y += 3)
{
g.DrawLine(pen2, 0, y, LINE_WIDTH, y);
}

frameCount++;
}
}

public class MyClass
{
[STAThread]
public static void Main(string[] args)
{
Application.Run(new MyForm());
}
}
}

2. WPF

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
using System.Threading;

namespace MyNamespace
{
public class MyWindow : Window
{
private static readonly Pen[] pens = new[] {
new Pen(Brushes.Red, 1.0),
new Pen(Brushes.Green, 1.0),
new Pen(Brushes.Blue, 1.0),
new Pen(Brushes.Cyan, 1.0),
new Pen(Brushes.Yellow, 1.0),
new Pen(Brushes.Magenta, 1.0),
};

private static readonly Random random = new Random();

private MyDrawing drawing;
private const int LINE_WIDTH = 800;
private volatile bool isRendering = true;

public MyWindow()
{
Width = LINE_WIDTH;
Height = LINE_WIDTH;
WindowStartupLocation = WindowStartupLocation.CenterScreen;

drawing = new MyDrawing(LINE_WIDTH);
Content = drawing;

Loaded += (o, e) => TestRenderingSpeed();
}

private void TestRenderingSpeed()
{
Console.Write(String.Format("Run for 10 seconds... "));

new Thread(() =>
{
Thread.Sleep(10000);
isRendering = false;
}).Start();

int totalPens = pens.Length;

while (isRendering)
{
drawing.DrawLines(pens[random.Next(totalPens)],
pens[random.Next(totalPens)]);
}

Console.WriteLine(String.Format("Finished.\nFrame count: {0}
Rate: {1} fps", drawing.FrameCount, drawing.FrameCount / 10));
}
}

public class MyDrawing : FrameworkElement
{
private DrawingVisual visual;
private readonly int lineWidth;
private int frameCount = 0;

public int FrameCount
{
get { return frameCount; }
}

public MyDrawing(int lineWidth)
{
this.lineWidth = lineWidth;
visual = new DrawingVisual();
}

public void DrawLines(Pen pen1, Pen pen2)
{
using (DrawingContext dc = visual.RenderOpen())
{
for (int x = 0; x < lineWidth; x += 3)
{
dc.DrawLine(pen1, new Point(x, 0), new Point(x, lineWidth));
}

for (int y = 0; y < lineWidth; y += 3)
{
dc.DrawLine(pen2, new Point(0, y), new Point(lineWidth, y));
}
}

frameCount++;
}

protected override int VisualChildrenCount
{
get { return 1; }
}

protected override Visual GetVisualChild(int index)
{
return visual;
}
}

public class MyClass
{
[STAThread]
public static void Main(string[] args)
{
Application app = new Application();
app.Run(new MyWindow());
}
}
}
  Peter Duniho replied to kndg
10-Mar-11 10:49 AM
That still sounds very slow (note that on my computer, which for your
initial test was slower than yours, I can render a single update in
50-60 ms or so).  Especially given???


70 fps would require a rendering time per frame of around 14ms, much
smaller than the 600ms you observe.  The discrepancy is probably related
to the fact that you are not really testing frame rate, because the
screen is not being updated between iterations of your drawing.

Fortunately, you have made the same mistake in the WPF version.  I would
guess the errors almost cancel each other out (but really, the up-shot
is that the results are inconclusive).


Sorry, I am not sure.  I have not had enough experience with WPF to know
how to force it to do something against its will.  Yet.  :)

That said, looking at your code, I think that if you were to figure out
how let the WPF dispatcher run between frames, you would  see the screen
update.  Either break the loop out as a repeatedly-called operation
invoked by the WPF dispatcher, or call something that runs the
dispatcher temporarily (similar to Application.DoEvents() in Forms???yuck!).

Of course, were you to do that, your "frame rate" (currently it is not
fair to call it a frame rate, because the screen is not actually
updating) would be even slower than 35 fps, probably significantly slower.

Bottom line: a correct performance comparison would constantly update
the _screen_, and measure how many _real_ displayed frames the program
can achieve over a measured amount of time.  Personally, I think that
the flawed tests you have done so far do demonstrate that at the
suggesting strongly that overall frame rate would be much better for
Forms than for WPF as well.  But the tests are inconclusive until in
both APIs, they are _really_ producing a single screen update for each

Pete
  kndg replied to Peter Duniho
10-Mar-11 10:50 PM
That strange... I did the test again (with no other application is
running) and the result still the same. 2000ms on original test, 600ms
when DoubleBuffered property set to "true". I did the test on Windows XP
(I know it is an old OS, but I do not know why I am still stuck with it)


Actually, there is a different on the second version. I had removed the
loop (50 iteration one), so the graphics just render 266 horizontal
lines and 266 vertical lines.

I also do not understand what you mean by the 'screen is not being updated
between iterations'. As from my code, I did a call to Render() method
which render the backbuffer graphics to the screen. Doesn't that mean
that the screen is updating or do you mean something else?


I had read about the CompositionTarget in which we can hook our method
to the Rendering event which will be called each time the WPF decide to
redraw itself. But I have no idea, when the time is. As per my code
sample, I change the DrawingContext each iteration and I think WPF
should mark the buffer as dirty and should redraw itself but from the
code observation, the screen only get updated on last iteration. Does
this have to do with threading? (Since the GUI thread is busying with
updating the DrawingContext, it has not get a chance to process the
WM_PAINT message?)


Is that mean that my above statement is correct? Even if that so, I have
no idea how to do that. I will googling about it or maybe search for the
equivalance of Application.DoEvents in WPF.


Thanks for your thought as always. I do find that the WPF is
revolutionary, but it is still full of mysteries which make the
understanding of it is challenging.

Regards.
  Peter Duniho replied to kndg
10-Mar-11 11:35 PM
I do not know either.  Other than to say that it is true that performance
of graphics operations is highly dependent on the operation being
performed, the video driver, and the hardware itself.  Your results are
odd (600 ms is a very long time for the amount of work being done), but
not impossible (as you obviously know :) ).


No, I overlooked the call to Render().  Assuming the screen really does
change with every iteration, then perhaps it is a relevant test after all.


Yes, I believe that is what is happening.  All the work you are doing
drawing is not going to have any effect until the WPF dispatcher gets to
run again, which does not happen until you are done with the entire loop.


I think WPF is great.  I just do not think that it is generally going to
be the answer to a performance problem.  Every WPF program I have ever
seen is slower than an equivalent Forms would be, albeit a lot prettier.  :)

There may in fact be scenarios where WPF works better than Forms,
performance-wise.  But I do not think that as a general rule, that would
be true.

Pete
  James A. Fortune replied to Peter Duniho
15-Mar-11 02:40 PM
=A0:)

Seema Ramchandani (Perf and Graphics) explained in one of the PDC 08
sessions that she uses XPerf to analyze WPF performance.  See:

http://msdn.microsoft.com/en-us/library/ff191077.aspx

I remember that the demonstration showed which parts of the screen
were being redrawn at each step.  She mentioned that WPF had been
enhanced to be more intelligent about "dirty" areas and about
threading.  Perhaps XPerf will help determine performance
differences.  The session was PC06, titled:

Building a Graphics-Intensive Application in Silverlight

Note: When paused for the first time or when the session is finished,
any of the Windows Media Player files made available by Microsoft
apparently try to access the internet for some reason.  Still, I thank
Microsoft for making PDC sessions available.  I have learned much
valuable information from them.  I do not know how to make WPF behave
like the Winform version, but I remember from the same session that
she used the following code to help her with intensive graphics:

sender.settings.enableFrameRateCounter =3D true;
sender.settings.MaxFrameRate =3D 1000;

I.e., she tried to push it to the limit and then find out where the
bottlenecks were using XPerf.  I do not doubt your observations, but I
do not find fault with developers using Winforms because they are more
comfortable with them, even in cases where WPF performs better.

James A. Fortune
CDMAPoster@FortuneJames.com

The first "Effective C#" book has a second edition, written in 2010,
that updates the advice in the book up to C# 4.0.