2011-07-05 129 views
1

我正在構建一個帶有ScanItem列表的樹形視圖。類ScanItem的居然是:通過遞歸搜索HierarchicalData

public class ScanItem 
    { 
     public string FullPath { get; set; } 
     public string Name 
     { 
      get 
      { 
       return Path.GetFileName(FullPath); 
      } 

     } 
     public DateTime ModifiedDate { get; set; } 
     public DateTime CreatedDate { get; set; } 
     public FileAttributes Attributes { get; set; } 
     public bool IsDirectory { get; set; } 


     public string Extension 
     { 
      get 
      { 
       if (IsDirectory) 
        return "Folder"; 
       else 
        return Path.GetExtension(Name); 
      } 
     } 

     public UInt64 Size { get; set; }    
    } 

爲了讓我創造一個TreeView,我需要爲了區別在我的樹視圖中的文件夾和文件來創建其他兩個類:

public class ScanFile : ScanItem 
    { 

    } 
    public class ScanDir : ScanItem 
    { 
     public List<ScanItem> Items { get; set; } 
     public ScanDir() 
     { 
      Items = new List<ScanItem>(); 
     }     
    } 

注意ScanFile類與ScanItem類似,ScanDir類具有一個名爲Items的額外屬性,並將包含其自身的項目列表。

所以,如果我在哪裏可以通過這個direcotory迭代(C:\ TEMP):enter image description here

我的列表實際上將包含:

enter image description here

需要注意的是,如果我展開一個SCANDIR對象我會得到另一個列表:

enter image description here

以填充日E採用樹狀:

enter image description here

所以我能夠填充使用遞歸通過在特定的路徑搜索文件和目錄此列表。

我只是想解釋我的情況,因爲互聯網中有幾個地方可以過濾樹狀視圖,而這正是我真正想要做的。但是,如果我可以遍歷列表中的每個項目,然後在不符合某些條件時將其刪除,那將會很好:

我實際上已嘗試使用以下遞歸方法來篩選我的結果。

public List<ScanItem> search(List<ScanItem> items) 
    { 
     var filter = new List<ScanItem>(); 

     foreach (var item in items) 
     { 
      if (!item.FullPath.Contains("stringIwantToLookFor")) continue; 
      filter.Add(item); 
      if (item.IsDirectory) 
      { 
       search(((ScanDir)item).Items);      
      }     
     } 

     return filter; 
    } 

我認爲,如果找到一個項目,我需要添加所有的父根目錄,這就是爲什麼它不起作用。我想構建自己的遞歸方法的原因是因爲我希望能夠根據特定條件過濾樹視圖。

編輯:

換句話說,如果我想有所有包含「X.txt」在我的列表視圖我想剛纔看到的項目: enter image description here

+0

「IsDirectory」檢查是一個代碼「氣味」。使用基於您正在處理的對象類型的條件邏輯來預測行爲不是多態的,而且過於頻繁且積極地使用,會導致難以閱讀和難以維護的代碼。您最好查看「訪問者」設計模式來解決搜索問題。 –

回答

2

我會這樣做:在您的ScanItem上創建public abstract ScanItem Seach(string s)。然後你可以用你想要搜索的字符串來調用它。

實際的實現應該是這樣的:

ScanFile

public override ScanItem Seach(string s) 
{ 
    if (Name.Contains(s)) 
     return this; 

    return null; 
} 

ScanDir

public override ScanItem Seach(string s) 
{ 
    var results = Items.Select(i => i.Seach(s)).Where(i => i != null).ToList(); 
    if (results.Any()) 
    { 
     var result = (ScanDir)MemberwiseClone(); 
     result.Items = results; 
     return result; 
    } 

    return null; 
} 

ScanFile實現很簡單:如果文件匹配,返回它,否則返回null。在ScanDir中,遞歸地調用所有子項目上的Search。如果它們中的任何一個返回非null,則創建當前對象的副本並將副本的Items僅設置爲匹配的副本。如果沒有匹配,則返回null

請注意,這將只搜索文件的名稱,而不是目錄。但如果你想這樣做,這種修改將是直接的。

+0

哇,這是非常感謝,非常感謝。那正是我所期待的。 –

0

我意識到我的評論到您的文章可能沒有足夠的描述性,所以我編寫了一些C#僞代碼來演示我在做什麼。

下面是使用訪問者模式來實現多態,鬆散耦合的方式搜索的例子:

interface FilesystemVistor 
{ 
    void Visit (FilesystemItem item); 
} 
interface FilesystemItem 
{ 
    void Accept(FilesystemVistor visitor); 
    string Name; 
} 

class Directory : FilesystemItem 
{ 
    private FilesystemItem[] _children; 
    public void Accept(FilesystemVistor visitor) { 
    visitor.Visit(this); 
    foreach(FilesystemItem item in _children) 
    { 
     visitor.Visit(item); 
    } 
    } 
} 
class File : FilesystemItem 
{ 
    public void Accept(FilesystemVistor visitor) { 
    visitor.Visit(this); 
    } 
} 

class FilesystemSearcher : FilesystemVistor 
{ 
    private List<string> _results; 
    public void Visit(FilesystemItem item) { 
    if (item.Name == "Foo") { _results.Add(item.Name); } 
    } 
} 

這種「訪客模式」爲基礎的設計將讓你實現任何類型的搜索,而無需搜索算法必須「知道」有關文件系統和文件系統結構的任何內容,並不需要像「IsDirectory」這樣的額外屬性來公開其實現細節。

+0

所以我會構造一個FilesystemSearcher的列表的權利?我將如何將此綁定到我的樹視圖?對不起,我是接口和WPF的新... –

1

你應該對目錄稍微不同,因爲現在,如果根目錄不符合標準,例程將立即退出。

試試這個:改變你的ScanItem一點:

public class ScanItem { 
    ... 
    public virtual bool IsDirectory { get; } 
    ... 
} 

添加到您的SCANFILE:

public class ScanFile : ScanItem { 
    public override bool IsDirectory { 
    get { return false; } 
    } 
} 

,並給你的SCANDIR:

public class ScanDir : ScanItem { 
    public List<ScanItem> Items { get; set; } 
    public ScanDir() { 
    Items = new List<ScanItem>(); 
    } 

    public ScanDir CopyWithoutChildren() { 
    return new ScanDir() { 
     FullPath = this.FullPath, 
     ModifiedDate = this.ModifiedDate, 
     CreatedDate = this.CreatedDate, 
     Attributes = this.Attributes, 
     Size = this.Size 
    }; 
    } 

    public override bool IsDirectory { 
    get { return true; } 
    } 
} 

現在做濾波在文件上,省略空目錄:

public List<ScanItem> search(List<ScanItem> items) { 
    var filter = new List<ScanItem>(); 

    foreach(var item in items) { 
    if(item.IsDirectory) { 
     List<ScanItem> potential = search(((ScanDir)item).Items); 
     if(potential.Count > 0) { 
     ScanDir dir = ((ScanDir)item).CopyWithoutChildren(); 
     dir.Items.AddRange(potential); 
     filter.Add(dir); 
     } 
    } else { 
     if(!item.FullPath.Contains("stringIwantToLookFor")) continue; 
     filter.Add(item); 
    } 
    } 

    return filter; 
} 

我沒有測試它,但我想應該做你想做的。

0

so如果我正在查找包含foo的文件,此方法將填充列表'newList'中包含foo的文件。在調用該方法之前,我必須將該列表設置爲等於新列表。我顯然缺少基本的實現,如更改foo參數等。我也想刪除我正在處理的空目錄。

private List<ScanDir> history = new List<ScanDir>(); 
    private ScanDir LastDir; 
    private List<ScanItem> newList = new List<ScanItem>(); 
    public void Search(List<ScanItem> allItems) //adds files that contain foo 
    { 
     bool updateLastDir = false; 

     foreach(ScanItem s in allItems) 
     { 
      if (updateLastDir) 
      { 
       history = (from a in history 
          select a).Distinct().ToList(); 

       LastDir = null; 
       for (int i = history.Count - 1; i >= 0; i--) 
       { 
        if (history[i].FullPath == Directory.GetParent(s.FullPath).ToString()) 
        { 
         LastDir = history[i]; 
         break; 
        }       
       } 

       updateLastDir = false;           
      } 
      if (s.IsDirectory) 
      { 
       var temp = new ScanDir { FullPath = s.FullPath, IsDirectory = true, comparePath = s.comparePath, Attributes = s.Attributes }; 

       if (LastDir == null) 
       { 
        newList.Add(temp);       
       } 
       else 
       { 
        LastDir.Items.Add(temp); 

       } 

       LastDir = temp; 
       history.Add(LastDir); 

       Search(((ScanDir)s).Items); 

       history.RemoveAt(history.Count - 1); 

       updateLastDir = true; 


      } 
      else 
      { 
       if (s.Name.Contains("Foo")) // then add it 
       { 
        if (LastDir == null)       
         newList.Add(s);       
        else       
         LastDir.Items.Add(s);      
       } 
      } 
     } 

    }