Tuesday, March 24, 2009

Creating Playlists With Linq - Another Great Use For CodeDomProvider



I recently found a new use for dynamically-compiled assemblies using CodeDomProvider. Combined with Linq, it enables the ultimate flexibility in music playlist generation.

My "Code Playlists", are snippets of code that can do whatever they want as long as they return an enumerable collection of song objects. As you can see above, simple Linq expressions make it really easy to generate pretty much any collection of songs that my music metadata can support.

Thursday, March 12, 2009

WPF Progress Bars Revisited

Because some people were having trouble understanding how to use my original WPF progress bar code, I thought I'd post an updated, easier to use version.

The problems people were having pretty much all centered around not understanding how to run their code in a background thread. My updated version handles this for you.

First, please have a look at the original post, as you will need some of the code from there:

WPF Progress Bars

The XAML and IProgressContext code is still the same, but here is an update partial class for the progress dialog, along with a delegate definition that it requires:

public delegate void ProgressWorkerDelegate(IProgressContext progressContext);

public partial class ProgressDialog : Window, IProgressContext
{
private bool canceled = false;
private ProgressWorkerDelegate workDelegate = null;

public bool Canceled
{
get { return canceled; }
}

public ProgressDialog() : this(null)
{
}

public ProgressDialog(ProgressWorkerDelegate workDelegate)
: this(workDelegate, false)
{
}

public ProgressDialog(ProgressWorkerDelegate workDelegate, bool startInBackground)
{
this.workDelegate = workDelegate;

InitializeComponent();

CancelButton.Click += new RoutedEventHandler(CancelButton_Click);

if (workDelegate != null)
{
if (startInBackground)
{
new Thread(new ThreadStart(StartWork)).Start();
}
else
{
StartWork();
}
}
}

private void StartWork()
{
workDelegate(this);
}

void CancelButton_Click(object sender, RoutedEventArgs e)
{
canceled = true;
CancelButton.IsEnabled = false;
}

public void UpdateProgress(double progress)
{
Dispatcher.BeginInvoke(DispatcherPriority.Background,
(SendOrPostCallback)delegate { Progress.SetValue(ProgressBar.ValueProperty, progress); }, null);
}

public void UpdateStatus(string status)
{
Dispatcher.BeginInvoke(DispatcherPriority.Background,
(SendOrPostCallback)delegate { StatusText.SetValue(TextBlock.TextProperty, status); }, null);
}

public void Finish()
{
Dispatcher.BeginInvoke(DispatcherPriority.Background,
(SendOrPostCallback)delegate { Close(); }, null);
}
}


The key difference is that you can now pass the dialog a delegate that will be used to do your work, and can have it automatically get run in the background.

To use it, do something like this:

ProgressDialog progressDialog = new ProgressDialog(MyWorkFunction, true);
progressDialog.ShowDialog();


Note that I am using ShowDialog() above to keep the operation modal. If you want the rest of your UI to still accept input, use Show() instead.

Your function to actually do the work would look something like this:

void MyWorkFunction(IProgressContext progressContext)
{
for (int i = 0; i < 100; i++)
{
if (myProgressContext.Canceled)
break;

progressContext.UpdateProgress((double)i / 100.0);
progressContext.UpdateStatus("Doing Step " + i);
}

progressContext.Finish();
}

Wednesday, March 4, 2009

Gravity Game

Gravity Game

My latest hobby project is a gravity-based game in the tradition of old classics like Lunar Lander, Gravitar and Thrust. Currently, the mechanics are pretty simple. Shoot things, avoid being shot, crashing, or being hit by an asteroid, and use your tractor beam to move around colored balls that act as keys to remove barriers.

I've also been playing with water dynamics.

Gravity Game

The game is XNA-based, and I'm using Farseer as my physics engine.

Monday, January 19, 2009

More FM Synth Progress



Ok, my "little" FM synthesizer project is now officially a bit out of control. It has grown to include a drum pattern editor, chord progressions and a mini song sequencer. WPF, with its clean separation between the UI and the underlying code is perfect for doing audio interface work. Too bad the digital audio world is still mired in C++...

Monday, January 12, 2009

WPF Layout Woes - MeasureOverride killed Schrödinger's Cat

A struggle I'm currently having with layout in WPF reminds me of the Observer Effect in physics. It turns out that calling Measure() on a child object has side effects. You would think that the measuring phase of layout would simply involve asking the child how big it wants to be. Instead, you need to pass it a size constraint.

Normally, that would not be a problem, but what if you want to size a set of children based on their relative desired sizes? Catch-22. You can't call Measure() on a child without knowing the maximum size you want it to be. And, in my situation, I don't know what size I want it to be without first measuring it. And, once you have measured an objected, its size "sticks" - changing it when you do Arrange() only alters the size of the clip rectangle (an important distinction if the child actually can be variably sized - such as content within a ScrollViewer).

Very frustrating.

I have found a fairly complicated way of working around the isue, but it seems like a fundamental (if not often encountered) flaw in the way WPF layout works.

Thursday, December 18, 2008

Dynamically Compiling Assemblies with CodeDomProvider

A few posts ago, I mentioned the use of CodeDomProvider to compile assemblies at runtime. It is really easy to do.

First, you'll need to include a few namespaces you normally wouldn't:

using System.CodeDom.Compiler;
using Microsoft.CSharp;


Next, setting up the assembly compiler:

CodeDomProvider codeProvider = new CSharpCodeProvider();
CompilerParameters compilerParameters = new CompilerParameters();

compilerParameters.GenerateExecutable = false;
compilerParameters.GenerateInMemory = true;
compilerParameters.IncludeDebugInformation = false;
compilerParameters.WarningLevel = 3;
compilerParameters.TreatWarningsAsErrors = false;
compilerParameters.CompilerOptions = "/optimize";


We're using a CSharpCodeProvider here - there are other code providers for other .NET languages.

Our compiler parameters specify that we are not generating an executable, we want to compile the assembly directly in memory, rather than to disk, and we aren't including debugging information. The warning level determines the threshold for generating compiler warnings. I'm not sure exactly what level 3 does, but it's what I've seen referenced by default. It doesn't really matter, anyway, as we also specify that we aren't treating warnings as errors. Finally, we tell the compiler to optimize.

This next bit is really the only tricky part. We need to tell the compiler what extra assemblies we want to reference. The most general method I've found so far is simply to reference all of the assemblies that our parent program references, as well as the parent assembly itself:

Assembly executingAssembly = Assembly.GetExecutingAssembly();
compilerParameters.ReferencedAssemblies.Add(executingAssembly.Location);

foreach (AssemblyName assemblyName in executingAssembly.GetReferencedAssemblies())
{
compilerParameters.ReferencedAssemblies.Add(Assembly.Load(assemblyName).Location);
}


With the setup done, compiling an assembly is easy:

string assemblyCode = "public class MyClass { ...<Code Goes Here>... }";

CompilerResults compileResults =
codeProvider.CompileAssemblyFromSource(compilerParameters, new string[] { assemblyCode });

Assembly myAssembly = compileResults.CompiledAssembly;


That's all there is to it. Note that you should also check to see if and compile errors were generated. The CompileResults class has an Errors property that holds a collection of the compile errors, if any.

To use your shiny new assembly, simply use reflection to extract your class from the assembly:

Type myType = codeAssembly.GetType("MyClass");


You'll end up with a Type you can use like any other - using Activator.CreateInstance() to create a new instance of your class, for example.

Wednesday, December 17, 2008

WPF MediaElement Requires Updated Windows Media Player

I just spent several baffled hours trying to figure out why some WPF media playback code that worked just fine on my previous computer was failing to work on my new computer.

It turns out that it wasn't my code at all. It seems that the WPF MediaElement control (which is used to play content using Windows Media Player) doesn't work with WMP version 9 (which is what came installed on my new PC).

Upgrading to the latest (version 11) fixed the problem.