Files
HawkeyeVision/Hawkeye.VisionBuilder/Features/ImagesStrip/Components/VirtualizedImageList.cs

273 lines
8.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace Hawkeye.VisionBuilder.Features.ImagesStrip.Components
{
/// <summary>
/// A virtualized image list control that efficiently displays image thumbnails
/// by loading only the images in the current window (managed by ImagesViewModel).
/// </summary>
public partial class VirtualizedImageList : UserControl
{
private ImagesViewModel _viewModel;
private Dictionary<int, ImageThumbnailControl> _thumbnailControls = new Dictionary<int, ImageThumbnailControl>();
private bool _isUpdating = false;
/// <summary>
/// Gets or sets the ImagesViewModel that manages the image data and windowing logic.
/// </summary>
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();
}
}
}
/// <summary>
/// Event raised when a thumbnail is clicked by the user.
/// </summary>
public event EventHandler<string> 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);
}
}
}