지난 포스팅에서는 TreeView의 확장(Expand) 버튼을 클릭했을 때 이벤트를 ViewModel에서 수신받는 방법을 알아봤습니다. 이번에는 확장할 때 현재 노드(디렉토리)의 자식을 추가하는 기능을 추가합니다. 윈도우의 파일탐색기 역시 같은 방식을 사용하고 있습니다. 구현 시 고려해야 할 내용은 노드를 추가할 때 자식 노드를 가지고 있는지 미리 확인해야 한다는 점입니다.
✅TreeView: 확장 시 자식 Directory 추가
Drive 정보
프로그램 실행 시 최초에 Drive 정보를 가져와서 TreeView에 추가합니다.
ExplorerMainViewModel() 의 생성자에서 이를 수행합니다.
드라이브 정보를 읽어서 TreeView에 추가하는 코드는 아래와 같습니다.
private void AddDrives()
{
DriveInfo[] allDrives = DriveInfo.GetDrives();
DirectoryTree? tree = DirectoryTreeVM.DirectoryTreeCollection;
foreach (DriveInfo driveName in allDrives)
{
if (driveName.DriveType == DriveType.Fixed)
{
tree?.Root.AddChild(new DirectoryInfoViewModel() { Name = driveName.Name });
}
}
}
ExplorerMainViewModel 생성자는 아래와 같이 되겠죠.
public ExplorerMainViewModel()
{
_fileInfoCollection = new ObservableCollection<FileInfoViewModel>();
DirectoryTreeVM = new DirectoryTreeViewModel();
AddDrives();
}
실행하면
드라이브는 추가됐으나 확장 버튼이 없습니다. 자식 노드가 없기 때문에 당연한 결과입니다.
그러므로 새 노드를 추가할 때 자식 노드를 가지고 있는지 확인하는 작업도 추가되어야 합니다.
기존 함수의 수정도 조금 필요합니다.
노드 추가 시 자식 노드 유무 확인
DirectoryTreeNode 클래스의 AddChild() 함수에서 하위 디렉토리까지 추가하도록 수정합니다.
public void AddChild(DirectoryInfoViewModel data, bool isAddChildren = false)
{
DirectoryTreeNode child = new DirectoryTreeNode(data);
child.Parent = this;
_children.Add(child);
if (isAddChildren == true)
{
AddChildDirectories();
}
}
AddChildDirectories() 함수는 아래와 같습니다.
private void AddChildDirectories(DirectoryTreeNode node)
{
try
{
var directoryList = Directory.GetDirectories(node.Data.FullPath);
foreach (string dir in directoryList)
{
try
{
DirectoryInfo di = new DirectoryInfo(dir);
if (di.Attributes.HasFlag(FileAttributes.System))
continue;
node.AddChild(new DirectoryInfoViewModel() { Name = Path.GetFileName(dir), FullPath = dir });
}
catch (System.UnauthorizedAccessException)
{
continue;
}
}
}
catch (DirectoryNotFoundException ex)
{
}
catch (System.UnauthorizedAccessException)
{
node.AddChild(new DirectoryInfoViewModel() { Name = Path.GetFileName(node.Data.FullPath), FullPath = node.Data.FullPath });
}
}
try ~ catch 가 지저분하게 위치하고 있는데 UnauthorizedAccessException과 DirectoryNotFoundException 이 디렉토리권한에 따라서 발생하기 때문에 우선 이렇게 처리했습니다. 나중에 위 exception 들에 대한 처리가 가능할지 확인해 보겠습니다.
위 함수를 간단히 설명하면 특정 노드(디렉토리)를 인자로 받아서 그 노드의 하위 디렉토리를 추가하는 코드입니다.
UnauthorizedAccessException 이 발생했을 때는 하위 디렉토리를 확인하지 않고 현재 디렉토리만 추가합니다.
AddDrives() 함수는 아래 코드처럼 true를 추가합니다.
private void AddDrives()
{
...
tree?.Root.AddChild(new DirectoryInfoViewModel() { Name = driveName.Name }, true);
...
}
Expanding 이벤트 처리
Expanding 이벤트가 발생했을 때 역시 현재 디렉토리의 하위 디렉토리의 하위 디렉토리를 추가해야 합니다....
코드는 아래와 같습니다.
DriectoryTreeNode 클래스에 아래 멤버함수를 추가합니다.
public으로 DirectoryTreeViewModel에서 Expanding 이벤트가 발생했을 때 호출합니다.
public void AddChildDirectories()
{
foreach (var child in Children)
{
AddChildDirectories(child);
}
}
DirectoryTreeViewModel에서는
private void OnExpandingTreeViewItem(DirectoryTreeNode obj)
{
obj.AddChildDirectories();
}
여기까지 진행한 뒤 실행하면
동적으로 잘 생성된 긴 하는데 같은 폴더명이 두 개씩 붙습니다.
Expanding 이벤트가 발생할 때마다 계속 붙는 현상이 발생합니다.
브레이크 포인트를 잡아서 확인해 보니
현재 디렉토리뿐만 아니라 부모 디렉토리까지 Expanding 이벤트가 발생합니다.
버블링이 발생하기 때문으로 보입니다.
버블링과 터널링에 대해서는 다음 포스팅에서 따로 다루겠습니다.
버블링으로 인해 현재 디렉토리에서 부모 디렉토리 (버블링 개념으로는 상위 컨트롤)으로 이벤트가 계속 발생합니다.
이를 막기 위해서는 RoutedEventArgs의 Handled를 true로 만들어야 합니다.
일단 버블링 문제가 맞는지 확인하기 위해 Codebehind에서 처리하겠습니다.
<Style TargetType="TreeViewItem">
<Setter Property="expanders:SupplementaryInteraction.Triggers"
Value="{StaticResource expandingTrigger}"/>
<EventSetter Event="Expanded" Handler="TreeViewItem_Expanded"/>
</Style>
TreeViewItem의 Style에 EventSetter를 등록해서 Expanding(Expanded) 이벤트 발생 시 Codebehind의 TreeViewItem_Expanded에서 RoutedEventArgs를 처리하도록 합니다.
MainView.xaml.cs
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
e.Handled= true;
}
이제 정상적으로 하위 노드가 생성되고 있는 줄 알았는데 열었다 닫았다 하면 자식 노드의 자식 노드가 계속 추가되고 있습니다. 같은 이름일 때는 생성하지 않도록 수정합니다.
시간 관계상 다음 포스팅에서 처리하겠습니다.
조금 복잡해졌으니 오늘 작업한 소스코드를 올립니다.
✅TreeView: 확장 시 자식 Directory 추가 - 끝
관련 포스팅
추가 예정