在CAD中调用WPF窗口的几种方法(二)

难道就此放弃辛苦所学的WPF吗?

天无绝人之路,终于找到了一个完美的解决方案:

https://forums.autodesk.com/t5/net/wpf-for-a-c-autocad-net-code/td-p/8102366

将主要内容翻译如下:

WPF(Windows Presentation Fundation)是用于创建图形界面的“新技术”(自Windows Vista和Framework 3.0以来)。 使用XAML语言可以创建类似HTML的丰富图形界面。

使用两个文件定义最小WPF接口: - .xaml文件,用于定义接口的外观,事件订阅和控件的数据绑定; - 文件.xaml.cs,它定义了用户界面和应用程序(代码隐藏)之间的交互逻辑。

默认情况下,除非您在WPF应用程序类型项目中,否则Visual Studio 2015不建议添加“Window(WPF)”作为要添加的元素。但是,您始终可以添加“用户控件(WPF)”并在.xaml和.xaml.cs文件中将Window替换为UserControl。 您还可以向Window标记添加“Title”属性和其他一些属性。 此时,您可以将“导出模板”(“文件”菜单)作为“项目模板”,以便将来提供Visual Studio。

静态对话框

作为一个开胃菜,让我们从一个简单的模态对话框开始,该对话框等同于之前的模态对话框(WinForm)。

主要区别在于使用XAML语言来描述用户界面,其体系结构看起来有点像HTML(比较在那里停止)。 这里命名控件以从后面的代码访问它们。控制事件订阅位于后面的代码中的事件处理程序。

要显示从AutoCAD一个模态对话框WPF,使用传递给它与重载的构造类似于用的WinForms用于创建System.Windows.Window的一个实例的Application.ShowModalWindow()方法。

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using System.Collections.Generic;
using System.Linq;
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>
        /// 创建命令实例
        /// 调用构造函数
        /// 方法'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("\nSpécifiez le centre: ");
                if (ppr.Status == PromptStatus.OK)
                {
                    // 在当前空间绘制园
                    using (var tr = db.TransactionManager.StartTransaction())
                    {
                        var curSpace =
                          (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
                        var ucs = ed.CurrentUserCoordinateSystem;
                        using (var circle = new Circle(
                          ppr.Value.TransformBy(ucs),
                          ucs.CoordinateSystem3d.Zaxis,
                          radius))
                        {
                            circle.Layer = layer;
                            curSpace.AppendEntity(circle);
                            tr.AddNewlyCreatedDBObject(circle, true);
                        }
                        tr.Commit();
                    }
                }
            }
        }

        /// <summary>
        /// 获取图层名称
        /// </summary>
        /// <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
<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">Calque :</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">Rayon :</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="OK"
                    Height="24" Width="80" Click="btnOK_Click"/>
            <Button Grid.Column="1" Margin="10" HorizontalAlignment="Left" Content="Annuler"
                    Height="24" Width="80" IsCancel="True" />
        </Grid>
    </Grid>
</Window>
ModalWpfDialog.xaml.cs
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>
        /// btnRadius_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>
        /// btnOK_Click事件
        /// </summary>
        /// <param name="sender">事件来源</param>
        /// <param name="e">事件数据</param>
        private void btnOK_Click(object sender, RoutedEventArgs e)
        {
            DialogResult = true;
        }

        /// <summary>
        /// txtRadius_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。

要稍微推动一下,请在下拉列表中的项目中添加图层颜色的颗粒。 在XAML中,在ComboBox标记内,我们定义了一个ItemTemplate来显示图层名称旁边的颜色颗粒。因此,与ComboBox控件相关的集合元素具有对应于图层名称的属性是另一种类型SolidColorBrush(WPF中用于绘制纯色的类型)。 我们在这里使用一个字典,其中包含图层(键)的名称和与每个图层(值)颜色对应的画笔。

Commands.cs
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Media;
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>
        ///创建命令实例
        ///调用构造函数
        ///方法'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("\nSpécifiez le centre: ");
                if (ppr.Status == PromptStatus.OK)
                {
                    // 在当前空间绘制园
                    using (var tr = db.TransactionManager.StartTransaction())
                    {
                        var curSpace =
                            (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
                        var ucs = ed.CurrentUserCoordinateSystem;
                        using (var circle = new Circle(
                            ppr.Value.TransformBy(ucs),
                            ucs.CoordinateSystem3d.Zaxis,
                            radius))
                        {
                            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
<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" >

    <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">Calque :</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">Rayon :</Label>
            <TextBox Grid.Column="1" Margin="5" HorizontalAlignment="Stretch"
                     Text="{Binding TextRadius, UpdateSourceTrigger=PropertyChanged}" />
            <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 Margin="10" HorizontalAlignment="Right" Content="OK" Height="24" Width="80"
                    IsEnabled="{Binding ValidNumber}" Click="btnOK_Click"/>
            <Button Grid.Column="1" Margin="10" HorizontalAlignment="Left" Content="Annuler"
                    Height="24" Width="80" IsCancel="True" />
        </Grid>
    </Grid>
</Window>
ModalWpfDialog.xaml.cs
using Autodesk.AutoCAD.EditorInput;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;
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>
        /// 获取或设置绑定到ComboBox控件中所选图层的LayerData实例。
        /// </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>
        /// 获取或设置“Radius”编辑框的文本
        /// </summary>
        public string TextRadius
        {
            get { return txtRad; }
            set
            {
                txtRad = value;
                ValidNumber = double.TryParse(value, out radius) && radius > 0.0;
                OnPropertyChanged(nameof(TextRadius));
            }
        }

        /// <summary>
        /// 获取或设置一个值,该值指示“Radius”编辑框中的文本是否表示有效数字。
		/// </summary>
        public bool ValidNumber
        {
            get { return validNumber; }
            set { validNumber = value; OnPropertyChanged(nameof(ValidNumber)); }
        }

        /// <summary>
        /// 创建ModalWpfDialog的新实例
        /// </summary>
        /// <param name="layers">创建ModalWpfDialog的新实例
</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>
        /// btnOK_Click事件
        /// </summary>
        /// <param name="sender">事件来源</param>
        /// <param name="e">事件数据</param>
        private void btnOK_Click(object sender, RoutedEventArgs e) => DialogResult = true;

        /// <summary>
        /// btnRadius_Click事件
        /// </summary>
        /// <param name="sender">事件来源</param>
        /// <param name="e">事件数据</param>
        private void btnRadius_Click(object sender, RoutedEventArgs e)
        {
            // inviter l'utilisateur à spécifier une distance
            var ed = AcAp.DocumentManager.MdiActiveDocument.Editor;
            var opts = new PromptDistanceOptions("\nSpécifiez le rayon: ");
            opts.AllowNegative = false;
            opts.AllowZero = false;
            var pdr = ed.GetDistance(opts);
            if (pdr.Status == PromptStatus.OK)
                TextRadius = pdr.Value.ToString();
        }
    }
}