Introduction
I was recently assigned a Windows Phone 8 project and I wanted a way to make my application dynamically adjust anything I wanted based on things like orientation, screen size, or even be dependent on object properties. It's possible to handle this all in C# code in the specific window by handling events, but I felt a XAML solution would be a cleaner way to go. Two other possibilities for this could be a behavior or a library.
Background
Users should be familiar with XAML in either Silverlight or WPF.
On Android devices (my background), you create two separate layout files (the equivalent of the XAML in WP8). You place the same elements on the layout identified by a unique ID, and the Android operating system alternates between these two layouts for you. This wasn't quite the way Windows Phone 8 worked. Instead you have a single layout page. Inside this page, you can create multiple DataTemplate
objects and alternate between them on things like orientation change, etc. I wasn't happy with this solution as it was not very elegant and required a fair amount of C# code and event handlers to make it work.
Using the Code
First, I created a couple of IValueConverter
classes. Let's take a look at one that handles layout changes based on Orientation. First the class:
public abstract class OrientationToValueConverter<T> : IValueConverter {
public T PortraitValue { get; set; }
public T LandscapeValue { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if (value is PageOrientation) {
var orientation = (PageOrientation)value;
if ((orientation == PageOrientation.Landscape) ||
(orientation == PageOrientation.LandscapeLeft) ||
(orientation == PageOrientation.LandscapeRight)) {
return LandscapeValue;
}
else if ((orientation == PageOrientation.Portrait) ||
(orientation == PageOrientation.PortraitDown) ||
(orientation == PageOrientation.PortraitUp)) {
return PortraitValue;
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
The class contains two values of any type, PortraitValue
and LandscapeValue
. It simply takes the PageOrientaion
value that gets passed into it, and returns back the corresponding portrait and landscape values as needed. What's the point of this? Well, you can create single liner classes that base off of this as follows:
public class OrientationToVisibilityConverter : OrientationToValueConverter<Visibility> { }
public class OrientationToStackPanelOrientationConverter : OrientationToValueConverter<Orientation> { }
public class OrientationToGridLengthConverter : OrientationToValueConverter<GridLength> { }
public class OrientationToScrollBarVisibilityConverter : OrientationToValueConverter<ScrollBarVisibility> { }
public class OrientationToDoubleConverter : OrientationToValueConverter<double> { }
public class OrientationToDoubleNullableConverter : OrientationToValueConverter<double?> { }
Let's examine just one of these new derived classes to see how they can make life easy for us. We'll look at the OrientationToVisibilityConverter
class.
We define this in the resources property of our PhoneApplicationPage
, or underlying Grid
as follows:
<Grid.Resources>
<converters:OrientationToVisibilityConverter x:Key="PortraitVisible" LandscapeValue="Collapsed" PortraitValue="Visible" />
<converters:OrientationToVisibilityConverter x:Key="LandscapeVisible" LandscapeValue="Visible" PortraitValue="Collapsed" />
</Grid.Resources>
Remember to add the namespace as well. Notice that we have two separate instances of the same converter here. One makes items visible in portrait mode only, and one makes items visible in landscape mode only. Let's implement this.
<TextBlock Text="Portrait Only"
Visibility="{Binding Orientation, ElementName=MyPage, Converter={StaticResource PortraitVisible}}" />
<TextBlock Text="Landscape Only"
Visibility="{Binding Orientation, ElementName=MyPage, Converter={StaticResource LandscapeVisible}}" />
In this case, you'd need to give your page a name (e.g. MyPage
). You can also use RelativeSource
bindings, but that's simpler. The result? Your content has automatically changed based on PageOrientation
.
What are the other classes for? Here are a few examples of how I am using them.
OrientationToStackPanelOrientationConverter
- Allows a StackPanel
to orient horizontally on landscape and vertical on portrait.OrientationToGridLengthConverter
- Allows a grid row or column to be sized. For example, you may want it auto sized in one case and star sized in another.OrientationToScrollBarVisibilityConverter
- Say you want all content to fit horizontally in landscape mode. You can disable the horizontal scroll bar in landscape and set it to auto in portrait.OrientationToDoubleConverter
- Can be used to set things like Width
, Height
, MaxHeight
, etc.OrientationToDoubleNullableConverter
- I use this specifically to set MaxHeight
or MaxWidth
. In this case, the x:Null
value means that there is no MaxHeight
while a numeric value will limit it.
Let's take a look at one more class:
public abstract class BoolToValueConverter<T> : IValueConverter { }
This class again defines two values of type T
called TrueValue
and FalseValue
. It does the same type of conversion except that it checks the incoming value for bool
, string
, and finally just a null
/not null
check:
if (value is bool) {
return (bool)value;
}
else if (value is string) {
return !string.IsNullOrWhiteSpace(value.ToString());
}
else {
return = (value != null);
}
Again like before, I created a few classes that expand on this. Here are two of them:
public class BoolToVisibilityConverter : BoolToValueConverter<Visibility> { }
public class BoolToDoubleConverter : BoolToValueConverter<double> { }
BoolToVisibilityConverter
should be pretty obvious. But in WP8, the Visibility.Hidden
type was removed. So you can only have items be visible or collapsed. What if you want to hide an item based on a condition but reserve its space on the screen? Simple. Just create a BoolToDoubleConverter
and bind it to the opacity! The item can get hidden based on bindings now. This class isn't typically bound to orientation for me but is usually just bound to a DataContext
or object property (e.g. If my screen has a DataContext
loaded, the 'Save' button can be visible).
No need to limit yourself to these two base classes; these are simply the two I chose for the article.
Points of Interest
XAML is an extremely powerful tool and I am glad to see the subset of it in Windows Phone 8 is as large as it is. There are missing features for those who are familiar with WPF, but nearly all of them can be worked around quite easily. The performance of these bindings and of the higher level languages such as C# and XAML perform very well on a Windows Phone 8 device. I was pleasantly surprised by it all. All in all, Windows Phone 8 is very fun to work with; the only downside being that you must install Windows 8 on your own machine to do so =(.
History