Я считаю, что это моя логическая ошибка.Я прокомментировал большую часть моего кода ниже.У меня есть FlowLayoutPanel
, который содержит UserControl
s в основном того же типа (есть также элемент управления заполнителя, показанный при перетаскивании других UserControl
s).Я реализовал множественный выбор этих элементов управления, и я хочу иметь возможность перетаскивать несколько элементов управления, проверяя их, а затем перетаскивая один из выбранных элементов управления.У меня проблема с алгоритмом определения конечных положений элементов управления после перемещения нескольких элементов управления.
Я использую .NET Framework 4.6.1.Я попытался удалить все перемещаемые элементы управления, а затем добавить их с правильными индексами.Я попытался сделать это, используя какие-то перестановки, как видно из кода ниже.
Об элементе управления заполнителем : когда пользователь перетаскивает пустое пространство FlowLayoutPanel
,заполнитель находится на последней позиции.Когда курсор мыши находится над правой стороной элемента управления, заполнитель находится справа.Когда мышь находится над вторичной диагональю прямоугольника элемента управления (термин взят из матриц), местозаполнитель должен отображаться с левой стороны, но мне пока не удалось этого сделать, поэтому заполнитель не отображается, когда курсор находится надвторичная диагональ.
internal void MoveClocksFromIndicesToIndex(List<int> oldIndices, int targetIndex)
{
// the clock on the target position
ClockData tcd = ClocksData[targetIndex];
// the source indices in ascending order
oldIndices = (from oi in oldIndices
orderby oi ascending
select oi).ToList();
// the source clocks in ascending indices order
List<ClockData> l = (from oi in oldIndices
select ClocksData[oi]).ToList();
// whether the target clock is between the source clocks
bool targetRemoved = false;
int i;
// for each source clock from the greatest index to the lowest
for (i = oldIndices.Count - 1; i >= 0; --i)
{
ClockData cd = ClocksData[oldIndices[i]];
// remove it from the collection
ClocksData.Remove(cd);
// if the current clock is the target clock
if (cd == tcd)
{
// mark it as removed
targetRemoved = true;
}
}
// if everything is moved on a position of a removed clock
if (targetRemoved)
{
// the first position from the left of the target (removed) clock
// (or from the right?) which is not moved is taken as target index
targetIndex =
(from k in Enumerable.Range(0, targetIndex/* + 1*/) // the 1 does not make sense currently in my head
orderby k descending
where !oldIndices.Contains(k)
select k).FirstOrDefault();
}
// the target index remained the same after removals
else if (tcd.GetIndex() == targetIndex)
{
}
// things have been taken from the left of the target
else if (tcd.GetIndex() < targetIndex)
{
// recompute the target index after
// removing what had to be removed
targetIndex = tcd.GetIndex() + oldIndices.Count;
}
// things have been put on the left of the target
// (impossible, before this was only removal)
else if (tcd.GetIndex() > targetIndex)
{
//// recompute the target index after
//// adding what had to be added
//targetIndex = tcd.GetIndex() - oldIndices.Count;
//// (added oldIndices.Count to the target index
//// because the target moved to the left)
}
// the indices of the source clocks after the total move
var newIndices = new List<int>();
i = 0;
// for each source clock in descending order of indices
for (int j = l.Count - 1; j >= 0; --j)
{
ClockData cd = l[i];
// if the new index is <= than the count of clocks (???)
int ni = targetIndex + i/* + 1*/ </*=*/ VisibleCount ?
targetIndex + i/* + 1*/ :
VisibleCount - 1;
// do the move
ClocksData.Insert(ni, cd);
// store the new index
newIndices.Add(ni);
// mark collection as unsaved
IsUnsaved = true;
++i;
}
// the sum of all the moves stored as a permutation
List<int> permutation = (from x in Enumerable.Range(0, VisibleCount)
select x).ToList();
// for each source index from the lowest
for (i = 0; i < oldIndices.Count; ++i)
{
int ni = newIndices[i];
int oi = oldIndices[i];
// do the move inside the permutation (verified code)
if (ni < oi)
{
// every positions on newIndex and those on
// its right are moved in right with 1 position
// (excepting the last in the list which has nowhere to go)
for (int j = oi - 1; j >= ni; --j)
{
permutation[j + 1] = permutation[j];
}
permutation[ni] = permutation[oi];
}
else if (ni > oi)
{
// from the right of oldIndex everything moves with a position to left
for (int j = oi + 1; j <= ni; ++j)
{
permutation[j - 1] = permutation[j];
}
permutation[ni] = permutation[oi];
}
}
for (i = 0; i < VisibleCount; ++i)
{
if (permutation[i] != i)
{
OnClockMoved(new ClockMovedEventArgs()
{
Clock = ClocksData[i],
OldIndex = i, // shall I reverse the i with permutation[i] in these two lines?
NewIndex = permutation[i]
});
}
}
}
Еще один пример:
internal void DragOverHandler(DragEventArgs drgevent)
{
if (drgevent.Data.GetDataPresent(typeof(List<ClockControl>)))
{
DragOverTimer.Start();
HasFocus = true;
Point p = PointToClient(new Point(drgevent.X, drgevent.Y));
var tc = GetChildAtPoint(p) as ClockControl;
bool doMovePlaceholder = true;
int newPlaceholderIndex;
// if there is a ClockControl under the cursor
if (tc != null)
{
// if the mouse is in its right side
if (GetDialOfCursorOverClockControl(tc) == 1)
{
// move the placeholder &
// also move the ClockControl
newPlaceholderIndex = Controls.GetChildIndex(tc) + 1;
}
// if the mouse is in its left side
else
{
// move the ClockControl,
// not the placeholder
newPlaceholderIndex = Math.Max(0, Controls.GetChildIndex(tc)/* - 1*/);
doMovePlaceholder = false;
}
}
// the mouse is over empty space
else
{
// move the placeholder at the end of the list
newPlaceholderIndex = Controls.Count;
}
// do move the placeholder?
if (doMovePlaceholder)
{
// the placeholder
ClockControlPlaceholder ph = MyClockListView.GetClockControlPlaceholder();
// a new string for the placeholder
string ns =
ClockControl.DraggedClockControls.Count.ToString();
if (ph.Label != ns)
{
ph.Label = ns;
}
// move it
if (Controls.GetChildIndex(ph) != newPlaceholderIndex)
{
Controls.SetChildIndex(ph, newPlaceholderIndex);
}
}
// don't move the placeholder, remove it because the
// cursor is over a ClockControl
else if (tc != null)
{
MyClockListView.RemoveClockControlPlaceholder();
}
}
}
Еще один пример:
internal void HandleDargDrop(object sender, DragEventArgs drgevent)
{
DragOverTimer.Stop();
// if the dragged data is not interesting
if (!drgevent.Data.GetDataPresent(typeof(List<ClockControl>)))
{
// remove the placeholder
MyClockListView.RemoveClockControlPlaceholder();
return;
}
// else
var data = (List<ClockControl>)drgevent.Data.GetData(typeof(List<ClockControl>));
// get the destination ClockListView
var temp = sender as Control;
while (temp != null &&
temp.GetType() != typeof(ClockListView))
{
temp = temp.Parent;
}
var destination = temp as ClockListView;
// get the source ClockListView
var source = data[0]?.Parent?.Parent as ClockListView;
// if there is no source (floating ClockControl-s in ClockForm-s)
if (source == null)
{
// remove the placeholder from the destination
MyClockListView.RemoveClockControlPlaceholder();
return;
}
// if the ClockControl-s dragged are dropped
// in the same source ClockListView
if (source == destination)
{
// remove the placeholder
MyClockListView.RemoveClockControlPlaceholder();
// position of mouse in destination ClockListView
Point p = destination.PointToClient(new Point(drgevent.X, drgevent.Y));
// get item under cursor
Control item = GetChildAtPoint(p);
// as a ClockControl
var hoveredClockControl = item as ClockControl;
// if null, please insert the dragged clocks at the end of the
// collection
// its index
int itemIndex = Controls.GetChildIndex(item, false);
// the hovered dial (no dial hovered? 2)
int dial = hoveredClockControl == null ? 2 : ClockFlowLayoutPanel.GetDialOfCursorOverClockControl(hoveredClockControl);
// index in model
int tdItemIndex = MyDataFile.ClockCollection.FindIndexOfClockWithView(hoveredClockControl);
// if the mouse is on the right side, move the dragged
// on the right side, else move the dragged on the left side
tdItemIndex = dial == 1 ? tdItemIndex + 1 : tdItemIndex;
// if the target index is above the clocks count
if (tdItemIndex == Controls.Count)
{
// set it to the last clock's index
--tdItemIndex;
}
//// if there is nothing on the left side
//if (tdItemIndex < 0)
//{
// // place the dragged on the first position
// tdItemIndex = 0;
//}
// indices of dragged items in model:
var tdDataIndex = new List<int>();
// compute these indices
foreach (ClockControl cc in data)
{
int index = MyDataFile.ClockCollection.FindIndexOfClockWithView(cc as IClockView);
if (index >= 0)
{
tdDataIndex.Add(index);
}
}
// when there is no ClockControl under the cursor, inserts the dropped clocks at the end of the collection
if (tdItemIndex == -1 || itemIndex == -1)
{
tdItemIndex =
itemIndex = MyDataFile.ClockCollection.ClocksData.
Count - 1;
}
// if there is no valid clock dragged
if (tdDataIndex.Count == 0)
{
// TODO: unknown case, analyze.
// remove the placeholder
MyClockListView.RemoveClockControlPlaceholder();
return;
}
// remove the placeholder
MyClockListView.RemoveClockControlPlaceholder();
// call RefreshDisplay only when the IDs have been swapped (not when the dragged clock is dropped in the position of the drag start):
// there is exactly one dragged clock and it is dropped on the
// drag start position
if (tdDataIndex.Count == 1 && tdDataIndex[0] == tdItemIndex)
{
// no move
}
else
{
// do the move
MyDataFile.ClockCollection.MoveClocksFromIndicesToIndex(tdDataIndex, tdItemIndex);
// refresh the displayed IDs
MyClockListView.RefreshDisplay();
}
}
// the source and destination ClockListView-s are
// different one from another
else
{
// remove the placeholder
MyClockListView.RemoveClockControlPlaceholder();
}
}
Элементы управления перемещаются в обработчиках событий, например:
private void MyDataFile_TimerMoved(object sender, ClockMovedEventArgs e)
{
foreach (IClockView tv in e.Clock.MyTimerViews)
{
if (tv.GetClocksView() == this)
{
SetChildIndex(tv, e.NewIndex);
break;
}
}
RefreshDisplay();
}
, где в данном случае SetChildIndex
вызывает фактическое ControlCollection.SetChildIndex
из FlowLayoutPanel
:
public void SetChildIndex(object child, int newIndex)
{
var tc = child as ClockControl;
if (tc == null) throw new ArgumentException();
if (newIndex < 0) throw new ArgumentOutOfRangeException();
if (MyClockFlowLayoutPanel.Controls.Contains(tc))
{
MyClockFlowLayoutPanel.Controls.SetChildIndex(tc, newIndex);
}
}
Я определил некоторые случаи перетаскивания, которые необходимо проверить,их комбинации (одиночный или множественный выбор):
- первый элемент;
- элементы между первым и последним элементами;
- последний элемент;
- пустое место после последнего элемента.
Я использую множественные вызовы ControlCollection.SetChildIndex
для перемещения элементов управления, но это не работает в этом случае (и других):
- У меня есть 4 предмета в этом порядке, названные: A, B, C, D.
- Я выбираю и перетаскиваю A и перемещаю его после D (на пустом месте).
- Результат: B, A, C, D, что неверно.
- Результат должен быть B, C, D, A.