所有上述示例均采用WinForms技术实现。而WPF(Windows Presentation Foundation)作为创建图形界面的"新技术",借助XAML语言定义界面,使开发者能够像编写HTML一样构建丰富的图形用户界面。
核心特性:
.xaml 文件:定义界面外观、事件订阅及控件数据绑定.xaml.cs 文件:实现用户界面与应用程序之间的交互逻辑(代码隐藏)Visual Studio集成说明: 默认情况下,若项目类型非WPF应用程序,Visual Studio 2015不会提供"Window (WPF)"作为可添加元素。此时可通过以下步骤解决:
.xaml 和 .xaml.cs 文件中将 UserControl 标签替换为 WindowTitle 等属性至 Window 标签作为示例,我们先实现一个与前文WinForm模态对话框功能等价的WPF版本。
主要区别在于使用XAML语言描述用户界面,其架构与HTML有几分相似(但仅止于此)。界面中的控件均被命名以便在代码隐藏文件中访问,控件事件则订阅到代码隐藏文件中的事件处理程序。
在AutoCAD中显示WPF模态对话框,需使用 Application.ShowModalWindow() 方法,并传入一个通过重载构造函数创建的 System.Windows.Window 实例,类似于WinForms中的用法。
Commands.cs
C#using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
[assembly: CommandClass(typeof(AcadUISample.ModalWpf.Commands))]
namespace AcadUISample.ModalWpf
{
public class Commands
{
// 实例字段
Document doc;
Database db;
Editor ed;
double radius; // 半径默认值
string layer; // 图层默认值
/// <summary>
/// 创建Commands类的新实例
/// 此构造函数在首次调用'CommandMethod'方法时针对每个文档运行一次
/// </summary>
public Commands()
{
// 私有字段初始化(初始默认值)
doc = AcAp.DocumentManager.MdiActiveDocument;
db = doc.Database;
ed = doc.Editor;
// 初始默认值
layer = (string)AcAp.GetSystemVariable("clayer");
radius = 10.0;
}
/// <summary>
/// 显示对话框的命令
/// </summary>
[CommandMethod("CMD_MODAL_WPF")]
public void ModalWpfDialogCmd()
{
var layers = GetLayerNames();
if (!layers.Contains(layer))
{
layer = (string)AcAp.GetSystemVariable("clayer");
}
// 显示对话框
var dialog = new ModalWpfDialog(layers, layer, radius);
var result = AcAp.ShowModalWindow(dialog);
if (result.Value)
{
// 更新字段
layer = dialog.Layer;
radius = dialog.Radius;
// 绘制圆
var ppr = ed.GetPoint("\n指定圆心: ");
if (ppr.Status == PromptStatus.OK)
{
// 在当前空间绘制圆
using (var tr = db.TransactionManager.StartTransaction())
{
var curSpace =
(BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
using (var circle = new Circle(ppr.Value, Vector3d.ZAxis, radius))
{
circle.TransformBy(ed.CurrentUserCoordinateSystem);
circle.Layer = layer;
curSpace.AppendEntity(circle);
tr.AddNewlyCreatedDBObject(circle, true);
}
tr.Commit();
}
}
}
}
/// <summary>
/// 获取图层列表
/// </summary>
/// <param name="db">此方法应用的数据库实例</param>
/// <returns>图层名称列表</returns>
private List<string> GetLayerNames()
{
using (var tr = db.TransactionManager.StartOpenCloseTransaction())
{
return ((LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead))
.Cast<ObjectId>()
.Select(id => ((LayerTableRecord)tr.GetObject(id, OpenMode.ForRead)).Name)
.ToList();
}
}
}
}
ModalWpfDialog.xaml
xml<Window x:Class="AcadUISample.ModalWpf.ModalWpfDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:AcadUISample.ModalWpf"
mc:Ignorable="d"
Title="ModalWpfDialog"
Height="160" Width="300"
MinHeight="160" MinWidth="250"
WindowStyle="ToolWindow"
WindowStartupLocation="CenterOwner" >
<Grid Background="WhiteSmoke">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!--第一行-->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Margin="5,15,5,5">图层:</Label>
<ComboBox x:Name="cbxLayer" Grid.Column ="1" Margin="5,15,10,5" HorizontalAlignment="Stretch" />
</Grid>
<!--第二行-->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Margin="5">半径:</Label>
<TextBox x:Name="txtRadius" Grid.Column="1" Margin="5" HorizontalAlignment="Stretch" TextChanged="txtRadius_TextChanged" />
<Button Grid.Column="2" Margin="5,5,10,5" Content=" > " Click="btnRadius_Click" />
</Grid>
<!--第三行-->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="btnOK" Margin="10" HorizontalAlignment="Right" Content="确定" Height="24" Width="80" Click="btnOK_Click"/>
<Button Grid.Column="1" Margin="10" HorizontalAlignment="Left" Content="取消" Height="24" Width="80" IsCancel="True" />
</Grid>
</Grid>
</Window>
ModalWpfDialog.xaml.cs
C#using Autodesk.AutoCAD.EditorInput;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
namespace AcadUISample.ModalWpf
{
/// <summary>
/// ModalWpfDialog.xaml的交互逻辑
/// </summary>
public partial class ModalWpfDialog : Window
{
// 私有字段
double radius;
/// <summary>
/// 获取所选图层名称
/// </summary>
public string Layer => (string)cbxLayer.SelectedItem;
/// <summary>
/// 获取半径值
/// </summary>
public double Radius => radius;
/// <summary>
/// 创建ModalWpfDialog的新实例
/// </summary>
/// <param name="layers">图层名称集合</param>
/// <param name="layer">默认图层名称</param>
/// <param name="radius">默认半径</param>
public ModalWpfDialog(List<string> layers, string layer, double radius)
{
InitializeComponent();
this.radius = radius;
cbxLayer.ItemsSource = layers;
cbxLayer.SelectedItem = layer;
txtRadius.Text = radius.ToString();
}
/// <summary>
/// 处理"Radius"按钮的"Click"事件
/// </summary>
/// <param name="sender">事件源</param>
/// <param name="e">事件数据</param>
private void btnRadius_Click(object sender, RoutedEventArgs e)
{
// 提示用户指定距离
var ed = AcAp.DocumentManager.MdiActiveDocument.Editor;
var opts = new PromptDistanceOptions("\n指定半径: ");
opts.AllowNegative = false;
opts.AllowZero = false;
var pdr = ed.GetDistance(opts);
if (pdr.Status == PromptStatus.OK)
{
txtRadius.Text = pdr.Value.ToString();
}
}
/// <summary>
/// 处理"OK"按钮的"Click"事件
/// </summary>
/// <param name="sender">事件源</param>
/// <param name="e">事件数据</param>
private void btnOK_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
/// <summary>
/// 处理"Radius"文本框的"TextChanged"事件
/// </summary>
/// <param name="sender">事件源</param>
/// <param name="e">事件数据</param>
private void txtRadius_TextChanged(object sender, TextChangedEventArgs e)
{
btnOK.IsEnabled = double.TryParse(txtRadius.Text, out radius);
}
}
}
除了丰富的界面内容,WPF还提供了强大的数据绑定机制,部分基于依赖属性(能够通知其值发生变化的属性)。
要使用此机制,必须将数据上下文(DataContext)设置为一个类(此处为包含代码隐藏的类),并且该类必须实现INotifyPropertyChanged接口。
为了说明这一点,我们继续使用之前的模态对话框示例。
在XAML中,一些控件的依赖属性与代码隐藏中定义的属性相绑定。因此,包含代码隐藏的类必须实现INotifyPropertyChanged接口。同样,"Radius"和"OK"按钮绑定到代码隐藏中定义的RoutedCommand实例,这些实例管理它们的行为。使用WPF绑定可以避免为不同控件命名。
为了进一步优化,我们在下拉列表项中添加一个与图层颜色相同的正方形。
在XAML中,在ComboBox标签内定义一个ItemTemplate,以便在图层名称旁边显示彩色正方形。因此,与ComboBox控件相关的集合元素必须具有一个对应图层名称的属性和另一个SolidColorBrush类型的属性(WPF中用于绘制纯色的类型)。这里我们使用一个字典,其中包含图层名称(键)和每个图层颜色对应的画刷(值)。
Commands.cs
C#using System.Collections.Generic;
using System.Linq;
using System.Windows.Media;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
[assembly: CommandClass(typeof(AcadUISample.ModalWpfBinding.Commands))]
namespace AcadUISample.ModalWpfBinding
{
public class Commands
{
// 实例字段
Document doc;
Database db;
Editor ed;
double radius; // 半径默认值
string layerName; // 图层默认值
/// <summary>
/// 创建Commands类的新实例
/// 此构造函数在首次调用'CommandMethod'方法时针对每个文档运行一次
/// </summary>
public Commands()
{
// 私有字段初始化(初始默认值)
doc = AcAp.DocumentManager.MdiActiveDocument;
db = doc.Database;
ed = doc.Editor;
// 初始默认值
layerName = (string)AcAp.GetSystemVariable("clayer");
radius = 10.0;
}
/// <summary>
/// 显示对话框的命令
/// </summary>
[CommandMethod("CMD_MODAL_WPF_BINDING")]
public void ModalWpfDialogCmd()
{
var layers = GetLayerBrushes();
if (!layers.ContainsKey(layerName))
{
layerName = (string)AcAp.GetSystemVariable("clayer");
}
var layer = layers.First(l => l.Key == layerName);
// 显示对话框
var dialog = new ModalWpfDialog(layers, layer, radius);
var result = AcAp.ShowModalWindow(dialog);
if (result.Value)
{
// 更新字段
layerName = dialog.Layer.Key;
radius = dialog.Radius;
// 绘制圆
var ppr = ed.GetPoint("\n指定圆心: ");
if (ppr.Status == PromptStatus.OK)
{
// 在当前空间绘制圆
using (var tr = db.TransactionManager.StartTransaction())
{
var curSpace =
(BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
using (var circle = new Circle(ppr.Value, Vector3d.ZAxis, radius))
{
circle.TransformBy(ed.CurrentUserCoordinateSystem);
circle.Layer = layerName;
curSpace.AppendEntity(circle);
tr.AddNewlyCreatedDBObject(circle, true);
}
tr.Commit();
}
}
}
}
/// <summary>
/// 收集图层及其对应颜色的画刷
/// </summary>
/// <returns>图层和画刷的字典</returns>
private Dictionary<string, SolidColorBrush> GetLayerBrushes()
{
var layerBrushes = new Dictionary<string, SolidColorBrush>();
using (var tr = db.TransactionManager.StartOpenCloseTransaction())
{
var layerTable = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead);
foreach (ObjectId id in layerTable)
{
var layer = (LayerTableRecord)tr.GetObject(id, OpenMode.ForRead);
var drawingColor = layer.Color.ColorValue;
var mediaColor = Color.FromRgb(drawingColor.R, drawingColor.G, drawingColor.B);
layerBrushes.Add(layer.Name, new SolidColorBrush(mediaColor));
}
}
return layerBrushes;
}
}
}
ModalWpfDialog.xaml
xml<Window x:Class="AcadUISample.ModalWpfBinding.ModalWpfDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:AcadUISample.ModalWpfBinding"
mc:Ignorable="d"
Title="ModalWpfDialog"
Height="160" Width="300"
MinHeight="160" MinWidth="250"
WindowStyle="ToolWindow"
WindowStartupLocation="CenterOwner" >
<Window.CommandBindings>
<CommandBinding Command="{x:Static local:ModalWpfDialog.OkCmd}"
Executed="OkCmdExecuted"
CanExecute="OkCmdCanExecute" />
<CommandBinding Command="{x:Static local:ModalWpfDialog.RadiusCmd}"
Executed="RadiusCmdExecuted"
CanExecute="RadiusCanExecute" />
</Window.CommandBindings>
<Grid Background="WhiteSmoke">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!--第一行-->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Margin="5,15,5,5">图层:</Label>
<ComboBox Grid.Column ="1" Margin="5,15,10,5" HorizontalAlignment="Stretch"
ItemsSource="{Binding Layers}" SelectedItem="{Binding Layer}">
<!--定义下拉列表的模板-->
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!--带有图层颜色的正方形-->
<Rectangle Grid.Column="0" Margin="3" VerticalAlignment="Stretch"
Width="{Binding Path=ActualHeight,
RelativeSource={RelativeSource Self}}"
Stroke="Black" StrokeThickness="0.5"
Fill="{Binding Value}"/>
<!--图层名称-->
<TextBlock Grid.Column="1" VerticalAlignment="Center"
Text="{Binding Key}" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<!--第二行-->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Margin="5">半径:</Label>
<TextBox Grid.Column="1" Margin="5" HorizontalAlignment="Stretch"
Text="{Binding TextRadius, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="2" Margin="5,5,10,5" Content=" > "
Command="{x:Static local:ModalWpfDialog.RadiusCmd}" />
</Grid>
<!--第三行-->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Margin="10" HorizontalAlignment="Right" Content="确定" Height="24" Width="80"
Command="{x:Static local:ModalWpfDialog.OkCmd}"/>
<Button Grid.Column="1" Margin="10" HorizontalAlignment="Left" Content="取消"
Height="24" Width="80" IsCancel="True" />
</Grid>
</Grid>
</Window>
ModalWpfDialog.xaml.cs
C#using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using Autodesk.AutoCAD.EditorInput;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
namespace AcadUISample.ModalWpfBinding
{
/// <summary>
/// ModalWpfDialog.xaml的交互逻辑
/// </summary>
public partial class ModalWpfDialog : Window, INotifyPropertyChanged
{
#region INotifyPropertyChanged实现
/// <summary>
/// 当属性变更时引发的事件
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// 在需要通知变更的属性的setter中调用的方法
/// </summary>
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
// 私有字段
KeyValuePair<string, SolidColorBrush> layer;
double radius;
string txtRad;
bool validNumber;
/// <summary>
/// 获取绑定到"确定"按钮的命令
/// </summary>
public static RoutedCommand OkCmd = new RoutedCommand();
/// <summary>
/// 获取绑定到"半径"按钮的命令
/// </summary>
public static RoutedCommand RadiusCmd = new RoutedCommand();
/// <summary>
/// 获取或设置绑定到ComboBox控件中所选图层的KeyValuePair实例
/// </summary>
public KeyValuePair<string, SolidColorBrush> Layer
{
get { return layer; }
set { layer = value; OnPropertyChanged(nameof(Layer)); }
}
/// <summary>
/// 获取绑定到ComboBox控件项的图层数据集合
/// </summary>
public Dictionary<string, SolidColorBrush> Layers { get; }
/// <summary>
/// 获取半径值
/// </summary>
public double Radius => radius;
/// <summary>
/// 获取或设置"半径"编辑框中的文本
/// </summary>
public string TextRadius
{
get { return txtRad; }
set
{
txtRad = value;
ValidNumber = double.TryParse(value, out radius) && radius > 0.0;
OnPropertyChanged(nameof(TextRadius));
}
}
/// <summary>
/// 获取或设置一个值,该值指示"半径"编辑框中的文本是否表示一个有效数字
/// </summary>
public bool ValidNumber
{
get { return validNumber; }
set { validNumber = value; OnPropertyChanged(nameof(ValidNumber)); }
}
/// <summary>
/// 创建ModalWpfDialog的新实例
/// </summary>
/// <param name="layers">要绑定到ComboBox控件的图层数据集合</param>
/// <param name="layer">默认图层数据</param>
/// <param name="radius">默认半径</param>
public ModalWpfDialog(Dictionary<string, SolidColorBrush> layers, KeyValuePair<string, SolidColorBrush> layer, double radius)
{
InitializeComponent();
// 定义数据上下文
DataContext = this;
// 初始化绑定
Layers = layers;
Layer = layer;
TextRadius = radius.ToString();
}
/// <summary>
/// 定义绑定到"确定"按钮的操作
/// </summary>
/// <param name="sender">事件源</param>
/// <param name="e">事件数据</param>
private void OkCmdExecuted(object sender, ExecutedRoutedEventArgs e) =>
DialogResult = true;
/// <summary>
/// 确定绑定的"确定"按钮操作是否可以执行
/// </summary>
/// <param name="sender">事件源</param>
/// <param name="e">事件数据</param>
private void OkCmdCanExecute(object sender, CanExecuteRoutedEventArgs e) =>
e.CanExecute = ValidNumber;
/// <summary>
/// 定义绑定到"半径"按钮的操作
/// </summary>
/// <param name="sender">事件源</param>
/// <param name="e">事件数据</param>
private void RadiusCmdExecuted(object sender, ExecutedRoutedEventArgs e)
{
// 提示用户指定距离
var ed = AcAp.DocumentManager.MdiActiveDocument.Editor;
var opts = new PromptDistanceOptions("\n指定半径: ");
opts.AllowNegative = false;
opts.AllowZero = false;
var pdr = ed.GetDistance(opts);
if (pdr.Status == PromptStatus.OK)
TextRadius = pdr.Value.ToString();
}
/// <summary>
/// 确定绑定的"半径"按钮操作是否可以执行
/// </summary>
/// <param name="sender">事件源</param>
/// <param name="e">事件数据</param>
private void RadiusCanExecute(object sender, CanExecuteRoutedEventArgs e) =>
e.CanExecute = true;
}
}
MVVM(模型-视图-视图模型)设计模式特别适合WPF应用。它允许将"业务"部分(通常是数据,即Model)与其展示(View)以及两者之间的交互逻辑(ViewModel)分离开来。
严格应用这种架构可能看起来有些繁琐(特别是在像这样简单的示例中),但它通过优化绑定的使用来鼓励更好的代码结构。
在此示例中,Model部分除了圆绘制命令外不包含任何特定代码。可以认为它由AutoCAD API组成。例如,图层下拉列表将使用UIBindings API与AutoCAD链接(无模式模式需要"动态"集合)。
Commands.cs
C#using Autodesk.AutoCAD.ApplicationServices.Core;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
[assembly: CommandClass(typeof(AcadUISample.PaletteWpf.Commands))]
namespace AcadUISample.PaletteWpf
{
public class Commands
{
// 静态字段
static CustomPaletteSet palette;
// 实例字段(默认值)
double radius = 10.0;
string layer;
/// <summary>
/// 显示面板的命令
/// </summary>
[CommandMethod("CMD_PALETTE_WPF")]
public void ShowPaletteSetWpf()
{
if (palette == null)
palette = new CustomPaletteSet();
palette.Visible = true;
}
/// <summary>
/// 绘制圆的命令
/// </summary>
[CommandMethod("CMD_CIRCLE_WPF")]
public void DrawCircleCmd()
{
var doc = Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;
// 选择图层
if (string.IsNullOrEmpty(layer))
layer = (string)Application.GetSystemVariable("clayer");
var strOptions = new PromptStringOptions("\n图层名称: ");
strOptions.DefaultValue = layer;
strOptions.UseDefaultValue = true;
var strResult = ed.GetString(strOptions);
if (strResult.Status != PromptStatus.OK)
return;
layer = strResult.StringResult;
// 指定半径
var distOptions = new PromptDistanceOptions("\n指定半径: ");
distOptions.DefaultValue = radius;
distOptions.UseDefaultValue = true;
var distResult = ed.GetDistance(distOptions);
if (distResult.Status != PromptStatus.OK)
return;
radius = distResult.Value;
// 指定圆心
var ppr = ed.GetPoint("\n指定圆心: ");
if (ppr.Status == PromptStatus.OK)
{
// 在当前空间绘制圆
using (var tr = db.TransactionManager.StartTransaction())
{
var curSpace =
(BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
using (var circle = new Circle(ppr.Value, Vector3d.ZAxis, distResult.Value))
{
circle.TransformBy(ed.CurrentUserCoordinateSystem);
circle.Layer = strResult.StringResult;
curSpace.AppendEntity(circle);
tr.AddNewlyCreatedDBObject(circle, true);
}
tr.Commit();
}
}
}
}
}
视图部分的用户界面分布在以下文件中:
CustomPaletteSet.cs
C#using System;
using Autodesk.AutoCAD.ApplicationServices.Core;
using Autodesk.AutoCAD.Windows;
namespace AcadUISample.PaletteWpf
{
internal class CustomPaletteSet : PaletteSet
{
// 静态字段
static bool wasVisible;
/// <summary>
/// 创建CustomPaletteSet的新实例
/// </summary>
public CustomPaletteSet()
:base("Palette WPF", "CMD_PALETTE_WPF", new Guid("{42425FEE-B3FD-4776-8090-DB857E9F7A0E}"))
{
Style =
PaletteSetStyles.ShowAutoHideButton |
PaletteSetStyles.ShowCloseButton |
PaletteSetStyles.ShowPropertiesMenu;
MinimumSize = new System.Drawing.Size(250, 150);
AddVisual("Circle", new PaletteTabView());
// 当没有文档处于活动状态时自动隐藏面板(无文档状态)
var docs = Application.DocumentManager;
docs.DocumentBecameCurrent += (s, e) =>
Visible = e.Document == null ? false : wasVisible;
docs.DocumentCreated += (s, e) =>
Visible = wasVisible;
docs.DocumentToBeDeactivated += (s, e) =>
wasVisible = Visible;
docs.DocumentToBeDestroyed += (s, e) =>
{
wasVisible = Visible;
if (docs.Count == 1)
Visible = false;
};
}
}
}
PaletteTabView.xaml
xml<UserControl x:Class="AcadUISample.PaletteWpf.PaletteTabView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:AcadUISample.PaletteWpf"
mc:Ignorable="d"
d:DesignHeight="140" d:DesignWidth="250">
<!--定义资源(颜色转换器)-->
<UserControl.Resources>
<local:LayerColorConverter x:Key="colorConverter"/>
</UserControl.Resources>
<Grid Background="WhiteSmoke">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!--第一行-->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Margin="5,15,5,5">图层:</Label>
<ComboBox x:Name="cbxLayer" Grid.Column ="1" Margin="5,15,10,5" HorizontalAlignment="Stretch"
ItemsSource="{Binding Layers}" SelectedItem="{Binding Layer}">
<!--定义下拉列表的模板-->
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!--带有图层颜色的正方形-->
<Rectangle Grid.Column="0" Margin="3" VerticalAlignment="Stretch"
Width="{Binding Path=ActualHeight, RelativeSource={RelativeSource Self}}"
Stroke="Black" StrokeThickness="0.5"
Fill="{Binding Color, Converter={StaticResource colorConverter}}"/>
<!--图层名称-->
<TextBlock Grid.Column="1" VerticalAlignment="Center" Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<!--第二行-->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Margin="5">半径:</Label>
<TextBox Grid.Column="1" Margin="5" HorizontalAlignment="Stretch" Text="{Binding TextRadius, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="2" Margin="5,5,10,5" Content=" > " Command="{Binding GetRadiusCommand}"/>
</Grid>
<!--第三行-->
<Button Grid.Row="2" Margin="5,15,5,5" Content="确定" Height="24" Width="80" Command="{Binding DrawCircleCommand}"/>
</Grid>
</UserControl>
PaletteTabView.xaml.cs
C#using System.Windows.Controls;
namespace AcadUISample.PaletteWpf
{
/// <summary>
/// PaletteTabView.xaml的交互逻辑
/// </summary>
public partial class PaletteTabView : UserControl
{
public PaletteTabView()
{
InitializeComponent();
// 定义与ViewModel部分的数据绑定
DataContext = new PaletteTabViewModel();
}
}
}
ViewModel部分支持交互逻辑(在前面的示例中由代码隐藏完成)。PaletteTabViewModel类通过继承关系实现INotifyPropertyChanged接口,以处理属性变更,就像前面的示例一样。
对于按钮,我们使用Command属性而不是代码隐藏中的'Click'事件及其处理程序,这使得可以将展示(View)与其逻辑(ViewModel)分离。此属性必须是实现ICommand接口的类型。
因此,在MVVM架构中,我们在此部分找到了两个不可避免的小类:
在ViewModel部分,还有一些展示所需的资源,包括一个LayerColorConverter类,用于将表示图层颜色的Autodesk.AutoCAD.Colors.Color类型元素(下拉列表的元素)转换为相应颜色的SolidColorBrush实例;正方形的填充将与此转换器关联。
PaletteTabViewModel.cs
C#using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Windows.Data;
using System.ComponentModel;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
namespace AcadUISample.PaletteWpf
{
class PaletteTabViewModel : ObservableObject
{
// 私有字段
ICustomTypeDescriptor layer;
double radius;
string txtRad;
bool validRad;
/// <summary>
/// 获取绑定到确定按钮的Command对象
/// 如果CanExecute谓词返回false,按钮将自动禁用
/// </summary>
public RelayCommand DrawCircleCommand =>
new RelayCommand((_) => DrawCircle(), (_) => validRad);
/// <summary>
/// 获取绑定到半径按钮(>)的Command对象
/// </summary>
public RelayCommand GetRadiusCommand =>
new RelayCommand((_) => GetRadius(), (_) => true);
/// <summary>
/// 获取或设置所选图层
/// </summary>
public ICustomTypeDescriptor Layer
{
get { return layer; }
set { layer = value; OnPropertyChanged(nameof(Layer)); }
}
/// <summary>
/// 获取图层集合
/// </summary>
public DataItemCollection Layers => AcAp.UIBindings.Collections.Layers;
/// <summary>
/// 获取或设置文本框中显示的半径值
/// </summary>
public string TextRadius
{
get { return txtRad; }
set
{
txtRad = value;
validRad = double.TryParse(value, out radius) && radius > 0.0;
OnPropertyChanged(nameof(TextRadius));
}
}
/// <summary>
/// 创建PaletteTabViewModel的新实例
/// </summary>
public PaletteTabViewModel()
{
TextRadius = "10";
Layer = Layers.CurrentItem;
Layers.CollectionChanged += (s, e) => Layer = Layers.CurrentItem;
}
/// <summary>
/// 由DrawCircleCommand调用的方法
/// 使用当前选项启动CMD_CIRCLE_WPF命令
/// </summary>
private async void DrawCircle()
{
var docs = AcAp.DocumentManager;
var ed = docs.MdiActiveDocument.Editor;
await docs.ExecuteInCommandContextAsync(
(_) =>
{
ed.Command("CMD_CIRCLE_WPF", ((INamedValue)Layer).Name, radius);
return Task.CompletedTask;
},
null);
}
// 对于AutoCAD 2016之前的版本,使用Document.SendStringToExecute
//private void DrawCircle() =>
// AcAp.DocumentManager.MdiActiveDocument?.SendStringToExecute(
// $"CMD_CIRCLE_WPF \"{((INamedValue)Layer).Name}\" {TextRadius} ", false, false, false);
/// <summary>
/// 由GetRadiusCommand调用的方法
/// </summary>
private void GetRadius()
{
// 提示用户指定距离
var ed = AcAp.DocumentManager.MdiActiveDocument.Editor;
var opts = new PromptDistanceOptions("\n指定半径: ");
opts.AllowNegative = false;
opts.AllowZero = false;
var pdr = ed.GetDistance(opts);
if (pdr.Status == PromptStatus.OK)
TextRadius = pdr.Value.ToString();
}
}
}
ObservableObject.cs
C#using System.ComponentModel;
namespace AcadUISample.PaletteWpf
{
/// <summary>
/// 提供实现INotifyPropertyChanged的类型
/// </summary>
class ObservableObject : INotifyPropertyChanged
{
/// <summary>
/// 当属性变更时引发的事件
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// 在需要通知变更的属性的'setter'中调用的方法
/// </summary>
/// <param name="propertyName">属性名称</param>
protected void OnPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
RelayCommand.cs
C#using System;
using System.Windows.Input;
namespace AcadUISample.PaletteWpf
{
/// <summary>
/// 提供实现ICommand的类型
/// </summary>
class RelayCommand : ICommand
{
readonly Action<object> execute;
readonly Predicate<object> canExecute;
/// <summary>
/// 创建RelayCommand的新实例
/// </summary>
/// <param name="execute">要执行的操作</param>
/// <param name="canExecute">指示操作是否可以执行的谓词</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
/// <summary>
/// 执行传递给构造函数的操作
/// </summary>
/// <param name="parameter">操作参数(可能为null)</param>
public void Execute(object parameter) => execute(parameter);
/// <summary>
/// 执行传递给构造函数的谓词
/// </summary>
/// <param name="parameter">谓词参数(可能为null)</param>
/// <returns>谓词执行的结果</returns>
public bool CanExecute(object parameter) => canExecute(parameter);
/// <summary>
/// 指示谓词返回值已更改的事件
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}
LayerColorConverter.cs
C#using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
namespace AcadUISample.PaletteWpf
{
/// <summary>
/// 提供获取图层颜色的转换方法
/// </summary>
[ValueConversion(typeof(ICustomTypeDescriptor), typeof(SolidColorBrush))]
class LayerColorConverter : IValueConverter
{
/// <summary>
/// 将表示图层的ICustomTypeDescriptor对象转换为其颜色
/// </summary>
/// <param name="value">要转换的AutoCAD颜色</param>
/// <param name="targetType">SolidColorBrush类型</param>
/// <param name="parameter">未使用</param>
/// <param name="culture">未使用</param>
/// <returns>表示图层颜色的SolidColorBrush实例</returns>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value is Autodesk.AutoCAD.Colors.Color)
{
var acadColor = (Autodesk.AutoCAD.Colors.Color)value;
var drawingColor = acadColor.ColorValue;
var mediaColor = Color.FromRgb(drawingColor.R, drawingColor.G, drawingColor.B);
return new SolidColorBrush(mediaColor);
}
return null;
}
/// <summary>
/// 反向转换方法(未使用)
/// </summary>
/// <returns>始终返回null</returns>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
}