Visually Located

XAML and GIS

Keeping ads in the same location when the phone orientation changes to landscape

There has been a lot of information flying around about ads in apps these days. Microsoft recently updated PubCenter reporting to include fill rates and number of requests for ads. Dvlup recently partnered with AdDuplex to in its reward program. With all of this hype, I thought I would talk about a common problem with placing ads in apps. That issue is keeping ads in the same location when rotating the phone. Most apps are Portrait apps and display ads either at the top or bottom of the app.

image

Displaying ads like this can be done with the following xaml

<Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <StackPanel Grid.Row="0" Margin="12,17,0,28">
        <TextBlock Text="STATIONARY AD" Style="{StaticResource PhoneTextNormalStyle}"/>
        <TextBlock Text="sample" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
    </StackPanel>
    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <!-- Other content here -->
    </Grid>
    <adDuplex:AdControl Grid.Row="2"/>
</Grid>

This style works really well for most apps, but what happens when you are displaying data that goes off of the screen? You could either say “Oh well”, you could wrap the text, or you could allow the user to rotate the phone so they can see more. Allowing the content of the page to display in Landscape is as simple as setting SupportedOrientations of the page to PortraitOrLandscape. Doing so allows the user to see more content, but has a side effect of the ad control taking up a lot of room at the bottom of the page.

image

When supporting Landscape, you really want the ads to stay stationary.

image

To solve this we’ll create a new Panel like control that will replace the “LayoutRoot” Grid of the standard page. I recently discovered that in two years of blogging I have yet to talk about creating a custom control. We’ll solve this problem by doing just that! A custom control is a control that is not derived from UserControl. Custom controls have a lot of benefits. My top two benefits are reusability and templating (ability to restyle the control). For this we’ll need to create a new ContentControl. A ContentControl allows for a single child to be placed within the xaml element or set from the Content property.

We’ll start by creating two files, one for code and one for xaml. Create a folder named StationaryAdPanel. In the folder add a new code file names StationaryAdPanel.cs and one code file named StationaryAdPanel.theme.xaml.

image

In the cs file, start defining the control.

public class StationaryAdPanel : ContentControl
{
    public StationaryAdPanel()
    {
        DefaultStyleKey = typeof(StationaryAdPanel);
    }
}

The constructor defines a DefaultStyleKey. This is the style that defines how the control looks. The style for the control needs to have a simple grid that will allow the ads to be moved to the sides that it needs to be on. Within the file add the following xaml that will define the layout of our control.

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Visual.Controls"
    xmlns:advertising="clr-namespace:Microsoft.Advertising.Mobile.UI;assembly=Microsoft.Advertising.Mobile.UI"
    xmlns:adDuplex="clr-namespace:AdDuplex;assembly=AdDuplex.WindowsPhone">
 
    <Style TargetType="local:StationaryAdPanel">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:StationaryAdPanel">
                    <Grid x:Name="PART_Root">
                        <Grid.RowDefinitions>
                            <!-- main content area -->
                            <RowDefinition Height="*"/>
                            <!-- ad area -->
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <!-- location for ads when rotated LandscapeRight -->
                            <ColumnDefinition Width="Auto"/>
                            <!-- Main content area -->
                            <ColumnDefinition Width="*"/>
                            <!-- location for ads when rotated LandscapeLeft -->
                            <ColumnDefinition Width="Auto"/>
                        </Grid.ColumnDefinitions>
                        
                        <!-- main content of the page -->
                        <ContentPresenter  Grid.Column="1" Content="{TemplateBinding Content}"/>
                        
                        <!-- simple ad rotator, defaulted to bottom of page -->
                        <Grid x:Name="PART_StationaryPanel" Grid.Row="1" Grid.Column="1" 
                              RenderTransformOrigin=".5,.5" Width="480" Height="80">
                            <adDuplex:AdControl x:Name="AdDuplexControl" Visibility="Collapsed"/>
                            <advertising:AdControl x:Name="PubCenterControl" 
                                                   AdUnitId="Image480_80" ApplicationId="test_client"
                                                   Height="80" Width="480" 
                                                   IsAutoCollapseEnabled="True"/>
                        </Grid>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Next we’ll define the code for the control. The first step is to override the OnApplyTemplate. This is the method that we use to subscribe to any control events, or just get a control to use later. To keep the ads stationary, we’ll need to listen to when the Orientation of the page changes.

private PhoneApplicationPage _page;
private FrameworkElement _stationaryPanel;
private Grid _rootGrid;
private UIElement _adDuplexControl;
 
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
 
    _rootGrid = GetTemplateChild("PART_Root") as Grid ?? new Grid();
    _stationaryPanel = GetTemplateChild("PART_StationaryPanel") as FrameworkElement ?? new ContentPresenter();
    _stationaryPanel.RenderTransform = new RotateTransform();
    _adDuplexControl = (UIElement)GetTemplateChild("AdDuplexControl");
 
    var adControl = (AdControl)GetTemplateChild("PubCenterControl");
 
    // simple "ad rotator" methods to set visibility of ad controls
    adControl.ErrorOccurred += OnPubCenterErrorOccurred;
    adControl.AdRefreshed += OnPubCenterAdRefreshed;
 
    if (DesignerProperties.GetIsInDesignMode(this) == false)
    {
        // We will need to get the page we are in and listen for the
        // OrientationChanged event
        var frame = (Frame)Application.Current.RootVisual;
        _page = ((PhoneApplicationPage)frame.Content);
        if (_page.SupportedOrientations == SupportedPageOrientation.PortraitOrLandscape)
        {
            _page.OrientationChanged += OnOrientationChanged;
            OnOrientationChanged(_page, new OrientationChangedEventArgs(_page.Orientation));
        }
    }
}

The difficult part about keeping the ad stationary when in landscape is that we will need to rotate it. The downside to rotation is that other controls around the rotated control to not adjust for the rotation. This is solved with controls like the LayoutTransformer from Telerik or by porting the LayoutTransformer from the Silverlight toolkit. However, using these controls requires a dependency on a third party library that we may not want. To overcome this we will ne to adjust for the rotation ourselves. We’ll handle this within the OnOrientationChanged event handler.

private void OnOrientationChanged(object sender, OrientationChangedEventArgs args)
{
    // margin for shifting ad panel when in landscape mode
    // the margin is equal to (width - height)/2 = (480 - 80)/2 = 200
    const int margin = 200;
 
    // initial value of the margin of the page. If the SystemTray has an opacity
    // we need to shift the page content over
    Thickness pageMargin = _page.Margin;
 
    // margin use when rotating the ad
    Thickness rotateMargin = new Thickness(0, 0, 0, 0);
    
    // angle to rotate the ad (if in landscape)
    double angle = 0;
 
    int rotateRow = 0;
    int rotateRowspan = 0;
    int rotateColumn = 0;
 
    switch (args.Orientation)
    {
        case PageOrientation.None:
            break;
        case PageOrientation.Portrait:
        case PageOrientation.PortraitUp:
        case PageOrientation.PortraitDown:
            rotateRow = 2;
            rotateColumn = 1;
            rotateRowspan = 1;
            if ((SystemTray.Opacity > 0) && (SystemTray.Opacity < 1))
            {
                pageMargin = new Thickness(0, 32, 0, 0);
            }
            break;
        case PageOrientation.Landscape:
        case PageOrientation.LandscapeRight:
            rotateRowspan = _rootGrid.RowDefinitions.Count;
            rotateMargin = new Thickness(-margin, 0, -margin, 0);
            angle = 90;
            if ((SystemTray.Opacity > 0) && (SystemTray.Opacity < 1))
            {
                pageMargin = new Thickness(0, 0, 72, 0);
            }
            break;
        case PageOrientation.LandscapeLeft:
            rotateRowspan = _rootGrid.RowDefinitions.Count;
            rotateColumn = 2;
            rotateMargin = new Thickness(-margin, 0, -margin, 0);
            angle = -90;
            if ((SystemTray.Opacity > 0) && (SystemTray.Opacity < 1))
            {
                pageMargin = new Thickness(72, 0, 0, 0);
            }
            break;
        default:
            throw new ArgumentOutOfRangeException();
    }
    _page.Margin = pageMargin;
 
    // now set location and rotation of ad panel
    _stationaryPanel.Margin = rotateMargin;
    ((RotateTransform)_stationaryPanel.RenderTransform).Angle = angle;
    Grid.SetRow(_stationaryPanel, rotateRow);
    Grid.SetColumn(_stationaryPanel, rotateColumn);
    Grid.SetRowSpan(_stationaryPanel, rotateRowspan);
}

We’ll wrap up the control by adding the methods to rotate the ads as needed.

private void OnPubCenterErrorOccurred(object sender, AdErrorEventArgs e)
{
    var pubCenterAd = ((UIElement)sender);
    if (pubCenterAd.Visibility == Visibility.Visible)
    {
        pubCenterAd.Visibility = Visibility.Collapsed;
        _adDuplexControl.Visibility = Visibility.Visible;
    }
}
 
private void OnPubCenterAdRefreshed(object sender, EventArgs eventArgs)
{
    var pubCenterAd = ((UIElement)sender);
    if (pubCenterAd.Visibility == Visibility.Collapsed)
    {
        pubCenterAd.Visibility = Visibility.Visible;
        _adDuplexControl.Visibility = Visibility.Collapsed;
    }
}

When writing custom controls, you need to define a “Generic.xaml” file located in a Themes folder in the root of the project. This is a requirement for custom controls. Nice file can contain the xaml style directly (the template we defined above) or it can reference the xaml files used to define the templates. I prefer to put the styles and templates in separate files rather than flood the Generic.xaml file. To start, create a folder named “Themes” in the root of the project. Right click the folder and add a new code file named “Generic.xaml”. In the file paste the following code (changing YOUR_PROJECT_NAME with the name of the project).

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/YOUR_PROJECT_NAME;component/Controls/StationaryAdPanel/StationaryAdPanel.Theme.xaml" />
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

We are now ready to use the control in our pages. The control is really simple to use. Take the LayoutRoot grid that you normally have, and wrap it with the new StationaryAdPanel.

<local:StationaryAdPanel>
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <!-- regular content -->
    </Grid>
</local:StationaryAdPanel>

With the root grid wrapped, we can rotate our apps to see more text.

image                     image

You can download a complete working sample from here.

Creating a Behavior to change text to any case

In a previous post we created a behavior that would capitalize any text. This Behavior served a great purpose allowing you to capitalize the text for any TextBlock. What happens when you need to lower case the text? You could create a second Behavior for this, or we could modify the Behavior so that it allows for multiple options. The first option just seems silly when we can reuse code, but how can we specify which text case we want to apply?

In the previous post I talked about some reasons why I like behaviors. One reason I did not list is that you can add Dependency Properties to them. Dependency Properties allow us to set properties in xaml or even better bind to other properties. You can add Dependency Properties to value converters, but that requires your value converter to inherit from DependencyObject. A Behavior already is a DependencyObject!

We’ll start with the ToUpperBehavior from before. We’ll first change the name to ChangeCaseBehavior and we’ll create an enum that will specify what case style we want to change to

public class ChangeCaseBehavior : Behavior<TextBlock>
{
    ...
}
 
public enum TextCase
{
    Upper,
    Lower,
    Title
}

This example will show three styles of text casing. I’ll leave it up to you to add others like camelCase or PascalCase. In our previous Behavior, we called ToUpper on the Text of the TextBlock, now we’ll want to respond to the different possibilities for TextCase. We can accomplish this with a simple switch statement within the UpdateText method

private void UpdateText()
{
    if (AssociatedObject == null) return;
    if (string.IsNullOrEmpty(AssociatedObject.Text)) return;
 
    switch (TextCase)
    {
        case TextCase.Upper:
            AssociatedObject.Text = AssociatedObject.Text.ToUpper();
            break;
        case TextCase.Lower:
            AssociatedObject.Text = AssociatedObject.Text.ToLower();
            break;
        case TextCase.Title:
            char[] text = AssociatedObject.Text.ToCharArray();
            
            // always upper case the first letter
            bool shouldUpper = true;
            for (int i = 0; i < text.Length; i++)
            {
                text[i] = shouldUpper 
                    ? Char.ToUpper(text[i]) 
                    : Char.ToLower(text[i]);
 
                // next letter should be upper case if this is a space
                shouldUpper = text[i] == ' ';
            }
            AssociatedObject.Text = new string(text);
            break;
        default:
            throw new ArgumentOutOfRangeException();
    }
    AssociatedObject.LayoutUpdated -= AssociatedObjectOnLayoutUpdated;
}

Next,  we’ll want the ability to set which text casing we want. To do this we’ll add a dependency property to the behavior and respond to the changing of the value.

public TextCase TextCase
{
    get { return (TextCase)GetValue(TextCaseProperty); }
    set { SetValue(TextCaseProperty, value); }
}
 
// Using a DependencyProperty as the backing store for TextCase.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextCaseProperty = DependencyProperty.Register(
    "TextCase",
    typeof(TextCase),
    typeof(ChangeCaseBehavior),
    new PropertyMetadata(TextCase.Upper, OnTextCaseChanged));
 
private static void OnTextCaseChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
    var behavior = (ChangeCaseBehavior)o;
    behavior.UpdateText();
}

Last time we setup the Behavior through Blend, this time we’ll modify the xaml to specify which case we want.

<TextBlock x:Name="TitleText" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0" 
           Text="{Binding Name}">
    <i:Interaction.Behaviors>
        <Behaviors:ChangeCaseBehavior TextCase="Title"/>
    </i:Interaction.Behaviors>
</TextBlock>

This ensures that our text will be title cased

image

Responding to the Windows Phone Toolkit ListPicker closing

I’ve often wondered why the ListPicker in the Windows Phone Toolkit does not have an Opened or Closed event, especially since the SelectionChanged event fires only when the selection changes (as it should). So how are you suppose to know when the ListPicker opens or closes. After all, the ComboBox control has DropDownOpened and DropDownClosed. But then I thought, “Do you really need those? You can always use the IsDropDownOpened property.” The ListPicker does not have an Opened or Closed event and it does not have an IsDropDownOpened property. What the ListPicker does have, is the ListPickerMode property.

//
// Summary:
//     Gets or sets the ListPickerMode (ex: Normal/Expanded/Full).
public ListPickerMode ListPickerMode { get; }

This property is actually a DependencyProperty that we can bind to! Let’s say you need to change the visibility of another control when the ListPicker opens/closes. Or maybe you need to change some text based on the picker being opened. We can use the Expression SDK to change values within xaml. For this example we’ll look at changing text based on the picker being opened. We have the following xaml defined.

<StackPanel>
    <TextBlock Text="Closed" Style="{StaticResource PhoneTextLargeStyle}"/>
    <toolkit:ListPicker x:Name="Picker">
        <sys:String>Option one</sys:String>
        <sys:String>Option two</sys:String>
        <sys:String>Option three</sys:String>
        <sys:String>Option four</sys:String>
    </toolkit:ListPicker>
</StackPanel>

With the above xaml, we will always show “Closed”. Let’s start using the Expression SDK. The first step is to add the Microsoft.Expression.Interactions and System.Windows.Interactivity references to your project.

image

Next we need to add a DataTrigger to the TextBlock. The DataTrigger will respond to the ListPickerMode property changing and then change the text of the TextBlock.

<TextBlock  Text="Closed" Style="{StaticResource PhoneTextLargeStyle}">
    <i:Interaction.Triggers>
        <e:DataTrigger Binding="{Binding ListPickerMode, ElementName=Picker}" Value="Expanded">
            <e:ChangePropertyAction PropertyName="Text" Value="Open" TargetName="PickerState"/>
        </e:DataTrigger>
        <e:DataTrigger Binding="{Binding ListPickerMode, ElementName=Picker}" Value="Normal">
            <e:ChangePropertyAction PropertyName="Text" Value="Closed" TargetName="PickerState"/>
        </e:DataTrigger>
    </i:Interaction.Triggers>
</TextBlock>

Maybe you want to use a ValueConverter and show/hide the TextBlock based on the picker showing. First we’ll need to create a new ValueConverter.

public class PickerModeToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null) return Visibility.Collapsed;
 
        var mode = (ListPickerMode) value;
 
        return mode == ListPickerMode.Expanded ? Visibility.Visible : Visibility.Collapsed;
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Next we’ll add the converter to the resources and use it to show the TextBlock.

<phone:PhoneApplicationPage.Resources>
    <local:PickerModeToVisibilityConverter x:Key="PickerModeConverter"/>
</phone:PhoneApplicationPage.Resources>
 
...
 
<TextBlock Text="Open" Style="{StaticResource PhoneTextLargeStyle}" 
           Visibility="{Binding ListPickerMode, ElementName=Picker, Converter={StaticResource PickerModeConverter}}" />

These were a couple of simple examples. You can do more by using the other built-in behaviors of the Expression SDK.