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;
                }
            }
        }
    }
}
相关推荐
"孙小浩15 小时前
HarmonyOS应用开发者高级-编程题-001
华为·wpf·harmonyos
baivfhpwxf20231 天前
WPF 免费UI 控件HandyControl
ui·wpf
qq_196055871 天前
WPF插入背景图
wpf
baivfhpwxf20231 天前
prism WPF 对话框
c#·wpf
baivfhpwxf20232 天前
WPF 登录页面
ui·wpf
baivfhpwxf20232 天前
prism WPF 模块
wpf
baivfhpwxf20232 天前
prism WPF 导航
wpf
军训猫猫头2 天前
87.在线程中优雅处理TryCatch返回 C#例子 WPF例子
开发语言·ui·c#·wpf
huizhixue-IT4 天前
华为存储考试内容&HCIP-Storage
wpf