Window settings and reoccurring background process

Topics: UI Architecture, Extensibility, Framework Services
May 12, 2011 at 7:54 PM

Here's two absolutely unrelated questions:

First, using the ShellView(Model) pattern in WPF, how can I set details about the Window, like titlebar text and icon?

Second, if you need to perform a task in the background every 1 second, how would you do it? I have a view/viewmodel that's only job is to display a single image whose image source changes based on system health. I want to poll the system health value every second (or 5 seconds) and update the property. In the past I've used Timers, but is there something better or more appropriate? I guess this might be an opinion question.

Coordinator
May 12, 2011 at 8:52 PM

Regarding the first question, you can just create your view as Window instead of a UserControl. In the future, I'm going to probably have some custom attached properties that allow you to keep your view as a UserControl and set the Window's properties. That's mostly helpful in situations where you might want to display the same view embedded and as it's own window, but need to specify window specific attributes only in the latter case.

May 12, 2011 at 10:09 PM

The current implementation of IWindowManager already binds a IHaveDisplayName.Name to the Window.Title.

Personally, I created a useful interface to manage image resources

public interface IHaveImageResource
{
     Uri Image { get; set; }
}

then, I modified the CM WindowManager to check if the VM implements such interface, and automatically bind the property to the Window.Icon (through a converter, to avoid null URIs, that are not supported by the native ImageSourceConverter).

Regarding the first question, I would use a simple timer on the VM side: every tick the timer would check the state and eventually change the IHaveImageResource.Image property.

Another approach (that I would prefer), would be design a proper service used to get the system health status and receive notification about it's change:

public enum SystemHealthStatus
{
     Normal,
     Busy,
     ...
     Overloaded  
}

public class ValueChangedEventArgs<T> : EventArgs
{
    public T OldValue { get; private set; }
    public T NewValue { get; private set; }

    public ValueChangedEventArgs(T oldValue, T newValue)
    {
         OldValue = oldValue;
         NewValue = newVallue;
    }
}

public interface ISystemHealthMonitor
{
     event EventHandler<ValueChangedEventArgs> StatusChanged; 
     SystemHealthStatus Status { get; }
}
The service would be a dependency of the view model, that could register to its event and react according to your needs (e.g change the image URI). The actual implementation of the service could use a timer (or whatever you prefer), to check periodically the system status and produce notification when needed.

This kind of approach would avoid to 'pollute' the VM with a part of logic that is not truly related to the view (i.e. the asynchronous system status check).

May 12, 2011 at 10:24 PM
BladeWise wrote:

The current implementation of IWindowManager already binds a IHaveDisplayName.Name to the Window.Title.

Personally, I created a useful interface to manage image resources

public interface IHaveImageResource
{
     Uri Image { get; set; }
}

then, I modified the CM WindowManager to check if the VM implements such interface, and automatically bind the property to the Window.Icon (through a converter, to avoid null URIs, that are not supported by the native ImageSourceConverter).

Hi BladeWise,

Can you post the WindowManager changes you mention? TIA

May 13, 2011 at 7:59 AM

Sure, the only modification required is to override the CreateWindow function, and plug-in your logic (in my codew base I even deal with localization this way, checking for a specific interface a binding my custom Culture attached property to a CultureInfo property).

        /// <summary>
        ///   Creates a window for the specified view model.
        /// </summary>
        /// <param name = "viewModel">The root model.</param>
        /// <param name = "isDialog">If set to <c>true</c> the create window is a dialog window.</param>
        /// <param name = "context">The context.</param>
        /// <returns>The created window.</returns>
        protected override Window CreateWindow(object viewModel, bool isDialog, object context)
        {
            var view = base.CreateWindow(viewModel, isDialog, context);

            var haveImageResource = viewModel as IHaveImageResource;
            if (haveImageResource != null && !ConventionManager.HasBinding(view, Window.IconProperty))
                view.SetBinding(Window.IconProperty, "Image", UriToImageSourceConverter.Default);

            return view;
        }

The UriToImageSourceConverter implementation is quite plain:

namespace PowersoftSDK.Controls.Converters
{
    #region Namespaces
    using System;
    using System.Globalization;
    using System.Windows.Data;
    using System.Windows.Media;

    #endregion

    /// <summary>
    ///   Class used to define a converter able to extract an image source from an URI.
    /// </summary>
    [ValueConversion(typeof(Uri), typeof(ImageSource), ParameterType = typeof(string))]
    [ValueConversion(typeof(Uri), typeof(ImageSource), ParameterType = typeof(ImageSource))]
    public class UriToImageSourceConverter : IValueConverter
    {
        #region Static Fields
        /// <summary>
        ///   The type converter.
        /// </summary>
        private static readonly ImageSourceConverter m_TypeConverter = new ImageSourceConverter();
        #endregion

        #region Static Properties
        /// <summary>
        ///   Gets the default instance of the class.
        /// </summary>
        /// <value>The the default instance of the class.</value>
        public static UriToImageSourceConverter Default
        {
            get { return Instancer._Instance; }
        }
        #endregion

        #region Static Members
        /// <summary>
        ///   Gets the fallback value.
        /// </summary>
        /// <param name = "value">The value.</param>
        /// <returns>The fallback value.</returns>
        private static ImageSource GetFallbackValue(object value)
        {
            if (value is ImageSource)
                return (ImageSource)value;
            if (value is string || value is Uri)
                return (ImageSource)m_TypeConverter.ConvertFrom(value);
            return null;
        }
        #endregion

        #region IValueConverter Members
        /// <summary>
        ///   Converts a value.
        /// </summary>
        /// <param name = "value">The value produced by the binding source.</param>
        /// <param name = "targetType">The type of the binding target property.</param>
        /// <param name = "parameter">The converter parameter to use.</param>
        /// <param name = "culture">The culture to use in the converter.</param>
        /// <returns>
        ///   A converted value. If the method returns null, the valid null value is used.
        /// </returns>
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value == null ? GetFallbackValue(parameter) ?? Binding.DoNothing : m_TypeConverter.ConvertFrom(value);
        }

        /// <summary>
        ///   Converts a value.
        /// </summary>
        /// <param name = "value">The value that is produced by the binding target.</param>
        /// <param name = "targetType">The type to convert to.</param>
        /// <param name = "parameter">The converter parameter to use.</param>
        /// <param name = "culture">The culture to use in the converter.</param>
        /// <returns>
        ///   A converted value. If the method returns null, the valid null value is used.
        /// </returns>
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
        #endregion

        #region Nested type: Instancer
        private static class Instancer
        {
            #region Static Fields
            internal static readonly UriToImageSourceConverter _Instance = new UriToImageSourceConverter();
            #endregion
        }
        #endregion
    }
}
As you can see, it just ensures that a null ImageSource is returned in case the source value is null (plus, it lets you specify an eventual fallback value as a parameter).