zoukankan      html  css  js  c++  java
  • cad.net 委托的学习

    定义

    首先要说的是:需求决定了学习,学习又衍生出新的需求.

    委托可以看成函数指针! (没有学过c语言的忽略这句话...)

    它几乎等价于一个回调函数.

    某些事情只能它来干,你不能,但是你想让他帮你干....

    (生活中的例子就是女朋友在公厕忘了带纸,你买来了之后,又基于道德的底线不能进去,只能委托其他女孩子带进去......你们怎么会有女朋友呢?还是用夹心饼干做例子吧....)
    img

    委托视频可参考

    不要认为它是一个不怎么需要掌握的技能,委托还有两大特点:
    委托本身有一条委托链,它是构成"事件"的方式,用+= -=实现.
    委托在.net core上还是"中间件"的构成要素.
    至于这两个地方这里就不详细说了,本次教程只针对cad数据库上用委托和lambda表达式.
    若你想知道函数指针在c#怎么调用,那就看vla-idata-get

    一个简单的快速说明

    //首先委托可以返回一个以上的变量出来,其次不用委托的话,它会是:
    var a = Fun1(out b);
    int c = Fun2(a);
    if(c == 0) 
        Fun3(b);
    //要写三个函数处理.
    
    //而委托压缩了它们:
    //先看调用:
    var e = FunK(a,b=>//从FunK获取a,b两个变量
    { 
        //处理a和b.
        return 0; //返回需要的值
    }/*FunK内部再处理c*/);
    
    //再看定义:
    int FunK(Func<int,int,int> fn)
    {
      int c = fn(11,12);//传出去两个参数
      //这里是FunK内部,处理参数c
      if(c == 0)
      {
         //其他处理
      }
      return 1234;//就是e
    }
    
    //单步调试可知,执行先流入FunK内,然后流出匿名函数,再回流到到FunK内.
    //形成了一种夹芯饼干式的编程方法,饼干的两端是固定的,把芯的内容抛出去给调用的人,让调用者给芯加料(巧克力,还是草莓酱)
    
    //为什么这么做呢?我认为最重要的是写sdk的,和调用者不是同一个人,
    //但是写sdk的人想调用者写的函数插在这个函数的中间.例如微软提供linq
    
    

    WinForm例子

    在窗体面板的多线程操作的时候,你就会遇到基于面板控制的委托...

    面板程序创建时候必然会创建一个主线程,这个主线程来维护所有的UI,

    你新建的线程只是辅助线程,用来计算数值等等信息,然后你要让主线程来处理,而不是其他线程直接处理,否则会引发非线程安全操作.

    这个时候就是委托上场了:

    一个简单的多线程UI demo:

    using System;
    using System.Threading;
    using System.Windows.Forms;
    
    namespace WindowsFormsApp1
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            //这样会点击一次按钮新建一次线程,所以高版本net才有携程这回事,Task不new就是携程
            private void Button1_Click(object sender, EventArgs e)
            {
                int k = 0;
                var th = new Thread(() =>
                {
                    while (true)
                    {
                        //此时线程不安全
                        k++;
    #if false
                        //此时调用委托达到线程安全
                        this.BeginInvoke(new Action(() =>
                        {
                            textBox1.Text = k.ToString();
                        }));
    #else
                        //直接关闭
                        //使用Invoke的时候会阻塞等待并等待主线程运行,然后关闭了界面产生这个错误
                        //System.ObjectDisposedException:“无法访问已释放的对象。
                        try
                        {
                            this.Invoke(new Action(() =>
                            {
                                textBox1.Text = k.ToString();
                            }));
                        }
                        catch (Exception)
                        {}
    #endif 
                        //当没有Sleep(1)的时候,代表此线程频繁委托主线程,
                        //以至于达到完全占用的情况,在同一时间片内无法调出.
                        Thread.Sleep(1);
                    }
                });
    
                //由于主程序窗体关闭时候此线程扔在执行,所以要设置到背景线程去
                th.IsBackground = true;
                th.Start();
            }
        }
    }
    

    若你还知道多线程的背后原理,看看这个视频

    CAD例子:无返回值写法

    "数据库给我一个事务,我要让它来操作其他东西..用完它之后如果没有命令你不许提交,你就给我提交去"..实现了一种默认控制...

    形象的理解一下: 相当于委托了一个狗腿子,让它帮你跑腿,然后它还会跑腿了之后自己还会回去它的狗窝...

            /// <summary>
            /// 事务处理器的封装,无返回值
            /// </summary>
            /// <param name="db">数据库对象</param>
            /// <param name="act">delegate函数</param>
            /// <param name="commit">是否提交,默认提交</param>
            public static void Action(this Database db, Action<Transaction> act, bool commit = true)
            {
                try
                {
                    using (var tr = db.TransactionManager.StartTransaction())
                    {
                        act.Invoke(tr);//事务就被传出去了
                        if (commit)
                        {
                            tr.Commit();
                        }
                        if (!tr.IsDisposed)
                        {
                            tr.Dispose();
                        }
                    }
                }
                catch (System.Exception e)
                {
                    Acap.ShowAlertDialog(e.Message);
                }
            }
    

    调用方法:

                //通过数据库,调用委托(狗腿子跑),(狗腿子捡回东西)拿到传出来的事务,然后加入图元到数据库..(跑会狗窝)再回到委托内,进行提交.
                Database db = Acap.DocumentManager.MdiActiveDocument.Database;            
                db.Action(tr =>
                {                var line = new Line(Point3d.Origin, new Point3d(1,1,0));
                    var acBlkTblRec = tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord;              
                    acBlkTblRec.AppendEntity(line);
                    tr.AddNewlyCreatedDBObject(line, true);
                });
    

    CAD例子:有返回值写法

            /// <summary>
            /// 事务处理器的封装,有返回值
            /// </summary>
            /// <typeparam name="T">泛型</typeparam>
            /// <param name="db">数据库对象</param>
            /// <param name="func">delegate函数</param>
            /// <param name="commit">是否提交,默认提交</param>
            /// <returns>泛型</returns>
            public static T Func<T>(this Database db, Func<Transaction, T> func, bool commit = true)
            {
                T rtn = default;//泛型返回
                try
                {
                    using (var tr = db.TransactionManager.StartTransaction())
                    {
                        rtn = func.Invoke(tr);//接收了返回值
                        if (commit)
                        {
                            tr.Commit();
                        }
                    }
                }
                catch (System.Exception e)
                {
                    Acap.ShowAlertDialog(e.Message);
                }
                return rtn; //返回接收到的
            }
    

    调用方法:

                var retLine = db.Func(tr =>
                {
                    var line = new Line(Point3d.Origin, new Point3d(1, 1, 0));
                    var acBlkTblRec = tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord;
                    acBlkTblRec.AppendEntity(line);
                    tr.AddNewlyCreatedDBObject(line, true);
                    return line;
                });
                //接着对返回的东西做其他处理.....
                var ents = new List<Entity>() { retLine };
    

    委托的需求诞生:

    你写了很多foreach (例如一百个功能你就写了一百次) 来遍历同一个集合(例如块表),

    然后要判断这个集合的 n位 是不是 Null ,因为 Null 获取属性就崩溃了....

    然后突然有一天你发现如果集合上的 n位 不为 Null ,但是 n.Length==0,也没发干活....

    你就发闹骚....难道我要改那一百个foreach吗? vs的替换? 要是接下来还有个新想法,那岂不是不能一劳永逸吗??

    岂不是每次都修改了一个标准代码,然后又去替换100个位置的代码?

    要是这个时候 只用修改了一个标准代码就可以完事了,不需要替换,就很爽了呢?

    这个时候,一劳永逸的委托就可以帮助你......

    需求1: 打开块表

    把上面遇到的问题转化成我的需求:

    1,打开块表;

    2,遍历块表;

    3,判断块表的id是否可用;

    4,打开id转为块表记录;

    5:再让给外部修改(通过委托扔给外面修改).

    6:接下来判断委托的返回值,看是不是要结束内部的循环

    A形写法:

        //委托 块表
        public delegate bool DelegateBlockTableRecord(BlockTableRecord record); 
    
        /// <summary>
        /// 遍历块表
        /// </summary>
        /// <param name="table"></param>
        /// <param name="tr">事务</param>
        /// <param name="de">委托</param> 
        public static void Traverse(this BlockTable table, Transaction tr, DelegateBlockTableRecord de)
        {
            foreach (var taid in table)
            {
                if (taid.IsOk())//如果你的新想法在这里..要进行判断 taid各种情况的话...需求无限增.....
                {
                    var rec = tr.GetObject(taid, OpenMode.ForRead) as BlockTableRecord;
                    if (rec != null && de(rec))//传rec出去给其他函数.
                    {
                        break;
                    }
                }
            }
        }
    

    现在因为不光有块表,还有层表,文字样式表,标注符号表....那么要写很多行这个委托句(还要不同名称的),就显得很笨了.....

    delegate bool DelegateBlockTableRecord(BlockTableRecord record); 
    

    而微软给我们提供了简化:

    Func<BlockTableRecord, bool>
    

    Func是有返回值的委托 ,意思是Func<传参1,返回值>,用法见下面代码

    注明:Action<传参1>这是无返回值的,而不能写成Func<传参1,Void>,或者Func<传参1,null>

    B型写法:

            /// <summary>
            /// 遍历块表
            /// </summary>
            /// <param name="table"></param>
            /// <param name="tr">事务</param>
            /// <param name="de">委托</param> 
            public static void Traverse(this BlockTable table, Transaction tr, Func<BlockTableRecord, bool> de)
            {
                foreach (var taid in table)
                {
                    if (taid.IsOk())
                    {
                        var rec = tr.GetObject(taid, OpenMode.ForRead) as BlockTableRecord;
                        if (rec != null && de(rec))//这里传块表记录出去给调用的函数!
                        {
                            break;
                        }
                    }
                }
            }
    

    A型和B型写法的调用方法:

             _block = _Transaction.GetObject(Database.BlockTableId, OpenMode.ForRead) as BlockTable;//块表
             _block.Traverse(_Transaction, btRec => //这里的btRec就是委托传出来的表记录!!!!
             {
                 if (btRec.Name == "块的名字")
                 {                 ..........其他操作
                     return true; //找到就中断内部的循环,这就是为什么需要 bool返回值的原因
                 }
                 return false;
             });
    

    需求增加: 全部符号表<模板类>

    然而,我觉得这样要每次打开符号表这句也挺烦的..因此我做了个模板类,并且实现了多个符号表的静态方法:

        public class Traverse<TTable, TTableRecord>
            where TTable : SymbolTable
            where TTableRecord : SymbolTableRecord
        {
            /// <summary>
            /// 遍历表记录
            /// </summary>
            /// <param name="tableId">符号表id</param>
            /// <param name="tr">事务</param>
            /// <param name="de">委托</param> 
            public Traverse(Transaction tr, ObjectId tableId, Func<TTableRecord, bool> de)
            {
                var bt = tr.GetObject(tableId, OpenMode.ForRead) as TTable;
                if (bt is LayerTable table)
                {
                    table = table.IncludingHidden;//层表包含隐藏的,全部显示出来
                    if (table is TTable tt)
                    {
                        bt = tt;
                    }
                }
                foreach (var taid in bt)
                {
                    if (taid.IsOk())
                    {
                        var rec = tr.GetObject(taid, OpenMode.ForRead) as TTableRecord;
                        if (rec != null && de(rec))
                        {
                            break;
                        }
                    }
                }
                bt.Dispose();
            }
        }
    
        //静态调用
        public static class TableRecord
        {
            //块表
            public static void TraverseBlockTable(this Database db, Transaction tr, Func<BlockTableRecord, bool> de)
            {
                new Traverse<BlockTable, BlockTableRecord>(tr, db.BlockTableId, de);
            }
    
            //层表
            public static void TraverseLayerTable(this Database db, Transaction tr, Func<LayerTableRecord, bool> de)
            {
                new Traverse<LayerTable, LayerTableRecord>(tr, db.LayerTableId, de);
            }
    
            //线型表
            public static void TraverseLinetypeTable(this Database db, Transaction tr, Func<LinetypeTableRecord, bool> de)
            {
                new Traverse<LinetypeTable, LinetypeTableRecord>(tr, db.LinetypeTableId, de);
            }
    
            //文字样式
            public static void TraverseTextStyleTable(this Database db, Transaction tr, Func<TextStyleTableRecord, bool> de)
            {
                new Traverse<TextStyleTable, TextStyleTableRecord>(tr, db.TextStyleTableId, de);
            }
    
            //标注样式
            public static void TraverseDimStyleTable(this Database db, Transaction tr, Func<DimStyleTableRecord, bool> de)
            {
                new Traverse<DimStyleTable, DimStyleTableRecord>(tr, db.DimStyleTableId, de);
            }
    
            //视口样式
            public static void TraverseViewportTable(this Database db, Transaction tr, Func<ViewportTableRecord, bool> de)
            {
                new Traverse<ViewportTable, ViewportTableRecord>(tr, db.ViewportTableId, de);
            }
    
            //视图样式
            public static void TraverseViewTable(this Database db, Transaction tr, Func<ViewTableRecord, bool> de)
            {
                new Traverse<ViewTable, ViewTableRecord>(tr, db.ViewTableId, de);
            }
    
            //坐标系表
            public static void TraverseUcsTable(this Database db, Transaction tr, Func<UcsTableRecord, bool> de)
            {
                new Traverse<UcsTable, UcsTableRecord>(tr, db.UcsTableId, de);
            }
    
            //注册应用表
            public static void TraverseRegAppTable(this Database db, Transaction tr, Func<RegAppTableRecord, bool> de)
            {
                new Traverse<RegAppTable, RegAppTableRecord>(tr, db.RegAppTableId, de);
            }
        }
    

    调用方法:

        db.TraverseBlockTable(tr, btr =>
        {
            if (btr.Name == "块名称")
            {            
                //.........其他操作                       
                return true;//找到就中断内部的循环,这就是为什么需要 bool返回值的原因
            }
            return false;
        });
    

    疑问和原理:

    你可能会提问,为什么要写奇奇怪怪的委托?

    因为这是一种用旧代码来减少新代码的方式.

    例如你可能已经厌烦了每次开启事务,再每次提交事务.也都可以利用这种方法来进行优化你的代码.

    如图所示制作委托函数时候,尽可能制作成夹芯饼干(或者循环内部),不然没有后面的处理的话,那倒不如去做一个简单的函数.

    img

    如果到这里你还没理解这样可以令遍历简单化的话,那么下面我的函数是实际上工程会用到的.

    继续看懂这个需求吧.

    需求2: 批量输入路径提取数据库

    你写了一个功能来让用户选择项目文件夹,遍历出一堆dwg,这一堆dwg有的是用户已经在cad打开的,有的是没有打开的.

    用户已经打开的,必须用前台来进行,而没有打开的就用后台来进行...否则cad会致命错误.(它就是那么不讲武德)

    那么前台和后台打开之后处理的代码是一样的,也就是委托的处理代码 =>{ 一样的处理代码 };

    而委托之前,前台要进行锁文档(后台不用),委托之后,前台要释放锁.这就涉及了封装时候的控制.

    但是封装之后,你只需要调用这个函数,然后给予一个路径,让它返回一个数据库,不需要再进行判断是否要锁和不锁.其后要释放还是不释放,如何保存等等

        public static class OpendDwg
        {
            /// <summary>
            /// 通过路径打开图纸(前后台均可)
            /// </summary>
            /// <param name="dwgpath">dwg文件</param>
            /// <param name="change">打开后的处理</param>
            /// <param name="isDispose">是否打开后就立即关闭</param>
            /// <param name="save">是否保存</param>
            public static void Read(string dwgpath, Action<Database> change,
                bool isDispose = true, bool save = false)
            {
                //先遍历一遍当前文档集合,避免打开出错
                var dm = Acap.DocumentManager;
                Editor ed = dm.MdiActiveDocument.Editor;
    
                Database sdb = null;
                Document doc = null;
                DocumentLock doclock = null;
                foreach (Document item in dm)
                {
                    if (item.Database.Filename == dwgpath)
                    {
                        doc = item;
                        sdb = doc.Database;
                        doclock = doc.LockDocument();//锁文档,操作非活动文档的时候必须
                        break;
                    }
                }
                if (sdb == null)
                {
                    //后台打开图纸
                    sdb = new Database(false, true);
                    sdb.ReadDwgFile(dwgpath, FileShare.ReadWrite, true, null);
                }
                try
                {
                    change(sdb);
                    if (save)
                    {
                        if (doc != null)
                        {
                            doc.SendStringToExecute("_qsave
    _regen
    ", false, true, true); //不需要切换文档 
                        }
                        else
                        {
                            //后台的数据库可以用这种方式保存
                            //这里错误会弹对话框,所以只能判断是不是DocumentManager的  
                            //如果是06版本运行的话,就不可以保存成07了,所以还是要这样写
                            bool flagSaveAs = true;
                            foreach (DwgVersion suit in Enum.GetValues(typeof(DwgVersion)))
                            {
                                if ((int)suit == 27)//07版的版本号
                                {
                                    flagSaveAs = false;
                                    sdb.SaveAs(sdb.Filename, (DwgVersion)27);
                                    break;
                                }
                            }
                            if (flagSaveAs)
                            {
                                sdb.SaveAs(sdb.Filename, DwgVersion.Max);
                            }
                        }
                    }
                }
                catch (System.Exception e)
                {
                    string str;
                    switch (e.Message)
                    {
                        case "eFileSharingViolation":
                            str = Environment.NewLine + "他人在打开此图!" + dwgpath +
                                  Environment.NewLine + "请他人关闭再操作!!";
                            break;
                        case "eWrongObjectType":
                            str = Environment.NewLine + "错误的对象类型";
                            break;
                        default:
                            str = Environment.NewLine + "此文件发生错误:  " + dwgpath +
                                  Environment.NewLine + "错误代码:  " + e.Message;
                            break;
                    }
                    ed.WriteMessage(str);
                }
                finally
                {
                    if (isDispose && !sdb.IsDisposed)
                    {
                        try
                        {
                            sdb.Dispose();//是不是每次都要关闭?
                        }
                        catch
                        { }
                    }
                    if (doclock != null)
                    {
                        try
                        {
                            doclock.Dispose();
                        }
                        catch
                        { }
                    }
                }
            }
        }
    

    调用方法:

                //遍历所有dwg,获取图签信息
                foreach (var path in dwgsPathArr)
                {
                    //这里可以自动处理前后台了
                    OpendDwg.Read(path, sdb =>
                    {
                        BackstageDatabase.Add(sdb); //.......获取图签的操作函数           
                    }, false);//不释放,如果选择了不释放,代表后台持续占用着图纸.
                }
    

    (完)

  • 相关阅读:
    08-Linux命令【rm】
    07-Linux命令【mv】
    06-Linux命令【cp】
    05-Linux命令【rmdir】
    04-Linux命令【mkdir】
    03-Linux命令【ls】
    02-Linux命令【cd】
    01-Linux命令【pwd】
    智慧城市3D园区
    自我觉醒
  • 原文地址:https://www.cnblogs.com/JJBox/p/12196287.html
Copyright © 2011-2022 走看看