WPF:自定義Metro樣式文件夾選擇對話框FolderBrowserDialog


1.前言

WPF並沒有文件選擇對話框,要用也就只有使用Winform版的控件。至今我也沒有尋找到一個WPF版本的文件選擇對話框。

 

可能是我眼濁,如果各位知道有功能比較健全的WPF版文件選擇對話框、文件打開對話框,還請留言告知。

 

這次做的是一個精簡版的文件選擇對話框。包含一個UserControl和一個承載UserControl的Window。

 

另外TreeView的樣式引用自Mahspps中的樣式。也就是如果需要使用這個文件選擇對話框,就必須要引用Mahapps的相關dll。

 

當然,我會提供整個項目的源代碼。如果大家不嫌棄,可以自己移除項目中的Mahspps相關引用。當然,這樣做可能會使得控件界面顯得比較難看,不過你還可以自己給TreeView提供你自己喜歡的樣式。

 

這個文件選擇對話框功能比較精簡,僅僅提供文件夾選擇的功能。其他諸如右鍵菜單、新建文件夾、文件夾拖放等功能目前並未實現。要完整使用WPF實現微軟完整版的FolderBrowserDialog,我相信這工作量還是相當大的。

2.開始分析

先打開Winform版本的文件選擇對話框看看是什么樣子:

 

可以看到,從上到下依次是:桌面(不可展開)、庫、計算機(展開就是各個磁盤驅動器)、網絡、控制面板、回收站、以及桌面上的各個文件夾。

不管微軟的這個習慣設計得怎么樣,反正用戶現在也習慣了這個設計。所以,本次實現的WPF版文件選擇對話框也大致采用這個設計。其中有幾點:

庫用得太少了,去掉。

網絡這個似乎有些是要輸入密碼的,我這沒有遠程的共享主機,所以不清楚微軟這個是怎么讓用戶輸入密碼的。暫不實現。

至於控制版面和回收站,我不知道微軟把這兩個放在文件選擇對話框里面算是什么意思?活躍氣氛么~~~

其實說到這里,我有個想法。微軟這個文件選擇對話框里面的操作文件的方式(包括右鍵菜單、文件拖放等)和系統的資源管理器實在太像了。所以我懷疑這個文件選擇對話框

里面的那棵樹實際上是嵌入的一個另類的資源管理器,而並非微軟單獨開發的一個樹。。。這樣說來,也可以解釋為什么微軟不提供WPF版的文件選擇對話框了,因為Explorer內部實現可能不是采用的WPF方式實現,所以改起來工作量很大...然后就不提供了。

當然,這些都是猜測,猜測。。

好了,話說回來,這個控件最關鍵的一點就是怎么把整個磁盤的文件通過一棵樹加載起來,總不能初始化的時候先把整個磁盤的文件組織成一棵樹?效率的原因決不允許你那樣做。

有沒有一種方法可以在用戶展開某個節點的時候才初始化它的子節點?

微軟為TreeView提供了一個模版:HierarchicalDataTemplate

使用這個模版,當用戶展開某個節點時,TreeView會展開這個節點並且初始化這個節點的子節點的子節點,也就是HierarchicalDataTemplate第一次初始化的時候就被初始化第一層和第二層,當用戶展開第一層的時候,它就開始初始化第三層...依次類推。因為使用這個模版初始化TreeView效率還是相當可觀的。

到此,我們為TreeView准備一個模型(Model):

這個模型應該至少有這幾個屬性:

1.Name,節點的名稱

2.FullName,節點所在位置的完整磁盤路徑

3.Children,節點的所有子節點

另外你如果還需要其他類似圖標等等也可以繼續加。

在本例中,Model如下:

可以看到,多出了一個Type,這個Type主要指節點的類型,比如上面提到的網絡、庫、控制面板等等,這個說它們是文件夾但又不太是,所以沒辦法和文件夾一樣統一處理。

所以給每個節點賦予一個類型,既方便處理,也方便以后的擴展。

Type的實現如下:

/// <summary>
    /// 文件項類型
    /// </summary>
    internal enum MetroFolderBrowserControlModelType
    {
        /// <summary>
        /// 表明這是一個文件夾
        /// </summary>
        Directory,
        /// <summary>
        /// 桌面
        /// </summary>
        Desktop,
        /// <summary>
        /// 計算機
        /// </summary>
        Computer,
        /// <summary>
        /// 磁盤驅動器
        /// </summary>
        Disk,
    }

考慮到不同類型的節點給它們不同的節點圖標顯得更好看,所以有這樣做:

        /// <summary>
        /// 文件項類型
        /// </summary>
        public MetroFolderBrowserControlModelType Type
        {
            get
            {
                return type;
            }
            set
            {
                switch(value)
                {
                    case MetroFolderBrowserControlModelType.Directory:
                        {
                            ItemImagePath = ImagePathHelper.FolderIconPath; 
                            break;
                        }
                    case MetroFolderBrowserControlModelType.Computer:
                        {
                            ItemImagePath = ImagePathHelper.ComputerIconPath; 
                            break;
                        }
                    case MetroFolderBrowserControlModelType.Desktop:
                        {
                            ItemImagePath = ImagePathHelper.DesktopIconPath; 
                            break;
                        }
                    case MetroFolderBrowserControlModelType.Disk:
                        {
                            ItemImagePath = ImagePathHelper.DiskIconPath;
                            break;
                        }
                    default:
                        {
                            ItemImagePath = ImagePathHelper.FolderIconPath; 
                            break;
                        }
                }
                type = value;
            }
        }

現在最主要的就是Children的實現還沒做好。Children的如何實現也關系到整個控件的效率。

針對不同的類型,采用不同的辦法獲取其Children:

        /// <summary>
        /// 子目錄(文件 + 文件夾)
        /// </summary>
        public ObservableCollection<MetroFolderBrowserControlModel> Children
        {
            get
            {
                try
                {
                    if (children != null)
                    {
                        return children;
                    }
                    children = new ObservableCollection<MetroFolderBrowserControlModel>();
                    switch(Type)
                    {
                        case MetroFolderBrowserControlModelType.Desktop:
                            {
                                break;
                            }
                        case MetroFolderBrowserControlModelType.Computer:
                            {
                                foreach (var device in Environment.GetLogicalDrives())
                                {
                                    if (Directory.Exists(device))
                                    {
                                        MetroFolderBrowserControlModel model = new MetroFolderBrowserControlModel();
                                        model.FileName = device;
                                        model.FullName = device;
                                        model.Type = MetroFolderBrowserControlModelType.Disk;
                                        children.InvokeAdd<MetroFolderBrowserControlModel>(model);
                                    }
                                }
                                break;
                            }
                        case MetroFolderBrowserControlModelType.Disk:
                        case MetroFolderBrowserControlModelType.Directory:
                            {
                                foreach (var item in ExplorerHelper.GetDirectoryChildrenItems(FullName, true, false, false))
                                {
                                    MetroFolderBrowserControlModel model = new MetroFolderBrowserControlModel();
                                    model.FileName = item.Name;
                                    model.FullName = item.FullName;
                                    children.Add(model);
                                }
                                break;
                            }
                        default:
                            {
                                break;
                            }
                    }
                    return children;
                }
                catch (Exception)
                {
                    //出現異常,返回空的集合
                    return null;
                }
            }
        }

接下來使用  綁好,這個樹差不多就展示出來了:

        <TreeView ItemsSource="{Binding MetroFolderBrowserControlModels}" x:Name="treeview">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                    <StackPanel Orientation="Horizontal">
                        <Image Source="{Binding ItemImagePath, Mode=TwoWay}" Width="16" Height="16"/>
                        <TextBlock Text="{Binding FileName}" Margin="5,0,0,0"/>
                    </StackPanel>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>

至於其他獲取用戶選擇的路徑、對話框的返回值等等就不繼續說了,有需要的可以下載源代碼查看:

下載源代碼:

http://download.csdn.net/detail/lyclovezmy/7655655

大致效果如下圖:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM