none
TreeViewコントロールで複数のTreeNodeを選択したい。 RRS feed

  • 質問

  • いつもお世話になっております。

    TreeViewコントロールで複数のTreeNodeを選択したいと思い、
    TreeView MultiSelectなどでネットで色々探したのですが解決出来ませんでした。

    やるべき事は
    1、ツリーノードの表示
    2、ノードの複数選択(削除&別ノードへの移動)
    3、ノード名の変更
    です。

    ネットを徘徊し、
    『CLMultiSelectTreeViewRelease1.0.1』と言うライブラリを発見し、
    やりたい事が実現出来そうだったのですがLabelEditフラグをtrueにすると
    ノードクリック時にスレッド絡みのハングアップがあり、使い物になりませんでした。

    色々調べても
    『そもそもTreeeViewで複数のノードを選択することが間違って・・・』
    と言った情報ばかりで困っております。

    何か解決策となりそうなご意見を頂けないでしょうか?

     

    2009年10月1日 16:52

回答

  • 色々調べても
    『そもそもTreeeViewで複数のノードを選択することが間違って・・・』
    と言った情報ばかりで困っております。
    実のところ、私も「複数のノード選択することが間違っているんじゃないか」と疑います。
    どうしてもそれを実現しなければならないといったことであったとしても、ニッチな部分になるので情報は少数か皆無になりがちです。

    もしかしたら、TreeView みたいなものをご自身で作ることになるかもしれません。(私見ですが、 TreeView ではできないと思った方が良さそう)
    求められていること(あるいは、求めていること)と、それを実現するためにかかる時間(費用)を見比べて十分にご検討ください。

    もしくは、仕様を若干調整することで、もう少し簡単に実現できる可能性もあるかもしれません。(求められていることが分からないので何とも言えません)
    解決した場合は、参考になった返信に「回答としてマーク」のボタンを利用して、回答に設定しましょう(複数に設定できます)。
    • 回答としてマーク zilch_1975 2009年10月4日 14:02
    2009年10月3日 10:25
    モデレータ
  • CodeProject にもまた別の Multi-select TreeView があるみたいですが。
    WinForm 的には複数選択は CheckBoxes プロパティ使うのが本道かしら。
    • 回答としてマーク zilch_1975 2009年10月4日 14:02
    2009年10月3日 13:54
  • 面倒なことをして複数選択しているように見せるよりもHongliangさんが書かれているように標準であるCheckBoxを表示させて複数選択させたほうが楽ですね。

    DrawModeをOwnerDrawTextにして、OnDrawNodeでNodeの色を変えて選択されている振りをさせて、複数選択できるTreeViewを作ってみました。
    ちょっとテストした限りでは複数選択、削除もラベル編集もできました。(スレッド絡みって何だろう)
    しかし選択されているNodeの移動の実装で、異なる階層が選択されている場合の移動はどういう形で移動させるとか考えると、すごく面倒だったので止めちゃいました。
    複数選択はアリだとしても、ParentNodeが全て同じNodeしか複数選択できないようにするなどの制限が無いと移動はやっかいなことになりますね。

    つくりかけ
    public class MultiSelectTreeview : TreeView
    {
        public MultiSelectTreeview()
            : base()
        {
            this.DrawMode = TreeViewDrawMode.OwnerDrawText;
        }
    
        private TreeNode lastSelctNode = null;
    
        protected override void OnBeforeSelect(TreeViewCancelEventArgs e)
        {
            lastSelctNode = base.SelectedNode;
            base.OnBeforeSelect(e);
        }
    
        protected override void OnAfterSelect(TreeViewEventArgs e)
        {
            if (e.Action != TreeViewAction.Expand)
            {
                if (Control.ModifierKeys == Keys.Control)
                {
                    if (!SelectedNodes.Contains(e.Node))
                    {
                        SelectedNodes.Add(e.Node);
                    }
                    else if (SelectedNodes.Count > 1)
                    {
                        SelectedNodes.Remove(e.Node);
                    }
                }
                else
                {
                    bool refresh = SelectedNodes.Count > 1;
                    SelectedNodes.Clear();
                    SelectedNodes.Add(e.Node);
                    if (refresh)
                    {
                        this.Refresh();
                    }
                }
            }
            base.OnAfterSelect(e);
        }
    
        protected override void OnDrawNode(DrawTreeNodeEventArgs e)
        {
            bool isSelectingNode = ((e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected);
            Brush backBrush;
            Color backColor;
            Color textColor;
    
            if (SelectedNodes.Contains(e.Node))
            {
                TreeNodeStates states = e.State | TreeNodeStates.Selected;
                e = new DrawTreeNodeEventArgs(e.Graphics , e.Node , e.Bounds , states);
                e.Graphics.FillRectangle(SystemBrushes.Highlight , e.Bounds);
                backBrush = SystemBrushes.Highlight;
                backColor = SystemColors.Highlight;
                textColor = SystemColors.HighlightText;
            }
            else
            {
                backBrush = new SolidBrush(e.Node.BackColor);
                backColor = e.Node.BackColor;
                if (e.Node.ForeColor.IsEmpty)
                {
                    textColor = this.ForeColor;
                }
                else
                {
                    textColor = e.Node.ForeColor;
                }
            }
    
            TextRenderer.DrawText(e.Graphics , e.Node.Text , this.Font , e.Bounds , textColor , backColor);
    
            if (isSelectingNode)
            {
                ControlPaint.DrawFocusRectangle(e.Graphics , e.Bounds);
            }
            base.OnDrawNode(e);
        }
    
        public MultiTreeNodeCollection SelectedNodes
        {
            get { return _SelectedNodes; }
        }
        private MultiTreeNodeCollection _SelectedNodes = new MultiTreeNodeCollection();
    
        public class MultiTreeNodeCollection : List<TreeNode>
        {
            public MultiTreeNodeCollection() : base() { }
            public MultiTreeNodeCollection(IEnumerable<TreeNode> nodes) : base(nodes) { }
        }
    }

    • 回答としてマーク zilch_1975 2009年10月5日 14:43
    2009年10月4日 7:14

すべての返信

  • 色々調べても
    『そもそもTreeeViewで複数のノードを選択することが間違って・・・』
    と言った情報ばかりで困っております。
    実のところ、私も「複数のノード選択することが間違っているんじゃないか」と疑います。
    どうしてもそれを実現しなければならないといったことであったとしても、ニッチな部分になるので情報は少数か皆無になりがちです。

    もしかしたら、TreeView みたいなものをご自身で作ることになるかもしれません。(私見ですが、 TreeView ではできないと思った方が良さそう)
    求められていること(あるいは、求めていること)と、それを実現するためにかかる時間(費用)を見比べて十分にご検討ください。

    もしくは、仕様を若干調整することで、もう少し簡単に実現できる可能性もあるかもしれません。(求められていることが分からないので何とも言えません)
    解決した場合は、参考になった返信に「回答としてマーク」のボタンを利用して、回答に設定しましょう(複数に設定できます)。
    • 回答としてマーク zilch_1975 2009年10月4日 14:02
    2009年10月3日 10:25
    モデレータ
  • CodeProject にもまた別の Multi-select TreeView があるみたいですが。
    WinForm 的には複数選択は CheckBoxes プロパティ使うのが本道かしら。
    • 回答としてマーク zilch_1975 2009年10月4日 14:02
    2009年10月3日 13:54
  • Azuleanさん
     本来やるべき事は
     『ノード』 に 『ある要素(情報)』 を持たせて、
     それを連結させることで階層構造を作り、編集するGUIツールです。
     簡単な例で言うとクラスにメンバを登録して、それぞれを結ぶことで階層構造を編集するような感じです。
     
     windows上のGUIツールであればなんでもいいのですが
     windowsの知識も乏しく、右往左往した結果、何とかTreeViewで実現できないかと思いました。
     
     
    2009年10月3日 16:57
  • 面倒なことをして複数選択しているように見せるよりもHongliangさんが書かれているように標準であるCheckBoxを表示させて複数選択させたほうが楽ですね。

    DrawModeをOwnerDrawTextにして、OnDrawNodeでNodeの色を変えて選択されている振りをさせて、複数選択できるTreeViewを作ってみました。
    ちょっとテストした限りでは複数選択、削除もラベル編集もできました。(スレッド絡みって何だろう)
    しかし選択されているNodeの移動の実装で、異なる階層が選択されている場合の移動はどういう形で移動させるとか考えると、すごく面倒だったので止めちゃいました。
    複数選択はアリだとしても、ParentNodeが全て同じNodeしか複数選択できないようにするなどの制限が無いと移動はやっかいなことになりますね。

    つくりかけ
    public class MultiSelectTreeview : TreeView
    {
        public MultiSelectTreeview()
            : base()
        {
            this.DrawMode = TreeViewDrawMode.OwnerDrawText;
        }
    
        private TreeNode lastSelctNode = null;
    
        protected override void OnBeforeSelect(TreeViewCancelEventArgs e)
        {
            lastSelctNode = base.SelectedNode;
            base.OnBeforeSelect(e);
        }
    
        protected override void OnAfterSelect(TreeViewEventArgs e)
        {
            if (e.Action != TreeViewAction.Expand)
            {
                if (Control.ModifierKeys == Keys.Control)
                {
                    if (!SelectedNodes.Contains(e.Node))
                    {
                        SelectedNodes.Add(e.Node);
                    }
                    else if (SelectedNodes.Count > 1)
                    {
                        SelectedNodes.Remove(e.Node);
                    }
                }
                else
                {
                    bool refresh = SelectedNodes.Count > 1;
                    SelectedNodes.Clear();
                    SelectedNodes.Add(e.Node);
                    if (refresh)
                    {
                        this.Refresh();
                    }
                }
            }
            base.OnAfterSelect(e);
        }
    
        protected override void OnDrawNode(DrawTreeNodeEventArgs e)
        {
            bool isSelectingNode = ((e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected);
            Brush backBrush;
            Color backColor;
            Color textColor;
    
            if (SelectedNodes.Contains(e.Node))
            {
                TreeNodeStates states = e.State | TreeNodeStates.Selected;
                e = new DrawTreeNodeEventArgs(e.Graphics , e.Node , e.Bounds , states);
                e.Graphics.FillRectangle(SystemBrushes.Highlight , e.Bounds);
                backBrush = SystemBrushes.Highlight;
                backColor = SystemColors.Highlight;
                textColor = SystemColors.HighlightText;
            }
            else
            {
                backBrush = new SolidBrush(e.Node.BackColor);
                backColor = e.Node.BackColor;
                if (e.Node.ForeColor.IsEmpty)
                {
                    textColor = this.ForeColor;
                }
                else
                {
                    textColor = e.Node.ForeColor;
                }
            }
    
            TextRenderer.DrawText(e.Graphics , e.Node.Text , this.Font , e.Bounds , textColor , backColor);
    
            if (isSelectingNode)
            {
                ControlPaint.DrawFocusRectangle(e.Graphics , e.Bounds);
            }
            base.OnDrawNode(e);
        }
    
        public MultiTreeNodeCollection SelectedNodes
        {
            get { return _SelectedNodes; }
        }
        private MultiTreeNodeCollection _SelectedNodes = new MultiTreeNodeCollection();
    
        public class MultiTreeNodeCollection : List<TreeNode>
        {
            public MultiTreeNodeCollection() : base() { }
            public MultiTreeNodeCollection(IEnumerable<TreeNode> nodes) : base(nodes) { }
        }
    }

    • 回答としてマーク zilch_1975 2009年10月5日 14:43
    2009年10月4日 7:14
  • Hongliangさん
     う、探し方が足りなかったようです。
     参考になりそうなものが沢山ありました。
     有難う御座いました。
    2009年10月4日 14:02
  •  gekkaさん
     
     とても参考になるサンプル有難うございました。
     CTRL+クリックで複数選択した後の解除時に
     BackColorの色が解除されるようにして
     SHIFT+クリック(lastSelctNodeから現在のクリックノードまで選択)に対応させれば、
     そのまま行けちゃいそうですね!(笑)
     是非、参考にさせて頂きます。
     
     仰る通り、
     『ParentNodeが全て同じNodeしか複数選択できないようにするなどの制限』
     は仕様的に見ても必須だと思っております。
     
     
     P.S.
      自作のプログラムでも悩んだのですが
      選択時(CTRL+クリック)にラベル編集にならないようにするには
      どのようにすれば旨く行きますか?

    2009年10月4日 14:30
  • SHIFT+クリック(lastSelctNodeから現在のクリックノードまで選択)に対応させれば

    これも階層をまたいだ選択をどうしようと考えて途中でやめました。

    選択時(CTRL+クリック)にラベル編集にならないようにするには
    どのようにすれば旨く行きますか?

    BeforeLabelEditイベントあるいはOnBeforeLabelEditで
    e.CancelEdit = ((Control.ModifierKeys & Keys.Control) == Keys.Control);
    としてキャンセルしてしまうのはどうでしょう。
    2009年10月4日 15:40
  • gekkaさん
     
     SHIFT+クリックは、同一階層のみにしようと考えています。
     OnBeforeLabelEdit・・・そうでした!
     OnAfterLabelEditで名前の重複チェックしてるのに!
     
     最後に、CTRL+クリックで選択解除した際のbackColorの件ですが
     ノード自体は選択からは外れているようなので描画更新のタイミングの問題でしょうか?
    2009年10月4日 16:23
  • ノード自体は選択からは外れているようなので描画更新のタイミングの問題でしょうか?
    クリックで解除しても素のTreeViewのSelectedNodeとして残っているからみたいです。こちらも解除して再描画させないといけないみたい。

    他にもSelectedNodeをクリックしてもOnBeforeSelectが発生しない(OnNodeMouseClickで対応)とか、思っていたよりもいろいろ対処しないといけないことがありそうです。
    #いじってる時間が無いのであんまり突っ込まないでくださいね
    2009年10月5日 13:19