.NET平臺下帶權限控制的TreeView控件節點生成算法

一、引言

在應用系統開發中,TreeView是一種使用頻率很高的控件。它的主要特點是能夠比較清晰地實現分類、導航、瀏覽等功能。因而,它的使用方法與編程技巧也一直受到技術人員的關注。隨着應用需求的變化,在很多情況下我們需要實現數據顯示的權限控制,即用戶看到的數據是經過過濾的,或是連續值,或是一些離散的值。就TreeView而言,原先可能顯示出來的是完整的具有嚴格父子關係得節點集,而經權限過濾後所要顯示的節點可能會變得離散,不再有完整的繼承關係。本文針對這一問題,通過對已有實現方法進行分析,提出改進算法。所附示例程序進一步解釋了算法設計思想。

 

二、三種常見生成方式的簡單分析

如文[2,3]所述,TreeView的生成基本上有三種方式:

1.         界面設計時在TreeView設計器或者代碼中直接填充TreeView節點。
這種方式通過拖放控件的方式生成樹,應用範圍窄,是一種非編程方式;

2.         XML文件中建立樹形結構。
這種方式通過XML文件()生成樹,從形式上來說,這種方式是比較直觀的。因爲XML本身就是一棵“樹”,在.NET 平臺下TreeView的自動生成代碼中,TreeView的實際內容也是由XML表示的。此外,基於XML文件生成樹對異構環境下的分佈式應用具有重要意義。事實上,利用XML作爲通用數據傳遞格式已得到普遍認可;

3.         從數據庫中得到數據(在.NET中,我們可以理解爲一個數據集),建立樹形結構。
這種方式通過父子關係遞歸生成樹,是最容易理解的一種編程實現方式。一般是自頂向下遞歸生成,得到廣泛應用。

這裏,我們不妨舉一個實際的例子來說明一下,假設我們有這樣的數據集(可以看作是一個公司的部門列表):

 

TagValue

ContentValue

ParentID

G01

行銷部

 

G02

顧問部

 

G03

研發部

 

G04

測試部

 

GS01

行銷一部

G01

GS02

行銷二部

G01

GS03

行銷三部

G01

GSL01

行銷一部北京辦

GS01

GSL02

行銷一部上海辦

GS01

GS04

顧問一部

G02

GS05

顧問二部

G02

GS06

研發一部

G03

GS07

研發二部

G03

GS08

測試一部

G04

GS09

測試二部

G04

GSL03

研發一部杭州分部

GS06

GSL04

研發一部西安分部

GS06

1  示例數據集

其中,TagValue是節點的實際值,ContentValue是用戶界面上節點顯示的值或者說標籤值,ParentID是節點的父節點的TagValue。若節點爲根節點,一般設ParentID爲空或等於本身的TagValue

       默認情況下,我們可以按照下面的算法把所有的節點裝配成一棵樹,

算法1:通過父子關係遞歸生成樹基本算法

l         Step 0:數據準備,給定數據集。
一般來說數據集遵循這樣的格式,即(TagValue,ContentValue,ParentID);

l         Step 1:給定待增加子節點的節點(初始時一般爲根節點),記作CurNode,以及待增加節點的ParentID值(初始時爲根節點的ParentID),記作CurParentID;

l         Step 2:在數據集中查找具有指定ParentID值的所有節點,得到節點集objArr[]
if (objArr == null)
   return;
else
{
  //
遍歷節點集
  for(int i=0; i<objArr.Length;i++)
  {
    
objArr[i]添加爲CurNode的子節點,同時遞歸(即將objArr[i]作爲CurNodeobjArr[i]TagValue作爲CurParentIDgoto Step 1;
  }
}

 

最終可以得到下圖所示的TreeView



1 TreeView效果圖

這種方法的缺陷在於"由父節點及子節點"的遍歷順序意味着每個子節點的父節點必須存在,否則將搜索不到,即可能出現斷層現象。在很多實際應用中,我們發現這種實現方式不能完全奏效,最典型的情況就是當需要對衆節點所表徵的實際值(比如機構列表,人員列表,資源列表等)進行權限控制時,這時往往從數據庫中篩選出來的數據集中節點會出現斷層現象。比如我們假設設定權限時給定數據如表2,即把第一行“行銷部”去掉(注:權限過濾操作已超出本文討論的範圍,這裏假定數據集已準好),則運用算法1生成的TreeView如圖2所示。

TagValue

ContentValue

ParentID

G02

顧問部

 

G03

研發部

 

G04

測試部

 

GS01

行銷一部

G01

GS02

行銷二部

G01

GS03

行銷三部

G01

GSL01

行銷一部北京辦

GS01

GSL02

行銷一部上海辦

GS01

GS04

顧問一部

G02

GS05

顧問二部

G02

GS06

研發一部

G03

GS07

研發二部

G03

GS08

測試一部

G04

GS09

測試二部

G04

GSL03

研發一部杭州分部

GS06

GSL04

研發一部西安分部

GS06

2 給定數據集


2 TreeView效果圖

可以看到,這裏產生了節點遺漏現象。一般來說,我們可以從兩方面入手去解決問題,一方面可以修正數據集,另一方面則可以修改生成樹算法。顯然直接修正數據集是很複雜的,且會帶來效率問題。而單方面修改生成樹算法也是不是很好(即把遺漏的節點直接插到根節點下),因爲這時會出現父輩和晚輩同級的現象。

三、通過深度編號遞歸生成樹算法

回顧到已有的一些方法(文[1~5]),其中基於節點深度生成樹的方法給我們一些啓發,我們在構造數據集時可以增加深度字段,但這裏的深度不是簡單的層級號,是一個擴展了的概念,具體地說其實是一個深度編號,它與父輩編號存在一定的對應關係。比如表1所示的數據集可以作如下編號:

TagValue

ContentValue

ParentID

DepthID

G01

行銷部

 

a001

G02

顧問部

 

a002

G03

研發部

 

a003

G04

測試部

 

a004

GS01

行銷一部

G01

a001001

GS02

行銷二部

G01

a001002

GS03

行銷三部

G01

a001003

GSL01

行銷一部北京辦

GS01

a001001001

GSL02

行銷一部上海辦

GS01

a001001002

GS04

顧問一部

G02

a002001

GS05

顧問二部

G02

a002002

GS06

研發一部

G03

a003001

GS07

研發二部

G03

a003002

GS08

測試一部

G04

a004001

GS09

測試二部

G04

a004002

GSL03

研發一部杭州分部

GS06

a003001001

GSL04

研發一部西安分部

GS06

a003001002

3 帶深度編號的數據集

其中,DepthID即是節點的深度編號。生成深度編號的過程其實也不復雜,首先我們可以制定編號的規則,比如層級編號的前綴、編碼長度、起始值等。當給某個節點編號時,只要找到所在層級的最大編號,然後增1。具體實現過程這裏不再細述。

於是,我們很自然地想到借鑑算法1的思想設計基於深度編號的生成樹程序。這時,我們可以根據當前節點的深度編號尋找其後代節點集,但要給出一個最大跨度(可以理解爲最高級與最低級間的間隔級數),因爲不可能無限制地找下去。這種方法可以部分程度上彌補"由父節點及子節點"的遍歷的缺陷,因爲當出現斷層時會沿着編號繼續往後找。但是還是會可能漏掉,比如我們給定數據集(把“研發一部”過濾掉):

TagValue

ContentValue

ParentID

DepthID

G01

行銷部

 

a001

G02

顧問部

 

a002

G03

研發部

 

a003

G04

測試部

 

a004

GS01

行銷一部

G01

a001001

GS02

行銷二部

G01

a001002

GS03

行銷三部

G01

a001003

GSL01

行銷一部北京辦

GS01

a001001001

GSL02

行銷一部上海辦

GS01

a001001002

GS04

顧問一部

G02

a002001

GS05

顧問二部

G02

a002002

GS07

研發二部

G03

a003002

GS08

測試一部

G04

a004001

GS09

測試二部

G04

a004002

GSL03

研發一部杭州分部

GS06

a003001001

GSL04

研發一部西安分部

GS06

a003001002

4 給定數據集

在生成樹過程中,當從“研發部”(a003)往下找子節點時,找到的應該是“研發二部”(a003002),因爲它是最近的節點。而下面的順序就是沿着“研發二部”再往下找,顯然不可能找到“研發一部杭州分部”和“研發一部西安分部”,因爲編號規則不一樣,這樣生成的樹同樣會漏掉節點。

我們提出一種新的算法,即打破傳統的遍歷順序,採用由底向上的遍歷順序。形象地說,傳統的方法是通過一個既有根節點或父節點來不斷衍生新的子節點(如圖3(a)所示),而新的算法是通過不斷聚集節點,形成子樹集,最後匯成一棵樹(如圖3(b)所示)。


3 TreeView節點生成流程示意圖

 

算法2:由底向上按層次(深度)遍歷法生成樹算法

l         Step 0:數據準備,給定數據集(TagValue,ContentValue,DepthID), TagValue是節點的實際值,ContentValue是節點顯示的值或者說標籤值,DepthID是節點的深度編號。若節點爲根節點,一般其DepthID長度爲最短。給定最大深度iMaxDepLen和最小深度iMinDepLen。給定用於存儲當前子樹的Hashtable;

l         Step 1:給定當前遍歷的層級長度iCurDepLen,初始設爲iMaxDepLen;

l         Step 2:在數據集中根據給定iCurDepLen查找滿足條件的層級,得到該層級的節點集objArr[]
if (objArr == null)
   return;
else
{
  //
遍歷節點集
  for(int i=0; i<objArr.Length;i++)
  {
   Step 2.1
查找objArr[i]的父節點,若無父節點,直接加入,goto Step 2.2;若有父節點,先查找父節點是否已在Hashtable中。若有,將其從Hashtable中移出並記爲tempParNode;否則生成新節點tempParNodegoto Step 2.3
   Step 2.2
若當前節點objArr[i]不在Hashtable中,在Hashtable中添加objArr[i]continue;
   Step 2.3
若當前節點objArr[i]不在Hashtable中,根據objArr[i]生成節點tempNode;否則,將其從Hashtable中移出,並記爲tempNode;將tempNode插到tempParNode中,並將存入Hashtable

  }
}

l         Step 3:若當前層級iCurDepLen大於最小層級iMinDepLen,則繼續回溯,將iCurDepLen1並作爲當前iCurDepLengoto Step 2;否則goto Step 4.

l         Step 4:在得到用Hashtable存儲的節點表後(實際上是一子樹表),遍歷Hashtable,將各棵子樹插入TreeView.

 

在該算法中,我們一開始便計算好數據集中節點深度編號的最小長度和最大長度,目的是爲了不盲目搜索。但如果數據集中每一層級的深度編號是固定長的,則可以更簡化搜索過程。存放臨時子樹的Hashtable的鍵值是當前子樹根節點的Tag值,這樣的好處是查找相當方便,不需要在TreeView中遍歷一個個節點。所以,每次處理上一層級的節點,只需看其父節點在不在Hashtable中,若在將其插入子樹,否則增加Hashtable項。

附錄示例程序實現了這一算法,這裏介紹一下關鍵的幾個函數。

函數形式及其參數解釋

功能

PopulateCompleteTree(ref System.Windows.Forms.TreeView objTreeView,DataSet dsSource,string strTreeCaption,int iTagIndex,int iContentIndex,int iDepthIndex)

1.        objTreeView是最終要生成的TreeView

2.        dsSource是給定數據集;

3.        strTreeCaptionTreeView根節點的名稱;

4.        iTagIndex是數據集中TagValue字段的列號;

5.        iContentIndex是數據集中ContentValue字段的列號;

6.        iDepthIndex是數據集中DepthID字段的列號;

1.        採用層次(深度)遍歷法生成樹主調函數;

2.        調用CollectNodes(DataSet dsSource,int iTagIndex,int iContentIndex,int iDepthIndex,int iCurDepLen,int iMinDepLen,ref Hashtable objArrNode)

CollectNodes(DataSet dsSource,int iTagIndex,int iContentIndex,int iDepthIndex,int iCurDepLen,int iMinDepLen,ref Hashtable objArrNode)

1.        dsSourceiTagIndexiContentIndexiDepthIndex同上;

2.        iCurDepLen指當前層級深度編號長度;

3.        iMinDepLen指最小深度即最頂層深度編號長度;

4.        objArrNode指用於存放中間子樹的Hashtable

1.        從底往上聚集節點;

2.        調用  LookupParentNode(DataSet dsSource,int iDepthIndex,string strSubDepth,int iTagIndex,int iContentIndex)

LookupParentNode(DataSet dsSource,int iDepthIndex,string strSubDepth,int iTagIndex,int iContentIndex)

1.      dsSourceiTagIndexiContentIndexiDepthIndex同上;

2.      strSubDepth指當前節點的深度編號(因爲是遞歸查找)

1.        查找最近的上控層級,因爲有可能父節點層級不存在。

 

 

此時若給定數據集(我們把“研發部”和“行銷一部”過濾掉),

TagValue

ContentValue

ParentID

DepthID

G01

行銷部

 

a001

G02

顧問部

 

a002

G04

測試部

 

a004

GS02

行銷二部

G01

a001002

GS03

行銷三部

G01

a001003

GSL01

行銷一部北京辦

GS01

a001001001

GSL02

行銷一部上海辦

GS01

a001001002

GS04

顧問一部

G02

a002001

GS05

顧問二部

G02

a002002

GS07

研發二部

G03

a003002

GS08

測試一部

G04

a004001

GS09

測試二部

G04

a004002

GSL03

研發一部杭州分部

GS06

a003001001

GSL04

研發一部西安分部

GS06

a003001002

5 給定數據集

則生成樹如下圖所示,


4 TreeView效果圖

這正是我們需要的結果。

當然,有時爲了結構的需要,我們還會採取所謂“中立”的方法。比如對於本文所提的TreeView控件節點生成問題,如果不想再寫算法去生成深度編號,那麼我們還可以通過給數據集增加標誌位的方法,即用標誌位來標識數據是否已被篩選。在運用傳統算法生成樹後,再檢查一下是否有未被篩選的數據,若有則查找其祖輩節點,將其插入祖輩節點。不過這裏的“查找祖輩節點”是在TreeView上進行的,當節點很多時其效率肯定沒有直接在數據集上搜索高。

另外,深度編號的引入不僅會給生成樹帶來方便,還可以讓權限設置更靈活。具體到我們的示例來說,一般如果我們要把某些部門過濾掉,那麼會把這些部門一個一個挑出來,我們稱之爲“離散值設置方式”。而當系統結構龐大時,我們更希望挑選一個區間,比如把一個部門及其下控的n級過濾掉,這是一個“連續值設置方式”,這時包含層級概念的深度編號可以很好地解決這個問題。實際的系統開發中,我們也發現採用這種方式是切實可行的。

 

四、其他TreeView生成方式

前面提到TreeView還可以通過XML文件()生成。這種方式實現的關鍵是構造出一個類似於TreeViewXML文檔或字符串出來。其基本思想應該與前面討論的算法是相似的,只是在程序實現上稍微複雜一些(其中,XML節點的索引可以基於文檔對象模型(DOM)來做)。另外還要注意的是,有很多的第三方TreeView控件,他們所支持的XML文檔的格式是不盡相同的。限於篇幅,本文不詳細討論具體實現過程。

五、小結

       本文主要討論了.NET平臺下TreeView控件節點生成程序設計,結合已有方法和實際需求,對設計方法進行了研究,給出了比較完整的解決方法。

在樹的具體應用中,除了生成樹之外,節點的增、刪、改、查甚至節點的升級和降級都是很常見的。本質上說,這些操作所涉及的是與業務相關的數據庫操作,所以在採用“由底向上按層次(深度)遍歷法”生成的TreeView中,這些操作的實現與傳統方法是一致的,額外的操作無非是添加或修改深度編號。當然,實際需求是變化多端的,相應算法的設計與分析也是無止境的。

 

參考文獻(Reference:

[1] Zane Thomas. DataViewTree for Windows Formshttp://www.abderaware.com/WhitePapers/ datatreeview.htm

[2] 李洪根. 樹形結構在開發中的應用, http://www.microsoft.com/china/community/Column/ 21.mspx

[3] 李洪根. .NET平臺下Web樹形結構程序設計, http://www.microsoft.com/china/community/ Column/30.mspx

[4] Don Schlichting. Populating the TreeView Control from a Database, http://www.15seconds. com/issue/030827.htm

[5] HOW TO: Populate a TreeView Control from a Dataset in Visual Basic .NET, http://support. microsoft.com/?kbid=320755

[6] Scott Mitchell. Displaying XML Data in the Internet Explorer TreeView Controlhttp://aspnet. 4guysfromrolla.com/articles/051403-1.aspx

 


-------------
source code:

using System;
using System.Data;
using System.Windows.Forms;
using System.Collections;


namespace PopTreeView
{
 /// <summary>
 /// TreeOperator 的摘要說明。
 /// </summary>
 public class TreeOperator
 {
  public TreeOperator()
  {
   //
   // TODO: 在此處添加構造函數邏輯
   //
  }


  /// <summary>
  /// 採用層次(深度)遍歷法生成樹
  /// </summary>
  /// <param name="objTreeView">目標樹</param>
  /// <param name="dsSource">數據集</param>
  /// <param name="strTreeCaption">樹顯示名</param>
  /// <param name="iTagIndex">值索引</param>
  /// <param name="iContentIndex">內容索引</param>
  /// <param name="iDepthIndex">層次索引</param>
  public static void PopulateCompleteTree(ref System.Windows.Forms.TreeView objTreeView,DataSet dsSource,string strTreeCaption,int iTagIndex,int iContentIndex,int iDepthIndex)
  {
   //從底層開始遍歷,開闢一個HashTable(以Tag值爲關鍵字),存放當前計算的節點
   objTreeView.Nodes.Clear();
   int iMaxLen = GetMaxDepthLen(dsSource,iDepthIndex);
   int iMinLen = GetTopDepthLen(dsSource,iDepthIndex);
   Hashtable objArrNode = new Hashtable();
   CollectNodes(dsSource,iTagIndex,iContentIndex,iDepthIndex,iMaxLen,iMinLen,ref objArrNode);

   TreeNode objRootNode = new TreeNode(strTreeCaption);
   
   //在得到節點表後,插入樹
   foreach(object objNode in objArrNode.Values)
   {
    TreeNode objNewNode = new TreeNode();
    objNewNode = (TreeNode)objNode;
    objRootNode.Nodes.Add(objNewNode);
   }

   objTreeView.Nodes.Add(objRootNode);
  }

  
  /// <summary>
  /// 從底往上聚集節點
  /// </summary>
  /// <param name="dsSource"></param>
  /// <param name="iTagIndex"></param>
  /// <param name="iContentIndex"></param>
  /// <param name="iDepthIndex"></param>
  /// <param name="iCurDepLen"></param>
  /// <param name="iMinDepLen"></param>
  /// <param name="objArrNode"></param>
  private static void CollectNodes(DataSet dsSource,int iTagIndex,int iContentIndex,int iDepthIndex,int iCurDepLen,int iMinDepLen,ref Hashtable objArrNode)
  {
   //收集節點
   System.Data.DataView dv;
   System.Windows.Forms.TreeNode tempNode;
   
   //查找給定層節點
   int i=iCurDepLen;
   do
   {
    dv = new DataView(dsSource.Tables[0]);
    string strExpr = "LEN(TRIM("+dsSource.Tables[0].Columns[iDepthIndex].ColumnName+"))="+Convert.ToString(i);
    dv.RowFilter = strExpr;
    i--;
   }while(i>=iMinDepLen && dv.Count<=0);
   iCurDepLen = i+1;

   #region 逐層回溯,收集節點
   foreach(System.Data.DataRowView drow in dv)
   {
    //查找父節點
    string[] strArrParentInfo = LookupParentNode(dsSource,iDepthIndex,drow[iDepthIndex].ToString().Trim(),iTagIndex,iContentIndex);
    string strTagValue = drow[iTagIndex].ToString().Trim();
    string strContentValue = drow[iContentIndex].ToString();

    //若無父節點,直接加入
    if (strArrParentInfo == null)
    {
     //當前節點不在Hashtable中
     if (objArrNode[strTagValue]==null)
     {
      //添加當前節點
      tempNode = new TreeNode(strContentValue);
      tempNode.Tag = strTagValue;
      objArrNode.Add(strTagValue,tempNode);
     }
    }
    else //有父節點,此時先查找父節點是否已在Hashtable中
    {
     string strParTagValue = strArrParentInfo[0].Trim();
     string strParContentValue = strArrParentInfo[1].Trim();

     //父節點已在Hashtable中
     if (objArrNode[strParTagValue]!= null)
     {
      //當前節點不在Hashtable中
      if (objArrNode[strTagValue]==null)
      {
       tempNode = new TreeNode(strContentValue);
       tempNode.Tag = strTagValue;
      }
      else
      {
       //取出並移除該節點,然後插入父節點
       tempNode = new TreeNode();
       tempNode =(TreeNode)objArrNode[strTagValue];
       objArrNode.Remove(strTagValue);
      }

      //插入到父節點中
      TreeNode tempParNode = new TreeNode();
      tempParNode = (TreeNode)objArrNode[strParTagValue];
      tempParNode.Nodes.Add(tempNode);
      objArrNode[strParTagValue] = tempParNode;
     }
     else //父節點不在Hashtable中
     {
      //當前節點不在Hashtable中
      if (objArrNode[strTagValue]==null)
      {
       tempNode = new TreeNode(strContentValue);
       tempNode.Tag = strTagValue;
      }
      else
      {
       //取出並移除該節點,然後插入父節點
       tempNode = new TreeNode();
       tempNode = (TreeNode)objArrNode[strTagValue];
       objArrNode.Remove(strTagValue);
      }

      //創建父節點並將當前節點插入到父節點中
      TreeNode tempParNode = new TreeNode(strParContentValue);
      tempParNode.Tag = strParTagValue;
      tempParNode.Nodes.Add(tempNode);
      objArrNode.Add(strParTagValue,tempParNode);
     }
    }
   }
   #endregion
   
   //還有未遍歷的層
   if (iCurDepLen>iMinDepLen)
   {
    CollectNodes(dsSource,iTagIndex,iContentIndex,iDepthIndex,iCurDepLen-1,iMinDepLen,ref objArrNode);
   }
   
  }


  /// <summary>
  /// 查找父親節點
  /// </summary>
  /// <param name="dsSource"></param>
  /// <param name="iDepthIndex"></param>
  /// <param name="strSubDepth"></param>
  /// <param name="iTagIndex"></param>
  /// <param name="iContentIndex"></param>
  /// <returns>找到返回由Tag值,內容值和深度值組成的字符串數組,否則返回null</returns>
  private static string[] LookupParentNode(DataSet dsSource,int iDepthIndex,string strSubDepth,int iTagIndex,int iContentIndex)
  {
   System.Data.DataView  dv;
   int iSubLen = strSubDepth.Length;
   
   if (iSubLen<=1)
   {
    return null;
   }
   
   int i=1;
   do
   {
    dv = new DataView(dsSource.Tables[0]);
    string strExpr ="TRIM("+dsSource.Tables[0].Columns[iDepthIndex].ColumnName+") = '"+strSubDepth.Substring(0,iSubLen-i)+"'";
    dv.RowFilter = strExpr;
    i++;
   }while(i<iSubLen && dv.Count<=0);

   if (dv.Count<=0)
   {
    return null;
   }
   else
   {
    string[] strArr = {dv[0][iTagIndex].ToString(),dv[0][iContentIndex].ToString(),dv[0][iDepthIndex].ToString()};
    return strArr;
   }
  }

  
  /// <summary>
  /// 得到最大深度值(深度的長度)
  /// </summary>
  /// <param name="dsSource">數據集</param>
  /// <param name="iDepthIndex">深度索引(列號)</param>
  /// <returns>最大深度值</returns>
  private static int GetMaxDepthLen(DataSet dsSource,int iDepthIndex)
  {
   DataRowCollection objRowCol = dsSource.Tables[0].Rows;
   int iMax = objRowCol[0][iDepthIndex].ToString().Trim().Length;
   
   foreach(DataRow objRow in objRowCol)
   {
    int iCurlen = objRow[iDepthIndex].ToString().Trim().Length;
    if (iMax<iCurlen)
    {
     iMax = iCurlen;
    }
   }

   return iMax;
  }


  /// <summary>
  /// 得到最小深度值(深度的長度)
  /// </summary>
  /// <param name="dsSource">數據集</param>
  /// <param name="iDepthIndex">深度索引(列號)</param>
  /// <returns>最小深度值</returns>
  private static int GetTopDepthLen(DataSet dsSource,int iDepthIndex)
  {
   DataRowCollection objRowCol = dsSource.Tables[0].Rows;
   int iMin = objRowCol[0][iDepthIndex].ToString().Trim().Length;
   
   foreach(DataRow objRow in objRowCol)
   {
    int iCurlen = objRow[iDepthIndex].ToString().Trim().Length;
    if (iMin>iCurlen)
    {
     iMin = iCurlen;
    }
   }

   return iMin;
  }


 }
}

發佈了61 篇原創文章 · 獲贊 0 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章