- 你好啊!
- 我也开始写博客了,记一些我遇到的但是搜起来资料很少的。
- (但是这里是要写什么呢?)
在VSTO中使用WPF进行用户交互时的线程设置
许多.NET桌面开发者习惯使用WPF开发用户界面。然而,在VSTO应用程序中使用WPF时,与传统桌面开发相比,必须正确设置线程以防止UI阻塞。 在传统桌面开发中,UI线程即是主线程。因此,我们通常在主线程中实例化Window并调用Show()方法来显示Window。但在VSTO应用程序中,主线程是VSTO_Main线程, Office外接程序将在VSTO_Main中通过COM组件访问Office中的数据以及执行操作。如果在该线程中实例化Window,那么所有用户与Window的交互及程序对COM组件的调用都将共享此线程。试想一下,如果我们打算在Window中展示Office中已有的数据,并在加载数据过程中显示等待动画,此时Window和数据读取程序将轮流使用VSTO_Main线程。当读取数据时,等待动画必须等待数据读取完成才能刷新界面,所以我们会看到等待动画卡顿。 为了解决这个问题,开发者可能会尝试将所有对COM组件的调用放在TaskPool中。然而,由于Office应用程序的单线程单元(STA)特性,在后台线程中调用COM组件会显著影响性能。例如,在一个程序中,需要读取Visio页面中的约300个形状并显示在UI中。如果在VSTO_Main线程中执行读取操作,约需10秒钟;而在后台线程中执行则需要约2分钟。这是因为后台线程在访问COM组件时,需要进行对象封送处理,极大降低了效率。 因此,最佳的解决方案是为WPF页面创建独立的线程。这可以通过以下步骤实现: 创建新线程:在新线程中实例化WPF窗口。 设置线程属性:确保新线程为STA线程,并启动消息循环。 管理线程间通信:通过Dispatcher将COM相关操作放在VSTO_Main中进行。 这样可以避免在主线程中进行繁重的UI操作,同时保持对COM组件的高效访问。 public partial class ThisAddIn { public static Dispatcher? VSTODispatcher { get; private set; } public static Dispatcher? UIDispatcher { get; private set; } private void ThisAddIn_Startup(object sender, System.EventArgs e) { // 记录下VSTO_Main线程的Dispatcher,以方便将相COM操作放在VSTO_Main中执行 VSTODispatcher = Dispatcher.CurrentDispatcher; // declare a UI thread to display wpf window var uiThread = new Thread(()=>{ // 记录下UI线程的Dispatcher,以方便在VSTO_Main线程中更新UI UIDispatcher = Dispatcher.CurrentDispatcher; var window = new MainWindow(); window.Show(); // 开启消息循环 System....
AE PID快速入门
本文以中涂洁净间循环风机为例介绍AE PID绘制设备原理图的基本流程。 详细步骤 在Visio中使用空白模板创建新的绘图,单位选择“公制单位”; 在功能区处切换至AE PID选项卡; 点击“编辑”-“初始化”按钮对当前绘图进行初始化。初始化操作将在当前页面中插入一个A0图框,并设置文档页面的网格宽度为2.5 mm。 此外,为文档增加2个AE指定样式。其中,“AE Normal”样式是设备单元的默认样式,“AE Pipeline”是管线的默认样式。 AE样式将使用“思源黑体”作为指定字体,在AE PID插件的安装过程中会自动为系统安装思源黑体。若未能正确显示思源黑体,请尝试手动安装。 若要修改图框大小,用鼠标右键点击图框,并在子类中选择合适的尺寸; 在选项卡中点击“编辑”-“库”加载模具库。 从“AE逻辑”库中拖拽“功能单元”至绘图区; 当您不确定所需的设备对象属于哪一个类型时,可以借助Visio模具的搜索栏进行搜索。这将帮助您快速找到所需的设备对象,无需事先知道其具体类型。 如果您正在使用Windows11系统,搜索功能可能未能如期使用,请按照修复了Visio桌面应用中的形状Windows 11的方法修补。 用鼠标右键点击该“功能单元”,打开“形状数据”面板; 在“形状数据”面板中输入“功能组”值:“GF612”,“功能组名称”值:“中途洁净间循环风机”; 此时,功能单元显示如下: 使用相同的方法从“AE基础”库中拖拽“鼓风机”至功能组内,此时可以看到功能单元边框被高亮为绿色,表示该风机已被加入功能单元。 保持风机的选中状态,在“形状数据”面板中键入“功能元件”值:11,按下回车后该值将显示为“GQ11”; 拖动GQ11上的黄色控制点,可以移动功能元件标签的位置; 为了表示鼓风机配备的电机,从“AE逻辑”库中拖拽“代理功能元件”至功能组内; 选中该代理功能元件,将黄色控制点拖拽至鼓风机上,使代理功能元件与鼓风机相关联; 被关联后,代理功能元件的形状数据处可以看见被关联设备的位号; 当被关联设备发生移动时,关联元件会跟随移动; 保持代理功能元件的选中状态,补充“元件位号”值:“MA01”,描述:“电机”,并将代理功能元件拖拽至鼓风机附近。 继续从“AE基础”库中拖拽“仪表”至功能单元中,并在右键菜单中选择“子类”-“本地面板监视仪表”; 补充形状数据并将仪表移动至合适的位置; 将仪表正中的控制点拖拽至鼓风机上; 使用同样的方法绘制下方的“中控监视操作仪表”; 从“AE管线”库中拖拽“管路”至绘图区,并在“子类”中选择“排出空气”使其显示为柠黄色; 将管路一段连在鼓风机的连接点上,另一端连接至其他对象,并在右键菜单中点击“改变箭头方向”; 多次点击直到箭头朝向如图所示; 使用相同的方法完成另一根管路的绘制; 在选项卡中点击“导出”-“BOM”查看当前图纸的BOM结构; 在选项卡中点击“编辑”-“图例”将在图签上方生成图例; 图例仅显示图纸中出现的设备。
Visio模具库建库指导
分享一些Visio模具库建库过程中值得注意的事项以及原因。 模具的BaseID的唯一性。 BaseID是Master对象的属性之一,Master对象有三个ID:BaseID,UniqueID和ID。BaseID在Master被创建时生成,且不再改变(除非使用程序修改)。因此BaseID非常适合作为维护Master对象时的唯一标识。这是因为,UniqueID会随着Master内容的修改而变化,ID会随着不同的文档而变化。 通常情况下,我们不需要关注BaseID,因为当我们将形状从绘图页拖拽至模具库时,会创建唯一的BaseID。但是,很多管理员在建立新模具时,为了省去添加通用形状和属性,如Tag文本,备注属性等,会直接在模具库中选中已经存在的模具复制粘贴,然后在粘贴后的模具中进行修改。这个时候站贴后的模具将和源模具具有相同的BaseID。为了保证BaseID的唯一性,可以在程序中调用Master.NewBaseID方法获取新的BaseID,或者直接在Openxml文档中键入新的BaseID。 模具的形状界限与网格线重合。 由于Visio中的默认吸附设置包括网格线,当模具的形状界限是网格的整数倍时,模具实例的边界可以吸附在网格线上,这样用者不需要再使用对齐工具也可以快速的实现多个形状的对齐,从而使页面看起来更整洁。 这就要求管理员在设计模具的形状时,充分考虑形状的轮廓尺寸。当然,有些情况下没有办法保证这些线条刚好经过网格线,但是仍然可以通过手动设置形状的Width和Height,使其包围框刚好位于网格线上。 例如,对于图中的两个阀门,尽管电磁气动阀的轮廓远比通用阀门大,但是他们都使用同样的形状界限,即5mmx5mm。(这样做另一个隐藏的好处是:当用户使用电磁气动阀替换通用阀门时,不需要再调整对象的位置,因为他们是同轴的。) 在ShapeSheet的Geometry中使用GUARD和Rel而不是使用绝对尺寸。 当我们在ShapeSheet中修改Geometry属性时,很容易遇到插入一个新的数据行引发形状的Width和Height重新计算,导致才修改好的形状意外改变。因此,在设计初期就应该使用GUARD对形状的Width和Height以及几何数据写保护,防止插入新的几何数据时Visio重新计算引发的意外情况。 在几何数据中使用相对值而不是绝对值,则是为了解决用户手动调整实例尺寸时,代表实例的几何形状可以保持正确的比例,以防出现原来是个原型,现在是个椭圆形的情况。 在创建具有多个子类的对象时,将表示不通子类的多个几何数据创建在同一个形状对象上,而不是使用多个形状对象。 这句话描述的可能比较抽象。有的时候,为了缩减模具库中模具的数量,管理员可能会考虑将多个具有类似的对象设计成一个模具,并通过属性切换显示与隐藏。例如,图中的阀门聚合了多种形式的阀体,并根据用户的选择进行形状的切换。 在实现这个功能时,有两种做法:方法一是在模具的形状组中创建多个形状用以表示不通的阀体,例如三个形状分别表示通用发、三通阀、角式阀;方法二是在一个形状中,插入多个几何数据块。我认为方法二是更好的做法,因为如果用户在使用过程中解散了图纸中实例的组,不会在图纸中生成隐藏的形状对象。尽管这样增加了管理的复杂性,因为无法通过形状的名称判断几何数据块表示的形状。
ReactiveUI ViewModel Properties总结
属性类型 在ReactiveUI中,ViewModel中的属性可以根据其用途划分为三种情况:读写属性(Read-Write Properties)、只读属性(Read-Only Properties)和输出属性(Output Properties)。 读写属性 (Read-Write Properties): 读写属性是可以被服务修改,也可以被用户在View中修改的属性。这类属性是我们通常比较熟悉的普通属性。 例如,用户的姓名可能是被服务加载的,而在加载之后又被用户修改。 只读属性 (Read-Only Properties): 只读属性是在构造函数中被初始化且在之后不再变化的属性。 例如, 用户的ID在一般情况下时不允许变化的。 输出属性 (Output Properties): 输出属性是ReactiveUI中新提出的概念,初次接触ReactiveUI时,可能会将输出属性与只读属性混为一谈。尽管输出属性对于用户而言是只读的,但是对于属性本身是可变的。这类属性通常由Observable变化而成,表示属性值可能随时间变化。 例如,用户负债率随用户的总资产和总负债变化,但负债率属性本身是不允许被用户修改的。 在ReactiveUI中,使用这三种属性类型可以更清晰地表示属性的特性和用途。读写属性用于需要双向绑定的数据,只读属性用于一次性初始化后不再改变的数据,而输出属性用于表示可能随时间变化的数据流。这种划分有助于更好地理解和管理ViewModel中的属性。 属性的声明与绑定方法 在明确了属性类型的基础上,ViewModel中所有非集合类型的属性都可以按照下面固定的方式进行声明与绑定。 读写属性的声明需要调用ReactiveObject的RaiseAndSetIfChanged方法,该方法实现了INotifydPropertyChanged。 private string name; public string Name { get => name; set => this.RaiseAndSetIfChanged(ref name, value); } 读写属性的绑定使用TView的Bind方法进行双向绑定。 this.WhenActivated(disposable => { this.Bind(ViewModel, vm => vm.Name, v => v.NameTextBox.Text) .DisposeWith(disposable); }); 只读属性使用一般的声明方式即可。 public int Id {get;} 绑定时,使用单向绑定。 this.WhenActivated(disposable => { this.OneWayBind(ViewModel, vm => vm.Id, v => v.IdTextBlock.Text) ....
Visual Studio Installer实现覆盖安装新版本的方法
当使用Visual Studio Installer进行打包时,要实现安装时自动卸载旧版本然后安装新版本,需要同时设置以下各点: Deployment Project Properties -> DetectNewerInstalledVersion -> True Deployment Project Properties -> RemovePreviousVersions -> True Deployment Project Properties -> Version 其中,Version被安装程序用来判断是否继续执行安装程序,所以Version值应大于上一个版本。 满足以上条件时,执行安装程序可以顺利执行,且控制面板中可以看到更新后的版本号。但是,安装程序仍然可能没有正确执行。这是因为项目的主输出并没有被正确拷贝。这往往是因为没有正确设置项目(程序项目,非部署项目)的AssemblyInfo。主输出中的Version将被用来比较是否需要拷贝主输出到安装目录,所以当上一版本的主输出版本为0.2.1.0时,即使已经设置部署项目的Version为0.2.2.0,由于此时主输出的版本仍然为0.2.1.0,安装过程中不会拷贝新生成的主输出到安装目录。 主输出的Version设置位于程序项目的AssemblyInfo.cs文件中。只有当此文件中的AssemblyVersion或AssemblyFileVersion的值大于上一个版本的值时,才会覆盖原安装目录的dll。