Red-letter day.
Halo 3 released at midnight.
Delivered today: LEGO Ultimate Collector’s Series Millennium Falcon.
Perfect.

Red-letter day.
Halo 3 released at midnight.
Delivered today: LEGO Ultimate Collector’s Series Millennium Falcon.
Perfect.

Here’s one that I won’t forget soon. I was working on an application that reads data from a Microsoft Access database and builds a string:
string MyString = MyTable.Rows[i]["MyColumn"].ToString(); string NewString = "stuff " + MyString + " some other stuff";
Pretty basic stuff. I was seeing a problem where NewString’s value was (assuming MyString = “MyStringData”) ”stuff MyStringData”–it cut off everything in the string after MyString.
The issue? When MyString is assigned, it includes \0 at the end (the string terminator character from C). .NET apparently stops marshalling at that point. I corrected it by:
string MyString = MyTable.Rows[i]["MyColumn"].ToString().Replace("", "");
I don’t know whether Access returned it terminated, or the application that originally wrote the data added the string terminator (which I suspect, as I’ve written tomes of code that pull data from Access in my life). I finally found it by breaking into the application, and when I hovered the cursor over MyString its data was displayed as “MyStringData\0″. (FYI, it appeared as just “MyStringData” in the string visualizer and Watch window, it was only the hover-over that gave it away.)
I’ve been working on an application the past few days that iterates through directories and processes files within those directories. I was looking for a good way to keep the user informed of progress, and I decided that using System.ComponentModel.BackgroundWorker was my best solution.
Here’s some relevant code for how I used UserState to keep the user updated of the progress:
namespace BackgroundWorkerSample
{
public partial class Main : Form
{
#region Form Callbacks
private void btnCancel_Click(object sender, EventArgs e)
{
// Let the user know the cancel is processing
btnCancel.Text = "Cancelling...";
btnCancel.Enabled = false;
// Ask the background worker to cancel its process
if ((null != _FileProcessingWorker) && (_FileProcessingWorker.IsBusy))
{
_FileProcessingWorker.CancelAsync();
}
}
private void btnProcess_Click(object sender, EventArgs e)
{
// Set up the background worker
_FileProcessingWorker = new BackgroundWorker();
_FileProcessingWorker.WorkerReportsProgress = true;
_FileProcessingWorker.WorkerSupportsCancellation = true;
// Add the events needed for the background worker
_FileProcessingWorker.DoWork += new DoWorkEventHandler(_FileProcessingWorker_DoWork);
_FileProcessingWorker.ProgressChanged += new ProgressChangedEventHandler(_FileProcessingWorker_ProgressChanged);
_FileProcessingWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_FileProcessingWorker_RunWorkerCompleted);
// Get the directory to process
string BasePath = lblDirectoryName.Text;
DirectoryInfo MyDirectory = new DirectoryInfo(BasePath);
DirectoryInfo[] MySubDirectories = MyDirectory.GetDirectories();
// Collect statistics for the progress bar
int SubDirectoryCount = MySubDirectories.GetLength(0);
MyProgressBar.Minimum = 0;
MyProgressBar.Step = 1;
MyProgressBar.Maximum = SubDirectoryCount;
MyProgressBar.Value = MyProgressBar.Minimum;
_LastCounter = 0;
// Instantiate the background worker and pass the file list
_FileProcessingWorker.RunWorkerAsync(MySubDirectories);
}
#endregion
#region File processing methods and events
private void _FileProcessingWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Cast the EventArgs to the expected type
DirectoryInfo[] MySubDirectories = (DirectoryInfo[])e.Argument;
// Loop through the subdirectories
for (int i = 0; i < MySubDirectories.GetLength(0); i++)
{
DirectoryInfo MySubDirectory = MySubDirectories[i];
// Keep track of the current directory
string CurrentDirectory = "Current Directory: " + MySubDirectory.Name;
// Loop through the matching files
foreach (FileInfo MyFile in MySubDirectory.GetFiles("*.txt"))
{
// See if the user has requested cancellation
if (_FileProcessingWorker.CancellationPending)
{
e.Cancel = true;
return;
}
// Keep track of the current file
string CurrentFile = "Current File: " + MyFile.Name;
// Create an object to report the status via UserState
string[] CurrentStatus = new string[2];
CurrentStatus[0] = CurrentDirectory;
CurrentStatus[1] = CurrentFile;
// Raise the ProgressChanged event to allow reporting of status
_FileProcessingWorker.ReportProgress(i, CurrentStatus);
// Do the needed processing on the file
Thread.Sleep(500);
}
}
}
private void _FileProcessingWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Update the progress bar and status labels when the background worker reports progress
// Cast the EventArgs to the expected type
if (typeof(string[]) == e.UserState.GetType())
{
string[] StatusMsg = (string[])e.UserState;
if (2 == StatusMsg.GetLength(0))
{
// Update the status labels
lblCurrentDirectory.Text = StatusMsg[0];
lblStatus.Text = StatusMsg[1];
}
}
// See if the percentage completion needs to be updated
if (e.ProgressPercentage > _LastCounter)
{
_LastCounter = e.ProgressPercentage;
// Increment the progress bar
MyProgressBar.PerformStep();
}
}
private void _FileProcessingWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Keep the display of the last directory and file processed
lblCurrentDirectory.Text = lblCurrentDirectory.Text.Replace("Current", "Last");
lblStatus.Text = lblStatus.Text.Replace("Current", "Last");
}
#endregion
}
}
You can download a complete project here.
Woo-hoo! Mega-Dorky Nerd God! (I really should have done better on history. But the questions were ancient history, and I’m better at 20th century or American history…)
Found at Michael “Cool Nerd King” McDaniel’s blog…
I received an error when serializing a class to XML:
Unable to generate a temporary class (result=1).
error CS0012: The type 'MyType' is defined in an assembly that is not referenced. You must add a reference to assembly 'MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=123412341234'.
Searching the web seemed to indicate that marking fields or properties of the class as non-serialized or adding a reference to the assembly containing the type would correct the problem. The odd thing is, MyType wasn’t part of the class being serialized; there were no fields or properties of MyType within the class!
Fortunately, I knew that it was working before I made some changes this morning. I had added the following:
public static implicit operator MyType(MySerializedClass parameterValue) {...}
to the class. When I changed it to an explicit operator, the error went away. I’m not sure why this behavior is so, but it solved my problem.
I’m working on a little app to automate some management tasks in iTunes (primarily the ability to locate a “dead” file in iTunes and look for its new location). I downloaded the Apple iTunes COM SDK documentation, and have built a little C# application to iterate through the library to find dead files.
What I’d like to be able to do is search the music directory for possible matches (artist, album, file size, etc.) and update the iTunes library with the new location. The code to loop through the library is pretty straightforward:
public static Hashtable GetInvalidFileList(iTunesAppClass iTunesAppObject)
{
bool CloseWhenDone = (null == iTunesAppObject);
if (CloseWhenDone)
{
iTunesAppObject = new iTunesAppClass();
}
Hashtable InvalidFileList = new Hashtable();
// Get the full library
IITLibraryPlaylist MyLibrary = iTunesAppObject.LibraryPlaylist;
// Get the track collection
IITTrackCollection MyTrackCollection = MyLibrary.Tracks;
for (int i = 0; i < MyTrackCollection.Count; i++)
{
// Get the track
IITTrack MyTrack = MyTrackCollection[i + 1];
// See if it's a file track
if (ITTrackKind.ITTrackKindFile == MyTrack.Kind)
{
// Cast it as a file track
IITFileOrCDTrack MyFileTrack = (IITFileOrCDTrack)MyTrack;
// Inspect the location
if (String.IsNullOrEmpty(MyFileTrack.Location))
{
int TrackDatabaseID = MyFileTrack.TrackDatabaseID;
string TrackName = "Artist: " + MyFileTrack.Artist + " Album: " + MyFileTrack.Album + " Song: " + MyFileTrack.Name;
InvalidFileList.Add(TrackDatabaseID, TrackName);
}
}
}
if (CloseWhenDone)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(iTunesAppObject);
iTunesAppObject = null;
GC.Collect();
}
return InvalidFileList;
}
However, updating the location appears to be problematic, as IITFileOrCDTrack.Location is read-only. It seems that the only way to do this through the COM interface is to add a new track, copy the meta from the old track, add the new track to all playlists that the old track was a member of, and delete the old track. That will set DateAdded to the time the application was run, and it’s a little kludgey as well. Is there a better way to do this?