色選択リストを作る

Silverlightで色を選択するためのリストが欲しかったので作ってみた。

普通ならComboBoxを使って、テンプレートを変更すればいいんだろうけど、現状のBeta2ではまだ用意されていない(というか正式版では用意されるんだよね?)ので、色々ごちゃごちゃ組み合わせてやってみた。

ColorPicker.xaml

<UserControl x:Class="SilverlightApplication1.ColorPicker"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Loaded="UserControl_Loaded">

    <StackPanel Background="White">
        <Border x:Name="colorBlock" Height="25" Margin="0,0,0,1" BorderThickness="1" BorderBrush="Black">
            <Button x:Name="dropButton" Width="20" Click="dropButton_Click"
                    HorizontalAlignment="Right">
                <Button.Template>
                    <ControlTemplate>
                        <Grid>
                            <Rectangle Fill="Silver" Width="20" />
                            <Polygon Points="0,0,12,0,6,6" Fill="Black"
                                     HorizontalAlignment="Center" VerticalAlignment="Center">
                            </Polygon>
                        </Grid>
                    </ControlTemplate>
                </Button.Template>
            </Button>
        </Border>

        <Canvas>
            <ListBox x:Name="colorList" Height="200" Visibility="Collapsed"
                 SelectionChanged="colorList_SelectionChanged">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Rectangle x:Name="colorRect" Fill="{Binding Brush}" Width="{Binding Width}" Height="20"
                                   Stroke="Black" StrokeThickness="0" MouseMove="colorRect_MouseMove" MouseLeave="colorRect_MouseLeave">

                            <Rectangle.Resources>
                                <Storyboard x:Name="highlight">
                                    <DoubleAnimation From="0" To="1" Duration="0"
                                                         Storyboard.TargetName="colorRect" Storyboard.TargetProperty="StrokeThickness" />
                                </Storyboard>
                            </Rectangle.Resources>
                        </Rectangle>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Canvas>
    </StackPanel>
</UserControl>

他の要素にオーバーラップさせて表示するListBoxをCanvasの中に配置することで実現できたけど、このColorPickerコントロールよりあとに追加されたコントロールにはZIndexで負けてしまうので隠れてしまう。このへんはどう解消すればいいのかわからない。

ColorPicker.xaml.cs

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Controls;
using System.Windows.Media.Animation;
using System.Reflection;
using System.Collections.Generic;
using System.Diagnostics;

namespace SilverlightApplication1 {
    /// <summary>
    /// 色の一覧リストから色を選択するComboBoxコントロール
    /// </summary>
    public partial class ColorPicker : UserControl {
        /// <summary>
        /// 
        /// </summary>
        public class ColorPickerItem {
            /// <summary>
            /// ブラシを取得、設定します。
            /// </summary>
            public Brush Brush {
                get;
                set;
            }
            /// <summary>
            /// 横幅を取得、設定します。
            /// </summary>
            public double Width {
                get;
                set;
            }
        }

        /// <summary>
        /// 選択している色を取得します。
        /// </summary>
        public Color? SelectedColor {
            get {
                SolidColorBrush brush = colorBlock.Background as SolidColorBrush;

                return brush != null ? (Color?)brush.Color : null;
            }
        }

        /// <summary>
        /// 選択している色が変更された時に呼び出されます。
        /// </summary>
        public event RoutedEventHandler SelectedColorChanged;
        /// <summary>
        /// SelectedColorChangedイベントを発生させます。
        /// </summary>
        /// <param name="e">イベント引数</param>
        protected virtual void OnSelectedColorChanged(RoutedEventArgs e) {
            if(SelectedColorChanged != null) SelectedColorChanged(this, e);
        }

        public ColorPicker() {
            InitializeComponent();
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e) {
            var colors = new List<ColorPickerItem>();

            foreach(var propInfo in typeof(Colors).GetProperties()) {
                if(propInfo.Name == "Transparent") continue;

                var color = (Color)propInfo.GetValue(
                    null, BindingFlags.Static | BindingFlags.GetProperty, null, null, null
                );
                colors.Add(new ColorPickerItem {
                    Brush = new SolidColorBrush(color),
                    Width = this.Width - 25
                });
            }
            colorList.ItemsSource = colors;
        }

        private void dropButton_Click(object sender, RoutedEventArgs e) {
            colorList.Visibility = (colorList.Visibility == Visibility.Visible) ?
                Visibility.Collapsed :
                Visibility.Visible;
        }

        private void colorList_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            colorList.Visibility = Visibility.Collapsed;
            colorBlock.Background = ((ColorPickerItem)colorList.SelectedItem).Brush;

            OnSelectedColorChanged(new RoutedEventArgs { Source=this });
        }

        private void colorRect_MouseMove(object sender, MouseEventArgs e) {
            ((Storyboard)((FrameworkElement)sender).FindName("highlight")).Begin();
        }
        private void colorRect_MouseLeave(object sender, MouseEventArgs e) {
            ((Storyboard)((FrameworkElement)sender).FindName("highlight")).Stop();
        }
    }
}

スクリーンショット

見ての通り、まったくスマートじゃない。Silverlightを勉強するにあたってWPFをざっと勉強してみたけど、WPFと比べてSilverlightには足りない機能が多い。
特にデータバインディング関係とイベントトリガー関係。この二つがしょぼいおかげで、WPFならコードが要らないところが、Silverlightだとしょうもないコードを書く必要が生じてしまう。

正式版になったらこのへんは解消するんだろうか?じゃないとちょっと微妙な感じになってくるなぁ。