28 November 2010

Converter for showing/hiding Silverlight/WP7 GUI elements with boolean model values

Typically, when you are writing Silverlight or Windows Phone 7 applications using the MVVM pattern, your viewmodels expose boolean values indicating something is going on – for instance, your background thread is loading data and thus you want to display a progress bar. For some reason the designers of XAML choose the Visibility property of a GUI element to be of type “Visibility”, an enum with values “Visible” and “Collapsed”.

Anyway, you could of course make your viewmodel expose properties of type Visibility but as an architect I would call that “bad form”. It’s so much easier to create a simple converter for this:

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace LocalJoost.Converters
{
  public class VisibilityConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, 
                   CultureInfo culture)
    {
      if (value != null && value is bool && parameter != null)
      {
        var bValue = (bool) value;
        var visibility = (Visibility)Enum.Parse(
             typeof (Visibility), parameter.ToString(),true);
        if (bValue) return visibility;
        return visibility == 
           Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
      }
      
      return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter,
                       CultureInfo culture)
    {
      throw new NotImplementedException();
    }
  }
}

This convertor assumes a boolean input value and a parameter of type Visibility. If the input value is true, it returns the value in the parameter, if it is false, it returns the opposite. So it’s basically a converter that converts one type of boolean in another, with the possibility to specify what is true and what is false ;-)

Typical usage snippet:

Visibility="{Binding IsDownloading, Converter={StaticResource VisibilityConverter}, ConverterParameter=Visible}"

which says that if the “IsDownloading” property of the model is true, the GUI object in case must be visible. Since I actually used this snippet on a Windows Phone 7 ProgressBar, this makes perfect sense.

13 November 2010

WIX configurable search & replace custom action for text files

Recently I had my first encounter with actually writing setups myself using WIX. This is a powerful albeit a bit complex technology that allows you to build MSI setups using XML files. It includes all kinds of tasks to modify files after installation, and thus you can for instance change the settings in configuration files based upon user input. Unfortunately good examples are few and far between, and I hope to save some poor sod a lot of time with this article

Trouble is that the standard tasks only know how to modify XML files. Now this is usually enough, but if you want to change plain text files files then you are basically on your own. WIX supports the idea of custom actions that you can write in C#, So I set out to write such a search & replace custom action that was to be configured by a custom table.

That turned out less straightforward than I thought. Under Windows 7 and Vista, with UAC enabled, part of the installation sequence is run with elevated rights, part is not, and your custom actions are running not with elevated rights unless they are ‘deferred’ but then you don’t have access to the custom table anymore. I spent quite some time figuring out why my configuration files were virtualized by the installer but those in the program directory themselves never got changed. Finally solved that catch-22 by following what appeared to be a beaten track: split the task into two task. The first task runs immediate, reads the table, and dumps its contents in a file, the second runs deferred.

The actual code consist out of two methods and a property. The class declaration with the property looks like this:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using Microsoft.Deployment.WindowsInstaller;

namespace LocalJoost.Wix
{
  public class CustomActions
  {
    /// <summary>
    /// Gets the search and replace data file location. 
 /// This is stored in the user's temp directory
    /// and used by the installer.
    /// </summary>
    /// <value>The search and replace data file.</value>
    private static string SearchAndReplaceDataFile
    {
      get
      {
       return Environment.GetEnvironmentVariable("TEMP") + 
            Path.DirectorySeparatorChar + 
           "SearchAndReplace.xml";
      }
    }
  }
}

This defines a hard coded XML file in the installer user's temp directory. Then, the first method that actually gathers the information and writes it into said XML file:

/// <summary>
/// This method should be declared with Execute="immediate" 
/// and called with Before="InstallFinalize"
/// Use in conjunction with SearchAndReplaceExec
/// </summary>
/// <param name="session">The session.</param>
/// <returns></returns>
[CustomAction]
public static ActionResult SearchAndReplaceInit(Session session)
{
  session.Log("Begin SearchAndReplaceInit");
  File.Delete(SearchAndReplaceDataFile);
  if (session.Database.Tables.Contains("SearchAndReplace"))
  {
     var lstSearchAndReplace = new List<SearchAndReplaceData>();
     using (var propertyView = 
      session.Database.OpenView("SELECT * FROM `SearchAndReplace`"))
     {
       propertyView.Execute();
       foreach (var record in propertyView)
       {
         var token = new SearchAndReplaceData
         {
           File = session.Format(record["File"].ToString()),
           Search = session.Format(record["Search"].ToString()),
           Replace = session.Format(record["Replace"].ToString())
         };
         lstSearchAndReplace.Add(token);
       }
     }
     var serializer = new TypedXmlSerializer<List<SearchAndReplaceData>>();
     serializer.Serialize(SearchAndReplaceDataFile, lstSearchAndReplace);
  }
  else
  {
    session.Log("No SearchAndReplace custom table found");
  }
  session.Log("End SearchAndReplaceInit");
  return ActionResult.Success;
}
and finally the method that reads the XML file and actually executes the search and replace actions
/// <summary>
/// This method should be decleared with Execute="deferred" 
/// and called with Before="InstallFinalize"
/// Use in conjunction with SearchAndReplaceInit
/// </summary>
/// <param name="session">The session.</param>
/// <returns></returns>
[CustomAction]
public static ActionResult SearchAndReplaceExec(Session session)
{
  session.Log("Begin SearchAndReplaceExec");
  if (File.Exists(SearchAndReplaceDataFile))
  {
    var serializer = new TypedXmlSerializer<List<SearchAndReplaceData>>();
    var tokens = serializer.Deserialize(SearchAndReplaceDataFile);
    tokens.ForEach(token =>
    {
      try
      {
        string fileContents;
   
        var file = new FileInfo(token.File);
        {
          if (file.Exists)
          {
            using (var reader = new StreamReader(file.OpenRead()))
            {
              fileContents = reader.ReadToEnd();
              reader.Close();
            }
            fileContents = fileContents.Replace(token.Search, token.Replace);
         
            using (var writer = new StreamWriter(file.OpenWrite()))
            {
              writer.Write(fileContents);
              writer.Flush();
              writer.Close();
            }
          }
        }
      }
      catch (Exception)
      {
        session.Log("Could not process file " + token.File);
      }
    });
    File.Delete(SearchAndReplaceDataFile);
  }
  session.Log("End SearchAndReplaceExec");
  return ActionResult.Success;
}
Attentive readers will have noticed this code actually uses two companion classes: SearchAndReplaceData:
namespace LocalJoost.Wix
{
  public class SearchAndReplaceData
  {
    public string File { get; set; }
    public string Search { get; set; }
    public string Replace { get; set; }
  }
}
and TypedXmlSerializer:
using System.Collections;
using System.IO;
using System.Xml.Serialization;

namespace LocalJoost.Wix
{
  public class TypedXmlSerializer<T> 
  {
    public void Serialize(string path, T toSerialize)
    {
      var serializer = new XmlSerializer(typeof(T));
      using (var fileStream = 
      new FileStream(path, FileMode.Create))
      {
        serializer.Serialize(fileStream, toSerialize);
        fileStream.Close();
      }
    }

    public T Deserialize(string path)
    {
      var serializer = new XmlSerializer(typeof(T));
      T persistedObject;
      using (var reader = new StreamReader(path))
      {
        persistedObject = (T)serializer.Deserialize(reader);
        reader.Close();
      }
      return persistedObject;
    }
  }
}
If you got this all up and running, actually using it means taking three steps. First you have to declare them in right into the top Product tag like this:
<CustomAction Id="SearchAndReplaceInit"
    BinaryKey="LJWix"
    DllEntry="SearchAndReplaceInit"
    Execute="immediate"/>

<CustomAction Id="SearchAndReplaceExec"
    BinaryKey="LJWix"
    DllEntry="SearchAndReplaceExec"
    Execute="deferred" Impersonate="no"/>
 
<Binary Id="LJWix" SourceFile="LocalJoost.Wix.CA.dll" />
This assumes that your WIX custom actions projects was called "LocalJoost.Wix" and your resulting dll is called “LocalJoost.Wix.CA.dll”. Here you see the use of "immediate" for the information gathering task and the "deferred" for the actual executing task. The second step is embedding the custom actions into the install execution sequence:
<InstallExecuteSequence>
  <Custom Action="SearchAndReplaceInit" Before="InstallFinalize"/>

  <Custom Action="SearchAndReplaceExec" Before="InstallFinalize"/>
</InstallExecuteSequence>
If you have looked closely at the SearchAndReplaceInit task, you see it's trying to read a custom SearchAndReplace table, so the third and final step is to define and actually fill that table:
<CustomTable Id="SearchAndReplace">
  <Column Id="Id" Type="string" Category="Identifier" PrimaryKey="yes"/>
  <Column Id="File" Type="string"/>
  <Column Id="Search" Type="string"/>
  <Column Id="Replace" Type="string"/>
  <Row>
    <Data Column="Id">id1</Data>
    <Data Column="File" >[INSTALLLOCATION]Somedirectory\Somefile.txt</Data>
    <Data Column="Search">Text to search for</Data>
    <Data Column="Replace">Text to replace this by</Data>
  </Row>
  <Row>
    <Data Column="Id">id2</Data>
    <Data Column="File" >[INSTALLLOCATION]Somedirectory\Someotherfile.txt</Data>
    <Data Column="Search">Some other text to search for</Data>
    <Data Column="Replace">Some other text to replace this by</Data>
  </Row>
</CustomTable>

Custom tables are also declared in the Product tag. The Id tag in the table is there because there seems to be an Id necessary, and the rest in pretty obvious: in the File tag you put the file you want to process, in Search what you want to search for, and in Replace what you want to replace it by. Duh :-) . And that’s pretty much what it there to it.

Now I would love to take credit for this, but the ideas behind it – not to mention significant parts of the code – were supplied by Kevin Darty who kindly assisted me by using his Twitter account when I was struggling with this. In the end I changed a lot of this code, but the samples he sent me saved me a lot of time. And true to the spirit of this blog and my promise to Kevin, I give back to the .NET community what it gave to me.

12 November 2010

Silverlight and Windows Phone 7 do not like DTDs

My current Windows Phone 7 project requires the possibility of reading configurations from various servers. For the GIS lovers among my audience: I am trying to read a GetCapabilities document from a WMS server. I was reading this one and this one, and the first one succeeded, while the second one failed. It became even more odd when I tried to write unit tests running from the full framework – then both links processed flawlessly. The error was “NotSupportedException” on XDocument.Load().

The second links contains a DTD from ye goode olde days and it turns out Silverlight and Windows Phone 7 by default are not very fond of DTD’s in documents. After some searching around I found the DtdProcessing Enumeration and I changed this code

var doc = XDocument.Load(stream)

into

using (var reader = XmlReader.Create(stream, 
  new XmlReaderSettings {DtdProcessing = DtdProcessing.Ignore}))
{
  var doc = XDocument.Load(reader)
}

And indeed, problem solved

02 November 2010

Swapping two elements in a bound ObservableCollection IMPROVED VERSION

Today I noticed something odd. I wanted to swap two elements in an ObservableCollection that was bound to a list box – so I could move objects up and down in the list box. I noticed that moving up always worked, and moving down led to a crash in my application with the message ‘The parameter is incorrect’ in the Application_UnhandledException method of App.Xaml. Fat lot of help that is, I can tell ya ;-)

Maybe this is a Windows Phone 7 only thing, but after a bit of experimenting I found out that you always have to change the value of the item that is last in the collection – i.e., the item with the highest index – first. This worked perfectly and I blogged about it. Later I found one exception, when I tried to swap the first and the second entry. Swapping second and first works, but the other way around gave once again ‘The parameter is incorrect’

This makes no sense at all, but I guess it has something to do with the fact that at one point in time the collection contains one object twice, and whatever is bound to it does not seem to like that very much. One of my household sayings is that were’s a will, there’s a workaround. So, second time around:

using System.Collections.Generic;
using System.Collections.ObjectModel;

using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace LocalJoost.Utilities
{
  public static class ObservableCollectionExtensions
  {
    public static void Swap<T>(
       this ObservableCollection<T> collection, 
       T obj1, T obj2)
    {  
      if (!(collection.Contains(obj1) && collection.Contains(obj2))) return;
      var indexes = new List<int>
         {collection.IndexOf(obj1), collection.IndexOf(obj2)};
      if(indexes[0] == indexes[1]) return;
      indexes.Sort();
      var values = new List<T> {collection[indexes[0]], collection[indexes[1]]};
      collection.RemoveAt(indexes[1]);
      collection.RemoveAt(indexes[0]);
      collection.Insert(indexes[0], values[1]);
      collection.Insert(indexes[1], values[0]);
    }
  }
}
and now I am really done with it. It's not always high science what I blog, just things I hope that save someone else a lot of time :-)