本案例聚焦于开发一款特色鲜明的 AutoCAD 插件。其核心功能在于,用户在精心设计的 WPF 控件界面中输入期望生成圆的数量,完成输入后,当用户点击 “生成” 按钮,一系列联动操作随即展开。通过数据绑定与命令绑定这一精妙机制,系统精准调用相应的业务逻辑代码。在此过程中,借助 AutoCAD 事务处理机制,在模型空间的块表记录里创建指定数量的圆。这些圆的中心位置、半径皆通过随机数生成,颜色也是随机分配。
在整体架构上,本案例成功实现了CAD侧栏菜单与 WPF 控件深度融合的 AutoCAD 插件。通过 C# 作为开发语言,搭建起 WPF 用户界面与 AutoCAD API 交互的桥梁,利用 WPF 强大的界面渲染能力构建美观交互界面,同时借助 AutoCAD API 深入操作 CAD 的绘图空间、数据库等底层功能,有效拓展了 AutoCAD 的功能边界。与传统 CAD 绘图方式相比,该插件在生成类似图形时,展现出更高的效率与灵活性。这不仅实现了 WPF 控件与 CAD 的高效交互,更为开发者在 AutoCAD 二次开发领域提供了极具价值的参考范例。
一、无mvvm绑定模式(一个文件搞定)
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控件并包装到ElementHostvar 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的MessageBoxSystem.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、命令类:
// 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文件
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:
<!-- 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:
// 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));}}
}
// 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
// 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;}}}}
}