Skip to content

File Explorer Programming Discussion

Gary edited this page Mar 10, 2015 · 2 revisions

Table of Contents

The ATF File Explorer Sample shows how to adapt controls to data. The folder view in the main window's left panel uses a TreeControl with a TreeControlAdapter, and the file panel on the right uses a System.Windows.Forms.ListView with a ListViewAdapter.

The sample also shows how to use MEF to add an arbitrary number of components to specify file information to display in a ListView.

Programming Overview

This page discusses the custom components added, such as FolderViewer and FileViewer, which control operations in the two panels of the main window. These components operate similarly, both creating controls and adapters that adapt the data to the control's view. The components each define a private view class, such as FileTreeView, that implements interfaces for the adapters to provide capabilities like viewing and selection.

The sample defines an IFileDataExtension interface that provides file and folder information. The sample defines several components that implement and export this interface. FileViewer imports these components and iterates through them, so that it is easy to add an additional file information provider component.

FileExplorer Components

FileExplorer follows the standard WinForms initialization pattern discussed in WinForms Application. In addition to standard ATF components, it adds the following components of its own to the MEF TypeCatalog:

  • FolderViewer: manage a TreeControl to display a folder hierarchy of the computer's "C:" drive. For discussion, see FolderViewer Component.
  • FileViewer: manage a ListView to display the selected folder's contents. For details, see FileViewer Component.
  • File extension components implementing IFileDataExtension, which provides information about files and folders. For the details, see IFileDataExtension Exporter Components. There are three extensions defined:
    • NameDataExtension: extension in FileDataExtensions.cs to display the folder or file name.
    • SizeDataExtension: extension in FileDataExtensions.cs to display the file's size.
    • CreationTimeDataExtension: extension in FileDataExtensions.cs to display folder or file creation time.

FolderViewer Component

The FolderViewer component's constructor creates a TreeControl to display the hierarchical data of the "C:" drive's contents as a tree:

[ImportingConstructor]
public FolderViewer(MainForm mainForm, FileViewer fileViewer)
{
    m_mainForm = mainForm;
    m_fileViewer = fileViewer;

    m_treeControl = new TreeControl();
    m_treeControl.Text = "Folder Viewer";
    m_treeControl.ImageList = ResourceUtil.GetImageList16();
    m_treeControl.SelectionMode = SelectionMode.One;
    m_treeControl.Dock = DockStyle.Fill;
    m_treeControl.Text = "Folder Viewer";
    m_treeControl.Width = 256;

    m_treeControlAdapter = new TreeControlAdapter(m_treeControl);
    m_fileTreeView = new FileTreeView();
    m_fileTreeView.SelectionChanged += new EventHandler(fileTreeView_SelectionChanged);
    m_treeControlAdapter.TreeView = m_fileTreeView;
}

The FileViewer parameter imported is a FileViewer object, discussed in FileViewer Component.

The constructor sets various properties of the new TreeControl. For example, the ImageList property is set to a list of image resources, so that an image can be used for folders in the TreeControl. The SelectionMode property is set so that only one item can be selected at a time.

After setting the TreeControl's properties, the constructor creates a TreeControlAdapter for that tree, which adapts the TreeControl to a data context that implements ITreeView. Finally, a new FileTreeView is created, which adapts the "C:" drive's contents to a tree of selectable items.

The component's IInitializable.Initialize() method sets the left panel of the main window to the newly created TreeControl:

void IInitializable.Initialize()
{
    m_mainForm.SplitContainer.Panel1.Controls.Add(m_treeControl);
}

The handler for the SelectionChanged event sets the Path property of the imported FileViewer component to the path of the selected folder:

private void fileTreeView_SelectionChanged(object sender, EventArgs e)
{
    Sce.Atf.Path<object> lastPath = m_fileTreeView.LastSelected as Sce.Atf.Path<object>;
    if (lastPath != null)
    {
        FileSystemInfo info = lastPath.Last as FileSystemInfo;
        if (info is DirectoryInfo)
            m_fileViewer.Path = info.FullName;
    }
}

FileViewer's Path determines what is displayed in the right panel of the main window, which is discussed in FileViewer Component.

TreeControlAdapter Class

FolderViewer uses this TreeControlAdapter constructor:

public TreeControlAdapter(TreeControl treeControl)
    : this(treeControl, null)
{
}

This constructor invokes the actual constructor used:

public TreeControlAdapter(TreeControl treeControl, IEqualityComparer<object> comparer)
{
    m_treeControl = treeControl;
    m_itemToNodeMap = new Multimap<object, TreeControl.Node>(comparer);

    m_treeControl.MouseDown += treeControl_MouseDown;
    m_treeControl.MouseUp += treeControl_MouseUp;
    m_treeControl.DragOver += treeControl_DragOver;
    m_treeControl.DragDrop += treeControl_DragDrop;

    m_treeControl.NodeExpandedChanged += treeControl_NodeExpandedChanged;
    m_treeControl.NodeSelectedChanged += treeControl_NodeSelectedChanged;
    m_treeControl.SelectionChanging += treeControl_SelectionChanging;
    m_treeControl.SelectionChanged += treeControl_SelectionChanged;
}

This code subscribes to event handlers for mouse operations, drag and drop, node expansion/contraction, and selection changes. This sample application uses all these events, except the drag and drop ones.

TreeControlAdapter has a TreeView property, shown here with its comments:

/// <summary>
/// Gets or sets the tree displayed in the control. When setting, consider having the
/// ITreeView object also implement IItemView, IObservableContext, IValidationContext,
/// ISelectionContext, IInstancingContext, and IHierarchicalInsertionContext.</summary>
public ITreeView TreeView
...

Recall that FolderViewer's constructor sets this TreeView property to the newly created FileTreeView, which implements ITreeView:

m_fileTreeView = new FileTreeView();
m_fileTreeView.SelectionChanged += new EventHandler(fileTreeView_SelectionChanged);
m_treeControlAdapter.TreeView = m_fileTreeView;

It also subscribes the FileTreeView to the SelectionChanged event, whose handler was shown previously.

FileTreeView Class

FileTreeView is a private class that implements ITreeView for TreeControlAdapter:

private class FileTreeView : ITreeView, IItemView, ISelectionContext

Its constructor sets up a new Selection object:

public FileTreeView()
{
    m_selection = new Selection<object>();
    m_selection.Changed += new EventHandler(selection_Changed);

    // suppress compiler warning
    if (SelectionChanging == null) return;
}

Selected items in the tree represent folders, and Selection provides what's needed to select these items. FileTreeView works mainly with System.IO.DirectoryInfo objects, which encapsulate information for a directory.

FileTreeView implements several interfaces.

ITreeView Interface

ITreeView generalizes a tree to a root with child objects:

  • Root: property for the root object of the tree view.
  • GetChildren(): get children of the given parent.
The ITreeView implementation works with file system objects, as seen in the next code example. Root simply returns directory information for the "C:" drive, because m_path is initialized to "C:\". GetChildren() gets the files and folders in a parent folder.
public object Root
{
    get { return new DirectoryInfo(m_path); }
}

public IEnumerable<object> GetChildren(object parent)
{
    IEnumerable<object> result = null;
    DirectoryInfo directoryInfo = parent as DirectoryInfo;
    if (directoryInfo != null)
        result = GetSubDirectories(directoryInfo); //may return null

    if (result == null)
        return EmptyEnumerable<object>.Instance;
    return result;
}

The private method GetSubDirectories() gets the contents of a directory, as an array of DirectoryInfo objects.

IItemView Interface

IItemView continues the work with the file system. Its GetInfo() method fills out an ItemInfo with information from a DirectoryInfo: the folder name, including whether it's a leaf, that is, whether it contains no files or folders.

public void GetInfo(object item, Sce.Atf.Applications.ItemInfo info)
{
    DirectoryInfo directoryInfo = item as DirectoryInfo;
    if (directoryInfo != null)
    {
        info.Label = directoryInfo.Name;
        info.ImageIndex = info.GetImageList().Images.IndexOfKey(Resources.ComputerImage);
        DirectoryInfo[] directories = GetSubDirectories(directoryInfo);

        info.IsLeaf =
            directories != null &&
            directories.Length == 0;
    }
}

ItemInfo's ImageIndex property is set to display a computer icon for each item in the tree view, using standard ATF image resources from the Resources class.

ISelectionContext Interface

ISelectionContext is the general interface for selections. It's very easy to implement here, because the constructor created a Selection object:

m_selection = new Selection<object>();

This interface implementation uses the Selection class's methods to provide everything ISelectionContext needs. For instance, GetSelection<T>() does this:

public IEnumerable<T> GetSelection<T>()
            where T : class
{
    return m_selection.AsIEnumerable<T>();
}

This interface also includes the SelectionChanged event, which is raised after the selection changes. This is important, because changing the selected item in the tree representation of the "C:" drive determines what's displayed in the right panel of the main window. As previously mentioned, this event's handler places the selected folder's path in FileViewer's Path property, which governs which folder's contents appear in FileViewer's ListView in the main window's right panel.

FileViewer Component

FileViewer displays the contents of a folder using a ListView control and parallels FolderViewer in its operation. FileViewer's constructor does basically the same things as FolderViewer's constructor: it creates a control, configures it, and then creates an adapter for the control. FileViewer creates a ListView rather than a TreeControl:

[ImportingConstructor]
public FileViewer(MainForm mainForm)
{
    m_mainForm = mainForm;

    // create a standard WinForms ListView control
    m_listView = new ListView();
    m_listView.Dock = DockStyle.Fill;
    m_listView.Text = "File Viewer";
    m_listView.BackColor = SystemColors.Window;
    m_listView.SmallImageList = ResourceUtil.GetImageList16();
    m_listView.AllowColumnReorder = true;

    // create an adapter to drive the ListView control
    m_listViewAdapter = new ListViewAdapter(m_listView);

    m_fileListView = new FileListView();
}

The new ListView's SmallImageList property is set to display small icons for files and folders in the ListView.

After the ListView's properties are set, a ListViewAdapter for that list is created. A ListViewAdapter adapts a ListView to a data context that implements IListView. Finally, a new FileListView is created, which adapts a directory to an observable list of items, that is, the folders and files in the selected folder in the TreeControl.

The component's IInitializable.Initialize() method accomplishes the following:

  • Makes a list of all the IFileDataExtension providers, which were imported into the m_extensions field:
[ImportMany] // gets all file data extensions
private IEnumerable<Lazy<IFileDataExtension>> m_extensions = null;
  • Sets the ListViewAdapter's ListView property to the new FileListView.
  • Adds the newly created ListView to the right panel of the main window.
void IInitializable.Initialize()
{
    // pass all file data extensions to adapter
    List<IFileDataExtension> list = new List<IFileDataExtension>();
    foreach (Lazy<IFileDataExtension> extension in m_extensions)
        list.Add(extension.Value);

    m_fileListView.FileDataExtensions = list.ToArray();

    // set the adapter's ListView to an adapter that returns directory contents
    m_listViewAdapter.ListView = m_fileListView;

    m_mainForm.SplitContainer.Panel2.Controls.Add(m_listView);

    SettingsServices.RegisterSettings(
        m_settingsService,
        this,
        new BoundPropertyDescriptor(this, () => ListViewSettings, "ListViewSettings", null, null));
}

FileViewer's Path property gets or sets the path to the folder whose contents are displayed in the ListView:

public string Path
{
    get { return m_fileListView.Path; }
    set
    {
        m_fileListView.Path = value;
        m_mainForm.Text = value;
    }
}

Note that this property simply accesses the Path property of the FileListView object. As discussed previously, FileViewer's Path property is set to the currently selected file path in the tree view when the selection changes by the FolderViewer's SelectionChanged event handler.

ListViewAdapter Class

FileViewer uses this ListViewAdapter constructor:

public ListViewAdapter(ListView listView)
{
    m_control = listView;
    m_control.View = View.Details;
    m_control.FullRowSelect = true;
    m_control.HideSelection = false;

    // default to allow sorting
    m_allowSorting = true;
    m_control.ListViewItemSorter = new ListViewItemSorter(m_control);

    m_control.AfterLabelEdit += control_AfterLabelEdit;
    m_control.ColumnWidthChanged += control_ColumnWidthChanged;
    m_control.MouseDown += control_MouseDown;
    m_control.MouseUp += control_MouseUp;
    m_control.DragOver += control_DragOver;
    m_control.DragDrop += control_DragDrop;
}

This code subscribes to event handlers for label and column changes, mouse operations, and drag and drop. This sample uses all these events, except the drag and drop ones.

ListViewAdapter has a ListView property, which gets or sets the IListView object for the list data. FileViewer's IInitializable.Initialize() method sets this property to the FileListView object, which implements IListView:

// set the adapter's ListView to an adapter that returns directory contents
m_listViewAdapter.ListView = m_fileListView;

FileListView is analogous to the FileTreeView that TreeControlAdapter uses and serves much the same function: to handle data in the view, a list view in this case — rather than a tree view.

FileListView Class

FileListView is a private class that implements IListView for the ListViewAdapter:

private class FileListView : IListView, IItemView, IObservableContext

This class does not implement selection, but it does implement several other interfaces. Although it implements IObservableContext, which contains events, it does not raise these events.

IListView Interface

IListView is the main interface used with ListView controls and abstracts an enumeration of objects that can be used as tags, one per row, in a list control, along with corresponding user-readable column names at the top.

The ColumnNames property provides an array of strings for the column names:

public string[] ColumnNames
{
    get
    {
        string[] result = new string[m_fileDataExtensions.Length];
        for (int i = 0; i < result.Length; i++)
            result[i] = m_fileDataExtensions[i].ColumnName;
        return result;
    }
}

ColumnNames enumerates the IFileDataExtension providers to get the ColumnName property for each one. Recall how m_fileDataExtensions was set up to contain all the IFileDataExtension providers:

List<IFileDataExtension> list = new List<IFileDataExtension>();
foreach (Lazy<IFileDataExtension> extension in m_extensions)
    list.Add(extension.Value);

m_fileListView.FileDataExtensions = list.ToArray();

And m_extensions is the field into which all the IFileDataExtension providers are imported:

[ImportMany] // gets all file data extensions
private IEnumerable<Lazy<IFileDataExtension>> m_extensions = null;

For further explanation of how IFileDataExtension is used, see IFileDataExtension Exporter Components.

The IListView.Items property contains all the items that the ListView displays. That is, it displays all the files and folders in the folder selected in the TreeControl in the left panel of the main window. Therefore, Items uses the m_path field, which, as previously noted, contains the currently selected file path in the tree view. Items uses DirectoryInfo methods to get lists of all the directories and files in the path specified by m_path:

public IEnumerable<object> Items
{
    get
    {
        if (m_path == null ||
            !Directory.Exists(m_path))
        {
            return EmptyEnumerable<object>.Instance;
        }

        DirectoryInfo directory = new DirectoryInfo(m_path);
        DirectoryInfo[] subDirectories = null;
        try
        {
            subDirectories = directory.GetDirectories();
        }
        catch
        {
        }
        if (subDirectories == null)
            subDirectories = new DirectoryInfo[0];

        FileInfo[] files = null;
        try
        {
            files = directory.GetFiles();
        }
        catch
        {
        }
        if (files == null)
            files = new FileInfo[0];

        List<object> children = new List<object>(subDirectories.Length + files.Length);
        children.AddRange(subDirectories);
        children.AddRange(files);
        return children;
    }
}

IItemView Interface

IItemView gets information about individual file or folder items to display in the view. This interface was also implemented by FileTreeView to display information about folders in the tree view.

GetInfo() fills out a given ItemInfo object to display item's information in the ListView:

public void GetInfo(object item, ItemInfo info)
{
    // set the first column info (name)
    FileSystemInfo fileSystemInfo = item as FileSystemInfo;
    if (fileSystemInfo is DirectoryInfo)
    {
        info.Label = fileSystemInfo.Name;
        info.ImageIndex = info.GetImageList().Images.IndexOfKey(Resources.FolderImage);
    }
    else if (fileSystemInfo is FileInfo)
    {
        info.Label = fileSystemInfo.Name;
        info.ImageIndex = info.GetImageList().Images.IndexOfKey(Resources.DocumentImage);
        info.IsLeaf = true;
    }

    // set the 2nd and 3nd columns info (size and creation time)
    info.Properties = new string[m_fileDataExtensions.Length-1];
    for (int i = 0; i < info.Properties.Length; i++)
        info.Properties[i] = m_fileDataExtensions[i+1].GetValue(fileSystemInfo);
}

GetInfo() casts the item to FileSystemInfo and determines whether it specifies information for a folder or file. It obtains the item's name and the appropriate image, depending on whether its a folder or file. Similarly to the implementation of IListView.ColumnNames, this property iterates the IFileDataExtension providers in m_fileDataExtensions to get file or folder information and then add it to the ItemInfo's Properties array.

IFileDataExtension Provider Components

This sample creates the IFileDataExtension interface to define components that provide file information for a given FileSystemInfo object. These components get the folder and file information displayed in FileViewer's ListView. As will be seen, you can easily add similar components to display additional information about folders and files.

IFileDataExtension Interface

IFileDataExtension defines two items in its interface:

  • string ColumnName property: get a string for the column name in the ListView for the file information. This information is obtained for FileListView's IListView.ColumnNames property, as described in FileListView Class.
  • string GetValue(FileSystemInfo fileSystemInfo) method: get a string value describing file information, such as a size or creation time, from a given FileSystemInfo. FileListView's IItemView.GetInfo() method uses this to get the information displayed in the ListView.

IFileDataExtension Exporter Components

The file FileDataExtension.cs contains several components that export IFileDataExtension. These components are imported by FileViewer, as previously described.

Note that FileViewer imports these components using [ImportMany], so that an arbitrary number of these components can be imported. In addition, FileViewer iterates through the list of imported IFileDataExtension providers, so that it is easy to add an additional provider.

All these components operate pretty much the same. For example, here's the CreationTimeDataExtension component that obtains the creation time:

[Export(typeof(IFileDataExtension))]
public class CreationTimeDataExtension : IFileDataExtension
{
    /// <summary>
    /// Gets the name of the column</summary>
    public string ColumnName
    {
        get { return "Creation Time"; }
    }

    /// <summary>
    /// Gets the value for the column and given file system item</summary>
    /// <param name="fileSystemInfo">Info describing the file or directory</param>
    /// <returns>Value for the column and given file system item</returns>
    public string GetValue(FileSystemInfo fileSystemInfo)
    {
        return fileSystemInfo.CreationTime.ToString(CultureInfo.CurrentCulture);
    }
}

ColumnName simply provides a string for the name to be used as the column label in the ListView.

GetValue() extracts the creation time from the given FileSystemInfo, returning it as a string.

Topics in this section

Clone this wiki locally