WPFなのにバインディング要素なしだけど、動けばいいんだよ
前回の
gogowaten.hatenablog.com
これを利用して作ったのが
これ
見た目の変化は、okとキャンセルボタンをつけただけ
FolderDialog.xaml
<Window xClass="_20191013_FolderDialog.FolderDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlnsx="http://schemas.microsoft.com/winfx/2006/xaml"
xmlnsd="http://schemas.microsoft.com/expression/blend/2008"
xmlnsmc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlnslocal="clr-namespace:_20191013_FolderDialog"
mcIgnorable="d"
Title="FolderDialog" Height="450" Width="400"
ResizeMode="CanResizeWithGrip"
WindowStartupLocation="CenterOwner"
WindowStyle="ToolWindow">
<Grid>
<GridRowDefinitions>
<RowDefinition/>
<RowDefinition Height="60"/>
</GridRowDefinitions>
<TreeView Name="Root"/>
<StackPanel GridRow="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>
</summary>
public partial class FolderDialog : Window
{
public FolderDialog(string folderPath, Window owner)
{
InitializeComponent();
this.Owner = owner;
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)
{
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; }
}
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;
}
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 イベント
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;
}
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;
if (info.GetDirectories().Length > 0)
{
Dummy = new TreeViewItem();
Items.Add(Dummy);
}
this.Expanded += (s, e) =>
{
if (IsAdd) return;
SubDirectorys = new ObservableCollection<DirectoryTreeItem>();
AddSubDirectory();
};
}
public void AddSubDirectory()
{
Items.Remove(Dummy);
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;
}
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;
}
}
}
}
使うとき
MainWindow.xaml
<Window xClass="_20191013_FolderDialog.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlnsx="http://schemas.microsoft.com/winfx/2006/xaml"
xmlnsd="http://schemas.microsoft.com/expression/blend/2008"
xmlnsmc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlnslocal="clr-namespace:_20191013_FolderDialog"
mcIgnorable="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>
</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.ShowDialog();
if (dialog.DialogResult == true)
{
TextBlockFullName.Text = dialog.GetFullPath();
}
}
}
}
フォルダ選択ボタンで
これは20行目でC:\Users\waten\Source\Repos\wpf_test2\20191010_TreeView3フォルダを指定した場合、そこまでのツリーを展開して表示
24行目のコメントを解除してフォルダ指定なしだと
フォルダ指定なしor存在しないフォルダパスしての場合は
アクセスできるドライブ一覧が表示される
適当なフォルダを選択して
ok押すと、ダイアログのウィンドウが閉じられて
MainWindowに取得したフォルダパスが表示された
FolderDialog.xaml
9行目、ResizeModeにCanResizeWithGripを指定すると、Windowの右下にサイズ変更用のグリップ⊿が表示される、なくてもいいけどサイズ変更しやすくなる
10行目、ウィンドウ表示するときの位置、CenterOwnerでオーナー指定されているウィンドウの中央に表示されるようになるみたい
11行目、ウィンドウの最小化ボタン、最大化ボタンを非表示
FolderDialog.xaml.cs
181行目からのDirectoryTreeItemクラスは前回からの引き継ぎ(コピペ)
変更箇所は
自身のサブフォルダ一覧を保持するように
186行目、public ObservableCollection<DirectoryTreeItem> SubDirectorys;//サブフォルダ用
を追加
アクセスできないフォルダは無視
サブフォルダ用のツリー作成時に、隠しフォルダとシステムフォルダだけを除外してたけど、アクセスできないフォルダも無視することにしたのが244行目からのtry
見えているんだけどアクセスできないフォルダがあって
この2つ、開こうとすると
エクスプローラーだとこうして注意が出て、続行を選択すればアクセスできるようになるけどアプリから普通にアクセスしようとするとエラーになるので、無視することにした
アクセスできないフォルダはDirectoryinfoクラスのGetDirectoriesでエラーになる
指定フォルがないときはドライブ一覧だけを表示
ドライブ名一覧はEnvironmentクラスのGetLogicalDrivesで取得、これにはアクセスできないドライブ名も混じっているので
tryで、作成エラーになったら無視することにした
仮想ドライブのアプリを使っていると状況によって、アクセスできないドライブ名が一時的にできるみたい
指定フォルダが在るときは、そこまでのツリーを作成して、展開して、選択状態にする
これが難しかった
74~129行目までがそれ
ルートになるドライブから指定フォルダまでのすべてのフォルダ名取得
C:\Users\waten\Source\Repos\wpf_test2\20191010_TreeView3
のとき
C:
Users
waten
Source
Repos
wpf_test2\20191010_TreeView3
を配列で取得
String.Splitをつかって\で分ければいいと思ったんだけど、ドライブ名のところでめんどくさいことになって、フルパスからDirectoryInfoを作って、そこからParentをたどって取得することになった
C:\Users\waten\Source\Repos\wpf_test2\20191010_TreeView3の場合
CドライブのサブフォルダからUsersを探して(88行目)
展開(91行目)、展開したときにサブフォルダのツリーが作成されるので
そのサブフォルダ群を取得(95行目)
83行目に戻って、目的のフォルダまで、繰り返し
指定フォルダを開く必要はないけどあったほうがいいかなあ、どう書けばいいかなあって書いてたら60行も余計にかかったけど、できた!
github.com