C#调用matlab,matlab图形Figure嵌入Winform窗体,并完美解决只能捕捉嵌入一次的问题

本文禁止转载,需提前获得允许, 侵权必究

(本文实现的前提,你要安装好matlab。本文用的是vs2015+matlab2017b)

1.首先第一步建立一个工程文件

2.添加Matlab Application(Version 9.3)Type libaray

在References这里右键点击——Add References,然后会弹出如图所示的对话框,按照图片所示的 顺序操作,(1) 选择COM(2)输入matlab进行搜索(3) 选择(注意这里要和你的matlab版本对应,我是matlab2017b,对应的是9.3)

C#调用matlab,matlab图形Figure嵌入Winform窗体,并完美解决只能捕捉嵌入一次的问题_第1张图片

添加引用后,引用(References)目录下多了一个MLApp文件,如果在编程是采用MLAppClass的话还需要将该文件的属性Embed Interop Types的True值改为False,不然会报错。

如果采用MLApp.MLApp则不会报错。

C#调用matlab,matlab图形Figure嵌入Winform窗体,并完美解决只能捕捉嵌入一次的问题_第2张图片

3.添加Properties

网上有很多调用matlab的方法,比如生成DLL文件,然后引用,在我这里老是 出现“类型初始值设定项引发异常”的错误,而且安装网上的方法也改不好,因此摸索出了另外一个方法。当然这个方法的前提是:你的电脑要事先安装好MATLAB。

第一步,就是在你的项目下面新建一个文本文件,改名为一个你想的名字,如“ttt.txt”,然后把你需要的matlab代码复制进去,如图:

C#调用matlab,matlab图形Figure嵌入Winform窗体,并完美解决只能捕捉嵌入一次的问题_第3张图片

第二步,在你的工程名那里,右键选择“Properties”,然后选择Resources——Add Existing File,弹出的对话框之后,选择刚刚你添加的txt文件。

C#调用matlab,matlab图形Figure嵌入Winform窗体,并完美解决只能捕捉嵌入一次的问题_第4张图片

这样,在你的资源文件夹下面会多出来一个ttt.txt的文件

C#调用matlab,matlab图形Figure嵌入Winform窗体,并完美解决只能捕捉嵌入一次的问题_第5张图片

这样做的目的是:在我们需要调用matlab代码的时候,我们用这个资源文件去生成一个.m文件,然后再执行这个.m文件,结束了再删除.m文件。为什么要这样做?因为如果直接在工程下面粘贴一个.m文件,使用VS自带的打包工具打包发布的时候, 这个文件是不会一起打包的,就造成了发布后调用不成功。当然,你可以使用其他的打包的工具。比如【Inno setup】。

好了, 本文的方法,首先要生成一个.m文件,很简单了,直接贴代码:

FileStream fs = new FileStream("ttt.m", FileMode.OpenOrCreate, FileAccess.ReadWrite); 
StreamWriter sw = new StreamWriter(fs); // 创建写入流
sw.WriteLine(Properties.Resources.ttt); // 写入Hello World
sw.Close(); //关闭文件
fs = null;

4.这里是关键的一步,调用matlab,

MLApp.MLApp matlab = null;
Type matlabAppType = System.Type.GetTypeFromProgID("Matlab.Application");
matlab = System.Activator.CreateInstance(matlabAppType) as MLApp.MLApp;
string path_project = Directory.GetCurrentDirectory();   //工程文件的路径,如bin下面的debug
string path_matlab = "cd('" + path_project + "')";    //自定义matlab工作路径
matlab.Execute(path_matlab);
matlab.Execute("clear all");
matlab.Execute("close all");
string command;
command = @"ttt(10,4)";
matlab.Execute(command);

这里面比较关键的几句:第4、5句,目的是让matlab的工作路径在当前路径,不然可能你这次调用和下次调用的路径不同,会导致错误。clear all是为了清除工作区,不然可能上次的结果会对下次造成影响。

5.这里的是画了一个图,第5步是将弹出的Figure对话框嵌入到窗体内,也很关键

首先在窗体内定义使用的windows API

#region //Windows API
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);//
[DllImport("user32.dll")]
public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int MoveWindow(IntPtr hWnd, int x, int y, int nWidth, int nHeight, bool BRePaint);
const int GWL_STYLE = -16;
const int WS_CAPTION = 0x00C00000;
const int WS_THICKFRAME = 0x00040000;
const int WS_SYSMENU = 0X00080000;
[DllImport("user32")]
private static extern int GetWindowLong(System.IntPtr hwnd, int nIndex);
[DllImport("user32")]
private static extern int SetWindowLong(System.IntPtr hwnd, int index, int newLong);

下一步,定义全局变量,在触发事件内实例化线程,我这里因为是点击按钮,所以后面的实例化写在了private void button1_Click(object sender, EventArgs e)里面

//定义在窗体内
public delegate void UpdateUI();//委托用于更新UI
Thread startload;//线程用于matlab窗体处理
IntPtr figure1;//图像句柄


//写在触发事件内,如Form_Load,这里我写在了Button_Click内 
//实例化线程,用来初次调用matlab,并把图像窗体放到winform
startload = new Thread(new ThreadStart(startload_run));
//运行线程方法
startload.Start();

线程里面的startload_run方法:

void startload_run()
        {
            
            int count50ms = 0;
            //实例化matlab对象


            //循环查找figure1窗体
            while (figure1 == IntPtr.Zero)
            {
                //查找matlab的Figure 1窗体
                figure1 = FindWindow("SunAwtFrame", "Figure 1");
                //延时50ms
                Thread.Sleep(50);
                count50ms++;
                //20s超时设置
                if (count50ms >= 400)
                {
                    label1.Text = "matlab资源加载时间过长!";
                    return;
                }
            }
            //跨线程,用委托方式执行
            UpdateUI update = delegate
            {
                //隐藏标签
                label1.Visible = false;
                //设置matlab图像窗体的父窗体为panel
                SetParent(figure1, panel1.Handle);
                //获取窗体原来的风格
                var style = GetWindowLong(figure1, GWL_STYLE);
                //设置新风格,去掉标题,不能通过边框改变尺寸
                SetWindowLong(figure1, GWL_STYLE, style & ~WS_CAPTION & ~WS_THICKFRAME);
                //移动到panel里合适的位置并重绘
                MoveWindow(figure1, 0, 0, 400 + 0, 300 + 0, true);
 

            };
            panel1.Invoke(update);
            //再移动一次,防止显示错误
            Thread.Sleep(100);
            MoveWindow(figure1, 0, 0, 400 + 0, 300 + 0, true);
        }

这里比较关键的是,MoveWindow(figure1, 0, 0, 400 + 0, 300 + 0, true)这一句,里面的参数,第四、第五个代表的是在panel里面的位置,很多的博客里面写的是MoveWindow(figure1, 0, 0, panel1.Width + 20, panel1.Height + 40, true),这样比较坑的是,万一你的图片比较大,而panel比较小,会使得图片显示不全,所以,要改成你图片实际的大小就好了。

6.解决只能捕捉一次窗体的问题

上述的方法,只能实现在第一次弹出Figure的时候能够捕捉嵌入,当再次运行的时候就会弹出来。我摸索了很久,大概3个小时候发现了解决方案,很简单。在你触发线程实例化之前加上这句话:

figure1 = IntPtr.Zero;

 

这样就可以把之前的figure1给释放了, 

就能再次捕捉了。哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈,开森。

7.执行完删除.m文件

定义删除的函数:

        public void DeleteFile(string path)
        {
            try
            {
                FileAttributes attr = File.GetAttributes(path);
                if (attr == FileAttributes.Directory)
                {
                    Directory.Delete(path, true);
                }
                else
                {
                    File.Delete(path);
                }
            }
            catch
            {
                FileNotFoundException ex;
            }
        }

然后在结束的时候,比如Form_Closing的方法里面写上:DeleteFile("ttt.m");即可

全部项目代码下载链接:https://download.csdn.net/download/voidfaceless/10831642

好了,全文结束。贴上参考文献,感谢以下的博客及作者:

【1】https://blog.csdn.net/yxy244/article/details/79305757#commentBox

【2】https://blog.csdn.net/zhupumao/article/details/51996113

【3】http://www.ilovematlab.cn/thread-234097-1-1.html

 

本文禁止转载,需提前获得允许, 侵权必究

你可能感兴趣的:(C#,混合编程)