# Monday, April 06, 2009

Asynchronous UI in WPF and Silverlight (and Windows Forms too)

In a few hours I will be giving a new presentation to the Atlanta MS Professionals User Group. The topic for tonight is Asynchronous UI.

I will be talking about a number of common development patterns, along with discussion of the benefits and tradeoffs of each, and demos of the more mainstream approaches. I will also be covering a very simple and elegant solution that meets most async requirements – Jeffrey Richter’s AsyncEnumerator.

In advance of tonight’s presentation, I am posting my slides and code early. You can download them from this link. They may also become available as a download for members of the MSPros user group after the session.

Also, as a raffle prize, I am donating a one-year subscription to MSDN Premium with Visual Studio 2008 Team Suite Edition. If you were to buy it from Amazon, this subscription would cost over $10,000, so be sure to show up tonight if for nothing else than to win.

Monday, April 06, 2009 4:42:57 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Thursday, June 19, 2008

Excellent Free Sound Effects Resource

I ran across SoundSnap today – what a great resource for finding free sound effects and music loops. For those participating in the GGMUG Silverlight Game Development Contest, this might come in handy for you, so please check it out!

http://www.soundsnap.com/

By the way, the GGMUG is a great venue to meet new people who are passionate about .NET development. If you live on the East side of town, and just can’t make it to those Alpharetta meetings held by the other user groups, then please check this new group out if you haven’t already.

Thursday, June 19, 2008 2:17:48 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Monday, June 09, 2008

A Simple Sound Effects Engine for Silverlight 2

Audio support is all too often one of the last things a developer thinks of when it comes to building a user interface. Fortunately, Silverlight offers some basic building blocks that make it fairly trivial to add event-driven sounds to our applications. I have taken the built-in functionality one step further, and created a simple run-time audio engine that makes it even easier.

First, I will discuss the requirements that I had in mind when I created this engine. Second, I will discuss the ideal API for a complete solution. Third, I submit the simple effects engine that I developed in order to meet those requirements and API.

Sound Effects Engine Requirements

  • Must be able to play looping “background music” tracks.
  • Must be able to play smaller sound effects on demand.
  • Must support concurrent (and overlapping) sounds effects without interfering with already-playing audio.
  • Must adequately handle the possibility of download delays when pulling down external audio files.
  • Should cache audio files between playbacks.
  • Should allow serving of audio from high-bandwidth CDN such as Silverlight Streaming.

An Ideal Sound Effects API

  • Should be very approachable. If possible, only a single line of code to play any audio (similar to PlaySound() API).
  • Should support any formats supported by Silverlight.
  • Should be efficient.
  • Should offer download and playback completion callbacks (for chaining and synchronization).
  • Should support aggressive background pre-caching of audio files.

The SilverlightToolbox Solution

To address the simplicity requirements, this sound effects engine is implemented as a single static C# class that can be easily included into any Silverlight project. It can exist in a referenced class library, or can be linked directly into the main project.

To address looping background music, a single method SetBackgroundLoop() is supplied. This will begin playback of a single audio clip, and will automatically repeat it. Only one track can be played as the background loop – calling this method again will terminate the running track and start the new one. You can also pass String.Empty to stop all background music.

To address typical sound effect clips. two overloads of the PlaySoundEffect() method are supplied. The simpler of the two methods will simply play a clip once it has been downloaded. The extended version allows you to specify a callback for when the clip has been downloaded and started playing, and a second callback that can be used for notification that the clip has ended.

To address the scenario where multiple sound effects might be played at the same time, the engine internally maintains a queue of MediaElements. When a new sound needs to played, it pulls an unused element and puts it into service. Once the sound is finished, the element is returned to the queue. This allows Silverlight itself to handle media mixing, and by caching those MediaElements, it does so without placing undue stress on the environment.

To address the issue of download delays, the startedCallback and completedCallback parameters of PlaySoundEffect() were introduced.

To address caching of audio files, a WebClient is used (which in turn leverages the browser’s cache). Furthermore, the engine will reuse any downloaded file streams (and is smart enough to not re-queue the same file if it already queued for download).

By using a MediaElement for playback, audio tracks are automatically supported from streaming sources and Content Delivery Networks, and can be of any type supported by Silverlight.

To support chaining of audio effects, and to support synchronization of UI with audio, the PlaySoundEffect() method accepts callback events that are fired when the clip has been downloaded, and again when it has completed playback. To further help with UI synchronization, a second utility class is provided (DelayedAction) which provides a simple wrapper for BackgroundWorker that can be used to easily delay execution of a block of code after a specified amount of time.

To support pre-caching of audio files, the PreloadMedia() method can be used.

Typical usage:

In order for the sound effects engine to function properly, it must be provided with a top-level XAML container (MediaElement currently will not perform playback without a parent object). This is done by calling the Initialize() method, typically from your program’s startup code:

SoundEffects.Initialize(this.LayoutRoot);

 

After this has been done, background music can be played, and we can also queue up some sound effect clips for later:

SoundEffects.SetBackgroundLoop("cautious-path.wma");
SoundEffects.PreloadMedia("pop1.wma");

 

Then, at various points throughout the application, we can play the sound effect (for example, in response to a control event):

SoundEffects.PlaySoundEffect("pop1.wma");

 

If at any point we need to delay the sound effect slightly, we can control this be introducing a short delay:

DelayedAction.Execute(2.0, () => SoundEffects.PlaySoundEffect("pop1.wma"));

 

Complete Source Code for DelayedAction.cs:

   1: using System;
   2: using System.ComponentModel;
   3: using System.Threading;
   4:  
   5: namespace Wintellect.SilverlightToolbox
   6: {
   7:     public class DelayedAction
   8:     {
   9:         class DelayedCallback
  10:         {
  11:             public TimeSpan Delay { get; set; }
  12:             public Action Callback { get; set; }
  13:         }
  14:  
  15:         public static void Execute(double seconds, Action callback)
  16:         {
  17:             BackgroundWorker Delay = new BackgroundWorker();
  18:             Delay.DoWork += (s, e) =>
  19:             {
  20:                 DelayedCallback DelayCallback = (DelayedCallback)e.Argument;
  21:                 Thread.Sleep(DelayCallback.Delay);
  22:                 e.Result = DelayCallback.Callback;
  23:             };
  24:             Delay.RunWorkerCompleted += (s, e) =>
  25:             {
  26:                 Action Callback = e.Result as Action;
  27:                 Callback();
  28:             };
  29:             Delay.RunWorkerAsync(new DelayedCallback
  30:             {
  31:                 Delay = TimeSpan.FromSeconds(seconds),
  32:                 Callback = callback
  33:             });
  34:         }
  35:     }
  36: }

 

Complete Source Code for SoundEffects.cs:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.IO;
   4: using System.Net;
   5: using System.Windows;
   6: using System.Windows.Controls;
   7: using System.Windows.Media;
   8:  
   9: namespace Wintellect.SilverlightToolbox
  10: {
  11:     public static class SoundEffects
  12:     {
  13:         static Panel Root;
  14:         static MediaElement BackgroundLoop = new MediaElement();
  15:         static WebClient EffectDownloader = new WebClient();
  16:  
  17:         static Queue<MediaElement> AvailableSoundEffectGenerators = new Queue<MediaElement>();
  18:         static Dictionary<string, Stream> DownloadedEffects = new Dictionary<string, Stream>();
  19:         static Queue<string> PendingDownloads = new Queue<string>();
  20:         static Queue<QueuedEffect> PendingEffects = new Queue<QueuedEffect>();
  21:         static Dictionary<MediaElement, Action> PendingStartupCallbacks = new Dictionary<MediaElement, Action>();
  22:         static Dictionary<MediaElement, Action> PendingCompletionCallbacks = new Dictionary<MediaElement, Action>();
  23:  
  24:         enum TargetType
  25:         {
  26:             BackgroundMusic,
  27:             SoundEffect,
  28:         }
  29:  
  30:         class QueuedEffect
  31:         {
  32:             public string MediaName { get; set; }
  33:             public TargetType Target { get; set; }
  34:             public Action StartedCallback { get; set; }
  35:             public Action CompletedCallback { get; set; }
  36:         }
  37:  
  38:         public static void Initialize(Panel root)
  39:         {
  40:             Root = root;
  41:             InitializeTarget(root, BackgroundLoop);
  42:             EffectDownloader.OpenReadCompleted += (s, e) =>
  43:             {
  44:                 DownloadedEffects[(string)e.UserState] = e.Result;
  45:                 DownloadEffects();
  46:                 PlayEffect();
  47:             };
  48:         }
  49:  
  50:         static void DownloadEffects()
  51:         {
  52:             if (PendingDownloads.Count == 0)
  53:                 return;
  54:             string MediaName = PendingDownloads.Dequeue();
  55:             EffectDownloader.OpenReadAsync(new Uri(MediaName, UriKind.Relative), MediaName);
  56:         }
  57:  
  58:         static void InitializeTarget(Panel root, MediaElement target)
  59:         {
  60:             target.Width = 0;
  61:             target.Height = 0;
  62:             target.Visibility = Visibility.Collapsed;
  63:             root.Children.Add(target);
  64:             target.AutoPlay = false;
  65:             target.MediaOpened += (s, e) =>
  66:             {
  67:                 MediaElement Target = s as MediaElement;
  68:                 Target.Volume = 0.35;
  69:                 Target.Play();
  70:                 if (PendingStartupCallbacks.ContainsKey(Target))
  71:                 {
  72:                     Target.Dispatcher.BeginInvoke(PendingStartupCallbacks[Target]);
  73:                     PendingStartupCallbacks.Remove(Target);
  74:                 }
  75:             };
  76:             target.MediaEnded += (s, e) =>
  77:             {
  78:                 MediaElement Target = s as MediaElement;
  79:                 Target.Stop();
  80:                 if (s == BackgroundLoop)
  81:                     Target.Play();
  82:                 else
  83:                     AvailableSoundEffectGenerators.Enqueue(Target);
  84:  
  85:                 if (PendingCompletionCallbacks.ContainsKey(Target))
  86:                 {
  87:                     Target.Dispatcher.BeginInvoke(PendingCompletionCallbacks[Target]);
  88:                     PendingCompletionCallbacks.Remove(Target);
  89:                 }
  90:             };
  91:         }
  92:  
  93:         static MediaElement GetUnusedEffectGenerator()
  94:         {
  95:             if (AvailableSoundEffectGenerators.Count > 0)
  96:                 return AvailableSoundEffectGenerators.Dequeue();
  97:             else
  98:             {
  99:                 MediaElement Result = new MediaElement();
 100:                 InitializeTarget(Root, Result);
 101:                 return Result;
 102:             }
 103:         }
 104:  
 105:         static void PlayEffect()
 106:         {
 107:             lock (PendingEffects)
 108:             {
 109:                 if (PendingEffects.Count == 0)
 110:                     return;
 111:                 QueuedEffect Effect = PendingEffects.Dequeue();
 112:                 if (DownloadedEffects.ContainsKey(Effect.MediaName))
 113:                 {
 114:                     MediaElement TargetElement = null;
 115:                     switch (Effect.Target)
 116:                     {
 117:                         case TargetType.BackgroundMusic:
 118:                             { TargetElement = BackgroundLoop; break; }
 119:                         case TargetType.SoundEffect:
 120:                             { TargetElement = GetUnusedEffectGenerator(); break; }
 121:                     }
 122:                     if (Effect.StartedCallback != null)
 123:                         PendingStartupCallbacks.Add(TargetElement, Effect.StartedCallback);
 124:                     if (Effect.CompletedCallback != null)
 125:                         PendingCompletionCallbacks.Add(TargetElement, Effect.CompletedCallback);
 126:                     TargetElement.SetSource(DownloadedEffects[Effect.MediaName]);
 127:                 }
 128:                 else
 129:                 {
 130:                     PendingEffects.Enqueue(Effect);
 131:                 }
 132:             }
 133:         }
 134:  
 135:         static void PlaySound(TargetType target, string mediaName, Action startedCallback, Action completedCallback)
 136:         {
 137:             if (target == TargetType.BackgroundMusic)
 138:                 if (BackgroundLoop.CurrentState != MediaElementState.Stopped)
 139:                     BackgroundLoop.Stop();
 140:  
 141:             if (mediaName != String.Empty)
 142:             {
 143:                 lock (PendingEffects)
 144:                 {
 145:                     if (!DownloadedEffects.ContainsKey(mediaName))
 146:                     {
 147:                         PendingDownloads.Enqueue(mediaName);
 148:                     }
 149:                     PendingEffects.Enqueue(new QueuedEffect
 150:                     {
 151:                         MediaName = mediaName,
 152:                         Target = target,
 153:                         StartedCallback = startedCallback,
 154:                         CompletedCallback = completedCallback
 155:                     });
 156:                 }
 157:                 DownloadEffects();
 158:                 PlayEffect();
 159:             }
 160:         }
 161:  
 162:         public static void SetBackgroundLoop(string mediaName)
 163:         {
 164:             PlaySound(TargetType.BackgroundMusic, mediaName, null, null);
 165:         }
 166:  
 167:         public static void PlaySoundEffect(string effectName)
 168:         {
 169:             PlaySound(TargetType.SoundEffect, effectName, null, null);
 170:         }
 171:  
 172:         public static void PlaySoundEffect(string effectName, Action startedCallback, Action completedCallback)
 173:         {
 174:             PlaySound(TargetType.SoundEffect, effectName, startedCallback, completedCallback);
 175:         }
 176:  
 177:         public static void PreloadMedia(string mediaName)
 178:         {
 179:             if (!DownloadedEffects.ContainsKey(mediaName))
 180:             {
 181:                 PendingDownloads.Enqueue(mediaName);
 182:                 DownloadEffects();
 183:             }
 184:         }
 185:     }
 186: }
Monday, June 09, 2008 5:59:22 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [7] | 

# Sunday, June 01, 2008

Gem Blaster entered into "RIA Run" contest

As a follow-up to the previous post, I have entered Gem Blaster into a game development contest held by the Microsoft RIA Development Center Portal web site. I am happy to say that it has made it into the Finalists group of 6 games that are being showcased for the next few weeks on the RIA web site:

http://www.devx.com/RIA/Door/37728

The final judging is done based on popularity among visitors there. They basically track number of visits to each game, as well as how long people stay there, whether they come back and revisit, etc. So if you like it, please pass the link around to your friends and ask them to check it out too.

Sunday, June 01, 2008 1:30:38 AM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Tuesday, May 13, 2008

Gem Blaster updated and enhanced for Silverlight 2

I finally got around to updating my Gem Blaster game from Silverlight 1.1 alpha to the 2.0 beta bits. Since there were very heavy changes to the control model and other aspects of the managed runtime, I had to rewrite a lot of the original code (this was fully expected by the way). While I was in there, I also took the opportunity to add a number of other new features such as a simple sound FX engine, a self-playing "demo mode", and a bunch of other improvements.

I intend to update CodePlex soon with the new source code, but for now you can try out the game here (embedded below). It is hosted on the Silverlight Streaming service, so feel free to pass the link around (it won't kill *my* servers). If you like, you can also directly embed the game using the following embeddable HTML snippet:

<iframe 
    src="http://silverlight.services.live.com/invoke/5683/GemBlasterV2/iframe.html"
    scrolling="no"
    frameborder="0"
    style="width:800px; height:625px">
</iframe>

 

Now that this project has been updated for Silverlight 2, I can move on to my next game project idea which (I am hoping) should really push the runtime to its limits :)

Tuesday, May 13, 2008 5:37:56 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Wednesday, February 27, 2008

Heading to MIX this year

This year I will be heading to MIX in Vegas along with thousands of other UX enthusiasts. I have always had a deep interest in all things related to User Experience, and for the past couple years have wished I could attend a MIX event, and finally this year I get to go!

blings_9_25_d[1]

What will I be doing at MIX? Well I will certainly be soaking up any and all information related to Silverlight and WPF. I will also stick my head into some of the designer-oriented sessions. It's a good thing to see how the other half lives. In between sessions I am hoping to meet all kinds of people who are also passionate about UX. And of course this is Vegas, so I am sure I will lose a few dollars in a casino at some point and drink more booze than is healthy.

Joining me will be a number of Wintellectuals including Steve Porter, Sergio Loscialo, Rik Robinson, Sara Faatz, and Todd Fine. I know of a number of other local Atlanta folks will be making the trip as well: Sean Gerety, Doug Turnure, and Shawn Wildermuth to name a few. Are you going too? If so, let me know and we can get together for a cup of coffee or a beer.

.NET | Events | General | Silverlight | WPF
Wednesday, February 27, 2008 9:11:59 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [1] | 

# Sunday, February 24, 2008

Slides and Code from Alabama Code Camp 6

I have uploaded my slides and associated source code that was demonstrated, for the benefit of those who were not able to attend my session, or for those who have asked for copies of the code and slides.

I understand that these will be made available from the Alabama Code Camp web site, but I am also making them available for download from my server as well: http://www.mindfusioncorp.com/weblog/content/binary/AlabamaCC6-Silverlight-KeithRome.zip

Sunday, February 24, 2008 7:20:18 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [0] | 

# Friday, February 22, 2008

Heading to Alabama Code Camp 6

I am packing my bags and heading out to Huntsville AL tonight. In the morning I will be kicking off the Silverlight Track at the Alabama Code Camp with a presentation titled "Tapping into the power of Silverlight 1.0". My goal with this presentation is to show that although 2.0 will indeed be a phenomenal release of the Silverlight platform, the currently shipping version is still very powerful and offers a tremendous amount of value.

If you are planning on attending this Code Camp event, please stop by and listen in!

Friday, February 22, 2008 11:15:08 AM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [0] | 

# Thursday, January 31, 2008

Source Code for Gem Blaster finally published to CodePlex

Last fall I showed off a new game that I had built using Silverlight 1.1 at a few User Group meetings across the southeast. I showed the internal "guts" of the puzzle game, and I think people generally thought it was a really cool project. I promised that I would make the code available, but never actually got around to publishing it.

Until tonight.

Gem Blaster is now (finally!) published to CodePlex at http://www.codeplex.com/gemblaster

Full source code is available there.

Also, if you just want to play the game (after all, it IS a game, and somewhat addictive too), I have a "live demo" posted to the Wintellect server here: http://demo.wintellect.com/gemblaster/default.html

image

Enjoy!

Thursday, January 31, 2008 12:00:56 AM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [0] | 

# Wednesday, January 30, 2008

PolyGraph3D: Making 3D happen with Silverlight 1.0!

Yes, you read that right. Silverlight 1.0 - as in unmanaged JavaScript - and 3D.

Last week I was reminded of the FarSeer Physics engine for XNA and Silverlight 2.0. I have a strange fascination for 3D software - stemming back from the years when I was a beta tester and plugin developer for Caligari trueSpace 3D - so I decided to poke around in the source code from CodePlex to see what they were doing. Turns out it was just mostly linear algebra and some simple mechanics - things I remembered working with many years ago before the age of managed runtimes and rich visual APIs. Back then we had to do all the heavy lifting with highly optimized C++ routines and drawing directly to an HWND. But things are different now, and what seemed incredibly hard in years past really didn't seem all that hard anymore.

So I was inspired I guess.

I know I could have started with the FarSeer code, and built upon that using C# and targeting the managed runtime. But oh no - that was too easy! I figured that if it can be done using simple trig and a solid model design, I bet I could make it work in JavaScript. After all, JavaScript's Math intrinsic object supports Sin() and Cos(), extensible objects, and arrays - and at the end of the day, that's all one really needs in order to create a 3D runtime. Sure, it involves a fair amount of matrix operations and dot-products and cross-products, but all those things can be learned by reading up on Wikipedia these days (or mathreference.com).

So with that, I set out to create an unmanaged 3D visualization engine for Silverlight...

I am making the code freely available and have already published out to CodePlex here: http://www.codeplex.com/polygraph3d

My efforts so far are looking pretty good (I think). Over the course of about 8 hours of coding and testing, I have the basics working. I can create a 3-dimensional polyhedron (a mesh if you will), and I can display the object in a Silverlight Canvas. I can apply scaling, rotations, and positioning of the polyhedra, and I can also build complex objects using polyhedron groups, each of which has its own local scaling/rotation/position transforms. It also supports a movable camera and performs rudimentary perspective correction as well as small optimizations for backface-culling. It currently only supports simple brushes for the object faces - I would like to implement a real texture mapping system, but that will require some very creative use of image skewing to pull it off.

I have a test page up that demos a simple 3D cube that you can move around the virtual viewport space...

image

For sure, it is a very simple demo. Clicking the red, green, and blue axis bars will move the cube around. The "spin" button introduces a gradual rotation around the Y axis... but since my rotation transform code is currently broken, the effect isn't quite what I had in mind. Hopefully I will figure that particular bug out soon.

One major hurdle I encountered was the fact that Silverlight does not let you draw directly on a Canvas, nor does it let you create your own controls. This posed a problem since I wanted to display things that simply could not be expressed using 2D XAML. I "solved" this problem by performing all of the mathematics needed to project the 3D scene into a 2D viewport, and then I use the vertex data from the projected mesh to build a series of 2D Path elements. It was indeed a dirty trick - but hey, sometimes you just have to make do with the capabilities of the platform.

Wednesday, January 30, 2008 10:33:45 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [0] | 

# Thursday, January 10, 2008

Attached Properties in Silverlight

To work with an "attached" property in Silverlight (such as Left and Top and ZIndex), we must normally use the pair of accessor methods GetValue() and SetValue().

This is because these properties are not defined for the object that actually uses them, but they are instead defined by another object that contains them. In the case of the Left and Top properties, this would be the containing Canvas. So in order to position an object, we often find that we must write code that resembles this:

var LeftPosition = MyXamlElement.GetValue("Canvas.Left");

LeftPosition += 100.0;

MyXamlElement.SetValue("Canvas.Left", LeftPosition);

This works just dandy, but I often forget that there is an easier shorthand way to work with these attached properties. It turns out that the default indexer for all Xaml objects is implemented through those very same accessor methods. This means that we can write the code from above as such:

var LeftPosition = MyXamlElement["Canvas.Left"];

LeftPosition += 100.0;

MyXamlElement["Canvas.Left"] = Left Position;

This is considerably more readable and even saves a few bytes of JavaScript code, which cuts down on code bloat - even if only slightly. Of course if I really wanted to reduce my code as much as possible, I could have reduced the above snippet even further to this last version:

MyXamlElement["Canvas.Left"] += 100.0;

And that's about as condensed as it gets.

Thursday, January 10, 2008 10:15:59 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [0] | 

# Monday, January 07, 2008

Performing Seek operations in Silverlight on a MediaElement

I ran into a small, but dastardly quirk in Silverlight today. I haven't seen it mentioned out there so figured I would post this in case someone else might suffer from the same problem someday...

If you have a MediaElement being used to display video, chances are pretty good that you also have what is known as a "scrub bar". or at least some buttons used to jump forward or backward in the video timeline. The documentation says that you can accomplish this by interacting with the Position property of the MediaElement. The Position property is of type TimeSpan, which is documented as being immutable except for the Seconds property, which is read/write. In other words, the only way to change the current playback position of a playing media element is to set Position.Seconds to the location you wish to jump to.

The only thing is - this actually doesn't work if you take the documentation at face value. This bit of code doesn't work as you might think it would:

MyVideo.Position.Seconds = MyVideo.Position.Seconds + 10.0;

In fact, that code has no effect at all. No error message, but also no change to the position.

The secret here is that while the TimeSpan is indeed modifiable through the Seconds property, the MediaElement doesn't really care about it unless you reassign the Position property itself. To compound this, there is no way to create your own TimeSpan from code in Silverlight 1.0, so the only option we have is to take an existing one. Here is the code to change the current position (which actually works):

var pos = MyVideo.Position;

pos.Seconds = pos.Seconds + 10.0;

MyVideo.Position = pos;

HTH

Monday, January 07, 2008 3:09:59 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [0] | 

# Saturday, December 01, 2007

Silverlight Bug: MediaElements will restart if their containment lineage changes

I have not yet found a workaround for this problem.

The setup is simple: Create two Canvas elements. In one canvas, place a MediaElement. Point the media element to a valid video source. It doesn't matter if it is set to AutoPlay or not (but if using manual play, then you will need to initiate playback in some manner after loading).

Now once that video playback has begun (it doesn't matter how), if you remove the MediaElement from the original canvas using Children.Remove() and then add it to the other canvas using Children.Add(), then the playback position will reset and will start playing from the beginning.

I would possibly expect this behavior if the MediaElement has the AutoPlay property enabled, but this also happens when AutoPlay is false. Basically, as long as the media is active, it is restarted if you relocate the element to another parent control. I am guessing this is a bug in the MediaElement's OnLoad implementation that is not respecting the AutoPlay setting if the media is currently being played, and is internally calling Stop() and Start().

Saturday, December 01, 2007 12:24:02 AM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [0] | 

Silverlight Bug: Using a packaged image source for multiple Image elements

I ran into this nasty little bug today... it is not easy to reproduce, but when it happens it is very annoying to debug.

First, this bug only occurs when you are using a ZIP file to package your external resources and using a Silverlight Downloader object to bring the package to the browser, and subsequently using Image.SetSource() to provide the image files to the visual elements.

Second, it's not really a bug in functionality exactly, but more of a nuisance to the viewer. You see, the images get displayed correctly, but Silverlight throws extra error messages.

The problem occurs when you reuse a single image resource from a downloaded zip archive for multiple Image elements. And it only happens when the two images are assigned to the same resource consecutively. Example:

function downloadComplete(sender, eventArgs)
{
  // this one is OK
  sender.GetHost().FindName(“Image1”).SetSource(sender, “picture.png”);
  // this one fails
  sender.GetHost().FindName(“Image2”).SetSource(sender, “picture.png”);
}

This is using Silverlight 1.0, but I assume the same issue also happens in 1.1.

The problem appears to be related to referencing the same resource twice consecutively. If you reference another resource from the Downloader between the two calls to SetSource(), then the problem goes away. Also, if you simply don't reuse the same resource like this, then it can be avoided.

UPDATE January 2008: My previous "fix" of referencing another resource in between uses of the same resource does not seem to always work. In addition, some Silverlight installations (not all!) are also raising spurious ImageError 4001 messages. The only complete fix I have found is to implement a custom error handler, and ignore those ImageErrors:

function handleError(sender, errorArgs)
{
  if (errorArgs.errorType == "ImageError" && errorArgs.errorCode == 3002)
    {
      // This error is raised sporadically by SetSource
      return;
    }
    if (errorArgs.errorType == "ImageError" && errorArgs.errorCode == 4001)
    {
      // This error is raised sporadically by SetSource
      return;
    }
  Silverlight.default_error_handler(sender, errorArgs);
}

Saturday, December 01, 2007 12:15:55 AM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [0] | 

# Thursday, October 18, 2007

Expression Design Service Pack 1

While it probably will not get as much attention as a Visual Studio or Windows Service Pack release, the Expression Team blog announced that there is an SP now available for Expression Design that solves some very nagging issues with the RTM version of the product. These problems mostly impacted those of us using Design and Blend with Silverlight, most were a nuisance and had workarounds, but this update really improves the "flow" of creating a Silverlight or WPF UI.

Some of the "big" fixes from my perspective:

  • Gradient Midpoints are now exported (glass fanatics across the globe rejoiced). This was one of my pet peeves with gradients - you could use Midpoints to create truly spectacular glass and glow effects in Design - but they would end up more along the lines of "craptacular" when exported to XAML. The workaround wasn't 100% equivalent, and was fairly annoying (convert midpoints to new gradient stops before exporting).
  • Exporting to XAML will now emit the correct (forward) slash character in image paths for Silverlight exports. This was an annoying problem when Silverlight 1.0 was sent to RTM and no longer supported the backslash.
  • Text exported as TextBlocks instead of Paths. Thatsa verra niiice!

One thing that hasn't been addressed that still bugs me is how the exporter handles layers: Each layer becomes a canvas stacked, each stacked on top of the other. This isn't inherently troublesome, except that the canvases all start at 0,0 and fill the workspace... which basically means that only the topmost canvas will recieve mouse events by default. It would be better if the exported canvases were only as large as they needed to be, and positioned accordingly.

Then again, I guess I could always file a bug/suggestion report that would be more likely to be seen by the Expression Team...

.NET | General | Silverlight | WPF
Thursday, October 18, 2007 7:23:12 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Wednesday, October 17, 2007

Win a free pass to Devscovery 2008!

John Robbins, the wizard of debugging, is looking for suggestions for the Devscovery 2008 Keynotes. To encourage submissions, he is offering a FREE conference pass to the person with the best idea. Full price for such a pass is $900, which is a bargain in itself, but FREE is even BETTER!

The spring Devscovery will be in New York, and the fall Devscovery will be held in Redmond - the winner will get to choose which event they prefer to attend. Having attended the fall Redmond conference this year, I HIGHLY recommend it! Even if I weren't a Wintellect employee, I would still be comfortable claiming that this is hands-down the best way to spend your training time and budget.

To submit a keynote idea, please either comment on John's post here or email your suggestion directly to him. You can submit as many ideas as you like.

.NET | Events | General | Silverlight | WPF
Wednesday, October 17, 2007 10:16:43 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Tuesday, August 28, 2007

Speaking in Charlotte tonight

Steve Porter and myself are driving up to Charlotte tonight to present on Silverlight at the Developers Guild meeting. Steve will be giving an overview of Silverlight features and development, and of course I will be deconstructing my newest Silverlight game - Gem Blaster!

If you remember Popper!, this is the next generation of that game engine, upgraded to work with the newer Alpha 1.1 Refresh, and with better graphics and gameplay. You can also go directly here to play the game: http://www.mindfusioncorp.com/gemblaster/

EDIT: The game has been moved to the Wintellect servers at: http://www.wintellect.com/gemblaster/

I am also working on getting the source code published to CodePlex, but it has taken a while due to my very busy work schedule.

Tuesday, August 28, 2007 11:14:12 AM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [2] | 

# Wednesday, August 15, 2007

Blend 2 August CTP Refresh

Using the August CTP of Expression Blend 2? Worried about the lack of a registration key and that ever-decreasing "remaining days" counter at startup?

The Expression team has released a new refresh build to address that. This build is supposed to not expire until January 2008. It also has a refreshed Silverlight template that creates projects compatible with the Alpha 1.1 refresh that shipped earlier this month. You can go here to find out more.

Too bad they didn't get in a quick fix for the clipped menus bug too...

Wednesday, August 15, 2007 10:05:39 AM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Wednesday, August 08, 2007

AirportWait (Silverlight Demo)

Here is a second Silverlight demo you may or may not have seen already... this one is from a former colleage, John West. AirportWait (http://www.airportwait.com/) is a website that provides congestion data for the various checkpoints in your local airport (where provided). It basically lets you get an idea of just how early you should plan on getting to that gate in order to make your flight. Silverlight is being used to create the polar (circular) charts.

Check it out!

Wednesday, August 08, 2007 6:48:16 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

SilverLife (Silverlight Demo) posted

Jeff Prosise just published a new code sample to his Wintellect blog to show his Silverlight adaptation of the classic Game of Life, which he calls "SilverLife".

It's a really cool demo of Silverlight capabilities and programming model, and pretty fun to play with as well. Best of all, he has the source code available to download as well so you can see exactly how things are being done under the hood.

Go check it out!

Wednesday, August 08, 2007 5:19:14 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Monday, July 02, 2007

Popper!

Popper! is a simple Silverlight puzzle game. I wrote it as an exercise of my own skill with the platform, but then decided it was a rather fun (at least for a little while...) little game, so I am sharing it with the rest of the developer community. Popper! is written against Silverlight 1.1 alpha, and is mostly in C#, although the initial splash screen is done using unmanaged javaScript (yes, on purpose).

I will be walking through the innards of Popper! at the Atlanta Cutting Edge .NET User Group meeting tonight. So if you will be attending, then come check it out. Or if you just want to kill some time at "work" today... then play with it a little and let me know how it goes (good or bad)...

http://www.mindfusioncorp.com/popper/

I know of one minor bug so far: if you try to click around while a set of bubbles is in the process of "popping", then an error is raised internally, and the game pretty much stops working. I just haven't had the time to get that bug addressed yet.

I intend to post the source for this sometime after this month's meeting. Pretty much that equates to "when I have time to do it".

.NET | Events | General | Silverlight | Web 2.0 | WPF
Monday, July 02, 2007 2:40:42 AM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Thursday, June 14, 2007

Quick Tip - Input Controls in Silverlight

UPDATE 10/25/2007: Updated this sample to function correctly with the Silverlight 1.0 release. The main changes necessary were the typical 0.9 to 1.0 changes (remove "Sys.", isWindowless now takes a string and not a boolean, event handlers no longer string based), and I also had to explicitly set the z-index of the INPUT element. Thank you Erik for bringing it to my attention.

 

I have seen a lot of questions and confusion regarding capturing input when using Silverlight. There are no Button, TextBox, or other similar controls to work with. I see many folks asking for help with building their own directly within Silverlight - and bless their hearts, that is a daunting task indeed! The UI model currently does not offer basic input capture features such as "focus" or "tabbing", or even control-level keystroke capture. So folks tend to start building those basic services first, before they ever even get to writing code and xaml to support that simple textbox they need.

If you read that first paragraph and thought "thats crazy, it shouldn't be that hard!", then I would agree with you. Fortunately, there is a FAR easier and more robust way to achieve the same thing. In fact, its something that is not new to Silverlight at all, it's been with us for years. I am of course talking about the tried-and-true html <input> tag.

One of the most overlooked aspects of Silverlight is that it is a component, not a platform. Your browser is the platform. It can do a lot of stuff, if you just ask it to. Nobody wants an entire site as a single Silverlight canvas, just like nobody wants an entire site as a Flash canvas (unless possibly it is a mini-game or rich media application)... Flash designers realized this fact years ago. And as a component, a part of the solution if you will, Silverlight can play nicely with it's neighbors. With just a little bit of effort and sprinkling a very minimal amount of javascript pixie dust, we can get a Silverlight applet talking to the rest of our html DOM. And that's exactly what I am going to show in this topic...

You can download the code demonstrated in this article here (QuickTip-TextboxesInSilverlight.zip).

First of all, I am doing this with Silverlight 1.0 beta (the javascript one), as I think the 1.1 alpha is far too likely to change, and this technique should work with either. That, and I am lazy and don't want to come back and revisit this post later to correct the code...

Secondly, I am using the current CTP builds of Visual Studio Orcas and Blend 2 (the May 2007 bits), both with the Silverlight extensions. If you are using something else, then your mileage may vary.

UPDATE 10/25/2007: Code updated for VS2008 beta2 and the RTM version of Silverlight 1.0.

Now on to the code... to be sure we are on the same page, I am creating a new project from scratch...

First, Create a new project in Blend. Select the Silverlight 1.0 (JavaScript) project type. It does not matter what you name the project, but for this example I went with "TextboxesInSilverlight".

Switch to XAML view and replace the default canvas with this markup:

<Canvas
 xmlns="
http://schemas.microsoft.com/client/2007"
 xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
 Width="252" Height="272"
 Background="#FFFF2121"
 >
  <TextBlock Width="64" Height="24" Canvas.Left="8" Canvas.Top="8"
             Text="Opacity" TextWrapping="Wrap"/>
  <Ellipse Opacity="1" Fill="#FF0406FF" Stroke="#FF000000"
           x:Name="TheCircle" Width="180" Height="180" 
           Canvas.Left="36" Canvas.Top="64"/>
</Canvas>

This will create a simple red canvas, with a blue circle.

At this point, I generally switch over to Visual Studio Orcas since Blend does not have Intellisense nor does it really know how to deal with JavaScript. You can do this easily by right-clicking a project item (such as the xaml file) and selecting "Edit in Visual Studio".

Next, we need to do one small housekeeping chore to make sure that our Silverlight canvas plays nicely - specifically, we need to ask it to operate "windowless", which will allow other dhtml elements to overlay it, Open the Default.html.js file, and modify the call to Silverlight.createObjectEx(). We want to add the parameter for isWindowless...

 Silverlight.createObjectEx({
  source: "Scene.xaml",
  parentElement: document.getElementById("SilverlightControlHost"),
  id: "SilverlightControl",
  properties: {
   width: "100%",
   height: "100%",
   version: "0.9",
   isWindowless: "true"
  },
  events: {
   onLoad: sceneLoaded
  }
 });

To make things a bit more "clean", we will create the input element directly from code, however it's always good to control visual styling with CSS. Therefore, open up the Default.html, and alter the <style> tag to match the following:

 <style type="text/css">
  div, body, input
  {
   margin: 0;
   padding: 0;
  }
  #opacity
  {
   margin: -272px 0 0 75px;
   z-index: 100;
  }
  .silverlightHost
  {
   margin: 40px auto auto auto;
   height: 272px;
   width: 252px;
  }
 </style>

This will handle the placement and sizing of the silverlight container <div> as well as the input control itself. The negative margin is not a typo - this is used to pull the input control "on top" of the silverlight canvas. We could have also used absolute positioning, but that is much more brittle, relative positioning FTW. Also, notice the use of "auto" margins for the main Silverlight Host <div>. This is how you can center content without resorting to using <center> or <table>... if you take nothing away from this post, at least remember that one trick.

The last thing we must do is wire the whole thing up. This can be done in many places, in many ways. I consider this particular TextBox to be an extension of the Xaml "scene", so I will add my code to the TextboxesInSilverlight.Scene class which was created for us by the Blend project template. This is not the only place you could do this kind of code, but I found that in this particular example it made the most sense. Had I been building a dialog for a game engine, I might have this code in a seperate script file that manages my game mechanics (but thats another article...).

First, we need to capture a global reference to the Scene object that is created (this object is instantiated by the createSilverlight() function of the Default.html.js script file we edited in a previous step). The purpose of capturing this reference is that we will need it later in an event handler. This will allow our html <input> control to communicate back with the silverlight content. This is easier done than said. Open the Scene.xaml.js file. Just before the definition of the TextboxesInSilverlight.Scene.prototype (look up javascript prototype for what this is if you are interested, but that discussion is out of scope for this article), add a line of code to declare the global reference:

var globalScene = null;

Now we will create a callback function that will be used to create our JavaScript object and initialize it:

function sceneLoaded(control, userContext, rootElement)
{
    globalScene = new TextboxesInSilverlight.Scene();
    globalScene.handleLoad(control, userContext, rootElement);
};

Now, the idea for this example is that the value of the text box will determine the "Opacity" Xaml property of the Ellipse shape in our markup. In order for the event handler we are about to add to be able to do this, we will capture a reference to the circle object (technically we can wait and use findName() later during the event handler, but I prefer to capture it only once - its just my style of coding). Add this line to the handleLoad function:

this.circle = control.content.findName("TheCircle");

Next, we will create the input control and add it to the DHTML document. We will add it directly to the same <div> that Silverlight has injected itself into, and therefore any layout or positioning that affects the Silverlight canvas will also affect our <input> box. Add this code to the end of the handleLoad function:

var opacityEdit = window.document.createElement("input");
opacityEdit.type = "text";
opacityEdit.id = "opacity";
opacityEdit.name = "opacity";
opacityEdit.value = "1.0"
;

We are almost done - only two more steps and then we can fire this thing up! First, we need to add an event handler to react to changes in the value property of the input control. Add this code to the very end of the handleLoad function:

opacityEdit.onpropertychange = function()
    {
        if (event.propertyName == "value")
        {
            globalScene.circle.Opacity = event.srcElement.value;
        }
    }

This effectively creates an anonymous function to handle property change events on the <input> control, which in turn updates the Opacity property of the circle shape. Cool, huh?

The last thing to do is finally add the new <input> element to the page, otherwise all the work until this point will have had no discernable impact at all... add this one last line to the handleLoad function:

this.control.parentElement.appendChild(opacityEdit);

At this point, your Scene.xaml.js file should look like this:

if (!window.TextboxesInSilverlight)
 window.TextboxesInSilverlight = {};

TextboxesInSilverlight.Scene = function()
{
}

var globalScene = null;

function sceneLoaded(control, userContext, rootElement)
{
    globalScene = new TextboxesInSilverlight.Scene();
    globalScene.handleLoad(control, userContext, rootElement);
}

TextboxesInSilverlight.Scene.prototype =
{
 handleLoad: function(control, userContext, rootElement)
 {
  this.control = control;
  
  this.circle = control.content.findName("TheCircle");
  
  var opacityEdit = window.document.createElement("input");
  opacityEdit.type = "text";
  opacityEdit.id = "opacity";
  opacityEdit.name = "opacity";
  opacityEdit.value = "1.0";
  opacityEdit.onpropertychange = function()
    {
      if (event.propertyName == "value")
      {
        globalScene.circle.Opacity = event.srcElement.value;
      }
    }
  
this.control.parentElement.appendChild(opacityEdit);
 } 
}

If the typo gods favor you, then you should be able to use F5 to run the page and see it all working together:

Notice the positioning of the input textboxes, and the interaction of them with the underlying Silverlight canvas. This is just a simple example, but can be a very powerful way to "plug in" Silverlight into the surrounding DHTML.

Thursday, June 14, 2007 4:25:05 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Monday, April 30, 2007

A storm is brewing

It has been a good while since this industry has had a significant shake-up, where the world as we know it changes almost overnight, and our skills and practices are all made obsolete. We generally seem to have one every few years or so, and according to my calculations, it has been about 7 or 8 years since the last one.

I am sure there were other events, but the first I can recall was the introduction of Object Oriented programming in the 80's. This paradigm shift left multitudes of mainframe COBOL and RPG analysts behind, forever to toil in a world of green on black terminal displays. Then in the early and mid 90's there was an explosion of "client-server" and "N-Tier" applications in the business world. These were all the rage, and again the flock was divided. Many OOP purists were left in the dust, trying to fend off the "younger kids" that embraced the 3GL and 4GL tools of the day. But as luck would have it, only a few years later the terms "client-server" and "N-Tier" took a back seat to the newest technology explosion - the age of the Web. Right or Wrong, everyone wanted to be on the web. Try as they might, the n-tier supporters could not withstand this assault. To this day, there remains a contingent of developers that cling to the world before the web - in the Microsoft kingdom, we call them "Windows Forms Programmers", or perhaps the slightly more dignified "Smart Client Developers". But the significant majority of development work admittedly goes into Web applications.

So for the last few generations of the industry, roughly every five to seven years, we experienced a wholesale disruption in the status quo. Things are no longer what we thought they were. Skills become unmarketable. Management becomes confused. Projects get scrapped. We have to retool - retool or else go the way of the Do-Do Bird (extinct).

The only problem is - it has been about eight years now since the last paradigm shift (I do not count .NET as a paradigm shift - it is simply a consolidation and improvement on ideas and methods already in place). It has been eight years, and I fear that we are long overdue. More than that though - I feel that perhaps, just perhaps, the paradigm shift has already begun - and that I can't see it due to my own Myopia. And what if the shift has already passed me by, and I have missed it entirely?

In conclusion, I think the shift is just now underway. I have smelled the crispness in the air that precedes a thunderstorm. I think the industry is about to change again, in a very significant way, and I hope to be a small part of it yet again. But in order to accept and participate in a significant change, a person must adapt to the new way of things. To that end I have begun the arduous task of retooling and rebranding myself. This will not be the first time, nor likely the last. As a as/400 specialist, converted to PC technician, converted to Delphi developer, converted to DBA, converted to Web Developer, and finally to .NET windows/web developer, I can say that I have definately been through this process before, and it does not scare me. What scares me is the thought of not adapting.

Some of the people I know and trust feel that they too have "seen the light". Some have their own theories about where to be when the music stops playing. My good friend Scooter seems to think that Sharepoint is the entire future. I don't necessarily agree with that. I have heard similar theories about the grand direction of things from others as well ("Linux is the future!", "Everything will be AJAX!", "OMGZ It's all going to Pocket PC format!") - most of which I cannot find reason with either. Everyone seems to agree that the winds are changing, only nobody appears to agree on the direction. But I have my own ideas and theories and will once again be betting the next half-decade or longer of my career on that insight. It hasn't let me down in the past - I trust it will not let me down this time either.

.NET | General | Silverlight | Web 2.0 | WPF
Monday, April 30, 2007 4:29:00 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [2] | 

# Monday, April 16, 2007

WPF/E Gets a Name

Soma tells us that WPF/E has finally recieved a more inspiring name. The announcement was made at the National Association of Broadcasters (NAB) conference in Vegas.

What's the new name? Silverlight. They even have a slick logo for it:

You can also see how it stacks up against Flash here.

.NET | Silverlight | Web 2.0 | WPF
Monday, April 16, 2007 3:09:06 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 
View Keith Rome's profile on LinkedIn

On this page....

Asynchronous UI in WPF and Silverlight (and Windows Forms too)
Excellent Free Sound Effects Resource
A Simple Sound Effects Engine for Silverlight 2
Gem Blaster entered into "RIA Run" contest
Gem Blaster updated and enhanced for Silverlight 2
Heading to MIX this year
Slides and Code from Alabama Code Camp 6
Heading to Alabama Code Camp 6
Source Code for Gem Blaster finally published to CodePlex
PolyGraph3D: Making 3D happen with Silverlight 1.0!
Attached Properties in Silverlight
Performing Seek operations in Silverlight on a MediaElement
Silverlight Bug: MediaElements will restart if their containment lineage changes
Silverlight Bug: Using a packaged image source for multiple Image elements
Expression Design Service Pack 1
Win a free pass to Devscovery 2008!
Speaking in Charlotte tonight
Blend 2 August CTP Refresh
AirportWait (Silverlight Demo)
SilverLife (Silverlight Demo) posted
Popper!
Quick Tip - Input Controls in Silverlight
A storm is brewing
WPF/E Gets a Name

Archives

Navigation

Categories

About

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

Sign In

Certification Logo Certification Logo Certification Logo Certification Logo Certification Logo

Powered by: newtelligence dasBlog 2.3.9074.18820