windowsforms-403431-controls-and-libraries-messages-notifications-and-dialogs-custom-folder-browsers.md
The DevExpress WinForms suite ships with skinable counterparts for default WinForms file/folder dialogs: XtraFolderBrowserDialog, XtraOpenFileDialog, and XtraSaveFileDialog. The FileExplorerAssistant component allows you to embed similar functionality in a form or UserControl, and build custom file/folder explorers that can navigate local storage and/or virtual node hierarchy.
This section explains core FileExplorerAssistant concepts and demonstrates how to build the standard file/folder browser similar to Windows Explorer.
Place the FileExplorerAssistant component on a form or UserControl, and invoke its smart-tag menu.
Click the “Add…” actions to add all three browser components:
All parts are default DevExpress controls with FileExplorerAssistant extensions attached to them. Extensions customize the appearance and behavior of controls, and allow these controls to communicate with their parent FileExplorerAssistant component.
Extensions include:
GridControl)TreeList)BreadcrumbEdit)PanelControl)When you click an “Add…” smart-tag menu action, the component creates the corresponding control-extension pair. If needed, you can click the “Edit Extensions” smart-tag menu action to create standalone extensions and bind them to controls on a form or UserControl.
Note
The FileExplorerAssistant loads default settings for attached controls and overrides their settings specified at design time. Specify properties in the Form.Load event handler:
private void Form1_Load(object sender, EventArgs e) {
gridView1.OptionsSelection.EnableAppearanceHotTrackedRow = DevExpress.Utils.DefaultBoolean.False;
}
Private Sub Form1_Load(sender As Object, e As EventArgs)
gridView1.OptionsSelection.EnableAppearanceHotTrackedRow = DevExpress.Utils.DefaultBoolean.False
End Sub
The TreeList Extension has the RootNodes collection that specifies the list of top-level nodes. Initially, this collection has only one node — the EnvironmentSpecialFolderNode that allows users to browse one of the special Windows folders: Desktop, My Documents, Program Files, Fonts, and others. The initial value of this node is “Desktop”, which allows users to browse the entire local storage of the current PC.
You can modify this collection to add other nodes:
PathNode points to a single local storage folder or drive. The path to this object is stored in the node’s Path property.
GuidNode allows you to add special folders that have GUIDs.
VirtualFolderNode and VirtualItemNode are custom nodes. You can specify their title and image, and VirtualFolderNodes can be populated with child nodes. Use these nodes to create custom entries that are not present on local storage.
The two core settings of the GridControl extension are:
CurrentPath is the currently browsed directory.
Integrates a preview pane. With it, users can preview Office-related documents (e.g., DOCX, XLSX, CSV), PDFs, HTML files, text files, and images before opening the actual file.
Extensions do not communicate with each other. For example, if a user selects a node inside a TreeList, the Data Grid does not reflect this navigation. You need to implement the communication between extensions based on the structure of your custom folder explorer.
A “navigation binding” is an object of the DevExpress.XtraDialogs.NavigationBinding class. These objects have two properties:
Source — specifies a control whose navigation is tracked.Target — specifies a control that should replicate the Source control navigation.When stored inside the FileExplorerAssistant.NavigationBindings collection, navigation bindings transmit navigation actions from one extension owned by this FileExplorerAssistant component to another.
You can add navigation bindings at design time (click the ellipsis button next to the “NavigationBindings” property in the Visual Studio Property Grid) or in code. At design time, the Collection Editor dialog checks whether both Target and Source properties are set, and highlights bindings that are missing either of these settings.
You can also edit navigation bindings in code:
NavigationBinding nbGridToBreadcrumb = new NavigationBinding();
nbGridToBreadcrumb.Source = gridControlExtension1;
nbGridToBreadcrumb.Target = breadCrumbExtension1;
fileExplorerAssistant1.NavigationBindings.Add(nbGridToBreadcrumb);
Dim nbGridToBreadcrumb As New NavigationBinding()
nbGridToBreadcrumb.Source = gridControlExtension1
nbGridToBreadcrumb.Target = breadCrumbExtension1
fileExplorerAssistant1.NavigationBindings.Add(nbGridToBreadcrumb)
The following example demonstrates how to bind a Preview Panel extension to Grid Control extension:
var gridExtension = assistant.Attach(grid, x => {
x.CurrentPath = initialPath;
});
var previewExtension = assistant.Attach(panel);
assistant.NavigationBindings.Add(new NavigationBinding() { Source = gridExtension, Target = previewExtension });
Dim gridExtension = assistant.Attach(grid, Sub(x) x.CurrentPath = initialPath)
Dim previewExtension = assistant.Attach(panel)
assistant.NavigationBindings.Add(New NavigationBinding() With {.Source = gridExtension, .Target = previewExtension})
Another way to connect extensions is to handle the CurrentItemChanged event available for every FileExplorerAssistant extension. This event fires every time a user selects a different item within a control. Handle CurrentItemChanged if you need to implement a custom extension communication logic that cannot be implemented with navigation bindings.
private void treeListExtension1_CurrentItemChanged(object sender, CurrentItemChangedEventArgs e) {
// Update Data Grid and Breadcrumb when the Tree List selection changes
gridControlExtension1.SetCurrentItem(e.CurrentItem);
breadCrumbExtension1.SetCurrentItem(e.CurrentItem);
}
Private Sub treeListExtension1_CurrentItemChanged(ByVal sender As Object, ByVal e As CurrentItemChangedEventArgs)
' Update Data Grid and Breadcrumb when the Tree List selection changes
gridControlExtension1.SetCurrentItem(e.CurrentItem)
breadCrumbExtension1.SetCurrentItem(e.CurrentItem)
End Sub
FileExplorerAssistant extensions expose an API that allows you to manage files and folders. This section demonstrates how to use this API to implement action buttons similar to those seen in the “Two-Panel File Manager” DevExpress demo.
Run Demo: Two-Panel File Manager, XtraDialogs Demo
DoDefaultAction — navigates inside the selected folder, or opens the selected file in a default program associated with the file’s extension (Notepad for .txt files, Photos for .png files, and so on).
Rename — renames the selected file or folder.
CopySelectedItem and MoveSelectedItem — copy or move the selected file or folder to the location specified by the method’s parameter. In the code below the selected file is copied to the currently open folder of another Grid.
CreateNewFolder — creates a new directory inside the currently browsed folder.
GoBack, GoForward, GoUp — navigation methods.
Properties that start with “Can” return whether the corresponding action can be executed for the current folder or file. For instance, if a user browses the SpecialEnvironmentFolder.Desktop, they cannot navigate to the parent folder (the GoUp method is not available), and the CanGoUp property returns false.
SetCurrentPath and SetCurrentSpecialFolder — allow you to browse a specific folder or special Windows folder.
The FileExplorerAssistant component allows you to create solutions that browse not only folders on local storage, but also virtual (custom) hierarchies.
Run Demo: Car Rental Catalogue, XtraDialogs Demo
To create a custom hierarchy for the Tree List control, populate the RootNodes collection of the control’s related extension with VirtualFolderNode and VirtualItemNode instances.
VirtualFolderNode class objects serve as “folders”. These objects have the Nodes collection that you can use to populate them with child folders or items.
VirtualFolderNode rootNode = new VirtualFolderNode("Root Node");
VirtualFolderNode childNode = new VirtualFolderNode("Child Node 1");
rootNode.ImageOptions.SvgImage = svgImageCollection1[0];
childNode.ImageOptions.SvgImage = svgImageCollection1[1];
// ...
// Add more child nodes
rootNode.Nodes.Add(childNode);
treeListExtension1.RootNodes.Add(rootNode);
Dim rootNode As VirtualFolderNode = New VirtualFolderNode("Root Node")
Dim childNode As VirtualFolderNode = New VirtualFolderNode("Child Node 1")
rootNode.ImageOptions.SvgImage = svgImageCollection1(0)
childNode.ImageOptions.SvgImage = svgImageCollection1(1)
' ...
' Add more child nodes
rootNode.Nodes.Add(childNode)
treeListExtension1.RootNodes.Add(rootNode)
VirtualItemNode class objects serve as “files”. These objects cannot embed other folders or items, and are not shown inside a Tree List (similar to local storage files).
VirtualFolderNode rootNode = new VirtualFolderNode("Root Node");
rootNode.Nodes.Add(new VirtualFolderNode("Child Node 1"));
((VirtualFolderNode)rootNode.Nodes[0]).Nodes.Add(item1);
Dim rootNode As New VirtualFolderNode("Root Node")
rootNode.Nodes.Add(New VirtualFolderNode("Child Node 1"))
CType(rootNode.Nodes(0), VirtualFolderNode).Nodes.Add(item1)
Combine virtual “folder” and “file” objects to create a custom hierarchy as your needs dictate.
The following example demonstrates how to preview SVG files:
using DevExpress.Dialogs.Core.Items;
using DevExpress.Utils.Svg;
using DevExpress.XtraEditors;
using DevExpress.XtraEditors.Base.Controls.Preview;
using DevExpress.XtraEditors.Controls;
using System.Windows.Forms;
var gridExtension = assistant.Attach(grid, x => {
x.CurrentPath = initialPath;
x.FilterString = "Svg files (*.svg)|*.svg";
});
var previewExtension = assistant.Attach(panel, x => {
x.CustomizePreview += (s, e) => {
if(e.Item.Extension.ToLower() == ".svg")
e.CustomPreviewHandler = new SvgPreviewHandler();
};
});
assistant.NavigationBindings.Add(new NavigationBinding() { Source = gridExtension, Target = previewExtension });
//...
public class SvgPreviewHandler : WinPreviewHandlerBase {
SvgImage svgImage;
public override bool Load(string file, ShellItem shellItem) {
base.Load(file, shellItem);
bool loaded = false;
if(shellItem.Extension.ToLower() == ".svg") {
try {
this.svgImage = SvgImage.FromFile(file);
loaded = true;
}
catch { }
}
return loaded;
}
public override Control CreatePreviewControl() {
var picEdit = new PictureEdit();
picEdit.SvgImage = svgImage;
picEdit.Enabled = false;
picEdit.BorderStyle = BorderStyles.NoBorder;
picEdit.Properties.SizeMode = PictureSizeMode.Squeeze;
picEdit.Properties.UseDisabledStatePainter = false;
return picEdit;
}
}
Imports DevExpress.Dialogs.Core.Items
Imports DevExpress.Utils.Svg
Imports DevExpress.XtraEditors
Imports DevExpress.XtraEditors.Base.Controls.Preview
Imports DevExpress.XtraEditors.Controls
Imports System.Windows.Forms
Private gridExtension = assistant.Attach(grid, Sub(x)
x.CurrentPath = initialPath
x.FilterString = "Svg files (*.svg)|*.svg"
End Sub)
Private previewExtension = assistant.Attach(panel, Sub(x)
AddHandler x.CustomizePreview, Sub(s, e)
If e.Item.Extension.ToLower() = ".svg" Then
e.CustomPreviewHandler = New SvgPreviewHandler()
End If
End Sub
End Sub)
assistant.NavigationBindings.Add(New NavigationBinding() With {.Source = gridExtension, .Target = previewExtension})
' ...
Public Class SvgPreviewHandler
Inherits WinPreviewHandlerBase
Private svgImage As SvgImage
Public Overrides Function Load(ByVal file As String, ByVal shellItem As ShellItem) As Boolean
MyBase.Load(file, shellItem)
Dim loaded As Boolean = False
If shellItem.Extension.ToLower() = ".svg" Then
Try
Me.svgImage = SvgImage.FromFile(file)
loaded = True
Catch
End Try
End If
Return loaded
End Function
Public Overrides Function CreatePreviewControl() As Control
Dim picEdit = New PictureEdit()
picEdit.SvgImage = svgImage
picEdit.Enabled = False
picEdit.BorderStyle = BorderStyles.NoBorder
picEdit.Properties.SizeMode = PictureSizeMode.Squeeze
picEdit.Properties.UseDisabledStatePainter = False
Return picEdit
End Function
End Class
Handle the following events to customize context menus (add new commands, hide/disable commands, etc.) in the WinForms File Explorer Assistant component:
ContextMenuShowingBeforeExecuteItemCommandassistant.Attach(grid, x => {
x.CurrentPath = initialPath;
string customCommandName = "custom";
x.ContextMenuShowing += (s, e) => {
e.MenuItems.AddCommand("Custom command", customCommandName);
};
x.BeforeExecuteItemCommand += (s, e) => {
if(e.CommandName == customCommandName) {
XtraMessageBox.Show("Custom command executed");
e.Cancel = true;
}
};
});
assistant.Attach(grid, Sub(x)
x.CurrentPath = initialPath
Dim customCommandName As String = "custom"
AddHandler x.ContextMenuShowing, Sub(s, e)
e.MenuItems.AddCommand("Custom command", customCommandName)
End Sub
AddHandler x.BeforeExecuteItemCommand, Sub(s, e)
If e.CommandName = customCommandName Then
XtraMessageBox.Show("Custom command executed")
e.Cancel = True
End If
End Sub
End Sub)