A little System.DiagnosticsI am not going to get overly verbose here; however the importance of application logging is immeasurable. When applications are deployed, obtaining ‘real life’ application can assist in diagnostic and overall application health situations. Excessive logging can be ineffective (too much to analyze) and burdensome (logging to a text file that grows forever) if not implemented appropriately.
When developing and deploying an application having the flexibility of what, when and how to log can alleviate the burden and ineffectiveness of logging. The System.Diagnostics namespace has a number of classes available that allow for this type of flexible logging. The applied use of these classes allows a developer to specify what to log in an application at various levels of detail within one application without the need to deploy diagnostic builds. Depending on diagnostic needs, the application’s config file can specify the level of diagnostic information to write and where. A simple Logging class (this is the class I created to use this functionality) can be developed as follows:
namespace BPSoftware
...{
public class Logging
...{
/**//// <summary>
/// strNameSpace is the base namespace for use in the logging message.
/// </summary>
public static string strNameSpace;

/**//// <summary>
/// GeneralTraceSwitch is a traceswitch object for logging. Switch level is set through
/// the application config switches section. The destination listener is also set through
/// the application config file.
/// </summary>
public static TraceSwitch GeneralTraceSwitch = new TraceSwitch( "General", "General Trace Switch" );

Methods#region Methods
/**//// <summary>
/// Writeline is the base logging method. Messages are logged to the listener specified
/// in the application config file.
/// </summary>
/// <param name="strClassName">Calling class name.</param>
/// <param name="strMethodName">Calling method name.</param>
/// <param name="strMessage">The message sent to the listener.</param>
public static void WriteLine( string strClassName, string strMethodName, string strMessage )
...{
lock ( typeof( Logging ) )
...{
Trace.WriteLine( string.Concat(
new object[] ...{
"(", DateTime.Now, ") ",
strNameSpace, ".", strClassName, ".", strMethodName, " -->", strMessage
}
) );
}
}

/**//// <summary>
/// Pass information to the base WriteLine if the TraceError switch is set.
/// </summary>
/// <param name="strClassName">Calling class name.</param>
/// <param name="strMethodName">Calling method name.</param>
/// <param name="strMessage">The message sent to the listener.</param>
public static void WriteLineTraceError( string strClassName, string strMethodName, string strMessage )
...{
if ( Logging.GeneralTraceSwitch.TraceError )
WriteLine( strClassName, strMethodName, strMessage );
}

/**//// <summary>
/// Pass information to the base WriteLine if the TraceWarning switch is set.
/// </summary>
/// <param name="strClassName">Calling class name.</param>
/// <param name="strMethodName">Calling method name.</param>
/// <param name="strMessage">The message sent to the listener.</param>
public static void WriteLineTraceWarning( string strClassName, string strMethodName, string strMessage )
...{
if ( Logging.GeneralTraceSwitch.TraceWarning )
WriteLine( strClassName, strMethodName, strMessage );
}

/**//// <summary>
/// Pass information to the base WriteLine if the TraceInfo switch is set.
/// </summary>
/// <param name="strClassName">Calling class name.</param>
/// <param name="strMethodName">Calling method name.</param>
/// <param name="strMessage">The message sent to the listener.</param>
public static void WriteLineTraceInfo( string strClassName, string strMethodName, string strMessage )
...{
if ( Logging.GeneralTraceSwitch.TraceInfo )
WriteLine( strClassName, strMethodName, strMessage );
}

/**//// <summary>
/// Pass information to the base WriteLine if the TraceVerbose switch is set.
/// </summary>
/// <param name="strClassName">Calling class name.</param>
/// <param name="strMethodName">Calling method name.</param>
/// <param name="strMessage">The message sent to the listener.</param>
public static void WriteLineTraceVerbose( string strClassName, string strMethodName, string strMessage )
...{
if ( Logging.GeneralTraceSwitch.TraceVerbose )
WriteLine( strClassName, strMethodName, strMessage );
}
#endregion
}
}
catch ( Exception ex)
...{
Logging.WriteLineTraceError( "ClassName", "Method", "Exception: " + ex.Message );
}
Logging.WriteLineTraceInfo( "ClassName", "Method",,
String.Format( "{0}: {1}", e.ActionType.ToString(), e.Message )
);
<system.diagnostics>
<trace autoflush="true" indentsize="4">
<!-- Specify listener output -->
<listeners>
<remove name="Default" />
<!-- Write to a log file -->
<add name="Default"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="AFileSync.log" />
<!-- Write to the Event Log-->
<!-- <add name="Default"
type="System.Diagnostics.EventLogTraceListener"
initializeData="Application" /> -->
</listeners>
</trace>
<switches>
<!-- Off = 0, Error = 1, Warning = 2, Info = 3, Verbose = 4 -->
<add name="General" value="3" />
</switches>
</system.diagnostics>
Control and List Windows Services
As I whipped up my file synchronizing service application, to synchronize files between my computer and my NAS), I worked with the System.ServiceProcess Namespace. The System.ServiceProcess Namespace "provides classes that allow you to implement, install, and control Windows service applications".
My file synchronization needs did not require that my application service to be "running" all the time. In, fact I only needed the service to run under certain situations. It was easy enough to get the service written and installed and now I wanted to 'automatically' (programmatically) start and stop my service when certain conditions were met.
Entrance - the ServiceController Class. The ServiceController Class "Represents a Windows service and allows you to connect to a running or stopped service, manipulate it, or get information about it." In order to get acclimated to the ServiceController Class I created a simple application to list and display information about the installed services.
using System;
using System.Windows.Forms;
using System.ServiceProcess;
namespace Services1
...{
public partial class Form1 : Form
...{
ServiceController controller;
public Form1()
...{
controller = new ServiceController();
controller.MachineName = ".";
InitializeComponent();
lstServices.DisplayMember = "DisplayName";
lstServices.ValueMember = "ServiceName";
lstServices.DataSource = ServiceController.GetServices();
}
private void StartService(string servicename)
...{
controller.ServiceName = servicename;
controller.Start();
}
private void PauseService(string servicename)
...{
controller.ServiceName = servicename;
if (controller.CanPauseAndContinue)
controller.Pause();
}
private void StopService(string servicename)
...{
controller.ServiceName = servicename;
if (controller.CanStop)
controller.Stop();
}

private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
...{
if (lstServices.SelectedItems.Count > 0)
...{
try
...{
controller.ServiceName = lstServices.SelectedValue.ToString();
textBox1.Text = String.Format(
"Service Name: {0}\r\nDisplay Name: {1}\r\nType: {2}\r\nStatus: {3}",
controller.ServiceName,
controller.DisplayName,
controller.ServiceType.ToString(),
controller.Status.ToString()
);
}
catch (Exception ex)
...{
textBox1.Text = String.Format("Error: \r\n{0}",ex.Message);
}
}
else
...{
textBox1.Clear();
}
}
private void btnStart_Click(object sender, EventArgs e)
...{
StartService(lstServices.SelectedValue.ToString());
}
private void btnPause_Click(object sender, EventArgs e)
...{
PauseService(lstServices.SelectedValue.ToString());
}
private void btnStop_Click(object sender, EventArgs e)
...{
StopService(lstServices.SelectedValue.ToString());
}
}
}
Let's Synchronize Some FilesRecently, I posted about the new addition to my backup strategy; a NAS. My intended use of this device is to have a hot backup of my files. The system I was using before was not capable of storing the amount of data I need backed up, nor was it easy to manage.
I modified the set of xCopy batch files that I used for my backups to write the files to the NAS.
This system worked fine, but I needed more. I manage a lot of data and have a lot of files. I wouldn't want to lose any in the event of a system failure. The one problem I see with using this process is the lack of a real time solution.
I was in search of a solution that would offer me real-time synchronization between my files and the NAS. I didn't have to search very far. The .NET Framework includes the FileSystemWatcher Class.
The FileSystemWatcher Class "Listens to the file system change notifications and raises events when a directory, or file in a directory, changes." (quoted directly from MSDN) After reading through this class I quickly whipped up a solution. I developed my own FileSync class that allowed me to easily synchronize files between my remote and local storage. I created a Windows Service (threaded) built around my class (the screen shot in this post is from a sample app I used to test my class) so that I could have the FileSync class running and keeping my files in sync without me needing to do anything. Here is the unit I created:
using System;
using System.IO;
using System.Diagnostics;
namespace BPSoftware
...{
class FileSync
...{
private FileSystemWatcher watcher;

Fields#region Fields
private string destpath;
public string DestPath
...{
get ...{ return destpath; }
set ...{ destpath = value; }
}
private string sourcepath;
public string SourcePath
...{
get ...{ return sourcepath; }
set ...{ sourcepath = value; }
}
private string filter;
public string Filter
...{
get ...{ return filter; }
set ...{ filter = value; }
}
private bool watchsubfolders;
public bool WatchSubFolders
...{
get ...{ return watchsubfolders; }
set ...{ watchsubfolders = value; }
}
#endregion

Constructors#region Constructors
public FileSync()
...{
this.watcher = new FileSystemWatcher();
this.watcher.InternalBufferSize = 0x20000;
this.watcher.NotifyFilter = NotifyFilters.FileNameNotifyFilters.LastWrite;
this.watcher.IncludeSubdirectories = false;
this.watcher.Changed += new FileSystemEventHandler(this.OnChanged);
this.watcher.Created += new FileSystemEventHandler(this.OnCreated);
this.watcher.Deleted += new FileSystemEventHandler(this.OnDeleted);
this.watcher.Renamed += new RenamedEventHandler(this.OnRenamed);
}
public FileSync(string source, string destination, string filefilter, bool watchsubs): this()
...{
this.SourcePath = source;
this.DestPath = destination;
this.Filter = filefilter;
this.WatchSubFolders = watchsubs;
}
#endregion

Methods#region Methods
private string GetDestinationFile(string infile)
...{
string fileName = Path.GetFileName(infile);
return (this.destpath + fileName);
}
private void LogEvent(string message, int id)
...{
EventLog.WriteEntry("FileSync", message, EventLogEntryType.Error, id);
}
public void Start()
...{
try
...{
if (this.watcher != null)
...{
this.watcher.Path = this.SourcePath;
this.watcher.Filter = this.Filter;
this.watcher.IncludeSubdirectories = this.WatchSubFolders;
this.watcher.Filter = this.Filter;
this.watcher.EnableRaisingEvents = true;
}
}
catch (Exception exception)
...{
this.LogEvent(exception.Message, 0x01);
}
}
public bool Stop()
...{
try
...{
if (this.watcher != null)
...{
this.watcher.EnableRaisingEvents = false;
this.watcher.Changed -= new FileSystemEventHandler(this.OnChanged);
this.watcher.Created -= new FileSystemEventHandler(this.OnCreated);
this.watcher.Deleted -= new FileSystemEventHandler(this.OnDeleted);
this.watcher.Renamed -= new RenamedEventHandler(this.OnRenamed);
}
}
catch (Exception exception)
...{
this.LogEvent(exception.Message, 0x02);
return false;
}
return true;
}
#endregion

Events#region Events
private void OnChanged(object source, FileSystemEventArgs e)
...{
try
...{
File.Copy(e.FullPath, this.GetDestinationFile(e.FullPath), true);
}
catch (Exception exception)
...{
this.LogEvent(exception.Message, 0x03);
}
}
private void OnCreated(object source, FileSystemEventArgs e)
...{
try
...{
File.Copy(e.FullPath, this.GetDestinationFile(e.FullPath));
}
catch (Exception exception)
...{
this.LogEvent(exception.Message, 0x04);
}
}
private void OnDeleted(object source, FileSystemEventArgs e)
...{
try
...{
File.Delete(this.GetDestinationFile(e.FullPath));
}
catch (Exception exception)
...{
this.LogEvent(exception.Message, 0x05);
}
}
private void OnRenamed(object source, RenamedEventArgs e)
...{
try
...{
File.Move(this.GetDestinationFile(e.OldFullPath), this.GetDestinationFile(e.FullPath));
}
catch (Exception exception)
...{
this.LogEvent(exception.Message, 0x06);
}
}
#endregion
}
}
DSOFile and Summary PropertiesYesterday I posted about using ShellExecuteEx to display the document properties dialog for a file or folder. Displaying the properties dialog is nice, however I also need to access the OLE document properties (Document Summary Properties), the information that is stored on the Summary and Custom tabs of the properties dialog. I did quite a bit of searching, reading and testing in an attempt to find a simple solution. I did get to learn a lot about the Document Summary Properties and it looked like my only option was to head down the road using the IPropertyStorage Interface. As I was testing some code I came across the best little ActiveX component that can quickly access the property information. The Dsofile.dll is an ActiveX component is available from Microsoft. They even include a sample application (although the samples are in VB6 and VB7 they are useable). This component simplifies (I like things simple) setting and getting the document summary properties. The KB article and download mention that this is meant for Office files, however I have found that it works fine on other files as well (the exception being that the Office documents seem to be the only types of files with Custom Properties). The other great thing about this component is that "You have a royalty-free right to use, to modify, to reproduce, and to distribute the Dsofile.dll sample file component and the C++ source code files in any way you find useful." That quote is taken directly from the DSOfile.dll Article ID: 224351 that is listed on Microsoft's Support site. My thoughts, why reinvent the wheel when you really don't need to. I highly recommend using this component if you need to access the extended properties. As usual, I whipped up a sample application testing this components functionality. The meager sample code is listed here (image of application screen is also included in this post):
public partial class Form1 : Form
...{
public Form1()
...{
InitializeComponent();
}
private void btnSelectFile_Click(object sender, EventArgs e)
...{
if (openFileDialog1.ShowDialog() == DialogResult.OK)
...{
textBox1.Text = openFileDialog1.FileName;
toolTip1.SetToolTip(textBox1, textBox1.Text);
lstSummary.Items.Clear();
OleDocumentPropertiesClass odpc = new OleDocumentPropertiesClass();
odpc.Open(
@openFileDialog1.FileName,
false,
dsoFileOpenOptions.dsoOptionOpenReadOnlyIfNoWriteAccess
);
// Summary "Standard" Properties
ListViewItem lvTitle = new ListViewItem();
lvTitle.Group = lstSummary.Groups[0];
lvTitle.Text = "Title";
lvTitle.SubItems.Add(odpc.SummaryProperties.Title);
lstSummary.Items.Add(lvTitle);
ListViewItem lvSubject = new ListViewItem();
lvSubject.Group = lstSummary.Groups[0];
lvSubject.Text = "Subject";
lvSubject.SubItems.Add(odpc.SummaryProperties.Subject);
lstSummary.Items.Add(lvSubject);
ListViewItem lvCategory = new ListViewItem();
lvCategory.Group = lstSummary.Groups[0];
lvCategory.Text = "Category";
lvCategory.SubItems.Add(odpc.SummaryProperties.Category);
lstSummary.Items.Add(lvCategory);
ListViewItem lvKeyword = new ListViewItem();
lvKeyword.Group = lstSummary.Groups[0];
lvKeyword.Text = "Keywords";
lvKeyword.SubItems.Add(odpc.SummaryProperties.Keywords);
lstSummary.Items.Add(lvKeyword);
ListViewItem lvComments = new ListViewItem();
lvComments.Group = lstSummary.Groups[0];
lvComments.Text = "Comments";
lvComments.SubItems.Add(odpc.SummaryProperties.Comments);
lstSummary.Items.Add(lvComments);
ListViewItem lvAuthor = new ListViewItem();
lvAuthor.Group = lstSummary.Groups[1];
lvAuthor.Text = "Author";
lvAuthor.SubItems.Add(odpc.SummaryProperties.Author);
lstSummary.Items.Add(lvAuthor);
ListViewItem lvRevision = new ListViewItem();
lvRevision.Group = lstSummary.Groups[1];
lvRevision.Text = "Revision Number";
lvRevision.SubItems.Add(odpc.SummaryProperties.RevisionNumber);
lstSummary.Items.Add(lvRevision);
// Custom Properties
foreach (DSOFile.CustomProperty cp in odpc.CustomProperties)
...{
ListViewItem lvc = new ListViewItem();
lvc.Group = lstSummary.Groups[2];
lvc.Text=cp.Name;
lvc.SubItems.Add(cp.get_Value().ToString());
lstSummary.Items.Add(lvc);
}
odpc.Close(false);
}
}
}This is something I'd also like to port to Delphi for use in those applications. Maybe this week sometime......
ShellExecuteEX and ShellExecuteInfo RevisitedWho says history doesn't repeat itself? I am working on a project that needs to display and access file properties. A quick search through the .NET 2.0 Framework didn't yield anything that popped out. I did however, remember our wonderful friend - the SHELL32 API. Previously, I had posted on using ShellExecuteEX with Delphi (I am a huge Delphi fan; I may not be posting much about it recently, but I did not forget it). Here is that same sample in using C#.
public const uint SW_SHOW =0x5;
public const uint SEE_MASK_INVOKEIDLIST = 0xC;
public enum verbs
...{
properties,
open,
edit,
explore,
print
}
[StructLayout(LayoutKind.Sequential)]
public struct SHELLEXECUTEINFO
...{
public int cbSize;
public uint fMask;
public IntPtr hwnd;
public String lpVerb;
public String lpFile;
public String lpParameters;
public String lpDirectory;
public uint nShow;
public int hInstApp;
public int lpIDList;
public String lpClass;
public int hkeyClass;
public uint dwHotKey;
public int hIcon;
public int hProcess;
}
[DllImport("shell32.dll")]
static extern bool ShellExecuteEx(ref SHELLEXECUTEINFO lpExecInfo);
public void MyShellExecuteInfo(string filename, verbs verb)
...{
SHELLEXECUTEINFO info = new SHELLEXECUTEINFO();
info.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(info);
switch (verb)
...{
case verbs.properties:
info.lpVerb = "properties";
break;
case verbs.print:
info.lpVerb = "print";
break;
case verbs.open:
info.lpVerb = "open";
break;
case verbs.explore:
info.lpVerb = "explore";
break;
case verbs.edit:
info.lpVerb = "edit";
break;
}
info.lpFile = filename;
info.nShow = SW_SHOW;
info.fMask = SEE_MASK_INVOKEIDLIST;
ShellExecuteEx(ref info);
}
A ToolStripMenuItem and a CheckOne nice addition to the .NET 2.0 framework is the ToolStripMenuItem Class. The ToolStripMenuItem Class replaces the MenuItem Class. The ToolStripMenuItem has a Checked property that gets or sets a value indicating if the item is checked or not. Coupled with the ToolStripMenuItem.CheckOnClick property, this can be useful for identifying which item has been selected or activated. Unfortunately, there isn't some sort of 'allow only one checked' property which allows for only one item in a menu to be 'Checked' at a time. This functionality can be easily added in code (or you could derive your own class that has this functionality; a nice addition to a personal control library). Create an OnClick and OnCheckChanged method that are referenced by each of the ToolStripMenuItems. Basically, you check to see if the current item is checked or not and then toggle the check for the other menu items. You could also check another property of the sender (such as the Tag property) to perform some other action.
Here is an example:
private void CheckedIconsToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
...{
if (sender is ToolStripMenuItem)
...{
if (!((ToolStripMenuItem)sender).Checked) return;
foreach (ToolStripMenuItem item in (((ToolStripMenuItem)sender).GetCurrentParent().Items))
...{
if (item != null && item != sender && item.Checked)
...{
item.Checked = false;
return;
}
}
}
}
private void CheckedToolStripMenuItem_Click(object sender, EventArgs e)
...{
if (sender is System.Windows.Forms.ToolStripMenuItem)
...{
if (!((ToolStripMenuItem)sender).Checked)
...{
((ToolStripMenuItem)sender).Checked = !((ToolStripMenuItem)sender).Checked;
};
switch (((ToolStripMenuItem)sender).Tag.ToString())
...{
case "1":
break;
case "2":
break;
case "3":
break;
case "4":
break;
case "5":
break;
default:
break;
};
}
}
A little FileIconInit for the System Image listI can hear the theme for Welcome Back Kotter as I write this. It seems that things have been quiet on here for quite sometime, however those that know me can attest to the fact that life away from here has been anything but that. Thankfully, things have returned to a point where a lot more content should find its way onto this world.
As things progress, I am doing a substantial amount of development using C# and the .NET Framework. I still continue to develop new utilities that satisfy my specific needs. I also try to update some of the available utilities. The most recent update has been to ASysIcon (this application was redeveloped using the .NET Framework). I've done quite a bit of work with icons in the past (also take a look at AIconExtract) and ASysIcon is a utility that displays the images found in the system image list. The system image list contains the icons that are associated with the different registered file types on a computer.
The retrieval of the system image list is a straight forward process. There are a few unmanaged API calls that work quite well for this task. One thing that may not be so apparent is that the the images retrieved will be those images that are cached in the system image list. If the system hasn't had a need to load them, well, they won't be in the list. This could pose some concern if you would like to retrieve the entire list. The trick is to get all the system images cached. Actually, this isn't really a trick. With little searching the FileIconInit function surfaces. This function will initialize or reinitialize this image list. FileIconInit has a boolean parameter determines if it should restore the image cache from disk. Passing true as this parameter will load all of the images, not just the recently cached ones. The following sample code shows how to display the system image list in a ListView:
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices; // http://support.microsoft.com/kb/319350
namespace SysIcons1
...{
public partial class Form1 : Form
...{
[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
...{
public IntPtr hIcon;
public IntPtr iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
//Retrieve the handle to the icon that represents the file and the index of the icon within the system image list
public const uint SHGFI_ICON = 0x100;
// Retrieve the index of a system image list icon.
public const uint SHGFI_SYSICONINDEX = 0x4000;
// Large icon
public const uint SHGFI_LARGEICON = 0x0;
// Small icon
public const uint SHGFI_SMALLICON = 0x1;
public const uint LVM_FIRST = 4096;
public const uint LVSIL_NORMAL = 0;
public const uint LVSIL_SMALL = 1;
public const uint LVM_SETIMAGELIST = LVM_FIRST + 3;
// need this style on Windows 9x
public const uint LVS_SHAREIMAGELISTS = 0x0040;
// Retrieves information about an object in the file system, such as a file, folder, directory, or drive root.
[DllImport("shell32.dll")]
public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
// The SendMessage function sends the specified message to a window or windows.
[DllImport("user32.dll")]
private static extern UInt32 SendMessage(IntPtr hWnd, UInt32 Msg,
UInt32 wParam, UInt32 lParam);
// Destroys an icon and frees any memory the icon occupied.
[DllImport("User32.dll")]
private static extern int DestroyIcon(System.IntPtr hIcon);
// Initializes or reinitializes the system image list.
// FileIconInit is not included in a header file. You must call it directly from Shell32.dll, using ordinal 660.
[DllImport( "Shell32.dll", EntryPoint = "#660")]
private static extern bool FileIconInit( bool fRestoreCache );
// Retrieves the number of images in an image list.
[DllImport( "comctl32.dll")]
private static extern int ImageList_GetImageCount( IntPtr himl);
public Form1()
...{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
...{
IntPtr hImgSmall; //handle to the small system image list
IntPtr hImgLarge; //handle to the large system image list
SHFILEINFO shinfo = new SHFILEINFO();
FileIconInit(true);
//Small Icons
hImgSmall = SHGetFileInfo("", 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_SYSICONINDEX SHGFI_SMALLICON);
//Large Icons
hImgLarge = SHGetFileInfo("", 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_SYSICONINDEX SHGFI_LARGEICON);
// Set Icon Images
SendMessage(listView1.Handle, LVM_SETIMAGELIST, LVSIL_SMALL, (uint)hImgSmall.ToInt32());
SendMessage(listView1.Handle, LVM_SETIMAGELIST, LVSIL_NORMAL, (uint)hImgLarge.ToInt32());
for ( int i = 0; i < ImageList_GetImageCount(hImgLarge) ; i++ )
...{
listView1.Items.Add(i.ToString(),i);
}
}
private int Get_Image_Index(string filename)
...{
IntPtr hImgSmall;
SHFILEINFO shinfo = new SHFILEINFO();
hImgSmall = SHGetFileInfo(filename, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo.GetType()), SHGFI_ICON SHGFI_SMALLICON);
DestroyIcon(shinfo.hIcon);
return (int)shinfo.iIcon;
}
}
A DataGridView, DataGridViewCheckBoxColumn, DataGridViewButtonColumn and DataColumn.
As with any “upgrade” new “things” are introduced. In this case I will refer to the DataGridView, DataGridViewCheckBoxColumn and the DataGridViewButtonColumn classes. The allows for the displaying of “data” in a grid (Note: when working with large amounts of data don’t forget to set the VirtualMode property). This source of the data can be anything that implements a IList, IListSource, IBindingList or IBindingListView interface. The data displayed doesn’t always have to be from a database, a one dimmensional array alos works well with a datagrid (take a look at the sample method PopulateDataGridView). It may sound intimidating, but this is all fairly straght forward.
namespace DrawInDataGrid1
...{
public partial class Form1 : Form
...{
private DataTable myTable;
private DataGridViewCheckBoxColumn checkcolumn;
private DataGridViewButtonColumn buttoncolumn;
private DataColumn colItem1, colItem2, colItem3;
private DataRow NewRow;
private DataView myView;
public Form1()
...{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
...{
// DataTable to hold data that is displayed in DataGrid
myTable = new DataTable("myTable");
// the three columns in the table
colItem1 = new DataColumn("CheckBox", Type.GetType("System.Boolean"));
colItem2 = new DataColumn("Button", Type.GetType("System.String"));
colItem3 = new DataColumn("String", Type.GetType("System.String"));
// add the columns to the table
myTable.Columns.Add(colItem1);
myTable.Columns.Add(colItem2);
myTable.Columns.Add(colItem3);
checkcolumn = new DataGridViewCheckBoxColumn(false);
checkcolumn.HeaderText = "CheckBox";
checkcolumn.DataPropertyName = "CheckBox";
buttoncolumn = new DataGridViewButtonColumn();
buttoncolumn.HeaderText = "Button";
buttoncolumn.DataPropertyName = "Button";
dataGridView1.Columns.Add(checkcolumn);
dataGridView1.Columns.Add(buttoncolumn);

// Fill in some data
NewRow = myTable.NewRow();
NewRow[0] = true;
NewRow[1] = "0";
NewRow[2] = "Test";
myTable.Rows.Add(NewRow);
NewRow = myTable.NewRow();
NewRow[0] = false;
NewRow[1] = "1";
NewRow[2] = "Next One";
myTable.Rows.Add(NewRow);
// DataView for the DataGridView
myView = new DataView(myTable);
myView.AllowDelete = false;
myView.AllowEdit = false;
myView.AllowNew = false;
// add an event to check for button click
this.dataGridView1.CellContentClick +=
new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridView1_CellContentClick);
// Assign DataView to DataGrid
dataGridView1.DataSource = myView;
}

private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
...{
if (sender is DataGridView)
...{
if ((((DataGridView)sender).Columns[e.ColumnIndex] is DataGridViewButtonColumn) &&
(e.RowIndex >= 0))
...{
MessageBox.Show(e.RowIndex.ToString());
}
}
}
}
}
System Information and the PerformanceCounter Class
The objective of that post was retrieving memory information (I also retrieve disk information in my ‘AboutBox’ discussed in that post). In the 'managed codeworld' there is another way to get different information values. Using the System.Diagnostics.PerformanceCounter Class you can retrieve ‘counter’ information from a local or remote machine. Many of you may be familiar with some of the information accessible through PerformanceCounters if you run the
System.Diagnostics.PerformanceCounter perfcounter;
private void cbxPerfCategories_SelectedIndexChanged(object sender, EventArgs e)
...{
cbxInstances.Enabled = true;
cbxInstances.Items.Clear();
cbxCounters.Items.Clear();
lstOutput.Items.Clear();
timer1.Enabled = false;
if (cbxPerfCategories.SelectedIndex != -1)
...{
if (PerformanceCounterCategory.Exists(cbxPerfCategories.Text))
...{
PerformanceCounterCategory perfcategory =
new PerformanceCounterCategory(cbxPerfCategories.Text);
cbxInstances.Items.AddRange(perfcategory.GetInstanceNames());
if (cbxInstances.Items.Count == 0)
...{
cbxInstances.Enabled = false;
// Get the counters for a category that has only one
// instance
GetCounters(perfcategory, "",cbxCounters);
}
}
}
}
private void GetCounters(PerformanceCounterCategory perfcategory,
string instancename,
ComboBox combobox)
...{
System.Diagnostics.PerformanceCounter[] counters;
if (instancename.Equals(""))
...{
counters = perfcategory.GetCounters();
}
else
...{
counters = perfcategory.GetCounters(instancename);
}
for (int i = 0; i < counters.Length; i++)
...{
combobox.Items.Add(counters[i].CounterName);
}
}
private void frmMain_Load(object sender, EventArgs e)
...{
PerformanceCounterCategory[] perfcategories;
try
...{
perfcategories = PerformanceCounterCategory.GetCategories();
for (int i = 0; i < perfcategories.Length; i++)
...{
cbxPerfCategories.Items.Add(perfcategories[i].CategoryName);
}
}
catch (Exception ex)
...{
MessageBox.Show(ex.Message, ex.Source, MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
private void cbxInstances_SelectedIndexChanged(object sender, EventArgs e)
...{
cbxCounters.Items.Clear();
lstOutput.Items.Clear();
timer1.Enabled = false;
if (cbxInstances.SelectedIndex != -1)
...{
if (PerformanceCounterCategory.InstanceExists(cbxInstances.Text, cbxPerfCategories.Text))
...{
PerformanceCounterCategory perfcategory =
new PerformanceCounterCategory(cbxPerfCategories.Text);
GetCounters(perfcategory, cbxInstances.Text, cbxCounters);
}
}
}
private void cbxCounters_SelectedIndexChanged(object sender, EventArgs e)
...{
lstOutput.Items.Clear();
if (cbxInstances.Text.Equals(""))
...{
perfcounter = new System.Diagnostics.PerformanceCounter(cbxPerfCategories.Text,
cbxCounters.Text, true);
}
else
...{
perfcounter = new System.Diagnostics.PerformanceCounter(cbxPerfCategories.Text,
cbxCounters.Text, cbxInstances.Text,true);
}
timer1.Enabled = true;
}
private void timer1_Tick(object sender, EventArgs e)
...{
float value;
value = perfcounter.NextValue();
lstOutput.Items.Insert(0,value.ToString("#,##0.00"));
}
Cryptography hash and a class - to goOne of the things I appreciate the most in OOP is the concept of classes. I have found that the use of classes allows for code to be easily maintained and reused. I look at classes as neat little packaged objects that can be ‘moved around’ and ‘worked with’ as their own. Another big benefit of classes is the concept of inheritance and polymorphism. In three previous posts (1, 2, 3) I discussed using the System.Security.Cryptography Namespace to calculate the hash values of text (strings) and files. I actually did create a class (that I actively use) from that sample.
using System;
using System.Text;
using System.Security.Cryptography;
using System.IO;
public class CryptoHash
...{
/**//// <summary>
/// public enum hashtypes { MD5, SHA1, SHA256, SHA384, SHA512 };
/// </summary>
public enum hashtypes ...{ MD5, SHA1, SHA256, SHA384, SHA512 };

Properties#region Properties
private Int64 _bytesread;
/**//// <summary>
/// Int64 Bytesread returns the number of bytes read when calculating the hash
/// value.
/// </summary>
public Int64 Bytesread
...{
get
...{
return _bytesread;
}
}
private hashtypes _hashtype;
/**//// <summary>
/// hashtypes Hashtype specifies the type of hash to calculate.
/// </summary>
public hashtypes Hashtype
...{
get
...{
return _hashtype;
}
set
...{
_hashtype = value;
}
}
#endregion

Constructors#region Constructors
public CryptoHash()
...{
}
public CryptoHash(hashtypes inhashtype)
...{
_hashtype = inhashtype;
}
#endregion

Methods#region Methods
private string GetByteString(byte[] data)
...{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.Length; i++)
...{
sb.Append(data[i].ToString("x2"));
}
string bytestring = sb.ToString();
return bytestring.ToUpper();
}
private string GetHash(byte[] data)
...{
HashAlgorithm ha;
switch (_hashtype)
...{
case hashtypes.MD5:
ha = MD5CryptoServiceProvider.Create();
break;
case hashtypes.SHA1:
ha = SHA1Managed.Create();
break;
case hashtypes.SHA256:
ha = SHA256Managed.Create();
break;
case hashtypes.SHA384:
ha = SHA384Managed.Create();
break;
case hashtypes.SHA512:
ha = SHA512Managed.Create();
break;
default:
ha = MD5CryptoServiceProvider.Create();
break;
}
_bytesread = data.Length;
byte[] res = ha.ComputeHash(data);
return GetByteString(res);
}

/**//// <summary>
/// GetStringHash calculates the hash value for a string.
/// </summary>
/// <param name="inputtext">String value.</param>
/// <returns>Returns the hash value of a file.</returns>
public string GetStringHash(string inputtext)
...{
return GetHash(Encoding.Default.GetBytes(inputtext));
}

/**//// <summary>
/// GetFileHash calculates the hash value for a file.
/// </summary>
/// <param name="filename">Full file path for the file.</param>
/// <returns>Returns the hash value of a file.</returns>
public string GetFileHash(string filename)
...{
_bytesread = 0;
byte[] filecontents;
FileInfo instance = new FileInfo(filename);
if (!instance.Exists)
...{
throw new FileNotFoundException();
}
else
...{
try
...{
FileStream ofs = instance.OpenRead();
filecontents = new byte[ofs.Length];
int offset = 0;
int count = filecontents.Length;
while (count > 0)
...{
int bytesread = ofs.Read(filecontents, offset, count);
count -= bytesread;
offset += bytesread;
}
// ofs.Read(filecontents, 0, filecontents.Length);
ofs.Close();
}
catch (Exception e)
...{
throw e;
}
}
return GetHash(filecontents);
}

/**//// <summary>
/// CompareStrings compares the hash value of two strings to determine
/// if they are the same.
/// </summary>
/// <param name="string1">The first string to compare.</param>
/// <param name="string2">The second string to compare.</param>
/// <returns>Returns True if the strings are identical (same hash value).</returns>
public bool CompareStrings(string string1, string string2)
...{
string1 = GetStringHash(string1);
string2 = GetStringHash(string2);
return ((string1.CompareTo(string2) == 0));
}

/**//// <summary>
/// CompareFiles compares the hash value of the contents of two files to determine
/// if they are the same.
/// </summary>
/// <param name="filename1">Full file path for the first file to compare.</param>
/// <param name="filename2">Full file path for the second file to compare.</param>
/// <returns>Returns True if the files are identical (same hash value).</returns>
public bool CompareFiles(string filename1, string filename2)
...{
filename1 = GetFileHash(filename1);
filename2 = GetFileHash(filename2);
return ((filename1.CompareTo(filename2) == 0));
}

/**//// <summary>
/// CompareStringToHash compares a string's hash with a hash to determine if the hash values are the same.
/// </summary>
/// <param name="string1">The string to compare.</param>
/// <param name="hash">The hash to compare to.</param>
/// <returns>Returns True if the string1 hash and hash match.</returns>