RaUI/Source/ryControls/Sheng.Winform.Controls/ShengListView/Layout/ShengListViewLayoutManager.cs

1230 lines
46 KiB
C#
Raw Normal View History

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
}
}