Как вычислить окончательные индексы элементов управления после перетаскивания нескольких элементов управления одновременно? - PullRequest
0 голосов
/ 29 марта 2019

Я считаю, что это моя логическая ошибка.Я прокомментировал большую часть моего кода ниже.У меня есть 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);
    }
}

Я определил некоторые случаи перетаскивания, которые необходимо проверить,их комбинации (одиночный или множественный выбор):

  1. первый элемент;
  2. элементы между первым и последним элементами;
  3. последний элемент;
  4. пустое место после последнего элемента.

Я использую множественные вызовы ControlCollection.SetChildIndex для перемещения элементов управления, но это не работает в этом случае (и других):

  1. У меня есть 4 предмета в этом порядке, названные: A, B, C, D.
  2. Я выбираю и перетаскиваю A и перемещаю его после D (на пустом месте).
  3. Результат: B, A, C, D, что неверно.
  4. Результат должен быть B, C, D, A.
...