Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

A Tool to Strip Zips of Unwanted Files before Submitting to CodeProject - Version 2

4.17/5 (6 votes)
24 Dec 2008CPOL8 min read 32.7K   330  
An article on some minor modifications to CPZipStripper
CPZipStripper Mainform Image

Introduction

I can do no better than to copy the introduction from Nish's original article.

The CPZipStripper is a simple tool I've been using when editing and submitting articles on CodeProject, and all it does is remove unwanted files from the zips - like debug and obj folder files, SUO files, PDB files, APS files, etc. to name a few. It's a one-click tool - so you don't have to waste time opening the zip in WinZip or some such tool and then manually delete unwanted files.

If you try out the original version of CPZipStripper and the 'Modify Configuration' button brings up the configuration file for editing in your preferred editor, you do not need this version.

I have included two versions of this tool in the downloads (above). Version 1.1 has conveniently been built using VS 2003 and .NET 1.1.  Version 1.2 was built using VS 2008 targetting .NET 2.0.

Background

Whilst recently writing my first article for CP, I stumbled on CPZipStripper and found it to be a really useful tool. It saves a lot of time when creating a zipped file of your source code to include with your article. After using it for my articles, I started to explore some of its other features. When I clicked on the Modify Configuration button, to see what it did, I was surprised to get an error message telling me "Your system does not have a specified XML editor.", because I do have an XML Editor. One that I like.

On investigating the code, I located the part that was causing this problem on my system. The XML Editor that I use did not set itself up as an 'Editor' when it was installed, it had made all the necessary registry entries for it to be the application to use when double clicking an XML file in Explorer but that is all done using the Open verb. CPZipStripper was looking for an Edit verb.

First of all, I started fiddling with the registry, after taking a backup, but soon realized that it was far too complicated and in addition, I didn't really know what I was doing. It occurred to me that it would be simpler to modify the source code, so I restored my registry and started to modify the source. Now there are those who would say I don't know what I am doing there either, but I pressed on. In addition to changing the code to allow it to try to Open an Editor if the Edit option failed, I decided to allow the user to select a default Editor for this tool. Having made the changes to suit myself, it occurred to me that others might find this useful. I therefore contacted Nishant Sivakumar, the original author of CPZIPStripper, who kindly agreed to my submitting this article.

Using the Code

Please read the original CPZipStripper article in addition to this one.

The changes I have made to the code of the original tool are mainly in the Click event handler for the 'Modify Configuration' button.
The method I have chosen to implement the changes, requires that the users preferences are stored, so I developed a settings class, referenced in the code below as zsSettings. The Settings Class is covered later.

Original Code

C#
private void BtnConfig_Click(object sender, System.EventArgs e)
{
	ProcessStartInfo psi = new ProcessStartInfo();

	try
	{
		psi.FileName = cfgxmlpath;
		psi.Verb = "Edit";
		psi.UseShellExecute = true;
		Process.Start(psi);
	}
	catch(Exception)
	{
		MessageBox.Show(
			"Your system does not have a specified XML editor.",
			"Error : XML editor missing!",
			MessageBoxButtons.OK, MessageBoxIcon.Error);
	}
}

New Code for V 1.1

C#
private void BtnConfig_Click(object sender, System.EventArgs e)
{
    ProcessStartInfo psi = new ProcessStartInfo();
    bool edited = false;

    if (!this.zsSettings.SetByUser)
    {
        try
        {
            // Look for system default Editor
            psi.FileName = cfgxmlpath;
            psi.Verb = "Edit";
            psi.UseShellExecute = true;
            Process.Start(psi);
            edited = true;
        }
        catch (Exception)
        {
            // No App listed for Edit, try Open.
            try
            {
                psi.FileName = cfgxmlpath;
                psi.Verb = "Open";
                psi.UseShellExecute = true;
                Process.Start(psi);
                edited = true;
            }
            catch
            {
                if (!this.zsSettings.SetByUser)
                {
                    // No Edit OR Open, Ask user for App.
                    if (MessageBox.Show("Your system does not have a
			specified XML editor." + Environment.NewLine +
			"Do you want to select a default application
			to edit XML files?", "No Default XML Editor Found!",
                        MessageBoxButtons.YesNo, MessageBoxIcon.Warning) ==
							DialogResult.Yes)
                    {
                        this.GetNewXmlEditor();
                    }
                }
            }
        }
    }

    if (!edited)
    {
        try
        {
            string editorfile = (string)this.zsSettings.XmlEditor;
            if (cfgxmlpath.IndexOf(" ") >= 0)
            {
                Process.Start(editorfile, "\"" + cfgxmlpath + "\"");
            }
            else
            {
                Process.Start(editorfile, cfgxmlpath);
            }
        }
        catch
        {
            MessageBox.Show("This tool is unable to edit the configuration file." +
		Environment.NewLine +
                "Close this application and try editing it manually,
		then restart this program.");
        }
    }
}

New Code for V 1.2

C#
private void BtnConfig_Click(object sender, System.EventArgs e)
{
	ProcessStartInfo psi = new ProcessStartInfo();
	bool edited = false;

	if (!this.zsSettings.SetByUser)
	{
		try
		{
			// Look for a system default editor
			psi.FileName = configXmlPath;
			psi.Verb = "Edit";
			psi.UseShellExecute = true;
			Process.Start(psi);
			edited = true;
		}
		catch (Exception)
		{
			// No App listed for Edit, try Open.
			try
			{
				psi.FileName = configXmlPath;
				psi.Verb = "Open";
				psi.UseShellExecute = true;
				Process.Start(psi);
				edited = true;
			}
			catch
			{
				if (!this.zsSettings.SetByUser)
				{
					// No Edit OR Open, Ask user for App.
					if (MessageBox.Show("Your system does not
						have a specified XML editor." +
					Environment.NewLine + "Do you want to
						select a default application
						to edit XML files?",
						"No Default XML Editor Found!",
						MessageBoxButtons.YesNo,
						MessageBoxIcon.Warning) ==
						DialogResult.Yes)
					{
						this.GetNewXmlEditor();
					}
				}
			}
		}
	}

	if (!edited)
	{
		try
		{
			string editorfile = this.zsSettings.XmlEditor;
			if (this.configXmlPath.Contains(" "))
			{
				Process.Start(editorfile, "\"" +
						this.configXmlPath + "\"");
			}
			else
			{
				Process.Start(editorfile, this.configXmlPath);
			}
		}
		catch
		{
			MessageBox.Show("This tool is unable to edit the
				configuration file." + Environment.NewLine +
				"Close this application and try editing it
				manually, then restart this program.");
		}
	}
}

The two new versions are practically identical, any differences are to allow for changes from .NET 1.1 and .NET 2.0.

The upper part of the method is contained in an if statement which tests whether the user has set an explicit default editor for this tool to use. There is no point in searching for system defaults in Windows, if they have.

Within the if block are two nested try/catch blocks. The outer block is the code from the original CPZipStripper. If this finds a registered editor, no problems, it loads the configuration file, for editing. When the editor is closed, it sets a flag, for use later. If there is an Exception the inner try/catch block is executed. The try section basically repeats the process from the outer block but replaces the Edit verb with Open. Again if a registered editor is found, it is used then the flag is set. An Exception from the inner block causes users to be presented with the option to select a default editor, or not. If they elect to select an editor, they are presented with the <a href="#xmlec">XMLEditorChooser</a> dialog, covered below, before control passes to the last section of the method, as it does if they chose not to set a default editor. The last section checks if the configuration has been edited. If it has, the method returns. If not, the editor setting is stored in a string. (I initially did it this way whilst debugging and as it doesn't really cause any problems and it's more readable, I left it like that). The path to the configuration file is checked for the presence of spaces. If there are any, the path gets enclosed in quotation marks.

The editor file is launched with the configuration file as the only parameter. If this causes an Exception, users are notified that there is no hope for them, and that they may as well give up.

The XMLEditorChooser Dialog

The visually obvious difference between these two versions and the original, is the addition of a 'Set XML Editor' button. Clicking this button displays a dialog which enables you to perform various tasks relating to the selection of an XML Editor that this tool will use.

Here is the dialog:

CPZipStripper SetEditor Dialog Image

There are three mutually exclusive options. Starting from the bottom and working up:

  1. If you check the 'Remove Current Settings' CheckBox, regardless of any other setting on the form, when you click OK the settings revert to the defaults:
    • XMLEditor - notepad.exe
    • SetByUser - false
      This means that if you click the 'Modify Configuration' button on the main form, don't have a default editor and decline the opportunity to select one, Notepad will be used as if it were the default editor.
      I have done this because I know that all you He-Men out there won't use anything other than Notepad for XML and HTML editing.
  2. If you check the 'Default to Notepad' CheckBox, but not the 'Remove Current Settings' one, regardless of the contents of the editor TextBox the settings will be set to:
    • XMLEditor - notepad.exe
    • SetByUser - true
      This means that  when you click the 'Modify Configuration' button on the main form, Notepad will be used as the editor, without any further option being explored.
  3. If you set the editor TextBox contents to a valid executable file but do not check either of the CheckBoxes the settings will be set to:
    • XMLEditor - executable from TextBox
    • SetByUser - true
      This means that when you click the 'Modify Configuration' button on the main form, your selected executable will be used as the editor, without any further option being considered.

Code for the Dialog

C#
public partial class XMLEditorChooser : Form
{
	private CPZipStripperSettings settings;

	protected XMLEditorChooser()
	{
		InitializeComponent();
	}

	internal static DialogResult Show(CPZipStripperSettings settings)
	{
		XMLEditorChooser chooser = new XMLEditorChooser();
		// save a reference to the settings in the dialog.
		chooser.settings = settings;
		// Set up defaults for FileChooser
		chooser.editorFileChooser.SelectedFile = settings.XmlEditor;
		chooser.editorFileChooser.FileDialog.Title =
				"Select a Default XML Editor";
		chooser.editorFileChooser.FileDialog.Filter =
				"Executable Files (*.EXE)|*.exe";
		chooser.editorFileChooser.FileDialog.Multiselect = false;

		return chooser.ShowDialog();
	}

	private void btnOK_Click(object sender, EventArgs e)
	{
		if (this.chboxRemoveCurrent.Checked)
		{
			// default settings - notepad, not set by user
			this.settings.XmlEditor = "notepad.exe";
			this.settings.SetByUser = false;
		}
		else if (this.chboxNotePad.Checked)
		{
			// notepad, set by user
			this.settings.XmlEditor = "notepad.exe";
			this.settings.SetByUser = true;
		}
		else
		{
			// Only set if file exists.
			if (File.Exists(this.editorFileChooser.SelectedFile))
			{
				this.settings.XmlEditor =
					this.editorFileChooser.SelectedFile;
				this.settings.SetByUser = true;
			}
		}
	}
}

All of the work for modifying the settings is done in the Click event handler for the OK button. Therefore, regardless of whether you modify the CheckBoxes or the TextBox contents, if you click Cancel, nothing gets changed.

I will mention, briefly, the chooser control used for selecting a default editor. This is a control I was developing for my own use, at the time I decided to write this article. I had only just started the FileChooserLibrary and simply in order not to delay this article, I made some horrible fudges, such as giving direct access to the OpenFileDialog, rather than surfacing the appropriate properties. I have included the code for this library in the .NET 1.1 version of the source code, but only the DLL for the .NET 2.0 version. By all means, use this as a basis for a control of your own but there are almost certainly better, more complete interpretations out there, some quite probably here on CodeProject.

The CPZipStripperSettings Class

Most of the code previously presented is identical whether it is for .NET 1.1 or .NET 2.0. This class however differs to a greater extent, mainly due to the improvements in the way .NET 2.0 handles configuration files.

Code for .NET 1.1

C#
public class CPZipStripperSettings
{
    private bool appSettingsChanged;
    // Variables used to store the application settings.
    private string xmlEditor = "notepad.exe";
    private bool setByUser = false;
    private string cpzsSettingsFile;


    public CPZipStripperSettings()
    {
        //
        // TODO: Add constructor logic here
        //
        cpzsSettingsFile = Application.LocalUserAppDataPath +
            @"\CPZipStripper.config";
    }

    // Properties used to access the application settings variables.
    public string XmlEditor
    {
        get
        {
            return this.xmlEditor;
        }

        set
        {
            if (value != this.xmlEditor)
            {
                this.xmlEditor = value;
                appSettingsChanged = true;
            }
        }
    }

    public bool SetByUser
    {
        get
        {
            return this.setByUser;
        }

        set
        {
            if (value != this.setByUser)
            {
                this.setByUser = value;
                appSettingsChanged = true;
            }
        }
    }

    // Serializes the class to the config file
    // if any of the settings have changed.
    public bool SaveAppSettings()
    {
        if (this.appSettingsChanged)
        {
            XmlSerializer cpzssSerializer = null;

            try
            {
                // Create an XmlSerializer for the
                // CPZipStripperSettings type.
                cpzssSerializer = new XmlSerializer(typeof(CPZipStripperSettings));
                using (StreamWriter cpzssWriter =
        new StreamWriter(cpzsSettingsFile, false))
                {
                    // Serialize this instance of the CPZipStripperSettings
                    // class to the config file.
                    cpzssSerializer.Serialize(cpzssWriter, this);

                    // If the FileStream is open, close it.
                    cpzssWriter.Close();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        return appSettingsChanged;
    }

    // Deserializes the class from the config file.
    public bool LoadAppSettings()
    {
        XmlSerializer cpzssSerializer = null;
        bool fileExists = false;

        try
        {
            // Create an XmlSerializer for the CPZipStripperSettings type.
            cpzssSerializer = new XmlSerializer(typeof(CPZipStripperSettings));
            FileInfo fi = new FileInfo(cpzsSettingsFile);
            // If the config file exists, open it.
            if (fi.Exists)
            {
                using (FileStream myFileStream = fi.OpenRead())
                {
                    // Create a new instance of the CPZipStripperSettings by
                    // deserializing the config file.
                    CPZipStripperSettings myAppSettings =
                        (CPZipStripperSettings)cpzssSerializer.Deserialize
        (myFileStream);

                    // Assign the property values to this instance of
                    // the CPZipStripperSettings class.
                    this.XmlEditor = myAppSettings.XmlEditor;
                    this.setByUser = myAppSettings.SetByUser;
                    fileExists = true;
                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }

        if (XmlEditor == null)
        {
            // If xmlEditor is not set, default
            // to notepad.
            this.XmlEditor = "notepad.exe";
            this.SetByUser = false;
            this.appSettingsChanged = true;
        }

        return fileExists;
    }
}

Code for .NET 2.0

C#
internal sealed class CPZipStripperSettings : ApplicationSettingsBase
{
	// If you used [ApplicationScopedSetting()] instead of [UserScopedSetting()],
	// you would NOT be able to persist any data changes!
	[UserScopedSetting()]
	[SettingsSerializeAs(System.Configuration.SettingsSerializeAs.String)]
	[DefaultSettingValue("notepad.exe")]
	public string XmlEditor
	{
		get
		{
			return ((string)this["XmlEditor"]);
		}

		set
		{
			this["XmlEditor"] = value;
		}
	}

	// If you used [ApplicationScopedSetting()] instead of [UserScopedSetting()],
	// you would NOT be able to persist any data changes!
	[UserScopedSetting()]
	[SettingsSerializeAs(System.Configuration.SettingsSerializeAs.String)]
	[DefaultSettingValue("false")]
	public bool SetByUser
	{
		get
		{
			return (bool)this["SetByUser"];
		}

		set
		{
			this["SetByUser"] = value;
		}
	}
}

The version for .NET 2.0 is so much shorter because it derives from ApplicationSettingsBase which already implements methods for loading and saving the settings. The class for .NET 1.1 has to include those methods. Admittedly, the actual code for loading and saving is not complicated, fairly standard Serializer/FileStream stuff, it's just the inconvenience of having to do it. Also the .NET 2.0 version makes extensive use of Attributes, one to say what the scope of the setting is, one for the serialization type (string/XML/binary etc.) and the last one to give the default value for each setting.

Points of Interest

I learned several things whilst putting the stuff together for this article. One was that there is a reason that programmers who write really good apps, that just work regardless of how a system is set up, earn their bread, big time! When Nish originally wrote CPZipStripper, it worked on his system, and almost certainly on many others. It didn't work on mine however, because one application on my system had set itself up in a way different from the equivalent application on Nishs'. Who'd have thunk it!

History

  • 24th December, 2008: First publication of this article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)