WPFで動画のサムネイルを表示する(1)
下の画像のようにエクスプローラ風ツリービューとリストボックスを使って動画のサムネイル(+ファイル名)を表示するプログラムを作ってみました。
エクスプローラ部分は、CodeProjectのA Multi-Threaded WPF TreeView Explorerを使いました。
動画のサムネイルの生成は前の記事で紹介したThumnailCreaterクラスを使っています。
まずXAMLから。エクスプローラ部分の詳細は上のリンク先を参照してください。
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="600" Width="800" Loaded="Window_Loaded">
<Grid Name="explorerGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TreeView Name="treeViewFolder" Grid.Row="0" Grid.Column="0" SelectedItemChanged="treeViewFolder_SelectedItemChanged">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate DataType="ContentPresenter">
<Grid>
<StackPanel Name="stackPanelImage" Orientation="Horizontal">
<Image Name="imageFolder" Width="20" Height="20" Stretch="Fill" VerticalAlignment="Center" Source="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=(local:TreeViewItemProperties.ItemImageName)}"></Image>
<TextBlock Text="{Binding}" Margin="5,0" VerticalAlignment="Center" />
<Button Name="buttonReload" Height="14" VerticalAlignment="Center" Visibility="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=(local:TreeViewItemProperties.IsCanceled), Converter={x:Static local:vc_BoolToVisiblity.Instance}}" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}}}" Click="buttonReload_Click">
<TextBlock FontSize="9" Text="Reload..." TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center"></TextBlock>
</Button>
<StackPanel Name="stackPanelLoading" Orientation="Horizontal" Visibility="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=(local:TreeViewItemProperties.IsLoading), Converter={x:Static local:vc_BoolToVisiblity.Instance}}">
<Grid Height="13" MinWidth="50" MaxWidth="75" Margin="5,0" >
<ProgressBar Name="progressbarLoading" Height="13" MinWidth="50" MaxWidth="75" IsIndeterminate="True" HorizontalAlignment="Center" VerticalAlignment="Center"></ProgressBar>
<TextBlock Name="textBlockLoading" Text="Loading..." FontSize="8.6" Margin="5,0" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
<Button Name="buttonCancelLoad" IsEnabled="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=(local:TreeViewItemProperties.IsCanceled), Converter={x:Static local:vc_FlipBool.Instance}}" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}}}" Height="14" Click="buttonCancelLoad_Click" VerticalAlignment="Center" >
<TextBlock FontSize="9" Text="Cancel" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center"></TextBlock>
</Button>
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.Resources>
</TreeView>
<GridSplitter Grid.Row="0" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Stretch" ShowsPreview="True" Width="3"></GridSplitter>
<DockPanel Grid.Row="0" Grid.Column="2">
<DockPanel DockPanel.Dock="Top" Margin="2">
<TextBlock DockPanel.Dock="Left" VerticalAlignment="Center" HorizontalAlignment="Center" Text="フィルタ:"></TextBlock>
<ComboBox Name="comboBoxFilter" SelectedIndex="0" SelectionChanged="comboBoxFilter_SelectionChanged">
<ComboBoxItem Name="comboBoxItemAll">全てのファイル(*.*)</ComboBoxItem>
<ComboBoxItem Name="comboBoxItemAvi">AVIファイル(*.avi)</ComboBoxItem>
<ComboBoxItem Name="comboBoxItemMov">MOVファイル(*.mov;*.qt)</ComboBoxItem>
<ComboBoxItem Name="comboBoxItemWmv">WMVファイル(*.wmv)</ComboBoxItem>
<ComboBoxItem Name="comboBoxItemFlv">FLVファイル(*.flv)</ComboBoxItem>
<ComboBoxItem Name="comboBoxItemMpg">MPGファイル(*.mpg;*.mpeg;*.m1v;*m2v)</ComboBoxItem>
<ComboBoxItem Name="comboBoxItemMp4">MP4ファイル(*.mp4;*.m4v;*.m4a)</ComboBoxItem>
<ComboBoxItem Name="comboBoxItemMkv">MKVファイル(*.mkv)</ComboBoxItem>
<ComboBoxItem Name="comboBoxItemTs">TSファイル(*.ts;*.m2ts)</ComboBoxItem>
</ComboBox>
</DockPanel>
<ListBox Name="listBoxFile">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Image Source="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=Tag, Converter={x:Static local:vc_PathToImageSource.Instance}}" Width="64" Height="64" VerticalAlignment="Center" HorizontalAlignment="Center"></Image>
<TextBlock Text="{Binding Converter={x:Static local:vc_FullPathToFileName.Instance}}"></TextBlock>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
</ListBox>
</DockPanel>
</Grid>
</Window>
構造的にはツリービュー部分とリストボックス+コンボボックス部分に二分されているだけなので分かりにくくはないと思います。
ただ、バインディング周りが少々複雑なので簡単に解説をします。
サムネイルとファイル名を表示するリストボックスはContentTemplate
を変更して独自の表示にしています。
その部分を抜き出すと、
<DataTemplate>
<StackPanel Orientation="Vertical">
<Image Source="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=Tag, Converter={x:Static local:vc_PathToImageSource.Instance}}" Width="64" Height="64" VerticalAlignment="Center" HorizontalAlignment="Center"></Image>
<TextBlock Text="{Binding Converter={x:Static local:vc_FullPathToFileName.Instance}}"></TextBlock>
</StackPanel>
</DataTemplate>
垂直方向に要素を配置するスタックパネルの中にImage
要素とTextBlock
要素を格納しています。
Image
要素は複雑なのでTextBlock
要素から説明しますと、Text
プロパティにバインディングを指定しています。
Text="{Binding Converter={x:Static local:vc_FullPathToFileName.Instance}}"
各ファイルを表すリストボックス項目のコンテンツにはそのファイルのフルパスが格納されていますので、TextBlock
要素に必要なのはTextBlock.Text
プロパティとListBoxItem.Content
を結び付けることです。この場合ですと、Text={Binding}
とだけ書けばTextBlock.Text
にコンテンツを渡すことができます。しかし、TextBlock
要素に表示したいのはフルパスではなくファイル名だけなので、コンバータを使ってフルパスからファイル名に変換します。コンバータは、vc_FullPathToFileName.Instance
です。vc_FullPathToFileName
クラスの定義を以下に示します。
[ValueConversion(typeof(string), typeof(string))]
public class vc_FullPathToFileName : IValueConverter
{
public static vc_FullPathToFileName Instance = new vc_FullPathToFileName();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Path.GetFileName((string)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("Cannot convert back");
}
}
次は、Image
要素です。
次回でコードを示しますが、Image
要素に表示する動画のサムネイルはコードビハインドで生成されてビットマップファイルとしてプログラムが存在するフォルダの配下に保存されます(一時的に)。そして、最終的に保存先のファイル名がリストボックス項目のTag
プロパティに格納されます。
なので、Image
要素にサムネイルを表示するために必要なことは、Image.Source
プロパティとその親のListBoxItem.Tag
プロパティを結び付けることです。それは、Image.Source
プロパティに指定するバインディング式を以下のようにすることで可能です。
Source="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=Tag, Converter={x:Static local:vc_PathToImageSource.Instance}}"
RelativeSource
のMode
をFindAncestor
にすることでコントロールの階層構造上でルートに向かって要素を探せます。AncestorType
をListBoxItem
にすれば、ルートに向かって検索した時に最初に見つかったListBoxItem
が返ってきます。これをバインディングのソースにして、パスをTag
プロパティにし、コンバータにvc_PathToImageSource.Instance.Instance
を指定しています。このコンバータは渡されたファイル名から適切なファイルを開き画像データを読み込んでそれを返すコンバータで下のようになります。特にピクセルフォーマットをBgra32
にすることに注意してください。
[ValueConversion(typeof(string), typeof(ImageSource))]
public class vc_PathToImageSource : IValueConverter
{
public static vc_PathToImageSource Instance = new vc_PathToImageSource();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string fileName = (string)value;
if (!File.Exists(fileName))
return null;
Bitmap bitmap = (Bitmap)Bitmap.FromFile(fileName);
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat);
byte[] buffer = new byte[4 * bitmapData.Width * bitmapData.Height];
Marshal.Copy(bitmapData.Scan0, buffer, 0, buffer.Length);
BitmapSource bitmapSource = BitmapSource.Create(bitmapData.Width, bitmapData.Height, 96, 96, PixelFormats.Bgra32, null, buffer, 4 * bitmapData.Width);
bitmap.UnlockBits(bitmapData);
bitmap.Dispose();
return bitmapSource;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("Cannot convert back");
}
}
これで、XAMLとコンバータ部分は終わりです。次回はイベントハンドラの実装です。
(続く...)
