c# WPF實(shí)現(xiàn)Windows資源管理器(附源碼)
今天我來寫一篇關(guān)于利用WPF來實(shí)現(xiàn)Windows的資源管理器功能,當(dāng)然只是局部實(shí)現(xiàn)這個(gè)功能,因?yàn)樵诤芏鄷r(shí)候我們需要來實(shí)現(xiàn)對(duì)本機(jī)資源的管理,當(dāng)然我們可以使用OpenFileDialog dialog = new OpenFileDialog()這個(gè)Microsoft.Win32命名空間下的這個(gè)類來實(shí)現(xiàn)一些資源查找和導(dǎo)入的功能,但是在很多時(shí)候我們可能需要更多的功能,并且希望能夠集成到我們自己的項(xiàng)目中,但是我們這個(gè)時(shí)候就不得不自己來寫一套來集成到我們的軟件中去了,因?yàn)镺penFileDialog這個(gè)是無法作為一個(gè)UserControl加入到我們的項(xiàng)目中的,當(dāng)然我們只是實(shí)現(xiàn)了其中的一部分功能,因?yàn)閃indows的資源管理器也是一個(gè)重量級(jí)的應(yīng)用,也是十分龐大和復(fù)雜的,這里只是通過這個(gè)Demo來加深對(duì)WPF的MVVM模式及軟件基本功的鞏固。
在正式介紹整體框架之前,首先來看看整體的結(jié)構(gòu),從而對(duì)其有一個(gè)大概的了解。
整個(gè)界面從大的方面來說主要包括三個(gè)方面:1 文件及文件夾顯示區(qū)域、2 導(dǎo)航區(qū)域、3 路徑顯示區(qū)域,其實(shí)在整個(gè)界面中,2和3都是圍繞1來進(jìn)行操作的,三個(gè)區(qū)域之間的耦合性其實(shí)是非常高的,所以常規(guī)的做法就是三個(gè)部分分為三個(gè)UserControl,并且同時(shí)綁定到一個(gè)ViewModel中,這樣整個(gè)層次也就比較清晰了,缺點(diǎn)是一個(gè)ViewModel中代碼太多,職責(zé)非常大,所以在這個(gè)DEMO中嘗試將三個(gè)部分分開,三個(gè)ViewModel來操作三個(gè)View里面的內(nèi)容,整個(gè)實(shí)現(xiàn)下來其實(shí)也有一些不足之處,那就是容易將問題復(fù)雜化,很多在一個(gè)類中就能夠完成的工作,最終要通過各種類與類之間的耦合來完成,所以通過這個(gè)DEMO希望自己能夠多一些思考,從而在軟件的設(shè)計(jì)中能夠再多一些經(jīng)驗(yàn),能夠把握好軟件粒度的問題,下面就軟件的具體內(nèi)容來深入分析一下。
第一部分:FileList
這個(gè)部分是整個(gè)文件和文件夾的顯示部分,再三權(quán)衡下,決定采用自定義DataGrid的方式來展現(xiàn)整個(gè)部分。
<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="點(diǎn)擊選中當(dāng)前對(duì)象"></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> 個(gè)項(xiàng)目</Run> <Run>(已選中 </Run> <Run Text="{Binding CurrentFileList,Converter={StaticResource CollectionSelectedCountConverter},Mode=OneWay}"></Run> <Run> 個(gè)項(xiàng)目)</Run> </TextBlock> </StackPanel> <TextBlock Text="該文件為空" Visibility="{Binding CurrentFileList.Count,Converter={StaticResource CountToVisibilityConverter}}" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock> </Grid> </ScrollViewer> </Grid> </UserControl>
這里都是一些常規(guī)的定義,這里就不再贅述,DataGrid樣式是引用Themes文件夾下面的CustomDataGrid的樣式,這里面也包括自定義的ScrollViewer樣式和ScrollBar的樣式,讀者也可以進(jìn)行思考,另外需要注意設(shè)置下面的幾個(gè)屬性。
1 IsReadOnly="True"這個(gè)屬性能夠保證在鼠標(biāo)點(diǎn)擊時(shí)候,不再顯示內(nèi)部的TextBox,從而使使用者不能夠隨意進(jìn)行編輯。
2 GridLinesVisibility="None" 這個(gè)能夠使整個(gè)DataGrid不再顯示分割線,從而使其樣式更加接近Windows的原生樣式。
3 CanUserAddRows="False" 這個(gè)屬性也非常重要,不然在整個(gè)顯示區(qū)域的下面會(huì)多出一行,當(dāng)用戶點(diǎn)擊它的時(shí)候會(huì)增加行。
4 AutoGenerateColumns="False"這個(gè)就比較熟悉了,一般不讓其自動(dòng)增加列。
5 SelectionUnit=“FullRow” 表示鼠標(biāo)點(diǎn)擊時(shí)選擇的單位是整行,而不是其中的單元格或者其他,關(guān)于其它的幾個(gè)枚舉值,讀者也可查閱相關(guān)了解。
6 SelectionMode=“Extended”允許多選,當(dāng)按下鼠標(biāo)的Ctrl鍵進(jìn)行點(diǎn)擊的時(shí)候能夠選中多個(gè)對(duì)象。
7 最后一個(gè)就是關(guān)于設(shè)置DataGrid的虛擬化容器了,具體設(shè)置方法是:
<Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True"></Setter> <Setter Property="VirtualizingStackPanel.VirtualizationMode" Value="Recycling" />
WPF中的VirtualizingStackPanel.VirtualizationMode 附加屬性指定 ItemsControl 中的面板如何虛擬化其子項(xiàng)。默認(rèn)情況下,VirtualizingStackPanel 將為每個(gè)可見項(xiàng)創(chuàng)建一個(gè)項(xiàng)容器,并在不再需要時(shí)(比如當(dāng)項(xiàng)滾動(dòng)到視圖之外時(shí))丟棄該容器。當(dāng) ItemsControl 包含多個(gè)項(xiàng)時(shí),創(chuàng)建和廢棄項(xiàng)容器的過程可能會(huì)對(duì)性能產(chǎn)生負(fù)面影響。如果 VirtualizingStackPanel.VirtualizationMode 設(shè)置為 Recycling,VirtualizingStackPanel 將重用項(xiàng)容器,而不是每次都創(chuàng)建新的項(xiàng)容器,這個(gè)是摘錄自MSDN的相關(guān)資料,由于我們加載的DataGrid的項(xiàng)不是很多,如果足夠多的情況下,效果可能會(huì)更加明顯,關(guān)于更多的“虛擬化”技術(shù)可以參考更多的資料。
這里我們需要重點(diǎn)關(guān)注的是當(dāng)我們雙擊DataGridRow時(shí)會(huì)打開對(duì)應(yīng)的子文件夾,同時(shí)單擊時(shí)會(huì)選中當(dāng)前的DataGridRow,這里關(guān)于事件的綁定我們使用的不是System.Windows.Interactivity這種方式來綁定事件的,這里我們通過自定義一個(gè)附加屬性來實(shí)現(xiàn)的,這里以鼠標(biāo)左鍵雙擊為例來進(jìn)行說明。
這里需要進(jìn)行說明的就是在OnMouseDoubleClick中,我們通過當(dāng)前鼠標(biāo)的點(diǎn)擊的Point來查找最終的DataGridRow 然后觸發(fā)綁定的Command事件,在前臺(tái)View中,我們只需要通過綁定到對(duì)應(yīng)的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層中的各種事件提供能了一種新的方式,這個(gè)是我們需要不斷去總結(jié)和分析的地方。
第二部分:Navigation
這一部分是我們的導(dǎo)航欄的部分,通過向前、向后、向上等快捷操作,我們能夠快速切換文件夾,從而使切換路徑變得更加容易,這一部分就是需要著重說一下最近瀏覽項(xiàng)目這個(gè)功能,它能夠保存用戶最近瀏覽的10個(gè)目錄(開發(fā)者自定義),從而方便用戶迅速切換不同的路徑,這個(gè)是通過ToggleButton和Popup這一對(duì)經(jīng)典的組合來實(shí)現(xiàn)的,具體實(shí)現(xiàn)請(qǐng)參考源代碼。這一部分重點(diǎn)來說一下當(dāng)鼠標(biāo)移動(dòng)到不同的位置時(shí),能夠變換綁定的圖標(biāo)是向前還是向后抑或選中狀態(tài),這個(gè)其實(shí)是通過綁定每一個(gè)Model的一個(gè)CurrentDirection來實(shí)現(xiàn)的,這里需要重點(diǎn)掌握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
這個(gè)部分是用來顯示當(dāng)前文件夾路徑,并進(jìn)行快速切換準(zhǔn)備的,這個(gè)部分是一個(gè)組合控件,主要是通過ItemsControl和ToggleButton和Popup來實(shí)現(xiàn)的,這個(gè)里面需要注意的是這里面綁定的命令和方法都是在FileList的ViewModel中定義的,這里為了最大程度的實(shí)現(xiàn)代碼的重用。很多時(shí)候我們會(huì)發(fā)現(xiàn)通過這種方式我們需要能夠隨時(shí)訪問到FileListViewModel中的內(nèi)容,這個(gè)是整個(gè)DEMO中最重要的部分,所以如何才能夠引用到FileListViewModel里面的內(nèi)容呢?
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> /// 當(dāng)前FileList的DataContext對(duì)象 /// </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)); }
通過定義一個(gè)AttachedDataContext對(duì)象,我們能夠?qū)ileListViewModel中定義的屬性分散到各個(gè)ViewModel中,這樣在一定程度上能夠保證避免FileListViewModel中代碼過多同時(shí)職責(zé)過重的問題,但是同時(shí)我們也發(fā)現(xiàn)了,如果彼此之間的耦合過大,采用這種方式會(huì)加重代碼之間的復(fù)雜度,因?yàn)橛袝r(shí)不得不通過Action或者事件等方式來進(jìn)行ViewModel之間的交互和通訊,所以降到這里我們不得不說一些較大較復(fù)雜的項(xiàng)目中使用框架的重要性了,比如Prism亦或是Caliburn.Micro等框架能夠使整個(gè)軟甲架構(gòu)看起來更加清楚和明白,這也是為了更好地增加軟件的模塊化和靈活性。
通過這個(gè)DEMO的分析,我們需要在不斷的實(shí)踐中去總結(jié)這類型的經(jīng)驗(yàn),從而使整個(gè)軟件顯得更加合理,最終使自己能夠真正地對(duì)軟件的架構(gòu)的思想有一個(gè)比較深入的了解。
最后需要整個(gè)Demo的請(qǐng)點(diǎn)擊此處進(jìn)行下載!
以上就是c# WPF實(shí)現(xiàn)Windows資源管理器(附源碼)的詳細(xì)內(nèi)容,更多關(guān)于c# WPF實(shí)現(xiàn)Windows資源管理器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- c# 基于wpf,開發(fā)OFD電子文檔閱讀器
- c# WPF中自定義加載時(shí)實(shí)現(xiàn)帶動(dòng)畫效果的Form和FormItem
- c# WPF中CheckBox樣式的使用總結(jié)
- C# WPF 自定義按鈕的方法
- c# WPF中通過雙擊編輯DataGrid中Cell的示例(附源碼)
- C# WPF Image控件的綁定方法
- c# WPF設(shè)置軟件界面背景為MediaElement并播放視頻
- 在C# WPF下自定義滾動(dòng)條ScrollViewer樣式的操作
- C#中WPF依賴屬性的正確學(xué)習(xí)方法
- C# WPF上位機(jī)實(shí)現(xiàn)和下位機(jī)TCP通訊的方法
- C# WPF使用AForge類庫操作USB攝像頭拍照并保存
- c# wpf使用GMap.NET類庫,實(shí)現(xiàn)地圖軌跡回放
相關(guān)文章
C#實(shí)現(xiàn)Word轉(zhuǎn)換TXT的方法詳解
這篇文章主要為大家詳細(xì)介紹了如何利用C#實(shí)現(xiàn)Word轉(zhuǎn)換TXT的功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C#有一定的幫助,感興趣的小伙伴可以跟隨小編一起了解一下2022-12-12C#無損高質(zhì)量壓縮圖片實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了C#無損高質(zhì)量壓縮圖片的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05c#?使用線程對(duì)串口serialPort進(jìn)行收發(fā)數(shù)據(jù)(四種)
本文主要介紹了c#?使用線程對(duì)串口serialPort進(jìn)行收發(fā)數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07解析美國(guó)東部時(shí)間與北京時(shí)間相互轉(zhuǎn)換的實(shí)現(xiàn)代碼
本篇文章是對(duì)美國(guó)東部時(shí)間與北京時(shí)間相互轉(zhuǎn)換的實(shí)現(xiàn)代碼進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05講解C#面相對(duì)象編程中的類與對(duì)象的特性與概念
這篇文章主要介紹了C#面相對(duì)象編程中的類與對(duì)象的特性與概念,OOP面向?qū)ο笳Z言相對(duì)C語言這樣面相過程的語言來說具有類和對(duì)象以及方法這樣的特性,需要的朋友可以參考下2016-01-01C# 本地函數(shù)與 Lambda 表達(dá)式詳細(xì)介紹
這篇文章主要介紹了C 語言本地函數(shù)與 Lambda 表達(dá)式,,由于 C# 長(zhǎng)期以來一直使用 lambda,因此從差異和相似之處來看本地函數(shù)確實(shí)是有意義的,感興趣的小伙伴可以參考下面文章內(nèi)容2021-09-09