午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

WPFでフォルダ選択ダイアログできた

WPFなのにバインディング要素なしだけど、動けばいいんだよ

 

前回の

gogowaten.hatenablog.com

これを利用して作ったのが

これ

f:id:gogowaten:20191013153804p:plain

フォルダ選択ダイアログ

見た目の変化は、okとキャンセルボタンをつけただけ

 

FolderDialog.xaml

<Window x:Class="_20191013_FolderDialog.FolderDialog"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_20191013_FolderDialog"
        mc:Ignorable="d"
        Title="FolderDialog" Height="450" Width="400"
        ResizeMode="CanResizeWithGrip"
        WindowStartupLocation="CenterOwner"
        WindowStyle="ToolWindow">
    <Grid>
    <Grid.RowDefinitions>
      <RowDefinition/>
      <RowDefinition Height="60"/>
    </Grid.RowDefinitions>

    <TreeView Name="Root"/>

    <StackPanel Grid.Row="1" Orientation="Horizontal" FlowDirection="RightToLeft">
      <Button Name="ButtonCancel" Content="Cancel" Width="100" Margin="40,10,10,10"/>
      <Button Name="ButtonOk" Content="Ok" Width="100" Margin="10"/>      
    </StackPanel>
  </Grid>
</Window>

 

 

FolderDialog.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

using System.IO;
using System.Collections.ObjectModel;


namespace _20191013_FolderDialog
{
    /// <summary>
    /// FolderDialog.xaml の相互作用ロジック
    /// </summary>
    public partial class FolderDialog : Window
    {
        public FolderDialog(string folderPath, Window owner)
        {
            InitializeComponent();
            this.Owner = owner;//確実に閉じるため、複数のdialogが作られると1個しか閉じられない
            this.KeyDown += FolderDialog_KeyDown;
            ButtonCancel.Click += ButtonCancel_Click;
            ButtonOk.Click += ButtonOk_Click;

            //ドライブ全部を表示
            string[] drives = Environment.GetLogicalDrives();
            for (int i = 0; i < drives.Length; i++)
            {
                AddNode(new DirectoryInfo(drives[i]));
            }

            //指定フォルダが在るときは、そこまで作成して展開
            if (folderPath != null)
            {
                if (Directory.Exists(folderPath))
                {
                    ExpandAll(new DirectoryInfo(folderPath));
                }
            }

        }

        public FolderDialog(Window owner) : this(null, owner)
        {

        }


        private bool AddNode(DirectoryInfo info)
        {
            //準備ができていないドライブならfalse
            try
            {
                Root.Items.Add(new DirectoryTreeItem(info));
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        #region 指定フォルダまで作成して展開
        //指定フォルダまですべて展開
        private void ExpandAll(DirectoryInfo info)
        {
            //ルートドライブ群取得
            ObservableCollection<DirectoryTreeItem> subTrees = GetDrives();

            //フォルダをさかのぼってすべてのフォルダ名取得
            List<DirectoryInfo> dirInfos = GetAllDirectoryInfo(info);
            //上から順番に展開していく
            DirectoryTreeItem subTree = null;
            for (int i = dirInfos.Count - 1; i >= 0; i--)
            {
                for (int ii = 0; ii < subTrees.Count; ii++)
                {
                    //名前一致で、そのツリーを展開
                    if (subTrees[ii].DirectoryInfo.Name == dirInfos[i].Name)
                    {
                        subTree = subTrees[ii];
                        subTree.IsExpanded = true;//ツリー展開、ここでサブフォルダが追加される
                        subTree.IsSelected = true;
                        subTree.BringIntoView();//見えるところまでスクロール
                        subTree.Focus();
                        subTrees = subTree.SubDirectorys;//次のサブフォルダ群
                        break;
                    }
                }
            }
            //指定フォルダのサブフォルダは展開しない(どっちでもいい)
            if (subTree != null) { subTree.IsExpanded = false; }
        }

        //ルート直下のTreeItemを取得
        private ObservableCollection<DirectoryTreeItem> GetDrives()
        {
            var subTrees = new ObservableCollection<DirectoryTreeItem>();
            for (int i = 0; i < Root.Items.Count; i++)
            {
                var item = (DirectoryTreeItem)Root.Items[i];
                subTrees.Add(item);
            }
            return subTrees;
        }

        //指定フォルダをさかのぼってルートドライブ群までのDirectoryInfo取得
        private List<DirectoryInfo> GetAllDirectoryInfo(DirectoryInfo info)
        {
            DirectoryInfo temp = info;
            var dir = new List<DirectoryInfo>();
            dir.Add(info);

            while (temp.Parent != null)
            {
                dir.Add(temp.Parent);
                temp = temp.Parent;
            }
            return dir;
        }
        public string GetFullPath()
        {
            return Root.SelectedItem.ToString();
        }
        private string[] GetDir(DirectoryInfo info)
        {
            return info.FullName.Split(Char.Parse("\\"));
        }

        #endregion


        #region イベント
        //エンターキーでResult
        private void FolderDialog_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter && Root.SelectedItem != null)
            {
                DialogResult = true;
            }
        }
        //キャンセルボタン
        private void ButtonCancel_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = false;
        }
        //okボタン
        private void ButtonOk_Click(object sender, RoutedEventArgs e)
        {
            if (Root.SelectedItem == null)
            {
                this.DialogResult = false;
            }
            else
            {
                this.DialogResult = true;
            }
        }


        private void ButtonTest_Click(object sender, RoutedEventArgs e)
        {

        }


        #endregion




        public class DirectoryTreeItem : TreeViewItem
        {
            public readonly System.IO.DirectoryInfo DirectoryInfo;
            private bool IsAdd;//サブフォルダを作成済みかどうか
            private TreeViewItem Dummy;//ダミーアイテム
            public ObservableCollection<DirectoryTreeItem> SubDirectorys;//サブフォルダ用

            public DirectoryTreeItem(System.IO.DirectoryInfo info)
            {
                DirectoryInfo = info;
                Header = info.Name;

                //サブフォルダが1つでもあれば
                if (info.GetDirectories().Length > 0)
                //展開できることを示す▷を表示するためにダミーのTreeViewItemを追加する
                {
                    Dummy = new TreeViewItem();
                    Items.Add(Dummy);
                }

                //イベント、ツリー展開時
                //サブフォルダを追加
                this.Expanded += (s, e) =>
                {
                    if (IsAdd) return;//追加済みなら何もしない
                                      //SubDirectoryInfos = new ObservableCollection<DirectoryInfo>();
                    SubDirectorys = new ObservableCollection<DirectoryTreeItem>();
                    AddSubDirectory();
                };


                ////フォルダ選択でサブフォルダ開閉
                //this.PreviewMouseLeftButtonDown += (s, e) =>
                //{
                //    var source = (DirectoryTreeItem)e.Source;
                //    if (source != s) return;
                //    //開閉
                //    source.IsExpanded = !IsExpanded;
                //    e.Handled = true;
                //};

            }



            //サブフォルダツリー追加
            public void AddSubDirectory()
            {
                Items.Remove(Dummy);//ダミーのTreeViewItemを削除

                //すべてのサブフォルダを追加
                System.IO.DirectoryInfo[] directories = DirectoryInfo.GetDirectories();
                for (int i = 0; i < directories.Length; i++)
                {
                    ////隠しフォルダ、システムフォルダは除外する
                    var fileAttributes = directories[i].Attributes;
                    if ((fileAttributes & System.IO.FileAttributes.Hidden) == System.IO.FileAttributes.Hidden ||
                            (fileAttributes & System.IO.FileAttributes.System) == System.IO.FileAttributes.System)
                    {
                        continue;
                    }

                    //サブフォルダにもアクセスできるフォルダのみItem追加
                    try
                    {
                        //これが通れば
                        directories[i].GetDirectories();
                        //追加
                        var item = new DirectoryTreeItem(directories[i]);
                        Items.Add(item);
                        SubDirectorys.Add(item);
                    }
                    catch (Exception)
                    {
                    }
                }
                IsAdd = true;//サブフォルダ作成済みフラグ
            }


            public override string ToString()
            {
                return DirectoryInfo.FullName;
            }
        }
    }
}
//    WPF TreeView – 展開されたブランチが見えるようにスクロールする方法 - コードログ
//https://codeday.me/jp/qa/20190128/169432.html

 

 

 

 

使うとき

MainWindow.xaml

<Window x:Class="_20191013_FolderDialog.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_20191013_FolderDialog"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="600">
    <Grid Margin="20">
    <StackPanel>
      <Button Content="フォルダ選択" Name="ButtonOpenFolderDialog"/>
      <TextBlock Name="TextBlockFullName"/>
    </StackPanel>
  </Grid>
</Window>

ダイアログを開くボタンと、取得したフォルダパスを表示するTextBlock追加

 

MainWindow.xaml.cs

using System.Windows;

namespace _20191013_FolderDialog
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            ButtonOpenFolderDialog.Click += ButtonOpenFolderDialog_Click;
        }

        private void ButtonOpenFolderDialog_Click(object sender, RoutedEventArgs e)
        {
            //フォルダ指定あり
            string folderPath;
            folderPath = @"C:\Users\waten\Source\Repos\wpf_test2\20191010_TreeView3";
            FolderDialog dialog = new FolderDialog(folderPath, this);

            //フォルダ指定なし
            //dialog = new FolderDialog(this);

            dialog.ShowDialog();
            if (dialog.DialogResult == true)
            {
                TextBlockFullName.Text = dialog.GetFullPath();
            }
            
        }
    }
}

 

f:id:gogowaten:20191013164117p:plain

MainWindow

フォルダ選択ボタンで

f:id:gogowaten:20191013164243p:plain

フォルダ指定あり

これは20行目でC:\Users\waten\Source\Repos\wpf_test2\20191010_TreeView3フォルダを指定した場合、そこまでのツリーを展開して表示

 

24行目のコメントを解除してフォルダ指定なしだと

f:id:gogowaten:20191013164533p:plain

フォルダ指定なし

フォルダ指定なしor存在しないフォルダパスしての場合は

アクセスできるドライブ一覧が表示される

 

適当なフォルダを選択して

f:id:gogowaten:20191013164803p:plain

フォルダ選択

ok押すと、ダイアログのウィンドウが閉じられて

 

f:id:gogowaten:20191013164908p:plain

取得したフォルダパス

MainWindowに取得したフォルダパスが表示された

 

 

 

 FolderDialog.xaml

f:id:gogowaten:20191013154742p:plain

Windowの設定

9行目、ResizeModeにCanResizeWithGripを指定すると、Windowの右下にサイズ変更用のグリップ⊿が表示される、なくてもいいけどサイズ変更しやすくなる

10行目、ウィンドウ表示するときの位置、CenterOwnerでオーナー指定されているウィンドウの中央に表示されるようになるみたい

11行目、ウィンドウの最小化ボタン、最大化ボタンを非表示

 

 

 

 

 

 

 FolderDialog.xaml.cs

181行目からのDirectoryTreeItemクラスは前回からの引き継ぎ(コピペ)

変更箇所は

自身のサブフォルダ一覧を保持するように

186行目、public ObservableCollection<DirectoryTreeItem> SubDirectorys;//サブフォルダ用

を追加

 

 

アクセスできないフォルダは無視

サブフォルダ用のツリー作成時に、隠しフォルダとシステムフォルダだけを除外してたけど、アクセスできないフォルダも無視することにしたのが244行目からのtry

見えているんだけどアクセスできないフォルダがあって

f:id:gogowaten:20191013160802p:plain

アクセスできないフォルダ

この2つ、開こうとすると

f:id:gogowaten:20191013160852p:plain

なんか注意される

エクスプローラーだとこうして注意が出て、続行を選択すればアクセスできるようになるけどアプリから普通にアクセスしようとするとエラーになるので、無視することにした

f:id:gogowaten:20191013161722p:plain

アクセスできないフォルダはDirectoryinfoクラスのGetDirectoriesでエラーになる

 

f:id:gogowaten:20191013162027p:plain

コンストラク

指定フォルがないときはドライブ一覧だけを表示

ドライブ名一覧はEnvironmentクラスのGetLogicalDrivesで取得、これにはアクセスできないドライブ名も混じっているので

f:id:gogowaten:20191013170006p:plain

ドライブ一覧追加

tryで、作成エラーになったら無視することにした

仮想ドライブのアプリを使っていると状況によって、アクセスできないドライブ名が一時的にできるみたい

 

 

 

 

指定フォルダが在るときは、そこまでのツリーを作成して、展開して、選択状態にする

これが難しかった

74~129行目までがそれ

f:id:gogowaten:20191013191526p:plain

指定フォルダがあるとき

ルートになるドライブから指定フォルダまでのすべてのフォルダ名取得

 

f:id:gogowaten:20191013170924p:plain

フォルダ名取得

C:\Users\waten\Source\Repos\wpf_test2\20191010_TreeView3

のとき

C:

Users

waten

Source

Repos

wpf_test2\20191010_TreeView3

を配列で取得

String.Splitをつかって\で分ければいいと思ったんだけど、ドライブ名のところでめんどくさいことになって、フルパスからDirectoryInfoを作って、そこからParentをたどって取得することになった

 

 

f:id:gogowaten:20191013192736p:plain

上から順にサブフォルダのツリーを作成して展開していく

C:\Users\waten\Source\Repos\wpf_test2\20191010_TreeView3の場合

CドライブのサブフォルダからUsersを探して(88行目)

展開(91行目)、展開したときにサブフォルダのツリーが作成されるので

そのサブフォルダ群を取得(95行目)

83行目に戻って、目的のフォルダまで、繰り返し

 

指定フォルダを開く必要はないけどあったほうがいいかなあ、どう書けばいいかなあって書いてたら60行も余計にかかったけど、できた!

 

 

github.com