admin管理员组文章数量:1026989
安装Addon插件
前序
前几天试装了CorelDraw2020和CorelDraw2021,发现了CorelDraw2020已经把“宏”都叫做“脚本”了;还有一个新名称“可视化工作室编辑器”,就是Visual Studio Tools for Applications了。
CorelDraw2021甚至还加入了JavaScript,但代码还只能是记事本编辑。看来CorelDraw要把二次开发的工具都要集齐了。技术更新很快,必须得用新版本了,不然很多新的功能都用不上。
自从装了Visual Studio 2019后,发现框架和功能都多了,除了.Net Framework,还加入了 .Net Core。.Net Core将简称为.Net,是独立于.Net Framework的一种新的框架。据官方资料,.Net Framework可能要停止迭代了,转向 .Net Core; .Net Core最大的特点是跨平台性,能在Windows、Linux 和 macOS等系统上运行,桌面端和移动端通杀,而.Net Framework只能运行在桌面端。官方建议.Net Framework项目向 .Net Core迁移。经过测试用户界面的Dll文件似乎还不能用 .Net Core来生成,所以用户界面的Dll还得是 framework框架。试用后,觉得.Net Framework和 .Net Core的桌面开发Windows和WPF只有些不大的区别,代码书写还没发现不能用原来书写方式的地方,用 .Net Core来开发项目,目前来看学习成本还是可以忽略的。这次安装Addon插件的项目就尝试用 .Net Core框架。CorelDraw也将主要采用CorelDraw2021。提一下,虽然用的是Win10,,安装CorelDraw2021和AI2021时,CorelDraw2021表示安装可以进行但使用可能有问题,AI2021干脆快要安装结束就表示系统不符合安装要求,原来问题出在Win10的版本还是停留在1709上;只好让Windows检查和自动更新了,完成后原来所安装的程序都能正常使用,AI2021也能装上了;但更新的时间可真的有点漫长。
Addon插件主要有4个文件:“AppUI.xslt”、“UserUI.xslt”、 “CorelDrw.addon”和一个“.dll”程序集文件。考虑到在别的电脑上拷贝这些文件不大可能依靠CorelDraw Addons 插件模板来完成,我们就探讨一下别的方法。
创建.Net Core窗体项目
我们启动Visual Studio,创建新项目,搜索Windows,选 .Net Core(),下一步。如下图:
填写项目名称(最好用英文吧),选择项目存放位置,下一步。出现界面如下图:
这里要选择目标框架, .Net Core 3.1或.NET 5.0。可以看出,原来还叫 .Net Core,现在叫.NET了。.NET 5.0肯定是更完善,我们就选它。项目创建就完成了。初始化文件结构如下:
和. Net Framework的引用部分不同,现在已经叫依赖项,就是这个项目要依赖的程序集框架,里面包括分析器和框架两部分。分析器按字面理解应该是分析代码用的,提出建议,提示,或错误警告等。框架分两部分:Microsoft.NETCore.App是.NETCore 的主体部分,所有.NETCore 都会有这部分;Microsoft.WindowsDesktop.App.WindowsForms是与项目模板相关的部分,现在我们创建的是桌面窗体项目,如果是WPF和控制台等,这部分的名称就不同了。分别展开一下,每部分都有许多程序集引用过来了,基本上我们可能会用到的都预先引用进来了,再也不用再去电脑上查找引用了,你只要在不同的代码文档中using一下就行了,这可方便多了。如下图:
但这里只有.NETCore框架里面的,我们还要动手添加我们熟悉的Corel.Interop.VGCore,我们来添加一下。如下图:
项目和共享的项目两项下都没有东西,那只能去COM下面找了,依旧没有Corel.Interop.VGCore。可能是VGCore不是提供.Net的,是面向.Net Framework的。如下图:
那只好向CorelDraw的安装目录下去查找了,找到了也还是能加进来的。如下图:
提一下,VGCore加入后,可能是不拷进本地(项目下)的,最好改为拷到本地,并且安装插件时把VGCore也拷过去。如下图:
窗体主代码
看一下窗体,窗体标题.Net Framework时有“设计”字样,现在成了Design,还没翻译过来。双击窗体。如下图:
自动转到代码区了,引用块引用了窗体操作时常用的程序集,而且自动生成加载事件函数Form1_ Load。如下图:
程序的执行在这里首先引用.Net窗体基本工具,然后是InitializeComponent,再到Form1_ Load。
Form1 : Form表示Form1这个类继承了Form类,就有了Form的特征属性方法。
public Form1():构造函数,必须带有类名;和一般函数不同,一般函数都要求有返回值或void。
InitializeComponent:初始化组件,这里就是构造窗体结构。
Form1_Load:窗体加载事件,窗体构造好后会执行里面的代码。
object sender:object翻译为对象,也是一个类,这个类也可以转化为其他类如int、double、Form等; 其它类都可以转化成这个类;MSDN上称互相转换过程为装箱和拆箱。Sender则是object类的具体对象。
EventArgs e:EventArgs事件参数类,e则是具体的参数。
在Form1_ Load代码块写下如下代码:
Corel.Interop.VGCore.Application app = new Corel.Interop.VGCore.Application();
MessageBox.Show(app.ActiveDocument.Name);
运行程序,正常能得到当前文件的名称(名称是不带目录但带后缀的)。可见.Net是可以像.Net Framework一样操作CorelDraw的。经测试发现,不同版本的VGCore程序集只能对对应版本的CorelDraw有效,比如2021版的VGCore引入操作CorelDraw2020,代码没有提示错误,但运行时就会发生错误。所以必须对不同版本的CorelDraw,准备不同的VGCore。我们来看下如何获取电脑上安装的CorelDraw有几个版本。
获取CorelDraw在注册表信息中的安装位置
Windows中,很多的系统和软件信息都存放在注册表上,用键盘键WIN+R键调出运行对话框,输入regedit回车,如下图:
这样就可以在注册表编辑器中查看注册表信息。依次展开HKEY_ LOCAL_ MACHINE、SOFTWARE、Corel、CorelDRAW,就可以看到该电脑上安装了几个版本的CorelDRAW。如下图:
20版就是2018版,22版就是2020版,如此类推。点击20.0这个目录(叫注册表键),可以看到键项的信息,其中有一个ProgramsDir项,其值是可执行程序在电脑上的目录。ProgramsDir项的值在程序中会用到。同样也可以查看其它版本的CorelDRAW的运行目录。如下图:
复制下面一段代码到Form1_ Load里面:
string coreldrawPath = @"SOFTWARE\Corel\CorelDRAW";//定位到注册表目录RegistryKey regKey = Registry.LocalMachine;RegistryKey coreldrawKey = regKey.OpenSubKey(coreldrawPath, false);string[] coreldrawVersonArr = coreldrawKey.GetSubKeyNames();//所有版本号数组List<string> programsDirList = new List<string>();for (int i = 0; i < coreldrawVersonArr.Length; i++){RegistryKey versonKey = coreldrawKey.OpenSubKey(coreldrawVersonArr[i]);string programsDir = "ProgramsDir";object dirObject = versonKey.GetValue(programsDir);RegistryValueKind regValueKind = versonKey.GetValueKind(programsDir);if (regValueKind == RegistryValueKind.String){programsDirList.Add(dirObject.ToString());}}
如下图:
你会看见许多的错误,红线标出来的地方。把鼠标放到红线处的单词RegistryKey上,在弹出的提示中点击“显示可能的修补程序”,弹出以下界面:
一般点击第一条修补建议,即程序不能确定你的RegistryKey词的意义,但又能在它的框架中找到这个词,所以提出修改建议为在文档头部引用部分引入程序集命名空间,该命名空间有这个类。第二条建议是直接从框架中一步一步找出这个词的出处。按第一条建议更正代码后你会发现红线都不见了,效果如下:
而在引用部分多了一条,如下图:
如果你选择第二条更正意见,那只有这一处的错误得到了修正,后面的错误还得进行更正。但如果是在你自己构造的公有函数里会经常这样做,以使你复制到别处后也不会发生错误。
RegistryKey:注册表键类,提供了访问和操作注册表键的功能。
“@"SOFTWARE\Corel\CorelDRAW"”:在C#中对目录路径的处理经常会用到@修饰符,其意义是因为“\”符号在C#中用来作为转义字符(如“\r”符号就表示新的一行的符号),在字符串前面加个@,告诉调试器这里众多的“\”不是转义字符,不要执行转义。当然你可以把所有的“\”都改为“\\”进行转义也是可以的。
Registry.LocalMachine:即是让注册表定位到HKEY_ LOCAL_ MACHINE的位置,这个位置下是众多系统和软件的信息所在。
OpenSubKey:展开到指定的子健。
string[] coreldrawVersonArr:定义一个字符串数组,以接受获取到的字符串数组,类似VBA中的dim coreldrawVersonArr() as string?不好意思,VBA基本忘得差不多了;为什么要定义这个类型的变量呢,你可以把鼠标放到GetSubKeyNames上面,明确告诉你GetSubKeyNames这个方法返回的是string[]。
List<string> programsDirList = new List<string>():初始化一个字串列,因为现在还不能直接获取到目标,所以只能先new初始化,以后看准机会再把目标装进来。programsDirList是变量名称。List<string>和string[]通过ToList和ToArray方法能互相转化并赋值。List<string>延展性更强,是string[]的升级代换者;在程序代码中推荐用List<string>的可变数组,可以删除里面的元素又能添加元素,而string[]一经定义或赋值定义,其元素数量就不能更改了。
For关键词:循环结构,即在指定的次数下执行相同的操作,以使代码简化,但也增加了复杂度;在无法确定具体的次数的情况下,你必须得用循环结构。
int i = 0; i < coreldrawVersonArr.Length; i++:定义变量i,且 i从0开始,到coreldrawVersonArr字串数组的长度再减去1结束,i每次循环都在自身上加上1以便判断是否要跳出循环。i++等同于i=i+1,同理i—就等同于i=i-1。
GetValue:获取键项的值方法,因为键项的值可能会返回string字串型或dword型,所以只能笼统的返回object型,你要用,就自己转为具体的类型(拆箱)再用。
GetValueKind方法:返回的是RegistryValueKind枚举型,所以以后还要通过判断(if)来处理取得的数据。
RegistryValueKind:enum枚举型,里面封装了所有可能出现的情况,这样做的目的是程序环境提供方不必一一去定义这类数据。使用的时候自己再去取自己需要的类型去匹配。如下图:
programsDirList.Add:字串列变量添加字串。每次添加一个。在输入左括号时Visual Studio就会提示您要输入什么类型的变量数据,这点蛮方便的。
dirObject.ToString:将object类型的数据转为字串型,几乎C#中的对象都能这样操作。如果object对象里面装的是字串型,你就能得到一串字串,而很多情况下都会如愿以偿,因为字串型的应用很广泛。
我们来测试一下代码的运行是否符合预期,我们从工具箱中拖一个richTextBox到窗体上,调整下大小位置,如下图:
在刚才代码的末尾加入以下代码,作为测试结果用:
string showResult = string.Join(System.Environment.NewLine, programsDirList);
richTextBox1.Text = showResult;
System.Environment.NewLine: NewLine新行,类似于"\r"或"\r\n",
System.Environment.NewLine让环境自己决定用什么字符代表新行,因为不同的环境对新的一行有不同的写法要求,好像CorelDraw中和WinForm中就不同,用到再去分析。这里不能用控制台输出的方式,是得不到数据展示的。
string.Join方法:将其它字串结构的对象连接起来成一条字串,这里是programsDirList字串列。
找到启动按钮,如下图:
因为力先生这里是一个解决方案包括多个项目,所以熟悉的“启动”已变成项目的名称“Setup”,点击“Setup”,可以看到以下效果:
可以看见2021版的程序目录前面的盘符是小写d,看一下注册表里确实如此。但这关系不大,对于路径操作,大小写盘符是不影响操作的,除非你是进行字符串处理。
构建窗体控件
主面板Panel
我们先把richTextBox1的控件(组件)从窗体上删除,选定后按DEL键就可以了。
由于其它电脑上所安装CorelDraw版本无法预知,所以窗体控件的构造只能代码去生成了。
首先是添加一个Panel(面板),删除最后两句的代码,再加入以下代码:
//构建板块
Panel panel = new Panel();
panel.BackColor = Color.AliceBlue;
panel.AutoSize = true;
panel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
this.Controls.Add(panel);
Panel panel = new Panel:初始化新的面板。Panel在工具箱的容器里面,意思是作为容器来用的。控件初始化时如果你不作指定,Marggin都是0,而在手动添加Marggin通常都是3,3,3,3。
Color:结构,既有不同的属性,也能包括方法。这里指定panel的背景颜色为AliceBlue,其实是不必要,只是为了观察方便。
AutoSize:指定是否可以随Panel里面的控件增减而自动调整尺寸,以适应里面的内容物。
AutoSizeMode:尺寸自动延伸的方式,默认是只增不减,指定其为GrowAndShrink,就是让它既能增又能减。
This:指代Form1本身。
this.Controls.Add(panel):在This的所有控件中再添加新的控件。可以看下Add方法的定义:
Form1和panel都是Control类,其实窗体里面所有的控件都是Control类派生出来的,比如你可以这样写:
Control control = panel;//用control来代替panel来操作
Controls:Control集合。
运行测试一下,如下图:
标签label
再加入以下代码:
//构建标签
int lineSpace = 40;
Label label = new Label();
label.AutoSize = true;
label.Text = "请选择要安装或卸载的版本";
label.Top = 0;label.Left = 0;// 函数块的代码基本上都是以分号来结束的,并不是以回车,所以这里写两句代码在同一行也是可以的。
panel.Controls.Add(label);
lineSpace:定义一个int整型变量,由于以后还要添加许多控件,所以用它来辅助确定控件的垂直方向位置。
Label:标签类,主要是用来写文字用的。
AutoSize:属性设置为true,则该标签能根据文字的长度自动伸缩;如果为false,则为固定的长度,溢出部分将被裁切隐藏。
Text:标签的文字。
Top,Left:标签在加入的容器对象的位置,由左边和顶部来确定。
选择版本单选按钮组
再加入以下代码:
//构建选择版本单选块
Panel panel1 = new Panel();
panel1.AutoSize = true;
panel1.AutoSizeMode = AutoSizeMode.GrowAndShrink;
panel1.Left = 0;
panel1.Top = label.Bottom + 30;
List<RadioButton> versonRadioButtonList = new List<RadioButton>();
for (int i = 0; i < programsDirList.Count; i++)
{
RadioButton radioButton = new RadioButton();
string progName = programsDirList[i].Split('\\')[3];
radioButton.AutoSize = true;
radioButton.Text = " " + progName;
radioButton.Left = 0;
radioButton.Top = i * lineSpace;
versonRadioButtonList.Add(radioButton);
if (i == 0) radioButton.Checked = true;
panel1.Controls.Add(radioButton);
}
panel.Controls.Add(panel1);
Panel panel1 = new Panel():要重新定义一个面板panel1,如果用panel = new Panel(),则是原来panel板块被重新构建。
panel1.Top = label.Bottom + 30:指定控件的位置Top。控件的位置不能赋值Right和Bottom指定,这两个属性是只读的。这里参照label的底部位置来确定panel1的顶部位置。如果不指定位置,就会在容器的左上角0,0的位置加入,有可能造成重叠。
RadioButton:单选按钮,这里构建了3个单选按钮,都在panel1里面加入,所以这3个按钮在panel1层次是互斥的,只能选择一个。
Location:指定位置,也就是左和上的位置。
Split('\\')[3]:就是按照字符串中的”\”符号来分成几片,3指第4片,因为C#的对象集合序号一般都是从0开始的。
radioButton.Text = " " + progName:连接字符串的符号是”+“加号。
if (i == 0) radioButton.Checked = true:让第一个radioButton的初始状态为选中状态。
panel1.Controls.Add(radioButton):这里需注意是用哪个控件来添加进去。panel1的父级是panel,panel的父级是Form1。
我们来测试下,效果如下:
可见panel是随其内容物而自动延伸的,由于文字末尾和下部都有固定的留空,所以右边和底部会有空间。
选择安装或卸载单选按钮组
再加入以下代码:
//构建选择安装或卸载单选按钮组Panel panel2 = new Panel();panel2.AutoSize = true;panel2.AutoSizeMode = AutoSizeMode.GrowAndShrink;panel2.Left = 0;panel2.Top = panel1.Bottom + 25;RadioButton radioButton1 = new RadioButton();radioButton1.Location = new Point(0, 0);radioButton1.Text = "安装或重新安装";radioButton1.AutoSize = true;radioButton1.Checked = true;RadioButton radioButton2 = new RadioButton();radioButton2.Left = radioButton1.Right + 20;radioButton2.Top = radioButton1.Top;radioButton2.AutoSize = true;radioButton2.Text = "卸载";List<RadioButton> setupRadioButtonList = new List<RadioButton> { radioButton1, radioButton2 };panel2.Controls.AddRange(new Control[] { radioButton1, radioButton2 });panel.Controls.Add(panel2);
List<RadioButton> setupRadioButtonList = new List<RadioButton> { radioButton1, radioButton2 }:这里由于对象数目已经确定,就不用初始化了,直接加进来,注意是花括号而不是小括号。
Controls.AddRange:就是一次加多个的意思。
再测试以下,效果如下图:
确定按钮
再加入以下代码块:
//构建确定按钮Button button = new Button();button.Text = "确定";button.Left = 0;button.Top = panel2.Bottom + lineSpace;panel.Controls.Add(button);//构建运行提示标签Label tipLabel = new Label();tipLabel.AutoSize = true;tipLabel.Location = new Point(0, button.Bottom + lineSpace);tipLabel.Text = "等待您的指令……";tipLabel.ForeColor = Color.Green;panel.Controls.Add(tipLabel);
再测试,效果如下图:
提示:如果你在写代码时需要对前面的部分代码进行重新测试,就在指定的位置插入代码:
return;//VBA中是Exit Sub
控件居中
下面要让panel面板居中于窗体,并去掉背景色,使用以下代码:
//窗体控件居中
panel.Location = new Point(Width / 2 - panel.Width / 2, Height / 2 - panel.Height / 2 - 20);
panel.BackColor = Control.DefaultBackColor;
Control.DefaultBackColor:设置为控件本身的默认背景色,即是和窗体的背景色一样,因为窗体的背景色没做过更改。
测试一下,效果如下:
还要使窗体居于屏幕的中央。这里预先写好了一个公用函数,把它复制到Form1_Load函数块的外面,类的里面,即和Form1_Load函数同级。代码如下:
public static void SetFormAtScreenCenter(Form form,bool maxSize){form.StartPosition = FormStartPosition.Manual; int formWidth = form.Width; int formHeight = form.Height;int workingAreaWidth = SystemInformation.WorkingArea.Width;int workingAreaHeight = SystemInformation.WorkingArea.Height; if (maxSize == true){form.Location = new Point(0, 0);form.Size = new Size(workingAreaWidth, workingAreaHeight);}else {form.Location = new Point(workingAreaWidth/2-form.Width/2, workingAreaHeight/2-form.Height/2);}}
form.StartPosition = FormStartPosition.Manual:关键是这一句,如果去掉,可能达不到预期。
然后在Form1_Load代码末尾再加一句:
SetFormAtScreenCenter(this, false);
窗体界面就构造完了。
注册按钮单击事件
下面要写确定按钮的事件函数了。如果是手动添加按钮,就可以通过双击事件属性Click来生成事件函数框架。由于窗体的元素都是动态添加的,就无法这样做。但也有解决办法,比如在Form1_Load代码button定义之后加入下面代码:
button.Click += (sender, e) =>
{
};
这里button是刚才创建的命令按钮的名称,Click单击事件;sender接收点击事件传过来的控件对象,object类型;e是事件参数,EventArgs类型。这个写法就是在函数内动态注册了一个事件。大括号内是触发事件要执行的代码,大括号后面要有英文冒号结束,可以看作这本身也是一行代码。我们在中间插入代码,如:
button.Click += (o, e) =>{tipLabel.Text = "配置开始……";tipLabel.Refresh();//定义与调用函数相对应的变量string coreldrawPath = string.Empty;//后面有赋值string addonDllTitle = "LixianCsCoreldraw";string copyFilesPath = string.Empty;//后面有赋值bool setupOrUnload = setupRadioButtonList[0].Checked;//确定选择版本的CorelDraw执行程序目录RadioButton radioButton = versonRadioButtonList.Find(a => a.Checked == true);int selectIndex = versonRadioButtonList.IndexOf(radioButton);string verson = versonRadioButtonList[selectIndex].Text.Trim().Split(' ').Last();string currentDirectory = System.IO.Directory.GetCurrentDirectory();copyFilesPath = currentDirectory + "\\" + verson;string selectCoreldrawRunPath = programsDirList[selectIndex];//确定是否有CorelDraw的进程在运行,如果有就找出与选择相对应的进程List<System.Diagnostics.Process> processList = System.Diagnostics.Process.GetProcesses().ToList();processList = processList.FindAll(p => p.ProcessName.Contains("CorelDRW"));int lastIndex = selectCoreldrawRunPath.LastIndexOf("\\");int lenth = selectCoreldrawRunPath.Length;if (lastIndex == lenth - 1) selectCoreldrawRunPath = selectCoreldrawRunPath.Remove(lenth - 1, 1);coreldrawPath = selectCoreldrawRunPath;System.Diagnostics.Process waitProcess = null;processList.ForEach(a =>{string aDir = a.MainModule.FileName;int aLastIndex = aDir.LastIndexOf("\\");int aLength = aDir.Length;string path = aDir.Remove(aLastIndex, aLength - aLastIndex);if (path.ToUpper() == selectCoreldrawRunPath.ToUpper()){waitProcess = a;}});if (waitProcess != null){tipLabel.Text = "保存CorelDRW" + verson + "文件,退出CorelDRW再重启……";tipLabel.ForeColor = Color.OrangeRed;
button.Enabled = false;Timer timer = new Timer();timer.Enabled = true;timer.Interval = 1000;timer.Tick += (to, te) =>{processList = System.Diagnostics.Process.GetProcesses().ToList();processList = processList.FindAll(p => p.ProcessName.Contains("CorelDRW"));waitProcess = processList.Find(p => p.MainModule.FileName.Contains(verson)); if (waitProcess == null){timer.Dispose();
button.Enabled = true;try{CopyOrDeleteFiles(coreldrawPath, addonDllTitle, copyFilesPath, setupOrUnload);tipLabel.Text = "已经配置好!可以关闭或继续配置。";tipLabel.ForeColor = Color.Green;}catch{tipLabel.BackColor = Color.Red;tipLabel.Text = "发生配置问题,请反馈……";}}};timer.Start();}else{try{CopyOrDeleteFiles(coreldrawPath, addonDllTitle, copyFilesPath, setupOrUnload);tipLabel.Text = "已经配置好!可以关闭或继续配置。";tipLabel.ForeColor = Color.Green;}catch{tipLabel.BackColor = Color.Red;tipLabel.Text = "发生配置问题,请反馈……";}}};
这段代码的作用就是判断是否有与已选择版本的CorelDraw在运行,如果有就要等待手动退出相应版本的CorelDraw,才能把插件文件拷到CorelDraw的Addon目录下。因为CorelDraw启动时先读取配置文件,然后调用DLL生成用户界面,DLL里面一般会有事件方法,在CorelDraw退出前都不会中断对DLL的控制,所以你无法拷贝或删除和原来同名的DLL。即使是首次安装,可以拷贝进去,但也需要CorelDraw重启动,CorelDraw才会重新加载新增的Addon插件,所以干脆就都等退出后再拷贝或删除。代码还在等待CorelDraw退出时加了个监控事件,监控是否已经退出。如果已经退出,则进行拷贝或删除文件的操作;如果无需退出,直接进行拷贝或删除文件的操作。
tipLabel.Refresh():刷新一下显示,要不由于程序的运行过快,显示不出来。
GetCurrentDirectory:获取当前程序集所在目录,即Setup.exe所在目录。
radioButtonList.Find(a => a.Checked == true):在选择版本单选按钮系列中查找Checked属性值等于true的单选按钮,有且只有一个;a指代的是radioButtonList元素中的可能的1个,当然你也可以用其它符号或单词。
IndexOf:获取目标对象在对象集合中的位置。
System.Diagnostics.Process:Process是当前电脑的进程,一般情况下打开一次应用软件,就会新开1个进程。在电脑上按Ctrl+Alt+Del组合键然后选择任务管理器,就可以观察到进程的实时状况。测试了一下, CorelDraw属于这种情况;CorelDraw也允许同一版本的自身可以启动多次,每启动1次,就新开1个进程;问题还是比较复杂。任务管理器的信息比如下图:
GetProcesses():获取电脑上所有的进程。
FindAll:和Find相比,Find是在集合中找到匹配的第1个,而FindAll则是找出所有匹配的。
p.ProcessName.Contains("CorelDRW"); ProcessName,进程的名称,也就是执行文件的名称,不带目录也不带后缀的。如“CorelDRW”就是CorelDraw进程的名称。多个进程可以有同1个进程名称。Contains包括,只要字串中含有"CorelDRW",就是所要查找的目标进程。
selectCoreldrawRunPath.Length:选择的CorelDRW版本程序的目录字符串的长度,1个英文字母和1个中文字长度都是1。Length也可以是对象数组的对象个数,比如代码块开头我们就定义了1个coreldrawVersonArr对象数组,你可以用coreldrawVersonArr.Length获得里面对象的个数。对象列的和集合的长度定义是Count,比如processList.Count;比如CorelDraw的Shapes就代表图形的集合,它的数量就可以Shapes.Count来获取。
selectCoreldrawRunPath.Remove(lenth - 1, 1):目的是把目录字串中的最后一个“\”字符去掉,因为目录操作中,允许最后1个目录名有或者没有反斜杠。字符串Remove方法,去掉你所指定的范围。字符串截取字符串还有另外1个函数Substring,是从原string中找出子string,并返回子string。如果用Replace方法,用新的字串替换掉查找到的子string,相同的全部替换掉。用这3个方法时,要注意是否要重新赋值给原string或新string,不然经常会得不到期望的结果。
System.Diagnostics.Process waitProcess = null:定义1个Process型的变量selectProcess,先赋值为null,因为很多情况下调试会检查对象是否实例化,如果不实例化就会认为有错误;if语块中的对象实例化在实际情况下不一定会被执行到。null空空如也的意思,万事皆空,大体上能有new的对象都可以赋值为null,可以理解为定义个只有外壳的对象;不可以有new的对象,比如int、double等就不能赋值null,可以先赋值0;string则是string.Empty或””。
processList.ForEach:遍历processList对象列中的每1个对象,这里语块中用了”a =>”,MSDN叫Lambda表达式,用于简化代码。你也可以用foreach (System.Diagnostics.Process process in processList){ }来代替。注意ForEach和foreach的大小写区别,C#对大小写是严格的,还有foreach语句块的大括号后面没有英文冒号。
Timer timer = new Timer():初始化一个Timer类控件,用来监控对应的CorelDraw是否退出完成,Timer的运行是开新的线程。那么现在就有了2个线程,主线程是Form1,新线程是timer。这里必须开新线程,要不在主线程中用do语句块会造成窗体堵塞假死,无法访问上面的控件包括关闭窗体。
button.Enabled = false:锁定button按钮,在timer监控期间不让重复做卸载或安装的事情。监控完成后再开锁。
timer.Enabled:设定timer是否有效。
timer.Interval:设置timer多少事件进行1次扫描监控。
timer.Tick += (to, te) =>{}:timer监控时要处理的事情。
timer.Start():开始监控任务。
timer.Dispose():销毁timer,包括在程序中占用的资源。窗体控件基本上都有Dispose这个方法。
try{}catch{}:必须分行写,是对代码的执行中发生的错误进行拦截和对错误的发生进行处理。如果语块中有错误,不进行拦截的话,会额外弹出错误信息,影响体验。在循环中经常会有一些对象集合种的对象用同样的方法去访问,有个别会发生错误,而这个对象可能不是我们所需要的,我们就可以用try{}catch{}去跳过它或进行一些处理。try{}catch{}必须成对贴身出现,还可以在后面加加finally{},进行进一步的处理。
CopyOrDeleteFiles(coreldrawPath, addonDllTitle, copyFilesPath, setupOrUnload):这里是传参调用外面的函数进行文件夹和文件夹下面的文件进行增删操作。
复制和删除文件夹的被调用函数
所以我们在Form1_Load外面插入已经写好的供调用的函数(函数先后次序一般没有规定):
private void CopyOrDeleteFiles(string coreldrawPath, string addonDllTitle, string copyFilesPath, bool setupOrUnload){string coreldrawAddonPath = coreldrawPath + "\\Addons\\";string meAddonPath = coreldrawAddonPath + addonDllTitle;if (setupOrUnload){richTextBox1.Text = meAddonPath;if (Directory.Exists(meAddonPath)){Directory.GetFiles(meAddonPath).ToList().ForEach(File.Delete);}else{Directory.CreateDirectory(meAddonPath);}Directory.GetFiles(copyFilesPath).ToList().ForEach(a =>{string dir = meAddonPath + "\\" + a.Split('\\').Last();File.Copy(a, dir, true);});}else{if (Directory.Exists(meAddonPath)){Directory.GetFiles(meAddonPath).ToList().ForEach(File.Delete);Directory.Delete(meAddonPath);}}}
private void CopyOrDeleteFiles:定义了一个类内函数,私有的,外部类不能访问,private也可以不写;void是不需要向调用函数返回数据,CopyOrDeleteFiles函数名。自定义函数都必须采用这样的结构。
(string coreldrawPath, string addonDllTitle, string copyFilesPath, bool setupOrUnload):规定了调用函数必须提供的参数类型,并且必须提供参数的值。每个参数用英文逗号隔开,末尾没有逗号。如果不需要参数传递括号内就留空,但括号也必须有。定义了这个被调用函数后,调用函数就要准备相应的类型的具体的数据提供给被调用函数,并且要一一对应。当然你也可以不用另外写被调用函数,有时为了将过长的代码分块写或其它函数也要调用就有必要另写函数块。调用函数传参时不能写参数类型,只提供具体的参数值就可以了。这个供调用函数的功能是:如果是安装或重新安装,就判断自己的Addon目录是否存在,不存在就先删除目录下的文件,如果目录不存在就新建目录,然后再把文件考进去;如果是卸载,如果有安装过,就把文件和目录清空。由于C#中不能像在电脑上手动删除文件夹时就把里面的文件全部删除了,所以要先删除文件,再删除目录。建立目录和文件同理。并且这里要求所有文件都在同一个文件夹内,如果需要在文件夹下再新建文件夹的,还要重写CopyOrDeleteFiles函数。
coreldrawPath:相应版本的插件所在目录,代码中已经赋值。
addonDllTitle:插件DLL的标题,不包括”.dll“,必须指定。
copyFilesPath:这里的处理是:在运行前在setup.exe同一个目录下,新建一个目录。如果对应2021版,就要在setup.exe建一个2021的目录;如果2020版,就要建一个2020的目录;以此类推,并把用到的文件再拷进去。建立完成后就由代码自己去获取。比如下图:
setupOrUnload:安装或卸载,安装就是true;卸载就是false。运行时赋值。
窗体运行
运行效果如下:
关闭CorelDRAW 2021后,运行效果如下图:
结语
这次课件实现和示例Addon插件的安装,介绍了窗体元素的构建方法,分析了C#语法、代码含义和用法。课件写的可以说很详细,对每个步骤和代码都有详细的分析,主要是面对刚入门的C#同行一起探讨。
经过代码编写和测试,认为.Net和.Net Framework除了引用框架外,使用方法基本相似,从.Net Framework转向.Net是完全可能的。新手可以直接入手.Net。生成目录内容不同,应用程序既生成了.exe,也生成了.dll,也许有相应的应用场景。
软件的升级都带来一些创新,一般也都兼容性更强,用新的版本开发项目也许会更便捷高效。
安装Addon插件
前序
前几天试装了CorelDraw2020和CorelDraw2021,发现了CorelDraw2020已经把“宏”都叫做“脚本”了;还有一个新名称“可视化工作室编辑器”,就是Visual Studio Tools for Applications了。
CorelDraw2021甚至还加入了JavaScript,但代码还只能是记事本编辑。看来CorelDraw要把二次开发的工具都要集齐了。技术更新很快,必须得用新版本了,不然很多新的功能都用不上。
自从装了Visual Studio 2019后,发现框架和功能都多了,除了.Net Framework,还加入了 .Net Core。.Net Core将简称为.Net,是独立于.Net Framework的一种新的框架。据官方资料,.Net Framework可能要停止迭代了,转向 .Net Core; .Net Core最大的特点是跨平台性,能在Windows、Linux 和 macOS等系统上运行,桌面端和移动端通杀,而.Net Framework只能运行在桌面端。官方建议.Net Framework项目向 .Net Core迁移。经过测试用户界面的Dll文件似乎还不能用 .Net Core来生成,所以用户界面的Dll还得是 framework框架。试用后,觉得.Net Framework和 .Net Core的桌面开发Windows和WPF只有些不大的区别,代码书写还没发现不能用原来书写方式的地方,用 .Net Core来开发项目,目前来看学习成本还是可以忽略的。这次安装Addon插件的项目就尝试用 .Net Core框架。CorelDraw也将主要采用CorelDraw2021。提一下,虽然用的是Win10,,安装CorelDraw2021和AI2021时,CorelDraw2021表示安装可以进行但使用可能有问题,AI2021干脆快要安装结束就表示系统不符合安装要求,原来问题出在Win10的版本还是停留在1709上;只好让Windows检查和自动更新了,完成后原来所安装的程序都能正常使用,AI2021也能装上了;但更新的时间可真的有点漫长。
Addon插件主要有4个文件:“AppUI.xslt”、“UserUI.xslt”、 “CorelDrw.addon”和一个“.dll”程序集文件。考虑到在别的电脑上拷贝这些文件不大可能依靠CorelDraw Addons 插件模板来完成,我们就探讨一下别的方法。
创建.Net Core窗体项目
我们启动Visual Studio,创建新项目,搜索Windows,选 .Net Core(),下一步。如下图:
填写项目名称(最好用英文吧),选择项目存放位置,下一步。出现界面如下图:
这里要选择目标框架, .Net Core 3.1或.NET 5.0。可以看出,原来还叫 .Net Core,现在叫.NET了。.NET 5.0肯定是更完善,我们就选它。项目创建就完成了。初始化文件结构如下:
和. Net Framework的引用部分不同,现在已经叫依赖项,就是这个项目要依赖的程序集框架,里面包括分析器和框架两部分。分析器按字面理解应该是分析代码用的,提出建议,提示,或错误警告等。框架分两部分:Microsoft.NETCore.App是.NETCore 的主体部分,所有.NETCore 都会有这部分;Microsoft.WindowsDesktop.App.WindowsForms是与项目模板相关的部分,现在我们创建的是桌面窗体项目,如果是WPF和控制台等,这部分的名称就不同了。分别展开一下,每部分都有许多程序集引用过来了,基本上我们可能会用到的都预先引用进来了,再也不用再去电脑上查找引用了,你只要在不同的代码文档中using一下就行了,这可方便多了。如下图:
但这里只有.NETCore框架里面的,我们还要动手添加我们熟悉的Corel.Interop.VGCore,我们来添加一下。如下图:
项目和共享的项目两项下都没有东西,那只能去COM下面找了,依旧没有Corel.Interop.VGCore。可能是VGCore不是提供.Net的,是面向.Net Framework的。如下图:
那只好向CorelDraw的安装目录下去查找了,找到了也还是能加进来的。如下图:
提一下,VGCore加入后,可能是不拷进本地(项目下)的,最好改为拷到本地,并且安装插件时把VGCore也拷过去。如下图:
窗体主代码
看一下窗体,窗体标题.Net Framework时有“设计”字样,现在成了Design,还没翻译过来。双击窗体。如下图:
自动转到代码区了,引用块引用了窗体操作时常用的程序集,而且自动生成加载事件函数Form1_ Load。如下图:
程序的执行在这里首先引用.Net窗体基本工具,然后是InitializeComponent,再到Form1_ Load。
Form1 : Form表示Form1这个类继承了Form类,就有了Form的特征属性方法。
public Form1():构造函数,必须带有类名;和一般函数不同,一般函数都要求有返回值或void。
InitializeComponent:初始化组件,这里就是构造窗体结构。
Form1_Load:窗体加载事件,窗体构造好后会执行里面的代码。
object sender:object翻译为对象,也是一个类,这个类也可以转化为其他类如int、double、Form等; 其它类都可以转化成这个类;MSDN上称互相转换过程为装箱和拆箱。Sender则是object类的具体对象。
EventArgs e:EventArgs事件参数类,e则是具体的参数。
在Form1_ Load代码块写下如下代码:
Corel.Interop.VGCore.Application app = new Corel.Interop.VGCore.Application();
MessageBox.Show(app.ActiveDocument.Name);
运行程序,正常能得到当前文件的名称(名称是不带目录但带后缀的)。可见.Net是可以像.Net Framework一样操作CorelDraw的。经测试发现,不同版本的VGCore程序集只能对对应版本的CorelDraw有效,比如2021版的VGCore引入操作CorelDraw2020,代码没有提示错误,但运行时就会发生错误。所以必须对不同版本的CorelDraw,准备不同的VGCore。我们来看下如何获取电脑上安装的CorelDraw有几个版本。
获取CorelDraw在注册表信息中的安装位置
Windows中,很多的系统和软件信息都存放在注册表上,用键盘键WIN+R键调出运行对话框,输入regedit回车,如下图:
这样就可以在注册表编辑器中查看注册表信息。依次展开HKEY_ LOCAL_ MACHINE、SOFTWARE、Corel、CorelDRAW,就可以看到该电脑上安装了几个版本的CorelDRAW。如下图:
20版就是2018版,22版就是2020版,如此类推。点击20.0这个目录(叫注册表键),可以看到键项的信息,其中有一个ProgramsDir项,其值是可执行程序在电脑上的目录。ProgramsDir项的值在程序中会用到。同样也可以查看其它版本的CorelDRAW的运行目录。如下图:
复制下面一段代码到Form1_ Load里面:
string coreldrawPath = @"SOFTWARE\Corel\CorelDRAW";//定位到注册表目录RegistryKey regKey = Registry.LocalMachine;RegistryKey coreldrawKey = regKey.OpenSubKey(coreldrawPath, false);string[] coreldrawVersonArr = coreldrawKey.GetSubKeyNames();//所有版本号数组List<string> programsDirList = new List<string>();for (int i = 0; i < coreldrawVersonArr.Length; i++){RegistryKey versonKey = coreldrawKey.OpenSubKey(coreldrawVersonArr[i]);string programsDir = "ProgramsDir";object dirObject = versonKey.GetValue(programsDir);RegistryValueKind regValueKind = versonKey.GetValueKind(programsDir);if (regValueKind == RegistryValueKind.String){programsDirList.Add(dirObject.ToString());}}
如下图:
你会看见许多的错误,红线标出来的地方。把鼠标放到红线处的单词RegistryKey上,在弹出的提示中点击“显示可能的修补程序”,弹出以下界面:
一般点击第一条修补建议,即程序不能确定你的RegistryKey词的意义,但又能在它的框架中找到这个词,所以提出修改建议为在文档头部引用部分引入程序集命名空间,该命名空间有这个类。第二条建议是直接从框架中一步一步找出这个词的出处。按第一条建议更正代码后你会发现红线都不见了,效果如下:
而在引用部分多了一条,如下图:
如果你选择第二条更正意见,那只有这一处的错误得到了修正,后面的错误还得进行更正。但如果是在你自己构造的公有函数里会经常这样做,以使你复制到别处后也不会发生错误。
RegistryKey:注册表键类,提供了访问和操作注册表键的功能。
“@"SOFTWARE\Corel\CorelDRAW"”:在C#中对目录路径的处理经常会用到@修饰符,其意义是因为“\”符号在C#中用来作为转义字符(如“\r”符号就表示新的一行的符号),在字符串前面加个@,告诉调试器这里众多的“\”不是转义字符,不要执行转义。当然你可以把所有的“\”都改为“\\”进行转义也是可以的。
Registry.LocalMachine:即是让注册表定位到HKEY_ LOCAL_ MACHINE的位置,这个位置下是众多系统和软件的信息所在。
OpenSubKey:展开到指定的子健。
string[] coreldrawVersonArr:定义一个字符串数组,以接受获取到的字符串数组,类似VBA中的dim coreldrawVersonArr() as string?不好意思,VBA基本忘得差不多了;为什么要定义这个类型的变量呢,你可以把鼠标放到GetSubKeyNames上面,明确告诉你GetSubKeyNames这个方法返回的是string[]。
List<string> programsDirList = new List<string>():初始化一个字串列,因为现在还不能直接获取到目标,所以只能先new初始化,以后看准机会再把目标装进来。programsDirList是变量名称。List<string>和string[]通过ToList和ToArray方法能互相转化并赋值。List<string>延展性更强,是string[]的升级代换者;在程序代码中推荐用List<string>的可变数组,可以删除里面的元素又能添加元素,而string[]一经定义或赋值定义,其元素数量就不能更改了。
For关键词:循环结构,即在指定的次数下执行相同的操作,以使代码简化,但也增加了复杂度;在无法确定具体的次数的情况下,你必须得用循环结构。
int i = 0; i < coreldrawVersonArr.Length; i++:定义变量i,且 i从0开始,到coreldrawVersonArr字串数组的长度再减去1结束,i每次循环都在自身上加上1以便判断是否要跳出循环。i++等同于i=i+1,同理i—就等同于i=i-1。
GetValue:获取键项的值方法,因为键项的值可能会返回string字串型或dword型,所以只能笼统的返回object型,你要用,就自己转为具体的类型(拆箱)再用。
GetValueKind方法:返回的是RegistryValueKind枚举型,所以以后还要通过判断(if)来处理取得的数据。
RegistryValueKind:enum枚举型,里面封装了所有可能出现的情况,这样做的目的是程序环境提供方不必一一去定义这类数据。使用的时候自己再去取自己需要的类型去匹配。如下图:
programsDirList.Add:字串列变量添加字串。每次添加一个。在输入左括号时Visual Studio就会提示您要输入什么类型的变量数据,这点蛮方便的。
dirObject.ToString:将object类型的数据转为字串型,几乎C#中的对象都能这样操作。如果object对象里面装的是字串型,你就能得到一串字串,而很多情况下都会如愿以偿,因为字串型的应用很广泛。
我们来测试一下代码的运行是否符合预期,我们从工具箱中拖一个richTextBox到窗体上,调整下大小位置,如下图:
在刚才代码的末尾加入以下代码,作为测试结果用:
string showResult = string.Join(System.Environment.NewLine, programsDirList);
richTextBox1.Text = showResult;
System.Environment.NewLine: NewLine新行,类似于"\r"或"\r\n",
System.Environment.NewLine让环境自己决定用什么字符代表新行,因为不同的环境对新的一行有不同的写法要求,好像CorelDraw中和WinForm中就不同,用到再去分析。这里不能用控制台输出的方式,是得不到数据展示的。
string.Join方法:将其它字串结构的对象连接起来成一条字串,这里是programsDirList字串列。
找到启动按钮,如下图:
因为力先生这里是一个解决方案包括多个项目,所以熟悉的“启动”已变成项目的名称“Setup”,点击“Setup”,可以看到以下效果:
可以看见2021版的程序目录前面的盘符是小写d,看一下注册表里确实如此。但这关系不大,对于路径操作,大小写盘符是不影响操作的,除非你是进行字符串处理。
构建窗体控件
主面板Panel
我们先把richTextBox1的控件(组件)从窗体上删除,选定后按DEL键就可以了。
由于其它电脑上所安装CorelDraw版本无法预知,所以窗体控件的构造只能代码去生成了。
首先是添加一个Panel(面板),删除最后两句的代码,再加入以下代码:
//构建板块
Panel panel = new Panel();
panel.BackColor = Color.AliceBlue;
panel.AutoSize = true;
panel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
this.Controls.Add(panel);
Panel panel = new Panel:初始化新的面板。Panel在工具箱的容器里面,意思是作为容器来用的。控件初始化时如果你不作指定,Marggin都是0,而在手动添加Marggin通常都是3,3,3,3。
Color:结构,既有不同的属性,也能包括方法。这里指定panel的背景颜色为AliceBlue,其实是不必要,只是为了观察方便。
AutoSize:指定是否可以随Panel里面的控件增减而自动调整尺寸,以适应里面的内容物。
AutoSizeMode:尺寸自动延伸的方式,默认是只增不减,指定其为GrowAndShrink,就是让它既能增又能减。
This:指代Form1本身。
this.Controls.Add(panel):在This的所有控件中再添加新的控件。可以看下Add方法的定义:
Form1和panel都是Control类,其实窗体里面所有的控件都是Control类派生出来的,比如你可以这样写:
Control control = panel;//用control来代替panel来操作
Controls:Control集合。
运行测试一下,如下图:
标签label
再加入以下代码:
//构建标签
int lineSpace = 40;
Label label = new Label();
label.AutoSize = true;
label.Text = "请选择要安装或卸载的版本";
label.Top = 0;label.Left = 0;// 函数块的代码基本上都是以分号来结束的,并不是以回车,所以这里写两句代码在同一行也是可以的。
panel.Controls.Add(label);
lineSpace:定义一个int整型变量,由于以后还要添加许多控件,所以用它来辅助确定控件的垂直方向位置。
Label:标签类,主要是用来写文字用的。
AutoSize:属性设置为true,则该标签能根据文字的长度自动伸缩;如果为false,则为固定的长度,溢出部分将被裁切隐藏。
Text:标签的文字。
Top,Left:标签在加入的容器对象的位置,由左边和顶部来确定。
选择版本单选按钮组
再加入以下代码:
//构建选择版本单选块
Panel panel1 = new Panel();
panel1.AutoSize = true;
panel1.AutoSizeMode = AutoSizeMode.GrowAndShrink;
panel1.Left = 0;
panel1.Top = label.Bottom + 30;
List<RadioButton> versonRadioButtonList = new List<RadioButton>();
for (int i = 0; i < programsDirList.Count; i++)
{
RadioButton radioButton = new RadioButton();
string progName = programsDirList[i].Split('\\')[3];
radioButton.AutoSize = true;
radioButton.Text = " " + progName;
radioButton.Left = 0;
radioButton.Top = i * lineSpace;
versonRadioButtonList.Add(radioButton);
if (i == 0) radioButton.Checked = true;
panel1.Controls.Add(radioButton);
}
panel.Controls.Add(panel1);
Panel panel1 = new Panel():要重新定义一个面板panel1,如果用panel = new Panel(),则是原来panel板块被重新构建。
panel1.Top = label.Bottom + 30:指定控件的位置Top。控件的位置不能赋值Right和Bottom指定,这两个属性是只读的。这里参照label的底部位置来确定panel1的顶部位置。如果不指定位置,就会在容器的左上角0,0的位置加入,有可能造成重叠。
RadioButton:单选按钮,这里构建了3个单选按钮,都在panel1里面加入,所以这3个按钮在panel1层次是互斥的,只能选择一个。
Location:指定位置,也就是左和上的位置。
Split('\\')[3]:就是按照字符串中的”\”符号来分成几片,3指第4片,因为C#的对象集合序号一般都是从0开始的。
radioButton.Text = " " + progName:连接字符串的符号是”+“加号。
if (i == 0) radioButton.Checked = true:让第一个radioButton的初始状态为选中状态。
panel1.Controls.Add(radioButton):这里需注意是用哪个控件来添加进去。panel1的父级是panel,panel的父级是Form1。
我们来测试下,效果如下:
可见panel是随其内容物而自动延伸的,由于文字末尾和下部都有固定的留空,所以右边和底部会有空间。
选择安装或卸载单选按钮组
再加入以下代码:
//构建选择安装或卸载单选按钮组Panel panel2 = new Panel();panel2.AutoSize = true;panel2.AutoSizeMode = AutoSizeMode.GrowAndShrink;panel2.Left = 0;panel2.Top = panel1.Bottom + 25;RadioButton radioButton1 = new RadioButton();radioButton1.Location = new Point(0, 0);radioButton1.Text = "安装或重新安装";radioButton1.AutoSize = true;radioButton1.Checked = true;RadioButton radioButton2 = new RadioButton();radioButton2.Left = radioButton1.Right + 20;radioButton2.Top = radioButton1.Top;radioButton2.AutoSize = true;radioButton2.Text = "卸载";List<RadioButton> setupRadioButtonList = new List<RadioButton> { radioButton1, radioButton2 };panel2.Controls.AddRange(new Control[] { radioButton1, radioButton2 });panel.Controls.Add(panel2);
List<RadioButton> setupRadioButtonList = new List<RadioButton> { radioButton1, radioButton2 }:这里由于对象数目已经确定,就不用初始化了,直接加进来,注意是花括号而不是小括号。
Controls.AddRange:就是一次加多个的意思。
再测试以下,效果如下图:
确定按钮
再加入以下代码块:
//构建确定按钮Button button = new Button();button.Text = "确定";button.Left = 0;button.Top = panel2.Bottom + lineSpace;panel.Controls.Add(button);//构建运行提示标签Label tipLabel = new Label();tipLabel.AutoSize = true;tipLabel.Location = new Point(0, button.Bottom + lineSpace);tipLabel.Text = "等待您的指令……";tipLabel.ForeColor = Color.Green;panel.Controls.Add(tipLabel);
再测试,效果如下图:
提示:如果你在写代码时需要对前面的部分代码进行重新测试,就在指定的位置插入代码:
return;//VBA中是Exit Sub
控件居中
下面要让panel面板居中于窗体,并去掉背景色,使用以下代码:
//窗体控件居中
panel.Location = new Point(Width / 2 - panel.Width / 2, Height / 2 - panel.Height / 2 - 20);
panel.BackColor = Control.DefaultBackColor;
Control.DefaultBackColor:设置为控件本身的默认背景色,即是和窗体的背景色一样,因为窗体的背景色没做过更改。
测试一下,效果如下:
还要使窗体居于屏幕的中央。这里预先写好了一个公用函数,把它复制到Form1_Load函数块的外面,类的里面,即和Form1_Load函数同级。代码如下:
public static void SetFormAtScreenCenter(Form form,bool maxSize){form.StartPosition = FormStartPosition.Manual; int formWidth = form.Width; int formHeight = form.Height;int workingAreaWidth = SystemInformation.WorkingArea.Width;int workingAreaHeight = SystemInformation.WorkingArea.Height; if (maxSize == true){form.Location = new Point(0, 0);form.Size = new Size(workingAreaWidth, workingAreaHeight);}else {form.Location = new Point(workingAreaWidth/2-form.Width/2, workingAreaHeight/2-form.Height/2);}}
form.StartPosition = FormStartPosition.Manual:关键是这一句,如果去掉,可能达不到预期。
然后在Form1_Load代码末尾再加一句:
SetFormAtScreenCenter(this, false);
窗体界面就构造完了。
注册按钮单击事件
下面要写确定按钮的事件函数了。如果是手动添加按钮,就可以通过双击事件属性Click来生成事件函数框架。由于窗体的元素都是动态添加的,就无法这样做。但也有解决办法,比如在Form1_Load代码button定义之后加入下面代码:
button.Click += (sender, e) =>
{
};
这里button是刚才创建的命令按钮的名称,Click单击事件;sender接收点击事件传过来的控件对象,object类型;e是事件参数,EventArgs类型。这个写法就是在函数内动态注册了一个事件。大括号内是触发事件要执行的代码,大括号后面要有英文冒号结束,可以看作这本身也是一行代码。我们在中间插入代码,如:
button.Click += (o, e) =>{tipLabel.Text = "配置开始……";tipLabel.Refresh();//定义与调用函数相对应的变量string coreldrawPath = string.Empty;//后面有赋值string addonDllTitle = "LixianCsCoreldraw";string copyFilesPath = string.Empty;//后面有赋值bool setupOrUnload = setupRadioButtonList[0].Checked;//确定选择版本的CorelDraw执行程序目录RadioButton radioButton = versonRadioButtonList.Find(a => a.Checked == true);int selectIndex = versonRadioButtonList.IndexOf(radioButton);string verson = versonRadioButtonList[selectIndex].Text.Trim().Split(' ').Last();string currentDirectory = System.IO.Directory.GetCurrentDirectory();copyFilesPath = currentDirectory + "\\" + verson;string selectCoreldrawRunPath = programsDirList[selectIndex];//确定是否有CorelDraw的进程在运行,如果有就找出与选择相对应的进程List<System.Diagnostics.Process> processList = System.Diagnostics.Process.GetProcesses().ToList();processList = processList.FindAll(p => p.ProcessName.Contains("CorelDRW"));int lastIndex = selectCoreldrawRunPath.LastIndexOf("\\");int lenth = selectCoreldrawRunPath.Length;if (lastIndex == lenth - 1) selectCoreldrawRunPath = selectCoreldrawRunPath.Remove(lenth - 1, 1);coreldrawPath = selectCoreldrawRunPath;System.Diagnostics.Process waitProcess = null;processList.ForEach(a =>{string aDir = a.MainModule.FileName;int aLastIndex = aDir.LastIndexOf("\\");int aLength = aDir.Length;string path = aDir.Remove(aLastIndex, aLength - aLastIndex);if (path.ToUpper() == selectCoreldrawRunPath.ToUpper()){waitProcess = a;}});if (waitProcess != null){tipLabel.Text = "保存CorelDRW" + verson + "文件,退出CorelDRW再重启……";tipLabel.ForeColor = Color.OrangeRed;
button.Enabled = false;Timer timer = new Timer();timer.Enabled = true;timer.Interval = 1000;timer.Tick += (to, te) =>{processList = System.Diagnostics.Process.GetProcesses().ToList();processList = processList.FindAll(p => p.ProcessName.Contains("CorelDRW"));waitProcess = processList.Find(p => p.MainModule.FileName.Contains(verson)); if (waitProcess == null){timer.Dispose();
button.Enabled = true;try{CopyOrDeleteFiles(coreldrawPath, addonDllTitle, copyFilesPath, setupOrUnload);tipLabel.Text = "已经配置好!可以关闭或继续配置。";tipLabel.ForeColor = Color.Green;}catch{tipLabel.BackColor = Color.Red;tipLabel.Text = "发生配置问题,请反馈……";}}};timer.Start();}else{try{CopyOrDeleteFiles(coreldrawPath, addonDllTitle, copyFilesPath, setupOrUnload);tipLabel.Text = "已经配置好!可以关闭或继续配置。";tipLabel.ForeColor = Color.Green;}catch{tipLabel.BackColor = Color.Red;tipLabel.Text = "发生配置问题,请反馈……";}}};
这段代码的作用就是判断是否有与已选择版本的CorelDraw在运行,如果有就要等待手动退出相应版本的CorelDraw,才能把插件文件拷到CorelDraw的Addon目录下。因为CorelDraw启动时先读取配置文件,然后调用DLL生成用户界面,DLL里面一般会有事件方法,在CorelDraw退出前都不会中断对DLL的控制,所以你无法拷贝或删除和原来同名的DLL。即使是首次安装,可以拷贝进去,但也需要CorelDraw重启动,CorelDraw才会重新加载新增的Addon插件,所以干脆就都等退出后再拷贝或删除。代码还在等待CorelDraw退出时加了个监控事件,监控是否已经退出。如果已经退出,则进行拷贝或删除文件的操作;如果无需退出,直接进行拷贝或删除文件的操作。
tipLabel.Refresh():刷新一下显示,要不由于程序的运行过快,显示不出来。
GetCurrentDirectory:获取当前程序集所在目录,即Setup.exe所在目录。
radioButtonList.Find(a => a.Checked == true):在选择版本单选按钮系列中查找Checked属性值等于true的单选按钮,有且只有一个;a指代的是radioButtonList元素中的可能的1个,当然你也可以用其它符号或单词。
IndexOf:获取目标对象在对象集合中的位置。
System.Diagnostics.Process:Process是当前电脑的进程,一般情况下打开一次应用软件,就会新开1个进程。在电脑上按Ctrl+Alt+Del组合键然后选择任务管理器,就可以观察到进程的实时状况。测试了一下, CorelDraw属于这种情况;CorelDraw也允许同一版本的自身可以启动多次,每启动1次,就新开1个进程;问题还是比较复杂。任务管理器的信息比如下图:
GetProcesses():获取电脑上所有的进程。
FindAll:和Find相比,Find是在集合中找到匹配的第1个,而FindAll则是找出所有匹配的。
p.ProcessName.Contains("CorelDRW"); ProcessName,进程的名称,也就是执行文件的名称,不带目录也不带后缀的。如“CorelDRW”就是CorelDraw进程的名称。多个进程可以有同1个进程名称。Contains包括,只要字串中含有"CorelDRW",就是所要查找的目标进程。
selectCoreldrawRunPath.Length:选择的CorelDRW版本程序的目录字符串的长度,1个英文字母和1个中文字长度都是1。Length也可以是对象数组的对象个数,比如代码块开头我们就定义了1个coreldrawVersonArr对象数组,你可以用coreldrawVersonArr.Length获得里面对象的个数。对象列的和集合的长度定义是Count,比如processList.Count;比如CorelDraw的Shapes就代表图形的集合,它的数量就可以Shapes.Count来获取。
selectCoreldrawRunPath.Remove(lenth - 1, 1):目的是把目录字串中的最后一个“\”字符去掉,因为目录操作中,允许最后1个目录名有或者没有反斜杠。字符串Remove方法,去掉你所指定的范围。字符串截取字符串还有另外1个函数Substring,是从原string中找出子string,并返回子string。如果用Replace方法,用新的字串替换掉查找到的子string,相同的全部替换掉。用这3个方法时,要注意是否要重新赋值给原string或新string,不然经常会得不到期望的结果。
System.Diagnostics.Process waitProcess = null:定义1个Process型的变量selectProcess,先赋值为null,因为很多情况下调试会检查对象是否实例化,如果不实例化就会认为有错误;if语块中的对象实例化在实际情况下不一定会被执行到。null空空如也的意思,万事皆空,大体上能有new的对象都可以赋值为null,可以理解为定义个只有外壳的对象;不可以有new的对象,比如int、double等就不能赋值null,可以先赋值0;string则是string.Empty或””。
processList.ForEach:遍历processList对象列中的每1个对象,这里语块中用了”a =>”,MSDN叫Lambda表达式,用于简化代码。你也可以用foreach (System.Diagnostics.Process process in processList){ }来代替。注意ForEach和foreach的大小写区别,C#对大小写是严格的,还有foreach语句块的大括号后面没有英文冒号。
Timer timer = new Timer():初始化一个Timer类控件,用来监控对应的CorelDraw是否退出完成,Timer的运行是开新的线程。那么现在就有了2个线程,主线程是Form1,新线程是timer。这里必须开新线程,要不在主线程中用do语句块会造成窗体堵塞假死,无法访问上面的控件包括关闭窗体。
button.Enabled = false:锁定button按钮,在timer监控期间不让重复做卸载或安装的事情。监控完成后再开锁。
timer.Enabled:设定timer是否有效。
timer.Interval:设置timer多少事件进行1次扫描监控。
timer.Tick += (to, te) =>{}:timer监控时要处理的事情。
timer.Start():开始监控任务。
timer.Dispose():销毁timer,包括在程序中占用的资源。窗体控件基本上都有Dispose这个方法。
try{}catch{}:必须分行写,是对代码的执行中发生的错误进行拦截和对错误的发生进行处理。如果语块中有错误,不进行拦截的话,会额外弹出错误信息,影响体验。在循环中经常会有一些对象集合种的对象用同样的方法去访问,有个别会发生错误,而这个对象可能不是我们所需要的,我们就可以用try{}catch{}去跳过它或进行一些处理。try{}catch{}必须成对贴身出现,还可以在后面加加finally{},进行进一步的处理。
CopyOrDeleteFiles(coreldrawPath, addonDllTitle, copyFilesPath, setupOrUnload):这里是传参调用外面的函数进行文件夹和文件夹下面的文件进行增删操作。
复制和删除文件夹的被调用函数
所以我们在Form1_Load外面插入已经写好的供调用的函数(函数先后次序一般没有规定):
private void CopyOrDeleteFiles(string coreldrawPath, string addonDllTitle, string copyFilesPath, bool setupOrUnload){string coreldrawAddonPath = coreldrawPath + "\\Addons\\";string meAddonPath = coreldrawAddonPath + addonDllTitle;if (setupOrUnload){richTextBox1.Text = meAddonPath;if (Directory.Exists(meAddonPath)){Directory.GetFiles(meAddonPath).ToList().ForEach(File.Delete);}else{Directory.CreateDirectory(meAddonPath);}Directory.GetFiles(copyFilesPath).ToList().ForEach(a =>{string dir = meAddonPath + "\\" + a.Split('\\').Last();File.Copy(a, dir, true);});}else{if (Directory.Exists(meAddonPath)){Directory.GetFiles(meAddonPath).ToList().ForEach(File.Delete);Directory.Delete(meAddonPath);}}}
private void CopyOrDeleteFiles:定义了一个类内函数,私有的,外部类不能访问,private也可以不写;void是不需要向调用函数返回数据,CopyOrDeleteFiles函数名。自定义函数都必须采用这样的结构。
(string coreldrawPath, string addonDllTitle, string copyFilesPath, bool setupOrUnload):规定了调用函数必须提供的参数类型,并且必须提供参数的值。每个参数用英文逗号隔开,末尾没有逗号。如果不需要参数传递括号内就留空,但括号也必须有。定义了这个被调用函数后,调用函数就要准备相应的类型的具体的数据提供给被调用函数,并且要一一对应。当然你也可以不用另外写被调用函数,有时为了将过长的代码分块写或其它函数也要调用就有必要另写函数块。调用函数传参时不能写参数类型,只提供具体的参数值就可以了。这个供调用函数的功能是:如果是安装或重新安装,就判断自己的Addon目录是否存在,不存在就先删除目录下的文件,如果目录不存在就新建目录,然后再把文件考进去;如果是卸载,如果有安装过,就把文件和目录清空。由于C#中不能像在电脑上手动删除文件夹时就把里面的文件全部删除了,所以要先删除文件,再删除目录。建立目录和文件同理。并且这里要求所有文件都在同一个文件夹内,如果需要在文件夹下再新建文件夹的,还要重写CopyOrDeleteFiles函数。
coreldrawPath:相应版本的插件所在目录,代码中已经赋值。
addonDllTitle:插件DLL的标题,不包括”.dll“,必须指定。
copyFilesPath:这里的处理是:在运行前在setup.exe同一个目录下,新建一个目录。如果对应2021版,就要在setup.exe建一个2021的目录;如果2020版,就要建一个2020的目录;以此类推,并把用到的文件再拷进去。建立完成后就由代码自己去获取。比如下图:
setupOrUnload:安装或卸载,安装就是true;卸载就是false。运行时赋值。
窗体运行
运行效果如下:
关闭CorelDRAW 2021后,运行效果如下图:
结语
这次课件实现和示例Addon插件的安装,介绍了窗体元素的构建方法,分析了C#语法、代码含义和用法。课件写的可以说很详细,对每个步骤和代码都有详细的分析,主要是面对刚入门的C#同行一起探讨。
经过代码编写和测试,认为.Net和.Net Framework除了引用框架外,使用方法基本相似,从.Net Framework转向.Net是完全可能的。新手可以直接入手.Net。生成目录内容不同,应用程序既生成了.exe,也生成了.dll,也许有相应的应用场景。
软件的升级都带来一些创新,一般也都兼容性更强,用新的版本开发项目也许会更便捷高效。
本文标签: 安装Addon插件
版权声明:本文标题:安装Addon插件 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/IT/1694650798a254529.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论