かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

ListBox間のアイテムの移動をアニメーションさせる

ここ最近お気に入りのFluidMoveBehaviorですが、ListBox間のアイテムの移動もアニメーションさせることができます。
まず、ListBox2つ置いてある画面に対応するViewModelを用意します。

namespace ListBoxItemMove.ViewModels
{
    using System.Collections.ObjectModel;

    public class ListMoveViewModel : ViewModelBase
	{
        private ListBoxItemViewModel leftSelectedItem;

        private ListBoxItemViewModel rightSelectedItem;

        public ListMoveViewModel()
		{
            // 左側にデフォルトで4個のアイテムを置いておく
            this.LeftItems = new ObservableCollection<ListBoxItemViewModel>
            {
                new ListBoxItemViewModel(),
                new ListBoxItemViewModel(),
                new ListBoxItemViewModel(),
                new ListBoxItemViewModel(),
            };

            // 右側は空
            this.RightItems = new ObservableCollection<ListBoxItemViewModel>();
		}

        /// <summary>
        /// 左側のリストボックスに表示するアイテム
        /// </summary>
        public ObservableCollection<ListBoxItemViewModel> LeftItems { get; private set; }

        /// <summary>
        /// 右側のリストボックスに表示するアイテム
        /// </summary>
        public ObservableCollection<ListBoxItemViewModel> RightItems { get; private set; }

        /// <summary>
        /// 左側のリストボックスで選択されているアイテム
        /// </summary>
        public ListBoxItemViewModel LeftSelectedItem
        {
            get
            {
                return this.leftSelectedItem;
            }

            set
            {
                this.leftSelectedItem = value;
                base.NotifyPropertyChanged("LeftSelectedItem");
            }
        }

        /// <summary>
        /// 右側のリストボックスで選択されているアイテム
        /// </summary>
        public ListBoxItemViewModel RightSelectedItem
        {
            get
            {
                return this.rightSelectedItem;
            }

            set
            {
                this.rightSelectedItem = value;
                base.NotifyPropertyChanged("RightSelectedItem");
            }
        }

        /// <summary>
        /// 右側のリストボックスで選択されているアイテムを左側のリストボックスへ移動
        /// </summary>
        public void MoveLeft()
        {
            if (this.RightSelectedItem == null)
            {
                return;
            }
            this.LeftItems.Add(this.RightSelectedItem);
            this.RightItems.Remove(this.RightSelectedItem);
        }

        /// <summary>
        /// 左側のリストボックスで選択されているアイテムを右側のリストボックスへ移動
        /// </summary>
        public void MoveRight()
        {
            if (this.LeftSelectedItem == null)
            {
                return;
            }
            this.RightItems.Add(this.LeftSelectedItem);
            this.LeftItems.Remove(this.LeftSelectedItem);
        }
    }
}

2つのリストと、選択されたアイテムを保持しておくプロパティと、選択されたアイテムを左から右、右から左に移動させるメソッドがあるだけのシンプルな作りです。ListBoxItemViewModelクラスは、以下のようなドンガラだけのクラスです。

namespace ListBoxItemMove.ViewModels
{
    public class ListBoxItemViewModel : ViewModelBase
    {
    }
}

こいつをバインドさせるViewをデザインします。

ListBoxのItemTemplateには赤い矩形(Border)を表示するようにしてみました。そして、ListBoxのItemsPanelにStackPanelを設定して、StackPanelにFluidMoveBehaviorをセットします。こいつはChildrenをターゲットにしてTagプロパティをDataContextにしておきます。

そして、ButtonにはViewModelのMoveLeftとMoveRightメソッドをCallMethodActionを使って関連付けます。


XAMLは以下のようになります。

<UserControl
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	xmlns:local="clr-namespace:ListBoxItemMove.ViewModels"
	xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
	mc:Ignorable="d"
	x:Class="ListBoxItemMove.ListMoveView"
	d:DesignWidth="640" d:DesignHeight="480">
	<UserControl.Resources>
		<local:ListMoveViewModel x:Key="TestViewModelDataSource" />
		<ItemsPanelTemplate x:Key="ItemsPanelTemplate1">
			<StackPanel>
				<i:Interaction.Behaviors>
					<ei:FluidMoveBehavior Tag="DataContext" AppliesTo="Children">
						<ei:FluidMoveBehavior.EaseY>
							<CircleEase EasingMode="EaseOut"/>
						</ei:FluidMoveBehavior.EaseY>
						<ei:FluidMoveBehavior.EaseX>
							<CircleEase EasingMode="EaseOut"/>
						</ei:FluidMoveBehavior.EaseX>
					</ei:FluidMoveBehavior>
				</i:Interaction.Behaviors>
			</StackPanel>
		</ItemsPanelTemplate>
		<DataTemplate x:Key="DataTemplate1">
			<Grid>
				<Border BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Left" Height="75" VerticalAlignment="Top" Width="75" CornerRadius="10">
					<Border.Background>
						<LinearGradientBrush EndPoint="0.987,0.96" StartPoint="0.053,0.04">
							<GradientStop Color="Red" Offset="0"/>
							<GradientStop Color="#FFFBE3E3" Offset="0.996"/>
							<GradientStop Color="#FFFF8787" Offset="0.478"/>
						</LinearGradientBrush>
					</Border.Background>
				</Border>
			</Grid>
		</DataTemplate>
	</UserControl.Resources>

	<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource TestViewModelDataSource}}">
		<Grid.ColumnDefinitions>
			<ColumnDefinition/>
			<ColumnDefinition Width="Auto" MinWidth="85"/>
			<ColumnDefinition/>
		</Grid.ColumnDefinitions>
		<ListBox Margin="5" ItemsSource="{Binding LeftItems}" SelectedItem="{Binding LeftSelectedItem, Mode=TwoWay}" ItemsPanel="{StaticResource ItemsPanelTemplate1}" ItemTemplate="{StaticResource DataTemplate1}"/>
		<ListBox Grid.Column="2" Margin="5" ItemsSource="{Binding RightItems}" SelectedItem="{Binding RightSelectedItem, Mode=TwoWay}" ItemsPanel="{StaticResource ItemsPanelTemplate1}" ItemTemplate="{StaticResource DataTemplate1}"/>
		<StackPanel Grid.Column="1" Margin="5" VerticalAlignment="Center">
			<Button Content="&gt;&gt;">
				<i:Interaction.Triggers>
					<i:EventTrigger EventName="Click">
						<ei:CallMethodAction TargetObject="{Binding Mode=OneWay}" MethodName="MoveRight"/>
					</i:EventTrigger>
				</i:Interaction.Triggers>
			</Button>
			<Button Content="&lt;&lt;">
				<i:Interaction.Triggers>
					<i:EventTrigger EventName="Click">
						<ei:CallMethodAction TargetObject="{Binding Mode=OneWay}" MethodName="MoveLeft"/>
					</i:EventTrigger>
				</i:Interaction.Triggers>
			</Button>
		</StackPanel>
	</Grid>
</UserControl>

実行したときの動作は、以下のようになります。こんなの自分で作れる日が来るとは思わなかったです。Windows Formだと凄い大変なんだろうなぁ。

プロジェクトは、以下からダウンロードできます。
ListBoxItemMove.zip