雑記 - otherwise

最近はDQ10しかやっていないダメ技術者がちまちまと綴る雑記帳

わんくま東京勉強会 #64 アフターフォロー (6) : ComboBox の代替 - LoopingSelector


日付や時刻と云った選択肢がループする様なケースでは、 ListPicker の代わりに LoopingSelector を利用する事が出来ます。
今回紹介するコントロールも全て Toolkit に含まれるコントロールのため、使用する際は Toolkit が必要になります。

DatePicker / TimePicker


よく使われるであろう日付選択と時刻選択については、それぞれ専用のコントロールが用意されています。
このコントロールを利用する事で、全画面での数値選択の UI を含めて利用可能です。

Custom LoopingSelector


日付や時刻以外にも、選択肢用のデータクラスと表示用のテンプレートを用意する事で、任意のデータを LoopingSelector で表示する事が可能です。

データ格納用のジェネリッククラス

まず、以下の様なクラスを用意します。
(これらのクラスをひとつずつ用意しておけば使い回しが可能です)

  • LoopingDataSourceBase.cs
using System;
using System.Windows.Controls;
using Microsoft.Phone.Controls.Primitives;

namespace WankumaTokyo64Sample.Controls
{
  public abstract class LoopingDataSourceBase : ILoopingSelectorDataSource
  {
    private object selectedItem;

    #region ILoopingSelectorDataSource Members

    public abstract object GetNext(object relativeTo);

    public abstract object GetPrevious(object relativeTo);

    public object SelectedItem
    {
      get
      {
        return this.selectedItem;
      }
      set
      {
        if (!object.Equals(this.selectedItem, value))
        {
          object previousSelectedItem = this.selectedItem;
          this.selectedItem = value;
          this.OnSelectionChanged(previousSelectedItem, this.selectedItem);
        }
      }
    }

    public event EventHandler<SelectionChangedEventArgs> SelectionChanged;

    protected virtual void OnSelectionChanged(object oldSelectedItem, object newSelectedItem)
    {
      EventHandler<SelectionChangedEventArgs> handler = this.SelectionChanged;
      if (handler != null)
      {
        handler(this, new SelectionChangedEventArgs(new object[] { oldSelectedItem }, new object[] { newSelectedItem }));
      }
    }

    #endregion
  }
}
  • ListLoopingDataSource.cs
using System;
using System.Collections.Generic;

namespace WankumaTokyo64Sample.Controls
{
  public class ListLoopingDataSource<T> : LoopingDataSourceBase
  {
    private LinkedList<T> linkedList;
    private List<LinkedListNode<T>> sortedList;
    private IComparer<T> comparer;
    private NodeComparer nodeComparer;

    public ListLoopingDataSource() { }

    public IEnumerable<T> Items
    {
      get { return this.linkedList; }
      set { this.SetItemCollection(value); }
    }

    private void SetItemCollection(IEnumerable<T> collection)
    {
      this.linkedList = new LinkedList<T>(collection);
      this.sortedList = new List<LinkedListNode<T>>(this.linkedList.Count);
      LinkedListNode<T> currentNode = this.linkedList.First;
      while (currentNode != null)
      {
        this.sortedList.Add(currentNode);
        currentNode = currentNode.Next;
      }
      IComparer<T> comparer = this.comparer;
      if (comparer == null)
      {
        if (typeof(IComparable<T>).IsAssignableFrom(typeof(T)))
        {
          comparer = Comparer<T>.Default;
        }
        else
        {
          throw new InvalidOperationException("There is no default comparer for this type of item. You must set one.");
        }
      }
      this.nodeComparer = new NodeComparer(comparer);
      this.sortedList.Sort(this.nodeComparer);
    }

    public IComparer<T> Comparer
    {
      get { return this.comparer; }
      set { this.comparer = value; }
    }

    public override object GetNext(object relativeTo)
    {
      int index = this.sortedList.BinarySearch(new LinkedListNode<T>((T)relativeTo), this.nodeComparer);
      if (index < 0)
      {
        return default(T);
      }
      LinkedListNode<T> node = this.sortedList[index].Next;
      if (node == null)
      {
        node = this.linkedList.First;
      }
      return node.Value;
    }

    public override object GetPrevious(object relativeTo)
    {
      int index = this.sortedList.BinarySearch(new LinkedListNode<T>((T)relativeTo), this.nodeComparer);
      if (index < 0)
      {
        return default(T);
      }
      LinkedListNode<T> node = this.sortedList[index].Previous;
      if (node == null)
      {
        node = this.linkedList.Last;
      }
      return node.Value;
    }

    private class NodeComparer : IComparer<LinkedListNode<T>>
    {
      private IComparer<T> comparer;

      public NodeComparer(IComparer<T> comparer)
      {
        this.comparer = comparer;
      }

      #region IComparer<LinkedListNode<T>> Members

      public int Compare(LinkedListNode<T> x, LinkedListNode<T> y)
      {
        return this.comparer.Compare(x.Value, y.Value);
      }

      #endregion
    }
  }
}
画面

XAML で見た目を設定します。

  • LoopingSelectorPage.xaml
<toolspr:LoopingSelector x:Name="ObjectSelector" ItemSize="335,128">
  <toolspr:LoopingSelector.ItemTemplate>
    <DataTemplate>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="72" />
          <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Image Grid.Column="0" Width="64" Height="64" HorizontalAlignment="Center" VerticalAlignment="Center" Source="{Binding ImagePath}" />
        <Grid Grid.Column="1">
          <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
          </Grid.RowDefinitions>
          <TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="{Binding Name}" Style="{StaticResource PhoneTextLargeStyle}" />
          <tools:WrapPanel Grid.Row="1" VerticalAlignment="Center">
            <TextBlock Text="{Binding StartDate, StringFormat=MM/dd}" Style="{StaticResource PhoneTextSmallStyle}" />
            <TextBlock Text="〜" Style="{StaticResource PhoneTextSmallStyle}" />
            <TextBlock Text="{Binding EndDate, StringFormat=MM/dd}" Style="{StaticResource PhoneTextSmallStyle}" />
          </tools:WrapPanel>
        </Grid>
      </Grid>
    </DataTemplate>
  </toolspr:LoopingSelector.ItemTemplate>
</toolspr:LoopingSelector>
データソースの設定

コードからデータソースを設定します。

  • LoopingSelectorPage.xaml.cs
var items = new List<ObjectLoopingSelectorSampleItem>
{
  new ObjectLoopingSelectorSampleItem { Name = @"おひつじ座", StartDate = new DateTime(2011,  3, 21), EndDate = new DateTime(2011,  4, 19), ImagePath = "/images/seiza1.png"  },
  new ObjectLoopingSelectorSampleItem { Name = @"おうし座",   StartDate = new DateTime(2011,  4, 20), EndDate = new DateTime(2011,  5, 20), ImagePath = "/images/seiza2.png"  },
  new ObjectLoopingSelectorSampleItem { Name = @"ふたご座",   StartDate = new DateTime(2011,  5, 21), EndDate = new DateTime(2011,  6, 21), ImagePath = "/images/seiza3.png"  },
  new ObjectLoopingSelectorSampleItem { Name = @"かに座",     StartDate = new DateTime(2011,  6, 22), EndDate = new DateTime(2011,  7, 22), ImagePath = "/images/seiza4.png"  },
  new ObjectLoopingSelectorSampleItem { Name = @"しし座",     StartDate = new DateTime(2011,  7, 23), EndDate = new DateTime(2011,  8, 22), ImagePath = "/images/seiza5.png"  },
  new ObjectLoopingSelectorSampleItem { Name = @"おとめ座",   StartDate = new DateTime(2011,  8, 23), EndDate = new DateTime(2011,  9, 22), ImagePath = "/images/seiza6.png"  },
  new ObjectLoopingSelectorSampleItem { Name = @"てんびん座", StartDate = new DateTime(2011,  9, 23), EndDate = new DateTime(2011, 10, 23), ImagePath = "/images/seiza7.png"  },
  new ObjectLoopingSelectorSampleItem { Name = @"さそり座",   StartDate = new DateTime(2011, 10, 24), EndDate = new DateTime(2011, 11, 22), ImagePath = "/images/seiza8.png"  },
  new ObjectLoopingSelectorSampleItem { Name = @"いて座",     StartDate = new DateTime(2011, 11, 23), EndDate = new DateTime(2011, 12, 21), ImagePath = "/images/seiza9.png"  },
  new ObjectLoopingSelectorSampleItem { Name = @"やぎ座",     StartDate = new DateTime(2011, 12, 22), EndDate = new DateTime(2012,  1, 20), ImagePath = "/images/seiza10.png" },
  new ObjectLoopingSelectorSampleItem { Name = @"みずがめ座", StartDate = new DateTime(2012,  1, 21), EndDate = new DateTime(2012,  2, 18), ImagePath = "/images/seiza11.png" },
  new ObjectLoopingSelectorSampleItem { Name = @"うお座",     StartDate = new DateTime(2012,  2, 19), EndDate = new DateTime(2012,  3, 20), ImagePath = "/images/seiza12.png" }
};
ObjectSelector.DataSource = new ListLoopingDataSource<ObjectLoopingSelectorSampleItem> { Items = items, SelectedItem = items[0] };