2011年12月5日 星期一

以ListView元件的VirtualMode實作TreeListView

(下載範例)

在.NET framework裡面的System.Windows.Forms元件家族中有一般常見的TreeView元件及ListView元件,但卻沒有結合這兩者的TreeListView元件或ListTreeView元件。網路上可以找到很不錯的資源,這裡另外介紹一個簡單的方式,可以透過現有的機制實作TreeListView特性。

在.NET裡的ListView元件有一個VirtualMode,它的功能是由user自行管理ListViewItem內容。當VirtualMode啟動時,ListView元件會透過RetrieveVirtualItem事件向user要指定序號的ListViewItem。透過這一層機制,就能向我們提供一個彈性,稍加應用就能用以製作TreeListView元件功能。

下面這個範例(C#),使用ListView元件的VirtualMode,模倣VisualStudio在debug時的LocalVariable視窗,如面圖所示。


首先建立內部資料結構,TreeListItem。
class TreeListItem
{
  public int Level = 0;
  public string Name;
  public string Value;
  public string Type;
  public List Child = new List();
  public bool Expand = false;
  public TreeListItem AddChild(string n, string v, string t)
  {
    TreeListItem c = new TreeListItem();
    c.Name = n;
    c.Value = v;
    c.Type = t;
    c.Level = this.Level + 1;

    Child.Add(c);

    return c;
  }
}
Level是用來表示階層,Child串列儲存下一個階層的子項目,而Expand則用來表示在Tree中這個項目是打開或關閉的將態。AddChild用來幫助加入一個子項目。

每當ListView元件內容變更時,我們需要手動去改變VirtualListSize的值,這時ListView元件就會透過RetrieveVirtualItem事件,向我們獲得每一個項目的內容。

在設定VirtualListSize的值之前,我們另外需要一個串列,用來存放每一個展開的可見的項目,這是為了方便同時也簡單實作。這個串列的內容,是根據已建立好的樹狀的項目資料,以遞迴方式展開取得。
TreeListItem Root = new TreeListItem();
Root是一個虛擬的項目,用來存放全部的資料內容。在範例程式開始時,會ctor裡事先建立完成。
  TreeListItem cThis = Root.AddChild("this", "{TreeListView.Form1, Text: }", "TreeListView.Form1");
  TreeListItem cRoot = cThis.AddChild("Root", "{TreeListView.Form1.TreeListItem}", "TreeListView.Form1.TreeListItem");
  TreeListItem cChild = cRoot.AddChild("Child", "Count = 0", "System.Collections.Generic.List");
  cChild.AddChild("Row View", "", "");
  cRoot.AddChild("Expand", "false", "bool");
  cRoot.AddChild("Level", "0", "int");
  cRoot.AddChild("Name", "null", "string");
  cRoot.AddChild("Value", "null", "string");
以上會建立如下結構的資料。
this
  Root
    Child
      Root View
    Expand
    Level
    Name
    Value
下面的程式碼定義BuildVirtualItems,根據目前的Expand狀態以遞迴方式重建VirtualItems串列,然後重設ListView元件的VirtualListSize值。
List VirtualItems = new List();

void BuildVirtualItems()
{
  VirtualItems.Clear();

  BuildVirtualItems(Root.Child);

  listView1.VirtualListSize = VirtualItems.Count;
}

void BuildVirtualItems(List Items)
{
  for (int i = 0; i < Items.Count; i++)
  {
    VirtualItems.Add(Items[i]);

    if (Items[i].Expand)
      BuildVirtualItems(Items[i].Child);
  }
}
當ListView元件的VirtualListSize的值被改變後,ListView元件會以RetrieveVirtualItem事件去取得新的項目內容。在RetrieveVirtualItem裡只需簡單的以事件參數傳遞進來的項目索引值到VirtualItems串列裡去抓出項目資料,以這筆資料新建一個ListViewItem物件回傳即可。
private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
  TreeListItem c = VirtualItems[e.ItemIndex];

  string Name = "";

  for (int i = 0; i < c.Level; i++)
    Name += "  ";

  if (0 < c.Child.Count)
  {
    if (c.Expand)
      Name += "- ";
    else
      Name += "+ ";
  }
  else
    Name += "  ";

  Name += c.Name;

  ListViewItem Item = new ListViewItem(Name);
  Item.SubItems.Add(c.Value);
  Item.SubItems.Add(c.Type);

  e.Item = Item;
}
這裡面用文字的+及-模擬樹狀結構的展開及關閉狀態鈕。 最後再加上一個處理滑鼠事件,處理DoubleClick用來展開或關閉項目。記得將ListView元件的FullRowSelect屬性設為true,這樣才不用只點擊第一個欄位才有作用。
private void listView1_MouseDoubleClick(object sender, MouseEventArgs e)
{
  ListViewItem lvi = listView1.GetItemAt(e.X, e.Y);
  if (null == lvi)
    return;

  TreeListItem c = VirtualItems[lvi.Index];
  if (0 == c.Child.Count)
    return;

  c.Expand = !c.Expand;

  BuildVirtualItems();
}
因為+/-鈕是用文字模擬出來的,如果要在畫面上顯示更精確的狀態,則只需要自行實作DrawItem及DrawSubItem即可。

(下載範例)

沒有留言:

張貼留言

Related Posts Plugin for WordPress, Blogger...