none
TreeViewItem.IsSelectedで選択項目を変更した場合の動作不良 RRS feed

  • 質問

  • TreeViewの項目が選択された場合にある条件で選択状態を以前の選択に戻す処理を行っています。

    TreeViewのSelectedItemChangedイベントによりTreeViewItem.IsSelectedで以前の項目をtrue

    に設定しています。

    正常に動作しているように見えるのですが、再度キャンセルされた項目を選択してもSelectedItemChangedイベント

    が上がってきません。

    想像ですが、TreeViewItem.IsSelectedで変更してもTreeView側の選択項目が更新されていない湯に思われます。

    以下に検証用のコードを記述します。

    XAMLコード--------------------------------------

    <TreeView Margin="49,37,25,25" Name="treeView1" SelectedItemChanged="treeView1_SelectedItemChanged">
      <TreeViewItem Header="項目1" />
      <TreeViewItem Header="項目2" />
      <TreeViewItem Header="項目3" />
    </TreeView>

    イベントハンドラ---------------------------------------------

      private void treeView1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
      {
       Console.WriteLine("--------------------");
       TreeViewItem itemNew = e.NewValue as TreeViewItem;
       TreeViewItem itemOld = e.OldValue as TreeViewItem;

       if (itemNew == null) return;
       if (itemNew == itemOld) return;

       if (itemOld != null) Console.WriteLine("itemOld:" + itemOld.Header);
       Console.WriteLine("itemNew:" + itemNew.Header);

       if (itemNew.Header.ToString() == "項目3") {
        if (itemOld != null)
        {
         //itemNew.IsSelected = false;
         itemOld.IsSelected = true;
        }
       }
      }

    項目3が選択された場合に強制的に以前の項目をIsSelected = trueにより選択状態に戻しています。

    もう一度項目3を選択してもSelectedItemChangedEventが発生しません。

    他の項目を選択した場合はSelectedItemChangedEventが発生します。

    この症状の回避方法をご存知の方はアドバイスをお願いします。

    2010年12月2日 23:54

回答

  • 私の環境で試してみたところ、同期・非同期問わず、StackOverflowでおちました。

    IsSelectedの変更で、SelectedItemChangedに再入しているようです。

    残念ながら、こちらの症状も回避方法はわかりませんでした。

     

    .Netのソースを見る限り、TreeViewItem::IsSelectedプロパティを変更すると、

    private static void TreeViewItem::OnIsSelectedChanged  // PropertyChangedCallback

    private void TreeViewItem::Select

    internal void TreeView::ChangeSelection

    protected virtual void TreeView::OnSelectedItemChanged

    と呼ばれて行って、イベントが発生しています。

    ちなみに、.Net Framework4のソースでトレースしました。

    taka88さんの環境でも1度、トレースするかソースを眺めてみてはいかがでしょうか?

    • 回答としてマーク 山本春海 2010年12月17日 5:49
    2010年12月9日 1:23
  • すいません、私は .NET 4 で確認をしていました。
    いま .NET 3.5 SP1 で試したところ、指摘通り動作しないのを確認できました。

    .NET 4 と .NET 3.5 SP1 で選択周りの挙動が変わっているようですが、申し訳ないのですが私も理由はわかりませんでした。
    しかし、選択状態を戻すのではなくて、選択できなくすることでしたら TreeViewItem の PreviewMouseLeftDown イベントを処理することで可能です。

    // XAML
    <TreeViewItem Header="項目3" PreviewMouseLeftButtonDown="TreeViewItem_PreviewMouseLeftButtonDown" />

    // C#
    private void TreeViewItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        // イベント処理を止める
        e.Handled = true;
    }

    • 回答としてマーク 山本春海 2010年12月17日 5:49
    2010年12月10日 14:18

すべての返信

  • SelectedItemChanged などの選択状態変更イベントで選択状態を変える場合は、Dispatcher.BeginInvoke を利用して非同期で変更してあげると上手くいきます。(手元では選択状態がループしているようでした)

    //itemOld.IsSelected = true;
    Dispatcher.BeginInvoke((Action)(() => itemOld.IsSelected = true));
    

    上のように変更したところ、イベントが正しく呼び出されることを確認しました。

    2010年12月6日 16:34
  • しばやんさん、返信を頂いていたのに気づくのに遅れて申し訳ありません。

    結論から申しますと入れ替えても動作は同じでした。

    --------------------1、最初に項目1を選択
    itemNew:項目1
    --------------------2、項目3を選択
    itemOld:項目1
    itemNew:項目3
    --------------------3、これはIsSelectした結果自動的に発生したイベント
    itemOld:項目3
    itemNew:項目1
    --------------------4、項目2は選択できる
    itemOld:項目1
    itemNew:項目2

     3の後、項目3を選択してもイベントは発生しません。

    どうもTreeViewは項目3がカレントと判断しているように思われます。

    が、4で項目2を選択するとitemOldは項目1と正しくイベントが発生します。

    TreeViewの内部で不整合が起こっているように思われます。

    以上、宜しくお願い致します。

     

    2010年12月8日 1:29
  • 私の環境で試してみたところ、同期・非同期問わず、StackOverflowでおちました。

    IsSelectedの変更で、SelectedItemChangedに再入しているようです。

    残念ながら、こちらの症状も回避方法はわかりませんでした。

     

    .Netのソースを見る限り、TreeViewItem::IsSelectedプロパティを変更すると、

    private static void TreeViewItem::OnIsSelectedChanged  // PropertyChangedCallback

    private void TreeViewItem::Select

    internal void TreeView::ChangeSelection

    protected virtual void TreeView::OnSelectedItemChanged

    と呼ばれて行って、イベントが発生しています。

    ちなみに、.Net Framework4のソースでトレースしました。

    taka88さんの環境でも1度、トレースするかソースを眺めてみてはいかがでしょうか?

    • 回答としてマーク 山本春海 2010年12月17日 5:49
    2010年12月9日 1:23
  • -toyo-さん、返信ありがとうございます。

    当方の環境を記述していませんでした。 .Net Framework 3.5SP1です。

    IsSelectedを設定するとSelectedItemChangedイベントが発生することは確認しています。

    これが先の返信に記載しました「3、これはIsSelectした結果自動的に発生したイベント」です。

    -toyo-さんの環境ではStackOverflowでおちましたとのことですが「4」のところで項目3を選択すると発生するのでしょうか?

    はずかしながら.NETのソースが入手できることを知りませんでした。

    # 公開するという話は聞いていたのですが既に公開されてのですね。

    # いまだにXXXlector(XXXは伏字)するしかないと思ていました。

    他サイトで紹介されていた記事をもとに設定したて、モジュールウインドウに表示されるものシンボルを読み込んだのですがステップインできません。

    別途Window Formとして作成したプロジェクトではコントロールの内部にステップインできました。

    時間が取れた際にソースを眺めてみます。

    貴重な情報をありがとうございました。

     

    2010年12月9日 16:01
  • -toyo-さんの環境ではStackOverflowでおちましたとのことですが「4」のところで項目3を選択すると発生するのでしょうか?

    厳密には2のところですね。
    項目3を選択したときに、itemOld.IsSelected=trueとしていますが、ここで再度イベントがきます。(ここまでは一緒のようですね)
    で、一旦ハンドラを抜けた後、再度イベントがきます。itemOld:項目1、itemNew:項目3となっています(わけがわかりません)

    で、対象のフレームワークを3.5に変更してやってみたところ、落ちなくなりました...
    taka88さんと同じ状態になったようです。.net frameworkのソースを見比べてみると確かに微妙に違いますが、ここまで動きが違うとは...

    他サイトで紹介されていた記事をもとに設定したて、モジュールウインドウに表示されるものシンボルを読み込んだのですがステップインできません。

    すみません。表現が悪かったようです。
    実は私も.net formawork内のトレースはできていません。
    以前はできていたのですが、3.5SP1あたりからできてない気がします。
    今は、呼び出し履歴を見ながら、ソースを目で追っかけています。
    TreeViewItem::IsSelectedを変更した場合と、クリックした場合では、当然ながら経路が違ってきているようなので、その辺を中心に調べるしかないかもしれませんね。

    あと、項目3がクリックされたときに選択状態を戻すのではなく、そのような状況では項目3を選択できなくする(ディセーブルにする)とかいう方法で考えたほうが、いいんじゃないでしょうか?(許されるならば...)

    お力になれず申し訳ないですが、私の力で限界のようです。
    どなたか別の方アドバイスお願いします。
    特に、.net framework のトレースができる方教えてください。

    2010年12月10日 1:21
  • すいません、私は .NET 4 で確認をしていました。
    いま .NET 3.5 SP1 で試したところ、指摘通り動作しないのを確認できました。

    .NET 4 と .NET 3.5 SP1 で選択周りの挙動が変わっているようですが、申し訳ないのですが私も理由はわかりませんでした。
    しかし、選択状態を戻すのではなくて、選択できなくすることでしたら TreeViewItem の PreviewMouseLeftDown イベントを処理することで可能です。

    // XAML
    <TreeViewItem Header="項目3" PreviewMouseLeftButtonDown="TreeViewItem_PreviewMouseLeftButtonDown" />

    // C#
    private void TreeViewItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        // イベント処理を止める
        e.Handled = true;
    }

    • 回答としてマーク 山本春海 2010年12月17日 5:49
    2010年12月10日 14:18
  • こんにちは、taka88 さん。

    MSDN フォーラムのご利用ありがとうございます。オペレーターの山本です。

    参考になると思われる情報に、勝手ながら私のほうで回答としてマークさせていただきました。
    投稿いただいたみなさん、ありがとうございました。
     
    今後とも、MSDN フォーラムをよろしくお願いいたします。それでは。
                                                                                 
    マイクロソフト株式会社 フォーラム オペレーター 山本 春海

    2010年12月17日 5:49