I've had a few questions about using the LoopingSelector described in "Using LoopingSelector from the Silverlight Toolkit", and I'd like to go over them.
How do I use this in MVVM?
By default the LoopingSelector doesn't expose a SelectedItem to bind to, instead the ILoopingSelectorDataSource does. The approach I ended up using was to expose my custom Data Source off the View Model and bind that to the DataSource of the LoopingSelector.
This allows the ViewModel to manipulate the data of the data source as well as work with the SelectedItem. Initially I was hesitant about the approach but thinking about it some more I prefer it. It separates the data from the user interface using the View / View Model split and exposes the data in the most appropriate manner. The view and view model would like something like:
public class PageViewModel : ViewModelBase
{
private ILoopingSelectorDataSource names;
public PageViewModel()
{
Names = new LoopingListDataSource<string>(new[] { "Larry", "Curly", "Moe"});
}
public ILoopingSelectorDataSource Names
{
get
{
return names;
}
set
{
names = value;
OnPropertyChanged("Names");
}
}
}
<controls:LoopingSelector DataSource="{Binding Names}" ItemSize="48, 48" Width="48"/>
How do I use text values instead of numbers?
The beauty of the ILoopingSelectorDataSource is that it's completely data type agnostic, your data source can be anything. I simply used numbers since it was easy to demo, the real key is in the GetNext and GetPrevious methods, this is what determines the display order of the list, if they return strings then you're pretty much working with text values. Here's an example of a list based data source.
public class LoopingListDataSource<T> : ILoopingSelectorDataSource
{
private T selectedItem;
private readonly IList<T> items;
public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
public LoopingListDataSource(IEnumerable<T> items)
{
this.items = items.ToList();
selectedItem = this.items.First();
}
protected virtual void OnSelectionChanged(SelectionChangedEventArgs e)
{
var selectionChanged = SelectionChanged;
if(selectionChanged != null)
selectionChanged(this, e);
}
public T SelectedItem
{
get
{
return selectedItem;
}
set
{
var oldValue = selectedItem;
var newValue = value;
if(oldValue.Equals(newValue))
return;
selectedItem = newValue;
OnSelectionChanged(new SelectionChangedEventArgs(new[] { oldValue }, new[] { newValue }));
}
}
public T GetNext(T relativeTo)
{
var index = items.IndexOf(relativeTo) + 1;
return index >= items.Count ? items[0] : items[index];
}
public T GetPrevious(T relativeTo)
{
var index = items.IndexOf(relativeTo) - 1;
return index < 0 ? items[items.Count - 1] : items[index];
}
object ILoopingSelectorDataSource.GetNext(object relativeTo)
{
return GetNext((T)relativeTo);
}
object ILoopingSelectorDataSource.GetPrevious(object relativeTo)
{
return GetPrevious((T)relativeTo);
}
object ILoopingSelectorDataSource.SelectedItem
{
get
{
return selectedItem;
}
set
{
SelectedItem = (T)value;
}
}
}
The Silverlight Toolkit for Windows Phone 7 contains a lot of really useful controls, including the DateSelector with it's very cool infinite list selector. What would be nice to be to have that sort of list for other types of data. Thankfully the team at Microsoft has made this possible.
Edit: I've uploaded some sample code at Silverlight Toolkit LoopingSelector Demo.
The DateSelector control builds off a simpler control named LoopingSelector which contains all the functionality for the looping control, we just need to provide the description of the available data. We do this through the interface ILoopingSelectorDataSource, for our simple example we'll use a numerical data source with a minimum and maximum.
The interface is pretty self explanatory, GetPrevious and GetNext methods allow you to define the order of the data. We use the SelectedItem property to hold a reference to currently selected item and use the SelectionChanged event to notify the control when the SelectedItem changes (we change it from within code for instance).
Here's our implementation of NumericDataSource.
public class NumbersDataSource : ILoopingSelectorDataSource
{
private int minimum = 1;
private int maximum = 100;
private int selectedItem = 1;
public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
protected virtual void OnSelectedChanged(SelectionChangedEventArgs e)
{
var selectionChanged = SelectionChanged;
if(selectionChanged != null)
selectionChanged(this, e);
}
public object GetNext(object relativeTo)
{
var nextValue = ((int)relativeTo) + 1;
return nextValue <= Maximum ? nextValue : Minimum;
}
public object GetPrevious(object relativeTo)
{
var previousValue = ((int)relativeTo) - 1;
return previousValue >= Minimum ? previousValue : Maximum;
}
public object SelectedItem
{
get
{
return selectedItem;
}
set
{
var oldValue = selectedItem;
var newValue = (int)value;
if(oldValue == newValue)
return;
selectedItem = newValue;
OnSelectedChanged(new SelectionChangedEventArgs(new[] { oldValue}, new[] { newValue }));
}
}
public int Minimum
{
get
{
return minimum;
}
set
{
minimum = value;
if(selectedItem < minimum)
SelectedItem = value;
}
}
public int Maximum
{
get
{
return maximum;
}
set
{
maximum = value;
if(selectedItem > maximum)
SelectedItem = value;
}
}
}
Now that we have our data source it's time to add a few controls to our page for this example we'll add three to mimic the style of the DateSelector. From my experimentaion I've found that a number of properties need to be set on the LoopingSelector for it to work correctly, specifically ItemSize and Width, both are fairly obvious but nessecary. For each Selector we'll create a seperate instance of our data source with some different parameters. We'll also create a DataTemplate resource and share that amongst the selectors.
The resulting xaml is as follows.
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.Resources>
<DataTemplate x:Key="NumberTemplate">
<Grid>
<TextBlock Text="{Binding}" FontSize="54" FontFamily="{StaticResource PhoneFontFamilySemiBold}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<controls:LoopingSelector Margin="12" Width="128" ItemSize="128,128" ItemTemplate="{StaticResource NumberTemplate}">
<controls:LoopingSelector.DataSource>
<local:NumbersDataSource />
</controls:LoopingSelector.DataSource>
</controls:LoopingSelector>
<controls:LoopingSelector Grid.Column="1" Margin="12" Width="128" ItemSize="128,128" ItemTemplate="{StaticResource NumberTemplate}">
<controls:LoopingSelector.DataSource>
<local:NumbersDataSource Minimum="10" Maximum="20" />
</controls:LoopingSelector.DataSource>
</controls:LoopingSelector>
<controls:LoopingSelector Grid.Column="2" Margin="12" Width="128" ItemSize="128,128" ItemTemplate="{StaticResource NumberTemplate}">
<controls:LoopingSelector.DataSource>
<local:NumbersDataSource Maximum="0" Minimum="-100" />
</controls:LoopingSelector.DataSource>
</controls:LoopingSelector>
</Grid>
Edit: I've uploaded some sample code at Silverlight Toolkit LoopingSelector Demo.
Currently the fastest way to display a string of html on the Windows Phone 7 is to use the Web Browser control, simply call the NavigateToString method passing the html you want to display.
When trying to use this in an MVVM pattern the method call becomes slightly problematic, we'd much rather have a bindable property. Thankfully we can use attached properties to achieve this.
public static class WebBrowserHelper
{
public static readonly DependencyProperty HtmlProperty = DependencyProperty.RegisterAttached(
"Html", typeof(string), typeof(WebBrowserHelper), new PropertyMetadata(OnHtmlChanged));
public static string GetHtml(DependencyObject dependencyObject)
{
return (string)dependencyObject.GetValue(HtmlProperty);
}
public static void SetHtml(DependencyObject dependencyObject, string value)
{
dependencyObject.SetValue(HtmlProperty, value);
}
private static void OnHtmlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var browser = d as WebBrowser;
if(browser == null)
return;
var html = e.NewValue.ToString();
browser.NavigateToString(html);
}
}
Once you have the attached property the binding becomes very simple.
<phone:WebBrowser cxi:WebBrowserHelper.Html="{Binding Question.Body}" />
If you really want to add some polish to your app I suggest looking at fellow kiwi Ben Gracewood's solution to having your html respect the current phone theme at Integrated Links and Styling for Windows Phone 7 Web Browser Control.
I've just uploaded the next three tutorials in the Windows Phone 7 Tutorials series. I ended up doing a batch of three this time due the simplistic nature of any tutorials involving features the emulator doesn't support well such as GPS and the Accelerometer.
Quake covers something very close to New Zealand at the moment, earthquakes. In the tutorial we cover retrieving earthquake data, parising it using Linq to Xml and placing quake markers on the new Bing Maps control.
Where am I? shows how you can use Reactive Extensions to emulate the GeoCoordinateWatcher class in the phone emulator.
Hold it Level again uses Reactive Extensions, this time to emulate the Accelerometer, we also use the VibrateController to provide tactile feedback to the user.