Saturday, February 27, 2010

{x:Static} Replacement in Silverlight


So, there's no {x:Static} in Silverlight. That kind of sucks. I wrote this value converter to try to get around that. Unlike {x:Static}, you should put the whole path in the Source and avoid using the Binding.Path property. The converter should parse everything as needed. The code below doesn't support indexers of any kind, but it wouldn't be too hard to add.

You can use it in your XAML like this:
<local:BlockContainer Blocks="{Binding Source=Cubus.Game.CubusGame.Current.CurrentPlayer.Blocks, Converter={StaticResource static}}"/>

Here's the class. My apologies for the formatting.
 
public class StaticConverter : IValueConverter
{
#region IValueConverter Members

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is string))
{
throw new ArgumentException("Value must be of type string.");
}

try
{
string staticValue = (string)value;

// First we need to find the type
string typeName = null;
Type type = null;
int periodIndex = 0;
int lastPeriodIndex = staticValue.LastIndexOf(".");

while (type == null && periodIndex < lastPeriodIndex)
{
periodIndex = staticValue.IndexOf(".", periodIndex + 1);
typeName = staticValue.Substring(0, periodIndex);

// This bizarre block of code is how you enumerate through
// loaded assemblies in Silverlight. Normally you'd use
// AppDomain.Current.GetAssemblies() or something like that.
foreach (AssemblyPart ap in Deployment.Current.Parts)
{
System.Windows.Resources.StreamResourceInfo sri = Application.GetResourceStream(new Uri(ap.Source, UriKind.Relative));
System.Reflection.Assembly assembly = new AssemblyPart().Load(sri.Stream);
type = assembly.GetType(typeName);

if (type != null)
{
break;
}
}
}

if (type == null)
{
throw new NullReferenceException(string.Format("No type was found matching the name {0}.", typeName));
}

// Now let's find the value.
// After the type name, the first part of the path
// should be a static member. After that, the rest
// should be instance members.

object workingValue = null;
Type workingType = type;
string[] path = staticValue.Substring(periodIndex + 1, staticValue.Length - (periodIndex + 1))
.Split(new string[] { "." }, StringSplitOptions.None);

for (int i = 0; i < path.Length; i++)
{
BindingFlags flags;
if (i == 0)
{
flags = (BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.GetField | BindingFlags.Public);
}
else
{
flags = (BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.GetField | BindingFlags.Public);
}

PropertyInfo propInfo = workingType.GetProperty(path[i], flags);
if (propInfo != null)
{
workingValue = propInfo.GetValue(workingValue, null);
}
else
{
FieldInfo fieldInfo = workingType.GetField(path[i], flags);
if (fieldInfo != null)
{
workingValue = fieldInfo.GetValue(workingValue);
}
else
{
throw new NullReferenceException(string.Format("No properties or fields were found matching the name {0}.", path[i]));
}
}

workingType = workingValue.GetType();
}

return workingValue;
}
catch
{
if (DesignerProperties.GetIsInDesignMode(new UserControl()))
{
return DependencyProperty.UnsetValue;
}
else
{
throw;
}
}
}

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

#endregion
}

Things in WPF that I wish were in Silverlight

  • {x:Static}
  • MultiBinding
  • Custom markup extensions
  • PropertyMetadata overrides
  • Adorners
  • Faster hit-testing
  • The Mouse class, being able to check mouse state without setting flags all over the place.

Reg-Free COM in VSTO Applications, Part 2

A simple test did have us using the Activation Context API to load a COM object in Word, however we never could get it working with a more advanced solution, such as ActiveX controls. The results of the basic test show that this should be possible, but for now this is on the back-burner. Someone with a very advanced knowledge of manifests and SxS should be able to get this set up.

From a high-level view, there are two things you need to do:
  1. P/Invoke the Activation Context API, and use it to load the manifest that your reg-free COM information is in (usually it's Native.[assemblyname].manifest) during the startup of your VSTO add-in.
  2. The VSTO build process will not include your Native.[assemblyname].manifest in the Application Manifest, so you'll have to add it yourself. Since I wrote it at work I can't share our method, but we ended up creating a command-line tool that runs as a post-build step and manually adds the file reference using XmlDocument, and re-hashes and re-signs everything.