c# WPF實現Windows資源管理器(附源碼)

    今天我來寫一篇關於利用WPF來實現Windows的資源管理器功能,當然隻是局部實現這個功能,因為在很多時候我們需要來實現對本機資源的管理,當然我們可以使用OpenFileDialog dialog = new OpenFileDialog()這個Microsoft.Win32命名空間下的這個類來實現一些資源查找和導入的功能,但是在很多時候我們可能需要更多的功能,並且希望能夠集成到我們自己的項目中,但是我們這個時候就不得不自己來寫一套來集成到我們的軟件中去瞭,因為OpenFileDialog這個是無法作為一個UserControl加入到我們的項目中的,當然我們隻是實現瞭其中的一部分功能,因為Windows的資源管理器也是一個重量級的應用,也是十分龐大和復雜的,這裡隻是通過這個Demo來加深對WPF的MVVM模式及軟件基本功的鞏固。

  在正式介紹整體框架之前,首先來看看整體的結構,從而對其有一個大概的瞭解。

  整個界面從大的方面來說主要包括三個方面:1 文件及文件夾顯示區域、2 導航區域、3 路徑顯示區域,其實在整個界面中,2和3都是圍繞1來進行操作的,三個區域之間的耦合性其實是非常高的,所以常規的做法就是三個部分分為三個UserControl,並且同時綁定到一個ViewModel中,這樣整個層次也就比較清晰瞭,缺點是一個ViewModel中代碼太多,職責非常大,所以在這個DEMO中嘗試將三個部分分開,三個ViewModel來操作三個View裡面的內容,整個實現下來其實也有一些不足之處,那就是容易將問題復雜化,很多在一個類中就能夠完成的工作,最終要通過各種類與類之間的耦合來完成,所以通過這個DEMO希望自己能夠多一些思考,從而在軟件的設計中能夠再多一些經驗,能夠把握好軟件粒度的問題,下面就軟件的具體內容來深入分析一下。

      第一部分:FileList

  這個部分是整個文件和文件夾的顯示部分,再三權衡下,決定采用自定義DataGrid的方式來展現整個部分。       

<UserControl x:Class="FileSelectorDemo.Views.FileList"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:converter="clr-namespace:FileSelectorDemo.Converters"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:defines="clr-namespace:FileSelectorDemo.Defines"
             xmlns:local="clr-namespace:FileSelectorDemo.Views"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <converter:CountToVisibilityConverter x:Key="CountToVisibilityConverter"></converter:CountToVisibilityConverter>
        <converter:TypeToVisibleConverter x:Key="TypeToVisibleConverter"></converter:TypeToVisibleConverter>
        <converter:TypeToCollapsedConverter x:Key="TypeToCollapsedConverter"></converter:TypeToCollapsedConverter>
        <converter:CollectionSelectedCountConverter x:Key="CollectionSelectedCountConverter"></converter:CollectionSelectedCountConverter>      
    </UserControl.Resources>
    <Grid>     
        <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
            <Grid>
                <StackPanel Orientation="Vertical">
                    <DataGrid x:Name="fileList" Style="{StaticResource DefaultDataGrid}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" defines:MouseDoubleClick.Command="{Binding OpenCurrentDirectory}"
                              defines:MouseDoubleClick.CommandParameter="{Binding SelectedItem,RelativeSource={RelativeSource Self}}"  IsReadOnly="True"
                              defines:MouseLeftButtonUpClick.Command="{Binding SelectCurrentFileListItem}" ItemsSource="{Binding CurrentFileList}"  CanUserAddRows="False"  AutoGenerateColumns="False" GridLinesVisibility="None">
                        <DataGrid.Columns>
                            <DataGridTemplateColumn  MinWidth="60">
                                <DataGridTemplateColumn.Header>
                                    <CheckBox Content="全選" Margin="2" FontSize="12" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding DataContext.IsStateCheckAll,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGrid}}}"></CheckBox>
                                </DataGridTemplateColumn.Header>
                                <DataGridTemplateColumn.CellTemplate>
                                    <DataTemplate>
                                        <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                                            <TextBlock Text="不可選" FontSize="12" Foreground="Black" Visibility="{Binding CurrentType,Converter={StaticResource TypeToCollapsedConverter}}" HorizontalAlignment="Left" VerticalAlignment="Center" ></TextBlock>
                                            <CheckBox IsChecked="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" IsThreeState="False" IsHitTestVisible="False" Visibility="{Binding CurrentType,Converter={StaticResource TypeToVisibleConverter}}"  HorizontalAlignment="Left" VerticalAlignment="Center" ToolTip="點擊選中當前對象"></CheckBox>
                                        </Grid>
                                    </DataTemplate>
                                </DataGridTemplateColumn.CellTemplate>
                            </DataGridTemplateColumn>
                            <DataGridTemplateColumn Header="名稱" >
                                <DataGridTemplateColumn.CellTemplate>
                                    <DataTemplate>
                                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
                                            <Image Source="{Binding Icon}" Margin="2"  HorizontalAlignment="Center" VerticalAlignment="Center"></Image>
                                            <TextBlock Text="{Binding Name}" Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
                                        </StackPanel>
                                    </DataTemplate>
                                </DataGridTemplateColumn.CellTemplate>
                            </DataGridTemplateColumn>
                            <DataGridTextColumn Header="修改日期"   Binding="{Binding CreateTime}"/>
                            <DataGridTextColumn Header="類型"   Binding="{Binding CurrentType}"/>
                            <DataGridTextColumn Header="大小"  Binding="{Binding Size}"/>
                        </DataGrid.Columns>
                    </DataGrid>
                    <TextBlock Margin="2 5" HorizontalAlignment="Left" VerticalAlignment="Center" >
                        <Run>總共 </Run>
                        <Run Text="{Binding CurrentFileList.Count,Mode=OneWay}"></Run>
                        <Run> 個項目</Run>
                        <Run>(已選中 </Run>
                        <Run Text="{Binding CurrentFileList,Converter={StaticResource CollectionSelectedCountConverter},Mode=OneWay}"></Run>
                        <Run> 個項目)</Run>
                    </TextBlock>
                </StackPanel>
                <TextBlock Text="該文件為空"  Visibility="{Binding CurrentFileList.Count,Converter={StaticResource CountToVisibilityConverter}}" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
            </Grid>
        </ScrollViewer>         
    </Grid>
</UserControl>

  這裡都是一些常規的定義,這裡就不再贅述,DataGrid樣式是引用Themes文件夾下面的CustomDataGrid的樣式,這裡面也包括自定義的ScrollViewer樣式和ScrollBar的樣式,讀者也可以進行思考,另外需要註意設置下面的幾個屬性。

  1  IsReadOnly=”True”這個屬性能夠保證在鼠標點擊時候,不再顯示內部的TextBox,從而使使用者不能夠隨意進行編輯。

  2  GridLinesVisibility=”None” 這個能夠使整個DataGrid不再顯示分割線,從而使其樣式更加接近Windows的原生樣式。

  3  CanUserAddRows=”False” 這個屬性也非常重要,不然在整個顯示區域的下面會多出一行,當用戶點擊它的時候會增加行。

  4  AutoGenerateColumns=”False”這個就比較熟悉瞭,一般不讓其自動增加列。

  5   SelectionUnit=“FullRow” 表示鼠標點擊時選擇的單位是整行,而不是其中的單元格或者其他,關於其它的幾個枚舉值,讀者也可查閱相關瞭解。

  6   SelectionMode=“Extended”允許多選,當按下鼠標的Ctrl鍵進行點擊的時候能夠選中多個對象。

  7   最後一個就是關於設置DataGrid的虛擬化容器瞭,具體設置方法是:

 <Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True"></Setter>
 <Setter Property="VirtualizingStackPanel.VirtualizationMode" Value="Recycling" />

  WPF中的VirtualizingStackPanel.VirtualizationMode 附加屬性指定 ItemsControl 中的面板如何虛擬化其子項。默認情況下,VirtualizingStackPanel 將為每個可見項創建一個項容器,並在不再需要時(比如當項滾動到視圖之外時)丟棄該容器。當 ItemsControl 包含多個項時,創建和廢棄項容器的過程可能會對性能產生負面影響。如果 VirtualizingStackPanel.VirtualizationMode 設置為 Recycling,VirtualizingStackPanel 將重用項容器,而不是每次都創建新的項容器,這個是摘錄自MSDN的相關資料,由於我們加載的DataGrid的項不是很多,如果足夠多的情況下,效果可能會更加明顯,關於更多的“虛擬化”技術可以參考更多的資料。

  這裡我們需要重點關註的是當我們雙擊DataGridRow時會打開對應的子文件夾,同時單擊時會選中當前的DataGridRow,這裡關於事件的綁定我們使用的不是System.Windows.Interactivity這種方式來綁定事件的,這裡我們通過自定義一個附加屬性來實現的,這裡以鼠標左鍵雙擊為例來進行說明。

  這裡需要進行說明的就是在OnMouseDoubleClick中,我們通過當前鼠標的點擊的Point來查找最終的DataGridRow 然後觸發綁定的Command事件,在前臺View中,我們隻需要通過綁定到對應的ICommand即可。

public class MouseDoubleClick
{
    public static DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached("Command",
        typeof(ICommand),
        typeof(MouseDoubleClick),
        new UIPropertyMetadata(CommandChanged));
 
    public static DependencyProperty CommandParameterProperty =
        DependencyProperty.RegisterAttached("CommandParameter",
                                            typeof(object),
                                            typeof(MouseDoubleClick),
                                            new UIPropertyMetadata(null));
 
    public static void SetCommand(DependencyObject target, ICommand value)
    {
        target.SetValue(CommandProperty, value);
    }
 
    public static void SetCommandParameter(DependencyObject target, object value)
    {
        target.SetValue(CommandParameterProperty, value);
    }
    public static object GetCommandParameter(DependencyObject target)
    {
        return target.GetValue(CommandParameterProperty);
    }
 
    private static void CommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        Control control = target as Control;
        if (control != null)
        {
            if ((e.NewValue != null) && (e.OldValue == null))
            {
                control.MouseDoubleClick += OnMouseDoubleClick;
            }
            else if ((e.NewValue == null) && (e.OldValue != null))
            {
                control.MouseDoubleClick -= OnMouseDoubleClick;
            }
        }
    }
 
    private static void OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
        DataGrid datagrid = sender as DataGrid;
        Point point = e.GetPosition(datagrid);
        IInputElement obj = datagrid.InputHitTest(point);
        DependencyObject target = obj as DependencyObject;
 
        while (target != null)
        {
            if (target is DataGridRow)
            {
                ICommand command = (ICommand)datagrid.GetValue(CommandProperty);
                object commandParameter = datagrid.GetValue(CommandParameterProperty);
                if (null != commandParameter)
                {
                    command.Execute(commandParameter);
                }
                break;
            }
            target = VisualTreeHelper.GetParent(target);
        }
    }
}  
defines:MouseDoubleClick.Command="{Binding OpenCurrentDirectory}"  defines:MouseDoubleClick.CommandParameter="{Binding SelectedItem,RelativeSource={RelativeSource Self}}"

  其中我們需要定義命名空間defines,這種方法為我們綁定View層中的各種事件提供能瞭一種新的方式,這個是我們需要不斷去總結和分析的地方。

  第二部分:Navigation

      這一部分是我們的導航欄的部分,通過向前、向後、向上等快捷操作,我們能夠快速切換文件夾,從而使切換路徑變得更加容易,這一部分就是需要著重說一下最近瀏覽項目這個功能,它能夠保存用戶最近瀏覽的10個目錄(開發者自定義),從而方便用戶迅速切換不同的路徑,這個是通過ToggleButton和Popup這一對經典的組合來實現的,具體實現請參考源代碼。這一部分重點來說一下當鼠標移動到不同的位置時,能夠變換綁定的圖標是向前還是向後抑或選中狀態,這個其實是通過綁定每一個Model的一個CurrentDirection來實現的,這裡需要重點掌握DataTrigger的使用方法。

<Popup Placement="Bottom" PlacementTarget="{Binding ElementName=NavigationPanel}" StaysOpen="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
               IsOpen="{Binding IsChecked,ElementName=History,Mode=OneWay}" PopupAnimation="Slide">
            <ItemsControl Background="#f5f5f5" BorderBrush="Gray" BorderThickness="1" Padding="0"
                          ItemsSource="{Binding AttachedDataContext.DirectoryHistory,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}}}">
                <ItemsControl.Template>
                    <ControlTemplate TargetType="{x:Type ItemsControl}">
                        <Border x:Name="outer" Padding="0 2" Background="#f5f5f5">
                            <StackPanel Orientation="Vertical" IsItemsHost="True"></StackPanel>
                        </Border>                       
                    </ControlTemplate>
                </ItemsControl.Template>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Button x:Name="radioButton" Command="{Binding AttachedDataContext.SwitchDirectory,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}}}"
                                CommandParameter="{Binding}">
                            <Button.Template>
                                <ControlTemplate TargetType="{x:Type Button}">
                                    <Border x:Name="bg" Padding="0" Background="#f5f5f5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
                                            <Path x:Name="path" Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center" Stroke="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"  Width="24" Height="24"
                                                  Opacity="0" StrokeThickness="2"  StrokeLineJoin="Round"  SnapsToDevicePixels="False">                                               
                                            </Path>                                        
                                            <Image Source="{Binding Icon}" Width="24" Height="24" Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center"></Image>
                                            <TextBlock Text="{Binding Name}" Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
                                        </StackPanel>
                                    </Border>
                                    <ControlTemplate.Triggers>
                                        <DataTrigger Binding="{Binding CurrentDirection}" Value="選中">
                                            <Setter Property="Opacity" Value="1" TargetName="path"></Setter>
                                            <Setter Property="Data" Value="M 2,10 L 8,14 18,6" TargetName="path"></Setter>
                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding CurrentDirection}" Value="向前">
                                            <Setter Property="Opacity" Value="0" TargetName="path"></Setter>
                                            <Setter Property="Data" Value="M8,6 L1,11 8,16 M0,11 L15,11" TargetName="path"></Setter>
                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding CurrentDirection}" Value="向後">
                                            <Setter Property="Opacity" Value="0" TargetName="path"></Setter>
                                            <Setter Property="Data" Value="M8,6 L15,11 8,16 M0,11 L15,11" TargetName="path"></Setter>
                                        </DataTrigger>
                                        <Trigger Property="IsMouseOver" Value="true">
                                            <Setter Property="Background" Value="#91c9f7" TargetName="bg"></Setter>
                                            <Setter Property="Opacity" Value="1" TargetName="path"></Setter>
                                        </Trigger>
                                    </ControlTemplate.Triggers>
                                </ControlTemplate>
                            </Button.Template> 
                        </Button>                                     
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Popup>

   第三部分:BreadCrumbView

   這個部分是用來顯示當前文件夾路徑,並進行快速切換準備的,這個部分是一個組合控件,主要是通過ItemsControl和ToggleButton和Popup來實現的,這個裡面需要註意的是這裡面綁定的命令和方法都是在FileList的ViewModel中定義的,這裡為瞭最大程度的實現代碼的重用。很多時候我們會發現通過這種方式我們需要能夠隨時訪問到FileListViewModel中的內容,這個是整個DEMO中最重要的部分,所以如何才能夠引用到FileListViewModel裡面的內容呢?

public partial class BreadCrumbView : UserControl
    {
        public BreadCrumbView()
        {
            InitializeComponent();
            Loaded +=new RoutedEventHandler(BreadCrumbView_Loaded);
        }
 
        private void BreadCrumbView_Loaded(object sender, RoutedEventArgs e)
        {
            this.DataContext = new ViewModels.BreadCrumbViewModel(AttachedDataContext);
        }
 
        /// <summary>
        /// 當前FileList的DataContext對象
        /// </summary>
        public object AttachedDataContext
        {
            get { return (object)GetValue(AttachedDataContextProperty); }
            set { SetValue(AttachedDataContextProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AttachedDataContextProperty =
            DependencyProperty.Register("AttachedDataContext", typeof(object), typeof(BreadCrumbView), new PropertyMetadata(null));
    }

    通過定義一個AttachedDataContext對象,我們能夠將FileListViewModel中定義的屬性分散到各個ViewModel中,這樣在一定程度上能夠保證避免FileListViewModel中代碼過多同時職責過重的問題,但是同時我們也發現瞭,如果彼此之間的耦合過大,采用這種方式會加重代碼之間的復雜度,因為有時不得不通過Action或者事件等方式來進行ViewModel之間的交互和通訊,所以降到這裡我們不得不說一些較大較復雜的項目中使用框架的重要性瞭,比如Prism亦或是Caliburn.Micro等框架能夠使整個軟甲架構看起來更加清楚和明白,這也是為瞭更好地增加軟件的模塊化和靈活性。

   通過這個DEMO的分析,我們需要在不斷的實踐中去總結這類型的經驗,從而使整個軟件顯得更加合理,最終使自己能夠真正地對軟件的架構的思想有一個比較深入的瞭解。

       最後需要整個Demo的請點擊此處進行下載!

以上就是c# WPF實現Windows資源管理器(附源碼)的詳細內容,更多關於c# WPF實現Windows資源管理器的資料請關註WalkonNet其它相關文章!

推薦閱讀: