using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; namespace Hawkeye.VisionBuilder.Features.ImagesStrip.Components { /// /// A virtualized image list control that efficiently displays image thumbnails /// by loading only the images in the current window (managed by ImagesViewModel). /// public partial class VirtualizedImageList : UserControl { private ImagesViewModel _viewModel; private Dictionary _thumbnailControls = new Dictionary(); private bool _isUpdating = false; /// /// Gets or sets the ImagesViewModel that manages the image data and windowing logic. /// public ImagesViewModel ViewModel { get => _viewModel; set { if (_viewModel != null) { // Unsubscribe from old view model _viewModel.ImagesAdded -= OnImagesAdded; _viewModel.ImagesRemoved -= OnImagesRemoved; _viewModel.CurrentImageChanged -= OnCurrentImageChanged; _viewModel.ImagesCleared -= OnImagesCleared; } _viewModel = value; if (_viewModel != null) { // Subscribe to new view model _viewModel.ImagesAdded += OnImagesAdded; _viewModel.ImagesRemoved += OnImagesRemoved; _viewModel.CurrentImageChanged += OnCurrentImageChanged; _viewModel.ImagesCleared += OnImagesCleared; // Load initial images RefreshImages(); } } } /// /// Event raised when a thumbnail is clicked by the user. /// public event EventHandler ImageSelected; public VirtualizedImageList() { InitializeComponent(); } private void OnImagesAdded(object sender, ImageAddedEventArgs[] e) { if (_isUpdating) return; _isUpdating = true; flowLayoutPanel.SuspendLayout(); try { foreach (var args in e.OrderBy(x => x.Index)) { AddThumbnail(args.Index, args.FilePath); } } finally { flowLayoutPanel.ResumeLayout(); _isUpdating = false; } UpdateCurrentSelection(); } private void OnImagesRemoved(object sender, ImageRemovedEventArgs[] e) { if (_isUpdating) return; _isUpdating = true; flowLayoutPanel.SuspendLayout(); try { foreach (var args in e.OrderByDescending(x => x.Index)) { RemoveThumbnail(args.Index); } } finally { flowLayoutPanel.ResumeLayout(); _isUpdating = false; } } private void OnCurrentImageChanged(object sender, CurrentImageChangedEventArgs e) { UpdateCurrentSelection(); ScrollToCurrentImage(); } private void OnImagesCleared(object sender, EventArgs e) { ClearAllThumbnails(); } private void AddThumbnail(int index, string filePath) { // Create new thumbnail control var thumbnail = new ImageThumbnailControl { ImagePath = filePath, Tag = index // Store the index in Tag for later reference }; thumbnail.ThumbnailClicked += OnThumbnailClicked; // Find the correct insertion position to maintain order int insertPosition = 0; foreach (Control control in flowLayoutPanel.Controls) { if (control.Tag is int controlIndex && controlIndex < index) { insertPosition++; } else { break; } } flowLayoutPanel.Controls.Add(thumbnail); flowLayoutPanel.Controls.SetChildIndex(thumbnail, insertPosition); _thumbnailControls[index] = thumbnail; } private void RemoveThumbnail(int index) { if (_thumbnailControls.TryGetValue(index, out var thumbnail)) { thumbnail.ThumbnailClicked -= OnThumbnailClicked; flowLayoutPanel.Controls.Remove(thumbnail); thumbnail.Dispose(); _thumbnailControls.Remove(index); } } private void ClearAllThumbnails() { _isUpdating = true; flowLayoutPanel.SuspendLayout(); try { foreach (var thumbnail in _thumbnailControls.Values) { thumbnail.ThumbnailClicked -= OnThumbnailClicked; thumbnail.Dispose(); } _thumbnailControls.Clear(); flowLayoutPanel.Controls.Clear(); } finally { flowLayoutPanel.ResumeLayout(); _isUpdating = false; } } private void UpdateCurrentSelection() { if (_viewModel == null) return; int currentIndex = _viewModel.CurrentIndex; // Update IsSelected property for all thumbnails foreach (var kvp in _thumbnailControls) { kvp.Value.IsSelected = (kvp.Key == currentIndex); } // Raise event for external listeners if (_thumbnailControls.TryGetValue(currentIndex, out var currentThumbnail)) { ImageSelected?.Invoke(this, currentThumbnail.ImagePath); } } private void ScrollToCurrentImage() { if (_viewModel == null) return; int currentIndex = _viewModel.CurrentIndex; if (_thumbnailControls.TryGetValue(currentIndex, out var thumbnail)) { // Scroll the thumbnail into view flowLayoutPanel.ScrollControlIntoView(thumbnail); } } private void OnThumbnailClicked(object sender, EventArgs e) { if (sender is ImageThumbnailControl thumbnail && _viewModel != null) { string imagePath = thumbnail.ImagePath; // Notify view model of selection _viewModel.SelectByPath(imagePath); // Raise event for external listeners ImageSelected?.Invoke(this, imagePath); } } private void RefreshImages() { if (_viewModel == null) return; ClearAllThumbnails(); _isUpdating = true; flowLayoutPanel.SuspendLayout(); try { // Add all currently loaded images foreach (var imageItem in _viewModel.LoadedImages.OrderBy(x => x.Index)) { AddThumbnail(imageItem.Index, imageItem.FilePath); } } finally { flowLayoutPanel.ResumeLayout(); _isUpdating = false; } UpdateCurrentSelection(); ScrollToCurrentImage(); } protected override void Dispose(bool disposing) { if (disposing) { if (_viewModel != null) { _viewModel.ImagesAdded -= OnImagesAdded; _viewModel.ImagesRemoved -= OnImagesRemoved; _viewModel.CurrentImageChanged -= OnCurrentImageChanged; _viewModel.ImagesCleared -= OnImagesCleared; } ClearAllThumbnails(); } base.Dispose(disposing); } } }