BLOG.CSHARPHELPER.COM

Delete large numbers of files easily and quickly in C#

Don't forget to enter the drawing for a copy of Visual Basic 2012 Programmer's Reference. Deadline May 7, 2013. Go here for details.

A while ago I needed to delete a large number of files that were created when I opened old projects in a newer version of Visual Studio. (As far as I know, there's no way to tell Visual Studio not to create these files.) I don't know why, but Windows Explorer was taking several seconds to delete each file and there were almost a hundred so the whole thing was taking far too long. Rather than waiting the 5 or so minutes it would have taken to delete them all, I wrote this program to delete the files. (This probably took longer than waiting for Windows Explorer to do the job but it was more interesting and if this happens again I'll have the program ready.)

Enter a directory path and a pattern to match and click List Files to see a list of the files matching the pattern in the directory you entered and its subdirectories. Initially all of the matching files are checked in the CheckedListBox. Review the list to make sure you want to delete the files, uncheck any that you want to keep, and click Delete to make the files go away forever.

If you want to move the files into the recycle bin instead of permanently deleting them, see this example:

The following code shows how the program lists the files that match the pattern you entered.

// List the selected files.
private void btnListFiles_Click(object sender, EventArgs e)
{
    clstFiles.Items.Clear();
    try
    {
        DirectoryInfo dir_info = new DirectoryInfo(txtPath.Text);
        foreach (FileInfo file_info in dir_info.GetFiles(
            txtPattern.Text, SearchOption.AllDirectories))
        {
            int index = clstFiles.Items.Add(file_info.FullName);
            clstFiles.SetItemChecked(index, true);
        }
        lblNumFiles.Text = clstFiles.Items.Count + " files";
        btnDelete.Enabled = clstFiles.CheckedIndices.Count > 0;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

The code starts by clearing the CheckedListBox. It then creates a DirectoryInfo object representing the directory path that you entered. It uses that object's GetFiles method to get a collection of FileInfo objects representing the files matching the pattern that you entered.

The program adds each file's name to the CheckedListBox and checks it. The code uses the index of the newly added file to check it instead of simply checking the last item in the list because the control's Sorted property is set to true. That means the new file may not be added to the end of the list. (Alternatively you could add all of the files and then check them all afterwards.)

This piece of code finishes by displaying the number of files listed and enabling the Delete button.

The following code shows how the program deletes the checked files.

// Delete the checked files.
private void btnDelete_Click(object sender, EventArgs e)
{
    string[] filenames = new string[clstFiles.CheckedItems.Count];
    clstFiles.CheckedItems.CopyTo(filenames, 0);
    foreach (string filename in filenames)
    {
        Console.WriteLine(filename);
        try
        {
            File.Delete(filename);
            clstFiles.Items.Remove(filename);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error deleting file. " + ex.Message);
            if (MessageBox.Show("Error deleting file. " + ex.Message +
                Environment.NewLine + Environment.NewLine +
                "Continue?", "Continue?",
                MessageBoxButtons.YesNo, MessageBoxIcon.Question)
                    == DialogResult.No)
                break;
        }
    }

    lblNumFiles.Text = clstFiles.CheckedItems.Count + " files";
    btnDelete.Enabled = clstFiles.CheckedItems.Count > 0;
}

Normally if you just want to loop through the checked items in a CheckedListBox, you can iterate over its CheckedItems collection. However, this example removes files from the CheckedListBox as they are deleted. A C# program cannot modify a collection while it is iterating over it so this code first copies the file names into an array. It then loops through the array so it can remove items from the CheckedListBox with no problems.

As it loops through the file names, the code displays the name in the Console window. It uses File.Delete to permanently delete the file and then removes the file name from the CheckedListBox.

If anything goes wrong, the code displays a MessageBox telling you what the problem is and asking if you want to continue. If you click No, the code breaks out of the loop. (I figure if something goes wrong, then it might happen a lot of times. For example, if you don't have delete permission on a directory containing hundreds of files, the program may fail hundreds of times and it could take you a while to close each MessageBox separately. This gives you a way to stop the process.

   

Book Drawing: Visual Basic 2012 Programmer's Reference

Visual Basic 2012 Programmer's Reference
$44.99, 840 pages, 2012
John Wiley & Sons
ISBN 13: 978-1118314074
Book sales these days are heavily driven by reviews and this book hasn't gotten enough. In an attempt to generate more reviews, I'm giving away 4 copies of the book. Here are some details:

  • By entering the drawing, you promise to post a review. I don't want you to promise a good review, just an honest review. I think this is a good book and it can stand for itself.
  • This is a Visual Basic book not a C# book. All of the same ideas apply to both languages but if you're not interested in Visual Basic, then you may find translating the details to be annoying.
  • This book also focuses on the programming language not on Metro issues (or whatever Microsoft is calling the new Windows 8 style today). It uses Visual Studio Express 2012 for Windows Desktop, which you can download for free here.
  • To enter, send an email with the subject "Book drawing" to [email protected] by May 7, 2013.

If you want a fast-paced in-depth introduction to Visual Basic, sign up for the drawing and you might win a free book!

(If by some chance you already own a copy of this book, or a copy of any of my books, please post a review! Reviews are what sells books and without sales I can't keep writing. Please don't make me get a real job!)

Make a modal context menu in C#

Sorry for the long hiatus. I've been finishing the initial draft for a book about algorithms and it turned out to be more time consuming than I had expected. I'll post details when it is closer to publication.

Not long ago, someone asked me whether you could make a modal context menu. He wanted to be able to display a floating menu and make the user pick a selection before continuing.

The ContextMenuStrip control doesn't work that way. Context menus are supposed to pop up when the user wants them not when the program wants to display them. I think the idea is that if the user made the menu appear, then the user should be able to make the menu disappear.

However, with some work you can make a form that behaves a lot like a modal context menu. This is a fairly long post so it's divided into two sections:

The Dialog Form

This example includes a form named ModalMenuForm that contains a single ListBox named lstItems. The program displays the user's choices (Pink, Light Green, and Light Blue) in the ListBox, but ListBox controls don't behave like ContextMenuStrips so the form needs to do some extra work to make it look like a context menu.

To make the ListBox look like a context menu, the ListBox is owner drawn. For information about owner-drawn ListBox controls, see these posts:

When the form loads, it uses the following code to set up the owner-drawn ListBox.

// Get the form ready.
private void ModalMenuForm_Load(object sender, EventArgs e)
{
    // Make the ListBox owner-drawn.
    lstItems.DrawMode = DrawMode.OwnerDrawVariable;

    // Set form properties.
    this.FormBorderStyle = FormBorderStyle.None;
    this.KeyPreview = true;

    // Make the form fit the ListBox.
    this.ClientSize = lstItems.Size;
}

This code makes the ListBox owner-drawn. It also sets the form's FormBorderStyle property to false so it doesn't display window controls, a title bar, or a border.

The code sets the form's KeyPreview property to true so it can look for the Enter and Esc keys. Finally this code resizes the form to fit the ListBox.

The form uses the following code to draw the owner-drawn ListBox.

// See: Make an owner-drawn ListBox in C#
// blog.csharphelper.com/2011/06/22/make-an-owner-drawn-listbox-in-c.aspx

// Calculate the size of an item.
private int ItemMargin = 5;
private void lstItems_MeasureItem(object sender, MeasureItemEventArgs e)
{
    // Get the ListBox and the item.
    ListBox lst = sender as ListBox;
    string txt = lst.Items[e.Index].ToString();

    // Measure the string.
    SizeF txt_size = e.Graphics.MeasureString(txt, this.Font);

    // Set the required size.
    e.ItemHeight = (int)txt_size.Height + 2 * ItemMargin;
    e.ItemWidth = (int)txt_size.Width;
}

// Draw the item.
private void lstItems_DrawItem(object sender, DrawItemEventArgs e)
{
    // Get the ListBox and the item.
    ListBox lst = sender as ListBox;
    string txt = lst.Items[e.Index].ToString();

    // Draw the background.
    e.DrawBackground();

    if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
    {
        // Selected. Draw with the system highlight color.
        e.Graphics.DrawString(txt, this.Font,
            SystemBrushes.HighlightText, e.Bounds.Left, e.Bounds.Top + ItemMargin);
    }
    else
    {
        // Not selected. Draw with ListBox's foreground color.
        using (SolidBrush br = new SolidBrush(e.ForeColor))
        {
            e.Graphics.DrawString(txt, this.Font, br,
                e.Bounds.Left, e.Bounds.Top + ItemMargin);
        }
    }

    // Don't draw the focus rectangle for
    // this example because the user cannot use
    // the arrow keys to change the selection.
    //// Draw the focus rectangle if appropriate.
    //e.DrawFocusRectangle();
}

The MeasureItem event handler returns the size needed for each of the menu items. The DrawItem event handler actually draws the items when needed. (See the links earlier for more details.)

There are three points worth mentioning in the DrawItem event handler. First, if the mouse is over the item being drawn, the DrawBackground method draws an appropriate highlighted background. (In the picture, the Light Green choice is highlighted.)

Second, if the item being drawn is selected, the code draws its text with the system highlight color so it looks like a highlighted menu item.

Third, the code does not draw a focus rectangle around the selected item. The focus rectangle shows which item is selected if you are tabbing among controls. Because you can't tab off of the modal ListBox, there's no reason to do this. (Besides, it looks funny.)

The form needs a few other pieces of code to make the ListBox look like a context menu. The following two event handlers make the ListBox select an appropriate item when the mouse moves over it.

// Select the ListBox item under the mouse.
private void lstItems_MouseMove(object sender, MouseEventArgs e)
{
    int index = lstItems.IndexFromPoint(e.Location);
    if (lstItems.SelectedIndex != index) lstItems.SelectedIndex = index;
}

// If the form isn't closing, deselect the ListBox item.
private void lstItems_MouseLeave(object sender, EventArgs e)
{
    // If the form is closing, leave the selection alone.
    if (DialogResult != DialogResult.None) return;
    if (lstItems.SelectedIndex != -1) lstItems.SelectedIndex = -1;
}

The MouseMove event handler selects the item that is under the mouse.

The MouseLeave event handler deselects all items.

If the user clicks on an item, the following code executes.

private void lstItems_Click(object sender, EventArgs e)
{
    if (lstItems.SelectedIndex < 0) DialogResult = DialogResult.Cancel;
    else DialogResult = DialogResult.OK;
}

This code sets the form's DialogResult property to OK or Cancel, depending on whether the ListBox has an item selected. Setting DialogResult automatically makes the form close and returns control to the program that displayed the form. It also makes the call to ShowDialog used to display the form return the value if DialogResult (either OK or Cancel).

If the user presses a key while the context menu form is visible, the following code executes.

// Close the dialog if the user presses Escape or Enter.
private void ModalMenuForm_KeyDown(object sender, KeyEventArgs e)
{
    // If the user pressed Escape, return DialogResult.Cancel.
    if (e.KeyCode == Keys.Escape) DialogResult = DialogResult.Cancel;

    // If the user pressed Escape, return
    // DialogResult.OK if a color is selected.
    if (e.KeyCode == Keys.Enter)
    {
        if (lstItems.SelectedIndex < 0) DialogResult = DialogResult.Cancel;
        else DialogResult = DialogResult.OK;
    }
}

If the user presses Escape, this code sets DialogResult to Cancel to close the form.

If the user presses Enter, the code sets DialogResult to either OK or Cancel depending on whether an item is currently selected in the ListBox. (If you don't want the user to be able to close the context menu without making any selection, comment out all of the statements that set DialogResult equal to Cancel.)

The final piece of the context menu form is the following SelectedColor property.

// Return the color selected in the ListBox.
public Color SelectedColor
{
    get
    {
        if (lstItems.SelectedItem == null) return SystemColors.Control;
        string color_name = lstItems.SelectedItem.ToString().Replace(" ", "");
        return Color.FromName(color_name);
    }
}

This read-only property returns the color that is currently selected in the ListBox. The code that displays the context menu should use this property to see which color the user selected. (In a program that uses the menu to select something else, you probably wouldn't want this property to return a color. Instead it could return the selection's index, the selection's text, or some other appropriate value.)

To find the color selected by the user, this code takes the name of the selected ListBox item (such as "Light Green"), removes any spaces (to get "LightGreen"), and uses the Color class's FromName method to get the appropriate color.

The Main Form

The main form uses the following code to display the context menu form.

// Display the "modal context menu."
private void btnModalMenu_Click(object sender, EventArgs e)
{
    // Create and initialize the dialog.
    ModalMenuForm dlg = new ModalMenuForm();
    Point pt = new Point(btnModalMenu.Right, btnModalMenu.Bottom);
    PositionDialog(dlg, pt);

    // Display the dialog.
    if (dlg.ShowDialog() == DialogResult.OK)
    {
        this.BackColor = dlg.SelectedColor;
    }
}

The code creates a new context menu form. It then gets a Point representing the location where the modal context menu should appear. This example positions the context menu at the lower right corner of the form's Modal Menu button. (See the picture.)

Next the code calls the PositionDialog method (shown shortly) to position the context menu form at that location.

The code then displays the context menu form by calling its ShowDialog method. ShowDialog displays the form modally and returns OK or Cancel depending on whether the user accepted or canceled the menu.

The following code shows the final piece to the example, the PositionDialog method.
// Position the dialog over the indicated point in this form's copordinates.
private void PositionDialog(Form dlg, Point location)
{
    // Translate into screen coordinates.
    Point pt = this.PointToScreen(location);
    Screen screen = Screen.FromControl(dlg);

    // Adjust if this is at an edge of the screen.
    if (pt.X < screen.WorkingArea.X)
        pt.X = screen.WorkingArea.X;
    if (pt.Y < screen.WorkingArea.Y)
        pt.Y = screen.WorkingArea.Y;
    if (pt.X > screen.WorkingArea.Right - dlg.Width)
        pt.X = screen.WorkingArea.Right - dlg.Width;
    if (pt.Y > screen.WorkingArea.Bottom - dlg.Height)
        pt.Y = screen.WorkingArea.Bottom - dlg.Height;

    // Position the dialog.
    dlg.StartPosition = FormStartPosition.Manual;
    dlg.Location = pt;
}

This method converts the indicated point from form the main form's coordinates to screen coordinates. It then gets the screen that holds the dialog. It adjusts the indicated point if necessary to keep the dialog on the screen. The method finishes by setting the dialog's StartPosition and Location properties so it begins in the desired location when it is displayed.

For more details, see these posts:

The process isn't as easy as displaying a ContextMenuStrip, but if you follow each of the steps you should be able to display a homegrown modal context menu.

   

Understand the startup form (or main form) in C#

When a C# Windows Forms program starts, it displays a startup form. That form has a special place in the program's life cycle. If that form ever closes, the application ends.

The program can create and close as many other forms as it wants in any order without closing the program. The startup form is special in that respect.

When you click this example's New Form button, the following code displays a new instance of the main form.

// Display a new form with a blue background.
private void btnNewForm_Click(object sender, EventArgs e)
{
    Form1 frm = new Form1();
    frm.BackColor = Color.LightBlue;
    frm.Show();
}

This code simply creates a new instance of the form, sets its background color to light blue so you can distinguish the new forms from the startup form, and displays the form.

Run the program and create and close a few forms. Then create a form or two and close the startup form to see that the whole program stops.

This is not really much of a problem as long as you are aware of the situation.

So what can you do if you don't want the user to stop the application by closing the main form? This example uses the following FormClosing event handler to prevent the startup form from closing if any form's "Prevent Startup From Closing" CheckBox is checked.

// If this is the main form and any other forms have their
// Prevent Startup From Closing boxes checked, don't close.
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    // See if this is the main form.
    if (this == Application.OpenForms[0])
        // See if any form has the CheckBox checked.
        foreach (Form1 frm in Application.OpenForms)
            if (frm.chkPreventStartupFromClosing.Checked)
            {
                e.Cancel = true;
                break;
            }
}

If the form that is executing the FormClosing event handler is the startup form, then it is first in the Application object's OpenForms collection. If this is the startup form, then the code loops through all of the open forms. If the CheckBox on any open form is checked, then the event handler sets e.Cancel to true to prevent the form from closing.

   

Position a form so it does not stick off the edge of the screen in C#

In the picture, the program initially positioned the top form so it was hanging off the right and bottom edges of the screen. The program's code moved the form so it fit in the screen's lower right corner.

When you click this form's New Form button, the following code displays a new instance of the program's form.

// Position a new instance of this form
// just to the right and below the button.
private void btnShowForm_Click(object sender, EventArgs e)
{
    // Get the location in this form's coordinate system.
    Point form_pt = new Point(
        btnShowForm.Left + btnShowForm.Width / 2,
        btnShowForm.Top + btnShowForm.Height / 2);

    // Translate into the screen coordinate system.
    Point screen_pt = this.PointToScreen(form_pt);

    // Create the new form.
    Form1 frm = new Form1();

    // Make sure the form will be completely on the screen.
    Screen screen = Screen.FromControl(this);
    if (screen_pt.X < 0) screen_pt.X = 0;
    if (screen_pt.Y < 0) screen_pt.Y = 0;
    if (screen_pt.X > screen.WorkingArea.Right - frm.Width)
        screen_pt.X = screen.WorkingArea.Right - frm.Width;
    if (screen_pt.Y > screen.WorkingArea.Bottom - frm.Height)
        screen_pt.Y = screen.WorkingArea.Bottom - frm.Height;

    // Position the new form.
    frm.StartPosition = FormStartPosition.Manual;
    frm.Location = screen_pt;
    frm.Show();
}

The code makes a Point representing the center of the button. It calls PointToScreen to translate the point's coordinates to screen coordinates.

The code then creates a new form. It gets the Screen object that is displaying the form and adjusts the Point's coordinates to ensure that the form is completely on that Screen.

Finally the code sets the form's position and displays the form.

   

Find the Screen object on which a form is running in C#

This is an issue if the user is using more than one monitor.

When this program starts, it uses the following code to get the Screen object that holds the form and display its device name, whether it is the primary screen, and its working area.

private void Form1_Load(object sender, EventArgs e)
{
    Screen screen = Screen.FromControl(this);
    txtDeviceName.Text = screen.DeviceName;
    txtIsPrimary.Text = screen.Primary.ToString();
    txtWorkingArea.Text = screen.WorkingArea.ToString();
    txtDeviceName.Select(0, 0);
}

The only non-obvious part of this code is the first statement where the program uses Screen.FromControl to get the Screen object holding the form.

   

Position a form over a location on the creating form in C#

When you click this example's button, the program uses the following code to display a new instance of the form positioned next to the button's lower left corner (as shown in the picture).

// Position a new instance of this form
// just to the right and below the button.
private void btnShowForm_Click(object sender, EventArgs e)
{
    // Get the location in this form's coordinate system.
    Point form_pt = new Point(btnShowForm.Right, btnShowForm.Bottom);

    // Translate into the screen coordinate system.
    Point screen_pt = this.PointToScreen(form_pt);

    // Create and position the form.
    Form1 frm = new Form1();
    frm.StartPosition = FormStartPosition.Manual;
    frm.Location = screen_pt;
    frm.Show();
}

The code creates a Point that holds the new form's desired location in this form's coordinate system. It then calls the form's PointToScreen method to convert the point into screen coordinates.

The code finishes by creating, positions, and displaying a new form. The only trick here is remembering to set the new form's StartPosition property to Manual so the form is positioned where its Location property says it should be.

   

Apply a filter to make a color embossed image in C#

This example extends the earlier one Apply filters to images to perform edge detection, smoothing, embossing, and more in C# by adding a new filter that embosses a color image without removing most of the color information. The filter's kernel is:

11-1
11-1
1-1-1

The kernel divides the result by 1 and uses no offset so it retains most of the image's color information. See the previous post for more information about how kernels work.

   

Use listeners to send Debug and Trace information into a file in C#

By default, the Debug and Trace class send messages to the Console window. Sometimes it might be nice to capture that text and save it in a file. This example shows how you can do that.

The Debug and Trace classes have a shared Listener collection that contains listener objects. The classes actually send their message to those objects not directly to the Console window. (Initially the Listeners collection contains a single listener that sends the text it receives to the Console window.)

You can remove the default listener from the collection if you like and you can add new listeners to the collection. This example uses the following code to add a TextWriterListener that appends the text it receives to a text file.

// Make the listener.
private void Form1_Load(object sender, EventArgs e)
{
    string trace_file = Application.StartupPath + "//trace.txt";
    Trace.Listeners.Add(new TextWriterTraceListener(trace_file));
    Trace.AutoFlush = true;
}

The code composes a file name. It then creates a TextWriterTraceListener, passing its constructor the file name, and adds the new listener to the Trace object's Listener collection. The Debug and Trace classes share Listeners collections so this listener will write both Trace and Debug messages into the file.

By default the Debug and Trace classes do not flush text buffers into text files so if the program ends without you explicitly flushing the output, some or all of the text may not be written into the file. The program avoids this problem by setting the Trace object's AutoFlush property to true.

If you enter a message in the program's TextBox and click Debug, the program executes the following code.

// Send a message to the Debug listeners.
private void btnDebug_Click(object sender, EventArgs e)
{
    Debug.WriteLine(txtMessage.Text);
    txtMessage.Clear();
    txtMessage.Focus();
}

This code uses the Debug.WriteLine method to send a message to the listeners. The default listener sends the text to the Console window and the other listener appends the text to the text file trace.txt.

   

Use branch and bound to recursively find the highest value path through a two-dimensional array of numbers in C#

(This method is similar to one that is described on my forthcoming book A Practical Introduction to Computer Algorithms. I'll post more about it later when it's closer to publication.)

The examples Recursively find the highest value path through a two-dimensional array of numbers in C# uses recursion to exhaustively search for the best possible path through the values in an array.

This is effective and reasonably simple but it can be very time consuming. There are roughly 3 choices at each row in the array (ignoring edge effects) and the program could start in any column. If the array has 14 rows and columns, then there are roughly 14 * 314 = 66,961,566 possible paths. In fact there are 24,590,106 paths through that array, still a big number.

Branch and bound is a technique that lets you trim many of the paths from the search. As it recursively builds test solutions, the algorithm keeps track of the test solution's total value. At each step, it calculates the maximum amount by which the value could be increased. For this example, it multiples the number of rows it has not yet examined by the maximum possible value of an array entry and adds that to the test solution's value. it is unlikely that the test solution could actually be improved to that value but this gives an upper bound on the test solution's value. If the total maximum possible value is smaller than the value of the best solution found so far, then the algorithm stops considering that test solution.

This may seem like a small change that wouldn't remove many paths from the search, but it can be quite effective. You can see in the picture that in one trial branch and bound reduced the number of values recursively examined from more than 24 million to only 30 thousand.

The following code shows the new search method. The highlighted lines show differences between this version and the previous exhaustive search.

// Recursively explore paths starting with the indicated row.
// Return the path's total value.
private void FindPath2(int[] test_path, int test_total, int row)
{
    NumFindPathCalls++;

    // See if we have a complete solution.
    if (row >= NumRows)
    {
        // We have a complete solution.
        // See if it is better than the best solution so far.
        if (test_total > BestTotal2)
        {
            // This is an improvement.
            BestTotal2 = test_total;
            Array.Copy(test_path, BestPath2, NumRows);
        }
    }
    else
    {
        // We don't have a complete solution.

        // See if there's any chance we can improve
        // on the best solution found so far.
        int rows_remaining = NumRows - row;
        if (test_total + MaxValue * rows_remaining <= BestTotal2) return;

        // Try the possibilities for this row.

        // Get the column used in the previous row.
        int prev_col = test_path[row - 1];

        // Column - 1
        if (prev_col > 0)
        {
            int col = prev_col - 1;
            test_path[row] = col;
            FindPath2(test_path, test_total + Numbers[row, col], row + 1);
        }

        // Column
        test_path[row] = prev_col;
        FindPath2(test_path, test_total + Numbers[row, prev_col], row + 1);

        // Column + 1
        if (prev_col < NumCols - 1)
        {
            int col = prev_col + 1;
            test_path[row] = col;
            FindPath2(test_path, test_total + Numbers[row, col], row + 1);
        }
    }
}

The algorithm still searches a lot of paths so this only works up to a point. For larger problems, even it isn't fast enough and you'll need to use heuristics to search for approximate paths through the array.

(The example program's DisplayNumbers method also shows the branch and bound solution with red letters so you can compare it to the exhaustive solution that has a green background.)

   

Calendar

May 2013
SuMoTuWeThFrSa
1234
567891011
12131415161718
19202122232425
262728293031

Subscribe


Blog Software
Blog Software