我正在使用WPF创建大量文本DrawText
,然后将它们添加到单个文本中Canvas
。
我需要在每个MouseWheel
事件中重新绘制屏幕,并且我意识到性能有点慢,所以我测量了对象创建的时间,它不到1毫秒!
那么可能是什么问题呢?很久以前,我想我在某处读到它实际上是在Rendering
花费时间,而不是创建和添加视觉效果。
这是我用来创建文本对象的代码,我只包含了必要的部分:
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;
}
}
}
并且此代码在每次MouseWheel
触发事件时运行:
var columnsGroupIds = new ColumnIdsInPlan(this);
MyCanvas.Children.Clear();
FixedLayer.Children.Add(columnsGroupIds);
可能是罪魁祸首?
平移时我也遇到了麻烦:
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;
}
我目前正在处理可能相同的问题,并且发现了一些出乎意料的事情。我正在渲染到WriteableBitmap,并允许用户滚动(缩放)和平移以更改渲染的内容。对于缩放和平移而言,该运动都显得不稳定,因此我自然地认为渲染花费的时间太长。经过一番检测,我确认我以30-60 fps的速度进行渲染。无论用户如何缩放或平移,渲染时间都不会增加,因此断断续续必须来自其他地方。
我改为看OnMouseMove事件处理程序。虽然WriteableBitmap每秒更新30-60次,但MouseMove事件仅每秒触发1-2次。如果减小WriteableBitmap的大小,则MouseMove事件会更频繁地触发,并且平移操作看起来更平滑。因此,断断续续实际上是MouseMove事件断断续续而不是渲染的结果(例如,WriteableBitmap正在渲染看上去相同的7-10帧,触发MouseMove事件,然后WriteableBitmap渲染新平移图像的7-10帧等)。
我尝试通过每次使用Mouse.GetPosition(this)更新WriteableBitmap时都通过轮询鼠标位置来跟踪平移操作。但是,结果相同,因为在更改为新值之前,返回的鼠标位置将在7-10帧内相同。
然后,我尝试轮询使用的PInvoke服务GetCursorPos鼠标位置就像这个SO答案例如:
[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;
}
}
这实际上起到了作用。每次调用GetCursorPos时都会返回一个新位置(鼠标移动时),因此在用户平移时,每个帧的渲染位置都稍有不同。同样的断断续续似乎正在影响MouseWheel事件,而我不知道如何解决该事件。
因此,尽管上述关于有效维护视觉树的所有建议都是好的做法,但我怀疑您的性能问题可能是由于某些因素干扰了鼠标事件的发生频率。就我而言,似乎是由于某种原因,渲染导致Mouse事件的更新和触发比平常慢得多。如果找到真正的解决方案而不是部分解决方法,我将对其进行更新。
编辑:好的,我对此进行了更多研究,我想我现在了解发生了什么。我将用更详细的代码示例进行解释:
我通过注册以处理此MSDN文章中所述的CompositionTarget.Rendering事件,以每帧为单位渲染到位图。基本上,这意味着每次渲染UI时都会调用我的代码,以便我可以更新位图。从本质上讲,这等效于您正在执行的渲染,只是您的渲染代码在幕后被调用,具体取决于您如何设置视觉元素,而我可以在哪里看到渲染代码。我重写OnMouseMove事件来更新一些变量,具体取决于鼠标的位置。
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);
}
}
问题在于,随着渲染花费更多时间,MouseMove事件(实际上是所有鼠标事件)的调用频率大大降低。当呈现代码花费15毫秒时,MouseMove事件每隔几毫秒被调用一次。当呈现代码花费30毫秒时,每隔几百秒就会调用MouseMove事件毫秒。我的发生原因的理论是,渲染是在WPF鼠标系统更新其值并触发鼠标事件的同一线程上进行的。该线程上的WPF循环必须具有一些条件逻辑,如果渲染在一帧中花费的时间太长,它将跳过进行鼠标更新的过程。当我的渲染代码在每一帧上花费“太长”时,就会出现问题。然后,由于渲染每帧要多花15毫秒,所以界面似乎没有变慢一点,而是因为额外的15毫秒渲染时间引入了两次鼠标更新之间的数百毫秒的延迟,所以该界面非常结巴。
我前面提到的PInvoke解决方法实际上绕过了WPF鼠标输入系统。每次进行渲染时,它都会直接到达源,因此使WPF鼠标输入系统饿死不再阻止我的位图正确更新。
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;
}
}
}
但是,这种方法并不能解决我的其余鼠标事件(MouseDown,MouseWheel等),而且我也不愿意将PInvoke方法用于所有鼠标输入,因此我决定最好不要饿死WPF鼠标输入系统。我最终要做的只是在确实需要更新时才更新WriteableBitmap。仅当某些鼠标输入影响了它时才需要更新它。因此,结果是我收到一帧的鼠标输入,更新下一帧的位图,但由于更新时间太长了几毫秒,所以在同一帧上没有收到更多的鼠标输入,然后下一帧我将收到更多鼠标输入,因为不需要再次更新位图。
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);
}
}
将相同的知识转换为您自己的特定情况:对于导致性能问题的复杂几何形状,我将尝试某种类型的缓存。例如,如果几何形状本身从不更改或不经常更改,请尝试将其渲染到RenderTargetBitmap,然后将RenderTargetBitmap添加到可视树中,而不是添加几何形状本身。这样,当WPF执行其渲染路径时,所需要做的就是将那些位图涂成斑点,而不是从原始几何数据中重建像素数据。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句