Introduction
In a previous post I showed you how to create a basic 3D model viewer in WPF using the MVVM pattern. In this post I’m going to show you how to take advantage of the MVVM design of this application by adding a couple more views (one view to move the camera, and one to change the mesh being viewed) and there will be one more view-model (for toggling the viewed mesh).
The Camera View
Here is the xaml for the Camera Control Panel, this will use the CameraVM as its DataContext.
<UserControl x:Class="TriRoot.Views.CameraPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<DockPanel>
<TextBlock Text="Rotation:" Width="65" />
<TextBlock DockPanel.Dock="Right" Width="65"
Text="{Binding Rotation}" />
<Slider x:Name="RotationSlider"
Minimum="0" Maximum="6.283"
Value="{Binding Rotation}"/>
</DockPanel>
<DockPanel >
<TextBlock Text="Up / Down:" Width="65" />
<TextBlock DockPanel.Dock="Right" Width="65"
Text="{Binding UpPercent}" />
<Slider x:Name="UpPercentSlider"
Minimum="-1" Maximum="1"
Value="{Binding UpPercent}"/>
</DockPanel>
</StackPanel>
</UserControl>
There is no additional code behind for this panel or the CameraVM. The sliders will update the values in the CameraVM through bindings when the user interacts with the sliders.
The following is an update to the test window to use this new panel. I’ve grayed out any code that was from the first post.
<Window x:Class="TriRoot.Test.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:TriRoot.Views;assembly=TriRoot.Blog"
xmlns:viewmodels="clr-namespace:TriRoot.ViewModels;assembly=TriRoot.Blog"
Title="TriRoot.Test" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type viewmodels:ViewportVM}">
<views:ViewportPanel />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:CameraVM}">
<views:CameraPanel />
</DataTemplate>
</Window.Resources>
<DockPanel>
<ContentPresenter x:Name="CameraVMContentPresenter" DockPanel.Dock="Top"/>
<ContentPresenter x:Name="ViewportVMContentPresenter" />
</DockPanel>
</Window>
And the CameraVM needs to be set as the Content of the new content presenter in the code behind
CameraVM cameraVM = new CameraVM() { CameraData = cameraData };
MeshVM meshVM = new MeshVM() { MeshData = meshData };
ViewportVM viewportVM = new ViewportVM()
{
Camera = cameraVM,
Mesh = meshVM
};
CameraVMContentPresenter.Content = cameraVM;
ViewportVMContentPresenter.Content = viewportVM;
Here’s a screenshot of the result:

In another post I have the details on how to implement a simple Cube Mesh against the IMeshData interface. The resulting class is the MeshDataCube. Next I’m going to write a view model and view that allows the user to select between viewing a simple triangle or the MeshDataCube.
The Mesh Editor View-Model
The mesh editor will have a couple commands for toggling the mesh in use. These commands get bound to buttons, so that when a user clicks the button, ICommand.Execute gets called. The DelegateCommand is an implementation of ICommand that takes an Action that gets invoked in DelegateCommand.Execute, and a Func<bool> that gets invoked and returned in DelegateCommand.CanExecute. In this implementation, I’m ignoring the parameters of Execute and CanExecute.
public class DelegateCommand : ICommand
{
Action _executeAction;
Func<bool> _canExecuteFunc;
public DelegateCommand(Action executeAction)
: this(executeAction, null)
{
}
public DelegateCommand(Action executeAction, Func<bool> canExecuteFunc)
{
_executeAction = executeAction;
_canExecuteFunc = canExecuteFunc;
}
public event EventHandler CanExecuteChanged;
public void Execute(object unused)
{
_executeAction();
}
public bool CanExecute(object unused)
{
return _canExecuteFunc != null ? _canExecuteFunc() : true;
}
public void FireCanExecuteChanged()
{
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, new EventArgs());
}
}
}
The MeshEditor has two commands, one for switching to a triangle mesh, and one for switching to a cube mesh. The SelectedMesh property is a reference to the MeshVM which contains the MeshData that will be altered when a command executes.
public class MeshEditorVM : DependencyObject
{
DelegateCommand _triangleCommand;
DelegateCommand _cubeCommand;
public MeshEditorVM()
{
_triangleCommand = new DelegateCommand(
MakeTriangle,
CanMakeTriangle);
_cubeCommand = new DelegateCommand(
MakeCube,
CanMakeCube);
}
#region MeshVM SelectedMesh (DependencyProperty w/OnChanged)
public static readonly DependencyProperty SelectedMeshDataProperty =
DependencyProperty.Register(
"SelectedMesh",
typeof(MeshVM),
typeof(MeshEditorVM),
new FrameworkPropertyMetadata(OnSelectedMeshChanged));
public MeshVM SelectedMesh
{
get { return (MeshVM)GetValue(SelectedMeshDataProperty); }
set { SetValue(SelectedMeshDataProperty, value); }
}
protected static void OnSelectedMeshChanged(
DependencyObject sender,
DependencyPropertyChangedEventArgs args)
{
MeshEditorVM me = (MeshEditorVM)sender;
me.RefreshCommands();
}
#endregion
public ICommand TriangleCommand { get { return _triangleCommand; } }
public ICommand CubeCommand { get { return _cubeCommand; } }
private void RefreshCommands()
{
_triangleCommand.FireCanExecuteChanged();
_cubeCommand.FireCanExecuteChanged();
}
private void MakeTriangle()
{
if (SelectedMesh != null)
{
SelectedMesh.MeshData = new MeshDataTriangle(
new Point3D(0.5, 0, 0),
new Point3D(0, 0.5, 0),
new Point3D(0, 0, 0.5));
RefreshCommands();
}
}
private bool CanMakeTriangle()
{
return SelectedMesh != null
&& !(SelectedMesh.MeshData is MeshDataTriangle);
}
private void MakeCube()
{
if (SelectedMesh != null)
{
SelectedMesh.MeshData = new MeshDataCube(1.0);
RefreshCommands();
}
}
private bool CanMakeCube()
{
return SelectedMesh != null
&& !(SelectedMesh.MeshData is MeshDataCube);
}
}
Here is the xaml for the panel that will use the MeshEditorVM as it s DataContext.
<UserControl x:Class="TriRoot.Views.MeshEditorPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Background="LemonChiffon" Margin="2">
<Button Content="Triangle" Command="{Binding TriangleCommand}" Margin="2"/>
<Button Content="Cube" Command="{Binding CubeCommand}" Margin="2"/>
</StackPanel>
</UserControl>
Again, the following is an update to the test window to use this new panel.
<Window x:Class="TriRoot.Test.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:TriRoot.Views;assembly=TriRoot.Blog"
xmlns:viewmodels="clr-namespace:TriRoot.ViewModels;assembly=TriRoot.Blog"
Title="TriRoot.Test" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type viewmodels:ViewportVM}">
<views:ViewportPanel />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:CameraVM}">
<views:CameraPanel />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:MeshEditorVM}">
<views:MeshEditorPanel />
</DataTemplate>
</Window.Resources>
<DockPanel>
<ContentPresenter x:Name="MeshEditorVMContentPresenter" DockPanel.Dock="Right"/>
<ContentPresenter x:Name="CameraVMContentPresenter" DockPanel.Dock="Top"/>
<ContentPresenter x:Name="ViewportVMContentPresenter" />
</DockPanel>
</Window>
And then in the code behind a MeshEditorVM gets hooked up to the new ContentPresenter.
ICameraData cameraData = new CameraData();
IMeshData meshData = new MeshDataTriangle(
new Point3D(0.5, 0, 0),
new Point3D(0, 0.5, 0),
new Point3D(0, 0, 0.5));
CameraVM cameraVM = new CameraVM() { CameraData = cameraData };
MeshVM meshVM = new MeshVM() { MeshData = meshData };
ViewportVM viewportVM = new ViewportVM()
{
Camera = cameraVM,
Mesh = meshVM
};
MeshEditorVM meshEditorVM = new MeshEditorVM()
{
SelectedMesh = meshVM
};
MeshEditorVMContentPresenter.Content = meshEditorVM;
CameraVMContentPresenter.Content = cameraVM;
ViewportVMContentPresenter.Content = viewportVM;
And in case you missed it from my first post, here’s the final screenshot:

BAM!



