当有大量图形需要绘制的时候,WPF推荐的是采用DrawingVisual结合DrawingCanvas进行绘制。
WPF中给出的DrawingCanvas代码如下,它从Canvas继承,并改写了VisualChildrenCount只读属性、GetVisualChild方法、AddVisual、DeleteVisual和ClearVisuals方法。然而,该代码不能显示Button之类的元素。
public class DrawingCanvas:Canvas
{
private List<Visual> visuals = new List<Visual>();
protected override int VisualChildrenCount
{
get
{ return visuals.Count; }
}
protected override Visual GetVisualChild(int index)
{
return visuals[index];
}
public void AddVisual(Visual visual)
{
visuals.Add(visual);
base.AddVisualChild(visual);
base.AddLogicalChild(visual);
}
public void DeleteVisual(Visual visual)
{
visuals.Remove(visual);
base.RemoveVisualChild(visual);
base.RemoveLogicalChild(visual);
}
public void ClearVisual()
{
foreach (var visual in visuals)
{
base.RemoveLogicalChild(visual);
base.RemoveVisualChild(visual);
}
visuals.Clear();
}
public DrawingVisual GetVisual(Point point)
{
HitTestResult hitResult = VisualTreeHelper.HitTest(this, point);
return hitResult.VisualHit as DrawingVisual;
}
}
为了改变上述状况,有两种方法。
方法一:
在DrawingCanvas中同时使用visuals列表和Children属性来存储DrawingVisuals对象和Button对象,代码如下所示。即在AddVisual方法中,先添加到visuals中,然后判断是否为button,如果是button则添加到Children中去,如果不是button则认为是DrawingVisual,则分别添加到可视化子对象集和逻辑子对象集中去。本来以为这种方法是可行的,用鼠标操作Button确实也可以,没什么问题。但是如果是采用触摸屏操作,则Button对象的TouchUp事件、TouchMove事件和Click事件无法被程序(用户)捕获,即无法触发该事件,只有TouchDown事件可以被触发。那怎么办呢?后来发现,当给Button的TouchDown事件绑定事件处理程序,并在事件处理程序中写出e.Handled=true,这时TouchUp事件、TouchMove事件和Click事件就都可以被触发了。 此方法即可以正常使用。
??注意:如果把DrawingCanvas放在ContentControl的ContentTemplate模板中使用的时候,可能出现问题(child与父级元素断开之类的异常),不知道如何解决该问题,所以暂时没有把DrawingCancas放在ContentTemplate中使用。
public class DrawingCanvas:Canvas
{
private List<Visual> visuals = new List<Visual>();
protected override int VisualChildrenCount
{
get
{ return visuals.Count; }
}
protected override Visual GetVisualChild(int index)
{
return visuals[index];
}
public void AddVisual(Visual visual)
{
visuals.Add(visual);
if(visual is Button btn)
{
Children.Add(btn); //在Canvas的Children中添加子元素
}
else
{
base.AddVisualChild(visual);
base.AddLogicalChild(visual);
}
}
public void DeleteVisual(Visual visual)
{
visuals.Remove(visual);
if (visual is Button btn)
{
Children.Remove(btn);
}
else
{
base.RemoveVisualChild(visual);
base.RemoveLogicalChild(visual);
}
}
public void ClearVisual()
{
foreach (var visual in visuals)
{
if (visual is Button btn)
{
Children.Remove(btn);
}
else
{ base.RemoveLogicalChild(visual);
base.RemoveVisualChild(visual);
}
}
visuals.Clear();
}
public DrawingVisual GetVisual(Point point)
{
HitTestResult hitResult = VisualTreeHelper.HitTest(this, point);
return hitResult.VisualHit as DrawingVisual;
}
}
方法二:
Bird鸟人在 “WPF DrawingVisual详解”一文中建议的:在Grid中放置DrawingCanvas用于绘制DrawingVisual,然后在放置Canvas来内置各种控件。DrawingCanvas和Canvas重叠起来。 (说明:原文描述是这样的:"在Grid中放置Panel用于绘制DrawingVisual,然后在放置Canvas来内置各种控件。Panel和Canvas重叠起来。" 本质是一样的,只是Panel和DrawingVisual的起名不同而已。)
此种方法的总体代码如下所示,我经过测试,这种方法是可行的,这确实是很好的思路,感谢Bird鸟人提供的思路。
不过需要注意的是:如果在Canvas元素或者外围包装的Grid元素中使用IsManipulationEnabled=true,那么Canvas中添加的所有Button的TouchMove、TouchUp和Click事件都无法被捕获到,即这些事件不能使用了。为了解决这个问题,这时需要给每个Button对象绑定一个TouchDown处理程序,并将TouchDown处理程序中添加e.Handled=true,然后TouchMove、TouchUp和Click事件就可以正常使用了,参见文章“WPF组件中当设置IsManipulationEnabled="True"时导致TouchUp不能被触发的解决方法”。
<Grid Name="TouchPadWrapper">
<local:DrawingCanvas Name="DrawingVisualCanvas"></local:DrawingCanvas>
<Canvas Name="ButtonCanvas"></Canvas>
</Grid>