WPF の ListBox で N 番目の要素にスタイルをあてる
◆ AlternationIndex/AlternationCount
◆ N が変化するなら MultiBinding で 2 つの値が一緒なら True 返す Converter を通す
◆ N が変化するなら MultiBinding で 2 つの値が一緒なら True 返す Converter を通す
ListBox で何番目かの要素にスタイルをあてたいときがあります
データはこういうの
XAML では ListBox に AlternationCount を設定しておき DataTrigger で AlternationIndex を参照します

AlternationIndex は要素のインデックスで 0 から順にひとつずつ増えていきます
AlternationCount の数ごとにリセットされるので AlternatonCount が 4 で要素が 10 あると 0,1,2,3,0,1,2,3,0,1 という AlternationIndex になります
AlternationCount に要素数より大きい数値を指定しておくと繰り返しが起きないので連番として使えます
ここでは 999 ですがちゃんとやるなら namespace にこれを指定しておいて
int の最大値を指定すれば
安心して連番にできます
999 だと要素数ふえると困りますしね
AlternationCount を 4 にした場合の実行結果はこうなります
左側の数値が 3 の次に 0 になっていますね

偶数番目とかに使えます
(CSS の nth-type-of のほうが使いやすいです)
Value のほうを変えたい要素の index に Binding すれば良さそうに思えます
ですが Value に対しては Binding ができないとエラーです
ちょっとめんどうですが MultiBinding と Converter を通して Binding プロパティの方に動的にかわる部分をまとめないといけないです
BindingData クラスに index プロパティを追加します
NotifyHelper はソースの変更通知するメソッドを使えるようにしているものです
特別なことはないので省略します
値が等しい場合に True を返すコンバータを作ります(用意されてそうなものだけど見当たらなかったです)
XAML の Trigger でこのコンバータを使います
こうすれば AlternationIndex と BindingData にある index が等しいときに True になり Trigger が実行されます
ListBox だとマウスオーバーしたら水色の枠と背景でわかりやすく表示してくれますが それと同じように全体に背景色を設定してみます
ItemTemplate の中ではなく ListBox の Resources に ListBoxItem のスタイルとして設定すればいいです
RelativeSource で ListBoxItem だったところを Self にしないといけないです

わずかに背景色のほうが小さいのは Border がない分です
それにしても XAML もコールバック地獄なみに波動拳してますね(笑
N が固定のとき
何番目というのが固定なときは単純ですデータはこういうの
private class BindingData
{
public List<Item> items { get; set; } = new List<Item>
{
new Item { name = "fawebae" },
new Item { name = "jnaikez" },
new Item { name = "nniq2la" },
new Item { name = "z9dkwol" },
new Item { name = "hik1kdz" },
new Item { name = "lpkm4ed" },
new Item { name = "kiijwee" },
new Item { name = "lpkm4ed" },
new Item { name = "kkkoaoe" },
new Item { name = "ddwafi1" },
};
}
public class Item
{
public string name { get; set; }
}
public MainWindow()
{
this.DataContext = new BindingData();
}
{
public List<Item> items { get; set; } = new List<Item>
{
new Item { name = "fawebae" },
new Item { name = "jnaikez" },
new Item { name = "nniq2la" },
new Item { name = "z9dkwol" },
new Item { name = "hik1kdz" },
new Item { name = "lpkm4ed" },
new Item { name = "kiijwee" },
new Item { name = "lpkm4ed" },
new Item { name = "kkkoaoe" },
new Item { name = "ddwafi1" },
};
}
public class Item
{
public string name { get; set; }
}
public MainWindow()
{
this.DataContext = new BindingData();
}
XAML では ListBox に AlternationCount を設定しておき DataTrigger で AlternationIndex を参照します
<ListBox ItemsSource="{Binding items}" AlternationCount="999">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=(ListBox.AlternationIndex)}" Value="3">
<Setter Property="Background" Value="LawnGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=(ListBox.AlternationIndex),StringFormat={}{0}:}" />
<TextBlock Text="{Binding name}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=(ListBox.AlternationIndex)}" Value="3">
<Setter Property="Background" Value="LawnGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=(ListBox.AlternationIndex),StringFormat={}{0}:}" />
<TextBlock Text="{Binding name}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>

AlternationIndex は要素のインデックスで 0 から順にひとつずつ増えていきます
AlternationCount の数ごとにリセットされるので AlternatonCount が 4 で要素が 10 あると 0,1,2,3,0,1,2,3,0,1 という AlternationIndex になります
AlternationCount に要素数より大きい数値を指定しておくと繰り返しが起きないので連番として使えます
ここでは 999 ですがちゃんとやるなら namespace にこれを指定しておいて
xmlns:s="clr-namespace:System;assembly=mscorlib"
int の最大値を指定すれば
AlternationCount="{x:Static s:Int32.MaxValue}"
安心して連番にできます
999 だと要素数ふえると困りますしね
AlternationCount を 4 にした場合の実行結果はこうなります
左側の数値が 3 の次に 0 になっていますね

偶数番目とかに使えます
(CSS の nth-type-of のほうが使いやすいです)
N が変化するとき
偶数番目などの使い方じゃなくて 選択中のものなど操作によってスタイル要素を変えたいときがあります<ListBox ItemsSource="{Binding items}" AlternationCount="{x:Static s:Int32.MaxValue}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=(ListBox.AlternationIndex)}" Value="{Binding index}">
<Setter Property="Background" Value="LawnGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=(ListBox.AlternationIndex),StringFormat={}{0}:}" />
<TextBlock Text="{Binding name}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=(ListBox.AlternationIndex)}" Value="{Binding index}">
<Setter Property="Background" Value="LawnGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=(ListBox.AlternationIndex),StringFormat={}{0}:}" />
<TextBlock Text="{Binding name}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
Value のほうを変えたい要素の index に Binding すれば良さそうに思えます
ですが Value に対しては Binding ができないとエラーです
ちょっとめんどうですが MultiBinding と Converter を通して Binding プロパティの方に動的にかわる部分をまとめないといけないです
BindingData クラスに index プロパティを追加します
private class BindingData : NotifyHelper
{
public List<Item> items { get; set; } = new List<Item>
{
new Item { name = "fawebae" },
new Item { name = "jnaikez" },
new Item { name = "nniq2la" },
new Item { name = "z9dkwol" },
new Item { name = "hik1kdz" },
new Item { name = "lpkm4ed" },
new Item { name = "kiijwee" },
new Item { name = "lpkm4ed" },
new Item { name = "kkkoaoe" },
new Item { name = "ddwafi1" },
};
private int _index = 0;
public int index
{
get { return this._index; }
set
{
this._index = value;
this.notify(nameof(index));
}
}
}
{
public List<Item> items { get; set; } = new List<Item>
{
new Item { name = "fawebae" },
new Item { name = "jnaikez" },
new Item { name = "nniq2la" },
new Item { name = "z9dkwol" },
new Item { name = "hik1kdz" },
new Item { name = "lpkm4ed" },
new Item { name = "kiijwee" },
new Item { name = "lpkm4ed" },
new Item { name = "kkkoaoe" },
new Item { name = "ddwafi1" },
};
private int _index = 0;
public int index
{
get { return this._index; }
set
{
this._index = value;
this.notify(nameof(index));
}
}
}
NotifyHelper はソースの変更通知するメソッドを使えるようにしているものです
特別なことはないので省略します
値が等しい場合に True を返すコンバータを作ります(用意されてそうなものだけど見当たらなかったです)
class AreEqualsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values.Length < 2)
{
return false;
}
foreach (var item in values.Skip(1))
{
if (!values[0].Equals(item))
{
return false;
}
}
return true;
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values.Length < 2)
{
return false;
}
foreach (var item in values.Skip(1))
{
if (!values[0].Equals(item))
{
return false;
}
}
return true;
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
XAML の Trigger でこのコンバータを使います
<ListBox ItemsSource="{Binding items}" AlternationCount="{x:Static s:Int32.MaxValue}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<local:AreEqualsConverter x:Key="equal_converter"/>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource equal_converter}">
<Binding RelativeSource="{RelativeSource AncestorType=ListBoxItem}" Path="(ListBox.AlternationIndex)" />
<Binding RelativeSource="{RelativeSource AncestorType=ListBox}" Path="DataContext.index" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="MistyRose"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=(ListBox.AlternationIndex),StringFormat={}{0}:}" />
<TextBlock Text="{Binding name}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<local:AreEqualsConverter x:Key="equal_converter"/>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource equal_converter}">
<Binding RelativeSource="{RelativeSource AncestorType=ListBoxItem}" Path="(ListBox.AlternationIndex)" />
<Binding RelativeSource="{RelativeSource AncestorType=ListBox}" Path="DataContext.index" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="MistyRose"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=(ListBox.AlternationIndex),StringFormat={}{0}:}" />
<TextBlock Text="{Binding name}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
こうすれば AlternationIndex と BindingData にある index が等しいときに True になり Trigger が実行されます
背景が全体になってない
今回のやりたいこととは関係ないですが このコードだと背景色が文字のところだけになっていて全体の背景色になっていませんListBox だとマウスオーバーしたら水色の枠と背景でわかりやすく表示してくれますが それと同じように全体に背景色を設定してみます
<ListBox ItemsSource="{Binding items}" AlternationCount="{x:Static s:Int32.MaxValue}">
<ListBox.Resources>
<local:AreEqualsConverter x:Key="equal_converter"/>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource equal_converter}">
<Binding RelativeSource="{RelativeSource Self}" Path="(ListBox.AlternationIndex)" />
<Binding RelativeSource="{RelativeSource AncestorType=ListBox}" Path="DataContext.index" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="MistyRose"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=(ListBox.AlternationIndex),StringFormat={}{0}:}" />
<TextBlock Text="{Binding name}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<ListBox.Resources>
<local:AreEqualsConverter x:Key="equal_converter"/>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource equal_converter}">
<Binding RelativeSource="{RelativeSource Self}" Path="(ListBox.AlternationIndex)" />
<Binding RelativeSource="{RelativeSource AncestorType=ListBox}" Path="DataContext.index" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="MistyRose"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=(ListBox.AlternationIndex),StringFormat={}{0}:}" />
<TextBlock Text="{Binding name}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
ItemTemplate の中ではなく ListBox の Resources に ListBoxItem のスタイルとして設定すればいいです
RelativeSource で ListBoxItem だったところを Self にしないといけないです

わずかに背景色のほうが小さいのは Border がない分です
それにしても XAML もコールバック地獄なみに波動拳してますね(笑
COMMENT
コメント一覧 (5)
-
- 2018/07/17 21:32
-
「背景が全体になってない」は、ずばりやりたいことなので是非参考にさせて頂きたいのですが
.xamlで以下のエラーが表示されどうしても駄目です。
名前 "AreEqualsConveter" は名前空間 "clr-namespace:XXXXX" に存在しません。
ちなみに「class AreEqualsConveter」はまるっとネームスペース"XXXXX"にコピペしているのですが・・。
-
- 2018/07/18 00:29
-
私の方のタイプミスですが 「AreEqualsConveter」 は r が抜けてました
もしかすると WPF c# 初心者さんが作ったものでは class 名か xaml の方ではちゃんと r をつけて 「AreEqualsConverter」 としてるのではないでしょうか?
そのせいで名前が違って存在しませんと言われてるのかもしれません
本文の方は揃ってはいますがミスの元なので直しておきますね
-
- 2018/07/18 00:47
-
それと このエラーはどのタイミングでているものでしょうか?
ビルド時ではなくデザイナの XAML の編集画面上ででているのなら一度ビルドすれば直ります
クラスを追加してもそれをビルドする前だとデザイナはその名前空間にあるクラスを把握できてないみたいでエラーが表示されます
-
- 2018/07/18 09:36
-
管理人様、
コメントバック、ありがとうございました。
ご指摘頂いた内容で変更→リビルドで問題解消しました!
-
- 2018/07/19 00:47
- 解決できたようで良かったです