Пользователи перетаскивают строки вверх и вниз в моем DataGridView. У меня есть вниз стандартная логика перетаскивания, но я хотел бы там быть темным маркером, указывающим, куда строка будет помещена после того, как я отпустил мыши.
Пример от Microsoft Access http://img718.imageshack.us/img718/8171/accessdrag.png
Пример от Microsoft Access; я хочу перетащить строки вместо столбцов
Кто-либо знает, как я пошел бы о выполнении этого? Действительно ли это встроено, или я должен был бы потянуть свой собственный маркер (если так, как я делаю это)?
Спасибо!
Я делал это для древовидного просмотра пару лет назад; не помню точно как, но рассмотрите возможность использования события MouseMove
DataGridView.
Пока происходит перетаскивание, ваш обработчик MouseMove должен:
DataGridView.PointToClient()
для преобразования их в относительные) DataGridViewRow.DividerHeight
. Если вы хотите сделать что-то свое с внешним видом строки под мышью (вместо того, чтобы просто использовать доступные свойства), вы можете использовать событие DataGridView.RowPostPaint
. Если вы реализуете обработчик этого события, который используется только при перетаскивании строки поверх другой строки, вы можете перекрасить верхнюю или нижнюю границу строки более жирной кистью. Пример из MSDN здесь.
Приложение, над которым я работаю, обрабатывает маркер как отдельный объект Panel с высотой 1 и BackColor равным 1. Объект Panel остается скрытым до тех пор, пока фактически не начнется перетаскивание. Эта функция, запускаемая по событию DragOver, реализует большую часть логики:
public static void frameG_dragover(Form current_form, DataGridView FRAMEG, Panel drag_row_indicator, Point mousePos)
{
int FRAMEG_Row_Height = FRAMEG.RowTemplate.Height;
int FRAMEG_Height = FRAMEG.Height;
int Loc_X = FRAMEG.Location.X + 2;
Point clientPoint = FRAMEG.PointToClient(mousePos);
int CurRow = FRAMEG.HitTest(clientPoint.X, clientPoint.Y).RowIndex;
int Loc_Y = 0;
if (CurRow != -1)
{
Loc_Y = FRAMEG.Location.Y + ((FRAMEG.Rows[CurRow].Index + 1) * FRAMEG_Row_Height) - FRAMEG.VerticalScrollingOffset;
}
else
{
Loc_Y = FRAMEG.Location.Y + (FRAMEG.Rows.Count + 1) * FRAMEG_Row_Height;
}
int width_c = FRAMEG.Columns[0].Width + FRAMEG.Columns[1].Width + FRAMEG.Columns[2].Width;
if ((Loc_Y > (FRAMEG.Location.Y)) && (Loc_Y < (FRAMEG.Location.Y + FRAMEG_Height - FRAMEG_Row_Height))) //+ FRAMEG_Row_Height
{
drag_row_indicator.Location = new System.Drawing.Point(Loc_X, Loc_Y);
drag_row_indicator.Size = new Size(width_c, 1);
}
if (!drag_row_indicator.Visible)
drag_row_indicator.Visible = true;
}
Помимо этого, вам просто нужно снова скрыть панель, когда перетаскивание завершено или перемещено из DataGridView.
Вот и было мое возможное решение. Этот элемент управления:
Вы можете делать все, что хотите, с помощью этого кода (без гарантии и т. д.)
using System;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace CAM_Products.General_Controls
{
public class DataGridViewWithDraggableRows : DataGridView
{
private int? _predictedInsertIndex; //Index to draw divider at. Null means no divider
private Timer _autoScrollTimer;
private int _scrollDirection;
private static DataGridViewRow _selectedRow;
private bool _ignoreSelectionChanged;
private static event EventHandler<EventArgs> OverallSelectionChanged;
private SolidBrush _dividerBrush;
private Pen _selectionPen;
#region Designer properties
/// <summary>
/// The color of the divider displayed between rows while dragging
/// </summary>
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Category("Appearance")]
[Description("The color of the divider displayed between rows while dragging")]
public Color DividerColor
{
get { return _dividerBrush.Color; }
set { _dividerBrush = new SolidBrush(value); }
}
/// <summary>
/// The color of the border drawn around the selected row
/// </summary>
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Category("Appearance")]
[Description("The color of the border drawn around the selected row")]
public Color SelectionColor
{
get { return _selectionPen.Color; }
set { _selectionPen = new Pen(value); }
}
/// <summary>
/// Height (in pixels) of the divider to display
/// </summary>
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Category("Appearance")]
[Description("Height (in pixels) of the divider to display")]
[DefaultValue(4)]
public int DividerHeight { get; set; }
/// <summary>
/// Width (in pixels) of the border around the selected row
/// </summary>
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Category("Appearance")]
[Description("Width (in pixels) of the border around the selected row")]
[DefaultValue(3)]
public int SelectionWidth { get; set; }
#endregion
#region Form setup
public DataGridViewWithDraggableRows()
{
InitializeProperties();
SetupTimer();
}
private void InitializeProperties()
{
#region Code stolen from designer
this.AllowDrop = true;
this.AllowUserToAddRows = false;
this.AllowUserToDeleteRows = false;
this.AllowUserToOrderColumns = true;
this.AllowUserToResizeRows = false;
this.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
this.ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single;
this.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.EnableHeadersVisualStyles = false;
this.MultiSelect = false;
this.ReadOnly = true;
this.RowHeadersVisible = false;
this.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
this.CellMouseDown += dataGridView1_CellMouseDown;
this.DragOver += dataGridView1_DragOver;
this.DragLeave += dataGridView1_DragLeave;
this.DragEnter += dataGridView1_DragEnter;
this.Paint += dataGridView1_Paint_Selection;
this.Paint += dataGridView1_Paint_RowDivider;
this.DefaultCellStyleChanged += dataGridView1_DefaultcellStyleChanged;
this.Scroll += dataGridView1_Scroll;
#endregion
_ignoreSelectionChanged = false;
OverallSelectionChanged += OnOverallSelectionChanged;
_dividerBrush = new SolidBrush(Color.Red);
_selectionPen = new Pen(Color.Blue);
DividerHeight = 4;
SelectionWidth = 3;
}
#endregion
#region Selection
/// <summary>
/// All instances of this class share an event, so that only one row
/// can be selected throughout all instances.
/// This method is called when a row is selected on any DataGridView
/// </summary>
private void OnOverallSelectionChanged(object sender, EventArgs e)
{
if(sender != this && SelectedRows.Count != 0)
{
ClearSelection();
Invalidate();
}
}
protected override void OnSelectionChanged(EventArgs e)
{
if(_ignoreSelectionChanged)
return;
if(SelectedRows.Count != 1 || SelectedRows[0] != _selectedRow)
{
_ignoreSelectionChanged = true; //Following lines cause event to be raised again
if(_selectedRow == null || _selectedRow.DataGridView != this)
{
ClearSelection();
}
else
{
_selectedRow.Selected = true; //Deny new selection
if(OverallSelectionChanged != null)
OverallSelectionChanged(this, EventArgs.Empty);
}
_ignoreSelectionChanged = false;
}
else
{
base.OnSelectionChanged(e);
if(OverallSelectionChanged != null)
OverallSelectionChanged(this, EventArgs.Empty);
}
}
public void SelectRow(int rowIndex)
{
_selectedRow = Rows[rowIndex];
_selectedRow.Selected = true;
Invalidate();
}
#endregion
#region Selection highlighting
private void dataGridView1_Paint_Selection(object sender, PaintEventArgs e)
{
if(_selectedRow == null || _selectedRow.DataGridView != this)
return;
Rectangle displayRect = GetRowDisplayRectangle(_selectedRow.Index, false);
if(displayRect.Height == 0)
return;
_selectionPen.Width = SelectionWidth;
int heightAdjust = (int)Math.Ceiling((float)SelectionWidth/2);
e.Graphics.DrawRectangle(_selectionPen, displayRect.X - 1, displayRect.Y - heightAdjust,
displayRect.Width, displayRect.Height + SelectionWidth - 1);
}
private void dataGridView1_DefaultcellStyleChanged(object sender, EventArgs e)
{
DefaultCellStyle.SelectionBackColor = DefaultCellStyle.BackColor;
DefaultCellStyle.SelectionForeColor = DefaultCellStyle.ForeColor;
}
private void dataGridView1_Scroll(object sender, ScrollEventArgs e)
{
Invalidate();
}
#endregion
#region Drag-and-drop
protected override void OnDragDrop(DragEventArgs args)
{
if(args.Effect == DragDropEffects.None)
return;
//Convert to coordinates within client (instead of screen-coordinates)
Point clientPoint = PointToClient(new Point(args.X, args.Y));
//Get index of row to insert into
DataGridViewRow dragFromRow = (DataGridViewRow)args.Data.GetData(typeof(DataGridViewRow));
int newRowIndex = GetNewRowIndex(clientPoint.Y);
//Adjust index if both rows belong to same DataGridView, due to removal of row
if(dragFromRow.DataGridView == this && dragFromRow.Index < newRowIndex)
{
newRowIndex--;
}
//Clean up
RemoveHighlighting();
_autoScrollTimer.Enabled = false;
//Only go through the trouble if we're actually moving the row
if(dragFromRow.DataGridView != this || newRowIndex != dragFromRow.Index)
{
//Insert the row
MoveDraggedRow(dragFromRow, newRowIndex);
//Let everyone know the selection has changed
SelectRow(newRowIndex);
}
base.OnDragDrop(args);
}
private void dataGridView1_DragLeave(object sender, EventArgs e1)
{
RemoveHighlighting();
_autoScrollTimer.Enabled = false;
}
private void dataGridView1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = (e.Data.GetDataPresent(typeof(DataGridViewRow))
? DragDropEffects.Move
: DragDropEffects.None);
}
private void dataGridView1_DragOver(object sender, DragEventArgs e)
{
if(e.Effect == DragDropEffects.None)
return;
Point clientPoint = PointToClient(new Point(e.X, e.Y));
//Note: For some reason, HitTest is failing when clientPoint.Y = dataGridView1.Height-1.
// I have no idea why.
// clientPoint.Y is always 0 <= clientPoint.Y < dataGridView1.Height
if(clientPoint.Y < Height - 1)
{
int newRowIndex = GetNewRowIndex(clientPoint.Y);
HighlightInsertPosition(newRowIndex);
StartAutoscrollTimer(e);
}
}
private void dataGridView1_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
if(e.Button == MouseButtons.Left && e.RowIndex >= 0)
{
SelectRow(e.RowIndex);
var dragObject = Rows[e.RowIndex];
DoDragDrop(dragObject, DragDropEffects.Move);
//TODO: Any way to make this *not* happen if they only click?
}
}
/// <summary>
/// Based on the mouse position, determines where the new row would
/// be inserted if the user were to release the mouse-button right now
/// </summary>
/// <param name="clientY">
/// The y-coordinate of the mouse, given with respectto the control
/// (not the screen)
/// </param>
private int GetNewRowIndex(int clientY)
{
int lastRowIndex = Rows.Count - 1;
//DataGridView has no cells
if(Rows.Count == 0)
return 0;
//Dragged above the DataGridView
if(clientY < GetRowDisplayRectangle(0, true).Top)
return 0;
//Dragged below the DataGridView
int bottom = GetRowDisplayRectangle(lastRowIndex, true).Bottom;
if(bottom > 0 && clientY >= bottom)
return lastRowIndex + 1;
//Dragged onto one of the cells. Depending on where in cell,
// insert before or after row.
var hittest = HitTest(2, clientY); //Don't care about X coordinate
if(hittest.RowIndex == -1)
{
//This should only happen when midway scrolled down the page,
//and user drags over header-columns
//Grab the index of the current top (displayed) row
return FirstDisplayedScrollingRowIndex;
}
//If we are hovering over the upper-quarter of the row, place above;
// otherwise below. Experimenting shows that placing above at 1/4
//works better than at 1/2 or always below
if(clientY < GetRowDisplayRectangle(hittest.RowIndex, false).Top
+ Rows[hittest.RowIndex].Height/4)
return hittest.RowIndex;
return hittest.RowIndex + 1;
}
private void MoveDraggedRow(DataGridViewRow dragFromRow, int newRowIndex)
{
dragFromRow.DataGridView.Rows.Remove(dragFromRow);
Rows.Insert(newRowIndex, dragFromRow);
}
#endregion
#region Drop-and-drop highlighting
//Draw the actual row-divider
private void dataGridView1_Paint_RowDivider(object sender, PaintEventArgs e)
{
if(_predictedInsertIndex != null)
{
e.Graphics.FillRectangle(_dividerBrush, GetHighlightRectangle());
}
}
private Rectangle GetHighlightRectangle()
{
int width = DisplayRectangle.Width - 2;
int relativeY = (_predictedInsertIndex > 0
? GetRowDisplayRectangle((int)_predictedInsertIndex - 1, false).Bottom
: Columns[0].HeaderCell.Size.Height);
if(relativeY == 0)
relativeY = GetRowDisplayRectangle(FirstDisplayedScrollingRowIndex, true).Top;
int locationX = Location.X + 1;
int locationY = relativeY - (int)Math.Ceiling((double)DividerHeight/2);
return new Rectangle(locationX, locationY, width, DividerHeight);
}
private void HighlightInsertPosition(int rowIndex)
{
if(_predictedInsertIndex == rowIndex)
return;
Rectangle oldRect = GetHighlightRectangle();
_predictedInsertIndex = rowIndex;
Rectangle newRect = GetHighlightRectangle();
Invalidate(oldRect);
Invalidate(newRect);
}
private void RemoveHighlighting()
{
if(_predictedInsertIndex != null)
{
Rectangle oldRect = GetHighlightRectangle();
_predictedInsertIndex = null;
Invalidate(oldRect);
}
else
{
Invalidate();
}
}
#endregion
#region Autoscroll
private void SetupTimer()
{
_autoScrollTimer = new Timer
{
Interval = 250,
Enabled = false
};
_autoScrollTimer.Tick += OnAutoscrollTimerTick;
}
private void StartAutoscrollTimer(DragEventArgs args)
{
Point position = PointToClient(new Point(args.X, args.Y));
if(position.Y <= Font.Height/2 &&
FirstDisplayedScrollingRowIndex > 0)
{
//Near top, scroll up
_scrollDirection = -1;
_autoScrollTimer.Enabled = true;
}
else if(position.Y >= ClientSize.Height - Font.Height/2 &&
FirstDisplayedScrollingRowIndex < Rows.Count - 1)
{
//Near bottom, scroll down
_scrollDirection = 1;
_autoScrollTimer.Enabled = true;
}
else
{
_autoScrollTimer.Enabled = false;
}
}
private void OnAutoscrollTimerTick(object sender, EventArgs e)
{
//Scroll up/down
FirstDisplayedScrollingRowIndex += _scrollDirection;
}
#endregion
}
}