I'm working on a training project that involves creating a Kanban board, and I'm having trouble implementing drag-n-drop for tickets both within and between categories.
In my current solution I use the CategoriesModel class, inherited from QAbstractListModel, which stores information about all categories in a particular project. It includes an object of class TicketsModel which also inherits from QAbstractListModel and is responsible for storing ticket information.
The problem is that I am not able to do the "correct" movement of the tickets within the model after visually dragging and dropping items within the DelegateModel.
In my current solution, I use the approach based on example from Qt documentation, but it doesn't provide information about moving items between two different ListView. Here are the main parts of my code that are responsible for moving the tickets:
void CategoriesModel::moveTicket(int fromCategory, int fromIndex, int toCategory, int toIndex)
{
Ticket* tick = getTicketByIndex(fromCategory, fromIndex);
m_categories.at(toCategory).getTickets()->insertTicketInto(tick, toIndex);
removeTicketByIndex(fromCategory, fromIndex);
QModelIndex fromModelIndex = createIndex(fromCategory, 0);
QModelIndex toModelIndex = createIndex(toCategory, 0);
emit dataChanged(fromModelIndex, fromModelIndex);
emit dataChanged(toModelIndex, toModelIndex);
}
void TicketsModel::moveTicketInternally(int fromIndex, int toIndex)
{
// Invalid indexes
if (fromIndex < 0 || fromIndex >= m_tickets.size() || toIndex < 0 || toIndex >= m_tickets.size()) { return; }
// Start and end indexes are the same
if (fromIndex == toIndex) { return; }
Ticket* item = m_tickets.takeAt(fromIndex);
insertTicketInto(item, toIndex);
QModelIndex fromModelIndex = createIndex(fromIndex, 0);
QModelIndex toModelIndex = createIndex(toIndex, 0);
emit dataChanged(fromModelIndex, fromModelIndex);
emit dataChanged(toModelIndex, toModelIndex);
}
Qml code responsible for the visualization of models:
// Ticket displaying
Item
{
MouseArea
{
id: mouseArea
property bool held: false
anchors.fill: parent
drag.target: held ? ticketRect : undefined
onPressAndHold: held = true
onReleased: held = false
DropArea
{
id: dropArea
anchors.fill: parent
property var fromIndex
property var toIndex
property var fromColumn
property var toColumn
onEntered: (drag) => {
fromIndex = drag.source.DelegateModel.itemsIndex;
toIndex = mouseArea.DelegateModel.itemsIndex;
fromColumn = drag.source.DelegateModel.groups[1];
toColumn = mouseArea.DelegateModel.groups[1];
if (fromColumn !== toColumn) {
categoriesModel.moveTicket(categoriesModel.getCategoryIndexById(fromColumn), fromIndex, categoriesModel.getCategoryIndexById(toColumn), toIndex);
} else {
visualModel.items.move(fromIndex, toIndex);
tickets.moveTicketInternally(fromIndex, toIndex);
}
}
}
…
}
}
// Сategories displaying
Item
{
id: category
property string categoryId
property alias tickets: visualModel.model
Rectangle
{
anchors.fill: parent
DelegateModel
{
id: visualModel
groups: DelegateModelGroup {
name: category.categoryId
includeByDefault: true
}
delegate: ModelControlTicket { header: name }
}
ListView
{
id: ticketList
model: visualModel
}
}
…
}
The main disadvantages of my approach, which are critical:
- There needs to be an asbtract "dummyTicket" that will zone the location to which the dragged ticket will move. When implementing it, there may be problems with data synchronization in the models.
- I use my own method
moveTicketInternallyto apply permutations fromDelegateModelto C++ model, which in my mind is definitely not good practice. - The current version of the solution may occasionally have an error
IndexOutOfRangefor propertyfromCategorywhich will cause the program to crash. Unfortunately, I didn't manage to find the reason for the array overrun.
I would be grateful for any suggestions on how best to implement drag-n-drop functionality in this context, as well as advice on how to "properly" move tickets within a model after visually dragging and dropping items within a DelegateModel. Maybe the best solution in this scenario would be to use a completely different approach? If so, which one?