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
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
private void BtnConfig_Click(object sender, System.EventArgs e)
{
ProcessStartInfo psi = new ProcessStartInfo();
bool edited = false;
if (!this.zsSettings.SetByUser)
{
try
{
psi.FileName = cfgxmlpath;
psi.Verb = "Edit";
psi.UseShellExecute = true;
Process.Start(psi);
edited = true;
}
catch (Exception)
{
try
{
psi.FileName = cfgxmlpath;
psi.Verb = "Open";
psi.UseShellExecute = true;
Process.Start(psi);
edited = true;
}
catch
{
if (!this.zsSettings.SetByUser)
{
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
private void BtnConfig_Click(object sender, System.EventArgs e)
{
ProcessStartInfo psi = new ProcessStartInfo();
bool edited = false;
if (!this.zsSettings.SetByUser)
{
try
{
psi.FileName = configXmlPath;
psi.Verb = "Edit";
psi.UseShellExecute = true;
Process.Start(psi);
edited = true;
}
catch (Exception)
{
try
{
psi.FileName = configXmlPath;
psi.Verb = "Open";
psi.UseShellExecute = true;
Process.Start(psi);
edited = true;
}
catch
{
if (!this.zsSettings.SetByUser)
{
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 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:
There are three mutually exclusive options. Starting from the bottom and working up:
- 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.exeSetByUser
- 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.
- 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.exeSetByUser
- 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.
- 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
public partial class XMLEditorChooser : Form
{
private CPZipStripperSettings settings;
protected XMLEditorChooser()
{
InitializeComponent();
}
internal static DialogResult Show(CPZipStripperSettings settings)
{
XMLEditorChooser chooser = new XMLEditorChooser();
chooser.settings = settings;
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)
{
this.settings.XmlEditor = "notepad.exe";
this.settings.SetByUser = false;
}
else if (this.chboxNotePad.Checked)
{
this.settings.XmlEditor = "notepad.exe";
this.settings.SetByUser = true;
}
else
{
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 CheckBox
es 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.
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
public class CPZipStripperSettings
{
private bool appSettingsChanged;
private string xmlEditor = "notepad.exe";
private bool setByUser = false;
private string cpzsSettingsFile;
public CPZipStripperSettings()
{
cpzsSettingsFile = Application.LocalUserAppDataPath +
@"\CPZipStripper.config";
}
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;
}
}
}
public bool SaveAppSettings()
{
if (this.appSettingsChanged)
{
XmlSerializer cpzssSerializer = null;
try
{
cpzssSerializer = new XmlSerializer(typeof(CPZipStripperSettings));
using (StreamWriter cpzssWriter =
new StreamWriter(cpzsSettingsFile, false))
{
cpzssSerializer.Serialize(cpzssWriter, this);
cpzssWriter.Close();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
return appSettingsChanged;
}
public bool LoadAppSettings()
{
XmlSerializer cpzssSerializer = null;
bool fileExists = false;
try
{
cpzssSerializer = new XmlSerializer(typeof(CPZipStripperSettings));
FileInfo fi = new FileInfo(cpzsSettingsFile);
if (fi.Exists)
{
using (FileStream myFileStream = fi.OpenRead())
{
CPZipStripperSettings myAppSettings =
(CPZipStripperSettings)cpzssSerializer.Deserialize
(myFileStream);
this.XmlEditor = myAppSettings.XmlEditor;
this.setByUser = myAppSettings.SetByUser;
fileExists = true;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
if (XmlEditor == null)
{
this.XmlEditor = "notepad.exe";
this.SetByUser = false;
this.appSettingsChanged = true;
}
return fileExists;
}
}
Code for .NET 2.0
internal sealed class CPZipStripperSettings : ApplicationSettingsBase
{
[UserScopedSetting()]
[SettingsSerializeAs(System.Configuration.SettingsSerializeAs.String)]
[DefaultSettingValue("notepad.exe")]
public string XmlEditor
{
get
{
return ((string)this["XmlEditor"]);
}
set
{
this["XmlEditor"] = value;
}
}
[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