本案例聚焦于开发一款特色鲜明的 AutoCAD 插件。其核心功能在于,用户在精心设计的 WPF 控件界面中输入期望生成圆的数量,完成输入后,当用户点击 "生成" 按钮,一系列联动操作随即展开。通过数据绑定与命令绑定这一精妙机制,系统精准调用相应的业务逻辑代码。在此过程中,借助 AutoCAD 事务处理机制,在模型空间的块表记录里创建指定数量的圆。这些圆的中心位置、半径皆通过随机数生成,颜色也是随机分配。
在整体架构上,本案例成功实现了CAD侧栏菜单与 WPF 控件深度融合的 AutoCAD 插件。通过 C# 作为开发语言,搭建起 WPF 用户界面与 AutoCAD API 交互的桥梁,利用 WPF 强大的界面渲染能力构建美观交互界面,同时借助 AutoCAD API 深入操作 CAD 的绘图空间、数据库等底层功能,有效拓展了 AutoCAD 的功能边界。与传统 CAD 绘图方式相比,该插件在生成类似图形时,展现出更高的效率与灵活性。这不仅实现了 WPF 控件与 CAD 的高效交互,更为开发者在 AutoCAD 二次开发领域提供了极具价值的参考范例。
一、无mvvm绑定模式(一个文件搞定)
cs
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Colors;
using System;
using System.Windows; // WPF基础命名空间
using System.Windows.Controls; // WPF控件
using System.Windows.Media; // WPF颜色
using acadApp = Autodesk.AutoCAD.ApplicationServices.Application; // AutoCAD应用别名
namespace WpfCircleGenerator
{
public class Commands
{
private static Autodesk.AutoCAD.Windows.PaletteSet _wpfPs;
// 修改后的PaletteSet创建代码
[CommandMethod("xx")]
public static void 侧栏菜单WPF()
{
if (_wpfPs == null)
{
_wpfPs = new Autodesk.AutoCAD.Windows.PaletteSet("WPF随机圆生成器")
{
Size = new System.Drawing.Size(380, 1300),
Dock = Autodesk.AutoCAD.Windows.DockSides.Left,
Style = Autodesk.AutoCAD.Windows.PaletteSetStyles.ShowCloseButton
};
// 创建WPF控件并包装到ElementHost
var wpfControl = new WpfCircleControl();
var elementHost = new System.Windows.Forms.Integration.ElementHost()
{
Child = wpfControl,
Dock = System.Windows.Forms.DockStyle.Fill
};
_wpfPs.Add("WpfCircleControl", elementHost); // 现在使用elementHost
_wpfPs.Visible = true;
}
else
{
_wpfPs.Visible = !_wpfPs.Visible;
}
}
}
// WPF用户控件(注意继承自System.Windows.Controls.UserControl)
public class WpfCircleControl : UserControl
{
private TextBox _txtCount;
private Button _btnCreate;
public WpfCircleControl()
{
CreateLayout();
// 设置控件背景色(比CAD2024默认深灰稍浅)
this.Background = new SolidColorBrush(System.Windows.Media.Color.FromRgb(59, 68, 83));//cad2024默认深灰
CreateLayout();
this.Width = 380; // 设置控件宽度
this.Height = 1200; // 设置控件高度
}
private void CreateLayout()
{
// 创建主布局容器,使用Grid布局
var mainGrid = new Grid();
mainGrid.Background = this.Background; // 继承控件背景
// 定义三行,每行高度为自动适应内容,并且行与行之间有一定的间距
mainGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
mainGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
mainGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
// 定义两列,第一列自适应内容宽度,第二列占据剩余空间
mainGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
mainGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
// 输入标签,提示用户输入圆的数量
var lblCount = new TextBlock()
{
Foreground = Brushes.White, // 白色文字
Text = "请输入圆的数量:",
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(20, 20, 10, 20) // 设置标签的外边距,增加空白空间
};
Grid.SetRow(lblCount, 0); // 将标签放置在第0行
Grid.SetColumn(lblCount, 0); // 将标签放置在第0列
// 输入文本框,用于用户输入圆的数量
_txtCount = new TextBox()
{
Background = new SolidColorBrush(System.Windows.Media.Color.FromRgb(59, 68, 83)),
Foreground = Brushes.White,
BorderBrush = Brushes.Gray,
//Width = 100, // 设置文本框的宽度
Height = 30, // 设置文本框的高度
Margin = new Thickness(10, 20, 20, 20), // 设置文本框的外边距
VerticalAlignment = VerticalAlignment.Center, // 文本框垂直居中对齐
HorizontalAlignment = HorizontalAlignment.Stretch,// 文本框水平拉伸以填充可用空间
// TextAlignment = System.Windows.TextAlignment.Center, // 文本内容水平居中显示
VerticalContentAlignment = System.Windows.VerticalAlignment.Center // 文本内容垂直居中显示
};
Grid.SetRow(_txtCount, 0); // 将文本框放置在第0行
Grid.SetColumn(_txtCount, 1); // 将文本框放置在第1列
// 按钮,点击后触发生成圆的操作
_btnCreate = new Button()
{
Background = new SolidColorBrush(System.Windows.Media.Color.FromRgb(79, 88, 103)),//按钮cad浅色
Foreground = Brushes.White,
BorderBrush = Brushes.DimGray,
Content = "生成",
//Width = 120, // 设置按钮的宽度
Height = 40, // 设置按钮的高度
Margin = new Thickness(20, 20, 20, 20), // 设置按钮的外边距
//HorizontalAlignment = HorizontalAlignment.Center, // 按钮水平居中对齐
HorizontalAlignment = HorizontalAlignment.Stretch, // 文本框水平拉伸以填充可用空间
VerticalAlignment = VerticalAlignment.Bottom // 按钮垂直底部对齐
};
_btnCreate.Click += BtnCreate_Click; // 为按钮的点击事件添加处理方法
Grid.SetRow(_btnCreate, 2); // 将按钮放置在第2行
Grid.SetColumnSpan(_btnCreate, 2); // 按钮跨两列显示
// 添加控件到网格布局中
mainGrid.Children.Add(lblCount);
mainGrid.Children.Add(_txtCount);
mainGrid.Children.Add(_btnCreate);
// 设置用户控件的内容为创建好的Grid布局
this.Content = mainGrid;
}
private void BtnCreate_Click(object sender, RoutedEventArgs e)
{
if (!int.TryParse(_txtCount.Text, out int count) || count <= 0)
{
// 使用WPF的MessageBox
System.Windows.MessageBox.Show("请输入有效的正整数!", "输入错误",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
Document doc = acadApp.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
// 关键修改:添加文档锁定
using (doc.LockDocument()) // 创建DocumentLock作用域
using (Transaction tr = db.TransactionManager.StartTransaction())
{
try
{
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
BlockTableRecord btr = tr.GetObject(
bt[BlockTableRecord.ModelSpace],
OpenMode.ForWrite) as BlockTableRecord;
Random rand = new Random();
for (int i = 0; i < count; i++)
{
Circle circle = new Circle
{
Center = new Point3d(
rand.NextDouble() * 100,
rand.NextDouble() * 100,
0),
Radius = rand.NextDouble() * 10 + 5,
ColorIndex = (short)rand.Next(1, 256)
};
btr.AppendEntity(circle);
tr.AddNewlyCreatedDBObject(circle, true);
}
tr.Commit();
doc.Editor.WriteMessage($"\n成功创建{count}个随机圆!");
}
catch (Exception ex)
{
doc.Editor.WriteMessage($"\n错误:{ex.Message}");
tr.Abort();
}
}
}
}
}
二、MVVM模式
生成效果如下:

0、文件结构:

1、命令类:
cs
// Commands.cs
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Windows;
using System.Windows.Forms.Integration;
using WpfCircleGenerator.Views;
[assembly: ExtensionApplication(typeof(WpfCircleGenerator.MyExtension))]
[assembly: CommandClass(typeof(WpfCircleGenerator.Commands))]
namespace WpfCircleGenerator
{
public class MyExtension : IExtensionApplication
{
public void Initialize()
{
Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\n插件已加载!输入\"xx\"运行。(山水qq443440204)\n");
}
public void Terminate() { }
}
public static class Dimensions
{
public const int Pheight = 1300; // 面板高度
public const int Pwidth = 400; // 面板宽度
}
public class Commands
{
private static PaletteSet _wpfPs;
[CommandMethod("XX")]
public static void OpenWpfPalette()
{
if (_wpfPs == null)
{
_wpfPs = new PaletteSet("山水WPF随机圆生成器")
{
Size = new System.Drawing.Size(Dimensions.Pwidth, Dimensions.Pheight),面板大小
Dock = DockSides.Left,停靠位置
Style = PaletteSetStyles.ShowCloseButton |//关闭按钮
PaletteSetStyles.ShowAutoHideButton |//自动隐藏按钮
PaletteSetStyles.ShowPropertiesMenu//|//属性菜单
};
var view = new CircleView();创建WPF控件
var host = new ElementHost
{
Child = view,将WPF控件添加到WinForms控件中
Dock = System.Windows.Forms.DockStyle.Fill
};
_wpfPs.Add("CircleGenerator", host);//将WinForms控件添加到面板中
_wpfPs.Visible = true;显示面板
try { _wpfPs.Location = new System.Drawing.Point(50, 200); } // 设置初始位置}
catch (Exception ex) { }
}
else
{
_wpfPs.Visible = !_wpfPs.Visible;
}
}
}
}
2、views:
2.1 xaml对应的cs文件
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfCircleGenerator.Views
{
/// <summary>
/// CircleView.xaml 的交互逻辑
/// </summary>
public partial class CircleView : UserControl
{
public CircleView()
{
InitializeComponent();
}
}
}
2.2 xaml:
cs
<!-- Views/CircleView.xaml -->
<UserControl x:Class="WpfCircleGenerator.Views.CircleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfCircleGenerator.ViewModels"
Width="380" Height="1200"
Background="#3B4453">
<UserControl.DataContext>
<vm:MainViewModel />
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 输入标签 -->
<TextBlock Grid.Row="0" Grid.Column="0"
Text="请输入圆的数量:"
Margin="20,20,10,20"
Foreground="White"
VerticalAlignment="Center"/>
<!-- 输入框Background="#3B4453" -->
<TextBox Grid.Row="0" Grid.Column="1"
Text="{Binding CircleCount, UpdateSourceTrigger=PropertyChanged}"
Margin="10,20,20,20"
Height="30"
Background="#3B4453"
Foreground="White"
BorderBrush="Gray"
VerticalContentAlignment="Center"/>
<!-- 生成按钮 -->
<Button Grid.Row="2" Grid.ColumnSpan="2"
Content="生成"
Command="{Binding GenerateCommand}"
Background="#4F5867"
Foreground="White"
BorderBrush="DimGray"
Margin="20"
Height="40"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom">
<!-- 自定义按钮样式 -->
<Button.Style>
<!-- 定义按钮的样式,TargetType指定样式应用于Button控件 -->
<Style TargetType="{x:Type Button}">
<!-- 默认状态下的样式设置 -->
<!-- 设置按钮背景色为深蓝灰色(RGB:79,88,103) -->
<Setter Property="Background" Value="#4F5867"/>
<!-- 设置按钮文字颜色为白色 -->
<Setter Property="Foreground" Value="White"/>
<!-- 设置按钮边框颜色为暗灰色 -->
<Setter Property="BorderBrush" Value="DimGray"/>
<!-- 定义按钮的控件模板,用于完全自定义按钮的外观 -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<!--
按钮的主要视觉元素 - Border控件
x:Name="border":为后续触发器提供引用目标
Background:绑定到按钮的Background属性
BorderBrush:绑定到按钮的BorderBrush属性
BorderThickness="1":1像素宽度的边框
CornerRadius="3":3像素圆角,使按钮边角略微圆润
-->
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1"
CornerRadius="3">
<!--
内容展示器,用于显示按钮的内容(如文字)
HorizontalAlignment/VerticalAlignment="Center":内容居中显示
-->
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<!-- 定义按钮的交互状态触发器 -->
<ControlTemplate.Triggers>
<!--
鼠标悬停状态触发器(IsMouseOver = True时激活)
效果:轻微改变背景和边框颜色,提供视觉反馈
-->
<Trigger Property="IsMouseOver" Value="True">
<!-- 将背景色改为稍亮的蓝灰色(RGB:95,104,119) -->
<Setter TargetName="border" Property="Background" Value="#5F6877"/>
<!-- 将边框色改为浅灰蓝色(RGB:141,153,166) -->
<Setter TargetName="border" Property="BorderBrush" Value="#8D99A6"/>
</Trigger>
<!--
按钮按下状态触发器(IsPressed = True时激活)
效果:加深背景色,模拟物理按压效果
-->
<Trigger Property="IsPressed" Value="True">
<!-- 将背景色改为更深的蓝灰色(RGB:59,68,83) -->
<Setter TargetName="border" Property="Background" Value="#3B4453"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</Grid>
</UserControl>
3、ViewModels:
cs
// ViewModels/MainViewModel.cs
using System;
using System.ComponentModel;
using System.Windows;
using WpfCircleGenerator.Services;
namespace WpfCircleGenerator.ViewModels
{
public class MainViewModel : INotifyPropertyChanged
{
private int _circleCount;
private readonly RelayCommand _generateCommand;
public int CircleCount
{
get => _circleCount;
set
{
_circleCount = value;
OnPropertyChanged(nameof(CircleCount));
}
}
public RelayCommand GenerateCommand => _generateCommand;
public MainViewModel()
{
_generateCommand = new RelayCommand(ExecuteGenerate, CanExecuteGenerate);
}
private bool CanExecuteGenerate() => CircleCount > 0;
private void ExecuteGenerate()
{
try
{
AcadService.CreateCircles(CircleCount);
}
catch (Exception ex)
{
MessageBox.Show($"生成失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
cs
// ViewModels/RelayCommand.cs
using System;
using System.Windows.Input;
namespace WpfCircleGenerator.ViewModels
{
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action execute, Func<bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;
public void Execute(object parameter) => _execute();
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
}
4、Services
cs
// Services/AcadService.cs
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
namespace WpfCircleGenerator.Services
{
public static class AcadService
{
public static void CreateCircles(int count)
{
var doc = Application.DocumentManager.MdiActiveDocument;
using (doc.LockDocument())
using (var tr = doc.TransactionManager.StartTransaction())
{
try
{
var db = doc.Database;
var bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
var btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
var rand = new Random();
for (int i = 0; i < count; i++)
{
var circle = new Circle
{
Center = new Point3d(rand.NextDouble() * 100, rand.NextDouble() * 100, 0),
Radius = rand.NextDouble() * 10 + 5,
ColorIndex = (short)rand.Next(1, 256)
};
btr.AppendEntity(circle);
tr.AddNewlyCreatedDBObject(circle, true);
}
tr.Commit();
doc.Editor.WriteMessage($"\n成功创建{count}个随机圆!");
}
catch
{
tr.Abort();
throw;
}
}
}
}
}