Sunday, February 24, 2013

WinRT, Caliburn.Micro and IoC - Part 3 (SuspensionManager)

In my previous posts I integrated both Ninject and Unity into Caliburn.Micro for WinRT. This fixed issues I was having with a recent 1.4.1 code change, removing implicit self bindings.

The next thing I wanted to work through was state management. The VisualStudio templates have a nice implementation of state management through SuspensionManager and LayoutAwarePage.  LayoutAwarePage isn't an ideal match as a View for Caliburn.Micro MVVM. It tries to implement it's own MVVM pattern with the DefaultViewModel property, it contains a whole nested observable class definition, LoadState/SaveState logic in the View, and just plain does too much non-view stuff. 

With that said, LayoutAwarePage has many things going for it with VisualState switching management and hooking key and mouse navigation. LayoutAwarePage is only loosely coupled to SuspensionManager, so I figured I could fix these issues while still using SuspensionManager. To start out, I copied the code from LayoutAwarePage into a new class called AppPage. Then, trimmed out the DefaultViewModel and the observable collection definition.


AppPage.cs (interesting bits)


namespace CaliburnMicro_IoC_Sample.Code
{

    public class AppPage : Page
    {

.
.
.
        #region Process lifetime management

        private String _pageKey;

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property provides the group to be displayed.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            // Returning to a cached page through navigation shouldn't trigger state loading
            if (this._pageKey != null)
                return;

            var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
            this._pageKey = "Page-" + this.Frame.BackStackDepth;

            if (e.NavigationMode == NavigationMode.New)
            {
                // Clear existing state for forward navigation when adding a new page to the
                // navigation stack
                var nextPageKey = this._pageKey;
                int nextPageIndex = this.Frame.BackStackDepth;
                while (frameState.Remove(nextPageKey))
                {
                    nextPageIndex++;
                    nextPageKey = "Page-" + nextPageIndex;
                }

                // Pass the navigation parameter to the new page
                this.LoadState(e.Parameter, null, e);
            }
            else
            {
                // Pass the navigation parameter and preserved page state to the page, using
                // the same strategy for loading suspended state and recreating pages discarded
                // from cache
                this.LoadState(e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey], e);
            }
        }

.
.
.

        /// <summary>
        /// Populates the page with content passed during navigation.  Any saved state is also
        /// provided when recreating a page from a prior session.
        /// </summary>
        /// <param name="navigationParameter">The parameter value passed to
        /// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
        /// </param>
/// <param name="pageState">A dictionary of state preserved by this page during an earlier
        /// session.  This will be null the first time a page is visited.</param>
protected virtual void LoadState(Object navigationParameter, Dictionary<String, Object> pageState, NavigationEventArgs e)
        {
            if (DataContext == null)
            {
                var svc = ServiceLocator.Current.GetInstance<inavigationservice>() as SuspensionFrameAdapter;
                if (svc != null) svc.DoNavigated(this, e);
            }

            if (DataContext is IHaveState && pageState != null && pageState.Count() > 0)
            {
                var haveState = DataContext as IHaveState;
                haveState.LoadState(Convert.ToString(navigationParameter), pageState);
            }
        }

        /// <summary>
        /// Preserves state associated with this page in case the application is suspended or the
        /// page is discarded from the navigation cache.  Values must conform to the serialization
        /// requirements of <see cref="SuspensionManager.SessionState"/>.
        /// </summary>
        /// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
protected virtual void SaveState(Dictionary<String, Object> pageState)
        {
            if (DataContext is IHaveState)
            {
                var haveState = DataContext as IHaveState;
                haveState.SaveState(pageState, SuspensionManager.KnownTypes);
            }
        }

        #endregion

    }
You may notice a strange DataContext null test in LoadState, that has to do with a problem with Frames and SuspensionManager, described below. It is not my best work, and I should probably refactor it to utilize another interface, rather than a little hidden knowledge that the INavigationService was implemented by SuspensionFrameAdapter.

Tuesday, February 12, 2013

WinRT, Caliburn.Micro and IoC - Part 2 (Unity)

In a previous post I started by wrapping Ninject (or the closest build I could find that worked with WinRT) for use in Caliburn.Micro.

I wanted to do the same thing for Unity, so I could compare and contrast the two in WinRT applications.  The good news is that Unity has a WinRT build. The bad news is only the core dll is available in WinRT.  Also the UnityServiceLocator.cs file is not included in the default build.  You will either need to copy the code into your project, or pull down the source, and rebuild after including the cs file into the WinRT project.

For those who don't want to pull down the source, you can just add this class to your project:

Monday, February 11, 2013

WinRT, Caliburn.Micro and IoC - Part 1 (Ninject)

I had been preparing to roll out a Windows 8 Store application (a rewrite of an existing Windows Phone app LEAF Buddy) and was trying to find the best components to use together. I like Caliburn.Micro, and it does have a build for WinRT. Unfortunately its SimpleContainer got even more simple in version 1.4.1, and will not auto-create concrete classes! You have to register everything you ever want to resolve.

This left me in the market for a good IoC for WinRT.  I have used Ninject in the past and really like it.  I have also used Unity.  I could really go either way.  I figured I would see how bad it was to use both and if I ran into any issues further on in my project.  To isolate myslef from any future migration issues I decided I would wrap any IoC I used in an IServiceLocator from the CommonServiceLocator project.  This way it should be fairly trivial to switch (as long as I stuck with constructor injection.

First up was Ninject.  I needed a ServiceLocator wrapper, and a CaliburnApplication wrapper.  Well, first I needed an instance of Ninject that would run on WinRT...  If you do what I did, your first stop was NuGet, which fails...

This was packaged 5/9/2012, so it must have been built with the pre-release WinRT build and .Net 4.5 framework.  The latest release on the Ninject download site is from March, 2012, and has an even earlier version number.

Anyway Remo Gloor has code in a github fork with a start towards a WinRT build. You will have to add a few conditional compile directives and fix up 2 or 3 class references, with the biggest part being disabling lightweight code generation... But, it shouldn't take more than an hour to get it compiling.


Monday, March 23, 2009

Localized WPF Applications

Have you every worried about how you were going to localize your WPF applications?

The WinForms localization model is fairly mature, built into the designer, and utilizes satellite assemblies, that allow for post-build extension of your application. This has many advantages, including being able to farm the localization off to third parties, without requiring them to recompile the original application.

The WinForms model also lead to concise class wrappers and tags for your localized data. That made it easy to access both in the designer, and in the code-behind.

WPF doesn't seem to have the whole localization concept fully baked at the moment. In my opinion LocBaml is to oppresive. Requiring you to spread UID's throughout every XAML document you have, then manually edit every one of them to fit into the planned language.

I just want to localize the words. Can't I use the old satellite assembly approach, for string tables instead of the embedded pages (i.e., BAML resources)?

Yes you can. First decide if you even want the XAML to even be in your satellite resource assemblies. Microsoft says to add the tag into the project file. But, you can localize string tables without doing this.
  • In your project, add a .resx - "Resources File" (I prefer doing this instead of using the existing Settings file - for separation).
       [example: Properties\Resources.resx]
  • Open the resource file, and change the access modifier to "Public".
  • Add additional "Resources Files" at the same level, in the same folder. Inserting the appropriate localization code into the name.
       [example: Properties\Resources.de-DE.resx]
  • Clear the Custom Tool field in the Properties window for each of these files.
  • Open your XAML file and add a reference to the class wrapper namespace.
       [In this case WPFTest.Properties]
  • Then you can go ahead and use elements from the resource string table directly in you XAML
XAML:

<Window x:Class="WPFTest.Window1"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:res="clr-namespace:WPFTest.Properties"

   Title="Window1" SizeToContent="WidthAndHeight">

    <StackPanel>

        <Button Name="button1" Click="button1_Click">Button</Button>

        <Label Name="label1" Content="{x:Static res:Resources.TestString}" />

    </StackPanel>

</Window>

CS Code:

        private void button1_Click(object sender, RoutedEventArgs e)

        {

            Properties.Resources.Culture = new System.Globalization.CultureInfo("de-DE");

            Window1 wnd = new Window1();

            wnd.Show();

        }

Note: You need to set the culture for the resources (if you wish to override the current system settings), before you show the WPF window.

Sunday, March 22, 2009

SQLite A Good Alternative

In the past I have talked about SQL Server Desktop Edition and VistaDB..

I've really been searching for that perfect - elusive, small, embedded, free database. Something that doesn't need an installation, supports encryption and just works. Especially in C#....

.Net is capable of xcopy deployment, and it would be nice if my database could go with me..

I have been leaning toward SQL CE (especially 3.5). It meets most of my requirements (you can copy the dll's for private deployment - and don't need to install)..

But, I think I found a better candidate: SQLite!.

And it is already inside of some things you are probably already using:
  • Adobe (Photoshop Lightroom, Air, possibly Acrobat Reader)
  • Apple (Apple Mail, Safari, possibly iPhone, and iPod touch)
  • Firefox
  • Google (Google Gears, Android)
  • McAfee
  • Microsoft (maybe in a game?)
  • Philips (mp3 players)
  • PHP
  • Python
  • REALbasic
  • Skype
  • Sun (Solaris 10)
  • Symbian
  • Toshiba

I have been playing with it for a while in Visual Studio 2008, and am very happy with what I am seeing. The ADO.Net provider gives very clean integration. I am having no problem accessing my data either through the Server Explorer, Direct Connections, LINQ, or even ADO.Net Entity Framework. It is fast, small, and has encryption support (the ADO.NET provider version -link provided below- includes 128 RC4 encryption).

Of course there are a few things to work around. SQLite itself is written in C, and the ADO.NET provider is a managed wrapper around the compiled code. So although .Net can target AnyCPU, there are flavors of the managed wrapper for x86, x64, Itanium, and ARM. Fortunately .Net seems to utilize late binding, and you can figure out your processor architecture and copy the correct DLL into place in time for your app to run properly.

SQLite Home Page
System.Data.SQLite ADO.NET Provider
SQLite Admin - UI Management Console

An example of copying the correct DLL in place:

    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            try
            {
                FileInfo fi = new FileInfo(Assembly.GetEntryAssembly().Location);
                string strSource, strDest = fi.DirectoryName + "\\System.Data.SQLite.dll";
                if (Microsoft.Build.Utilities.ProcessorArchitecture.CurrentProcessArchitecture == Microsoft.Build.Utilities.ProcessorArchitecture.AMD64)
                {
                    strSource = fi.DirectoryName + "\\System.Data.SQLitex64.dll";
                }
                else
                {
                    strSource = fi.DirectoryName + "\\System.Data.SQLitex86.dll";
                }
                // Copy the correct dll in place
                if (!File.Exists(strDest)  new FileInfo(strSource).Length != new FileInfo(strDest).Length)
                {
                    File.Copy(strSource, strDest, true);
                }

            }
            catch (Exception exc)
            {
                MessageBox.Show(string.Format("Error copying DLL: {0}",exc.Message));
            }
        }
    }