Silverlight ChildWindows and VisualStates

Unfortunately, as of the latest version of Silverlight 4 and Blend 4, there’s a feature supportability mismatch. Blend 4’s designer may lead to you to believe and expect that VisualStates will work within a Silverlight 4 application. (It led me down that path).

In fact, VisualStates do not work directly inside of a ChildWindow. VisualStates are only applicable on the root element of a ControlTemplate or UserControl (as described here). I just spent the last 20 minutes learning about this limitation the hard way – by trying it, over and over. :)  It’s not a bug, it just isn’t supported.

If you want to use VisualStates within a ChildWindow, the simplest workaround for this problem is to wrap the content of the child window into a new UserControl and place that UserControl in the ChildWindow (as a child). You’ll need to do a little more work to expose the functionality of the UserControl to the containing ChildWindow (like OK/Cancel button handling for example), but it’s simple work. I added an event which indicates the child window should be closed:

public event EventHandler<CloseDialogEventArgs> CloseDialog;
public class CloseDialogEventArgs : EventArgs
{
    public bool? DialogResult { get; set; }

    private CloseDialogEventArgs()
    {

    }
    public CloseDialogEventArgs(bool? dialogResult)
    {
        DialogResult = dialogResult;
    }

    public static new CloseDialogEventArgs Empty = new CloseDialogEventArgs();
}

Then, it can be closed:

private void CancelButton_Click(object sender, RoutedEventArgs e)
{
    if (CloseDialog != null)
    {
        CloseDialog(this, new CloseDialogEventArgs(false));
    }
}

Electronical Products . . . !? Awesome!

From a message that wasn’t marked as spam in my inbox:

image

“They can offer you all kinds of electronical products which you need like laptops, gps, TV LCD, cell phones, ps3, MP3/4, motorcycles, etc….”

I’m as a big of a fan of electronical products as the next guy, but I’m really excited about the electronical motorcycle! I’m in a good mood in shopping right now, so I should be set!

(Yes, I’m aware of the fact that there are electric motorcycles – I’m just skeptical they did and that they meant to specifically mention it).

Silverlight Stopwatch Class, Part 2

I recently made a fix to a Silverlight stopwatch class I wrote last year.

In doing so, I decided to kick it up a notch.

using (new StopwatchPlus("Constructor"))
{
    //Debug.WriteLine("Initialized: Ellapsed Ticks: {0}, Ellapsed Milliseconds: {1}", sw.EllapsedTicks, sw.EllapsedMilliseconds); InitializeComponent();
    Thread.Sleep(600);            
}

Instead of just a simple Stopwatch class, I added functionality to make it easy to capture timings for blocks of code, output to the Debug Output window, and perform any custom start/stop action.

 

StopwatchPlus sp1 = new StopwatchPlus(sw2 => 
        Debug.WriteLine("Time! {0}", sw2.EllapsedMilliseconds));
Thread.Sleep(500);
sp1.Stop();    // this will call the stopAction defined in the constructor 

 

In non-debug builds, the StopwatchPlus class does not send output to the debug window. (I used the ConditionalAttribute … I know I could have used conditional compilation statements, but I didn’t. :-) ).

using System;
using System.Diagnostics;

namespace WiredPrairie.Silverlight
{
    /// <summary> /// StopwatchPlus is used to measure the general performance of Silverlight functionality. Silverlight /// does not provide a high resolution timer as is available in many operating systems, /// so the resolution of this timer is limited to milliseconds. This class is best used to measure /// the relative performance of functions over many iterations. /// </summary> public sealed class StopwatchPlus : IDisposable {
        private long _startTick;
        private long _elapsed;
        private bool _isRunning;
        private string _name;
        private Action<StopwatchPlus> _startAction;
        private Action<StopwatchPlus> _stopAction;

        /// <summary> /// Creates an instance of the StopwatchPlus class and starts the timer. By /// providing a value to the name parameter, the Debug Console automatically /// will include the current values when the timer was started and stopped with /// this name. /// </summary> /// <param name="name"></param> public StopwatchPlus(string name)
            : this(name, WriteStart, WriteResults) { } 

        /// <summary> /// Creates an instance of the StopwatchPlus class and starts the timer. /// </summary> /// <param name="stopAction">Action to take when the Stop method is called.</param> public StopwatchPlus(Action<StopwatchPlus> stopAction)
            :this(null, stopAction)
        {
        }

        /// <summary> /// Creates an instance of the StopwatchPlus class and starts the timer. /// </summary> /// <param name="startAction">Action to take when the timer starts.</param> /// <param name="stopAction">Action to take when the Stop method is called.</param> public StopwatchPlus(Action<StopwatchPlus> startAction,
            Action<StopwatchPlus> stopAction)
            :this(null, startAction, stopAction)
        {
        }

        /// <summary> /// Creates an instance of the StopwatchPlus class and starts the timer. /// </summary> /// <param name="name">Name of this stop watch (used as output)</param> /// <param name="startAction">Action to take when the timer starts.</param> /// <param name="stopAction">Action to take when the Stop method is called.</param> public StopwatchPlus(string name,
            Action<StopwatchPlus> startAction, 
            Action<StopwatchPlus> stopAction)
        {
            _name = name;
            _startAction = startAction;
            _stopAction = stopAction;
            Start();
        }

        public string Name
        {
            get { return _name; }
        }

        /// <summary> /// Completely resets and deactivates the timer. /// </summary> public void Reset()
        {
            _elapsed = 0;
            _isRunning = false;
            _startTick = 0;
        }

        /// <summary> /// Begins the timer. /// </summary> public void Start()
        {
            if (!_isRunning)
            {
                _startTick = GetCurrentTicks();
                _isRunning = true;
                if (_startAction != null)
                {
                    _startAction(this);
                }
            }
        }

        /// <summary> /// Stops the current timer. /// </summary> public void Stop()
        {
            if (_isRunning)
            {
                _elapsed += GetCurrentTicks() - _startTick;
                _isRunning = false;
                if (_stopAction != null)
                {
                    _stopAction(this);
                }
            }
        }

        /// <summary> /// Gets a value indicating whether the instance is currently recording. /// </summary> public bool IsRunning
        {
            get { return _isRunning; }
        }

        /// <summary> /// Gets the Ellapsed time as a Timespan. /// </summary> public TimeSpan Ellapsed
        {
            get { return TimeSpan.FromMilliseconds(EllapsedMilliseconds); }
        }

        /// <summary> /// Gets the Ellapsed time as the total number of milliseconds. /// </summary> public long EllapsedMilliseconds
        {
            get { return GetCurrentElapsedTicks() / TimeSpan.TicksPerMillisecond; }
        }

        /// <summary> /// Gets the Ellapsed time as the total number of ticks (which is faked /// as Silverlight doesn't have a way to get at the actual "Ticks") /// </summary> public long EllapsedTicks
        {
            get { return GetCurrentElapsedTicks(); }
        }

        private long GetCurrentElapsedTicks()
        {
            return (long)(this._elapsed + (IsRunning ? (GetCurrentTicks() - _startTick) : 0));
        }

        private long GetCurrentTicks()
        {
            // TickCount: Gets the number of milliseconds elapsed since the system started. return Environment.TickCount * TimeSpan.TicksPerMillisecond;
        }

        #region IDisposable Members

        public void Dispose()
        {
            Stop();
        }

        #endregion private static void WriteStart(StopwatchPlus sw)
        {
            WriteStartInternal(sw);
        }

        // This is not called in a Release build [Conditional("DEBUG")]
        private static void WriteStartInternal(StopwatchPlus sw)
        {
            Debug.WriteLine("BEGIN\t{0}", sw._name);

        }

        private static void WriteResults(StopwatchPlus sw)
        {
            WriteResultsInternal(sw);
        }

        // This is not called in a Release build [Conditional("DEBUG")]
        private static void WriteResultsInternal(StopwatchPlus sw) 
        {
            Debug.WriteLine("END\t{0}\t{1}", sw._name, sw.EllapsedMilliseconds);
        }
    }
}