February 12, 2010
Cannot find property named 'StringFormat'
A few of our users have had Logos 4 crash on startup with the following exception:
System.Windows.Markup.XamlParseException: 'pack://application:Name.xaml' value cannot be assigned to property 'Source' of object 'System.Windows.ResourceDictionary'. Cannot find DependencyProperty or PropertyInfo for property named 'StringFormat'. Property names are case sensitive. Error at object 'System.Windows.Data.Binding' in markup file 'Name.xaml'.
StringFormat is a new feature in .NET 3.5 SP1; this error indicates that 3.5 SP1 is not installed properly. One should first try to download and install .NET 3.5 SP1 normally; then repair the current installation (through Control Panel, Add/Remove Programs); if all that fails, use the .NET Framework Cleanup Tool to uninstall and reinstall the framework.
The full exception and callstack for this error is:
System.Windows.Markup.XamlParseException: Cannot find DependencyProperty or PropertyInfo for property named 'StringFormat'. Property names are case sensitive. Error at object 'System.Windows.Data.Binding' in markup file 'Name.xaml'.
at System.Windows.Markup.XamlParseException.ThrowException(String message, Exception innerException, Int32 lineNumber, Int32 linePosition, Uri baseUri, XamlObjectIds currentXamlObjectIds, XamlObjectIds contextXamlObjectIds, Type objectType)
at System.Windows.Markup.XamlParseException.ThrowException(ParserContext parserContext, Int32 lineNumber, Int32 linePosition, String message, Exception innerException)
at System.Windows.Markup.BamlRecordReader.ThrowException(SRID id, String parameter)
at System.Windows.Markup.BamlRecordReader.ReadPropertyRecordBase(String attribValue, Int16 attributeId, Int16 converterTypeId)
at System.Windows.Markup.BamlRecordReader.ReadPropertyConverterRecord(BamlPropertyWithConverterRecord bamlPropertyRecord)
at System.Windows.Markup.BamlRecordReader.ReadRecord(BamlRecord bamlRecord)
at System.Windows.Markup.OptimizedTemplateContentHelper.ReadSubtreeRecord(BamlRecord record)
at System.Windows.Markup.OptimizedTemplateContent.ReadSharedRecord(BamlRecord bamlRecord)
at System.Windows.Markup.OptimizedTemplateContent.ReadRecord(BamlRecord bamlRecord)
at System.Windows.Markup.OptimizedTemplateContent.AddContentRecord(BamlRecord bamlRecord)
at System.Windows.Markup.TemplateBamlRecordReader.AddContentRecord(BamlRecord bamlRecord)
at System.Windows.Markup.TemplateBamlRecordReader.ReadRecord(BamlRecord bamlRecord)
at System.Windows.Markup.BamlRecordReader.Read(Boolean singleRecord)
at System.Windows.Markup.TemplateTreeBuilderBamlTranslator.ParseFragment()
at System.Windows.Markup.TreeBuilder.Parse()
at System.Windows.Markup.XamlTemplateSerializer.ConvertBamlToObject(BamlRecordReader reader, BamlRecord bamlRecord, ParserContext context)
at System.Windows.Markup.BamlRecordReader.ReadElementStartRecord(BamlElementStartRecord bamlElementRecord)
at System.Windows.Markup.BamlRecordReader.ReadRecord(BamlRecord bamlRecord)
at System.Windows.Markup.BamlRecordReader.Read(Boolean singleRecord)
at System.Windows.Markup.TreeBuilderBamlTranslator.ParseFragment()
at System.Windows.Markup.TreeBuilder.Parse()
at System.Windows.Markup.XamlReader.LoadBaml(Stream stream, ParserContext parserContext, Object parent, Boolean closeStream)
at System.Windows.Application.LoadBamlStreamWithSyncInfo(Stream stream, ParserContext pc)
at MS.Internal.AppModel.AppModelKnownContentFactory.BamlConverter(Stream stream, Uri baseUri, Boolean canUseTopLevelBrowser, Boolean sandboxExternalContent, Boolean allowAsync, Boolean isJournalNavigation, XamlReader& asyncObjectConverter)
at MS.Internal.AppModel.MimeObjectFactory.GetObjectAndCloseStream(Stream s, ContentType contentType, Uri baseUri, Boolean canUseTopLevelBrowser, Boolean sandboxExternalContent, Boolean allowAsync, Boolean isJournalNavigation, XamlReader& asyncObjectConverter)
at System.Windows.ResourceDictionary.set_Source(Uri value)
--- End of inner exception stack trace ---
at System.Windows.Markup.XamlParseException.ThrowException(String message, Exception innerException, Int32 lineNumber, Int32 linePosition, Uri baseUri, XamlObjectIds currentXamlObjectIds, XamlObjectIds contextXamlObjectIds, Type objectType)
at System.Windows.Markup.XamlParseException.ThrowException(ParserContext parserContext, Int32 lineNumber, Int32 linePosition, String message, Exception innerException)
at System.Windows.Markup.BamlRecordReader.ThrowException(String message, Exception innerException)
at System.Windows.Markup.BamlRecordReader.ReadPropertyRecordBase(String attribValue, Int16 attributeId, Int16 converterTypeId)
at System.Windows.Markup.BamlRecordReader.ReadPropertyConverterRecord(BamlPropertyWithConverterRecord bamlPropertyRecord)
at System.Windows.Markup.BamlRecordReader.ReadRecord(BamlRecord bamlRecord)
at System.Windows.Markup.BamlRecordReader.Read(Boolean singleRecord)
at System.Windows.Markup.TreeBuilderBamlTranslator.ParseFragment()
at System.Windows.Markup.TreeBuilder.Parse()
at System.Windows.Markup.XamlReader.LoadBaml(Stream stream, ParserContext parserContext, Object parent, Boolean closeStream)
at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)
Posted by Bradley Grainger at 12:51 PM | Comments (0) | TrackBack
December 22, 2009
Implementing TextRunTypographyProperties
Low-level text rendering in WPF is achieved by implementing a TextSource class that returns TextRun objects which have TextRunProperties describing the formatting of the text. The TextSource is passed to a TextFormatter, which reads the runs and the properties and lays out the glyphs appropriately.
Recently we received a bug report that the furtive pataḥ was not being positioned correctly: it was centred under the consonant instead of being displayed to the right. However, this only happened when it was laid out by using the TextFormatter directly, instead of using a high-level class like TextBlock or FormattedText. Thus, we knew it was a bug in our code, and not a fundamental Hebrew display problem in WPF.
One inconvenience when using the low-level text formatting APIs is that many of the public types are abstract base classes that have to be implemented almost entirely from scratch. TextRunTypographyProperties, in particular, has over 40 abstract properties that must be implemented.
I eventually traced the problem to the use of typography properties. If the TextRunProperties.TypographyProperties property is null, WPF uses a default set of typography properties (this produced the correct output); otherwise it uses the properties from that instance (this produced the incorrect output). Further investigation revealed that setting the TextRunTypographyProperties.ContextualAlternates property to true caused the pataḥ to be positioned correctly.

When we first implemented our own TextRunTypographyProperties class, we just let .NET initialise all the properties to their default values (false, 0, etc.), and then turned on a couple of properties we wanted: standard ligatures and old-style numerals. However, there are four properties that are documented as being true by default: ContextualAlternates, ContextualLigatures, Kerning, and StandardLigatures. Strictly speaking, the documentation is incorrect: these properties can't be true by default, because TextRunTypographyProperties is an abstract class that has no default implementation. But if you are providing your own implementation, it would be wise to heed the documentation and set those properties to true by default.
Posted by Bradley Grainger at 11:54 AM | Comments (0) | TrackBack
December 16, 2009
Displaying a Splash Screen with C++ (Part V)
This is Part V of a series on creating a splash screen application in native code. For more information, see the Introduction and the Code License.
Part V: Windows 7 Taskbar Compatibility
The new taskbar in Windows 7 introduces a significant usability problem with the program presented so far: the taskbar considers the splash screen app and the “real” app to be two separate programs, and gives them separate icons on the taskbar. If the user pins the shortcut (to the splash screen) that was added to the Start Menu, a new icon is added when the real application launches, which breaks the standard behaviour for pinned programs.
Windows 7 adds a number of new APIs dealing with Application User Model IDs, which “are used extensively by the taskbar in Windows 7 and later systems to associate processes, files, and windows with a particular application”. The SetCurrentProcessExplicitAppUserModelID function, specifically, lets us identify both processes (the splash screen and the application) as being part of the same logical application, so they get grouped under the same taskbar button. The guidelines on “How to Form an Application-Defined AppUserModelID” suggest that the company name, product name, and version be used to create an AppModelUserID; I’ll use “YourCompany.YourApp.1” in the following code.
The only complicating factor is that this API is only present in Windows 7, so it has to be called conditionally for backwards compatibility with Windows XP and Vista.
To the C++ splash screen application, add a call to this new function:
typedef HRESULT (*SETCURRENTPROCESSEXPLICITAPPUSERMODELIDPROC)(PCWSTR AppID);
// Gives this process an explicit App User Model ID, so that it can be treated as one item (with
// the main application window and the shortcut) by Windows 7.
void SetAppUserModelId()
{
// try to load Shell32.dll
HMODULE hmodShell32 = LoadLibrary(L"shell32.dll");
if (hmodShell32 != NULL)
{
// see if the function is exposed by the current OS
SETCURRENTPROCESSEXPLICITAPPUSERMODELIDPROC pfnSetCurrentProcessExplicitAppUserModelID =
reinterpret_cast<SETCURRENTPROCESSEXPLICITAPPUSERMODELIDPROC>(GetProcAddress(hmodShell32,
"SetCurrentProcessExplicitAppUserModelID"));
if (pfnSetCurrentProcessExplicitAppUserModelID != NULL)
{
pfnSetCurrentProcessExplicitAppUserModelID(L"YourCompany.YourApp.1");
}
FreeLibrary(hmodShell32);
}
}
To the C# WPF application, add similar code (with a version check):
/// <summary>
/// Sets the Windows 7 application user model ID that will apply to the entire process. This identifier allows an application to
/// group its associated processes and windows under a single taskbar button.
/// </summary>
/// <param name="appId">The application user model ID.</param>
public static void SetApplicationUserModelId(string appId)
{
// check for Windows 7
Version version = Environment.OSVersion.Version;
if ((version.Major > 6) || (version.Major == 6 && version.Minor >= 1))
{
SafeNativeMethods.SetCurrentProcessExplicitAppUserModelID(appId);
}
}
[SuppressUnmanagedCodeSecurity]
internal static class SafeNativeMethods
{
[DllImport("shell32.dll")]
public static extern void SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID);
}
These methods should be called as soon as possible, and certainly before any windows are created.
Finally, your MSI that installs a desktop or Start menu shortcut for the application needs to set the System.AppUserModel.ID property on the installed shortcut, as detailed in the Windows 7 Taskbar support with the MsiShortcutProperty table blog post.
Posted by Bradley Grainger at 5:24 PM | Comments (0) | TrackBack
November 5, 2009
How to Crash every WPF application
Now that we’ve released Logos 4, our users are really helping us stress-test WPF. (It’s still a little remarkable that after three years, ours is the first (and only!) WPF application installed on many of our users’ systems.)
On one system, the application was crashing at startup, with the following exception:
System.TypeInitializationException: The type initializer for
'System.Windows.Media.FontFamily' threw an exception. --->
System.ArgumentException: Illegal characters in path.
at System.IO.Path.CheckInvalidPathChars(String path)
at System.IO.Path.GetFileName(String path)
at MS.Internal.FontCache.FontSourceCollection.SetFontSources()
at MS.Internal.FontCache.FontSourceCollection.GetEnumerator()
at MS.Internal.FontCache.FamilyCollection.BuildFamilyList(List`1& familyList,
SortedDictionary`2& familyNameList, SortedList`2& frequentStrings)
at MS.Internal.FontCache.FamilyCollection.MS.Internal.FontCache.
IFontCacheElement.AddToCache(CheckedPointer newPointer, ElementCacher cacher)
at MS.Internal.FontCache.HashTable.Lookup(IFontCacheElement e, Boolean add)
at MS.Internal.FontCache.CacheManager.Lookup(IFontCacheElement e)
at System.Windows.Media.FontFamily.PreCreateDefaultFamilyCollection()
at System.Windows.Media.FontFamily..cctor()
We traced this to having an illegal path char in one of the fonts listed in the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts registry key. To reproduce the problem, you can simply edit one of those values on your own machine and add a colon, pipe, or any other illegal path character to one of the values. Now, any WPF application on your system will crash as soon as it attempts to display its first UI.
If the user of your application discovers this issue, the only thing to do is to examine each of the Fonts registry values and correct/delete any that contain invalid characters. (Or write a program to do this for you.)
I’ve filed this as Connect issue 508419; we’ve also noted that others have encountered the same problem (and fixed it in a similar way).
Posted by Bradley Grainger at 10:24 AM | Comments (3) | TrackBack
October 22, 2008
Detecting Bindings that should be OneTime
In WPF, a Binding's source can be any .NET object; the target of the Binding will be updated when the specified property on that source changes. This works best when the source property is a DependencyProperty, or when the source object implements INotifyPropertyChanged; these objects have built-in support for property value changed notifications. In other cases, the ComponentModel infrastructure (as exposed by the PropertyDescriptor class) stores the source object in a global table in order to track clients who wish to be notified when a property value changes.
Binding to a regular property of a regular .NET object (that doesn't implement INotifyPropertyChanged) has two drawbacks:
- It may be needlessly inefficient. If, for example, the source object is not implementing INotifyPropertyChanged because it's immutable, creating and attaching value changed handlers is unnecessary overhead.
- It can cause a memory leak.
Both these problems can be eliminated by setting the Mode of the Binding to OneTime, but in a large application, determining all the bindings that could be OneTime is not an easy task. Some spelunking (with .NET Memory Profiler and .NET Reflector) showed that the (internal) ReflectTypeDescriptionProvider class has a static Hashtable containing all objects that have had value changed handlers added. A common reason for objects to end up in that Hashtable is their participation in a WPF binding, so enumerating this Hashtable at runtime can help track down bindings that may need to be changed. (And if an object is never removed from this hashtable, that may be a sign of a memory leak.)
This method uses reflection to dump the contents of the ReflectTypeDescriptionProvider._propertyCache hashtable for diagnostic purposes (the definition of the ReflectPropertyDescriptorInfo class is given later):
private static ReadOnlyCollection<ReflectPropertyDescriptorInfo> GetReflectPropertyDescriptorInfo()
{
List<ReflectPropertyDescriptorInfo> listInfo = new List<ReflectPropertyDescriptorInfo>();
// get the ReflectTypeDescriptionProvider._propertyCache field
Type typeRtdp = typeof(PropertyDescriptor).Module.
GetType("System.ComponentModel.ReflectTypeDescriptionProvider");
FieldInfo propertyCacheFieldInfo = typeRtdp.GetField("_propertyCache",
BindingFlags.Static | BindingFlags.NonPublic);
Hashtable propertyCache = (Hashtable) propertyCacheFieldInfo.GetValue(null);
if (propertyCache != null)
{
// try to make a copy of the hashtable as quickly as possible (this object can be accessed by other threads)
DictionaryEntry[] entries = new DictionaryEntry[propertyCache.Count];
propertyCache.CopyTo(entries, 0);
FieldInfo valueChangedHandlersFieldInfo = typeof(PropertyDescriptor).GetField("valueChangedHandlers",
BindingFlags.Instance | BindingFlags.NonPublic);
// count the "value changed" handlers for each type
foreach (DictionaryEntry entry in entries)
{
PropertyDescriptor[] pds = (PropertyDescriptor[]) entry.Value;
if (pds != null)
{
foreach (PropertyDescriptor pd in pds)
{
Hashtable valueChangedHandlers = (Hashtable) valueChangedHandlersFieldInfo.GetValue(pd);
if (valueChangedHandlers != null && valueChangedHandlers.Count != 0)
listInfo.Add(new ReflectPropertyDescriptorInfo(entry.Key.ToString(), pd.Name,
valueChangedHandlers.Count));
}
}
}
}
listInfo.Sort();
return listInfo.AsReadOnly();
}
The following code implements a window that displays all the properties that were found. It can be used by adding it to a WPF application and creating a special diagnostic button or keystroke that opens the window. You can open two windows and compare the lists side-by-side, or use the Refresh button to regenerate the list (after interacting with your application's UI) to see if any properties have been added or removed.
ReflectPropertyDescriptorWindow.xaml:
<Window x:Class="OneTimeBinding.ReflectPropertyDescriptorWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:OneTimeBinding"
Title=".NET Properties used in Binding Paths" Height="450" Width="450" WindowStartupLocation="CenterScreen"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type src:ReflectPropertyDescriptorInfo}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding TypeName, Mode=OneTime}"/>
<TextBlock>.</TextBlock>
<TextBlock FontWeight="Bold" Text="{Binding PropertyName, Mode=OneTime}"/>
<TextBlock Text="{Binding DisplayHandlerCount, Mode=OneTime}"/>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<DockPanel>
<Button DockPanel.Dock="Top" Margin="4"
Click="RefreshButton_Click">_Refresh</Button>
<ScrollViewer Margin="4">
<ItemsControl ItemsSource="{Binding ReflectProperties}"/>
</ScrollViewer>
</DockPanel>
</Window>
ReflectPropertyDescriptorWindow.xaml.cs:
public partial class ReflectPropertyDescriptorWindow : Window
{
public ReflectPropertyDescriptorWindow()
{
InitializeComponent();
ReflectProperties = GetReflectPropertyDescriptorInfo();
}
public static readonly DependencyProperty ReflectPropertiesProperty =
DependencyProperty.Register("ReflectProperties", typeof(ReadOnlyCollection<ReflectPropertyDescriptorInfo>),
typeof(ReflectPropertyDescriptorWindow), new PropertyMetadata());
public ReadOnlyCollection<ReflectPropertyDescriptorInfo> ReflectProperties
{
get { return (ReadOnlyCollection<ReflectPropertyDescriptorInfo>) GetValue(ReflectPropertiesProperty); }
set { SetValue(ReflectPropertiesProperty, value); }
}
private void RefreshButton_Click(object sender, RoutedEventArgs e)
{
ReflectProperties = GetReflectPropertyDescriptorInfo();
}
private static ReadOnlyCollection<ReflectPropertyDescriptorInfo> GetReflectPropertyDescriptorInfo()
{
// as shown above
}
}
And finally, the definition of the immutable ReflectPropertyDescriptorInfo object, which is used as the source of a OneTime binding in the UI:
public sealed class ReflectPropertyDescriptorInfo : IEquatable<ReflectPropertyDescriptorInfo>,
IComparable<ReflectPropertyDescriptorInfo>
{
public ReflectPropertyDescriptorInfo(string typeName, string propertyName, int handlerCount)
{
m_typeName = typeName;
m_propertyName = propertyName;
m_handlerCount = handlerCount;
}
public string TypeName
{
get { return m_typeName; }
}
public string PropertyName
{
get { return m_propertyName; }
}
public int HandlerCount
{
get { return m_handlerCount; }
}
public string DisplayHandlerCount
{
get { return m_handlerCount == 1 ? "" : string.Format(CultureInfo.InvariantCulture,
" ({0:n0} handlers)", m_handlerCount); }
}
public int CompareTo(ReflectPropertyDescriptorInfo other)
{
if (object.ReferenceEquals(other, null))
return 1;
int compareResult = m_typeName.CompareTo(other.m_typeName);
if (compareResult == 0)
compareResult = m_propertyName.CompareTo(other.m_propertyName);
if (compareResult == 0)
compareResult = m_handlerCount.CompareTo(other.m_handlerCount);
return compareResult;
}
// Implementations of Equals, GetHashCode, operators, etc. elided for brevity
readonly string m_typeName;
readonly string m_propertyName;
readonly int m_handlerCount;
}
Posted by Bradley Grainger at 6:57 PM | Comments (2) | TrackBack
October 15, 2008
Keep your WPF UI responsive
Hopefully, everyone writing Windows applications understands the value of keeping the UI responsive. That is, we all know that it is important to avoid long-running or long-waiting work in the UI thread, because it prevents the window from doing anything - it can't respond to keystrokes or clicks; it can't even update its display. So, we make sure that any work we do is so fast that the user won't notice, or we move that work into background threads.
Still, it is easy to miss something, especially when an operation usually completes quickly, but can be much slower under certain circumstances. Until recently, we could get away with it from time to time - in fact, we might display a "Working" message right before we started the work, or we might set the mouse cursor to the "hourglass" on the hopes that the user won't get too impatient and terminate the application.
These days, however, Windows is much less forgiving. Under Windows Vista, at least, if a window is not responsive for five seconds or so, the window flickers a bit and Windows adds the humiliating "(Not Responding)" message to the window caption.

Even so, surely the user could deal with that from time to time. A temporarily frozen user interface isn't the end of the world; after all, that program is working hard! Just let it finish and everything will be fine again.
Unfortunately, things get much worse when running a WPF application under Windows Vista with Aero enabled (the glassy window captions). If that application becomes unresponsive, you get the "(Not Responding)" message, as well as something we like to call the "black screen of death" - the entire content of the window goes pitch black.

This is surely unacceptable - nothing says "terminate me" like a black window. In the end, I suppose this will be best for the user, because we're trying extra hard to make sure that nothing we do on the UI could ever take five seconds. But it's certainly a lot harder than displaying the trusty old hourglass.
Posted by Ed Ball at 9:11 AM | Comments (0) | TrackBack
August 19, 2008
Image Format Error when Loading from a Stream
The Microsoft Windows Imaging Component (WIC) is “an extensible framework for encoding, decoding, and manipulating images”. It's also the core of WPF’s System.Windows.Media.Imaging classes; this meant that a curious exception I got when using BitmapSource eventually led me to discover a possible bug in IWICImagingFactory::CreateDecoderFromStream.
My code was loading a large number of images from disk. The files contained a header with some image metadata, followed immediately by a regular Windows bitmap (in the ubiquitous BMP file format). The code would read the header from the stream, then load the bitmap from the rest of the stream, as follows:
using (Stream stream = new FileStream(filename, FileMode.Open))
{
// read header
stream.Read(header, 0, header.Length);
// etc.
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
return bitmap;
}
Some images would fail to load, with EndInit throwing a mysterious System.IO.FileFormatException: “The image format is unrecognized”. The InnerException was System.Runtime.InteropServices.COMException (0x88982F07), with an HRESULT of a WIC error code: WINCODEC_ERR_UNKNOWNIMAGEFORMAT.
My first thought was that the images were somehow corrupted, but further investigation showed that the files loaded without errors if the header preceding the bitmap was removed from the file, or if the bitmap data following the header was first copied to a new MemoryStream before being loaded. I observed the same behaviour with IWICImagingFactory::CreateDecoderFromStream when I rewrote the test harness as a C++ COM application: if the IStream containing the image contained any data preceding the bitmap data, an error HRESULT would sometimes be returned.
It appears that, in certain circumstances, CreateDecoderFromStream assumes that the bitmap data begins at the stream's origin, and absolute offsets within the stream are used when seeking; thus, the image data must begin at offset 0 within the stream. As a workaround, you can copy the image data to a new MemoryStream (but note that this may increase memory usage). The solution I chose was to write a thin Stream wrapper class that handles calls to Position, Seek, Length, etc. and adjusts the offsets so that the image now appears to start at offset 0; all other calls are passed straight through to the underlying FileStream. This allows WIC and WPF to load all the images without having to make an unnecessary copy of the bitmap, or having to change the legacy file format.
Posted by Bradley Grainger at 6:33 PM | Comments (1) | TrackBack
July 18, 2008
SetData actually adds data
Incredibly, MSDN does not make it clear that the SetData method of IDataObject does not replace the data, but actually adds the data to the data object.
So, if you want multiple formats, just call SetData multiple times.
Update: The SetData method of the Clipboard class replaces the data. No wonder I'm so confused!
Posted by Ed Ball at 1:15 PM | Comments (0) | TrackBack
April 5, 2008
“Memory leak” with BitmapImage and MemoryStream
The code snippet below has a small “memory leak”:
BitmapImage bitmap = new BitmapImage();
byte[] buffer = GetHugeByteArray(); // from some external source
using (MemoryStream stream = new MemoryStream(buffer, false))
{
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
bitmap.Freeze();
}
// use bitmap...
The BitmapImage keeps a reference to the source stream (presumably so that you can read the StreamSource property at any time), so it keeps the MemoryStream object alive. Unfortunately, even though MemoryStream.Dispose has been invoked, it doesn't release the byte array that the memory stream wraps. So, in this case, bitmap is referencing stream, which is referencing buffer, which may be taking up a lot of space on the large object heap. Note that there isn't a true memory leak; when there are no more references to bitmap, all these objects will (eventually) be garbage collected. But since bitmap has already made its own private copy of the image (for rendering), it seems rather wasteful to have the now-unnecessary original copy of the bitmap still in memory.
The solution here is fairly straightforward: create an implementation of Stream that wraps another stream (in this example, the MemoryStream). The Dispose method of this wrapper class needs to release the wrapped stream, so that it can be garbage collected. Once the BitmapImage is initialised with this wrapper stream, the wrapper stream can be disposed, releasing the underlying stream, and allowing the large byte array itself to be freed.
Posted by Bradley Grainger at 12:40 PM | Comments (5) | TrackBack
March 25, 2008
AllowDrop in WPF
When implementing a drag-and-drop target in WPF, don't forget to set AllowDrop to true! You wouldn't think it would be that hard to remember – after all, it usually doesn't work until you set it. However, I just tracked down a bug that resulted from AllowDrop not being set to true on a drag-and-drop target. In most circumstances, it was inheriting a true AllowDrop from an ancestor element, so it worked fine, but in other circumstances, one of its ancestors was setting AllowDrop to false for some reason. I really don't understand why, and I'm guessing there's a WPF bug hiding in there somewhere, but the fix was easy – always set AllowDrop to true if you want to handle the drag-and-drop target events (DragEnter, DragOver, Drop, etc.).
Incidentally, this was my first bug fix for which the .NET Framework Library Source Code proved invaluable. The ability to step through the code, set breakpoints, watch variables, etc. was key to tracking this one down.
Posted by Ed Ball at 1:26 PM | Comments (0) | TrackBack
February 6, 2008
Finding ancestor elements in WPF
Philipp Sumi recently posted a method that uses VisualTreeHelper.GetParent to find an ancestor of a WPF element. The problem with using VisualTreeHelper.GetParent is that it doesn't work with "content elements" like Hyperlink, Run, etc., which means that his TryFindFromPoint method would fail if the mouse cursor was over one of those types of elements. We've had to write our own GetParent method that traverses through content elements:
public static DependencyObject GetParent(DependencyObject obj)
{
if (obj == null)
return null;
ContentElement ce = obj as ContentElement;
if (ce != null)
{
DependencyObject parent = ContentOperations.GetParent(ce);
if (parent != null)
return parent;
FrameworkContentElement fce = ce as FrameworkContentElement;
return fce != null ? fce.Parent : null;
}
return VisualTreeHelper.GetParent(obj);
}
We only recently discovered ContentOperations.GetParent; I won't be surprised if there are other "GetParent" methods hiding in WPF that we'll eventually need to support as well.
We also have an ancestor-finding method that avoids recursion:
public static T FindAncestorOrSelf<T>(DependencyObject obj)
where T : DependencyObject
{
while (obj != null)
{
T objTest = obj as T;
if (objTest != null)
return objTest;
obj = GetParent(obj);
}
return null;
}
It's been fun finding so much great WPF content in blogs; I hope the trend continues!
Update: If you want to eliminate the 'parent' and 'fce' temporary variables from GetParent, you can use our IfNotNull extension method and replace that block of code with:
return ContentOperations.GetParent(ce) ??
(ce as FrameworkContentElement).IfNotNull(fce => fce.Parent);
Posted by Ed Ball at 9:23 AM | Comments (2) | TrackBack
January 22, 2008
Using Process.Start to link to the Internet
The easiest way to navigate the user's default Internet browser to a specified URL is to call System.Diagnostics.Process.Start(string).
Process.Start("http://www.logos.com/");
The MSDN documentation seems inaccurate on a few points. It suggests that this method should only be called from an STA thread, which isn't true – it will create a new STA thread and run from there if necessary. Worse, a comment in the example suggests that this method doesn't work with a URL to launch an Internet browser – obviously, it works just fine.
We have found that, in some circumstances, with some browsers, Process.Start will raise an exception, even though it might actually succeed, so we wrap the call in a try/catch block and hope for the best.
try
{
Process.Start("http://www.logos.com/");
}
catch (Exception)
{
}
Interestingly, when used to launch the default Internet browser in this way, Process.Start "waits" until the Internet browser is displayed before returning. I must use quotation marks here, because when Process.Start is called from a UI thread (e.g., the main STA thread of a WPF application), it pumps messages while it waits, which means that the user can still interact with the application, and events like mouse clicks and keystrokes will be processed by the application while it is "waiting". So, make sure that you don't do any work after calling Process.Start that could fail due to user activity in the meantime. Process.Start is probably being called from an event handler, so the best thing to do after calling Process.Start is nothing else.
One thing that bothers me about most applications that link to the Internet is that the Internet browser can take a while to appear. You click on a link, but nothing happens, and you wonder if you actually clicked the link, so you click again, and ultimately end up opening the Web site twice. In a WPF application, a nice way to deal with this problem is to set Mouse.OverrideCursor to Cursors.AppStarting while Process.Start is running. (Fortunately, since Process.Start pumps messages, it doesn't "hang" the application while you wait.)
try
{
Mouse.OverrideCursor = Cursors.AppStarting;
Process.Start("http://www.logos.com/");
}
catch (Exception)
{
}
finally
{
Mouse.OverrideCursor = null;
}
Wrap that up in a utility method and you're good to go.
Posted by Ed Ball at 9:09 AM | Comments (2) | TrackBack
January 17, 2008
Hyperlinks to the Web in WPF
The obvious choice for implementing a link to a Web site in WPF is the Hyperlink class. Note, however, that the Hyperlink class is a FrameworkContentElement, which means it doesn't know how to render itself, but must be hosted by a TextBlock or a FlowDocument viewer like FlowDocumentScrollViewer or RichTextBox. So the easiest way to display a hyperlink is to wrap it in a TextBlock.
<TextBlock>
<Hyperlink NavigateUri="http://code.logos.com/blog/">Blog</Hyperlink>
</TextBlock>
If your application is hosted in Internet Explorer (XBAP or loose XAML), the browser will be navigated accordingly. If the Hyperlink is in the Page of a NavigationWindow or Frame, that window or frame will be navigated.
The Hyperlink doesn't work at all from a standalone Window, however. Lauren Lavoie discusses the workaround -- your application needs to intercept the navigation and use Process.Start with the URL to launch the default browser. The best way to do that is to handle the Hyperlink.RequestNavigate routed event on the Hyperlink or one of its ancestors. (You could also just handle the Hyperlink.Click event, but Lauren's solution seems better.) And the easiest way to do that is with an attached dependency property:
<TextBlock lbx:HyperlinkUtility.LaunchDefaultBrowser="True">
<Hyperlink NavigateUri="http://code.logos.com/blog/">Blog</Hyperlink>
</TextBlock>
And how is that dependency property implemented? We have some really cool utility code for registering dependency properties in a type-safe manner, but I'll use traditional methods for now:
public static class HyperlinkUtility
{
public static readonly DependencyProperty LaunchDefaultBrowserProperty = DependencyProperty.RegisterAttached("LaunchDefaultBrowser", typeof(bool), typeof(HyperlinkUtility), new PropertyMetadata(false, HyperlinkUtility_LaunchDefaultBrowserChanged));
public static bool GetLaunchDefaultBrowser(DependencyObject d)
{
return (bool) d.GetValue(LaunchDefaultBrowserProperty);
}
public static void SetLaunchDefaultBrowser(DependencyObject d, bool value)
{
d.SetValue(LaunchDefaultBrowserProperty, value);
}
private static void HyperlinkUtility_LaunchDefaultBrowserChanged(object sender, DependencyPropertyChangedEventArgs e)
{
DependencyObject d = (DependencyObject) sender;
if ((bool) e.NewValue)
ElementUtility.AddHandler(d, Hyperlink.RequestNavigateEvent, new RequestNavigateEventHandler(Hyperlink_RequestNavigateEvent));
else
ElementUtility.RemoveHandler(d, Hyperlink.RequestNavigateEvent, new RequestNavigateEventHandler(Hyperlink_RequestNavigateEvent));
}
private static void Hyperlink_RequestNavigateEvent(object sender, RequestNavigateEventArgs e)
{
Process.Start(e.Uri.ToString());
e.Handled = true;
}
}
But wait, what is ElementUtility? I guess I couldn't avoid our utility code entirely – ElementUtility.AddHandler calls UIElement.AddHandler, ContentElement.AddHandler, or UIElement3D.AddHandler, depending on the type of the element. Similarly with ElementUtility.RemoveHandler. This dependency property should be supported on the TextBlock (a UIElement) or the Hyperlink (a ContentElement), so we need to support both types of elements. The implementation of AddHandler and RemoveHandler is left as an exercise for the reader.
I also want to talk more about Process.Start, but that will have to wait until my next post.
Posted by Ed Ball at 10:52 AM | Comments (1) | TrackBack
January 11, 2008
Data binding in a FlowDocument or Text Block
One of the great features of WPF is the flow content model for text. The TextBlock element can be used to display inline elements that allow bold, italic, hyperlinks, etc. The FlowDocument content element can represent entire documents of rich content -- paragraphs, tables, figures, etc. FlowDocumentScrollViewer and similar elements can be used to display a FlowDocument.
However, one of the most annoying things about working with the flow content model is the poor support for data binding. In particular, the Text property of the Run element is not a dependency property and thus is not bindable, which means that simple data-bound placeholders in the content aren't feasible.
<!-- this doesn't work -->
<TextBlock>Name: <Run Text="{Binding Name}" />
</TextBlock>
A common workaround for this problem is to replace the Run with a TextBlock, since the Text property of a TextBlock is bindable. This workaround is unsatisfying, however, because the text in the TextBlock does not wrap properly with surrounding text, selection doesn't work properly, etc.
<!-- this isn't ideal -->
<TextBlock>Name: <TextBlock Text="{Binding Name}" />
</TextBlock>
A better workaround is provided by Filipe Fortes and Paul Stovell. Filipe proposes a BindableRun class that derives from Run and provides a BoundText dependency property that sets the Text property whenever it changes. Paul proposes an attached dependency property BindableText that has the same effect. We use a similar attached property at Logos, but here's an example of Filipe's solution:
<!-- this usually works -->
<TextBlock>Name: <bt:BindableRun BoundText="{Binding Name}" />
</TextBlock>
Either solution is great -- except for the error. In comments to both Filipe's and Paul's posts, as well as in an MSDN forum post, you'll find that users of this technique sometimes encounter a confusing error: Collection was modified; enumeration operation may not execute.
A reply to the MSDN forum post by Ifeanyi Echeruo provides a workaround for the error -- delay the setting of the Text property by using Dispatcher.BeginInvoke. This workaround is effective, but it can result in annoying flickering -- the user will probably be able to see the text of the Run change from the empty string to the bound value.
Fortunately, we've found an improved workaround. Here's the short version: set the DataContext property on the BindableRun as follows (but read the update below):
<!-- this always works -->
<TextBlock>Name: <bt:BindableRun BoundText="{Binding Name}"
DataContext="{Binding DataContext, RelativeSource=
{RelativeSource AncestorType=FrameworkElement}}" />
</TextBlock>
The error occurs when the binding is updated because of a change to an inherited dependency property. The most common scenario is when the inherited DataContext changes. I don't have access to the WPF source code, but, based on the call stack, it appears that an inherited properly like DataContext is propagated to its descendants. When the enumeration of descendants gets to the BindableRun, the BindableText properly changes according to the new DataContext, which sets the Run property. However, for some reason, changing the flow content invalidates the enumeration and raises an exception.
Direct binding doesn't cause the enumeration of descendants. So, to avoid the error, don't allow your binding to depend on any inherited dependency properties. The most commonly used inherited dependency property is the DataContext, which is used implicitly by data bindings that don't specify an explicit source. You can avoid using the inherited DataContext by setting the Source or RelativeSource on the binding. Easier still, you can set the DataContext of the BindableRun directly, which updates your DataContext without relying on the enumeration described above. The very simplest way to solve the problem, then, as shown above, is to bind the DataContext to that of its FrameworkElement ancestor, which is outside of the flow content and thus doesn't update while the flow content is being enumerated.
Update: In some circumstances, the use of RelativeSource for the DataContext binding can result in the DataContext being null when the UI element is first displayed, only to be corrected after a moment. To avoid this behavior, bind directly to the ancestor by name.
<!-- this works even better -->
<TextBlock x:Name="tb">Name: <bt:BindableRun
BoundText="{Binding Name}"
DataContext="{Binding DataContext, ElementName=tb}}" />
</TextBlock>
We also experienced printing problems when using the RelativeSource solution, so I highly recommend the ElementName solution.
Posted by Ed Ball at 12:35 PM | Comments (3) | TrackBack