sliding window for vision builder image strip pannel
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user