在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();
}
}
}