Ubisam-MergeTool/src/MergeHub/Views/FolderView.xaml.cs
ksj09406 196af0a913 D-7 끌어다 놓기를 화면에서 확인하고 세 군데를 고쳤다
경로 칸(TextBox)이 Drop 을 먼저 먹어 놓아도 아무 일이 없었다 - Preview 로 옮겼다.
파일 비교의 경로 글자는 Background 가 없어 놓을 자리 자체가 없었다.
칸에 놓아도 다시 견주지 않던 것을 DropFile / DropFolder 로 고쳤다.
파일을 하나씩 놓아 짝을 만들 수 있게 했다.
검색 결과 칸에 머리글을 넣고 줄 간격을 목록과 맞췄다.
2026-09-09 15:32:52 +09:00

188 lines
8.1 KiB
C#

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using MergeHub.ViewModels;
namespace MergeHub.Views
{
/// <summary>
/// 폴더 비교 탭. 트리 자체는 XAML 이 다 하고, 여기서는 TreeView 가
/// 바인딩으로 못 넘겨 주는 것(선택 항목·더블클릭·Enter)만 이어 준다.
/// </summary>
public partial class FolderView : UserControl
{
public FolderView()
{
InitializeComponent();
}
/// <summary>TreeView.SelectedItem 은 읽기 전용이라 바인딩할 수 없다. 여기서 옮겨 준다.</summary>
private void Tree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (DataContext is FolderViewModel vm) vm.SelectedNode = e.NewValue as FolderNode;
}
/// <summary>
/// 항목을 더블클릭하면 그 파일을 "파일 비교" 탭에서 연다.
///
/// 클릭한 자리에서 항목을 직접 집는다 - TreeView 의 선택은 첫 클릭에 따라오지만,
/// 펼침 화살표처럼 선택을 바꾸지 않는 자리를 두 번 누르면 <b>엉뚱한 항목</b>이 열린다.
/// </summary>
private void Tree_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (DataContext is not FolderViewModel vm) return;
if (NodeUnder(e.OriginalSource as DependencyObject) is not { } node) return;
// 폴더는 펼치고 접는 것이 기본 동작이다. 그대로 둔다.
if (node.IsDirectory) return;
vm.SelectedNode = node;
vm.OpenInDiffCore(node);
e.Handled = true;
}
/// <summary>Enter 로도 연다. 목록을 키보드로 훑는 사람에게는 이쪽이 빠르다.</summary>
private void Tree_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Enter) return;
if (DataContext is not FolderViewModel vm || vm.SelectedNode is not { } node) return;
if (node.IsDirectory) return;
vm.OpenInDiffCore(node);
e.Handled = true;
}
/// <summary>찾기 칸에서 Enter 를 누르면 바로 찾는다. 단추까지 가는 걸음을 줄인다.</summary>
private void SearchBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Enter) return;
if (DataContext is not FolderViewModel vm) return;
if (vm.SearchCommand.CanExecute(null)) vm.SearchCommand.Execute(null);
e.Handled = true;
}
/// <summary>찾은 줄을 더블클릭하면 그 자리에서 파일 비교를 연다.</summary>
private void HitList_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (DataContext is not FolderViewModel vm) return;
if (HitUnder(e.OriginalSource as DependencyObject) is not { } hit) return;
vm.SelectedHit = hit;
vm.OpenHitCore(hit);
e.Handled = true;
}
/// <summary>Enter 로도 연다. 결과를 키보드로 훑는 쪽이 빠르다.</summary>
private void HitList_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Enter) return;
if (DataContext is not FolderViewModel vm || vm.SelectedHit is not { } hit) return;
vm.OpenHitCore(hit);
e.Handled = true;
}
/// <summary>클릭이 떨어진 자리에서 위로 올라가며 그 줄의 결과를 찾는다.</summary>
private static Services.FolderSearchHit? HitUnder(DependencyObject? source)
{
while (source is not null)
{
if (source is ListBoxItem item) return item.DataContext as Services.FolderSearchHit;
source = System.Windows.Media.VisualTreeHelper.GetParent(source);
}
return null;
}
/// <summary>클릭이 떨어진 시각 요소에서 위로 올라가며 그 줄의 항목을 찾는다.</summary>
private static FolderNode? NodeUnder(DependencyObject? source)
{
while (source is not null)
{
if (source is TreeViewItem item) return item.DataContext as FolderNode;
if (source is FrameworkElement { DataContext: FolderNode node }) return node;
source = System.Windows.Media.VisualTreeHelper.GetParent(source);
}
return null;
}
// ================= 끌어다 놓기 =================
/// <summary>
/// 탐색기에서 끌어 온 것을 받을 수 있다고 알린다.
///
/// 이 표시가 없으면 커서에 <b>금지 표시</b>가 뜨고 놓아도 아무 일이 없다.
/// 파일·폴더가 아닌 것(글자·그림)은 받지 않는다.
/// </summary>
private static void AllowFileDrop(DragEventArgs e)
{
e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None;
e.Handled = true;
}
private void View_DragOver(object sender, DragEventArgs e) => AllowFileDrop(e);
/// <summary>
/// 화면 아무 데나 놓았을 때. 두 개를 놓으면 양쪽에 하나씩 들어간다.
///
/// 판단은 명령줄로 들어오는 길과 <b>같은 것</b>을 쓴다 - 폴더면 폴더 비교, 파일이면 파일 비교로
/// 탭까지 옮긴다. 폴더 화면에 파일을 놓았다고 아무 일도 안 하는 것은 불친절하다.
/// </summary>
private void View_Drop(object sender, DragEventArgs e)
{
if (DataContext is not FolderViewModel vm) return;
if (DroppedPaths(e) is not { Length: > 0 } paths) return;
vm.Workspace.RouteDroppedPaths(paths);
e.Handled = true;
}
/// <summary>
/// 왼쪽·오른쪽 칸에 바로 놓으면 <b>그 자리에만</b> 들어간다.
///
/// XAML 에서 <c>PreviewDrop</c> 으로 받는 까닭 - 경로 칸이 <c>TextBox</c> 라서
/// <c>TextBoxBase</c> 의 클래스 처리기가 <c>DragOver</c>·<c>Drop</c> 을 먼저 먹는다.
/// 그쪽은 글자 끌어다 놓기만 알아서 파일 목록은 버리고 <c>Handled</c> 로 막아 버린다.
/// 실제로 화면에서 칸 위에 놓아 봤더니 아무 일도 일어나지 않았다.
/// 터널링(Preview)은 클래스 처리기보다 먼저 도므로 이쪽이 먼저 받는다.
/// </summary>
private void LeftFolder_Drop(object sender, DragEventArgs e) => DropIntoSlot(e, left: true);
private void RightFolder_Drop(object sender, DragEventArgs e) => DropIntoSlot(e, left: false);
private void DropIntoSlot(DragEventArgs e, bool left)
{
if (DataContext is not FolderViewModel vm) return;
if (DroppedPaths(e) is not { Length: > 0 } paths) return;
// 파일을 놓았으면 그 파일이 든 폴더로 받는다 - 폴더 칸이니 그렇게 읽는 편이 자연스럽다.
var folder = Services.PathUtil.FullFolderPath(paths[0]);
if (folder.Length == 0) folder = Services.PathUtil.FolderOfFile(paths[0]);
if (folder.Length == 0) return;
// 두 개를 한꺼번에 놓았으면 나머지 하나는 반대쪽에 넣는다.
if (paths.Length > 1)
{
var other = Services.PathUtil.FullFolderPath(paths[1]);
if (other.Length == 0) other = Services.PathUtil.FolderOfFile(paths[1]);
if (other.Length > 0)
{
if (left) vm.RightFolder = other;
else vm.LeftFolder = other;
}
}
// 넣고 나서 양쪽이 다 찼으면 그 자리에서 견준다.
vm.DropFolder(folder, left);
e.Handled = true;
}
private static string[]? DroppedPaths(DragEventArgs e) =>
e.Data.GetDataPresent(DataFormats.FileDrop) ? e.Data.GetData(DataFormats.FileDrop) as string[] : null;
}
}