RaUI/Source/ryControls/Sheng.Winform.Controls/ShengListView/Layout/ShengListViewLayoutManager.cs
鑫Intel c3d4ddf574 ### 2021-07-29更新
------
#### MyDbV4   V3.0.2107.2901
- *.[新增]新增支持计算文件MD5。
- *.[新增]部分DataProvider功能移植到DbExtension里,增加扩展性。
- *.[新增]UnixTimeToDateTime和JSTimeToDateTime新增支持long参数。
- *.[合并]合并RyWeb项目到MyDb里。

#### ryControlsV4    V3.0.2107.2901
  -  *.[改进]优化减少大量IDE警告和消息。
2021-07-29 17:09:32 +08:00

1230 lines
46 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Diagnostics;
namespace Sheng.Winform.Controls
{
/// <summary>
/// 默认布局引擎以类似微软ListView的方式按行排列项
/// </summary>
public class ShengListViewLayoutManager
{
#region
/// <summary>
/// 框选的最短开始长度
/// </summary>
private const int SELECTION_TOLERANCE = 5;
/// <summary>
/// 框选时滚动条的自动滚动速度
/// </summary>
private const int AUTOSCROLL_VALUE = 10;
#endregion
#region
private ShengListView _imageListView;
//滚动条放到布局引擎中来定义,这样可以实现不同布局引擎完全不同的布局方式
//比如为图像分组显示的引擎可以同时显示多个水平滚动条
private readonly VScrollBar _vScrollBar = new VScrollBar();
private int ScrollBarWidth
{
get { return _vScrollBar.Width; }
}
/// <summary>
/// 用于鼠标框选时,框出了控件中项的显示范围时,自动滚动滚动条
/// </summary>
private readonly Timer _scrollTimer = new Timer();
/// <summary>
/// 自动滚动时,滚动值
/// </summary>
private int _autoScrollValue = 0;
/// <summary>
/// 是否处于框选状态中
/// </summary>
internal bool MouseSelecting { get; private set; }
/// <summary>
/// 内框offset内框offset是绝对相对于滚动条的
/// </summary>
private int _itemsAreaOffset = 0;
/// <summary>
/// 整个可显示项的边界的offset包括上下padding部分
/// </summary>
private int ItemsBoundsOffset
{
get
{
int offSet = _itemsAreaOffset - (ItemsArea.Location.Y - ItemsBounds.Location.Y);
if (offSet < 0)
offSet = 0;
return offSet;
}
}
/// <summary>
/// 鼠标按下时项区域边界的offset即 _itemsAreaOffset
/// 用于框选时,跨越可视部队画框
/// </summary>
private int _mouseItemsAreaOffset = 0;
///// <summary>
///// 当前所能显示的最大列数
///// </summary>
//private int _columnCount;
/// <summary>
/// 当前所能显示的最大行数
/// </summary>
private int _rowCount;
#endregion
#region
private ShengListViewRenderer _renderer;
/// <summary>
///
/// </summary>
protected ShengListViewRenderer Renderer
{
get { return _renderer; }
set { _renderer = value; }
}
/// <summary>
/// Gets whether the shift key is down.
/// </summary>
internal bool ShiftKey { get { return _imageListView.ShiftKey; } }
/// <summary>
/// Gets whether the control key is down.
/// </summary>
internal bool ControlKey { get { return _imageListView.ControlKey; } }
internal bool Focused { get { return _imageListView.Focused; } }
internal Rectangle SelectionRectangle { get; private set; }
internal bool Suspend
{
get { return _imageListView.Suspend; }
}
internal string DisplayMember
{
get
{
return _imageListView.DisplayMember;
}
}
//TODO:在出现滚动条后,计算错误
//临时调用用完成后改为internal
private int _firstPartiallyVisible;
/// <summary>
///
/// </summary>
public int FirstPartiallyVisible { get { return _firstPartiallyVisible; } }
private int _lastPartiallyVisible;
/// <summary>
///
/// </summary>
public int LastPartiallyVisible { get { return _lastPartiallyVisible; } }
private int _firstVisible;
/// <summary>
///
/// </summary>
public int FirstVisible { get { return _firstVisible; } }
private int _lastVisible;
/// <summary>
///
/// </summary>
public int LastVisible { get { return _lastVisible; } }
/// <summary>
/// 没有任何项
/// </summary>
public bool IsEmpty
{
get
{
return _imageListView.IsEmpty;
}
}
/// <summary>
/// 没有任何项处于可显示状态
/// </summary>
public bool NoneItemVisible
{
get
{
return _firstPartiallyVisible == -1 || _lastPartiallyVisible == -1;
}
}
/// <summary>
/// 整个控件区域
/// </summary>
public Rectangle ClientArea
{
get { return _imageListView.ClientRectangle; }
}
/// <summary>
/// 整个可用于绘制项的可视区域
/// 去除左右Padding部分去除滚动条
/// </summary>
public Rectangle ItemsBounds
{
get
{
int scrollBarWidth = this.ScrollBarWidth;
Rectangle clientArea = this.ClientArea;
Padding padding = _imageListView.Padding;
int x = padding.Left;
int width = clientArea.Width - padding.Left - padding.Right - scrollBarWidth;
Rectangle itemsBounds = new Rectangle(x, clientArea.Y, width, clientArea.Height);
return itemsBounds;
}
}
/// <summary>
/// 用于绘制项的区域
/// 考虑边距间隔大小和滚动条区域
/// </summary>
public Rectangle ItemsArea
{
//要考虑边距间隔大小和滚动条区域
get
{
int scrollBarWidth = this.ScrollBarWidth;
Rectangle clientArea = this.ClientArea;
Padding padding = _imageListView.Padding;
int x = padding.Left;
int y = padding.Top;
int width = clientArea.Width - padding.Left - padding.Right - scrollBarWidth;
int height = clientArea.Height - padding.Top - padding.Bottom;
//在最小化窗口后clientArea的尺寸为0
if (width < 0) width = 1;
if (height < 0) height = 1;
Rectangle itemsArea = new Rectangle(x, y, width, height);
return itemsArea;
}
}
//TODO:项的大小是不能固定死的,因为要考虑到操作系统的默认字体大小
private int _itemHeight = 24;
/// <summary>
/// 项的尺寸
/// 不放在ListView本身中定义而是放在LayoutManager中定义是因为不同的布局方式
/// 可能会是带长宽的Size做为itemSize比如平铺的方式
/// </summary>
internal int ItemHeight
{
get { return _itemHeight; }
set
{
_itemHeight = value;
_itemHeightWithMargin = _itemHeight + _margin;
}
}
private int _margin = 2;
/// <summary>
/// 项周围的边距
/// </summary>
public int Margin
{
get { return _margin; }
set
{
_margin = value;
_itemHeightWithMargin = _itemHeight + value;
}
}
private int _itemHeightWithMargin;
internal int ItemHeightWithMargin
{
get { return _itemHeightWithMargin; }
}
#endregion
#region debug
/// <summary>
///
/// </summary>
public int StartRow { get; set; }
/// <summary>
///
/// </summary>
public int EndRow { get; set; }
//public int StartCol { get; set; }
//public int EndCol { get; set; }
#endregion
#region
/// <summary>
///
/// </summary>
/// <param name="imageListView"></param>
public ShengListViewLayoutManager(ShengListView imageListView)
{
_imageListView = imageListView;
//_itemSize = new Size(ImageSize, ImageSize);
_itemHeightWithMargin = _itemHeight + _margin;
UpdateScrollBars();
_vScrollBar.Dock = DockStyle.Right;
_imageListView.Controls.Add(_vScrollBar);
_vScrollBar.Scroll += new ScrollEventHandler(VScrollBar_Scroll);
_vScrollBar.ValueChanged += new EventHandler(VScrollBar_ValueChanged);
_scrollTimer.Interval = 20;
_scrollTimer.Enabled = false;
_scrollTimer.Tick += new EventHandler(ScrollTimer_Tick);
//_renderer = new ListViewStandardRenderer(this);
//_renderer = new ListViewRenderer(this);
//_renderer = new ListViewDescriptiveRenderer(this);
//_renderer.Theme = _imageListView.Theme;
}
#endregion
#region
void VScrollBar_Scroll(object sender, ScrollEventArgs e)
{
//这里判断的原因是在鼠标操作滚动条时,即使只点一下,这里也会进来两次,原因不明
if (_itemsAreaOffset != e.NewValue)
{
_itemsAreaOffset = e.NewValue;
}
}
void VScrollBar_ValueChanged(object sender, EventArgs e)
{
//这里判断的原因是可能在 _vScrollBar_Scroll 事件里就赋值过了
if (_itemsAreaOffset != _vScrollBar.Value)
{
_itemsAreaOffset = _vScrollBar.Value;
}
_imageListView.SuspendLayout();
if (MouseSelecting)
{
//如果处于框选状态,在重新绘制控件之前需要计算新的 SelectionRectangle
//因为SelectionRectangle一开始是在MouseMove中计算的而当鼠标拉出控件边界时
//滚动条会继续滚动,如果此时保持鼠标不动,就要靠这里计算 SelectionRectangle 了
SelectionRectangle = CreateSelectionRectangle();
SelectItemsByRectangle(SelectionRectangle);
}
_imageListView.ResumeLayout(true);
CalculateVisibleItemsRange();
}
void ScrollTimer_Tick(object sender, EventArgs e)
{
//自动滚动之后,必须重新绘制控件
//借助 _vScrollBar_ValueChanged 事件实现
//另外,在自动滚动之后,光重绘还不行,必须重新计算 SelectionRectangle
//SelectionRectangle 一开始是在 MouseMove 中计算的,如果在自动滚动的过程中不移动鼠标
//那就靠 _vScrollBar_ValueChanged 计算了
int scrollValue = _vScrollBar.Value + _autoScrollValue;
if (scrollValue > _vScrollBar.Maximum)
_vScrollBar.Value = _vScrollBar.Maximum;
else if (scrollValue < 0)
_vScrollBar.Value = 0;
else
_vScrollBar.Value = scrollValue;
}
#endregion
#region
internal bool ContainerExtendMember(string member)
{
return _imageListView.ContainerExtendMember(member);
}
internal string GetExtendMember(string member)
{
return _imageListView.GetExtendMember(member);
}
internal string GetItemText(object itemValue)
{
return _imageListView.GetItemText(itemValue);
}
internal string GetItemText(object itemValue, string propertyName)
{
return _imageListView.GetItemText(itemValue, propertyName);
}
internal void OnItemsRemoved(List<ShengListViewItem> items)
{
_renderer.OnItemsRemoved(items);
}
internal void Dispose()
{
_imageListView.Controls.Remove(_vScrollBar);
_vScrollBar.Dispose();
_scrollTimer.Stop();
_scrollTimer.Enabled = false;
_scrollTimer.Dispose();
_renderer.Dispose();
_imageListView = null;
}
#endregion
#region
///// <summary>
///// 测量当前项应该的高度
///// </summary>
///// <returns></returns>
//public int MeasureItemHeight()
//{
// int textHeight = _imageListView.Font.Height;
// int height = _borderSize * 2 + textHeight + Padding.Top + Padding.Bottom;
// if (ShowDescription)
// height = height + textHeight + _textSpaceBetween;
// return height;
//}
/// <summary>
///
/// </summary>
/// <param name="graphics"></param>
public void Render(Graphics graphics)
{
//Debug.Write("Render(Graphics graphics)" + Environment.NewLine);
Update();
_renderer.Render(graphics);
}
/// <summary>
///
/// </summary>
/// <param name="graphics"></param>
/// <param name="item"></param>
public void RenderItem(Graphics graphics, ShengListViewItem item)
{
Debug.Assert(MouseSelecting == false, "MouseSelecting 为 " + MouseSelecting.ToString());
_renderer.RenderItem(graphics, item);
}
/// <summary>
/// 更新整个布局引擎的状态
/// </summary>
public void Update()
{
//Debug.Write("Update()" + Environment.NewLine);
CalculateGrid();
CalculateVisibleItemsRange();
UpdateScrollBars();
}
/// <summary>
/// 判断指定的项是否处于可见状态
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public ShengListViewItemVisibility IsItemVisible(ShengListViewItem item)
{
int itemIndex = _imageListView.Items.IndexOf(item);
if (_imageListView.Items.Count == 0)
return ShengListViewItemVisibility.NotVisible;
if (itemIndex < 0 || itemIndex > _imageListView.Items.Count - 1)
return ShengListViewItemVisibility.NotVisible;
if (itemIndex < _firstPartiallyVisible || itemIndex > _lastPartiallyVisible)
return ShengListViewItemVisibility.NotVisible;
else if (itemIndex >= _firstVisible && itemIndex <= _lastVisible)
return ShengListViewItemVisibility.Visible;
else
return ShengListViewItemVisibility.PartiallyVisible;
}
/// <summary>
/// 获取项的呈现区域
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public Rectangle GetItemBounds(ShengListViewItem item)
{
int index = _imageListView.Items.IndexOf(item);
return GetItemBounds(index);
}
/// <summary>
///
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public Rectangle GetItemBounds(int index)
{
Point location = ItemsArea.Location;
//测算滚动条向下滚动过的高度,做为初始 Y 坐标
location.Y += _margin / 2 - _itemsAreaOffset;
////itemIndex % _columnCount 得到项在第几列
////itemIndex / _columnCount 得到项在第几行
//location.X += _margin / 2 + (index % _columnCount) * _itemSizeWithMargin.Width;
////在初始 Y 坐标的基础上算出此项所在行计算出其应该在的Y坐标
location.Y += index * _itemHeightWithMargin;
location.X = ItemsArea.X;
return new Rectangle(location, new Size(ItemsArea.Width, _itemHeight));
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public List<ShengListViewItem> GetItems()
{
return _imageListView.Items.ToList();
}
/// <summary>
/// 获取当前所有可见项
/// </summary>
/// <returns></returns>
public List<ShengListViewItem> GetVisibleItems()
{
List<ShengListViewItem> items = new List<ShengListViewItem>();
for (int i = _firstPartiallyVisible; i <= _lastPartiallyVisible; i++)
{
items.Add(_imageListView.Items[i]);
}
return items;
}
/// <summary>
///
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
public ShengListViewHitInfo HitTest(Point point)
{
int itemIndex = -1;
//这里对XY坐标的减实现目的是平移坐标
//传进来的是相对控件左上角的坐标应平移为Padding后的内框左上角相对坐标
//但是要考虑滚动时上面的Padding也允许显示项
//X坐标直接向右平移即可
//Y坐标在没有滚动时直接向下平移如果存在滚动当滚动值小于顶部Padding时
//将Y坐标平移至项的最层端Y坐标上Padding-滚动值
//相对于Padding后的内框坐标系的坐标
Point relativePoint = point;
// Normalize to item area coordinates
relativePoint.X -= ItemsBounds.Left;
//y即平移量
//此处y坐标需要在绘制项的区域Padding后的区域的基础上考虑滚动条的offset
int y = ItemsArea.Top - _itemsAreaOffset;
if (y < 0) y = 0;
relativePoint.Y -= y;
if (relativePoint.X > 0 && relativePoint.Y > 0)
{
//当前点击的行和列的索引从0开始
//int col = relativePoint.X / _itemSizeWithMargin.Width;
int row = (relativePoint.Y + ItemsBoundsOffset) / _itemHeightWithMargin;
////判断点的是不是右边的空白,可能右边会有比较大的空白,又没大到够完整的显示一列图像
//bool isNotHitRightEmptyArea = col <= _columnCount - 1;
//if (isNotHitRightEmptyArea)
//{
// int index = row * _columnCount + col;
// //判断是不是点在图像区域内还是图像边上四周的Margin上
// Rectangle bounds = GetItemBounds(index);
// //判断点的坐标是不是在项的显示区域Bounds)内,要用相对整个控件的原始坐标
// //因为项的bounds是相对整个控件的
// bool isHitInItem = bounds.Contains(point.X, point.Y);
// if (isHitInItem)
// {
// itemIndex = index;
// }
//}
int index = row;
Rectangle bounds = GetItemBounds(index);
bool isHitInItem = bounds.Contains(point.X, point.Y);
if (isHitInItem)
{
itemIndex = index;
}
}
//是否点击在了有效项上
bool itemHit = itemIndex >= 0 && itemIndex < _imageListView.Items.Count;
ShengListViewHitInfo hitInfo = new ShengListViewHitInfo(itemIndex, itemHit);
return hitInfo;
}
#endregion
#region
/// <summary>
///
/// </summary>
/// <param name="e"></param>
public void MouseDown(MouseEventArgs e)
{
/*
* 如果按下的是鼠标右键
* 如果按在已选定的项上,只切换焦点,不改变选择
* 如果按在未选定项上,则切换为点的项为选中项
* 不考虑键盘按键
*/
_mouseItemsAreaOffset = _itemsAreaOffset;
List<ShengListViewItem> oldSelectedItems = _imageListView.GetSelectedItems();
ShengListViewHitInfo hitInfo = HitTest(e.Location);
if (hitInfo.ItemHit)
{
ShengListViewItem item = _imageListView.Items[hitInfo.ItemIndex];
List<ShengListViewItem> allItems = _imageListView.Items.ToList();
ShengListViewItem currentFocusedItem = _imageListView.FocusedItem;
if (_imageListView.LeftButton)
{
#region
if (_imageListView.AllowMultiSelection == false)
{
//如果点击的项就是当前选择的项
if (oldSelectedItems.Count > 0 && oldSelectedItems.Contains(item))
{
//判断Control键是否按下如果按下了Control键取消当前项的选择状态
if (_imageListView.ControlKey)
item.Selected = false;
}
else
{
//如果点击的项不是当前选择的项
//清除原选定项的选定状态
_imageListView.ClearSelect();
//设置新项为选定项
item.Selected = true;
}
}
#endregion
#region
//在同时按下 Control 和 Shift 的情况下,优先考虑 Shift
else
{
#region Shift
//判断是否按下了 Shift ,如果按下 Shift不考虑 Control 的状态
//也不用考虑是否点击的项是否是现有选定项之一
if (_imageListView.ShiftKey)
{
//如果当前存在具有输入焦点的项
if (currentFocusedItem != null && currentFocusedItem != item)
{
//连续选中从当前具有焦点的项至点击的项之间的所有项
//并将不在此范围内的项取消选中状态
int startIndex = Math.Min(currentFocusedItem.Index, hitInfo.ItemIndex);
int endIndex = Math.Max(currentFocusedItem.Index, hitInfo.ItemIndex);
foreach (var i in from c in oldSelectedItems where c.Index < startIndex || c.Index > endIndex select c)
{
i.Selected = false;
}
for (int i = startIndex; i <= endIndex; i++)
{
ShengListViewItem eachItem = allItems[i];
if (eachItem.Selected == false)
eachItem.Selected = true;
}
}
//如果当前不存在具有输入焦点的项
else
{
//清除原选定项的选定状态
_imageListView.ClearSelect();
item.Selected = true;
}
}
#endregion
#region Shift键没有处于按下状态
else
{
#region
if (oldSelectedItems.Count > 0 && oldSelectedItems.Contains(item))
{
//判断是否按下了 Control且没有按下 Shift
if (_imageListView.ControlKey && _imageListView.ShiftKey == false)
{
item.Selected = false;
}
//判断是否什么键都没有按下
if (_imageListView.ControlKey == false && _imageListView.ShiftKey == false)
{
//清除原选定项的选定状态
_imageListView.ClearSelect();
item.Selected = true;
}
}
#endregion
#region
else
{
//判断Control键是否按下如果按下了Control键则保持原有选择的情况把新项也设置为选中
//否则清除当前选择
if (_imageListView.ControlKey == false)
{
//清除原选定项的选定状态
_imageListView.ClearSelect();
}
item.Selected = true;
}
#endregion
}
#endregion
}
#endregion
}
else
{
//如果点在未选中的项上
if (oldSelectedItems.Contains(item) == false)
{
_imageListView.ClearSelect();
//设置新项为选定项
item.Selected = true;
}
}
#region
//设置新的输入焦点要放在后面处理因为在使用Shift连续选择时需要用到原具有焦点的项
if (currentFocusedItem == null || (currentFocusedItem != null && currentFocusedItem != item))
{
if (currentFocusedItem != null)
currentFocusedItem.Focused = false;
item.Focused = true;
}
#endregion
}
else
{
_imageListView.ClearSelect();
}
List<ShengListViewItem> newSelectedItems = _imageListView.GetSelectedItems();
if (oldSelectedItems.SequenceEqual(newSelectedItems) == false)
{
_imageListView.NeedPaint();
_imageListView.OnSelectedItemChanged();
}
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
public void MouseUp(MouseEventArgs e)
{
if (MouseSelecting)
{
MouseSelecting = false;
_scrollTimer.Enabled = false;
_autoScrollValue = 0;
_imageListView.NeedPaint();
}
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
public void MouseMove(MouseEventArgs e)
{
Point lastMouseDownLocation = _imageListView.LastMouseDownLocation;
#region
if (MouseSelecting)
{
//处于框选状态时,框内的项被选中不能在 MouseMove 事件中处理
//因为当鼠标移出控件,滚动条自动滚动时,不动鼠标,就不会触发 MouseMove 事件
#region
if (_scrollTimer.Enabled == false)
{
//需向下滚动
if (e.Y > ItemsBounds.Bottom)
{
_autoScrollValue = AUTOSCROLL_VALUE;
_scrollTimer.Enabled = true;
}
//需向上滚动
else if (e.Y < ItemsBounds.Top)
{
_autoScrollValue = -AUTOSCROLL_VALUE;
_scrollTimer.Enabled = true;
}
}
//鼠标从控件外面又回到了控件内,则停止自动滚动
else if (_scrollTimer.Enabled && ItemsBounds.Contains(e.Location))
{
_scrollTimer.Enabled = false;
_autoScrollValue = 0;
}
#endregion
//创建选择框 Rectangle
SelectionRectangle = CreateSelectionRectangle();
SelectItemsByRectangle(SelectionRectangle);
_imageListView.NeedPaint();
}
#endregion
#region
else
{
//如果允许多选,鼠标处于按下状态,且距上次点击的点,大于了最小框选开始尺寸
if (_imageListView.AllowMultiSelection && _imageListView.AnyMouseButton && (
(Math.Abs(e.Location.X - lastMouseDownLocation.X) > SELECTION_TOLERANCE ||
Math.Abs(e.Location.Y - lastMouseDownLocation.Y) > SELECTION_TOLERANCE)))
{
MouseSelecting = true;
}
}
#endregion
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
public void OnMouseWheel(MouseEventArgs e)
{
int offSet = _itemsAreaOffset;
int newYOffset = offSet - (e.Delta / SystemInformation.MouseWheelScrollDelta)
* _vScrollBar.SmallChange;
if (newYOffset > _vScrollBar.Maximum - _vScrollBar.LargeChange + 1)
newYOffset = _vScrollBar.Maximum - _vScrollBar.LargeChange + 1;
if (newYOffset < 0)
newYOffset = 0;
if (newYOffset < _vScrollBar.Minimum) newYOffset = _vScrollBar.Minimum;
if (newYOffset > _vScrollBar.Maximum) newYOffset = _vScrollBar.Maximum;
_vScrollBar.Value = newYOffset;
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
public void OnKeyDown(KeyEventArgs e)
{
// If the shift key or the control key is pressed and there is no focused item
// set the first item as the focused item.
if ((ShiftKey || ControlKey) && _imageListView.Items.Count != 0 &&
_imageListView.FocusedItem == null)
{
_imageListView.Items[0].Focused = true;
}
ShengListViewItem currentFocusedItem = _imageListView.FocusedItem;
if (_imageListView.Items.Count != 0)
{
int index = 0;
if (currentFocusedItem != null)
index = currentFocusedItem.Index;
int newindex = ApplyNavKey(index, e.KeyCode);
if (index != newindex)
{
#region index做选择
if (ControlKey)
{
// Just move the focus
}
else if (_imageListView.AllowMultiSelection && ShiftKey)
{
int startIndex = 0;
int endIndex = 0;
List<ShengListViewItem> selectedItems = _imageListView.GetSelectedItems();
if (selectedItems.Count != 0)
{
startIndex = selectedItems[0].Index;
endIndex = selectedItems[selectedItems.Count - 1].Index;
_imageListView.ClearSelect();
}
if (index == startIndex)
startIndex = newindex;
else if (index == endIndex)
endIndex = newindex;
for (int i = Math.Min(startIndex, endIndex); i <= Math.Max(startIndex, endIndex); i++)
{
_imageListView.Items[i].Selected = true;
}
}
else
{
_imageListView.ClearSelect();
_imageListView.Items[newindex].Selected = true;
}
currentFocusedItem.Focused = false;
_imageListView.Items[newindex].Focused = true;
EnsureVisible(newindex);
#endregion
//触发事件
_imageListView.OnSelectedItemChanged();
}
}
_imageListView.NeedPaint();
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
public void OnKeyUp(KeyEventArgs e)
{
//Refresh();
}
#endregion
#region
private void Refresh()
{
_imageListView.Refresh();
}
/// <summary>
/// Calculates the maximum number of rows and columns
/// that can be fully displayed.
/// </summary>
private void CalculateGrid()
{
Rectangle itemArea = this.ItemsArea;
//_columnCount = (int)System.Math.Floor((float)itemArea.Width / (float)_itemSizeWithMargin.Width);
_rowCount = (int)System.Math.Floor((float)itemArea.Height / (float)_itemHeightWithMargin);
//if (_columnCount < 1) _columnCount = 1;
if (_rowCount < 1) _rowCount = 1;
}
/// <summary>
/// 计算当前可见项的index范围
/// </summary>
private void CalculateVisibleItemsRange()
{
//Rectangle itemsArea;
//这里必须把控件的内部Padding值考虑进来
//_visibleOffset 是相对于 ItemsArea 的,此处要得到相对于整个控件可视区域的 offSet
//因为显示的图片项即使超出了 ItemsArea ,但还是在可视区域内,还是完全可见的,在计算时需要考虑
//ItemsArea.Location.Y - ItemsBounds.Location.Y 实际上就是Padding
//但是这样写逻辑上好些,因为实际意义在于 ItemsArea 和 ItemsBounds 之间的区域也是可以显示内容的在意义上与Padding无关
int offSet = _itemsAreaOffset - (ItemsArea.Location.Y - ItemsBounds.Location.Y);
if (offSet < 0)
offSet = 0;
int itemAreaHeight = ItemsBounds.Height;
_firstPartiallyVisible = (int)System.Math.Floor((float)offSet / (float)_itemHeightWithMargin);
_lastPartiallyVisible = (int)System.Math.Ceiling((float)(offSet + itemAreaHeight) / (float)_itemHeightWithMargin) - 1;
if (_firstPartiallyVisible < 0) _firstPartiallyVisible = 0;
if (_firstPartiallyVisible > _imageListView.Items.Count - 1) _firstPartiallyVisible = _imageListView.Items.Count - 1;
if (_lastPartiallyVisible < 0) _lastPartiallyVisible = 0;
if (_lastPartiallyVisible > _imageListView.Items.Count - 1) _lastPartiallyVisible = _imageListView.Items.Count - 1;
_firstVisible = (int)System.Math.Ceiling((float)offSet / (float)_itemHeightWithMargin);
_lastVisible = (int)System.Math.Floor((float)(offSet + itemAreaHeight) / (float)_itemHeightWithMargin) - 1;
if (_firstVisible < 0) _firstVisible = 0;
if (_firstVisible > _imageListView.Items.Count - 1) _firstVisible = _imageListView.Items.Count - 1;
if (_lastVisible < 0) _lastVisible = 0;
if (_lastVisible > _imageListView.Items.Count - 1) _lastVisible = _imageListView.Items.Count - 1;
}
/// <summary>
/// 更新滚动条状态
/// </summary>
private void UpdateScrollBars()
{
if (_imageListView.Items.Count > 0)
{
_vScrollBar.Minimum = 0;
_vScrollBar.Maximum = Math.Max(0,
(int)Math.Ceiling(
(float)_imageListView.Items.Count) * _itemHeightWithMargin - 1);
_vScrollBar.LargeChange = ItemsArea.Height;
_vScrollBar.SmallChange = _itemHeightWithMargin;
bool vScrollRequired = (_imageListView.Items.Count > 0) &&
(_rowCount < _imageListView.Items.Count);
_vScrollBar.Visible = vScrollRequired;
//此处重新计算滚动条的滚动值
//当滚动条出现,并滚动到底部时,改变控件的高度,就是向下拉大窗体
//已经绘制的项下面会开始出现空白就是因为滚动条的值还是旧值_itemsAreaOffset也没有变化
//除非用鼠标点一下滚动条,否则滚动条的 ValueChanged 事件也不会触发
//所以绘制出的项的起始Y轴是不对的必须在此重新计算滚动条的Value值
if (_itemsAreaOffset > _vScrollBar.Maximum - _vScrollBar.LargeChange + 1)
{
_vScrollBar.Value = _vScrollBar.Maximum - _vScrollBar.LargeChange + 1;
_itemsAreaOffset = _vScrollBar.Value;
}
}
else
{
_vScrollBar.Visible = false;
_vScrollBar.Value = 0;
_vScrollBar.Minimum = 0;
_vScrollBar.Maximum = 0;
}
}
/// <summary>
/// 创建框选框
/// </summary>
/// <returns></returns>
private Rectangle CreateSelectionRectangle()
{
Point mousePoint = _imageListView.PointToClient(Cursor.Position);
Point lastMouseDownLocation = _imageListView.LastMouseDownLocation;
#region
//当框选的同时,滚动滚动条
//计算offset:
//由于可视区域和项的一般显示区域之间有个padding而滚动条是以内部区域为标准的
//所以当滚动条开始滚动时可能项还是全部显示在可视范围内的padding区也可以显示
//那么此时 _itemsBoundsOffset 还是 0而按下鼠标时 _mouseDownOffset 记录的也是当时的 _itemsBoundsOffset
//那么在计算 SelectionRectangle 的 Y 坐标时,
//如果直接用 _itemsBoundsOffset 参与计算就会产生一个和padding有关的误差
//如 lastMouseDownLocation.Y - (viewOffset - _mouseDownOffset) ,假如此时向下滚动了一点
//但所有的项还在可视范围内,那么 就会是 lastMouseDownLocation.Y - (0 - 0)
//SelectionRectangle 的 Y 坐标就差生了误差
//解决的办法是使用 _itemsAreaOffset既滚动条的Value使框框的Y坐标与滚动条同步滚动即可
#endregion
int viewOffset = _itemsAreaOffset;
Point pt1 = new Point(lastMouseDownLocation.X, lastMouseDownLocation.Y - (viewOffset - _mouseItemsAreaOffset));
Point pt2 = new Point(mousePoint.X, mousePoint.Y);
Rectangle rect = new Rectangle(Math.Min(pt1.X, pt2.X), Math.Min(pt1.Y, pt2.Y),
Math.Abs(pt1.X - pt2.X), Math.Abs(pt1.Y - pt2.Y));
return rect;
}
/// <summary>
/// 根据矩形区域选择项
/// </summary>
/// <param name="rect"></param>
private void SelectItemsByRectangle(Rectangle rect)
{
int viewOffset = _itemsAreaOffset;
Point pt1 = new Point(SelectionRectangle.Left, SelectionRectangle.Top);
Point pt2 = new Point(SelectionRectangle.Right, SelectionRectangle.Bottom);
//- ItemsArea.Y 和 - ItemsArea.X
//是因为选择框的是以整个控件可视区域为坐标系的而绘制的项是在Padding区内的
//那么就需要修正或者说同步这个偏差,才能得到正确的行列
int startRow = (int)Math.Floor((float)(Math.Min(pt1.Y, pt2.Y) + viewOffset - ItemsArea.Y) /
(float)this._itemHeightWithMargin);
int endRow = (int)Math.Floor((float)(Math.Max(pt1.Y, pt2.Y) + viewOffset - ItemsArea.Y) /
(float)this._itemHeightWithMargin);
//int startCol = (int)Math.Floor((float)(Math.Min(pt1.X, pt2.X) - ItemsArea.X) /
// (float)this._itemSizeWithMargin.Width);
//int endCol = (int)Math.Floor((float)(Math.Max(pt1.X, pt2.X) - ItemsArea.X) /
// (float)this._itemSizeWithMargin.Width);
//行不能这样判断因为_rowCount表示的只是控件可视范围内的可视行数
//在框选时框会跨越可视区域的这里的endRow要的是实际的项所在行数
//列不存在这个问题,因为不支持水平滚动
//if (endRow >= _rowCount)
// endRow = _rowCount -1;
//if (endCol >= _columnCount)
// endCol = _columnCount - 1;
//if (startCol < 0)
// startCol = 0;
//创建一个应该被选定的项的index数组
int itemsCount = _imageListView.Items.Count;
List<int> selectItemsIndex = new List<int>();
for (int i = startRow; i <= endRow; i++)
{
int index = i;
if (index >= 0 && index < itemsCount)
selectItemsIndex.Add(index);
//for (int j = startCol; j <= endCol; j++)
//{
// int index = i * _columnCount + j;
// if (index >= 0 && index < itemsCount)
// selectItemsIndex.Add(index);
//}
}
//如果当前没有按下Shift键那么
//判断当前选定的项中有没有不在框选区内的,如果有将其取消选定
if (ShiftKey == false)
{
List<ShengListViewItem> currentSlectedItems = _imageListView.GetSelectedItems();
foreach (var item in currentSlectedItems)
{
if (selectItemsIndex.Contains(item.Index) == false)
item.Selected = false;
}
}
ShengListViewItemCollection allItems = _imageListView.Items;
//使框选区内的项都处于选中状态
foreach (var index in selectItemsIndex)
{
if (allItems[index].Selected == false)
allItems[index].Selected = true;
}
//debug
StartRow = startRow;
EndRow = endRow;
//StartCol = startCol;
//EndCol = endCol;
}
/// <summary>
/// 应用导航键,如上下左右,返回应用导航键之后的项的坐标
/// </summary>
private int ApplyNavKey(int index, Keys key)
{
int itemsCount = _imageListView.Items.Count;
if (key == Keys.Up && index > 0)
index -= 1;
else if (key == Keys.Down && index < itemsCount - 1)
index += 1;
else if (key == Keys.Left && index > 0)
index--;
else if (key == Keys.Right && index < itemsCount - 1)
index++;
else if (key == Keys.PageUp && index >= (_rowCount - 1))
index -= (_rowCount - 1);
else if (key == Keys.PageDown && index < itemsCount - (_rowCount - 1))
index += (_rowCount - 1);
else if (key == Keys.Home)
index = 0;
else if (key == Keys.End)
index = itemsCount - 1;
if (index < 0)
index = 0;
else if (index > itemsCount - 1)
index = itemsCount - 1;
return index;
}
/// <summary>
/// 使指定下标的项处于可见状态
/// </summary>
/// <param name="itemIndex"></param>
public void EnsureVisible(int itemIndex)
{
int itemCount = _imageListView.Items.Count;
if (itemCount == 0) return;
if (itemIndex < 0 || itemIndex >= itemCount) return;
// Already visible?
Rectangle bounds = this.ItemsBounds;
Rectangle itemBounds = GetItemBounds(itemIndex);
if (bounds.Contains(itemBounds) == false)
{
int delta;
if (itemBounds.Top < bounds.Top)
delta = bounds.Top - itemBounds.Top;
else
{
int topItemIndex = itemIndex - (_rowCount - 1) ;
if (topItemIndex < 0) topItemIndex = 0;
delta = bounds.Top - GetItemBounds(topItemIndex).Top;
}
int newYOffset = this.ItemsBoundsOffset - delta;
if (newYOffset > _vScrollBar.Maximum - _vScrollBar.LargeChange + 1)
newYOffset = _vScrollBar.Maximum - _vScrollBar.LargeChange + 1;
if (newYOffset < _vScrollBar.Minimum)
newYOffset = _vScrollBar.Minimum;
//mViewOffset.X = 0;
//mViewOffset.Y = newYOffset;
//hScrollBar.Value = 0;
_vScrollBar.Value = newYOffset;
}
}
#endregion
}
}