原始碼下載
2.0 下載頁面
1.0 下載頁面
注意:連結是微軟SkyDrive頁面,下載時請用瀏覽器直接下載,用某些下載工具可能無法下載
原始碼環境:Microsoft Visual C# 2008 Express
程式ViewModel名稱是FileSystemObjectViewModel,它把檔案和檔案夾統一視為一類,並用FileSystemObjectType枚舉來區分類型。ViewModel的邏輯很簡單,這裡就不再多說了,下面主要介紹一些其他需要注意的地方。
建議下載原始碼作參考。
在2.0後:
程式使用.NET 4.0中Directory類型所支援的EnumerateDirectories和EnumerateFiles方法。
即時資料載入
檔案瀏覽器最基本的效能考慮就應該是資料的即時載入,也就是說展開的資料夾檢視都是在使用者進行TreeViewItem的展開操作之後才被載入的,當然資料夾檢視收縮後已經載入的資料是不會被扔掉的,下次展開會直接開啟已經載入的資料。
那麼每當一個ViewModel被建立後,初始化函數會進行這樣一種操作,如果它是檔案夾的話,快速檢查當前檔案夾是否有子成員(子檔案夾或子檔案),如果有的話,把一個特殊的沒有任何值的特殊ViewModel加在當前ViewModel的Children屬性中。這樣WPF資料繫結會在當前產生的TreeViewItem的Items加入這個由特殊ViewModel所產生的另一個TreeViewItem,當前TreeViewItem左側會顯示“加號”,代表著當前檔案夾有子成員,而事實上,子成員的具體內容只有當該檔案夾被使用者展開後才會被載入。
MVVM中View中是沒有事件代碼的,只有XAML,因此我們首先要利用TreeViewItem的IsExpanded相依性屬性,同時ViewModel中也有IsExpanded屬性,用雙向的資料繫結使這兩個屬性可以互相設定(在本例中其實只存在View設定ViewModel的情況)。
參考FileSystemObjectViewModel中的OnExpanded方法:
/// <summary>
/// 節點被展開後的操作
/// </summary>
protected virtual void OnExpanded()
{
//是否有特殊節點
if (HasSpecialChild)
{
//將要展開的節點擁有沒有列舉的子成員(第一次開啟)
//我們需要移除特殊節點,並將子檔案夾加入到Children中
RemoveSpecialChild();
foreach (var dir in GetSubDirs())
_Children.Add(new FileSystemObjectViewModel(dir, FileSystemObjectType.Folder));
foreach (var file in GetSubFiles())
_Children.Add(new FileSystemObjectViewModel(file, FileSystemObjectType.File));
}
}
開啟TreeView的虛擬化
在.NET 3.5後,微軟增加了VirtualizingStackPanel對TreeView的支援,虛擬化可以使TreeView得到更客觀的效能,但是需要手動聲明VirtualizingStackPanel的附加屬性來使虛擬化起作用。
<TreeView VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"></TreeView>
VirtualizingStackPanel.VirtualizationMode預設是Standard:建立並丟棄項容器,應該儘可能的使用Recycling:重用項容器。
從檔案或檔案夾中提取表徵圖
Windows Forms中有Icon.ExtractAssociatedIcon(MSDN:http://msdn.microsoft.com/en-us/library/system.drawing.icon.extractassociatedicon.aspx),但其缺點是無法從提取檔案夾(包括磁碟根目錄)的表徵圖,並且在WPF程式中使用需要額外引用System.Drawing命名空間DLL。
因此使用SHGetFileInfo本地Windows API(MSDN:http://msdn.microsoft.com/en-us/library/bb762179.aspx),並將非託管表徵圖資源轉換成WPF的BitmapSource。
感謝互連網,就直接用這個連結所提供的代碼來做SHGetFileInfo的執行:(http://stackoverflow.com/questions/6008600/get-program-icons),我把它包在一個叫IconExtractor的類中。
View
主View:FileSystemView使用ObjectDataProvider來調用ViewModel(FileSystemObjectViewModel)的靜態方法來索取當前系統的所有磁碟根目錄並將結果綁定在TreeView的DataContext上。
資源中還包括一個自訂的資料轉換器(IconConverter)用來將FileSystemObjectViewModel轉換成相應的檔案或檔案夾表徵圖,並通過Image顯示在TreeView的HierarchicalDataTemplate中。
FileSystemView:
<UserControl.Resources>
<ObjectDataProvider x:Key="dataProvider"
ObjectType="loc:FileSystemObjectViewModel"
MethodName="GetSystemDrives"/>
<loc:IconConverter x:Key="iconConverter" />
</UserControl.Resources>
<TreeView VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
DataContext="{Binding Source={StaticResource dataProvider}}"
ItemsSource="{Binding Children}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="loc:FileSystemObjectViewModel"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Converter={StaticResource iconConverter}}" />
<TextBlock Text="{Binding DisplayName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>