273 lines
8.1 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|