3

I have a WinForm Application which takes the structure of two TreeViews and implements them as Folders into the path which the users chose in the Drop Down.

The Drop Down currently gets all the chooseable folders from Z:

Now my TreeView loremPath has the right Drive with Z: but ipsumPath should go into R: but with the same Drop Down - Because the second Drive has the exact folder structure as Z: so instead of building a whole new Drop Down, I just need to change the path in ipsumPath to R: and can use one Drop Down for both Treeviews.

So I had a previous Question on StackOverflow and I got recommended to use Hardcoded paths for both TreeViews, but I can't figure out how to implement that.

I tried something like:

        var testPath= new DirectoryInfo("R:\\").GetDirectories();
        var treeSeperator = ipsumPath.PathSeparator;
        var dirSep = Path.DirectorySeparatorChar.ToString();

        foreach (var node in GetCheckedNodes(ipsumPath.Nodes))
        {
            var sPath = Path.Combine(testPath.ToString(), node.FullPath.Replace(treeSeperator, dirSep));
            Directory.CreateDirectory(sPath);
        }

But that didn't work at all.

My whole Code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.IO;
using IWshRuntimeLibrary;
using System.Reflection;

namespace Form1
{
    public partial class Form1 : Form
    {
        [DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
        private static extern IntPtr CreateRoundRectRgn
         (
             int nLeftRect,
             int nTopRect,
             int nRightRect,
             int nBottomRect,
             int nWidthEllipse,
             int nHeightEllipse
         );

        public Form1()
        {
            InitializeComponent();
            this.FormBorderStyle = FormBorderStyle.None;
            Region = System.Drawing.Region.FromHrgn(CreateRoundRectRgn(0, 0, Width, Height, 20, 20));
            foreach (TreeNode tn in loremPath.Nodes)
            {
                tn.Expand();
            }
            foreach (TreeNode tn in ipsumPath.Nodes)
            {
                tn.Expand();
            }
            ipsumDropDown.Items.AddRange(new[] { "R:\\", "Z:\\" });
            loremDropDown.DataSource = new DirectoryInfo($"{ipsumDropDown.SelectedItem}").GetDirectories();
        }

        private void CreateShortcutToCurrentAssembly(string saveDir)
        {
            var testPath = loremDropDown.SelectedValue.ToString();
            WshShell wshShell = new WshShell();
            string fileName = testPath + "\\" + Application.ProductName + ".lnk";
            IWshShortcut shortcut = (IWshShortcut)wshShell.CreateShortcut(fileName);
            shortcut.TargetPath = Application.ExecutablePath;
            shortcut.Save();
        }

        private void close_Click(object sender, EventArgs e)
        {
            System.Windows.Forms.Application.Exit();
        }

        private void loremPath_AfterCheck(object sender, TreeViewEventArgs e)
        {
            if (e.Action == TreeViewAction.Unknown) return;

            foreach (TreeNode n in e.Node.Children())
                n.Checked = e.Node.Checked;

            foreach (TreeNode p in e.Node.Parents())
                p.Checked = p.Nodes.OfType<TreeNode>().Any(n => n.Checked);
        }

        private IEnumerable<TreeNode> GetCheckedNodes(TreeNodeCollection nodeCol)
        {
            foreach (TreeNode node in nodeCol)
            {
                if (node.Checked ||
                    node.Nodes.Cast<TreeNode>().Any(n => n.Checked))
                {
                    yield return node;
                }

                foreach (TreeNode childNode in GetCheckedNodes(node.Nodes))
                {
                    if (childNode.Checked)
                        yield return childNode;
                }
            }
        }
        private void projektordnerGenerieren_Click(object sender, EventArgs e)
        {
            var destPath = loremDropDown.SelectedValue.ToString();
            var treeSep = loremPath.PathSeparator;
            var dirSep = Path.DirectorySeparatorChar.ToString();

            foreach (var node in GetCheckedNodes(loremPath.Nodes))
            {
                var sPath = Path.Combine(destPath, node.FullPath.Replace(treeSep, dirSep));
                Directory.CreateDirectory(sPath);
            }
            foreach (var node in GetCheckedNodes(ipsumPath.Nodes))
            {
                var sPath = Path.Combine(destPath, node.FullPath.Replace(treeSep, dirSep));
                Directory.CreateDirectory(sPath);
            }
            string folder = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
            CreateShortcutToCurrentAssembly(folder);
        }

        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            this.loremPath.SelectedNode = this.loremPath.Nodes[0];
            this.ipsumPath.SelectedNode = this.ipsumPath.Nodes[0];
            loremPath.SelectedNode.Text = textBox1.Text;
            ipsumPath.SelectedNode.Text = textBox1.Text;
        }

        private void ipsumPath_AfterCheck(object sender, TreeViewEventArgs e)
        {
            if (e.Action == TreeViewAction.Unknown) return;

            foreach (TreeNode n in e.Node.Children())
                n.Checked = e.Node.Checked;

            foreach (TreeNode p in e.Node.Parents())
                p.Checked = p.Nodes.OfType<TreeNode>().Any(n => n.Checked);
        }

        public const int WM_NCLBUTTONDOWN = 0xA1;
        public const int HT_CAPTION = 0x2;

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        public static extern bool ReleaseCapture();

        private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                ReleaseCapture();
                SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
            }
        }

        private void alleErweitern_Click(object sender, EventArgs e)
        {
            foreach (TreeNode tn in loremPath.Nodes)
            {
                tn.Expand();
            }
            foreach (TreeNode tn in ipsumPath.Nodes)
            {
                tn.Expand();
            }
        }

        private void alleReduzieren_Click(object sender, EventArgs e)
        {
            foreach (TreeNode tn in loremPath.Nodes)
            {
                tn.Collapse();
            }
            foreach (TreeNode tn in ipsumPath.Nodes)
            {
                tn.Collapse();
            }
        }

        private void minimize_Click(object sender, EventArgs e)
        {
            this.WindowState = FormWindowState.Minimized;
        }
    }
    static class TreeViewExtensions
    {
        public static IEnumerable<TreeNode> Children(this TreeNode node)
        {
            foreach (TreeNode n in node.Nodes)
            {
                yield return n;

                foreach (TreeNode child in Children(n))
                    yield return child;
            }
        }

        public static IEnumerable<TreeNode> Parents(this TreeNode node)
        {
            var p = node.Parent;

            while (p != null)
            {
                yield return p;

                p = p.Parent;
            }
        }
    }
}
  • 1
    How about one more `ComboBox` that contains the destination drives `F:, Z:, X:...`? Then you can handle it's `SelectedIndexChanged` or committed event to refill the second combo box. To get then the right path from the `SelectedValue`. – dr.null Sep 08 '22 at 14:10
  • You mean one for each driver and when one is changed he changes the second one to the same ComboBox Item ? – TheCodingTentacle Sep 08 '22 at 14:12
  • No, just one `ComboBox` that contains the available destination drives. When you select one, refill the second `ComboBox` the same way you have here. Give it a try. – dr.null Sep 08 '22 at 14:19
  • Ok I made two seperate ComboBox and it works how it should be but I thought maybe to hide the second ComboBox and leave the first visible and change the invisible one to the same of the first one. How does it work combined with SelectedIndexChanged – TheCodingTentacle Sep 08 '22 at 14:19
  • How is it possible to sync. both ComboBoxes with just the folder and not the whole path.. – TheCodingTentacle Sep 08 '22 at 14:32
  • Say you have `cmbDrives.Items.AddRange(new[] { "F:\\", "Z:\\" });`, Handle it's `SelectionChangeCommitted` event to `loremDropDown.DataSource = new DirectoryInfo($"{cmbDrives.SelectedItem}").GetDirectories();`. Then you'll get the right `loremDropDown.SelectedValue`. Yes you have the same dir structure in both drives but still they are two different destinations. Right? – dr.null Sep 08 '22 at 14:33
  • Yes different destinations, correct. – TheCodingTentacle Sep 08 '22 at 14:42
  • @dr.null so your last comment should fix the issue ? cause I didn't figure it out yet – TheCodingTentacle Sep 12 '22 at 08:26
  • I get: "the name "cmbDrives is not existing in the current context" even tho I added cmbDrives.Items.AddRange(new[] { "F:\\", "Z:\\" }); right above – TheCodingTentacle Sep 12 '22 at 14:04
  • Buddy, that is supposed to be the name of the additional `ComboBox` control that I suggested to add to list the destination drives. Drop new `ComboBox` above the current one and name it `cmbDrives` and the code should work. Why didn't you ask 4 days ago instead of the bounty thing. I waited to try and fix it yourself. Try now and report plz. – dr.null Sep 12 '22 at 14:14
  • System.ArgumentException: "The path is in an invalid format." at: loremDropDown.DataSource = new DirectoryInfo($"{cmbDrives.SelectedItem}").GetDirectories() – TheCodingTentacle Sep 12 '22 at 14:41
  • I edited my Question so you can see what I did. – TheCodingTentacle Sep 12 '22 at 14:48
  • 1
    OK, give me sometime to read it and put it together with some examples. – dr.null Sep 12 '22 at 15:01
  • Some controls should be able to achieve this function. – Housheng-MSFT Sep 13 '22 at 09:28

1 Answers1

3

The problem you are trying to solve here is to mirror the checked TreeNode branches in the file system and create similar directory structure in a destination directory. A destination directory can be in one of specific drives such as R: and Z:. So, let's break it down.

Destination Drives

First, you need to list the target drives in list-control such as ComboBox to select one. You can get the fixed and ready drives in your system by the DriveInfo.GetDrives method or by adding them manually if don't want to list them all. I'll use here the later approach. From here, in the designer, drop a ComboBox and name it cmbDrives then add in the form`s constructor:

public Form1()
{
    InitializeComponent();
    // ...

    cmbDrives.Items.AddRange(new[] { "R:\\", "Z:\\" });
    cmbDrives.SelectedIndex = 0;  
}

Destination Directories

You already have the loremDropDown combo box which lists the destination directories/folders. Handle the cmbDrives.SelectedIndexChanged event to populate the destination directories in the selected drive. Revisit the constructor to add:

public Form1()
{
    InitializeComponent();
    // ...

    cmbDrives.Items.AddRange(new[] { "R:\\", "Z:\\" });
    cmbDrives.SelectedIndex = 0; 

    loremDropDown.DisplayMember = "Name";
    loremDropDown.ValueMember = "FullName"; 
}

And add the event handler:

private void cmbDrives_SelectedIndexChanged(object sender, EventArgs e)
{
    var selDrive = cmbDrives.SelectedItem.ToString();
    loremDropDown.DataSource = new DirectoryInfo(selDrive).GetDirectories();
}

Now when you select the R: drive for example, loremDropDown.SelectedValue returns the full path of the selected destination directory like R:\\SomeSelectedDirectory\. When you select another drive like Z:, the path of the same directory will be Z:\\SomeSelectedDirectory\. So you don't need to do anything else.

Create the Directory Structure

Brought from the previous answer:

private void SomeButton_Click(object sender, EventArgs e)
{
    var destPath = loremDropDown.SelectedValue.ToString();
    var treeSep = pathLorem.PathSeparator;
    var dirSep = Path.DirectorySeparatorChar.ToString();

    foreach (var node in GetCheckedNodes(pathLorem.Nodes))
    {
        var sPath = Path.Combine(destPath, node.FullPath.Replace(treeSep, dirSep));
        Directory.CreateDirectory(sPath);
    }

    // The same for `ipsumPath` TreeView if the target destination is the same.
}

private IEnumerable<TreeNode> GetCheckedNodes(TreeNodeCollection nodeCol)
{
    foreach (TreeNode node in nodeCol)
    {
        if (node.Checked ||
            node.Nodes.Cast<TreeNode>().Any(n => n.Checked))
        {
            yield return node;
        }

        foreach (TreeNode childNode in GetCheckedNodes(node.Nodes))
        {
            if (childNode.Checked)
                yield return childNode;
        }
    }
}

SO73649197


On the other hand, if you have two TreeView controls and you need to create directory structures in two different drives at the same time where both drives have the same destination directory structure, then you just need to change the drive... (ignore the cmbDrives in this scenario)...

public Form1()
{
    InitializeComponent();
    // ...

    loremDropDown.DisplayMember = "Name";
    loremDropDown.ValueMember = "FullName";
    loremDropDown.DataSource = new DirectoryInfo("F:\\").GetDirectories();
}

private void SomeButton_Click(object sender, EventArgs e)
{
    var driveF = "C:\\";
    var driveZ = "D:\\";
    var selDir = loremDropDown.SelectedValue.ToString();
    var destPathF = selDir.Replace(Path.GetPathRoot(selDir), driveF);
    var destPathZ = selDir.Replace(Path.GetPathRoot(selDir), driveZ);
    var treeSep = pathLorem.PathSeparator;
    var dirSep = Path.DirectorySeparatorChar.ToString();
    var shortcuts = new HashSet<string>();

    foreach (var node in GetCheckedNodes(pathLorem.Nodes))
    {
        var sPath = Path.Combine(destPathF, node.FullPath.Replace(treeSep, dirSep));
        Directory.CreateDirectory(sPath);

        if (node.Level == 0) shortcuts.Add(sPath.TrimStart(driveF.ToArray()));
    }

    foreach (var node in GetCheckedNodes(ipsumPath.Nodes))
    {
        var sPath = Path.Combine(destPathZ, node.FullPath.Replace(treeSep, dirSep));
        Directory.CreateDirectory(sPath);

        if (node.Level == 0) shortcuts.Add(sPath.TrimStart(driveZ.ToArray()));
    }

    foreach (var shortcut in shortcuts)
    {
        var dirF = $"{driveF}{shortcut}";
        var dirZ = $"{driveZ}{shortcut}";

        if (Directory.Exists(dirF) && Directory.Exists(dirZ))
        {
            CreateShortcut(dirF, dirZ);
            CreateShortcut(dirZ, dirF);
        }
    }

    // Optional...
    // CreateShortcut(destPathF, destPathZ);
    // CreateShortcut(destPathZ, destPathF);
}

private void CreateShortcut(string shortcutPath, string targetPath)
{
    WshShell wshShell = new WshShell();
    string fileName = Path.Combine(shortcutPath, $"{Application.ProductName}.lnk");
    IWshShortcut shortcut = (IWshShortcut)wshShell.CreateShortcut(fileName);
    shortcut.TargetPath = targetPath;
    shortcut.Save();
}

Moreover, if it is possible that pathLorem and ipsumPath each of which can output in F: or Z: or somewhere else, then you need to combine the two ideas mentioned here and add two drive selectors (ex. ComboBox) to select from them the destination drive for each instead of hard-coding them. Or, if handling a single TreeView a time is acceptable, then you just need a single cmbDrives combo box and two RadioButton controls to select accordingly pathLorem or ipsumPath control to process.

dr.null
  • 4,032
  • 3
  • 9
  • 12
  • the last example was exactly what I was looking for 1 week long - Your answer was really good and teaching. I will now lookup your Code and try to understand everything. Thanks you the mvp! – TheCodingTentacle Sep 13 '22 at 06:33
  • Is there maybe a way to include a shortcut from the folder generated in Z: to R: ? So a shortcut of the new folder in Z: to the new folder in R: – TheCodingTentacle Sep 13 '22 at 06:41
  • A shortcut from the File generated in Z: but the shortcut is placed in the file in R: - So it's File.Copy ? – TheCodingTentacle Sep 13 '22 at 15:27
  • yes my version was already ok but we both make a shortcut of the software - i want to make a shortcut of loremPath TreeView in ipsumPath TreeView when they are created so a user could click on the shortcut of Z: when he is on R: to go to his twin Z: folder – TheCodingTentacle Sep 14 '22 at 05:51
  • It works :) But I meant to put the shortcut in the generated folder, now it will generate the shortcut in the folder chosen in the dropdown not in the treeview structure – TheCodingTentacle Sep 15 '22 at 06:49
  • @TheCodingTentacle Now I'm gonna cry! Define _generated folder_, hard code it in here. And is it a _generated folder_ or _generated folders_? Anyway, the `CreateShortcut` has two params, the destination and target paths. try to pass the right values from the caller. Plz report if you still need a hand. – dr.null Sep 15 '22 at 14:41
  • no, don't cry. Everything works how it should work, thank you - just the shortcut is created in the folder the user can choose in the dropdown but it should be in the TreeView folder - I have one Parent folder like for example Books and then its Child folders -> Book 1 -> Book2 ..etc. and I want the shortcut inside "Books" - right now its outside "Books" and is in the folder chosen in the Dropdown. – TheCodingTentacle Sep 15 '22 at 15:03
  • 1
    I did: shortcutPath, pathLorem.SelectedNode.Text, $"{pathLorem.SelectedNode.Text}.lnk" - and it works :))) – TheCodingTentacle Sep 15 '22 at 15:07
  • Thanks again - you teached and saved me, you the man. – TheCodingTentacle Sep 15 '22 at 15:08
  • Thanks man, seems like not fully fixed. the shortcut won't go in the project it's just going to the dropdown file – TheCodingTentacle Sep 15 '22 at 15:37
  • 1
    @TheCodingTentacle So you need also shortcuts in `Books`, `Papers`, `Folders`, the parent folders. So the shortcut in `F:` Books folder takes you to `Z:` Books folder. If that sounds right to you, then I'll add the code after having my lunch. Up vote this comment to confirm or comment to correct me. – dr.null Sep 15 '22 at 15:49
  • Yes correct but I just have one Parent folder so for example just Books. – TheCodingTentacle Sep 16 '22 at 06:02
  • 1
    @TheCodingTentacle Try now and report plz. – dr.null Sep 16 '22 at 15:41
  • 1
    It works! Thank you so much :) Now I will create some folders! :D – TheCodingTentacle Sep 19 '22 at 06:20