FileExplorer is a WPF control that allow user to browse
filesystem contents. FileExplorer1 (ver1) consist of a
directory tree and file list, although it's ver1 it supports shell
directory, context menu, drag and drop, multi-select in file
list.
However, in ver1, the base currency is defined as
System.IO.FileSystemInfoEx (Ex), that means the control
communicate using a DirectoryInfoEx. As I maintained both Ex
and COFE(ExA), I had to maintain two copies of the source code,
which create additional workloads and inconsistancy. The
original design also disallow the implementation of other File
System entities.
As Windows 8 metro application will disallow Win32, and will
require to use Windows Runtime (WinRT) to access the file system,
a new FileExplorer has to be coded to work with Metro or other
kind of file systems.
ver2 is a rewrite of the controls using the same
Model-View-ViewModel (MVVM) pattern, and solved some of the issues
in ver1.
Then you can use the control in xaml:
<uc:Explorer2 x:Name="explr" Style="{DynamicResource qz_Explorer_Style}" Background="{Binding Background, ElementName=mainWindow}" />and set the code-behind view model :
explr.DataContext = new ExExplorerViewModel(new ExProfile());
(DirTreeViewModel.LookupChild() method) public override TreeViewItemViewModel LookupChild(IFileSystemInfoExA node, FuncThe problem is that:cancelCheck) { ... foreach (TreeViewItemViewModel subModel in tvm) { if (subModel.Equals(node)) return subModel; else if (subModel is DirTreeViewModel) { DirTreeViewModel subDirModel = subModel as DirTreeViewModel; IDirectoryInfoExA subDir = subDirModel.EmbeddedDirModel.EmbeddedDirEntry; if (node.Equals(subDir)) return subDirModel; else if (subDirModel.CanLookUp && COFETools.HasParent(node, subDir)) return subDirModel.LookupChild(node, cancelCheck); } ... }
public virtual void PlaceBounty(EntryModelThe subdir's PlaceBouny() method notify them a new bounty is available, so they will perform a lookup:bountyModel) { if (bountyModel is DirectoryModel ) { if (SelectedNavViewModel != null && SelectedNavViewModel.EmbeddedDirModel.EmbeddedDir.Equals(bountyModel)) { //False alarm, already Selected SelectedNavViewModel.IsSelected = true; return; } else if (SelectedNavViewModel != null && SelectedNavViewModel.EmbeddedDirModel.HasChild(bountyModel.EmbeddedEntry)) { //Fast mode, item is subentry of current selected Bounty = (DirectoryModel )bountyModel; SelectedNavViewModel.IsSelected = false; SelectedNavViewModel.PlaceBounty(); } else { //Slow mode, iterate from root if (SelectedNavViewModel != null) SelectedNavViewModel.IsSelected = false; Bounty = (DirectoryModel )bountyModel; foreach (NavigationItemViewModel subDir in _subDirectories.OrderBy((nivm) => { return -nivm.EmbeddedEntryModel.CustomPosition; })) if (subDir.EmbeddedDirModel.EqualsOrHasChild(bountyModel)) { subDir.PlaceBounty(); break; } else subDir.CollapseAll(); } } }
public void PlaceBounty() { if (_rootModel == null || _rootModel.Bounty == null || !IsDirectory || _isSeparator) return; if (EmbeddedDirModel.EqualsOrHasChild(_rootModel.Bounty)) { if (EmbeddedDirModel.Equals(_rootModel.Bounty)) { //Exchange bounty if cached. if (!EmbeddedDirModel.IsCached && _rootModel.Bounty.IsCached) _embeddedDirModel = _rootModel.Bounty; _rootModel.RequestBountyReward(this); } else { IsSelected = false; if (_isInited) { IsExpanded = true; foreach (NavigationItemViewModelsubDirVM in _subDirectories.OrderBy((nivm) => { return -nivm.EmbeddedEntryModel.CustomPosition; })) if (subDirVM.EmbeddedDirModel.EqualsOrHasChild(_rootModel.Bounty)) { subDirVM.PlaceBounty(); //Tell sub-directory bounty is up. break; } else subDirVM.CollapseAll(); //NOYB, collapse! } else { //Let greed does all the work.... IsExpanded = true; } } } else IsSelected = false; }
public class EntryModel<FI, DI, FSI> { ... }where FI means FileInfo (non-container) , DI means DirectoryInfo (container) and FSI means FileSystemInfo (common ancestor for FI/DI).
public class ExFileModel: FileModel<FileInfoEx,DirectoryInfoEx,FileSystemInfoEx> { .... } public class ExDirectoryModel: DirectoryModel<FileInfoEx,DirectoryInfoEx,FileSystemInfoEx> { .... }In the case of COFE, it's IFileInfo, IDirectoryInfo and IFileSystemInfo.
public class ExAFileModel: FileModel<IFileInfo,IDirectoryInfo,IFileSystemInfo> { .... } public class ExADirectoryModel: DirectoryModel<IFileInfo,IDirectoryInfo,IFileSystemInfo> { .... }
NavigationRootVM (Directory tree root), NavigationItemVM
(Drectory tree item) and DndDirectoryViewerVM (File list)
implements ISupportDrag<EntryM> and
ISupportDrop<EntryM>, which contain drag and drop related
methods, like SelectedItems (items being dragged),
CurrentDropTarget (to know the destination) and Drop()
method (initialize the Dnd).
A DragDropHelper is a static class, once registered, will look
for the interfaces (ISupportDrag and ISupportDrop) in the View
Models (from the control's DataContext), and uses the methods to
facilitate Drag and Drop.
FileDragDropHelper is used to handle File based Drag and Drop,
which uses the FileDrop from the incoming DataObject. To
improve performance, FileDragDropHelper also store the dragging
item in a private property named _dataObj, so inter-application
drag and drop is done based on that local variable.
(_dataObj is a VirtualDataObject, it delay the construction of the
files till they are dropped).
To use FileDragDropHelper, one has to call the
ExplorerVM.RegisterDragAndDrop() method, and override the
Profile.GetSupportedAddAction() method, which is used to
determine if the source (array of FI) and be added to the target
(a DI), you can return an AddActions enum (All, Move, Copy, Link,
None).
If the AddActions is not None, DragDropHelper, will call Profile.Put() method, which calls Profile.Copy() and Link() method.
The input section of Navigation bar display suggestion based on
input, to enable this feature it uses the Profile.Lookup() method:
The following examples return the typed path concated with
"_FileExplorer2".
public override IEnumerable<Suggestion> Lookup(string lookupText) { yield return new Suggestion(lookupText + "_FileExplorer2"); }
You can implement your own search mechanism here (e.g. Windows
Search).
Notification bar uses profile.GetMetadata() method to obtain
metadata of one or multiple EntryModels. GetMetadata()
return a IEnumerable of GenericMetadataM.
GenericMetadataMlet you specify a type, different type will be
displayed differently:
Type |
Display as |
int,uint,long,ulong,float,double |
String |
short |
Percentage bar |
DateTime |
Format as DateTime String |
string |
String |
string[] |
Comma separated string |
The type is identified by MetadataVM (which is constructed by using the ToViewModel() method), using the IsDateTime/Number/Percent/String/StringArray properties. Then this property is accessed by Statusbar's DataTemplate (Statusbar.xaml) to determine what to show.
EntryMetadaM is inherited from GenericMetadataM, which specify
that the metadata is related to one or more EntryMs, the following
code construct the statusbar item shown above.
//Without the 4th parameter, so no header, and occupy whole line.
yield return new EntryMetadataModel<String, FileInfoEx, DirectoryInfoEx, FileSystemInfoEx>(
appliedModels,"ddddd (1).txt", "Key_Label");
//With the 4th parameter
yield return new EntryMetadataModel<String, FileInfoEx, DirectoryInfoEx, FileSystemInfoEx>(
appliedModels,UITools.SizeInK((ulong)model.Length), "Key_Size", "Selected size");
//If the generic type is short, display as percentage progress bar.
yield return new EntryMetadataModel<short, FileInfoEx, DirectoryInfoEx, FileSystemInfoEx>(
appliedModels, 13, "Key_Percent", "13 Percent");
//If the generic type is DateTime, display as datetime.
yield return new EntryMetadataModel<DateTime, FileInfoEx, DirectoryInfoEx, FileSystemInfoEx>(
appliedModels, DateTime.Now, "Key_Now", "Now");
Notification bar display NotificationItemVM at the top of the
Statusbar, these NotificationItemVM are generated by
NotificationSourceVM. NotificationItemVM have a list of
properties for customization:
CanDoWork |
If true, double click the
NotificationItem will execute DoWork() method. |
IsProgressShown |
Circular progress bar on the
left side of a NotificationItem, use PercentCompleted to set
it's progress. |
Icon |
Bitmap of the item. |
Header |
Header text. |
HintMessage / HintTitle |
Shown when mouse over the
NotificationItem. |
Priority |
Determine the position of an
item. |
GetCommands() method. |
Return a list of commands (in
CommandModel), which shown in dropdown menu. |
One can define a NotificationSourceM, which implements
GetNotificationItems() and NotifyItemUpdate() methods.
These NotificationSourceM can be returned in the
Profile.GetNotificationSources() method.
public override IEnumerable<NotificationSourceModel> GetNotificationSources()
{
yield return ExWorkNotificationSourceModel.Instance; }
Each button (include the dropdown) on the toolbars are actually a
CommandModel in the code. When selection changes, toolbar calls
the Profile.GetCommands() method.
CommandModel is an abstract class, there are a number of usable
derived class (see the diagram), the model that derived from
DirectoryCommandModel have IsDirectory set to true, which will
shown on the toolbar as a dropdown (or sub-menu if it's already in
a dropdown).
The following sample inserted a close button in the main menu:
public override IEnumerable<CommandModel>
GetCommands(EntryModel<FileInfoEx, DirectoryInfoEx,
FileSystemInfoEx>[] appliedModels)
{
yield return new
GenericCommandModel(ApplicationCommands.Close);
}
Noted that organize, toggle preview and viewmode dropdown is
predefine as default, if you want to remove them you will have to
modify DirectoryViewerVM.getActionModel() and remove them
manually.