iPhone風のボタン付きリストボックスを作る

前回にListBoxのリストアイテムを枠いっぱいに引き伸ばす方法を紹介しました。今回はこれを使ってiPhone風の右端にボタンがあるリストボックス(下図)を作ってみます。

リストアイテムをリストボックス幅にフィットさせる

前回紹介したようにListBox.HorizontalContentAlignment = Strechを設定すれば枠いっぱいに引き伸ばされます。

ただ一点不具合があります。リストアイテムのテキストが長い場合にはリストボックス幅よりも大きくなってしまいます。この結果、右端のボタンがリストボックスからはみ出てしまい表示されなくなります。これを防ぐために、ScrollViewer.HorizontalScrollBarVisibility = Disabled を入れます。横スクロールが無効になり、スクロールバーが表示されなくなるのと同時に、リストアイテムはリストボックス幅に縮められます。

        <ListBox ItemTemplate="{StaticResource ListDataTemplate}"
                 ItemsSource="{StaticResource ListData}"
                 ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                 HorizontalContentAlignment="Stretch" />

レイアウト設定

丸ボタンを右端に配置し、余ったスペースをTextBlockに割り当てるためにDockPanelを使いました。テキスト以外の部品が増えるようならGridパネルなどの方が分かりやすくなるかも知れません。

<Window x:Class="wpf110421IconInListBox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="200">

    <Window.Resources>

        <!--雰囲気を出すためのスタイル定義(適当)-->
        <LinearGradientBrush x:Key="BackgroundBrush1" 
                             EndPoint="0.5,1" StartPoint="0.5,0">
            <GradientStop Color="White" Offset="0" />
            <GradientStop Color="CadetBlue" Offset="1" />
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="BackgroundBrush2" EndPoint="0.5,1" StartPoint="0.5,0">
            <GradientStop Color="#FF96CBFF" Offset="0" />
            <GradientStop Color="#FF0B2B4B" Offset="1" />
        </LinearGradientBrush>
        <!--雰囲気を出すためのスタイル定義(丸ボタン)-->
        <ControlTemplate x:Key="BallButtonTemplate" TargetType="Button">
            <Grid>
                <Ellipse Fill="{StaticResource BackgroundBrush2}" />
                <Ellipse Margin="1" Stroke="LightSteelBlue" StrokeThickness="2" />
                <TextBlock Text="&gt;" 
                           FontSize="9" Foreground="White" 
                           HorizontalAlignment="Center" 
                           VerticalAlignment="Center" />
            </Grid>
        </ControlTemplate>


        <!--リストボックス用のテンプレート-->
        <DataTemplate x:Key="ListDataTemplate">
            <Grid>
                <!--リストアイテムの背景-->
                <Rectangle RadiusX="5" RadiusY="5" 
                           Fill="{StaticResource BackgroundBrush1}" />
                <DockPanel Margin="4">
                    <!--丸ボタン:右寄せ-->
                    <Button DockPanel.Dock="Right" 
                            Width="20" Height="20"
                            Template="{StaticResource BallButtonTemplate}" />
                    <!--リストアイテムのテキスト表示-->
                    <TextBlock Text="{Binding}" VerticalAlignment="Center" />
                </DockPanel>
            </Grid>
        </DataTemplate>

        <!--リストボックス用のデータ-->
        <x:Array xmlns:sys="clr-namespace:System;assembly=mscorlib"
                 x:Key="ListData" Type="sys:String">
            <sys:String></sys:String>
            <sys:String>いい</sys:String>
            <sys:String>ううう</sys:String>
            <sys:String>あいうえおかきくけこさしすせそ</sys:String>
        </x:Array>
        
    </Window.Resources>

    
    <Grid>
        <!--データテンプレートの幅をスクロール領域全幅にフィットさせる-->
        <ListBox ItemTemplate="{StaticResource ListDataTemplate}"
                 ItemsSource="{StaticResource ListData}"
                 ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                 HorizontalContentAlignment="Stretch" />

    </Grid>
    
</Window>

雰囲気を出すため冒頭で背景グラデーションやボタンのスタイルを定義していますがテキトーです。この辺を無視すればずいぶんシンプルに実現できていると感じると思います。

動作させて、ウィンドウ枠をいろいろ変えてみるとリストボックスの大きさに合わせて丸ボタンが追従しているのが分かります。また高さをすぼめて縦スクロールバーが出てきてもうまく避けています。

参考:その他の強引な方法

今回はListBoxのプロパティで上手く解決しましたが、データテンプレートの幅を親スクロール領域の幅に強引にバインディングする方法もあります。知っていると何かの際に役に立つかも知れませんので簡単に紹介します。

        <DataTemplate x:Key="ListDataTemplate">
            <Grid Margin="-2,0"
                  Width="{Binding RelativeSource={RelativeSource AncestorType=ScrollViewer}, Path=ViewportWidth}" >
                      :
                    (省略)

親要素のScrollViewerオブジェクトを見つけてきて、データテンプレート直下のパネルのWidthにバインディングしています。またこのままだと僅かに大きくなってしまい横スクロールバーが出てしまいますので、Marginに負値を入れて一回り小さなサイズにしています。