none
ListBoxのItems.Addで文字を直接入力した時のセレクトがおかしくなる RRS feed

  • 質問

  • 下のコードを実行した時、ListBox内の"AAA"の文字のセレクトは正常ですが、"ZZZ"の文字のセレクトがへんです。

     

     

    using System;
    using System.IO;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Navigation;

    namespace listbox
    {
     public partial class Window1
     {
      public Window1()
      {
       this.InitializeComponent();
      }

      private void OnClick(object sender, RoutedEventArgs e)
      {
       ListBox ls = new ListBox();
       ls.SelectionMode = SelectionMode.Single;
       
       for( int i = 0; i < 3; i++ )
       {
        ListBoxItem li1 = new ListBoxItem();
        li1.Content = "AAA";
        ls.Items.Add(li1);
       }

       ls.Items.Add("ZZZ");
       ls.Items.Add("ZZZ");
       ls.Items.Add("ZZZ");
       
       ls.Name = "ls1";
       
       ls.Width = 100;
       ls.Height = 100;
       
       Grid gd = (Grid)this.FindName("LayoutRoot" );
       gd.Children.Add( ls );

      }
     }
    }

     

     

    2007年10月17日 9:11

すべての返信

  • コード ブロック

    foreach (object item in ls.Items)
    {
        Console.WriteLine(item.GetType().ToString());
    }
     

     

    を入れてやると,どういう型になっているか?

     

     

     

     

    cf.

    アイテムコンテナ(item container)は,
    この場合,ListBoxItem ですが,明示的に作成した場合は,

    論理ツリーに参加しますが,

    暗黙的に作成された場合は,

    アイテムコンテナ(この場合ListBoxItem)は,論理ツリーに参加しません。
    なので,

    明示的に作成した場合は,各繰り返しの中身の親は,ListBoxItemになるけど,

    暗黙的に作成された場合は,各繰り返しの中身の親は,ListBoxになります。
    プロパティ値の伝播において,ListBoxItemを経由しなくなるわけです。

    2007年10月17日 12:58
  • resありがとうございます。

     

    まあ、いいです。次のバージョンで修正されていれば。

     

     

    2007年10月18日 2:04
  • もしかしたら、仕様ではないかと。

    Itemsの中に、同じインスタンスが複数あると、選択がおかしくなるみたいです。

     

    "ZZZ"って、別々に書いてあってもインターン プールに入るので、同じインスタンスになりますよね。

    ループ中の"AAA"は、文字列自体は同じインスタンスだけど毎回newしているのでインスタンスが別になります。

     

    仕様だとすると、これが直ってくる確率は低いかと思います。

    2007年10月18日 2:42
  • 動的に文字列を追加しても,なるようですね。

     

    ただ,背景色の変更などをListBoxItem(のデフォルトのスタイル)が担当してるとしたら,

    それが論理ツリーの親にならないというのは,都合が悪そうですけどね。

     

    暗黙のアイテムコンテナの説明は以下にあります。

    cf.

    Controls Content Model Overview

    スタイルは,暗黙のアイテムコンテナにも適用されるけど,

    それが,親になっていないというのは,何かとまずいということでしょう。

    もちろん,ListBox内部で適切に処理すれば,いいんでしょうけど。

    (今は,ハッシュテーブル等の利用で同じになってしまってるとか,

    動的にインターンしてるとかかもしれませんが,

    ただ,Ctrl & クリック で消したりできるので...うーん,何かが変なのかもしれません。

    ListBox側のStringに対する特別な処理が拡張になってしまっているのかもしれません)

    でも,

    ListBox側でStringオブジェクトだけは特別に処理してしまったら/

    というかたぶん処理しているとしたら,

    せっかくの機能と表示の分離が,

    String型 の時だけ従来道理という変な仕様になってしまう気がするんですけどね。

    ListBox は,データを繰り返すものという機能のみ になってくれていないと。

    WPFの仕組み的に,

    Visual を持たないもの,この場合,String型 が,いきなり描画されるというのは違和感があります。

    なので,Addした時に,

    コード ブロック

           <ListBox Name="listBox2" Margin="10">
                    <TextBlock>string1</TextBlock>
                    <TextBlock>string1</TextBlock>
                    <TextBlock>string1</TextBlock>
                    <TextBlock>string1</TextBlock>
                    <TextBlock>string1</TextBlock>
            </ListBox>

     

    のように書いたときと同じ感じにしてくれるとか。

     

    フロードキュメント では,そのあたりがはっきりしていて,

    Visual を持たないもの,FrameworkContentElement は,

    Visual を持つものに入れないといけません。

     

    # VC#2008 Express Beta2 日本語版 でテストしています。

    2007年10月21日 15:48
  • StringをVisualにして表示することに関しては、ContentPresenterやContentControlのContentに文字列を

    渡しても何の問題もなく表示することから、そこら辺の処理を実装するだけで処理してるんじゃな

    いでしょうか ?

    要するに、文字列だけが特別なんではなく、Visualではない物は、ContentPresenterを通して表示する

    ことで、Stringだけを特別な扱いとはしていない。って言う実装なんではないでしょうかねぇ?

     

    今回のは、ItemContainerGenerator.ContainerFromItem辺りが悪さをする原因なんじゃないでしょうかね ?

    選択の変換等を何処が行っているのか ? とかもありそうですけど、itemから選択を変更するよう

    な場合、このContainerFromItemでcontainerを取って来て、IndexFromContainerでインデックスを・・・

    ってやると、多分、同じインスタンスが複数あった場合には、先頭のIndexしか取ってこれないでしょう。

    ItemContainerGeneratorの仕様が変わるか、使い方が変わるかしないと、この問題については解決でき

    ないのではないかと思っています。

     

    ItemsControlでは、ContenerやPanel等を切り離して実装できる仕組みになっているようで、それを繋ぎ

    合わせているのがItemContainerGeneratorの存在となっているので、使い方の変更も難しいのでは ?

    と予想します。

     

    ここまで書いて、今、ちょっと気になって調べてみたら、インスタンスの一致ではなく、Equalsで比較

    しているようです。

    ItemContainerGeneratorかなと思っていたんですが、ItemsControl.Items.IndexOfの方かも知れませんね。

    2007年10月22日 1:54
  • そういえば,(この場合は,暗黙のListBoxItemの) ContentPresenter が,
    .ToString() を呼び出しているだけでしたね。

    ListBox が String型に対してのみ特別なことをしているわけではないですね。

     

    2007年10月22日 7:49
  • ItemsControl の派生クラスの Selector において,
    選択した値を管理しておくときに,

    それ自身を主キーにして管理してしまっているので,
    同じものがあるとダメのようなことが,すでに 2007年1月地点で認識されてるようですね。

    Bug in ItemsControl?

    Selector uses the items themselves as the primary key for the SelectedItems collection, which means it gets confused when the same item (or two items that compare equal) appears in more than one place in the underlying collection.  This can happen even with reference equality semantics - all you need is a collection that contains the same item in more than one place.

     

    Selector stores the selected items in a hash table. 

     

    これは直らないかも。

     

     

    3.5用のヘルプにあるように,論理ツリーの関係で,

    暗黙のListBoxItemの場合,
    例えば,前景色が ListBoxItem のものでなく ListBox のものが
    伝播してしまうなどのことがらもあるので,

    ListBoxItem は,必須というところに落ち着くんでは。

     

    2007年10月24日 1:16
  • CLR via C# の 第5章 の最後のところにある Note での

    Object の GetHashCode() は,オーバーライドされていると,
    固有の値を返してこない恐れがあるので,
    RuntimeHelpers の GetHashCode( Object ) を使うと
    オブジェクトの ユニークID を得るのに役に立つ というのも,
    文字列のインターン(string interning) が絡んでくると,

    ItemsControl の問題だけでなく,

    文字列をHashTableやDictionaryなどで使う際は,
    注意が必要 ってことなんですね。

     

    追記:

    ここ RuntimeHelpers.GetHashCode メソッド (Object) にはないけど,

    3.5用の新しいMSDNライブラリには,
    Remarks として説明が追加されています。

    RuntimeHelpers.GetHashCode Method (Object)

    RuntimeHelpers.GetHashCode メソッド (Object) [2007.12.03 現在,まだ,プレリリース版]

     

    # AppDomain内の話で済むのなら,
    # RuntimeHelpers の GetHashCode( Object ) メソッドを
    # 一意の値を得るのに使えるのだけど,
    # 実は,文字列インターン というわなが待っているという話ですね。

    2007年12月3日 1:48