From de6eae9d064d8641224911a9b00a038ac57e17d3 Mon Sep 17 00:00:00 2001 From: Stepan Pilipenko Date: Thu, 13 Nov 2025 22:09:34 +0300 Subject: [PATCH] collection --- MainPage.xaml | 28 +++--- MainPage.xaml.cs | 207 +++++++++++++++++++++++++++++------------ PanelCollectionView.cs | 59 ++++++++++++ 3 files changed, 221 insertions(+), 73 deletions(-) create mode 100644 PanelCollectionView.cs diff --git a/MainPage.xaml b/MainPage.xaml index f38dd28..0c0e962 100644 --- a/MainPage.xaml +++ b/MainPage.xaml @@ -27,22 +27,22 @@ LineBreakMode="MiddleTruncation" /> - - - + + - - - + + LeftItems { get; } = new(); public ObservableCollection RightItems { get; } = new(); private string _leftPath = string.Empty; private string _rightPath = string.Empty; + // Для управления выделением и фокусом + private Button? _currentFocusedButton; + private bool _isLeftPanelActive = true; + private int _leftSelectedIndex = -1; + private int _rightSelectedIndex = -1; + public string LeftPath { get => _leftPath; @@ -48,111 +53,195 @@ public partial class MainPage : ContentPage, INotifyPropertyChanged private FileSystemItem? _selectedLeftItem; private FileSystemItem? _selectedRightItem; + // Для двойного нажатия + private FileSystemItem? _lastClickedItem; + private bool? _lastIsLeftPanel; + public MainPage(IFileSystemService fileService) { InitializeComponent(); _fileService = fileService; BindingContext = this; + // Инициализация шаблонов + InitializeCollectionViews(); + var root = _fileService.GetRootPath(); LoadDirectory(root, true); LoadDirectory(root, false); + + // Устанавливаем фокус на страницу + this.Focus(); } - private void LoadDirectory(string path, bool isLeft) + private void InitializeCollectionViews() { - var items = _fileService.GetDirectoryContents(path).ToList(); - var panel = isLeft ? LeftPanel : RightPanel; - var targetItems = isLeft ? LeftItems : RightItems; - var currentPathProperty = isLeft ? nameof(_currentLeftPath) : nameof(_currentRightPath); + LeftPanel.ItemTemplate = PanelCollectionView.CreateItemTemplate(isLeftPanel: true, page: this); + RightPanel.ItemTemplate = PanelCollectionView.CreateItemTemplate(isLeftPanel: true, page: this); + } - // Очистка панели - panel.Clear(); - targetItems.Clear(); - // Заполнение - foreach (var item in items) - { - targetItems.Add(item); + protected override void OnHandlerChanged() + { + base.OnHandlerChanged(); + this.Focus(); + } - var button = new Button + public void HandleItemClick(FileSystemItem item, bool isLeftPanel) + { + if (_lastIsLeftPanel == isLeftPanel && _lastClickedItem == item) + { + // Двойной клик + if (isLeftPanel) + OnLeftItemDoubleTapped(item); + else + OnRightItemDoubleTapped(item); + } + else + { + // Одинарный клик - выделение + if (isLeftPanel) { - Text = item.DisplayText, - TextColor = Colors.Black, - Padding = new Thickness(10), - HeightRequest = 40, - BackgroundColor = Colors.Transparent, - BorderWidth = 0, - CommandParameter = item - }; - - button.Clicked += isLeft ? OnLeftItemActivated : OnRightItemActivated; - - // Добавляем визуальные состояния - var normal = new VisualState { Name = "Normal" }; - var focused = new VisualState { Name = "Focused" }; - focused.Setters.Add(new Setter { Property = Button.BorderColorProperty, Value = Colors.Black }); - focused.Setters.Add(new Setter { Property = Button.BorderWidthProperty, Value = 2 }); - - var pressed = new VisualState { Name = "Pressed" }; - pressed.Setters.Add(new Setter { Property = Button.BackgroundColorProperty, Value = Color.FromArgb("#e0e0e0") }); - - var commonStates = new VisualStateGroup { Name = "CommonStates" }; - commonStates.States.Add(normal); - commonStates.States.Add(focused); - commonStates.States.Add(pressed); - - var groups = new VisualStateGroupList(); - groups.Add(commonStates); - - VisualStateManager.SetVisualStateGroups(button, groups); - - panel.Add(button); + _isLeftPanelActive = true; + _leftSelectedIndex = LeftItems.IndexOf(item); + _selectedLeftItem = item; + _selectedRightItem = null; + } + else + { + _isLeftPanelActive = false; + _rightSelectedIndex = RightItems.IndexOf(item); + _selectedRightItem = item; + _selectedLeftItem = null; + } + UpdateVisualSelection(); } + _lastIsLeftPanel = isLeftPanel; + _lastClickedItem = item; + } + + private void LoadDirectory(string path, bool isLeft) + { + var items = _fileService.GetDirectoryContents(path).ToList(); - // Обновление пути if (isLeft) { + LeftItems.Clear(); + foreach (var item in items) LeftItems.Add(item); _currentLeftPath = path; LeftPath = path; + + // Сбрасываем выделение при загрузке новой директории + _leftSelectedIndex = items.Count > 0 ? 0 : -1; } else { + RightItems.Clear(); + foreach (var item in items) RightItems.Add(item); _currentRightPath = path; RightPath = path; + + _rightSelectedIndex = items.Count > 0 ? 0 : -1; } + + UpdateVisualSelection(); } - private void OnLeftItemActivated(object sender, EventArgs e) + // Обработчики выделения (вызываются при изменении Selection в CollectionView) + private void OnLeftSelectionChanged(object sender, SelectionChangedEventArgs e) { - var button = (Button)sender; - var item = (FileSystemItem)button.CommandParameter; + // Обновляем индексы при программном изменении selection + if (e.CurrentSelection.FirstOrDefault() is FileSystemItem selectedItem) + { + _leftSelectedIndex = LeftItems.IndexOf(selectedItem); + _isLeftPanelActive = true; + UpdateVisualSelection(); + } + } - _selectedLeftItem = item; - _selectedRightItem = null; + private void OnRightSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (e.CurrentSelection.FirstOrDefault() is FileSystemItem selectedItem) + { + _rightSelectedIndex = RightItems.IndexOf(selectedItem); + _isLeftPanelActive = false; + UpdateVisualSelection(); + } + } - if (item.IsDirectory) + private void UpdateVisualSelection() + { + //Сбрасываем предыдущий фокус + if (_currentFocusedButton != null) { - LoadDirectory(item.FullName, true); + VisualStateManager.GoToState(_currentFocusedButton, "Normal"); + _currentFocusedButton = null; + } + + if (_isLeftPanelActive && _leftSelectedIndex >= 0 && _leftSelectedIndex < LeftItems.Count) + { + var item = LeftItems[_leftSelectedIndex]; + SetFocusToItem(LeftPanel, item); + _selectedLeftItem = item; + _selectedRightItem = null; + } + else if (!_isLeftPanelActive && _rightSelectedIndex >= 0 && _rightSelectedIndex < RightItems.Count) + { + var item = RightItems[_rightSelectedIndex]; + SetFocusToItem(RightPanel, item); + _selectedRightItem = item; _selectedLeftItem = null; } } - private void OnRightItemActivated(object sender, EventArgs e) + private void SetFocusToItem(CollectionView collectionView, FileSystemItem item) { - var button = (Button)sender; - var item = (FileSystemItem)button.CommandParameter; + // Используем Dispatcher чтобы дождаться рендеринга + Dispatcher.Dispatch(() => + { + var container = FindButtonContainer(collectionView, item); + if (container is Button button) + { + VisualStateManager.GoToState(button, "Focused"); + _currentFocusedButton = button; + _lastClickedItem = item; - _selectedRightItem = item; - _selectedLeftItem = null; + // Прокручиваем к выбранному элементу + collectionView.ScrollTo(item, position: ScrollToPosition.MakeVisible, animate: false); + } + }); + } + private Button? FindButtonContainer(CollectionView collectionView, FileSystemItem item) + { + // Ищем кнопку в логических дочерних элементах + foreach (var child in collectionView.LogicalChildren) + { + if (child is Button button && button.BindingContext == item) + { + return button; + } + } + return null; + } + + public void OnLeftItemDoubleTapped(FileSystemItem item) + { + if (item.IsDirectory) + { + LoadDirectory(item.FullName, true); + } + } + + public void OnRightItemDoubleTapped(FileSystemItem item) + { if (item.IsDirectory) { LoadDirectory(item.FullName, false); - _selectedRightItem = null; } } + // Остальные методы без изменений private async void OnCopyClicked(object sender, EventArgs e) { await ProcessFileOperation(async (src, destDir) => diff --git a/PanelCollectionView.cs b/PanelCollectionView.cs new file mode 100644 index 0000000..7c7b9b0 --- /dev/null +++ b/PanelCollectionView.cs @@ -0,0 +1,59 @@ +using Microsoft.Maui.Controls; + +namespace CommanderApp; + +public static class PanelCollectionView +{ + public static DataTemplate CreateItemTemplate(bool isLeftPanel, MainPage page) + { + return new DataTemplate(() => + { + var button = new Button + { + Padding = new Thickness(15, 8, 5, 8), // left, top, right, bottom + HeightRequest = 40, + BackgroundColor = Colors.Transparent, + BorderWidth = 0, + TextColor = Colors.Black, + HorizontalOptions = LayoutOptions.Fill, + }; + + button.SetBinding(Button.TextProperty, new Binding("DisplayText")); + button.SetBinding(Button.CommandParameterProperty, new Binding(".")); + + // Обработчик нажатия + button.Clicked += (s, e) => + { + if (s is Button btn && btn.CommandParameter is FileSystemItem item) + { + page.HandleItemClick(item, isLeftPanel); + } + }; + + // Визуальные состояния для фокуса + var visualStateGroups = new VisualStateGroupList(); + var commonStates = new VisualStateGroup { Name = "CommonStates" }; + + var normalState = new VisualState { Name = "Normal" }; + normalState.Setters.Add(new Setter { Property = Button.TextColorProperty, Value = Colors.Black }); + + var focusedState = new VisualState { Name = "Focused" }; + focusedState.Setters.Add(new Setter { Property = Button.BorderColorProperty, Value = Colors.Blue }); + focusedState.Setters.Add(new Setter { Property = Button.BorderWidthProperty, Value = 2 }); + + var pressedState = new VisualState { Name = "Pressed" }; + pressedState.Setters.Add(new Setter { Property = Button.TextColorProperty, Value = Colors.Black }); + pressedState.Setters.Add(new Setter { Property = Button.BackgroundColorProperty, Value = Color.FromArgb("#e0e0e0") }); + + commonStates.States.Add(normalState); + commonStates.States.Add(focusedState); + //commonStates.States.Add(pressedState); + visualStateGroups.Add(commonStates); + + VisualStateManager.SetVisualStateGroups(button, visualStateGroups); + + return button; + }); + } + +} \ No newline at end of file