I have drawn a good deal of lines and texts on several canvases in WPF. I have used the most lightweight element possible in WPF: DrawingVisual
I have drawn lines on a different canvas and I have bound their thickness
to the inverse of the zoom factor so that I can get a uniform line thickness while zooming. That means when I'm zooming I'm only redrawing the canvas with the lines. The canvas with the texts is only created at the start of the program.
Now I have encountered a rather odd problem. When I'm zooming I'm getting a slow performance. The performance gets worse when I use dashed line style. Slow like when you load a big text file in a word and you have problems scrolling it!
My first thought was that maybe the creation of the lines is taking too much. Since I'm creating those at each zoom (On mouse wheel), So I used a modest stopwatch to measure the time it takes between when I scroll mouse wheel till the creation of the lines ends. To my surprise it only takes 1 ms! So the creation of the lines cannot be the issue.
With further examination I figured out that I'm getting a rather slow performance while panning! Slow like when you a trying to pan in very very big image in windows!
So what could be the problem? I know you will want some code but since the code is very long I'll only show what goes on inside Mousewheel
event:
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
var sw = new Stopwatch();
sw.Start();
var st = GetScaleTransform(Window);
var tt = GetTranslateTransform(Window);
var absoluteX = MousePos.Current.X * st.ScaleX + tt.X;
var absoluteY = MousePos.Current.Y * st.ScaleY + tt.Y;
const double zoomfactorforwheel = 1.3;
if (e.Delta > 0)
{
st.ScaleX = Math.Min(st.ScaleX * zoomfactorforwheel, Scalemax);
st.ScaleY = Math.Min(st.ScaleY * zoomfactorforwheel, Scalemax);
}
else
{
st.ScaleX = Math.Max(st.ScaleX / zoomfactorforwheel, Scalemin);
st.ScaleY = Math.Max(st.ScaleY / zoomfactorforwheel, Scalemin);
}
tt.X = absoluteX - MousePos.Current.X * st.ScaleX;
tt.Y = absoluteY - MousePos.Current.Y * st.ScaleY;
Scale = st.ScaleX;
// Inside this function I'm drawing the lines on drawingvisual
// Then I'm adding the drawingvisual to a canvas
DrawZeroWidthDrawing(Scale);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
I'm starting to think that maybe it has to do something with the way things get rendered in WPF, or maybe with how mouse buttons are getting handled in WPF? It seems like something is interfering with mouse event frequency. Any suggestions are very appreciated.
Update: I have tried to implement what GameAlchemist said in my code to my surprise it is still slow.
private MouseWheelEventArgs e;
private bool zoomNeeded;
CompositionTarget.Rendering += CompositionTargetOnRendering;
private void CompositionTargetOnRendering(object sender, EventArgs eventArgs)
{
if (zoomNeeded)
{
zoomNeeded = false;
DoZoom();
DrawZeroWidthDrawing();
}
}
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
this.e = e;
zoomNeeded = true;
}
private void DoZoom()
{
var st = GetScaleTransform(Window);
var tt = GetTranslateTransform(Window);
var absoluteX = MousePos.Current.X * st.ScaleX + tt.X;
var absoluteY = MousePos.Current.Y * st.ScaleY + tt.Y;
const double zoomfactorforwheel = 1.3;
if (e.Delta > 0)
{
st.ScaleX = Math.Min(st.ScaleX * zoomfactorforwheel, Scalemax);
st.ScaleY = Math.Min(st.ScaleY * zoomfactorforwheel, Scalemax);
}
else
{
st.ScaleX = Math.Max(st.ScaleX / zoomfactorforwheel, Scalemin);
st.ScaleY = Math.Max(st.ScaleY / zoomfactorforwheel, Scalemin);
}
tt.X = absoluteX - MousePos.Current.X * st.ScaleX;
tt.Y = absoluteY - MousePos.Current.Y * st.ScaleY;
Scale = st.ScaleX;
}
private static TranslateTransform GetTranslateTransform(UIElement element)
{
return (TranslateTransform)((TransformGroup)element.RenderTransform)
.Children.First(tr => tr is TranslateTransform);
}
private static ScaleTransform GetScaleTransform(UIElement element)
{
return (ScaleTransform)((TransformGroup)element.RenderTransform)
.Children.First(tr => tr is ScaleTransform);
}
I figured out maybe redrawing the lines is the culprit but it seems not! I removed the call to the function that actually redraws them with the new thicknesses and only left the scaling part. The result didn't change! I'm still lacking performance.
private void CompositionTargetOnRendering(object sender, EventArgs eventArgs)
{
if (zoomNeeded)
{
zoomNeeded = false;
DoZoom();
}
}
I tried to measure the FPS using WPF performance tool, it falls back to 16~24 during zoom!