OK, OK: I know I said I’d talk more about overrules, last week, but – as is often the way, I’m afraid to say – I got distracted. The good news, though, is that I got distracted by something genuinely interesting, and well worth sharing.
I’ve been working on upgrading the WinForms user interface of an existing .NET application to use WPF, the Windows Presentation Foundation. For those wanting a thorough grounding in WPF, I recommend watching Fenton Webb’s highly-rated webcast series on WPF, just one of the interesting webcasts that can be downloaded and viewed from our API training schedule:
AutoCAD: Using WPF in your Applications - Part 1 (28.9 Mb)
AutoCAD: Using WPF in your Applications - Part 2 (16.3 Mb)
In this post I’m going to walk through creating a simple WPF User Control which we can then host inside an AutoCAD PaletteSet.
Firstly we need to add a WPF User Control to our Visual Studio 2008 (or higher) project:
We’ll accept the default name for the purposes of this project.
Now we should see our blank user-control hosted in both a designer window and a XAML editing pane:
For the content of our user-control, I decided to take a static 2D image and apply some effects to it, inspired by this page, which also has a thorough explanation of the various effects applied (which saves me from doing the same :-).
You can copy & paste this XAML into the editing pane:
<UserControl x:Class="WPF_Palette.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="380" Width="300">
<StackPanel Background="Gray">
<Border BorderBrush="OldLace" BorderThickness="10" CornerRadius="10"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Source="TTIF.png" Width="200" Height="200"
Stretch="Fill" x:Name="blogImage" />
<Border.RenderTransform>
<SkewTransform CenterX="250" CenterY="0"
AngleX="0" AngleY="350" />
</Border.RenderTransform>
</Border>
<Border Width="220" Height="300" BorderThickness="10"
CornerRadius="10" BorderBrush="OldLace">
<Border.Background>
<VisualBrush Visual="{Binding ElementName=blogImage}">
<VisualBrush.Transform>
<ScaleTransform ScaleX="1" ScaleY="-1"
CenterX="200" CenterY="150" />
</VisualBrush.Transform>
</VisualBrush>
</Border.Background>
<Border.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="Black" />
<GradientStop Offset="0.3" Color="Transparent" />
</LinearGradientBrush>
</Border.OpacityMask>
<Border.RenderTransform>
<SkewTransform CenterX="260" CenterY="0"
AngleX="40" AngleY="350" />
</Border.RenderTransform>
</Border>
</StackPanel>
</UserControl>
For this to work, I added a simple screenshot of this blog as an item in the project, calling it “TTIF.png”. I simply added it to the project via the Solution Explorer, using Add –> Existing Item… no need to create a resource.
Now we should see our User Control has taken shape:
Now it’s simply a matter of defining a command to create a PaletteSet to host our WPF content:
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Integration;
namespace WPF_Palette
{
public class Commands
{
static PaletteSet _ps = null;
[CommandMethod("WPFP")]
public void ShowWPFPalette()
{
if (_ps == null)
{
// Create the palette set
_ps = new PaletteSet("WPF Palette");
_ps.Size = new Size(400, 600);
_ps.DockEnabled =
(DockSides)((int)DockSides.Left + (int)DockSides.Right);
// Create our first user control instance and
// host it on a palette using AddVisual()
UserControl1 uc = new UserControl1();
_ps.AddVisual("AddVisual", uc);
// Create our second user control instance and
// host it in an ElementHost, which allows
// interop between WinForms and WPF
UserControl1 uc2 = new UserControl1();
ElementHost host = new ElementHost();
host.AutoSize = true;
host.Dock = DockStyle.Fill;
host.Child = uc2;
_ps.Add("Add ElementHost", host);
}
// Display our palette set
_ps.KeepFocus = true;
_ps.Visible = true;
}
}
}
For the application to build you’ll need to add project references to the .NET assemblies System.Drawing and WindowsFormsIntegration (in addition to the usual AcMgd.dll and AcDbMgd.dll, of course).
The above code deliberately adds two instances of our control into the PaletteSet, to show the different techniques for doing so:
- The first uses the new (to AutoCAD 2010) AddVisual() method to add in our WPF User Control into a Palette
- This works, but doesn’t dock the control inside the palette (something I hope to see addressed in the future – as it stands this isn’t particularly useful)
- The second uses an ElementHost to host our WPF User Control – essentially hosting our WPF control in one that works with a WinForms UI. We then use the standard Add() method to add it, as we have done in the past
Now the application should build. When we NETLOAD it into AutoCAD and run the WPFP command, we should see our docked palette set with the first tab visible:
We can see that this tab hosts our control, but it doesn’t fill the palette.
If we select the second tab, we see things are better, there:
If you undock the palette set you can see more easily the way the control is hosted by each palette.
I’m still on the steep part of the learning curve with respect to WPF (and expect I’ll be there for some time), but so far I’m really impressed with its capabilities, especially around binding a user-interface’s properties to the data it hosts. I’ll be sharing more of my findings related to WPF in due course.