Ik ben MVP voor C#, dus het wordt tijd dat ik terug ga naar mijn roots en wat C# code laat zien.
In mijn vorige post heb ik laten zien hoe je een kubus definieert. Het kwam neer op veel punten berekenen en vervolgens aangeven hoe je die punten met elkaar verbindt. Nu hebben we allemaal een computer voor onze neus staan en als er iets is waar computers goed in zijn, is het wel in rekenen. Waarom zouden we dat dan handmatig doen? Laten we de punten voor de kubussen in code berekenen!
Nu is het natuurlijk erg eenvoudig om een stuk code te schrijven die een paar parameters krijgt (een positie en de afmetingen) en die dan XAML code uitspuugt die je kunt plakken in je XAML bestand, maar dat is niet wat we willen.
We willen dat we in onze XAML code de volgende code op kunnen nemen met als resultaat dat er een mooi kubusje op het scherm staat:
<detrio:Box LeftBottomCorner="0 0 0"
Width="2.0"
Height="1.0"
Depth="1.5"
>
<detrio:Box.Material>
<DiffuseMaterial Brush="Blue"/>
</detrio:Box.Material>
</detrio:Box>
Dat gaan we dan ook doen.
We moeten onze Box class kunnen toevoegen aan onze Viewport3D. We hebben gezien hoe we dat in XAML doen: we maken een GeometryModel3D, zetten de punten en triangleindices goed en klaar. Het lijkt voor de hand liggend om onze Box class af te leiden van GeometryModel3D ware het niet dat dat niet kan: bijna alles in WPF is sealed en kan dus niet als base-class gebruikt worden.
Een van de weinige classes die dat niet hebben is ModelVisual3D. Nu is dit gelukkig ook de class die in de Children collection van een Viewport3D geplaatst kan worden, dus laten we die maar gebruiken.
Definieer de volgende class:
//-----------------------------------------------------------------------
// <copyright file="Box.cs" company="Detrio Consultancy b.v.">
// Copyright (c) Detrio Consultancy b.v. All rights reserved.
// </copyright>
// <author>Dennis Vroegop</author>
//-----------------------------------------------------------------------
namespace WpfApplication11
{
#region Using statements
using System;
using System.Windows.Media.Media3D;
#endregion
/// <summary>
/// Een rechthoekig ding voor op het scherm
/// </summary>
internal class Box : ModelVisual3D
{
}
}
We hebben nu een Box class welke afgeleid is van ModelVisual3D. Hier kunnen we in de Content dan onze GeometryModel3D maken, en in de GeometryModel3D onze MeshGeometry3D. Aangezien we dat altijd moeten doen kunnen we daar gelijk even een paar private fields van maken:
internal class Box : ModelVisual3D
{
private GeometryModel3D _model;
private MeshGeometry3D _mesh;
/// <summary>
/// Initializes a new instance of the <see cref="Box"/> class.
/// </summary>
public Box()
{
_model = new GeometryModel3D();
_mesh = new MeshGeometry3D();
_model.Geometry = _mesh;
this.Content = _model;
}
}
Die hebben we maar alvast!
Om een object zichtbaar te maken moeten we ook Materials kunnen toevoegen. Dus daar maken we gelijk maar even een property van. Of liever gezegd: een DependencyProperty. Voor diegene die niet weten wat een DependencyProperty is, raad ik je aan om deze post eens te bekijken. Ik wacht wel even....
Voeg de volgende code toe:
1 /// <summary>
2 /// De material property die het uiterlijk bepaalt
3 /// </summary>
4 private static readonly DependencyProperty MaterialProperty =
5 GeometryModel3D.MaterialProperty.AddOwner(
6 typeof(Box),
7 new PropertyMetadata(MaterialPropertyChanged));
8
9
10 /// <summary>
11 /// Gets or sets the material.
12 /// </summary>
13 /// <value>The material.</value>
14 public Material Material
15 {
16 get { return (Material)GetValue(MaterialProperty); }
17 set { SetValue(MaterialProperty, value); }
18 }
19
20 /// <summary>
21 /// Wordt aangeroepen als de MaterialProperty van inhoud wijzigt
22 /// (static).
23 /// </summary>
24 /// <param name="obj">The obj.</param>
25 /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/>
26 /// instance containing the event data.</param>
27 private static void MaterialPropertyChanged(
28 DependencyObject obj,
29 DependencyPropertyChangedEventArgs args)
30 {
31 (obj as Box).MaterialPropertyChanged(args);
32 }
33
34 /// <summary>
35 /// Wordt aangeroepen als de MaterialProperty van inhoud wijzigt
36 /// (van deze instance)
37 /// </summary>
38 /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/>
39 /// instance containing the event data.</param>
40 private void MaterialPropertyChanged(DependencyPropertyChangedEventArgs args)
41 {
42 _model.Material = args.NewValue as Material;
43 }
Dit is nogal wat. Ik zal uitleggen wat er hier gebeurt.
In regels 1 tot en met 7 maken we de nieuwe DependencyProperty. Of liever gezegd, we vertellen de Material property van _model (die heeft immers de Material property al) dat er een nieuwe owner bij gekomen is: onze Box! Als er nu iets gebeurt met de Material property op de _model, dan krijgen wij daar ook een notificatie van. En andersom uiteraard ook: als wij iets met Material doen, komt dat uiteindelijk bij de GeometryModel3D _model aan.
In regels 10 tot en met 18 maken we de normale CLR property aan, die niets anders doet dan de waardes doorgeven naar en van de DependencyProperty.
In regels 20 tot en met 32 hebben we de static method die aangeroepen wordt als er iets met de Material gebeurd. Het enige wat we hier doen is het aanroepen van de instance method MaterialPropertyChanged zodat de juiste instance actie kan ondernemen. Dit gebeurdt in regels 34 tot en met 43. We geven hier de nieuwe material door aan onze _model.
Laten we gelijk een paar andere DependencyProperties toevoegen:
1. Een Point3D voor de LeftBottomCorner waar de box in de scene geplaatst moet worden
2. Een double die de Width aangeeft
3. Een double die de Height aangeeft
4. Een double die de Depth aangeeft.
Komt 'ie:
1 /// <summary>
2 /// Left bottom corner
3 /// </summary>
4 private static readonly DependencyProperty LeftBottomCornerProperty =
5 DependencyProperty.Register("LeftBottomCorner",
6 typeof(Point3D),
7 typeof(Box),
8 new PropertyMetadata(new Point3D(0.0, 0.0, 0.0), PropertyChanged));
9
10 /// <summary>
11 /// Width
12 /// </summary>
13 private static readonly DependencyProperty WidthProperty =
14 DependencyProperty.Register("Width",
15 typeof(double),
16 typeof(Box),
17 new PropertyMetadata(1.0, PropertyChanged));
18
19 /// <summary>
20 /// Height
21 /// </summary>
22 private static readonly DependencyProperty HeightProperty =
23 DependencyProperty.Register("Height",
24 typeof(double),
25 typeof(Box),
26 new PropertyMetadata(1.0, PropertyChanged));
27
28 /// <summary>
29 /// Depth
30 /// </summary>
31 private static readonly DependencyProperty DepthProperty =
32 DependencyProperty.Register("Depth",
33 typeof(double),
34 typeof(Box),
35 new PropertyMetadata(1.0, PropertyChanged));
36
37 /// <summary>
38 /// Gets or sets the left bottom corner.
39 /// </summary>
40 /// <value>The left bottom corner.</value>
41 public Point3D LeftBottomCorner
42 {
43 get { return (Point3D)GetValue(LeftBottomCornerProperty); }
44 set { SetValue(LeftBottomCornerProperty, value); }
45 }
46
47 /// <summary>
48 /// Gets or sets the width.
49 /// </summary>
50 /// <value>The width.</value>
51 public double Width
52 {
53 get { return (double)GetValue(WidthProperty); }
54 set { SetValue(WidthProperty, value); }
55 }
56
57 /// <summary>
58 /// Gets or sets the height.
59 /// </summary>
60 /// <value>The height.</value>
61 public double Height
62 {
63 get { return (double)GetValue(HeightProperty); }
64 set { SetValue(HeightProperty, value); }
65 }
66
67 /// <summary>
68 /// Gets or sets the depth.
69 /// </summary>
70 /// <value>The depth.</value>
71 public double Depth
72 {
73 get { return (double)GetValue(DepthProperty); }
74 set { SetValue(DepthProperty, value); }
75 }
76
77 /// <summary>
78 /// Handles the Property Changed event (static)
79 /// </summary>
80 /// <param name="obj">The sender.</param>
81 /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/>
82 /// instance containing the event data.</param>
83 private static void PropertyChanged(
84 DependencyObject obj,
85 DependencyPropertyChangedEventArgs args)
86 {
87 (obj as Box).PropertyChanged(args);
88 }
89 /// <summary>
90 /// Handles the Property Changed event (static)
91 /// </summary>
92 /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/>
93 /// instance containing the event data.</param>
94 private void PropertyChanged(DependencyPropertyChangedEventArgs args)
95 {
96 // opnieuw tekenen!
97 }
Een hele lap, maar het meeste moet duidelijk zijn.
We definieren een aantal DependencyProperties, maken de CLR properties en zorgen ervoor dat ze allemaal de PropertyChanged method aanroepen.
Als er een wijziging komt in een van deze properties moeten we de box opnieuw gaan tekenen. Dat is wat we de volgende keer gaan doen!