Calender

S M T W T F S
   1234
567891011
12131415161718
19202122232425
262728293031 
<< August 2018 >>

Categories

Archives

Recent Entries

Recent Comment

Recent Trackback

w closet×JUGEM

-

WPF TreeViewを使ってみた(2)

@media print { body { margin: 2mm 9mm; } .original-url { display: none; } #article .float.left { float: left !important; } #article .float.right { float: right !important; } #article .float { margin-top: 0 !important; margin-bottom: 0 !important; } } WPF TreeViewを使ってみた(2)

WPF TreeViewを使ってみた(2)

前回はTreeViewの簡単な実装だけやってみた
今回は要素の取得まで実装してみたが、
取得方法を調べたり考えたりしてたら前回の実装方法と大分変わってしまった

作るもの

前回と同じような感じだが、エクスプローラー的なものにしてみる
2017-04-28_23h25_10.png

作ってみる

前回同様MVVM的に作る
※ReactivePropertyを使用
(参考:かずきのBlog@hatena/ReactiveProperty オーバービュー)

考えてみる

まずTreeViewのSelectedItemとかにプロパティをBindすればいいじゃん、と思ったら読み取り専用でBindできないことが判明
それじゃどうするか
TreeViewItemを継承させてSelectedイベントに選択したものを取得する処理を追加したらいいのでは?
ってことでやってみる

Model

まずはModel
といっても今回はこれがほぼすべて

public class Model_TreeViewItem:TreeViewItem
    {
        public DirectoryInfo _Directory { get; set; }
        private bool _Expanded { get; set; } = false;
        public ReactiveProperty<Model_TreeViewItem> _SelectionItem { get; set; } = new ReactiveProperty<Model_TreeViewItem>();

        public Model_TreeViewItem(string path)
        {
            this._Directory = new DirectoryInfo(path);
            if (_Directory.GetDirectories().Count() > 0)
            {
                this.Items.Add(new TreeViewItem());
                this.Expanded += Model_TreeViewItem_Expanded;
            }
            this.Header = CreateHeader();
            this.Selected += Model_TreeViewItem_Selected;
        }

        private void Model_TreeViewItem_Expanded(object sender, RoutedEventArgs e)
        {
            if (!_Expanded)
            {
                this.Items.Clear();
                foreach (DirectoryInfo dir in _Directory.GetDirectories())
                {
                    if (dir.Attributes == FileAttributes.Directory)
                    {
                        this.Items.Add(new Model_TreeViewItem(dir.FullName));
                    }
                }
                _Expanded = true;
            }
        }

        private StackPanel CreateHeader()
        {
            StackPanel sp = new StackPanel() { Orientation = Orientation.Horizontal };
            sp.Children.Add(new Image()
            {
                Source = new BitmapImage(new Uri(@"Resources\Folder.ico", UriKind.Relative)),
                Width = 15,
                Height = 18,
            });
            sp.Children.Add(new TextBlock() { Text = _Directory.Name });
            return sp;
        }

        private void Model_TreeViewItem_Selected(object sender, RoutedEventArgs e)
        {
            _SelectionItem.Value = (this.IsSelected) ? this : (Model_TreeViewItem)e.Source ;
        }
    }

いきなり長めのコードになってしまったので細かく解説する

前述したようにTreeViewItemを継承している
プロパティは以下の通り

_Directory
自身のフォルダ
_Expanded
一度でも展開したかどうか
_SelectionItem
選択しているオブジェクト

まず、コンストラクタで渡されたパスをもとにDirectoryInfoを作って、そのフォルダにサブフォルダがあるかを調べている
一度にすべてのフォルダを走査して子要素を追加すると処理に時間がかかってしまうのでここでは1階層下のもののみ走査している
子要素があるフォルダになにも追加しないと展開する▷マークが表示されないので、サブフォルダがあるものはItemsにダミーのTreeItemを追加している

そんでExpandedイベントでサブフォルダを取得してそれぞれをnewした自身のクラスをItemに追加している

今回はエクスプローラー風にフォルダ名の左にアイコンをつけたかったのでHeaderプロパティにStackPanelをセットしている
詳細は省く

あと、Selectedイベント
こいつが曲者でハマってしまった
こいつは子要素側で発生しても親要素に伝播して親要素でも同じイベントが発生する
なので単純に(Model_TreeViewItem)e.Sourceとやっただけでは取得できない
考えた結果、この伝播する動作を利用してルートの親要素にイベント発生源のオブジェクトを運ぶことにした
_SelectionItem.Value = (this.IsSelected) ? this : (Model_TreeViewItem)e.Source ;
では、_SelectionItemプロパティに、自身が選択状態であれば自身を、そうでなければイベント発生源をセットしている
これでルートの_SelectionItemプロパティにBindすれば選択しているオブジェクトが取得できるようになった

ViewModel

シンプル
ただのViewModel
Modelのコンストラクタにパスを渡してるだけ

    class ViewModel
    {
        public List<Model_TreeViewItem> VM { get; }

        public ViewModel()
        {
            VM = new List<Model_TreeViewItem>()
            {
                new Model_TreeViewItem(@"C:\Users\yoshiki\Desktop\てすと")
            };
        }
    }

View

Modelのおかげでこんだけで済む

        <TreeView x:Name="treeView" ItemsSource="{Binding VM}">

選択中のDirectoryはこんな感じでBindできる

<TextBlock Text="{Binding VM/_SelectionItem.Value._Directory.Name}" />

おまけ

ListViewにファイル一覧をアイコン付きで表示してみる場合
Converterを使えばよさげ
_SelectionItem.Value._DirectoryをBindしてConvertメソッドで受け取る

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DirectoryInfo dir = (DirectoryInfo)value;
            List<StackPanel> list = new List<StackPanel>();
            foreach(var file in dir.GetFiles())
            {
                StackPanel sp = new StackPanel() { Orientation = Orientation.Horizontal };
                System.Drawing.Icon icon = System.Drawing.Icon.ExtractAssociatedIcon(file.FullName);
                sp.Children.Add(new Image()
                {
                    Source= Imaging.CreateBitmapSourceFromHIcon(icon.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()),
                    Width = 15,
                    Height = 18,
                });
                sp.Children.Add(new TextBlock()
                {
                    Text = file.Name
                });
                list.Add(sp);
                icon.Dispose();
            }
            return list;
        }

ざっくり解説すると、受け取ったDirectoryInfo内のファイルを取得し、それぞれの関連付けられたアイコンとファイル名をStackPanelに詰め込んでそれをリストにして返している

まとめ

自分なりに考えて実装してみたら少しややこしくなった
思い付きでやってみたため間違っている可能性があるかも
この実装方法が正しいかはわからないが、Bindしたりするのはかなり楽になったと思う



iPadから送信
  • 2018.08.09 Thursday
  • 19:16

Comment
Send Comment








   
この記事のトラックバックURL
Trackback