003集——《利用 C# 与 AutoCAD API 开发 WPF 随机圆生成插件》(侧栏菜单+WPF窗体和控件+MVVM)

本案例聚焦于开发一款特色鲜明的 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;
                }
            }
        }
    }
}
相关推荐
FuckPatience5 天前
WPF 具有跨线程功能的UI元素
wpf
诗仙&李白5 天前
HEFrame.WpfUI :一个现代化的 开源 WPF UI库
ui·开源·wpf
He BianGu5 天前
【笔记】在WPF中Binding里的详细功能介绍
笔记·wpf
He BianGu5 天前
【笔记】在WPF中 BulletDecorator 的功能、使用方式并对比 HeaderedContentControl 与常见 Panel 布局的区别
笔记·wpf
123梦野6 天前
WPF——效果和可视化对象
wpf
He BianGu6 天前
【笔记】在WPF中Decorator是什么以及何时优先考虑 Decorator 派生类
笔记·wpf
时光追逐者7 天前
一款专门为 WPF 打造的开源 Office 风格用户界面控件库
ui·开源·c#·.net·wpf
He BianGu7 天前
【笔记】介绍 WPF XAML 中 Binding 的 StringFormat详细功能
笔记·wpf
Rotion_深8 天前
C# WPF使用线程池运行Action方法
c#·wpf·线程池
攻城狮CSU8 天前
WPF 深入系列.2.布局系统.尺寸属性
wpf