Saturday 24 December 2011

Obelisk - WP7 MVVM Tombstone Library

Just published first release of "Obelisk" a WP7 library designed to take the pain out of MVVM tombstone persistence.

This library offers an easy way to implement tombstone persistence in WP7 applications. Once the TombstoneHelper is attched to the application, View Models can be registered and all properties with [TombstonePersist] attribute set will be automatically persisted.

Monday 19 December 2011

WP7 Microsoft.Phone.BackgroundAudio.AudioTrack.Tag String Length Limit

This is a bit of an odd one, I found out that the AudioTrack.Tag string property has a limit of 2047, any more than this and an exception is thrown when the AudioTrack object is assigned to BackgroundAudioPlayer.Instance.Track:

HRESULT = 0x8007007A

Friday 16 December 2011

WP7 MVVM Panorama SelectedIndex Binding

The Panorama control doesn't support SelectedItem or SelectedIndex binding which is important for maintaining state on re-activation after tombstoning and may be required for navigation. To solve this problem, I've created a Panorama Behavior called TrackablePonaramaBehavior which can be attached to a panorama and offers a dependency property for binding to the selected index. The System.Windows.Interactivity library needs referencing to use it:

using System;
using Microsoft.Phone.Controls;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace Demo.Behaviors
{
    public class TrackablePanoramaBehavior : Behavior<Panorama>
    {
        private Panorama _panarama = null;
        private bool _updatedFromUI = false;

        // DP for binding index
        public static readonly DependencyProperty SelectedIndexProperty =
            DependencyProperty.Register("SelectedIndex", typeof(int), typeof(TrackablePanoramaBehavior),
            new PropertyMetadata(0, new PropertyChangedCallback(SelectedIndexPropertyChanged)));

        // Index changed by view model
        private static void SelectedIndexPropertyChanged(DependencyObject dpObj, DependencyPropertyChangedEventArgs change)
        {
            if(change.NewValue.GetType() != typeof(int) || dpObj.GetType() != typeof(TrackablePanoramaBehavior))
                return;

            TrackablePanoramaBehavior track = (TrackablePanoramaBehavior)dpObj;

            // If this flag is not checked, the panorama smooth transition is overridden
            if (!track._updatedFromUI)
            {
                Panorama pan = track._panarama;

                int index = (int)change.NewValue;

                if (pan.Items.Count > index)
                {
                    pan.DefaultItem = pan.Items[(int)change.NewValue];
                }
            }

            track._updatedFromUI = false;
        }

        public int SelectedIndex
        {
            get { return (int)GetValue(SelectedIndexProperty); }
            set { SetValue(SelectedIndexProperty, value); }
        }

        protected override void OnAttached()
        {
            base.OnAttached();

            this._panarama = base.AssociatedObject as Panorama;
            this._panarama.SelectionChanged += _panarama_SelectionChanged;
        }       

        protected override void OnDetaching()
        {
            base.OnDetaching();

            if(this._panarama != null)
                this._panarama.SelectionChanged += _panarama_SelectionChanged;
        }

        // Index changed by UI
        private void _panarama_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            _updatedFromUI = true;
            SelectedIndex = _panarama.SelectedIndex;
        }
    }
}


To implement this in the View, the following XAML is used (I chopped most xmlns out to make it more concise):

<phone:PhoneApplicationPage

    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

    DataContext="{Binding Main, Source={StaticResource Locator}}">
  
    <!--LayoutRoot contains the root grid where all other page content is placed-->
    <Grid x:Name="LayoutRoot">
        <controls:Panorama>
            <i:Interaction.Behaviors>
                <track:TrackablePanoramaBehavior SelectedIndex="{Binding Path=SelectedIndex, Mode=TwoWay}" />
            </i:Interaction.Behaviors>


And simply bind to the view model:

public int SelectedIndex
{
    get { return this._selectedIndex; }
    set
    {
        if (this._selectedIndex != value)
        {
            this._selectedIndex = value;

            base.RaisePropertyChanged("SelectedIndex");
        }
    }
}

Monday 12 December 2011

Silverlight (WP7) ListBox & ItemsControl Item Commanding

ListBoxes are a really powerful and flexible way of displaying data in Silverlight, the main problem I find is when you want to use an MVVM pattern and detect when an item is selected and re-selected. It is possible to bind to the SelectedItem property and look for changes in the VM to perform some kind of action, however if you want to select the same item again you need to select something else, then re-select it because SelectedItem becomes latched.

Instead of using a ListBox, it is more flexible to us an ItemsControl (which a ListBox derives from). This has the functionality we require but without the SelectedItem dependency property which we aren't using. The following example has an Items control with a button containing an image in the ItemTemplate:

<Grid x:Name="LayoutRoot">
  <ItemsControl ItemsSource="{Binding Path=Items, Mode=OneWay}">
    <ItemsControl.ItemsPanel>
      <ItemsPanelTemplate>
        <StackPanel Orientation="Horizontal" />
      </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
      <DataTemplate>
        <Button>
          <Image Source="{Binding Path=Url, Mode=OneWay}" Stretch="Uniform" />
        </Button>
      </DataTemplate>
    </ItemsControl.ItemTemplate>
  </ItemsControl>
</Grid>

This is fairly straight forward, but the problem is, if you add a command to the button like this:

<Button Command="{Binding ImageCommand}" CommandParameter="{Binding}" >
  <Image Source="{Binding Path=Url, Mode=OneWay}" Stretch="Uniform" />
</Button>

The button will try and bind to a command in the data item which doesn't exist; instead of the parent view model.

To get round this, the command needs to get outside of the bound item's context, into the main view model by binding to the grid's data context (it could be any named, non-templated element on a page/user control) using the ElementName and Path binding properties:

<Button Command="{Binding ElementName=LayoutRoot, Path=DataContext.ImageCommand}"
  CommandParameter="{Binding}" >
  <Image Source="{Binding Path=Url, Mode=OneWay}" Stretch="Uniform" />
</Button>

This can now easily be bound to a view model containing a command like this:

private ICommand _imageCommand = null;

public ICommand ImageCommand

{
    get { return this._imageCommand; }
}

private bool IsCommandExecutable

{
    get { return true; }
}

// Call this in VM constructor

private void InitialiseCommands()
{
    // Join
    this._imageCommand = new DelegateCommand((param) =>
    {
        this.DoSomething((ImageDetails)param);
        },
            (p) =>
            {
                return this.IsCommandExecutable;
            });
    }

private void DoSomething(ImageDetails obj)

{

}


This technique obviously works with ListBox controls as well, so buttons can be placed alongside other content whilst maintaining the selection mechanism. This example has a delete button alongside some title text:

<Grid x:Name="LayoutRoot">
  <ListBox ItemsSource="{Binding Items, Mode=OneWay}"
    SelectedItem="{Binding SelectedItem, Mode=TwoWay>
    <ListBox.ItemsPanel>
      <ItemsPanelTemplate>
        <StackPanel Orientation="Horizontal" />
      </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemTemplate>
      <DataTemplate>
<Grid>
 <Grid.ColumnDefinitions>
   <ColumnDefinition Width="Auto"/>
   <ColumnDefinition Width="*"/>
 <Grid.ColumnDefinitions>
          <TextBlock Text="{Binding Title} />
          <Button Grid.Column="1" Command="{Binding DeleteCommand}" />
        <Grid>
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>
</Grid>

Thursday 8 December 2011

Git .gitignore File and Visual Studio

Just started using Git and GitHub in the last few weeks (missing TFS to be honest!). Anyway it's handy to put a .gitignore file in the root of you repository folder to stop Git showing all the files you don't want to Commit. First off in Windows it's not easy to create a .gitignore file as Windows complains about the file not having a name, so this can be solved by launhing Notepad from a commandline with .gitignore as the file argument:

notepad.exe .gitignore

There is a standard selection of ignore rules on the Help.GitHub site.

In addition to these I added the following for VS:

# VS files
######################
*.suo
*.user
Bin/
obj/
*.
*.vsmdi
*.testsettings
*.cachefile

Wednesday 7 December 2011

Windows Phone Toolkit Expander Template Fix

The expander has a little line which appears alongside the drop-down panel. I noticed that this line is visible behind the header, so I tweaked the template to animate the line opacity so it is hidden when collapsed:

<Style x:Key="ExpanderViewStyle" TargetType="toolkit:ExpanderView">
            <Setter Property="HorizontalAlignment" Value="Stretch"/>
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            <Setter Property="ItemsPanel">
                <Setter.Value>
                    <ItemsPanelTemplate>
                        <StackPanel/>
                    </ItemsPanelTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="toolkit:ExpanderView">
                        <Grid>
                            <Grid.Resources>
                                <QuadraticEase x:Key="QuadraticEaseOut" EasingMode="EaseOut"/>
                                <QuadraticEase x:Key="QuadraticEaseInOut" EasingMode="EaseInOut"/>
                            </Grid.Resources>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="41"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="Auto"/>
                            </Grid.RowDefinitions>
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="ExpansionStates">
                                    <VisualStateGroup.Transitions>
                                        <VisualTransition From="Collapsed" GeneratedDuration="0:0:0.15" To="Expanded">
                                            <Storyboard>
                                                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="ItemsCanvas">
                                                    <EasingDoubleKeyFrame EasingFunction="{StaticResource QuadraticEaseOut}" KeyTime="0:0:0.00" Value="0"/>
                                                    <EasingDoubleKeyFrame x:Name="CollapsedToExpandedKeyFrame" EasingFunction="{StaticResource QuadraticEaseOut}" KeyTime="0:0:0.15" Value="1"/>
                                                </DoubleAnimationUsingKeyFrames>
                                                <DoubleAnimation Duration="0" To="1.0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ItemsCanvas"/>
                                                <DoubleAnimation Duration="0" To="1.0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="Line"/>
                                            </Storyboard>
                                        </VisualTransition>
                                        <VisualTransition From="Expanded" GeneratedDuration="0:0:0.15" To="Collapsed">
                                            <Storyboard>
                                                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="ItemsCanvas">
                                                    <EasingDoubleKeyFrame x:Name="ExpandedToCollapsedKeyFrame" EasingFunction="{StaticResource QuadraticEaseInOut}" KeyTime="0:0:0.00" Value="1"/>
                                                    <EasingDoubleKeyFrame EasingFunction="{StaticResource QuadraticEaseInOut}" KeyTime="0:0:0.15" Value="0"/>
                                                </DoubleAnimationUsingKeyFrames>
                                                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ItemsCanvas">
                                                    <EasingDoubleKeyFrame EasingFunction="{StaticResource QuadraticEaseInOut}" KeyTime="0:0:0.00" Value="1.0"/>
                                                    <EasingDoubleKeyFrame EasingFunction="{StaticResource QuadraticEaseInOut}" KeyTime="0:0:0.15" Value="0.0"/>
                                                </DoubleAnimationUsingKeyFrames>
                                                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="Line">
                                                    <EasingDoubleKeyFrame EasingFunction="{StaticResource QuadraticEaseInOut}" KeyTime="0:0:0.00" Value="1.0"/>
                                                    <EasingDoubleKeyFrame EasingFunction="{StaticResource QuadraticEaseInOut}" KeyTime="0:0:0.15" Value="0.0"/>
                                                </DoubleAnimationUsingKeyFrames>
                                                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="ItemsCanvas">
                                                    <EasingDoubleKeyFrame EasingFunction="{StaticResource QuadraticEaseInOut}" KeyTime="0:0:0.00" Value="0.0"/>
                                                    <EasingDoubleKeyFrame EasingFunction="{StaticResource QuadraticEaseInOut}" KeyTime="0:0:0.15" Value="-35"/>
                                                </DoubleAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualTransition>
                                    </VisualStateGroup.Transitions>
                                    <VisualState x:Name="Collapsed">
                                        <Storyboard>
                                            <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="ItemsCanvas"/>
                                            <DoubleAnimation Duration="0" To="0.0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ItemsCanvas"/>
                                            <DoubleAnimation Duration="0" To="0.0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="Line"/>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Expanded">
                                        <Storyboard>
                                            <DoubleAnimation Duration="0" Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="ItemsCanvas"/>
                                            <DoubleAnimation Duration="0" To="1.0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ItemsCanvas"/>
                                            <DoubleAnimation Duration="0" To="1.0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="Line"/>
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                                <VisualStateGroup x:Name="ExpandabilityStates">
                                    <VisualState x:Name="Expandable"/>
                                    <VisualState x:Name="NonExpandable">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="ExpandableContent">
                                                <DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="Collapsed"/>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="Line">
                                                <DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="Collapsed"/>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="NonExpandableContent">
                                                <DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="Visible"/>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <ListBoxItem x:Name="ExpandableContent" Grid.ColumnSpan="2" Grid.Column="0" toolkit:TiltEffect.IsTiltEnabled="True" Grid.Row="0" Grid.RowSpan="2">
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="41"/>
                                        <ColumnDefinition Width="*"/>
                                    </Grid.ColumnDefinitions>
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto"/>
                                        <RowDefinition Height="Auto"/>
                                        <RowDefinition Height="Auto"/>
                                    </Grid.RowDefinitions>
                                    <ContentControl x:Name="Header" Grid.ColumnSpan="2" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" Grid.Column="0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.Row="0"/>
                                    <ContentControl x:Name="Expander" ContentTemplate="{TemplateBinding ExpanderTemplate}" Content="{TemplateBinding Expander}" Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Margin="11,0,0,0" Grid.Row="1"/>
                                    <Grid x:Name="ExpanderPanel" Background="Transparent" Grid.ColumnSpan="2" Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"/>
                                </Grid>
                            </ListBoxItem>
                            <Line x:Name="Line" Grid.Column="1" HorizontalAlignment="Left" Grid.Row="2" Opacity="0" Grid.RowSpan="2" Stretch="Fill" Stroke="{StaticResource PhoneSubtleBrush}" StrokeThickness="3" X1="0" X2="0" Y1="0" Y2="1"/>
                            <ContentControl x:Name="NonExpandableContent" Grid.ColumnSpan="2" ContentTemplate="{TemplateBinding NonExpandableHeaderTemplate}" Content="{TemplateBinding NonExpandableHeader}" Grid.Column="0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.Row="0" Grid.RowSpan="2" Visibility="Collapsed"/>
                            <Canvas x:Name="ItemsCanvas" Grid.Column="1" Margin="11,0,0,0" Opacity="0.0" Grid.Row="2">
                                <Canvas.RenderTransform>
                                    <CompositeTransform TranslateY="0.0"/>
                                </Canvas.RenderTransform>
                                <ItemsPresenter x:Name="Presenter"/>
                            </Canvas>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

Thursday 20 October 2011

sllauncher.exe 5.0.60818.0 - OutOfBrowserSettings.Icons Issue

We've been testing an SL4 app which is used both in and out of browser agains SL5RC. Which runs fine in browser, but when launched out-of-browser, sllauncher crashed with the following error:

Faulty module name: unknown v0.0.0.0

Exception Code: 0xc0000005

I created a dummy SL5 test app and it ran OOB fine.

After stripping back our main app to almost nothing I still had the same problem, but I noticed we had icons set in the OOB settings. OutOfBrowserSettings.xml Example:

<OutOfBrowserSettings ShortName="SLApp" EnableGPUAcceleration="True" ShowInstallMenuItem="False">
  <OutOfBrowserSettings.Blurb>Install SLApp application out of the browser</OutOfBrowserSettings.Blurb>
  <OutOfBrowserSettings.WindowSettings>
    <WindowSettings Title="SLApp" Height="60" Width="595" />
  </OutOfBrowserSettings.WindowSettings>
  <OutOfBrowserSettings.SecuritySettings>
    <SecuritySettings ElevatedPermissions="Required" />
  </OutOfBrowserSettings.SecuritySettings>
  <OutOfBrowserSettings.Icons>
    <Icon Size="16,16">Icons/SLApp16x16.png</Icon>
    <Icon Size="32,32">Icons/SLApp32x32.png</Icon>
    <Icon Size="48,48">Icons/SLApp48x48.png</Icon>
    <Icon Size="128,128">Icons/SLApp128x128.png</Icon>
  </OutOfBrowserSettings.Icons>
</OutOfBrowserSettings>

(These settings can be set in the SL app OOB properties dialogue as well)

IF THE ICONS BLOCK IS REMOVED:

<OutOfBrowserSettings ShortName="SLApp" EnableGPUAcceleration="True" ShowInstallMenuItem="False">
  <OutOfBrowserSettings.Blurb>Install SLApp application out of the browser</OutOfBrowserSettings.Blurb>
  <OutOfBrowserSettings.WindowSettings>
    <WindowSettings Title="SLApp" Height="60" Width="595" />
  </OutOfBrowserSettings.WindowSettings>
  <OutOfBrowserSettings.SecuritySettings>
    <SecuritySettings ElevatedPermissions="Required" />
  </OutOfBrowserSettings.SecuritySettings>
</OutOfBrowserSettings>

The aplication runs fine OOB!

This looks like a bug in sllauncher.exe 5.0.60818.0.

Wednesday 7 September 2011

RichTextBlock XAML Bindable Rich Text Block

I recently needed to use a RichTextBox for a project, but was disapointed to find there is no way of binding the content of the control. Luckily it's possible to access the XAML property and inject RichText format XAML into it. I decided to create a control derived from RichTextBox with a dependency property  used to bind the Rich Text XAML.

The first step is to create the control (I re-exposed the mouse click event as the RichTextBox buries it):

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;

using System.Windows.Ink;
using System.Windows.Input;

namespace RichTextBlock
{
  /// <summary>
  /// Used for databinding to Xaml property via new XamlSource DP
  /// </summary>
  public class RichXamlTextBlock : RichTextBox
  {
    public event MouseButtonEventHandler MouseClicked = null;
    private static string _xamlStart = "<Section xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"><Paragraph>";
    private static string _xamlEnd = "</Paragraph></Section>";

    public RichXamlTextBlock()
      : base()
    {
      base.Cursor = Cursors.Arrow;
    }

    #region XamlSource

    public static readonly DependencyProperty XamlSourceProperty = DependencyProperty.Register("XamlSource", typeof(string), typeof(RichXamlTextBlock),
        new PropertyMetadata(null, new PropertyChangedCallback(OnXamlSourcePropertyChanged)));

    public string XamlSource
    {
      get { return (string)GetValue(XamlSourceProperty); }
      set
      {
        SetValue(XamlSourceProperty, value);
      }
    }

    private static void OnXamlSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      RichXamlTextBlock rtb = (RichXamlTextBlock)d;

      rtb.Xaml = string.Format("{0}{1}{2}", _xamlStart, e.NewValue, _xamlEnd as string);
    }

    #endregion

    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
    {
      if (this.MouseClicked != null)
      {
        this.MouseClicked(this, e);
      }
    }
  }
}

The next step is to create a View Model with some XAML rich text. It is important to be careful what you bind as you could end up doing an 'injection attack' on your markup by accident!

using System;

namespace RichTextBlock
{
    public class ViewModel
    {
        private string _xamlSource = "This is a demonstration of the <Run Text=\"RichTextBlock\" FontWeight=\"Bold\" Foreground=\"Green\"/>. The control's XAML can be data bound unlike a normal <Run Text=\"RichTextBox\" FontWeight=\"Bold\" Foreground=\"Red\"/>.";

        public string XamlSource
        {
            get { return this._xamlSource; }
        }
    }
}

Next the new control needs adding to a view:

<UserControl x:Class="RichTextBlock.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:RichTextBlock"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
   
    <UserControl.DataContext>
        <local:ViewModel />
    </UserControl.DataContext>
   
    <UserControl.Resources>
       
        <Style x:Key="RichXamlTextBlockStyle" TargetType="local:RichXamlTextBlock">
            <Setter Property="IsReadOnly" Value="True" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="local:RichXamlTextBlock">
                        <Grid x:Name="ContentElement" Background="{TemplateBinding Background}" />
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
       
    </UserControl.Resources>

    <Grid x:Name="LayoutRoot" Background="White">
        <local:RichXamlTextBlock XamlSource="{Binding Path=XamlSource, Mode=OneWay}" Style="{StaticResource RichXamlTextBlockStyle}" />
    </Grid>
</UserControl>

I also added a stripped down template to remove the TextBox type appearance to get it looking like a TextBlock.

Wednesday 10 August 2011

Toolkit Chart ControlTemplate

<ControlTemplate TargetType="toolkit:Chart">
                        <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}">
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="*"/>
                                </Grid.RowDefinitions>
                                <toolkit:Title Content="{TemplateBinding Title}" Style="{TemplateBinding TitleStyle}"/>
                                <Grid Margin="0,15,0,15" Grid.Row="1">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="*"/>
                                        <ColumnDefinition Width="Auto"/>
                                    </Grid.ColumnDefinitions>
                                    <toolkit:Legend x:Name="Legend" Grid.Column="1" Header="{TemplateBinding LegendTitle}" Style="{TemplateBinding LegendStyle}"/>
                                    <System_Windows_Controls_DataVisualization_Charting_Primitives:EdgePanel x:Name="ChartArea" Style="{TemplateBinding ChartAreaStyle}">
                                        <Grid Style="{TemplateBinding PlotAreaStyle}" Canvas.ZIndex="-1"/>
                                        <Border BorderBrush="#FF919191" BorderThickness="1" Canvas.ZIndex="10"/>
                                    </System_Windows_Controls_DataVisualization_Charting_Primitives:EdgePanel>
                                </Grid>
                            </Grid>
                        </Border>
                    </ControlTemplate>

Tuesday 2 August 2011

WIX 3 NETSH CustomAction

I've been writing a WIX 3 installer for a Windows Service with 2 self hosted WCF services. In order to connect to the WCF services, a Namespace reservation must be made using netsh.exe. I wanted to get the WIX installer to perform this operation during installation so decided to use a CustomAction to achieve this. As anybody who uses WIX will know, it is extremely powerful but sparsely documented and quiet frustrating to use. It took a while to get the CustomAction to work, so I thought I'd share the solution:

!-- Custom action to set WCF namespace reservation -->

<CustomAction Id="ListenerServiceAddReservation"
                  Directory="INSTALLLOCATION"
                  ExeCommand="[SystemFolder]netsh.exe http add urlacl url=http://+:8888/ServiceNamespace/TestService/ sddl=D:(A;;GX;;;WD)"
                  Return="asyncWait" />

<CustomAction Id="ListenerServiceDeleteReservation"
                  Directory="INSTALLLOCATION"
                  ExeCommand="[SystemFolder]netsh.exe http delete urlacl url=http://+:8888/ ServiceNamespace/TestService/"
                  Return="asyncWait" />
  
<InstallExecuteSequence>       
    <Custom Action="ListenerServiceDeleteReservation" Before="InstallFinalize">Installed</Custom>
    <Custom Action="ListenerServiceAddReservation" Before="InstallFinalize">NOT Installed</Custom>
</InstallExecuteSequence>

Monday 6 June 2011

Failed to create receiver object from assembly

I ran into a problem re-installing a WSP package which I'd added a feature receiver to, using powershell; it looked to have worked, but when I looked in Central Administration and looked at the solutions, it had installed with the following error:

ServerX : Failed to create receiver object from assembly "XXXX.YYYY.WebParts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=abcd1234efgh5678", class "XXXX.YYYY.WebParts.Features.Feature1.Feature1EventReceiver" for feature "WebParts_Feature1" (ID: 57cf6cbd-72e9-43a2-bf85-bb947587073f).: System.ArgumentNullException: Value cannot be null.
Parameter name: type
   at System.Activator.CreateInstance(Type type, Boolean nonPublic)
   at Microsoft.SharePoint.Administration.SPFeatureDefinition.get_ReceiverObject()
ServerX : Failed to create receiver object from assembly "XXXX.YYYY.WebParts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=abcd1234efgh5678", class "XXXX.YYYY.WebParts.Features.Feature1.Feature1EventReceiver" for feature "WebParts_Feature1" (ID: 57cf6cbd-72e9-43a2-bf85-bb947587073f).: System.ArgumentNullException: Value cannot be null.
Parameter name: type
   at System.Activator.CreateInstance(Type type, Boolean nonPublic)
   at Microsoft.SharePoint.Administration.SPFeatureDefinition.get_ReceiverObject()


It turns out that the SharePoint Timer service which handles solution installation caches DLLs and it had cached the old DLL which had no feature receiver. A simple re-start on the service solved the problem.

Found solution here after much googling:

http://social.technet.microsoft.com/Forums/en/sharepoint2010setup/thread/55217486-0df5-43ca-9487-cdb7a66334c9

Thursday 2 June 2011

SharePoint Application Page With Ribbon Only Master Page

Overview
I recently created a site which contained a Silverlight (surprise surprise!) web part of fixed size as designed, however the users requested that it should be maximised with the SharePoint menus and headings removed to increase the footprint of the application. I didn't want to change the site definition master page, so decided to create a stripped down master page with only a ribbon and an application page with the SL app hard coded into the page to allow it to resize properly.

Master Page
I need to acknowledge Randy Drisgill for the base master pages. I stripped down the _starter_foundation.master page to look like the following (sorry about the lack of colour, it was making the browser rendering too slow):

<%@ Master language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "
http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="wssuc" TagName="Welcome" src="~/_controltemplates/Welcome.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="DesignModeConsole" src="~/_controltemplates/DesignModeConsole.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="MUISelector" src="~/_controltemplates/MUISelector.ascx" %>
<html xmlns="
http://www.w3.org/1999/xhtml" lang="<%$Resources:wss,language_value %>" dir="<%$Resources:wss,multipages_direction_dir_value %>" runat="server">
<head runat="server">

 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 <meta http-equiv="Expires" content="0"/>
 <meta http-equiv="X-UA-Compatible" content="IE=8"/>

 <!-- robots -->
 <SharePoint:RobotsMetaTag runat="server"/>

 <!-- page title - overridden by asp:content on pages or page layouts -->
 <title runat="server"><asp:ContentPlaceHolder id="PlaceHolderPageTitle" runat="server">SiteName</asp:ContentPlaceHolder></title>

 <!-- favicon -->
 <SharePoint:SPShortcutIcon runat="server" IconUrl="/Style Library/sitename/favicon.ico"/>

 <!-- all OOTB css -->
 <SharePoint:CssLink runat="server" Version="4"/>
 <SharePoint:Theme runat="server"/>

 <!-- page manager interacts with script and the sharepoint object model -->
 <SharePoint:SPPageManager runat="server"/>

 <!-- unified logging service -->
 <SharePoint:ULSClientConfig runat="server"/>

 <!-- identifies to scripting elements that this is a v4 master page. required for scrolling? -->
 <script type="text/javascript">
  var _fV4UI = true;
 </script>

 <!-- load SharePoint javascript -->
 <SharePoint:ScriptLink language="javascript" Defer="true" runat="server"/>

  <style type="text/css">
  html, body {
     height: 100%;
     overflow: none;
        }

        body {
         padding: 0;
         margin: 0;
        }
 </style>

 <!-- link to our custom css  -->
 <SharePoint:CssRegistration name="/Style Library/sitename/style.css" After="corev4.css" runat="server"/>

 <!-- javascript to override the active-x message in ie
  // See
http://blog.drisgill.com/2010/02/removing-name-activex-control-warning.html for more info
  // Remove if the IM pressence icons are needed in SharePoint
 -->
 <script type="text/javascript">
  function ProcessImn(){}
  function ProcessImnMarkers(){}
 </script>

 <!-- additional header delegate control -->
 <SharePoint:DelegateControl runat="server" ControlId="AdditionalPageHead" AllowMultipleControls="true"/>

 <!-- additional header placeholder - overridden by asp:content on pages or page layouts -->
 <asp:ContentPlaceHolder id="PlaceHolderAdditionalPageHead" runat="server"/> 

 <!-- microsoft says these should always be inside the head tag. -->
    <asp:ContentPlaceHolder id="PlaceHolderBodyAreaClass" runat ="server"/>
 <asp:ContentPlaceHolder id="PlaceHolderTitleAreaClass" runat ="server"/>
</head>

<body onload="javascript:_spBodyOnLoadWrapper();">
<form runat="server" onsubmit="return _spFormOnSubmitWrapper();" style="height:100%">
<!-- handles SharePoint scripts -->
<asp:ScriptManager id="ScriptManager" runat="server" EnablePageMethods="false" EnablePartialRendering="true" EnableScriptGlobalization="false" EnableScriptLocalization="true" />

<!-- controls the web parts and zones -->
<WebPartPages:SPWebPartManager runat="server"/>

<!-- =====  Begin Ribbon ============================================================ -->
<div id="s4-ribbonrow" class="s4-pr s4-ribbonrowhidetitle">
 <div id="s4-ribboncont">

  <!-- ribbon starts here -->
  <SharePoint:SPRibbon
   runat="server"
   PlaceholderElementId="RibbonContainer"
   CssFile="">

   <!-- ribbon left side content starts here -->
   <SharePoint:SPRibbonPeripheralContent
    runat="server"
    Location="TabRowLeft"
    CssClass="ms-siteactionscontainer s4-notdlg">

     <!-- site actions -->
     <span class="ms-siteactionsmenu" id="siteactiontd">
     <SharePoint:SiteActions runat="server" accesskey="<%$Resources:wss,tb_SiteActions_AK%>" id="SiteActionsMenuMain"
      PrefixHtml=""
      SuffixHtml=""
      MenuNotVisibleHtml="&amp;nbsp;"
      >
      <CustomTemplate>
      <SharePoint:FeatureMenuTemplate runat="server"
       FeatureScope="Site"
       Location="Microsoft.SharePoint.StandardMenu"
       GroupId="SiteActions"
       UseShortId="true"
       >
       <SharePoint:MenuItemTemplate runat="server" id="MenuItem_EditPage"
        Text="<%$Resources:wss,siteactions_editpage%>"
        Description="<%$Resources:wss,siteactions_editpagedescriptionv4%>"
        ImageUrl="/_layouts/images/ActionsEditPage.png"
        MenuGroupId="100"
        Sequence="110"
        ClientOnClickNavigateUrl="javascript:ChangeLayoutMode(false);"
        />
       <SharePoint:MenuItemTemplate runat="server" id="MenuItem_TakeOffline"
        Text="<%$Resources:wss,siteactions_takeoffline%>"
        Description="<%$Resources:wss,siteactions_takeofflinedescription%>"
        ImageUrl="/_layouts/images/connecttospworkspace32.png"
        MenuGroupId="100"
        Sequence="120"
        />
       <SharePoint:MenuItemTemplate runat="server" id="MenuItem_CreatePage"
        Text="<%$Resources:wss,siteactions_createpage%>"
        Description="<%$Resources:wss,siteactions_createpagedesc%>"
        ImageUrl="/_layouts/images/NewContentPageHH.png"
        MenuGroupId="200"
        Sequence="210"
        UseShortId="true"
        ClientOnClickScriptContainingPrefixedUrl="if (LaunchCreateHandler('Page')) { OpenCreateWebPageDialog('~site/_layouts/createwebpage.aspx') }"
        PermissionsString="AddListItems, EditListItems"
        PermissionMode="All" />
       <SharePoint:MenuItemTemplate runat="server" id="MenuItem_CreateDocLib"
        Text="<%$Resources:wss,siteactions_createdoclib%>"
        Description="<%$Resources:wss,siteactions_createdoclibdesc%>"
        ImageUrl="/_layouts/images/NewDocLibHH.png"
        MenuGroupId="200"
        Sequence="220"
        UseShortId="true"
        ClientOnClickScriptContainingPrefixedUrl="if (LaunchCreateHandler('DocLib')) { GoToPage('~site/_layouts/new.aspx?FeatureId={00bfea71-e717-4e80-aa17-d0c71b360101}&amp;ListTemplate=101') }"
        PermissionsString="ManageLists"
        PermissionMode="Any"
        VisibilityFeatureId="00BFEA71-E717-4E80-AA17-D0C71B360101" />
       <SharePoint:MenuItemTemplate runat="server" id="MenuItem_CreateSite"
        Text="<%$Resources:wss,siteactions_createsite%>"
        Description="<%$Resources:wss,siteactions_createsitedesc%>"
        ImageUrl="/_layouts/images/newweb32.png"
        MenuGroupId="200"
        Sequence="230"
        UseShortId="true"
        ClientOnClickScriptContainingPrefixedUrl="if (LaunchCreateHandler('Site')) { STSNavigate('~site/_layouts/newsbweb.aspx') }"
        PermissionsString="ManageSubwebs,ViewFormPages"
        PermissionMode="All" />
       <SharePoint:MenuItemTemplate runat="server" id="MenuItem_Create"
        Text="<%$Resources:wss,siteactions_create%>"
        Description="<%$Resources:wss,siteactions_createdesc%>"
        MenuGroupId="200"
        Sequence="240"
        UseShortId="true"
        ClientOnClickScriptContainingPrefixedUrl="if (LaunchCreateHandler('All')) { STSNavigate('~site/_layouts/create.aspx') }"
        PermissionsString="ManageLists, ManageSubwebs"
        PermissionMode="Any" />
       <SharePoint:MenuItemTemplate runat="server" id="MenuItem_ViewAllSiteContents"
        Text="<%$Resources:wss,quiklnch_allcontent%>"
        Description="<%$Resources:wss,siteactions_allcontentdescription%>"
        ImageUrl="/_layouts/images/allcontent32.png"
        MenuGroupId="300"
        Sequence="302"
        UseShortId="true"
        ClientOnClickNavigateUrl="~site/_layouts/viewlsts.aspx"
        PermissionsString="ViewFormPages"
        PermissionMode="Any" />
        <SharePoint:MenuItemTemplate runat="server" id="MenuItem_EditSite"
        Text="<%$Resources:wss,siteactions_editsite%>"
        Description="<%$Resources:wss,siteactions_editsitedescription%>"
        ImageUrl="/_layouts/images/SharePointDesigner32.png"
        MenuGroupId="300"
        Sequence="304"
        UseShortId="true"
        ClientOnClickScriptContainingPrefixedUrl="EditInSPD('~site/',true);"
        PermissionsString="AddAndCustomizePages"
        PermissionMode="Any"
       /> 
       <SharePoint:MenuItemTemplate runat="server" id="MenuItem_SitePermissions"
        Text="<%$Resources:wss,people_sitepermissions%>"
        Description="<%$Resources:wss,siteactions_sitepermissiondescriptionv4%>"
        ImageUrl="/_layouts/images/Permissions32.png"
        MenuGroupId="300"
        Sequence="310"
        UseShortId="true"
        ClientOnClickNavigateUrl="~site/_layouts/user.aspx"
        PermissionsString="EnumeratePermissions"
        PermissionMode="Any" />
       <SharePoint:MenuItemTemplate runat="server" id="MenuItem_Settings"
        Text="<%$Resources:wss,settings_pagetitle%>"
        Description="<%$Resources:wss,siteactions_sitesettingsdescriptionv4%>"
        ImageUrl="/_layouts/images/settingsIcon.png"
        MenuGroupId="300"
        Sequence="320"
        UseShortId="true"
        ClientOnClickNavigateUrl="~site/_layouts/settings.aspx"
        PermissionsString="EnumeratePermissions,ManageWeb,ManageSubwebs,AddAndCustomizePages,ApplyThemeAndBorder,ManageAlerts,ManageLists,ViewUsageData"
        PermissionMode="Any" />
       <SharePoint:MenuItemTemplate runat="server" id="MenuItem_CommitNewUI"
        Text="<%$Resources:wss,siteactions_commitnewui%>"
        Description="<%$Resources:wss,siteactions_commitnewuidescription%>"
        ImageUrl="/_layouts/images/visualupgradehh.png"
        MenuGroupId="300"
        Sequence="330"
        UseShortId="true"
        ClientOnClickScriptContainingPrefixedUrl="GoToPage('~site/_layouts/prjsetng.aspx')"
        PermissionsString="ManageWeb"
        PermissionMode="Any"
        ShowOnlyIfUIVersionConfigurationEnabled="true" />
      </SharePoint:FeatureMenuTemplate>
      </CustomTemplate>
       </SharePoint:SiteActions></span>

     <!-- global navigation dhtml popout menu -->
     <asp:ContentPlaceHolder id="PlaceHolderGlobalNavigation" runat="server">
     <SharePoint:PopoutMenu
      runat="server"
      ID="GlobalBreadCrumbNavPopout"
      IconUrl="/_layouts/images/fgimg.png"
      IconAlt="<%$Resources:wss,master_breadcrumbIconAlt%>"
      IconOffsetX=0
      IconOffsetY=112
      IconWidth=16
      IconHeight=16
      AnchorCss="s4-breadcrumb-anchor"
      AnchorOpenCss="s4-breadcrumb-anchor-open"
      MenuCss="s4-breadcrumb-menu">
      <div class="s4-breadcrumb-top">
       <asp:Label runat="server" CssClass="s4-breadcrumb-header" Text="<%$Resources:wss,master_breadcrumbHeader%>" />
      </div>
      <asp:ContentPlaceHolder id="PlaceHolderTitleBreadcrumb" runat="server">
       <SharePoint:ListSiteMapPath
        runat="server"
        SiteMapProviders="SPSiteMapProvider,SPContentMapProvider"
        RenderCurrentNodeAsLink="false"
        PathSeparator=""
        CssClass="s4-breadcrumb"
        NodeStyle-CssClass="s4-breadcrumbNode"
        CurrentNodeStyle-CssClass="s4-breadcrumbCurrentNode"
        RootNodeStyle-CssClass="s4-breadcrumbRootNode"
        NodeImageOffsetX=0
        NodeImageOffsetY=353
        NodeImageWidth=16
        NodeImageHeight=16
        NodeImageUrl="/_layouts/images/fgimg.png"
        RTLNodeImageOffsetX=0
        RTLNodeImageOffsetY=376
        RTLNodeImageWidth=16
        RTLNodeImageHeight=16
        RTLNodeImageUrl="/_layouts/images/fgimg.png"
        HideInteriorRootNodes="true"
        SkipLinkText="" />
      </asp:ContentPlaceHolder>
     </SharePoint:PopoutMenu>
    </asp:ContentPlaceHolder>

   <!-- save button at top of ribbon -->
   <SharePoint:PageStateActionButton id="PageStateActionButton" runat="server" Visible="false" /></SharePoint:SPRibbonPeripheralContent>

   <!-- ribbon right side content starts here -->
   <SharePoint:SPRibbonPeripheralContent
    runat="server"
    Location="TabRowRight"
    ID="RibbonTabRowRight"
    CssClass="s4-trc-container s4-notdlg">

    <!-- GlobalSiteLink0 delegate - the variation selector / shows nothing by default otherwise -->
    <SharePoint:DelegateControl runat="server" ID="GlobalDelegate0" ControlId="GlobalSiteLink0" />

    <!-- Welcome / Login control -->
    <div class="s4-trc-container-menu">
      <div>
      <wssuc:Welcome id="IdWelcome" runat="server" EnableViewState="false">
      </wssuc:Welcome>
      <!-- MultiLingual User Interface menu -->
      <wssuc:MUISelector ID="IdMuiSelector" runat="server"/>
     </div>
    </div>

    <!-- GlobalSiteLink2 delegate default shows nothing -->
    <SharePoint:DelegateControl ControlId="GlobalSiteLink2" ID="GlobalDelegate2" Scope="Farm" runat="server" />

    <!-- link to launch developer dashboard if its activated by admin -->
    <span>
     <span class="s4-devdashboard">
     <Sharepoint:DeveloperDashboardLauncher
      ID="DeveloperDashboardLauncher"
      NavigateUrl="javascript:ToggleDeveloperDashboard()"
      runat="server"
      ImageUrl="/_layouts/images/fgimg.png"
      Text="<%$Resources:wss,multipages_launchdevdashalt_text%>"
      OffsetX=0
      OffsetY=222
      Height=16
      Width=16 />
     </span>
    </span>
   </SharePoint:SPRibbonPeripheralContent>
   </SharePoint:SPRibbon>
  
   <!-- end main ribbon control -->
 </div>

    <!-- dynamic notification area -->
 <div id="notificationArea" class="s4-noti"></div>

    <!-- old navigation delegate? -->
 <asp:ContentPlaceHolder ID="SPNavigation" runat="server">
   <SharePoint:DelegateControl runat="server" ControlId="PublishingConsole" Id="PublishingConsoleDelegate">
     </SharePoint:DelegateControl>
 </asp:ContentPlaceHolder>

 <!-- top web part panel -->
 <div id="WebPartAdderUpdatePanelContainer">
  <asp:UpdatePanel
   ID="WebPartAdderUpdatePanel"
   UpdateMode="Conditional"
   ChildrenAsTriggers="false"
   runat="server">
   <ContentTemplate>
    <WebPartPages:WebPartAdder ID="WebPartAdder" runat="server" />
   </ContentTemplate>
   <Triggers>
    <asp:PostBackTrigger ControlID="WebPartAdder" />
   </Triggers>
  </asp:UpdatePanel>
 </div>
</div>

<!-- =====  End Ribbon and other Top Content ============================================================ -->
<asp:ContentPlaceHolder id="PlaceHolderMain" runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderFormDigest" runat="server">
 <SharePoint:FormDigest runat="server"/>
</asp:ContentPlaceHolder>

<input type="text" name="__spDummyText1" style="display:none;" size="1"/>
<input type="text" name="__spDummyText2" style="display:none;" size="1"/>

</form>
<SharePoint:WarnOnUnsupportedBrowsers runat="server"/>
</body>
</html>

I deployed the page to the masterpage gallery with the following module (elements.xml):

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="
http://schemas.microsoft.com/sharepoint/">
  <Module Name="ApplicationMaster" Url="_catalogs/masterpage">
    <File Path="ApplicationMaster\_ribbon_only.master" Url="_ribbon_only.master" IgnoreIfAlreadyExists="FALSE" Type="GhostableInLibrary" />
  </Module>
</Elements>

Application Page
The next step was to create an appication page, which is fairly straight forward, however there is a trick to get the new master page to load. The dynamic master page assignment is removed from the markup and then the new master page is assigned on page initialisation.
Markup:

<%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %>
<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TrendViewPage.aspx.cs" Inherits="XXXX.AppPage" DynamicMasterPageFile="~masterurl/default.master" %>

<asp:Content ID="Title" ContentPlaceHolderID="PlaceHolderPageTitle" runat="server" style="height:100%">AppPage</asp:Content>
<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server" style="height:100%">
</asp:Content>

C# Code Behind:

using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Web;
using System.Collections.Generic;

namespace XXXX
{
    public partial class AppPage : LayoutsPageBase
    {

        protected void Page_PreInit(object sender, EventArgs e)
        {
            // Override master page
            this.MasterPageFile = "../../_catalogs/masterpage/_ribbon_only.master";
        }

    }
}

The master page is assigned during initialisation because custom master pages can't be dynamically assined to app pages in markup. I originally deployed the page to the _layouts folder for testing and was able to use the DynamicMasterPageFile="~masterurl/default.master" attribute in the Page element and override it in the PreInit method, however once I moved it to a library so it could be scoped at site collection level, this did not work and produced the following error :

The dynamicmasterpagefile attribute on the page directive is not allowed in this page.

Removing this attribute (as the markup shows) works fine and simply allows the master to be assigned at runtime.
The app page is deployed to a document library using the following module (elements.xml):

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="
http://schemas.microsoft.com/sharepoint/">
  <Module Name="AppPage" Url="Lists/AppPageLib">
  <File Path="AppPage\AppPage.aspx" Url="AppPage.aspx"  Type="GhostableInLibrary" IgnoreIfAlreadyExists="False" />
</Module>
</Elements>

The page should look like this (obviously you would have some content in the app page!):