2012-04-24 331 views
2

感謝您抽出時間閱讀本文。如何從SQL查詢構建對象層次結構? (對於WPF TreeView)

我在從SQL數據庫獲取數據時嘗試構建層次結構對象時遇到問題。 請注意,我是一個新手程序員。

你如何建立一個層次不明的對象?當我說未知級別時,我的意思是,每個節點可能具有不同數量的子節點,而子節點又可能具有不同數量的自己的子節點,如此等等。

這個想法是我需要創建一個使用我的SQL數據綁定到WPF TreeView控件的分層對象。

下面我已經包含了我到目前爲止的代碼。 代碼的第一位是我的類由屬性組成。請注意,「Products」類具有引用自身的ObservableCollection。我認爲這是你構建嵌套節點的方式。即列表內的列表。

第二段代碼是我從Get數據庫下載數據的Get方法。這裏是我需要的一些如何將下載的數據分類到層次結構中。

產品類別(屬性)

public class Products : INotifyPropertyChanged, IDataErrorInfo 
{ 
    private Int64 m_ID; 
    private SqlHierarchyId m_Hierarchy; 
    private string m_Name; 
    private ObservableCollection<Products> m_ChildProducts; 

    // Default Constructor 
    public Products() 
    { 
     ChildProducts = new ObservableCollection<Products>(); 
    } 

    //Properties 

    public Int64 ID 
    { 
     get 
     { 
      return m_ID; 
     } 
     set 
     { 
      m_ID = value; 
      OnPropertyChanged(new PropertyChangedEventArgs("ID")); 
     } 
    } 

    public SqlHierarchyId Hierarchy 
    { 
     get 
     { 
      return m_Hierarchy; 
     } 
     set 
     { 
      m_Hierarchy = value; 
      OnPropertyChanged(new PropertyChangedEventArgs("Hierarchy")); 
     } 
    } 

    public String Name 
    { 
     get 
     { 
      return m_Name; 
     } 
     set 
     { 
      m_Name = value; 
      OnPropertyChanged(new PropertyChangedEventArgs("Name")); 
     } 
    } 

    public Int16 Level 
    { 
     get 
     { 
      return m_Level; 
     } 
     set 
     { 
      m_Level = value; 
      OnPropertyChanged(new PropertyChangedEventArgs("Level")); 
     } 
    } 

    public Int64 ParentID 
    { 
     get 
     { 
      return m_ParentID; 
     } 
     set 
     { 
      m_ParentID = value; 
      OnPropertyChanged(new PropertyChangedEventArgs("ParentID")); 
     } 
    } 

    public ObservableCollection<Products> ChildProducts 
    { 
     get 
     { 
      return m_ChildProducts; 
     } 
     set 
     { 
      m_ChildProducts = value; 
      OnPropertyChanged(new PropertyChangedEventArgs("ChildProducts")); 
     } 
    } 

    //INotifyPropertyChanged Event 
    public event PropertyChangedEventHandler PropertyChanged; 
    public void OnPropertyChanged(PropertyChangedEventArgs e) 
    { 
     if (PropertyChanged != null) 
      PropertyChanged(this, e); 
    } 
} 

方法,該方法從SQL數據庫中獲取數據:

public static ObservableCollection<Products> GetProductsHierarchy() 
    { 

     ObservableCollection<Products> productsHierarchy = new ObservableCollection<Products>(); 

     SqlConnection connection = new SqlConnection(DBConnection.GetConnection().ConnectionString); 

     string selectStatement = "SELECT ID, Hierarchy, Name, Hierarchy.GetLevel() AS Level, Hierarchy.GetAncestor(1) AS ParentHierarchy, " + 
               "(SELECT ID " + 
               "FROM SpecProducts " + 
               "WHERE (Hierarchy = SpecProducts_1.Hierarchy.GetAncestor(1))) AS ParentID " + 
           "FROM SpecProducts AS SpecProducts_1 " + 
           "WHERE (EnableDisable IS NULL) " + 
           "ORDER BY Hierarchy"; 

     SqlCommand selectCommand = new SqlCommand(selectStatement, connection); 

     try 
     { 
      connection.Open(); 
      SqlDataReader reader = selectCommand.ExecuteReader(); 

      while (reader.Read()) 
      { 

       Products product = new Products(); 
       product.ID = (Int64)reader["ID"]; 
       product.Name = reader["Name"].ToString(); 
       product.Hierarchy = (SqlHierarchyId)reader["Hierarchy"]; 
       product.Level = (Int16)reader["Level"]; 
       if (reader["ParentID"] != DBNull.Value) 
       { 
        product.ParentID = (Int64)reader["ParentID"]; 
       } 
       else 
       { 
        product.ParentID = 0; 
       } 

       productsHierarchy.Add(product); 

       // *** HOW TO BUILD HIERARCHY OBJECT WITH UNKNOWN & VARYING LEVELS? 
       // *** ADD PRODUCT TO CHILDPRODUCT 
      } 

      return productsHierarchy; 
     } 
     catch (SqlException ex) 
     { 
      throw ex; 
     } 
     finally 
     { 
      connection.Close(); 
     } 
    } 

下面我已附加表示我的SQL查詢的數據的結構的圖像。 請注意,未來添加更多產品時,層級可能會變得更深。我需要創建的Hierarchy對象應該具有足夠的靈活性,以便無論節點級別的數量如何都可以擴展。

非常感謝您的時間,所有幫助非常感謝。

SQL Query Data

*********編輯26/04/2012 14:37 *******************

請在下面找到一個鏈接,下載我的項目代碼(這裏只包含treeview代碼)。 有人可以看看它,看看爲什麼我不能創建2級以上的節點?

該代碼是由用戶HB MAAM給我的。感謝您「HB MAAM」迄今的幫助!

Click this link to download code

+0

您能否顯示層次結構的基本視覺效果?通過僞代碼。 – 2012-04-24 16:01:10

+0

嗨。感謝您的答覆。我現在可以上傳圖片到這個論壇。請在我的文章中找到我的SQL數據看起來像給定的查詢。我已經從原來的方式略微修改了SQL查詢。所有更新已在上面的代碼中修改。 – RobHurd 2012-04-24 16:37:46

+0

不需要所有那只是parentId會很好 – 2012-04-24 17:09:39

回答

3

我將創建爲一個例子,
1-第一我將創建,其保存來自DB的數據的類

public class SqlDataDto 
{ 
    public int? Id { get; set; } 
    public int? ParentId { get; set; } 

    public String Name { get; set; } 
    public String OtherDataRelatedToTheNode { get; set; } 
} 

2-該數據將被轉換以分層的數據,我們將使用這個類來保存它:

public class LocalData : INotifyPropertyChanged 
{ 
    private int? _id; 
    public int? Id 
    { 
     get { return _id; } 
     set { _id = value; OnPropertyChanged("Id"); } 
    } 

    private int? _parentId; 
    public int? ParentId 
    { 
     get { return _parentId; } 
     set { _parentId = value; OnPropertyChanged("ParentId"); } 
    } 

    private string _name; 
    public String Name 
    { 
     get { return _name; } 
     set { _name = value; OnPropertyChanged("Name"); } 
    } 

    private string _otherDataRelatedToTheNode; 
    public String OtherDataRelatedToTheNode 
    { 
     get { return _otherDataRelatedToTheNode; } 
     set { _otherDataRelatedToTheNode = value; OnPropertyChanged("OtherDataRelatedToTheNode"); } 
    } 

    private LocalData _parent; 
    public LocalData Parent 
    { 
     get { return _parent; } 
     set { _parent = value; OnPropertyChanged("Parent"); } 
    } 

    private ObservableCollection<LocalData> _children; 
    public ObservableCollection<LocalData> Children 
    { 
     get { return _children; } 
     set { _children = value; OnPropertyChanged("Children"); } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
    public void OnPropertyChanged(String propertyName) 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this,new PropertyChangedEventArgs(propertyName)); 
     } 
    } 
}  

3-最後我們需要將sql數據更改爲hi erarchical之一:

public List<LocalData> GetHerachy(List<SqlDataDto> sqlData) 
{ 
    var sqlParents = sqlData.Where(q => q.ParentId == null).ToList(); 
    var parents = sqlParents.Select(q => new LocalData {Id = q.Id, Name = q.Name}).ToList(); 
    foreach (var parent in parents) 
    { 
     var childs = sqlData.Where(q => q.ParentId == parent.Id).Select(q => new LocalData { Id = q.Id, Name = q.Name , Parent = parent}); 
     parent.Children = new ObservableCollection<LocalData>(childs); 
    } 
    return parents; 
} 

4-那麼你可以創建一個虛擬的數據,並將其轉換並顯示在樹:

var sqlData = new List<SqlDataDto> 
        { 
         new SqlDataDto {Id = 1, ParentId = null, Name = "F1"} 
         , new SqlDataDto {Id = 2, ParentId = null, Name = "F2"} 
         , new SqlDataDto {Id = 3, ParentId = 1, Name = "S1"} 
         , new SqlDataDto {Id = 4, ParentId = 2, Name = "S21"} 
         , new SqlDataDto {Id = 5, ParentId = 2, Name = "S22"} 
        }; 
treeView.ItemsSource = GetHerachy(sqlData); 

5-樹應該是這樣的:

<TreeView Name="treeView"> 
    <TreeView.ItemTemplate> 
     <HierarchicalDataTemplate ItemsSource="{Binding Children}"> 
      <TextBlock Text="{Binding Name}" /> 
     </HierarchicalDataTemplate> 
    </TreeView.ItemTemplate> 
</TreeView> 
+0

嗨,非常感謝您的迴應。你的解決方案運作良好但是,如果我添加第三級節點,它似乎不起作用。例如,如果我添加另一個sqlData項目,其中ParentId = 5,則該樹只顯示深度不爲3的兩個級別。即,具有parentID = 5的新項目不可見。你知道這可能是爲什麼嗎?再次,非常感謝您的幫助。 – RobHurd 2012-04-25 09:48:01

+0

首先歡迎你,第二你應該接受答案,如果你的問題解決了,最後嘗試添加下面的節點到sql數據實例,並且你要添加它們的任何其他節點將如下所示,新的SqlDataDto {Id = 6,ParentId = null,Name =「This is the root(id = 6)」} ,new SqlDataDto {Id = 7,ParentId = 6,Name =「這是父級6的孩子」} ,new SqlDataDto { Id = 8,ParentId = 6,Name =「這是父母6的另一個孩子」} – 2012-04-25 15:18:05

+0

Hi HB MAAM。是的,對不起,我會標記你的答案,但目前它沒有完全奏效。如果我添加一個新的項目,如下所示:new SqlDataDto {Id = 6,ParentId = 5,Name =「Id5的子項」}則不會創建它。它不是在孩子內部創造一個孩子。當調試Id = 5的項目具有空的子項時,因爲它應該有一個Id = 6的子項。再次感謝您的時間幫助我。 – RobHurd 2012-04-25 16:53:44

0

// *** HOW TO BUILD HIERARCHY OBJECT WITH UNKNOWN & VARYING LEVELS?

而不是
ObservableCollection<Products> productsHierarchy = new ObservableCollection<Products>();
使用Dictionary<Int64, Products> IdToProduct = new ...

當你循環你的產品;做一個IdToProduct[product.ID] = product;

然後,循環完成IdToProduct收集和做;

if(product.ParentID != 0) 
{ 
    IdToProduct[product.ParentID].ChildProducts.Add(product); 
} 

現在,您的產品 - > ChildProducts關係被映射出來。

可選,添加屬性的Products class
public bool IsCategory { get { return (ChildProducts.Count >= 1); } } // e.g. Oven
public bool IsProduct { get { return !(IsCategory); } } // e.g. Electric (Oven)

現在,你有大部分的模型定義您的視圖。

article是使用WPF的TreeView的事實出發點。

提示:爲您HierarchicalDataTemplate

<TreeView.ItemTemplate> 
    <HierarchicalDataTemplate DataType="{x:Type local:Products}" 
           ItemsSource="{Binding ChildProducts}"> 
     <TextBlock Text="{Binding Name}" /> 
    </HierarchicalDataTemplate> 
</TreeView.ItemTemplate> 

起點,您應該創建一個MainViewModel類有:
public Products RootProduct { get; set; }(通知屬性更改屬性)

後,你做你的SQL語法分析,什麼不可以;這樣做:
RootProduct = IdToProduct.FirstOrDefault(product => (product.Level == 0));

<TreeView ItemsSource="{Binding RootProduct.ChildProducts}">

1

您需要使用遞歸來填充每個對象的兒童名單。這對於WPF HierarchicalDataTemplate的工作是必需的。否則,你只能得到第一級。 There是使用Linq方法ForEach()並傳遞Action參數的替代方法。以下解決方案非常簡單易懂:

public List<Product> Products { get; set; } 

public MainViewModel() 
{ 
    Products = new List<Product>(); 

    Products.Add(new Product() { Id = 1, Name = "Main Product 1", ParentId = 0 }); 
    Products.Add(new Product() { Id = 3, Name = "Sub Product 1", ParentId = 1 }); 
    Products.Add(new Product() { Id = 4, Name = "Sub Product 2", ParentId = 1 }); 
    Products.Add(new Product() { Id = 5, Name = "Sub Product 3", ParentId = 1 }); 
    Products.Add(new Product() { Id = 6, Name = "Sub Product 3.1", ParentId = 5 }); 


    this.ProcessRootNodes(); 
} 

private void ProcessRootNodes() 
{ 
    var rootNodes = Products.Where(x => x.ParentId == 0).ToList(); 

    for (int i = 0; i < rootNodes.Count; i++) 
    { 
rootNodes[i].Children = this.AddChildren(rootNodes[i]); 
    } 
} 

private List<Product> AddChildren(Product entry) 
{ 
    var children = Products.Where(x => x.ParentId == entry.Id).ToList(); 

    for(int i=0;i<children.Count;i++) 
    { 
children[i].Children = this.AddChildren(children[i]); 
    } 

    return children; 
}