【发布时间】:2014-12-20 21:06:01
【问题描述】:
我在 WPF 的几个画布上绘制了大量的线条和文本。我在 WPF 中使用了最轻量级的元素:DrawingVisual
我在不同的画布上绘制了线条,并将它们的thickness 绑定到缩放因子的倒数,以便在缩放时获得均匀的线条粗细。这意味着当我缩放时,我只是用线条重绘画布。带有文本的画布仅在程序开始时创建。
现在我遇到了一个相当奇怪的问题。当我缩放时,我的表现很慢。当我使用虚线样式时,性能会变差。速度很慢,就像您在一个单词中加载一个大文本文件并且滚动它时遇到问题!
我的第一个想法是,线条的创建可能需要太多时间。由于我在每次缩放时创建这些(在鼠标滚轮上),所以我使用了一个适度的秒表来测量从滚动鼠标滚轮到线条创建结束之间的时间。令我惊讶的是,它只需要 1 毫秒!所以线条的创建不是问题。
经过进一步检查,我发现我在平移时的性能相当缓慢!当您尝试在 Windows 中平移非常非常大的图像时,速度会很慢!
那么可能是什么问题?我知道你会想要一些代码,但由于代码很长,我只会展示 Mousewheel 事件中发生的事情:
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);
}
我开始认为这可能与 WPF 中呈现事物的方式有关,或者可能与 WPF 中鼠标按钮的处理方式有关? 似乎有什么东西干扰了鼠标事件频率。任何建议都非常感谢。
更新:我尝试实现 GameAlchemist 在我的代码中所说的内容,但令我惊讶的是它仍然很慢。
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);
}
我发现重画线条可能是罪魁祸首,但似乎不是!我删除了对实际使用新厚度重绘它们的函数的调用,只留下了缩放部分。结果没变!我仍然缺乏表现。
private void CompositionTargetOnRendering(object sender, EventArgs eventArgs)
{
if (zoomNeeded)
{
zoomNeeded = false;
DoZoom();
}
}
我尝试使用 WPF 性能工具测量 FPS,它在缩放期间回落到 16~24!
【问题讨论】:
-
试用 WPF 的性能分析工具。
-
每秒执行多少次?因为如果超过60次,那就是浪费时间。您可能会考虑只存储一些数据 + 在鼠标处理程序中设置一个“脏”标志,并有一个名为 30 次/秒的绘制方法,如果脏了则重绘。
-
@GameAlchemist 感谢您关注我几个月来一直坚持这个问题。不幸的是,我不知道如何测量它的帧率。我怎么能得到那个?你能帮我举个小例子作为答案吗?前段时间我也问了一个类似的问题,一位用户给了我一些关于鼠标频率的提示,但我无法理解。你可以在这里看到它:stackoverflow.com/questions/24119311/…
-
@GameAlchemist 顺便说一句,我在平移时也遇到了这个问题。
-
你引用的答案很有趣。通过在 mouseWheel 处理程序和其他鼠标处理程序中处理脏标志来寻求我建议的解决方案,然后在 CompositionTarget.Rendering 上挂钩单个绘制方法。只有在明显设置了脏标志的情况下才应该重绘,然后在重绘后将其设置为false。
标签: c# wpf wpf-controls