Category Archives: WPF

Sample Quiz on Delayed Execution of IEnumerable

A sample quiz to test your knowledge of delayed execution of IEnumerable<T>.

Assume you have a method as follows:


public IEnumerable<AppointmentSlot> GetOpenSlots(Dictionary<string, string> request)
{
return _openSlots;
}

The above method returns a list of open slots which could simply have been a collection prepoluated when the class was constructed.

Say somewhere else in the code you call this method as follows:


public RelayCommand GetOpenAppointmentSlotsCmd
{
get { return new RelayCommand(o => true, o =>
{
OpenAppointmentSlots = _resourcesServiceClient
.GetOpenSlots(null)
.Select(s => new AppointmentSlotViewModel(s));
});}
}

Where OpenAppointmentSlots is a property of type IEnumerable<T> and T is of type AppointmentSlotViewModel which in simple form could look something like  this:


public class AppointmentSlotViewModel : ViewModelBase
{
public AppointmentSlot AppointmentSlot { get; }

public AppointmentSlotViewModel(AppointmentSlot slot)
{
AppointmentSlot = slot;
}

public DateTime StartsAt => AppointmentSlot.StartsAt;
public int DurationInMiutes => AppointmentSlot.DurationInMiutes;
public string ProviderId => AppointmentSlot.ProviderId;

public RelayCommand BookAppointmentSlot
{
get
{
return new RelayCommand(o => NotBooked, o =>
{
NotBooked = false;
});
}
}

private bool _notBooked = true;
public bool NotBooked
{
get { return _notBooked; }
set
{
SetField(ref _notBooked, value, "NotBooked");
}
}
}

Further assume you had the list of OpenAppointmentSlots bound to a ListView and had a DataTemplate which manipulated the NotBooked property of AppointmentSlotViewModel.

Now, say somewhere else in the code, perhaps in the same view model which gets the list of open slots, you attempt to submit these selected open slots for booking as follows:


public RelayCommand BookAppointmentCmd
{
get
{
return new RelayCommand(o => true, o =>
{
var booked = OpenAppointmentSlots..Where(s => s.IsBooked()).Select(nb => nb.AppointmentSlot);
BookedAppointmentStatus = _resourcesServiceClient.BookAppointments(booked);
});
}
}

After manipulating the UI such that you now have two slots booked,  what do you suppose will be the result of the variable called booked, in the above snippet and why?

 

WPF Grid Horizontal andVertical Seperator

This gets me every time… so I will blog this and will never have to Google around for it.  Hopefully, when I do so, this blog post appears top in the Google search results.

I need a WPF Grid Separator, basically a visual line that allows two parts of a page to be resized…..

I google for “WPF horizontal seperator” and the closest thing that came around was this blog post .  Close but not quite, so I though I could just translate the Width=”5″ to Heigh=”5″ and HorizontalAlignment=“Stretch” to VerticalAlignment=”Stretch”. Those translations do not quite work. So here it is in one place, the XAML for a WPF Horizontal and Vertical grid splitter.

At this point, my XAML looked like this:


<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
<GridSplitter Grid.Row="1" Height="5" HorizontalAlignment="Stretch" />
</Grid>

Happy coding…

Practical guidance on improving user experience by marshalling to UI thread just-in-time

Marshalling work between the between a background and main thread is very common pattern in many UI frameworks and Windows Presentation Foundation aka WPF is one of them. When you have a long running operation to perform, this needs to be done on a background thread, freeing the main UI thread from responding to peripheral events from mouse and keyboard operations, improving the overall user experience.

In WPF, there is one main UI thread and all objects created by the main UI thread are owned by the UI thread. Access to these objects, therefore, must be done on the main UI thread. This is well documented literature. However, what is missing is architectural guidance that one should take into consideration when designing applications that require lots of marshalling between the background and main thread. In this post I attempt this using a practical example, which involves an application that plots bus stops and associated activities on a map.

Say or application on ocassion, via a background thread calls a RESTFul web-service to retrieve spatial representation of air contaminants resulting from say a nuclear disaster within 10 km of a given position. The result of such a computation would typically look like this:

This typically is a long running operation so needs to be done in on a background thread. The result of this web-service call could be a collection of spatial records, containing bus stops including their respective coordinate and some visitation information that can be plotted onto a map.

Plotting geometries represented using vendor neutral specifications such as the Open Geospatial Consortium’s Web Feature Service specification aka OGC WFS, onto a map such as ThinkGeo’s MapSuite for WPF Desktop, or even ESRI’s ArcGIS Runtime SFK for WPF requires converting the raw data into the respective vendor’s format.

Let’s use ThinkGeo’s MapSuite as case study and take a few minutes to quickly understand their one small portion of their underlying architecture.

A ThinkGeo WpfMap object is the main UI element rendered on screen. An instance of this object exposes a collection of Overlays. There are many different types of overlays but for the sake of this discussion we limit ourselves to the LayerOverlay.

A LayerOverlay contains a collection of layers. There are many different layer types but fundamentally, the layer is responsible for converting whatever geometry context it is provided into an image that can be rendered by the Overlay. Visual representation of features can be controlled on some layers, such as the InMemoryFeatureLayer by assigning the layer an appropriate style.

Converting raw data from a vendor neutral OGC format to ThinkGeo feature representationfeature, creating the appropriate layer and styles can all be done on the background thread, since

  • these objects do not derive from DispatcherObject
  • we could be dealing with a thousand of these objects

Only after the layer has been created and we are ready to add it to the Overlays collection do we need to marshall to the UI thread. To encapsulate all of this in one place, one can implement a custom ManagedInMemoryFeatureLayer deriving from InMemoryFeatureLayer with the following logic:


    public class ManagedInMemoryFeatureLayer : InMemoryFeatureLayer
    {
        public event Action<object, EventArgs> FeaturesCreated;

        private readonly StylesFactory _stylesFactory;
        private readonly MapSuiteFeatureMapper _featureMapper;
        private readonly ILoggerFacade _logger;
        private readonly object _lock = new object();

        public ManagedInMemoryFeatureLayer(InMemoryLayerContext context, StylesFactory stylesFactory, MapSuiteFeatureMapper featureMapper)
        {
            if (!context.DataManager.IsSchemaDefined())
            {
                throw new InvalidDataException(" Geo data manager schema is not defined.");
            }
            
            Guard.ThrowIfArgumentIsNull(context.RenderInfo);
            _stylesFactory = stylesFactory;
            _featureMapper = featureMapper;

            Name = context.Name;
            SetupDataManager(context);
            
            SymbolizationInfo = context.RenderInfo;
            ApplyRenderer(stylesFactory, SymbolizationInfo, true);

            // NOTE: this line is crucial especially when you have features that need to 
            // be rendered based on the attributes.  So it is important the the geospatial 
            // schema is defined, hence the initial check.
            SetupLayerDataSchema(context.DataManager);

            // A random chosen number.
            MinNumberOfFeaturesRequiredForIndex = 20;
        }

        private void SetupDataManager(InMemoryLayerContext context)
        {
            DataManager = context.DataManager;

            DataManager.GeoDatasetChanged += (o, e) => Refresh(DataManager.GeoDataset);

            context.IsPendingLayerCreation = false;
        }

        public int MinNumberOfFeaturesRequiredForIndex { get; set; }
        public Style Style { get; set; }
        public IGeoDataManager DataManager { get; private set; }
        public ISymbolizationInfo SymbolizationInfo { get; private set; }
        public ObjectState LayerState { get; set; }
        public SpatialFilter PostFilter { get; set; }

        public void ApplyRenderer(StylesFactory styleFactory, ISymbolizationInfo stylingInfo, bool refreshLayer)
        {
            if (stylingInfo == null)
            {
                throw new ArgumentNullException("stylingInfo");
            }

            Style = styleFactory.CreateStyle(stylingInfo);

            if (stylingInfo.UseDefaultSymbolInfo)
            {
                SetDefaultStyle();
            }
            else
            {
                SetValueStyles(styleFactory, stylingInfo);
            }

            if (refreshLayer)
            {
                RecreateFeatures();
            }
        }

        private void SetupLayerDataSchema(IGeoDataManager goeDataManager)
        {
            var geoDatasetSchema = goeDataManager.GeoDataset.Schema;
            if (geoDatasetSchema != null && geoDatasetSchema.Columns.Count > 0)
            {
                Open();

                foreach (var field in geoDatasetSchema.Columns)
                {
                    if (field.ValidateColumn())
                    {
                        Columns.Add(new FeatureSourceColumn(field.Name, field.TypeString, 100));
                    }
                }

                if (SymbolizationInfo.ContainsAttibutesForStyling)
                {
                    foreach (var styleField in SymbolizationInfo.GetAttributeNamesForSpecialStyles())
                    {
                        Columns.Add(new FeatureSourceColumn(styleField));
                    }
                }

                Close();
            }
        }

        protected void SetDefaultStyle()
        {
            var oldStyle = ZoomLevelSet.ZoomLevel01.CustomStyles.FirstOrDefault(s => string.CompareOrdinal(s.Name, Style.Name) == 0);
            if (oldStyle != null)
                ZoomLevelSet.ZoomLevel01.CustomStyles.Remove(oldStyle);
            ZoomLevelSet.ZoomLevel01.CustomStyles.Add(Style);
            ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
        }

        protected void SetValueStyles(StylesFactory stylesFactory, ISymbolizationInfo renderInfo)
        {
            var attributeSymbolInfos = renderInfo.GetAttributedSymbolInfos();
            Guard.ThrowIfArgumentIsNull(attributeSymbolInfos);

            var valueStyles = stylesFactory.CreateValueStyles(attributeSymbolInfos);
            ZoomLevelSet.ZoomLevel01.CustomStyles.Clear();
            foreach (var style in valueStyles)
            {
                ZoomLevelSet.ZoomLevel01.CustomStyles.Add(style);
            }

            ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
        }

        public void RecreateFeatures()
        {
            Refresh(DataManager.GeoDataset);
        }

        public void Refresh(IGeoCollection dataset)
        {
            Task.Factory.StartNew(() =>
            {
                try
                {
                    if (PostFilter != null)
                    {
                        PostFilter.PreProcess();
                    }

                    IList<Feature> featuresToAdd = _featureMapper.CreateFeatures(dataset.GetFeatures(PostFilter)).ToArray();
                    lock (_lock)
                    {
                        InternalFeatures.Clear();
                        InternalFeatures.AddRange(featuresToAdd);
                    }

                    if (InternalFeatures.Count > MinNumberOfFeaturesRequiredForIndex)
                    {
                        BuildIndex();
                    }

                    RequestDrawing();
                    FeaturesCreated(null, null);
                }
                catch (Exception ex)
                {
                    LayerState = ObjectState.Error;
                    _logger.Error(ex, DataManager.Id.Alias);
                }
            });
        }   
        
    }
}

Creation of this class can happen on the background thread. Feature mapping can also occur on the background thread. However, when the layer calls RequestDrawing or FeatureCreated, interested parties are supposed to ensure these events are handled on the UI thread.

In our sample application, an entity called InMemoryFeatureLayerViewModel which manages a layer and its associated legend entry, a legend being the table of content representing all layers on a map, subscribes to FeaturesCreated event exposed by the layer and responds as accordingly. A snippet of a sample InMemoryFeatureLayerViewModel implementation is listed below:

 public class InMemoryFeatureLayerViewModel : FeatureLayerModel<LayerOverlay, ManagedInMemoryFeatureLayer, InMemoryLayerContext>
    {
        private readonly ManagedInMemoryFeatureLayer _layer;
        public InMemoryFeatureLayerViewModel(InMemoryLayerContext context, ManagedInMemoryFeatureLayer layer, bool isRemovable)
            : base(context)
        {
            Layer = _layer = layer;
            LayerDefinition.Layer = layer;
            LayerContext = context;

            context.RenderInfoChanged += OnRenderInfoChanged;
            _layer.FeaturesCreated += OnLayerFeaturesCreatedCreated;
        }

        private void OnRenderInfoChanged(object sender, DataEventArgs<ISymbolizationInfo> e)
        {
            // we need to execute on main UI thread...
            var dispatcher = GetMainDispatcher();

            dispatcher.BeginInvoke(new Action(() =>
            {
                // update our legend representation
                Representation = LayerContext.GetRepresentation();

                // update our layer styles and referesh layer
                _layer.ApplyRenderer(StylesFactory.Instance, e.Data, true);

            }));
        }

        private void OnLayerFeaturesCreatedCreated(object sender, EventArgs e)
        {
            _layer.FeatureSource.Open();
            var bbox = _layer.FeatureSource.GetBoundingBox();
            _layer.FeatureSource.Close();
            RefreshOverlay(bbox);
        }

   protected void RefreshOverlay(RectangleShape boundingBox)
        {
            if (Overlay == null)
                return;

            if (ParentViewModel == null)
                return;

            Dispatcher mapDispatcher = GetMainDispatcher();
            // this needs to be serviced at a high priority, so we use Normal
            mapDispatcher.BeginInvoke(new Action(() =>
            {
                //TODO: if there are performance concerns for rapidly changing layers, cache casted view.
                Overlay.PanTo(boundingBox);
                Overlay.SafelyRefresh();

                if (ParentViewModel != null)
                {
                    var map = (ExtendedWpfMap)ParentViewModel.GetMapObject();
                    if (map != null)
                    {
                        map.CurrentExtent = boundingBox;
                        map.SafelyRefresh();
                    }
                }
            }), DispatcherPriority.Normal);
        }
    }

Only when refreshing the overlay do we need to marshal to the UI thread. Otherwise, we waste UI thread cycles doing things that can be delegated to the background thread compromising application responsiveness.

One challenge when modelling your solution is reducing the number of entity definitions that rely on DispatcherObject, to just those required for rendering. This way, you can do most of the work on background threads, since these entities are thread agnostic, reserving the UI thread for rendering purposes only. This becomes especially useful, if your application comprises of multiple views rendering data grids, maps and charts. With such applications, you really want the UI thread to do nothing but render and respond to mouse and keyboard events.

Happy coding!

Windows Phone Listbox: ensure ItemSource is bound before SelectedItem

Just spent a couple of hours trying to figure out why setting the SelectedItem of a ListBox kept crashing the application. It turns out that setting the ItemSource binding before the SelectedItem is a no go as per this StackOverflow posting.

So beware when designing your Windows Phone pages. For example, look at the snippet below.

 <toolkit:ListPicker            
            Header="Select a stop"
            Margin="{StaticResource PhoneMargin}"
            Grid.Row="2" 
            Grid.Column="0"
            SelectionMode="Single"
             ItemsSource="{Binding Stops}"
            SelectedItem="{Binding SelectedStop, Mode=TwoWay}">

If you place SelectedItem before ItemSource application will crash when you attempt to programmatically set the SelectedItem.

Use case for custom Log4Net MemoryAppender

When developing an enterprise grade desktop or web application, there is always a need to be able to view application logs including activities in one of the views. These activities could include user actions, system warnings or errors, triggered from the client or server.

Server errors or warnings could be polled for, through a WCF or RESTFul API, and stored in an inmemory collection that is bound to some data grid in a view. Log messages generated on the client, either through exceptions, caught and logged somewhere or via actions that require auditing such as changing sensitive system parameters, can also share the same inmemory collection with data retreived from the server.

Log4Net is a popular .NET library used for application logging. The general pattern in using Log4Net is as follows:

1. Get an ILog instance.
2. Call any of the API on the ILog instance to log your message.

Log4Net log messages eventually are persisted to entitities called Appenders. There are FileAppenders, ConsoleAppenders, InMemoryAppenders and many more. An Appender is the destination of a log message.

We can leverage an MemoryAppender to catch application error or warning messages into a collection which can then be bound to a view. The code is as follows:

 public class NotifyingInMemoryLog4NetAppender : MemoryAppender
    {
        private readonly static BindableCollection<LogMessage> LogData = new BindableCollection<LogMessage>();
        private readonly static List<LoggingEvent> CachedEvents = new List<LoggingEvent>();
        private readonly Timer _flushTimer;
        private readonly object _lock  = new object();
        public NotifyingInMemoryLog4NetAppender()
        {
            _flushTimer = new Timer((arg) =>
            {
                lock (_lock)
                {
                    if (CachedEvents.Count > 0)
                    {
                        LogData.AddRange(CachedEvents.Select(Convert));
                        CachedEvents.Clear();
                        
                    }    
                }
            }, null, 5000, 3000);
        }

        public IListExtended Logs
        {
            get { return LogData; }
        }

        private DateTime _lastEventTime = DateTime.Now;
        private readonly TimeSpan _threshold = new TimeSpan(0, 0, 0, 0, 500);

        protected override void Append(LoggingEvent loggingEvent)
        {
            var now = DateTime.Now;
            
            // if we are getting flooded, cache the events and process them later.
            if (now - _lastEventTime < _threshold)
            {
                lock (_lock)
                {
                    CachedEvents.Add(loggingEvent);
                }
            }
            else
            {
                LogData.Add(Convert(loggingEvent));    
            }

            _lastEventTime = DateTime.Now;
        }

        private Severity ConvertFrom(Level level)
        {
            if (level == Level.Error)
                return Severity.Error;
            if (level == Level.Warn)
                return Severity.Warning;
            if (level == Level.Info)
                return Severity.Information;

            return Severity.Trace;
        }

        private LogMessage Convert(LoggingEvent loggingEvent)
        {
            return new LogMessage
            {
                Timestamp = loggingEvent.TimeStamp,
                Message = loggingEvent.RenderedMessage,
                Severity = ConvertFrom(loggingEvent.Level)
            };
            
        }
    }

This custom appender is used as follows:

<log4net debug="true">
    <appender name="InMemory" type="Infrastructure.Dictionary.Facades.NotifyingInMemoryLog4NetAppender, Pidac.Infrastructure.Dictionary ">
       <conversionPattern value="%5level [%thread] (%file:%line) - %message%newline" />
    </appender>
    <appender name="Console" type="log4net.Appender.ConsoleAppender">
      <layout type="log4net.Layout.PatternLayout">
        <!-- Pattern to output the caller's file name and line number -->
        <conversionPattern value="%5level [%thread] (%file:%line) - %message%newline" />
      </layout>
    </appender>
    <appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
      <file value="dosewin.log" />
      <appendToFile value="true" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="10" />
      <maximumFileSize value="10MB" />
      <staticLogFileName value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
      </layout>
    </appender>
    <root>
      <!--ALL,DEBUG,INFO,WARN,ERROR,FATAL-->
      <level value="DEBUG" />
      <appender-ref ref="Console" />
      <appender-ref ref="RollingFile" />
      <appender-ref ref="InMemory" />
    </root>
  </log4net>

When you call any of the ILog API’s the Append(LoggingEvent loggingEvent) method in our custom implementation is called. We add this message to a local queue and then subsequently pass this on to the Static LogData structure which could be bound to a view. Note that since we do not really care about preserving the order in which the messages appear, since most datagrids today provide an ability for data to be sorted prior to presentation, a simple List will suffice for our queue.

And that is it.

WPF resource path resolution is all about context

Say you wanted to defined a Menu and Toolbar in a XAML file that associated with any code behind. One reason why you would do this is to be able to support a generic data grid view that you pass it a menu, toolbar and status bar with with its asssociated view model. As you can image, such a framework can allow you use a single DatagridView implementation for many different types of view.

Such a menu can be defined as follows:

<Menu
    DockPanel.Dock="Top"
    MinHeight="25"
    Background="Transparent"   
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:utils="clr-namespace:Pidac.Controls.Dictionary.Utils;assembly=Pidac.Controls.Dictionary">
    <Menu.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <utils:SharedResourceDictionary Source="../Resources/Styles.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Menu.Resources>
    <MenuItem 
        Header="File"                  
        Margin="5,1,5,0" >
        <MenuItem 
            Header="Copy"   
            Command="{Binding Commands[CopyCmd]}" CommandParameter="{Binding ViewID}"  
            Icon="{StaticResource CopyImg16}"/>
        <Separator />
        <MenuItem 
            Header="Export" 
            Command="{Binding Commands[ExportToFileCmd]}" 
            CommandParameter="{Binding ViewID}"
            Icon="{StaticResource DocumentExportImg16}"/>
        <MenuItem
            Header="Forward as Attachment" 
            CommandParameter="{Binding ViewID}"
            Command="{Binding Commands[AttachToEmailCmd]}"
            Icon="{StaticResource DocumentAttachImg16}"/>
        <MenuItem 
            Header="Print" 
            Command="{Binding Path=Commands[PrintCmd]}" 
            CommandParameter="{Binding ViewID}"
            Icon="{StaticResource PrintImg16}"/>
        <Separator/>                       
        <MenuItem 
            Header="Refresh Now" 
            Command="{Binding Path=Commands[RefreshViewCmd]}"
            CommandParameter="{Binding ViewID}" 
            Icon="{StaticResource RefreshImage16}"/>   
        <MenuItem 
            Header="Close" 
            Command="{Binding Path=Commands[CloseCmd]}" 
            CommandParameter="{Binding ViewID}"
            Icon="{StaticResource CloseOrExitImage16}"/>
            
    
    <MenuItem Header="Tools" Margin="5,1,5,0" >           
        <MenuItem 
            Header="Query" 
            Command="{Binding Path=Commands[QueryCmd]}" 
            CommandParameter="{Binding ViewID}"
            Icon="{StaticResource SearchImage16}"/>
        <Separator/>
        <MenuItem 
            Header="Select columns" 
            Command="{Binding Path=Commands[SelColumnsCmd]}" 
            CommandParameter="{Binding ViewID}"
            Icon="{StaticResource TableSelectColumnImage16}"/>
        <MenuItem 
            Header="Set Options" 
            Command="{Binding Path=Commands[PropertiesCmd]}" 
            CommandParameter="{Binding ViewID}"
            Icon="{StaticResource SetOptionsImage16}"/>
        </MenuItem>
    </MenuItem>
</Menu>

and the code that will parse this XAML into an menu instance that will be added to the object tree is as follows:

   public static TObject CreateObjectFromResource<TObject>(string resourceUrl, string szBaseUri) //where TObject : UIElement
        {
            var context = new ParserContext();
            if (szBaseUri != null)
                context.BaseUri = new Uri(szBaseUri);

            object Object = null;
            using (Stream resource = ResourceUtils.GetResourceStream(resourceUrl))
                Object = XamlReader.Load(resource, context);

            return (TObject)Object;
        }

Now this is the thing. You MUST absolutely have your resource paths right, otherwise, this you will scratch head for a couple of hours just to get this simple part of the framework in place. The two lines of pain are:

    xmlns:utils="clr-namespace:Pidac.Controls.Dictionary.Utils;assembly=Pidac.Controls.Dictionary"

and

       <utils:SharedResourceDictionary Source="../Resources/Styles.xaml"/>

In the first line, I am using a custom SharedResourceDictionary implementation to re-use styles. Even though this class is implemented in the same assembly that has the XAML resource, I need to still provide the assembly attribute value as well. Doing this:

    xmlns:utils="clr-namespace:Pidac.Controls.Dictionary.Utils"

throws the following exception and this is possibly due to the fact that the XAML parser code is not contained in the same assembly as the SharedResourceDictionary implementation.

On the second line, however, providing an absolute path is not necessary. Reason here is that XAML resource file is embedded within an assembly, which implies that the WPF style resolving process will look for the style relative to the resource location in its containing assembly. An absolute path never hurts but its not necessary.

WPF textblock combine binding with static text

Instead of having say two text boxes in a horizontally aligned stack panel, one configured to display a static label and another configured to display dynamic text via binding as follows:

  <StackPanel Orientation="Horizontal">               
                <TextBlock Margin="10,0" Text="Licensed To:"/>
                <TextBlock Margin="10,0" Text="{Binding LicensedTo" />
            </StackPanel>            

WPF 4 + allows me to define a binding alongside static text using a single TextBlock.

The syntax for this is as follows:

  <StackPanel>               
                <TextBlock Margin="10,0">
                   Licensed To: <TextBlock Margin="10,0" Text="{Binding LicensedTo" />
                </TextBlock>
            </StackPanel>            

And if this is all you used the StackPanel for, it is no longer necessary, simplifying your visual tree.