Thursday, May 14, 2009

Error Message: The directory name is invalid

Over the last week I was working on serialization of a generic domain model to XML. I had seen a few examples of using Silverlight 3's new feature SaveFileDialog(), so I implemented it in order to save the XML to my local hard drive. I got this error - the directory name is invalid.



I asked a friend who is learning Silverlight to create a small project using SaveFileDialog() command, and it worked for him without a problem. He sent me the project, I ran it unchanged, and I go the same error. So I ran this Google search:

Silverlight "The directory name is invalid"

and found some helpful entries about Protected Mode in IE. I followed the instructions to turn off Protected Mode:

Tool .. Internet Options .. Security. Here is the picture.



I checked MSN to see what I was giving up by unchecking the box. This is what it said:

Understanding Protected Mode

Protected Mode is an important step forward in security for Internet Explorer (IE); it helps protect users from attack by running an IE process with greatly restricted privileges on Windows Vista. While Protected Mode does not protect against all forms of attack, it significantly reduces the ability of an attack to write, alter, or destroy data on the user's machine or to install malicious code.

Disabling this would sound bad to any user. Furthermore, being in the reduced security mode affected my Google home page and my Twitter login.

What looked like this:



Now looks like this:



As you can see, this would be very confusing for someone using your application, who is expecting the File...Save option to act like it would in any other desktop application. In fact, turning off Protected Mode is likely to scare users away. For this reason I have decided to add other options for the user to persist information, like saving to an account on the server or in isolated storage.

To finish on a positive note the blogs say that Microsoft will be fixing this issue before the final release of Silverlight 3.

Friday, May 8, 2009

Beat Box Application - Part 3: Playing Media Elements

In this part, I will talk about a tip I learned while working with media sources, and some insight I have gained when playing multiple media sources simultaneously. The source code for the complete project is on Codeplex. The complete source code for the beat box can be downloaded here:

http://drumpanel.codeplex.com/

Before this project, whenever I worked with MediaElements and media sources, I always loaded the sources dynamically from the ClientBin folder off of the server. This makes sense if the user is selecting from a number of sources or if the sources are large. However, in this application -- since the drum sound files are small and they are all likely to play at some time during the song -- I decided to load them as resources into the actual XAP file that is downloaded when the application is started. To do this you need to specify the file as a resource.



Then you need to load the source file into the MediaElement using the following code:



int i = 0;
string sSounds = "crash;hhclosed;hhopen;kick;snare;tom1;tom2;tom3";
foreach (string sKey in sSounds.Split(';'))
{
string sResource = string.Format(@"DrumPanel;component/sounds/{0}.mp3", sKey);
StreamResourceInfo oStream = Application.GetResourceStream(new Uri(sResource, System.UriKind.Relative));
Sounds.Add(i,oStream);

MediaElement oElement = new MediaElement();
Elements.Add(i, oElement);
LayoutRoot.Children.Add(oElement);
i++;
}


As for playing the sounds, the MediaElement is reading from a stream and the sound will start playing from the beginning. I have set AutoPlay = False so it will not play until I call the method Play. Also, I have learned through trial and error that in order to replay the sound, you first need to call the Stop method before you call the Play method again.

A drum machine plays a rhythm, which means on any beat, any one of the sounds can be played. Every sound has the same duration. If we were creating a piano synthesizer we would need to account for the duration of each note, like quarter notes and whole notes, but for drums this is not an issue.

Here is the code that plays all the sounds on each kit panel when the timer triggers a new beat. Each drum sound is stopped and then the active ones are played again on the next beat. The interesting thing about making music using software is you can do things you could never do in real life, like play all the drum sounds at once.


void Timer_Tick(object sender, EventArgs e)
{
PlayBeat();
}

public void PlayBeat()
{
MovingCursor++;
if (MovingCursor >= MaxCount)
MovingCursor = 0;
}
public void BackBeat()
{
MovingCursor--;
if (MovingCursor <= 0)
MovingCursor = MaxCount-1;
}

public int MovingCursor
{
get { return (int)GetValue(MovingCursorProperty); }
set { SetValue(MovingCursorProperty, value); }
}

public static readonly DependencyProperty MovingCursorProperty =
DependencyProperty.Register("MovingCursor", typeof(int), typeof(BeatPanel),
new PropertyMetadata(0,OnCursorChange));

protected static void OnCursorChange(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
BeatPanel oPanel = obj as BeatPanel;
oPanel.KitRest((int)args.OldValue);
oPanel.KitPlay((int)args.NewValue);
}



public void Play()
{
Background = new SolidColorBrush(Colors.Black);
for (int i = 0; i < 8; i++)
{
Ellipse oEllipse = Children[i] as Ellipse;
if (oEllipse.Opacity < .6)
continue;

StreamResourceInfo oInfo = Sounds[i];
MediaElement oElement = Elements[i];
oElement.SetSource(oInfo.Stream);
}
}

Friday, May 1, 2009

Beat Box Application - Part 2: Custom Panel Hit Testing

The beat box application is constructed using a nested set of custom panels. The Kit Panel is a vertical arrangement of ellipses, one for each drum sound. Kit Panels are arranged horizontally on a Beat Panel and played from left to right. The drum rhythm is controlled by a timer that generates 120 / beats per min. The complete source code for the beat box can be downloaded here:

http://drumpanel.codeplex.com/

Although it only took about 4 hours to build this application, during its development I discovered some code refactoring that I now use as best practice when creating custom panels. I thought I would discuss them here on the blog, because once implemented in the source code, the issues become obscured.

Custom panel layout and hit test behaviors

Did you know that the width and height of a custom panel are not calculated by default just because you load the XAML elements? Furthermore, you will not really notice this because the XAML elements will layout correctly even if the width and height are set to zero or NAN. However, having the correct width and height are critical to painting the background color and to detecting a mouse hit on the panel itself.


Watching this video could save you hours of fustration:

video


Taking advantage of WPF dependency properties

WPF is a model that forces you to think about graphic interaction differently than using HTML, WinForms, or ASP.NET. The introduction of binding and use of the dependency property pattern let me refactor the code into a very simplified form.
 public int MovingCursor
{
get { return (int)GetValue(MovingCursorProperty); }
set { SetValue(MovingCursorProperty, value); }
}

// Using a DependencyProperty as the backing store for Cursor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MovingCursorProperty =
DependencyProperty.Register("MovingCursor", typeof(int), typeof(BeatPanel),
new PropertyMetadata(0,OnCursorChange));

protected static void OnCursorChange(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
BeatPanel oPanel = obj as BeatPanel;
oPanel.KitRest((int)args.OldValue);
oPanel.KitPlay((int)args.NewValue);
}

What I really love about dependency properties in this example is that they automatically track changes in the value. When a value changes, you can work with both the old value and the new value. This is great in an application in which I am moving the cursor, because in this case I not only need to change the color of the background on the currently selected item, but I also need to clear the background color of the previously selected item, and start and stop the sounds. This in turn lets me simplify the API for playing the beat:
        public void PlayBeat()
{
MovingCursor++;
if (MovingCursor >= MaxCount)
MovingCursor = 0;
}
public void BackBeat()
{
MovingCursor--;
if (MovingCursor <= 0)
MovingCursor = MaxCount-1;
}

And it can easily be driven from the key board or mouse just by setting the value of the MovingCursor. Here are some examples:
        void BeatPanel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point oPoint = e.GetPosition(null);
foreach (UIElement oElement in VisualTreeHelper.FindElementsInHostCoordinates(oPoint, this))
{
KitPanel oKit = oElement as KitPanel;
if (oKit != null)
MovingCursor = (int)oKit.Tag;
}
}