I want to make a treeview that shows all folders on the system, and only shows music files, such as .mp3 .aiff .wav etc.
I remember reading that I need to use a recursive function or something along those lines.
Usually most computers have thousands of folders and hundreds of thousands of files, so displaying all of them in a TreeView recursively with be very slow and consume a lot of memory, view my answer in this question, citing my answer with some modifications when can get a pretty usable GUI:
// Handle the BeforeExpand event
private void treeView1_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
if (e.Node.Tag != null) {
AddDirectoriesAndMusicFiles(e.Node, (string)e.Node.Tag);
}
}
private void AddDirectoriesAndMusicFiles(TreeNode node, string path)
{
node.Nodes.Clear(); // clear dummy node if exists
try {
DirectoryInfo currentDir = new DirectoryInfo(path);
DirectoryInfo[] subdirs = currentDir.GetDirectories();
foreach (DirectoryInfo subdir in subdirs) {
TreeNode child = new TreeNode(subdir.Name);
child.Tag = subdir.FullName; // save full path in tag
// TODO: Use some image for the node to show its a music file
child.Nodes.Add(new TreeNode()); // add dummy node to allow expansion
node.Nodes.Add(child);
}
List<FileInfo> files = new List<FileInfo>();
files.AddRange(currentDir.GetFiles("*.mp3"));
files.AddRange(currentDir.GetFiles("*.aiff"));
files.AddRange(currentDir.GetFiles("*.wav")); // etc
foreach (FileInfo file in files) {
TreeNode child = new TreeNode(file.Name);
// TODO: Use some image for the node to show its a music file
child.Tag = file; // save full path for later use
node.Nodes.Add(child);
}
} catch { // try to handle use each exception separately
} finally {
node.Tag = null; // clear tag
}
}
private void MainForm_Load(object sender, EventArgs e)
{
foreach (DriveInfo d in DriveInfo.GetDrives()) {
TreeNode root = new TreeNode(d.Name);
root.Tag = d.Name; // for later reference
// TODO: Use Drive image for node
root.Nodes.Add(new TreeNode()); // add dummy node to allow expansion
treeView1.Nodes.Add(root);
}
}
Recursively searching all drives for particular files is not going to work well. It will take about a minute to do so with today's large drives.
One standard trick, used by Windows Explorer, is to only list the top level directories and files. It puts a dummy node in a directory node. When the user opens the node (BeforeExpand event), it searches only that directory and replaces the dummy node with the directories and files found it that directory. Again putting a dummy node in the directories. Etcetera.
You can see this at work by adding an empty subdirectory. The directory node will be shown with the + glyph. When you open it, Explorer discovers that there are no directory or files to be listed and deletes the dummy node. The + glyph disappears.
This is very fast, listing the content of a single directory takes well less than a second. There's a problem however using this approach in your case. The odds that a directory contains a suitable music file are small. The user will constantly be frustrated by finding out that the navigating through a set of subdirectories produces nothing.
That's why Windows has a dedicated place to store specific media files. My Music in this case. Use Environment.GetFolderPath(Environment.SpecialFolder.MyMusic) to find it. Iterating it should not take long.
Related
Is it possible to configure a FileSystemWatcher to identify all files, except files with the name containing a string of text?
For example, I want a FileSystemWatcher to identify all files, except .xml files. Can this be done?
I currently have:
FileSystemWatcher watcher = new FileSystemWatcher()
{
Filter = "*" // identifies all files
};
watcher.Created += OnChanged;
private void OnChanged(object source, FileSystemEventArgs args)
{
if (args.Name.ToLower().EndsWith(".xml"))
{
return;
}
// otherwise, do some stuff
}
This gets the job done, but I would rather not trigger the event when a .xml is found, if possible.
You could instead use a pattern in the filenames to watch.
*files*.* to match all files containing the string files
I have a Windows forms desktop application with a webBrowser control. I navigate to a directory on the server webBrowser1.Navigate(new Uri("\\\\srvername\\share\\directory\\")) to allow users to open files from that directory.
When they double click on files they get a Windows Security message every time.
When they click OK, the file opens as desired.
If I set navigate to a local directory webBrowser1.Navigate(new Uri("C:\\Temp\\")); I don't get the message.
Is there a programmatic way to prevent this from showing or is this something that will have to be added to the browser as a trusted site? Standard users on our network don't have access to add trusted sites. I have tried to check the "Include all network paths UNC" in the local intranet section of IE.
I have tried
webBrowser1.ScriptErrorsSuppressed = true;
but that doesn't work as it seems to be meant for script errors happen from a webpage displayed in the control.
Well I found two resolutions to my question.
Is it possible? As #jacob and all my research suggests, I don't think it is. If you are going to use the webBrowser control to view local or UNC path you will have to make changes to IE security or deal with the message for every file that is opened.
Resulution #1
Change security settings in IE. My organization has everything locked down so changes to IE settings can only be made through group policy.
Add file://servername/* to the Intranet Zone via group policy for the desired domain OU.
Enable the IE setting “Launching applications and files in an IFRAME” policy via the group policy for the desired domain OU.
Resulution #2
Replace the webBrowser control in my app with other controls that can accomplish the same functionality. This is one I went with so I would have more control over all working of the files and directories. I also don't have to worry about the security settings and group policy enforcement being correct and working correctly. I also don't have to worry about changes in these areas effecting me.
Here is the article I used to get started
The walk through as it is works pretty well. I went through it in a one off project before integrating it into my project. I need to change a few things to suit me. Here is my code as it ended up. I made some changes and added some selecting/clicking events.
Boolean FocusTreeview = false;
public frmMain()
{
InitializeComponent();
treeView1.NodeMouseClick += new TreeNodeMouseClickEventHandler(treeView1_NodeMouseClick);
}
// Start by calling this.
private void PopulateTreeView()
{
TreeNode rootNode;
treeView1.Nodes.Clear();
listView1.Items.Clear();
DirectoryInfo info = new DirectoryInfo("Pass the root folder here.");
// Add the root directory.
if (info.Exists)
{
rootNode = new TreeNode(info.Name);
rootNode.Tag = info;
GetDirectories(info.GetDirectories(), rootNode);
treeView1.Nodes.Add(rootNode);
treeView1.Nodes[0].Expand();
// This selects the root directory
NodeSelect(rootNode);
}
}
/********************************************/
private void GetDirectories(DirectoryInfo[] subDirs, TreeNode nodeToAddTo)
{
// This will list all the directories and subdirectories
TreeNode aNode;
DirectoryInfo[] subSubDirs;
foreach (DirectoryInfo subDir in subDirs)
{
aNode = new TreeNode(subDir.Name, 0, 0);
aNode.Name = subDir.ToString();
aNode.Tag = subDir;
aNode.ImageKey = "Directory";
subSubDirs = subDir.GetDirectories();
if (subSubDirs.Length != 0)
{
GetDirectories(subSubDirs, aNode);
}
nodeToAddTo.Nodes.Add(aNode);
}
}
/********************************************/
private void NodeSelect(TreeNode newSelected)
{
//Load all the files and directorys in the selected node from the treeview
listView1.Items.Clear();
DirectoryInfo nodeDirInfo = (DirectoryInfo)newSelected.Tag;
ListViewItem.ListViewSubItem[] subItems;
ListViewItem item = null;
foreach (DirectoryInfo dir in nodeDirInfo.GetDirectories())
{
item = new ListViewItem(dir.Name, 0);
subItems = new ListViewItem.ListViewSubItem[]
{new ListViewItem.ListViewSubItem(item, "Directory"),
new ListViewItem.ListViewSubItem(item,
dir.LastAccessTime.ToShortDateString())};
item.SubItems.AddRange(subItems);
listView1.Items.Add(item);
}
foreach (FileInfo file in nodeDirInfo.GetFiles())
{
item = new ListViewItem(file.Name, 1);
subItems = new ListViewItem.ListViewSubItem[] { new ListViewItem.ListViewSubItem(item, "File"), new ListViewItem.ListViewSubItem(item, file.LastAccessTime.ToShortDateString()) };
item.SubItems.AddRange(subItems);
listView1.Items.Add(item);
}
listView1.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
}
/********************************************/
void treeView1_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
TreeNode newSelected = e.Node;
NodeSelect(newSelected);
}
/********************************************/
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
// When something is selected load it's contents to the listview if it is a directory
if (listView1.SelectedItems.Count > 0)
{
var item = listView1.SelectedItems[0];
// Don't do anything if it's just a file
if (item.SubItems[1].Text == "File")
{
return;
}
TreeNode[] tns = treeView1.Nodes.Find(item.Text, true);
if (tns.Length > 0)
{
treeView1.Focus();
NodeSelect(tns[0]);
treeView1.SelectedNode = tns[0];
tns[0].Expand();
FocusTreeview = true;
}
}
}
/********************************************/
private void listView1_MouseUp(object sender, MouseEventArgs e)
{
// This needs to be done so the directory appears to stay selected in the treeview
if (FocusTreeview == true)
{
treeView1.Focus();
FocusTreeview = false;
}
}
/********************************************/
private void listView1_DoubleClick(object sender, EventArgs e)
{
// This will open the files that are double clicked on
if (listView1.SelectedItems.Count > 0)
{
var item = listView1.SelectedItems[0];
// Don't do anything for directories
if (item.SubItems[1].Text == "Folder")
{
return;
}
// Open the selected file
Process.Start(globals.szJobFolderPath + item.Text);
}
}
It ended up looking like this.
You do lose the icons of known files like Word docs but that is not a big deal to me. You could add icons for the known file types you will have to your imagelist and put some if statements (or switch cases) in the section where it adds items to the listview. Just analyze the file extensions.
Hope this helps somebody someday!
I'm confused on trying to display all .mdf files (and other database files) in a listview from a selected drive (ex. C:, D:) using a combobox (dropdown menu style).
Somehow the idea of the code escapes my mind
private void Form1_Load(object sender, EventArgs e)
{
foreach (DriveInfo dir in DriveInfo.GetDrives())
cmbDrive.Items.Add(dir.ToString());
}
private void btnScan(object sender, EventArgs e)
{
ListViewItem item = new ListViewItem(Directory.GetFiles(cmbDrive.Text));
string path = cmbDrive.Text;
string extension = "*.mdf";
lstvwdb2.Items.Add(Directory.GetFiles(path, extension));
}
If you return an array of string (Directory.GetFiles) then you should loop over the returned list of filenames and add every single item one by one. If you try to add the whole array you will get an exception because the ListViewItemCollection.Add method cannot handle an array.
However, remember that Directory.GetFiles with only two parameters returns only the files that match your extension variable and are located EXACTLY in the folder expressed by the path variable (the root of the drive in your case). If any of your MDF files is located in a subfolder of that path variable it is not catched by the above GetFiles call. There is an overload of the GetFiles that takes an enum value instructing the GetFiles method to work recursively on all the underlying subfolders. Its use or not depends on your requirements
private void btnScan(object sender, EventArgs e)
{
string path = cmbDrive.Text;
string extension = "*.mdf";
string[] files = Directory.GetFiles(path, extension);
//string[] files = Directory.GetFiles(path, extension, SearchOption.AllDirectories);
foreach(string s on files)
lstvwdb2.Items.Add(s);
}
Beware that searching on the C: drive with the option AllDirectories could be problematic due to the presence of numerous reserved folders that cause an Exception if you try to read them.
To bypass this problem you could copy the code provided by Marc Gravell in its answer here and paste it in a utility class (for example a FileUts class), then loop using a foreach calling that method instead of Directory.GetFiles
private void btnScan(object sender, EventArgs e)
{
string path = cmbDrive.Text;
string extension = "*.mdf";
foreach(string s in FileUts.GetFiles(path, extension))
lstvwdb2.Items.Add(s);
}
public static class FileUts
{
// Code provided by Marc Gravell
public static IEnumerable<string> GetFiles(string root, string searchPattern)
{
.....
}
}
Seems like you're messing things around a bit ...
Your list view item can take a string[] in the constructor, and your item collects of the list view should take a list view item in it's add method.
Try something like this:
// this is what you want to add to your lstvwdb2 items !!
string path = cmbDrive.Text;
string extension = "*.mdf";
ListViewItem item = new ListViewItem(Directory.GetFiles(cmbDrive.Text));
// You should add the above to your items collection
lstvwdb2.Items.Add(item);
To begin with, I'm relatively new to programming. I went through some introductory C# training for my new job, and it's the first language I've worked with.
I recently had a business problem that I decided to solve using C#, both to save time (I had hoped) and to learn more C# in the process. The business problem I mentioned was this: I had 600+ Word files that I needed to audit. For each document, I had to make sure that...
There was no text with strike-through anywhere in the document.
Track Changes was disabled.
There were no pending changes (as in changes that were made while
Track Changes was enabled and have yet to be accepted or
rejected).
There were no comments.
It would have been fastest to have my program iterate through all of the documents, making changes as it went along. But because of the nature of this assignment I wanted to make the changes manually, limiting the program's use to generating a list of files (out of the 600) where changes were necessary, and detailing what changes needed to be made for each of those files.
So, I have a button that calls up a FolderBrowserDialog.
private void AddFolderButtonClick(object sender, EventArgs e)
{
var folderBrowser = new FolderBrowserDialog();
if (folderBrowser.ShowDialog() != DialogResult.OK)
{
return;
}
this.progressBar1.Visible = true;
this.progressBar1.Style = ProgressBarStyle.Marquee;
this.Cursor = Cursors.WaitCursor;
var args = new List<string>(Directory.EnumerateDirectories(folderBrowser.SelectedPath));
// Get list of files in selected directory, adding to list of directories
args.AddRange(Directory.EnumerateFiles(folderBrowser.SelectedPath));
this.displayListBox.BeginUpdate();
foreach (string path in args)
{
if (File.Exists(path))
{
// This path is a file
this.ProcessFile(Path.GetFullPath(path));
}
else if (Directory.Exists(path))
{
// This path is a directory
this.ProcessDirectory((Path.GetFullPath(path)));
}
else
{
Console.WriteLine(Resources.Finder_Invalid_File_Or_Directory, path);
}
}
this.displayListBox.EndUpdate();
this.progressBar1.Visible = false;
this.progressBar1.Style = ProgressBarStyle.Continuous;
this.Cursor = Cursors.Default;
}
Together, the following two methods iterate through all subdirectories and files to create a full list of all files below the top level directory selected through the FolderBrowserDialog:
private void ProcessDirectory(string targetDirectory)
{
// Process the list of files found in the directory.
string[] fileEntries = Directory.GetFiles(targetDirectory);
foreach (string fileName in fileEntries)
{
this.ProcessFile(fileName);
}
// Recurse into subdirectories of this directory.
string[] subdirectoryEntries = Directory.GetDirectories(targetDirectory);
foreach (string subdirectory in subdirectoryEntries)
{
this.ProcessDirectory(subdirectory);
}
}
private void ProcessFile(string path)
{
Console.WriteLine(Resources.Finder_File_Processed, path);
string fileName = Path.GetFileName(path);
if (fileName == null || fileName.StartsWith(#"~$") || this.selectedFilesList.Contains(path))
{
return;
}
this.selectedFilesList.Add(path);
this.filePathsCountLabel.Text = (#"Count: " + this.selectedFilesList.Count);
this.displayListBox.Items.Add(path);
}
Once all this code has run, I get a full list of documents. I click a button and the program does what it's supposed to from here on out. Okay, cool. I mentioned before that half of the reason I chose to use C# to solve this was for the sake of learning. At this point I've got everything I need but what I really want to know is how can I implement threading to make the GUI responsive while the list of files is being generated? I've looked through several examples. They made sense. For some reason I just can't get my head around it for this application though. How can I make the whole process of processing subdirectories and files happen without locking up the GUI?
I believe what you need could be found here.
In short, to use a backgroundworker which does all the work on a separate thread thus prevents GUI freezes, first you instantiate BackgroundWorker and handle the DoWork event. Then you call RunWorkerAsync, optionally with an object argument.
As a skeleton code:
class myClass
{
static BackgroundWorker myBw = new BackgroundWorker();
static void Main()
{
myBw .DoWork += myBw_DoWork;
myBw .RunWorkerAsync ("an argument here");
Console.ReadLine();
}
static void myBw_DoWork (object sender, DoWorkEventArgs e)
{
// This is called on the separate thread, argument is called as e.Argument
// Perform heavy task...
}
}
You have to create a separate thread to process your work. Look at this if you are using .NET 4.0+ or this for older versions.
With Task, you can write
Task.Factory.StartNew(() => DoAction()
where DoAction is your function that starts to process data.
But do not forget to use Invoke, if you want to act with GUI from separate thread. For example, if you want to update some Label text from separate thread, you have to do this
label1.Invoke(() => label1.Text = "Some Text");
I've just finished making this recursive method:
/// <summary>
/// Recursively process a given directory and add its file to Library.xml
/// </summary>
/// <param name="sourceDir">Source directory</param>
public void ProcessDir(string sourceDir)
{
string[] fileEntries = Directory.GetFiles(sourceDir, "*.mp3");
foreach (string fileName in fileEntries)
{
Song newSong = new Song();
newSong.ArtistName = "test artist";
newSong.AlbumName = "test album";
newSong.Name = "test song title";
newSong.Length = 1234;
newSong.FileName = fileName;
songsCollection.Songs.Add(newSong);
}
string[] subdirEntries = Directory.GetDirectories(sourceDir);
foreach (string subdir in subdirEntries)
{
if ((File.GetAttributes(subdir) & FileAttributes.ReparsePoint) != FileAttributes.ReparsePoint)
{
ProcessDir(subdir);
}
}
}
Everything is working as expected, the only problem I'm having is: How do I know when this method finishes execution? Is there something made for that very purpose in .NET?
There's nothing special in .NET that tells you this... Basically, the first call to ProcessDir will return after the recursion has ended.
Well, you could always put a line of code indicating the end of execution after your initial ProcessDir call:
ProcessDir("MyDir");
Console.WriteLine("Done!");
You could try using a global variable to keep track of it.
private int _processDirTrack = 0;
public void ProcessDir(string sourceDir)
{
_processDirTrack++; // Increment at the start of each
string[] fileEntries = Directory.GetFiles(sourceDir, "*.mp3");
foreach (string fileName in fileEntries)
{
Song newSong = new Song();
newSong.ArtistName = "test artist";
newSong.AlbumName = "test album";
newSong.Name = "test song title";
newSong.Length = 1234;
newSong.FileName = fileName;
songsCollection.Songs.Add(newSong);
}
string[] subdirEntries = Directory.GetDirectories(sourceDir);
foreach (string subdir in subdirEntries)
{
if ((File.GetAttributes(subdir) & FileAttributes.ReparsePoint) != FileAttributes.ReparsePoint)
{
ProcessDir(subdir);
}
}
_processDirTrack--; // Decrement after the recursion. Fall through means it got to
// the end of a branch
if(_processDirTrack == 0)
{
Console.WriteLine("I've finished with all of them.");
}
}
I'm assuming the (albeit correct) answer RQDQ provided isn't the one you are looking for?
In case you have a long running task of which you want to check how far it is along you can use a BackgroundWorker.
Ofcourse this background worker doesn't magically know how much more files to process, so you have to call ReportProgress whenever you can give an estimate of how far along you are.
In order to try to estimate how much longer the processing will take, you could try the following:
Check for total disk space the folder occupies, and keep track of much you already processed.
Check for amount of files you have to process vs. how much you still have to process.
If you're wanting to know when it ends to signal/start another process in your application, you could raise an event. This may be over kill, but hey, it's another way to look at it if it suits your need. You just need to add an event to the class where ProcessDir() is a member.
private int _processDirTrack = 0;
public event EventHandler DirProcessingCompleted;
At the end of your method you would raise your event like so
DirProcessingCompleted(this, new EventArgs());
You subscribe to these events with an eventhandler somewhere else in your code
myClass.DirProcessingCompleted += new EventHandler(ProcessingComplete_Handler);
Note: you don't have to subscribe to an event in this manner; you could also subscribe with a delegate or lambda expression instead.
To wrap it all up you create your method that is called whenever an event is raised by the object.
private void ProcessingComplete_Handler(object sender, EventArgs e)
{
// perform other operations here that are desirable when the event is raised ...
}
I'm guessing what you're trying to figure out is how to know that you've reached the deepest level of recursion that is going to occur.
In this case, you'll know that you're in the last recursive call of the function when subdirEntries is empty, since the function will no longer recurse at that point. That's the best you can do, there's no universal answer as to how to know when a function will cease to recurse. It's entirely dependent upon what the conditions to recurse are.
Edit: Wanted to clarify. This will check each time you end a single chain of recursions. Considering your code can recurse multiple times per call, my solution will only signify the end of a single chain of recursions. In the case of recursively navigating a tree, this will occur at every leaf node, not at the deepest level of recursion.