cad.net 委托的學習


定義

首先要說的是:需求決定了學習,學習又衍生出新的需求.

委托可以看成函數指針! (沒有學過c語言的忽略這句話...)
它幾乎等價於一個回調函數.

但是它的全名應該叫有泛型傳參的函數指針數組,它確實是個動態數組,因為它有委托鏈.
委托本身有一條委托鏈,它是構成"事件"的方式,用+= -=實現.

委托在.net core上還是"中間件"的構成要素.
至於這地方這里就不詳細說了,

本次教程只針對cad數據庫上用委托和lambda表達式.
若你想知道函數指針在c#怎么調用,那就看vla-idata-get

委托一詞的文字解釋:
某些事情只能它來干,你不能,但是你想讓他幫你干....
(生活中的例子就是女朋友在公廁忘了帶紙,你買來了之后,又基於道德的底線不能進去,只能委托其他女孩子帶進去......你們怎么會有女朋友呢?還是用夾心餅干做例子吧....)
img

委托視頻可參考

一個簡單的快速說明

//首先委托可以返回一個以上的變量出來,其次不用委托的話,它會是:
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 TraverseEntitys(this Database db, Transaction tr, Func<Entity, bool> de)
    {
        bool flag = false;
        db.TraverseBlockTable(tr, btRec =>
                              {
                                  foreach (var entId in btRec)
                                  {
                                      if (entId.IsOk())
                                      {
                                          var ent = entId.ToEntity(tr);
                                          if (ent == null)
                                          {
                                              continue;
                                          }
                                          ent.UpgradeOpen();
                                          if (de(ent))
                                          {
                                              ent.DowngradeOpen();
                                              ent.Dispose();
                                              flag = true;
                                              break;
                                          }
                                          ent.DowngradeOpen();
                                          ent.Dispose();
                                      }
                                  }
                                  return flag;
                              });
    }

    //層表
    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\n_regen\n", 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);//不釋放,如果選擇了不釋放,代表后台持續占用着圖紙.
}

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM