質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.77%

ListBox(WPF)で各アイテムに連番を与える方法

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 3,670

kawauso

score 52

お世話になっております。

行いたいこと
ListBoxのitemに連番を振りたいと思っています。
ListBoxの並び替え(過去の質問)機能を実装しているので、順番が変われば値を更新したいと思っています。

行ったこと
こちらのItemsControl の各項目表示にインデックス値を使うを参考にAlternationCountプロパティを用いることで、
連番を振ることはできました。
しかし、2番目以降のものを一番上へと並び替えた際に、それの値がAlternationCountの最大値になってしまいます。

 (AlternationCount = 10)
(連番: アイテム名)

0: item1
1: item2
2: item3
3: item4

↓ (item2を一番上にもってくる)

9: item2
0: item1
1: item3
2: item4

解決策をご存知の方、よろしくお願いいたします。

追記:
立て続けに質問、申し訳ございません。
たとえば、下記のようなViewModelにIndexを振りたい場合はどのようにすればよろしいでしょうか。

public class MyItem: BindableBase
{
    public int Index { set; get; }
    public string ItemName { set; get; }
            :
}

private ObservableCollection<MyItem> MyItemCollection { set; get; } = new ObservableCollection<MyItem>()
{
    new MyItem{ ItemName="item1" },
    new MyItem{ ItemName="item2" },
    new MyItem{ ItemName="item3" },
    new MyItem{ ItemName="item4" },
    new MyItem{ ItemName="item5" }
};
<ListBox.ItemTemplate>

        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Index/>
                <TextBlock Text=" : " />
                <TextBlock Text="{Binding ItemName}" />
            </StackPanel>
        </DataTemplate>
</ListBox.ItemTemplate>

下記のように記述してみたところ、
<TextBlock Text="{Binding Path=Index, RelativeSource={RelativeSource AncestorType=ListBoxItem}, Converter={StaticResource ItemContainerToIndexConverter}}"/>

このようなエラーが出力されます。
System.Windows.Data Error: 40 : BindingExpression path error: 'Index' property not found on 'object' ''ListBoxItem' (Name='')'. BindingExpression:Path=Index; DataItem='ListBoxItem' (Name=''); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

リスト更新時に ListBoxItems.Reflesh() を呼ぶことでAlternationCountの最大値になってしまう現象は回避できるようです。

ですが、ListBoxの項目がスクロールするほど数が多いとうまくいかないことがあるようですので、別の方法として IValueConverter を使用した例を挙げます


このValueConverterは、入力されたListBoxItemが所属するListBoxでの位置を返します。

using System;
using System.Globalization;
using System.Windows.Controls;
using System.Windows.Data;

:

    public class ItemContainerToIndexConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var container = value as ContentControl;
            var itemsControl = ItemsControl.ItemsControlFromItemContainer(container);
            return itemsControl != null ? itemsControl.Items.IndexOf(container.DataContext) : -1;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

使用例は以下のようになります。
コレクションが変更されたら表示を最新状態にするようにObservableCollectoin<T>.CollectionChangedイベントを使用しています。

using System.Collections.ObjectModel;

:

    public partial class MainWindow : Window
    {
        public ObservableCollection<string> Collection { get; } = new ObservableCollection<string>()
        {
            "Item1",
            "Item2",
            "Item3",
            "Item4",
            "Item5",
        };

        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;
            this.Collection.CollectionChanged += Collection_CollectionChanged;
        }

        private void Collection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            this.listBox.Items.Refresh();
        }
    }
        <ListBox x:Name="listBox" ItemsSource="{Binding Collection}">
            <ListBox.Resources>
                <local:ItemContainerToIndexConverter x:Key="ItemContainerToIndexConverter"/>
            </ListBox.Resources>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Converter={StaticResource ItemContainerToIndexConverter}}"/>
                        <TextBlock Text=" : " />
                        <TextBlock Text="{Binding}" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

(追記)
データにIndexプロパティを持たせられるのであれば、ValueConverterも使用しない素直な実装になります。
以下の例では項目変更時に全てのIndexを更新するようにしていますが、項目を入れ替える時にその項目のIndexも入れ替える、という方法もあります。

    public partial class MainWindow : Window
    {
        public ObservableCollection<MyItem> Collection { get; } = new ObservableCollection<MyItem>()
        {
            new MyItem{ ItemName="item1" },
            new MyItem{ ItemName="item2" },
            new MyItem{ ItemName="item3" },
            new MyItem{ ItemName="item4" },
            new MyItem{ ItemName="item5" }
        };

        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;

            RefleshCollectionIndex();
            this.Collection.CollectionChanged += (s, e) => RefleshCollectionIndex();
        }

        public void RefleshCollectionIndex()
        {
            for (int i = 0; i < this.Collection.Count; ++i)
            {
                this.Collection[i].Index = i;
            }
        }
    }

    //
    public class MyItem : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }

        private int _index;
        public int Index
        {
            get { return _index; }
            set { if (_index != value) { _index = value; RaisePropertyChanged(); } }
        }

        private string _itemName;
        public string ItemName
        {
            get { return _itemName; }
            set { if (_itemName != value) { _itemName = value; RaisePropertyChanged(); } }
        }
    }

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/08/21 15:14

    回答ありがとうございます。
    ご提示いただいたサンプルコードは思い通りの振る舞いをしてくれました。非常に助かりました。

    質問文には書いていませんでしたが、ViewModelにIndexを割り振りたいと思っております。
    質問文に、コードと試してみたことを追記しました。
    もしよろしければ、ヒントでも構いませんので、お教えいただければ幸いです。

    キャンセル

  • 2017/08/21 17:39

    Indexプロパティを使用した例を追記しました。

    キャンセル

  • 2017/08/21 18:47

    回答ありがとうございます。
    行いたかった実装ができました。大変勉強になりました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.77%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る