Dziś odcinek o klasach Visuals. Czym się one różnią od omówionych już klas Gemoetry oraz jak ich możemy użyć. Visuals są trochę dziwnym tworem w WPF, rezydującym na znacznie niższym poziomie niż większość elementów, z którymi się stykami. Trzeba się zatem mocno napracować aby coś z tej klasy wydobyć. Napiszmy więc kawałek kodu:
DrawingGroup group = FindResource(“drawingObject”) as DrawingGroup;
DrawingVisual visual = new DrawingVisual();
using(var context = visual.RenderOpen())
{
context.DrawDrawing(group);
}
a w XAMLu dodatkowo
<Window.Resources>
<DrawingGroup x:Key=”drawingObject”>
<GeometryDrawing Brush=”Pink” Geometry=”M0 0S 50,50 100,0 l10,30″>
<GeometryDrawing.Pen>
<Pen Thickness=”2″ Brush=”Black” DashStyle=”{x:Static DashStyles.DashDot}” DashCap=”Round” />
GeometryDrawing.Pen>
GeometryDrawing>
DrawingGroup>
Window.Resources>
gdy jednak uruchomimy program zobaczymy puste okienko. Niestety nie ma tak prosto. Czego zatem nam brakuje? Po pierwsze musimy zarejestrować nasz obiekt w drzewie logicznym i wizualnym.
AddVisualChild(visual);
AddLogicalChild(visual);
Jednak to nadal nie wszystko 🙁
protected override int VisualChildrenCount
{
get
{
return 1;
}
}
protected override Visual GetVisualChild(int index)
{
if (index!=0)
throw new ArgumentOutOfRangeException();
return visual;
}
A na ekranie ukaże się nam taki oto widok.
Jak zatem tego używać w sensowny sposób i czy jest jakiś zysk z tej klasy?
Zwykle robi się to tak, że tę klasę wykorzystuje się w oddzielnym elemencie typu UIElement i to w nim przeciąża się te dwie metody odpowiednio. Czy jest z tego zatem jakiś zysk? odpowiedź oczywiście jest twierdząca. Dzięki temu, że klasa ta jest bardzo nisko w hierarchii unika się całego narzutu związanego z tworzeniem “ciężkich” elementów Drawing. Klasa DrawingContext wspiera multum metod do rysowania bezpośrednio na niej, podobnie jak znany z WinForms obiekt Graphics. Co zatem jeszcze za wykonać?
HitTesting
Jak to sprawdzić? Wystarczy użyć pomocniczej klasy VisualTreeHelper.
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
Point point = e.GetPosition(null);
HitTestResult result = VisualTreeHelper.HitTest(visual, point);
if(result.VisualHit.GetType() == typeof(DrawingVisual))
{
//do sth.
}
}
W przypadku gdy mamy leżące na sobie elementy i wszystkie powinny reagować na kliknięcia należy posłużyć się trochę inną techniką.
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
Point point = e.GetPosition(null);
VisualTreeHelper.HitTest(this, null, HitTestCallback, new PointHitTestParameters(point));
}
private HitTestResultBehavior HitTestCallback(HitTestResult result)
{
if (result.VisualHit.GetType() == typeof(DrawingVisual))
{
DrawingVisual drawingVisual = result.VisualHit as DrawingVisual;
}
return HitTestResultBehavior.Continue;
}
Możemy oczywiście przerwać dalsze poszukiwania zwracając z metody HitTestResultBehavior.Stop
Następny odcinek będzie o Shapes.
Founder of Octal Solutions a .NET software house.
Passionate dev, blogger, occasionally speaker, one of the leaders of Wroc.NET user group. Microsoft MVP. Podcaster – Ostrapila.pl