気楽なソフト工房

プログラミングについていろいろな記事を書いています。



mykonos2008

Author:mykonos2008
システムエンジニアとして働いている30代の会社員です。
仕事や趣味でプログラムを書いている方の役に立つ記事を書いていきたいと思っています。
ご意見、ご感想はこちらまで
If you are an english speaker,Please visit my english blog.

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
WPFのListViewを初めて使用する方は、WindowsフォームのListViewでいう「LargeIconモード」が、WPFのListViewでは提供されていないことに
驚きとショックを受けるのではないでしょうか?

私もショックを受けた1人です。先日、発表させていただいた英会話学習ソフト「イメージで覚える英単語」では、
LargeIcon風のデザインで、英単語と英単語に対応した画像を表示しています。

(画面ショット)


これをやるために、ListViewを使おうとしたのですが、WPFのListViewにある表示モードでは、それが出来ないことが分かり愕然としました。
一時はListViewをあきらめて、WrapPanelで実装しようかと思っていたのですが、DataBindingや項目選択の機能など、
欠かせない機能があるため、ListViewを拡張する決心をしました。

ネット上のリソースをいろいろ探したところ、いくつかやり方があるみたいでしたが、以下のサイトのやり方を
参考に、実装しました。

How to Create a Custom View

ざっくり言うと、やることは以下の3つです。
1. 新しい表示モードが使用するスタイルを定義する。
2. ListViewの表示モードの基底クラスであるViewBaseを拡張し、新しい表示モードを作成する。
3. ListViewの表示モード(Viewプロパティ)に作成した表示モードを指定する。

まず、1番目のスタイル定義から説明します。
「Themes」ディレクトリ以下に、「Generic.xaml」を作成し、以下のようにして、
「ListView」と「ListViewItem」のスタイルを定義しまます。

<ResourceDictionary xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
                    xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
                    xmlns:l="clr-namespace:ImageWord.Controls">   
    <!-- ListViewのスタイル定義 -->
    <Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type l:ImageView},
                                               ResourceId=ImageView}" ・・・・①
        TargetType="{x:Type ListView}" BasedOn="{StaticResource {x:Type ListBox}}">
        <Setter Property="BorderBrush" Value="Black"/>
        <Setter Property="BorderThickness" Value="0.5"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <Border Name="bd" BorderBrush="{TemplateBinding BorderBrush}" 
                           BorderThickness="{TemplateBinding BorderThickness}"
                        Background="{TemplateBinding Background}" Margin="{TemplateBinding Margin}">
                        <ScrollViewer Margin="{TemplateBinding Padding}">
                            <WrapPanel ItemWidth="160" IsItemsHost="True" MinWidth="100"
                              Width="{Binding ActualWidth,
                                RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}">
                            </WrapPanel>
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <!-- ListViewItemのスタイル定義 -->
    <Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type l:ImageView},
                                        ResourceId=ImageViewItem}" ・・・・②
         TargetType='{x:Type ListViewItem}' BasedOn='{StaticResource {x:Type ListBoxItem}}'>
        <Style.Resources>
            <l:UriImageConverter x:Key="uriImageConverter"/>
        </Style.Resources>
        <Setter Property='Padding' Value='3'/>
        <Setter Property='Margin' Value='5'/>
        <Setter Property='HorizontalContentAlignment' Value='Center'/>
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <Border Background="White">
                        <StackPanel Orientation="Vertical">
                            <Grid Width="150" Height="126">
                                <Image Margin="0,3,0,3" Source="{Binding ImageFullPath}" ・・・・③
                                      Width="{Binding PreferredWidth}" Height="{Binding PreferredHeight}"/>
                             </Grid>
                            <TextBlock Text="{Binding Text}" TextWrapping="Wrap" HorizontalAlignment="Center" 
                                       FontSize="15" Name="WordText" Foreground="Black" Margin="0,0,0,5"/>
                        </StackPanel>
                    </Border>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

設定したスタイルの内容の詳細は省略させて頂きますが、ポイントの1つ目は、それぞれ「ListView」と「ListViewItem」を
TargetTypeとするスタイルを定義することです。

そして、2つ目のポイントは、それぞれのスタイルの「x:key」にComponentResourceKeyを指定することです。(上記①と②)
なぜ、「ComponentResourceKey」で無ければならないか・・・・すみません、分かりません。真似しました。
(サンプルをいくつか見ましたが、みんなこうしていました。理由が分かる方、教えてくださいませ。)
ここに指定したキーを後で、ViewBaseを拡張したクラスの中で使用します。

③の部分で、ListViewItemのContentTemplateの中にImageを定義していますが、SourceのBindingに指定している
「ImageFullPath」はListViewにバインドしたコレクション(ObservableCollection)に格納されている要素の
属性です。
(ちょっと分かりにくいですね。。OberservableCollectionにWordというクラスを詰めてそれをListViewにバインドしています。
そして、WordクラスのプロパティとしてImageFullPathが定義されていて、そこに画像のフルパスが設定されています。)

2つめの手順はViewBaseの拡張です。

namespace ImageWord.Controls
{
    public class ImageView : ViewBase
    {
        //ListViewのスタイルのキーを返す
        protected override object DefaultStyleKey
        {
            get { return new ComponentResourceKey(GetType(), "ImageView"); }
        }

        //ListViewItemのスタイルのキーを返す
        protected override object ItemContainerDefaultStyleKey
        {
            get { return new ComponentResourceKey(GetType(), "ImageViewItem"); }
        }
    }
}

といってもやることは少しです。ViewBaseのプロパティ「DefaultStyleKey」と「ItemContainerDefaultStyleKey」
をオーバーライドして「Genaric.xaml」でListViewとListViewItem用に定義したスタイルに割り当てたキーを
返却するだけです。

「Genaric.xaml」ではこんな風にキーを定義していました。

x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type l:ImageView},ResourceId=ImageView}"

TypeInTargetAssemblyにImageViewのTypeインスタンスをResourceIdにImagwViewという文字列を指定しています。
ViewBaseで返却しているComponentResourceKeyにも同じ指定をしていることが分かると思います。

最後の3番目の手順ですが、XAMLでListViewのViewに作成したImageViewクラスを指定します。

     <ListView>
         <ListView.View>
             <l:ImageView/>
         </ListView.View>
     </ListView>

後はListViewのItemsSourceにデータソースになるコレクションを設定するのみです。

コメント

コメントの投稿

管理者にだけ表示を許可する

トラックバック

http://csfun.blog49.fc2.com/tb.php/90-b6a1330e

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。