Still looking for a sponsor Max Paulousky is looking for a Silverlight/.Net job in the Commonwealth

Share to Facebook Tweet this! Share to MySpace Share to Google Share to Live   Share via AddThis

Windows Phone Looping Selector for Digits

Abstracts

Windows Phone controls toolkit has a great control for displaying date/time (DatePicker and TimePicker). Fortunately, it is quite easy to build based on them a control that allows selecting digits. For example, I am working on a solution to allow users select metric parameters – height, width, cost etc. As a result, I implemented own control based on LoopingSelector and ILoopingSelectorDataSource.

Implementation

DatePicker and TimePicker controls are a set of primitive controls – box for displaying data, additional PhonePage and looping selectors themselves. If you activate a Picker, you will see the next screen:

Choose date screen for DatePicker control

This screen contains three looping selectors – for month, day and year. The control that is used for these elements is LoopingSelector. It is not sealed and allows inheritance.

It is needed to define several additional fields for LoopingSelector control to allow developers set max/min bound, step, default value etc.

Here is the list of dependency properties I define in the control:

 
        /// <summary>
        /// The MaxValue DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty MaxValueProperty =
                DependencyProperty.Register("MaxValue", typeof(int), typeof(DigitLoopingSelector), new PropertyMetadata(100, ValueChanged));
 
        /// <summary>
        /// The MinValue DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty MinValueProperty =
                DependencyProperty.Register("MinValue", typeof(int), typeof(DigitLoopingSelector), new PropertyMetadata(0, ValueChanged));
 
        /// <summary>
        /// The Step DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty StepProperty =
                DependencyProperty.Register("Step", typeof(int), typeof(DigitLoopingSelector), new PropertyMetadata(1, ValueChanged));
 
        /// <summary>
        /// The Step DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty SelectedItemProperty =
                DependencyProperty.Register("SelectedItem", typeof(int), typeof(DigitLoopingSelector), new PropertyMetadata(0, ValueChanged));
 
        /// <summary>
        /// The Step DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty DefaultValueProperty =
                DependencyProperty.Register("DefaultValue", typeof(int), typeof(DigitLoopingSelector), new PropertyMetadata(0, ValueChanged));
 
        /// <summary>
        /// The Step DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty StringFormatProperty =
                DependencyProperty.Register("StringFormat", typeof(string), typeof(DigitLoopingSelector), new PropertyMetadata(string.Empty, ValueChanged));
 
        private static void ValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            DigitLoopingSelector picker = (DigitLoopingSelector)obj;
            picker.DataSource = new DigitDataSource(picker.MinValue, picker.MaxValue, picker.Step, picker.DefaultValue, picker.StringFormat);
        }

Additionally, I define StringFormat and SelectedItem properties. First one allows defining format for a displaying digits. For example, If I display two selectors for selecting meters/centimeters (feet/inches etc) I would prefer to add leading zeros for centimeters/inches when they are lower then ten. The second property allow quickly selecting a new current value for the control.

But that control cannot work without datasource. it provide data that will be displayed, apply formatting etc. I am going to implement ILoopingSelectorDataSource interface. It contains three methods and one event:

/// <summary>
/// Defines how the LoopingSelector communicates with data source.
/// </summary>
public interface ILoopingSelectorDataSource
{
    /// <summary>
    /// Get the next datum, relative to an existing datum.
    /// </summary>
    /// <param name="relativeTo">The datum the return value will be relative to.</param>
    /// <returns>The next datum.</returns>
    object GetNext(object relativeTo);
 
    /// <summary>
    /// Get the previous datum, relative to an existing datum.
    /// </summary>
    /// <param name="relativeTo">The datum the return value will be relative to.</param>
    /// <returns>The previous datum.</returns>
    object GetPrevious(object relativeTo);
 
    /// <summary>
    /// The selected item. Should never be null.
    /// </summary>
    object SelectedItem { get; set; }
 
    /// <summary>
    /// Raised when the selection changes.
    /// </summary>
    event EventHandler<SelectionChangedEventArgs> SelectionChanged;
}

GetNext and GetPrevious methods should return values that are after and before the current value accordingly. SelectedItem allows getting and setting a current value.SelectionChanged event should be fired when SelectedItem is changed.

Here is my implementation:

public class DigitDataSource : ILoopingSelectorDataSource
{
    public DigitDataSource(int minValue, int maxValue, int step, int defaultValue, string stringFormat)
    {
        MinValue = minValue;
        MaxValue = maxValue;
        Step = step;
        SelectedItem = defaultValue;
        StringFormat = stringFormat;
    }
 
    public int MinValue
    {
        get;
        set;
    }
 
    public int MaxValue
    {
        get;
        set;
    }
 
    public int Step
    {
        get;
        set;
    }
 
    private string ApplyFormat(int digit)
    {
        return digit.ToString(StringFormat);
    }
 
    public string StringFormat
    {
        get;
        private set;
    }
 
    #region ILoopingSelectorDataSource Members
 
    public object GetNext(object relativeTo)
    {
        return Convert.ToInt32(relativeTo) + Step > MaxValue ? ApplyFormat(MinValue) : ApplyFormat(Convert.ToInt32(relativeTo) + Step);
    }
 
    public object GetPrevious(object relativeTo)
    {
        return Convert.ToInt32(relativeTo) - Step < MinValue ? ApplyFormat(MaxValue) : ApplyFormat(Convert.ToInt32(relativeTo) - Step);
    }
 
    public int selectedItem;
    public object SelectedItem
    {
        get
        {
            return ApplyFormat(selectedItem);
        }
        set
        {
            int newValue = Convert.ToInt32(value);
            if (selectedItem != newValue)
            {
                    int previousSelectedItem = selectedItem;
                    selectedItem = newValue;
                    EventHandler<SelectionChangedEventArgs> handler = SelectionChanged;
                    if (handler != null)
                        handler(this, new SelectionChangedEventArgs(new object[] { ApplyFormat(previousSelectedItem) }, new object[] { ApplyFormat(selectedItem) }));
            }
        }
    }
 
    public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
 
    #endregion
}

GetNext and GetPrevious methods returns data in any case. For example, if the next value is more than maximum, then the method should return minimal value and vice versa. Both methods return formatted string.

Of course, if you need more complex algorithm for displaying digits, you can implement your own ILoopingSelectorDataSource and bind to a looping control.

You can use following code to allows users selecting width of some construction:

<Grid x:Name="WidthPanel"  >
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="0.4*"/>
        <ColumnDefinition Width="0.1*"/>
        <ColumnDefinition Width="0.35*"/>
        <ColumnDefinition Width="0.15*"/>
    </Grid.ColumnDefinitions>
    <my:DigitLoopingSelector 
    Grid.Column="0"
    Name="WidthMeterSelector" 
Size="108,108"
Margin="6,3" HorizontalAlignment="Right" Margin="0" Width="108" IsExpanded="True"
 
    MaxValue="15" MinValue="1" DefaultValue="3" SelectedItem="{Binding WidthMeter}" >
        <my:DigitLoopingSelector.ItemTemplate>
            <DataTemplate>
                <TextBlock 
    Text="{Binding BindsDirectlyToSource=True}"
    FontSize="54" TextAlignment="Center" VerticalAlignment="Center"
    FontFamily="{StaticResource PhoneFontFamilySemiBold}"/>
            </DataTemplate>
        </my:DigitLoopingSelector.ItemTemplate>
    </my:DigitLoopingSelector>
 
    <TextBlock Grid.Column="1" Margin="0,0,0,0" TextWrapping="Wrap" Text="m" FontSize="48" VerticalAlignment="Center"/>
 
    <my:DigitLoopingSelector 
    Grid.Column="2"
    Name="WidthCentimeterSelector" 
Size="108,108"
Margin="6,3" 
    MaxValue="99" StringFormat="D2"
    MinValue="0" HorizontalAlignment="Right" Margin="48,0,0,0" Width="108" IsExpanded="True"
    >
        <my:DigitLoopingSelector.ItemTemplate>
            <DataTemplate>
                <TextBlock 
    Text="{Binding BindsDirectlyToSource=True}"
    FontSize="54" TextAlignment="Center" VerticalAlignment="Center"
    FontFamily="{StaticResource PhoneFontFamilySemiBold}"/>
            </DataTemplate>
        </my:DigitLoopingSelector.ItemTemplate>
    </my:DigitLoopingSelector>
    <TextBlock Grid.Column="3" Margin="0" TextWrapping="Wrap" Text="cm" FontSize="48" VerticalAlignment="Center"/>
</Grid>
 

On the following figure you can see how it could look like in your application:

Digit Looping Selector control

Of course, you can put the controls in a smaller panel, change font size for digits, size of boxes etc. according to your needs.

Sources

You can grab sources here.

Conclusion

The DigitLoopingSelector control is very useful and easy to use – you need just use my superstructure for the control and datasource or implement your one for tricky scenarios.

This work is licensed under a Creative Commons Attribution By license.

Leave a Comment [ RSS ]

  • reverse a string

    sir i wanted to reverse a string in wimdows phone7, how can i do this, ie..
    ..
    input box - "you are intelligent" (user type this in the input box)
    ..
    press enter. (user presses enter) (stirng is readed and dispalyed)
    ..
    ouput box display - "tnegilletni era uoy" (will be displayed to user)..
    ..
    please help me

  • re: Windows Phone Looping Selector for Digits

    If you are not kidding me....

    using System.Linq;
    {
    ....
    OutputBox.Text = InputBox.Text.ToCharArray().Reverse().ToString();
    ....
    }

  • re: Windows Phone Looping Selector for Digits

    Requesting Gravatar... phejndorf says:

    This is great stuff - I've really been looking for this (but not dared venture into building it myself)! Seems like MS has never really accepted the need for numeric input (ie a built in numeric textbox etc), forcing us to resort fancy 3rd party stuff or to after-the-fact validation instead.

    One question: Can you make the list "band" fade away again automatically or programmatically? It seems that you have to tap an extra time to make this happen - instead I would like it to happen when the scrolling has settled and a number is selected or at least when the second list (ie cm) is activated. The latter method seems to be what happens in the built in datetime picker.

  • re: Windows Phone Looping Selector for Digits

    Requesting Gravatar... Arjan says:

    Thanks for this! I have been looking for such a thing and I found the datepicker but no element for a single number!

  • re: Windows Phone Looping Selector for Digits

    Requesting Gravatar... Mike says:

    Great stuff. I presume this can also be done with my horizontal looping selector: http://blog.supaywasi.com/2011/09/horizontal-looping-selector-mango-update/

  • re: Windows Phone Looping Selector for Digits

    Requesting Gravatar... Ryan says:

    I am having trouble implementing this correctly. For some reason I am getting an error as follows;

    "The property 'my:DigitalLoopingSelector.ItemTemplate is not declared as an attachable property"

    Any idea why I may be getting this?

    Thanks in advance.

Comments have been closed on this topic.