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的请点击此处进行下载!
WPF实现Windows资源管理器(附源码)的更多相关文章
- 【转】Visual Studio团队资源管理器 Git 源码管理工具简单入门
1.1 环境 Visual Studio + GitLab (其他版本同理) 1.2 Git操作过程图解 1.3 常见名词解释 拉取(Pull):将远程版本库合并到本地版本库,相当于(Fetch+Me ...
- Android 音视频深入 十九 使用ijkplayer做个视频播放器(附源码下载)
项目地址https://github.com/979451341/Myijkplayer 前段时候我觉得FFmpeg做个视频播放器好难,虽然播放上没问题,但暂停还有通过拖动进度条来设置播放进度,这些都 ...
- C# VLCPlayer视频播放器(附源码)
VLCPlayer视频播放器. 支持本地播放,支持网络URL.支持全屏,截图. 基于VLCPlayer. 附带基本代码. 下载地址:http://pan.baidu.com/s/1nvjNvID
- 一步步实现windows版ijkplayer系列文章之七——终结篇(附源码)
一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...
- wpf 模拟抖音很火的罗盘时钟,附源码,下载就能跑
wpf 模拟抖音很火的罗盘时钟,附源码 前端时间突然发现,抖音火了个壁纸,就是黑底蕾丝~~~ 错错错,黑底白字的罗盘时钟! 作为程序员的我,也觉得很新颖,所以想空了研究下,这不,空下来了就用wpf, ...
- wpf 模拟3D效果(和手机浏览图片效果相似)(附源码)
原文 wpf 模拟3D效果(和手机浏览图片效果相似)(附源码) pf的3D是一个很有意思的东西,类似于ps的效果,类似于电影动画的效果,因为动画的效果,(对于3D基础的摄像机,光源,之类不介绍,对于依 ...
- 在Winform窗体中使用WPF控件(附源码)
原文:在Winform窗体中使用WPF控件(附源码) 今天是礼拜6,下雨,没有外出,闲暇就写一篇博文讲下如何在Winform中使用WPF控件.原有是我在百度上搜索相关信息无果,遂干脆动手自己实现. W ...
- 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生
[转].NET(C#):浅谈程序集清单资源和RESX资源 目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ...
- (原创)通用查询实现方案(可用于DDD)[附源码] -- 简介
[声明] 写作不易,转载请注明出处(http://www.cnblogs.com/wiseant/p/3985353.html). [系列文章] 通用查询实现方案(可用于DDD)[附源码] -- ...
随机推荐
- vue.js鼠标经过和离开事件 mouseover mouseout
每天学习一点点 编程PDF电子书.视频教程免费下载:http://www.shitanlife.com/code @mouseover="showEwm(1)" @mouseout ...
- P1705 爱与愁过火(背包)
本来是个搜索题,但是自觉的成了背包! 多重用正序,01用逆序. 抽象出来一下,一个物体的体积为ai, 每次装入背包需要bi(在题目中为菜数量)分钟(这个题目只是bi为 1 而已)问在r分钟内,装比n大 ...
- 在 C 代码中嵌入 Python 语句或使用 Python 模块 (Visual Studio 2013 环境设置)
1) 新建一个 内嵌 Python 语句的 C 代码, // This is a test for check insert the Python statements or module in C. ...
- 使用java实现快速排序(挖坑填数法和指针交换法)
快速排序:通过一趟排序,将数据分为两部分,其中一部分中的所有数据比另外一部分的所有数据要小,然后按照此方法,分别对这两部分进行排序,达到最终的排序结果. 每趟排序选取基准元素,比该基准元素大的数据放在 ...
- [Micropython]TPYBoard v202 v102+v202 家庭无线温湿度检测
一.实验器件 1.TPYBoard v102 1块 2.TPYBoard v202 1块 3.Nokia 5110LCD显示屏 1块 4.DHT11温湿度传感器 1个 5.micro USB 数据线 ...
- Java中的hashCode() 和 equals()的若干问题解答
一.hashCode()的作用 哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native int ...
- day93之微信推送
python之微信推送详解 用什么推送 -邮件 -微信推送 -短信推送微信推送 -公众号(不能主动给用户发消息) -认证的公众号:需要营业执照,需要交钱,可以发多篇文章 - ...
- scrapy 爬取糗事百科
安装scrapy conda install scrapy 创建scrapy项目 scrapy startproject qiubai 启动pycharm,发现新增加了qiubai这个目录 在spid ...
- Autofac容器对象实例的几种生命周期类型
实例范围决定了如何在同一服务的请求之间共享实例. 请注意,您应该熟悉生命周期范围的概念,以便更好地理解此处发生的情况. 当请求服务时,Autofac可以返回单个实例(单实例作用域),新实例(每个依赖作 ...
- 为什么大公司一定要使用DevOps?
0 DevOps的意图 究竟什么是DevOps? 要想回答这个问题,首先要明确DevOps这个过程参与的人员是谁?即开发团队和IT运维团队!那么,DevOps的意图是什么呢?即在两个团队之间,建立良好 ...