Przenoszenie wybranych wierszy w listView za pomocą przycisku

0

Hej.

Niby banalny problem ale tak pokręcony, że potrzebuję pomocy. Piszę aplikację w WinForms z funkcją edycji tracklist płyt Audio-CD. Otóż chciałbym za pomocą jednego przycisku przenosić wybrany utwór/track (dalej wymieniany jako wiersz) w listView do dołu i to zawsze o jeden krok - tzn. jak zaznaczę 3-ci wiersz i kliknę buttona to przeniesie się on na 4-tą pozycję a ten z czwartej wskoczy na trzecią. Kolejne kliknięcie przycisku przeniesie tym razem 4-ty wiersz na piątą pozycję a 5-ty na czwartą, itd.

Po uruchomieniu programu i załadowaniu albumu i zaznaczaniu 3-go utowru sprawa przedstawia się następująco:
Screenshot 2024-01-02 231025.png
Po klknięciu przycisku ładnie on zamienia się miejscami z 4-tym (czyli na razie wszystko jest OK):
Screenshot 2024-01-02 231102.png
Ale gdy kliknę drugi raz przycisk aby ten sam utwór (mercury) zamienił się miejscem z 5-tym to zaczynają się dziac dziwne rzeczy:
Screenshot 2024-01-02 231128.png
Poniżej zamieszczam kod przenoszenia wierszy listView'a o jeden w dół:

public partial class Form1 : Form
{
    public List<AlbumTracks> AlbumTracksList;
}
private void Form1_Load(object sender, EventArgs e)
{
    AlbumTracksList = new List<AlbumTracks>();
}
private void MoveDownSelectedTrack()
{
    AlbumTracks currentTrack = null;
    AlbumTracks nextTrack = null;
    int albumIndex = listBox1.SelectedIndex;
    int albumId = AlbumsList[albumIndex].AlbumId;
    int currentTrackListIndex = -1;
    int currentTrackNumber = -1;
    int currentDiscNumber = -1;
    int nextTrackListIndex = -1;
    int nextTrackNumber = -1;
    int nextDiscNumber = -1;
    int currentTrackIndex = -1;
    int currentTrackId = -1;
    int nextTrackId = -1;
    int step = -1;
    List<int> tracksIds = new List<int>();
    List<int> tracksIndices = new List<int>();
    List<AlbumTracks> tracks = AlbumTracksList.FindAll(track => track.AlbumId == albumId);
    foreach (int selectedIndex in listView1.SelectedIndices)
    {
        step++;
        AlbumTracks? cTrack = tracks?.Find(track => track.TrackListIndex == selectedIndex);
        if (cTrack != null)
        {
            currentTrackId = cTrack.TrackId;
            tracksIds.Add(currentTrackId);
            tracksIndices.Add(selectedIndex);
        }
        AlbumTracks? nTrack = tracks?.Find(track => track.TrackListIndex == selectedIndex + 1);
        if (nTrack != null)
        {
            nextTrackId = nTrack.TrackId;
        }
        nextTrack = tracks.Find(track => track.TrackId == nextTrackId);
        if (!listView1.Items[listView1.SelectedIndices[step] - 1].Selected && listView1.Items[listView1.SelectedIndices[step]].Selected)
        {
            nextTrack = tracks.Find(track => track.TrackId == nextTrackId);
            nextTrackListIndex = nextTrack.TrackListIndex;
            nextTrackNumber = nextTrack.TrackNumber;
            nextDiscNumber = nextTrack.DiscNumber;
        }
        currentTrack = tracks.Find(track => track.TrackId == tracksIds[step]);
        currentTrackListIndex = currentTrack.TrackListIndex;
        currentTrackNumber = currentTrack.TrackNumber;
        currentDiscNumber = currentTrack.DiscNumber;
        currentTrackIndex = AlbumTracksList.IndexOf(currentTrack);
        AlbumTracksList[currentTrackIndex + 1] = currentTrack;
        AlbumTracksList[currentTrackIndex + 1].TrackListIndex = currentTrackListIndex;
        AlbumTracksList[currentTrackIndex + 1].TrackNumber = currentTrackNumber;
        AlbumTracksList[currentTrackIndex + 1].DiscNumber = currentDiscNumber;
        if (!listView1.Items[listView1.SelectedIndices[step] + 1].Selected)
        {
            AlbumTracksList[currentTrackIndex] = nextTrack;
            AlbumTracksList[currentTrackIndex].TrackListIndex = nextTrackListIndex;
            AlbumTracksList[currentTrackIndex].TrackNumber = nextTrackNumber;
            AlbumTracksList[currentTrackIndex].DiscNumber = nextDiscNumber;
        }
    }
    List<AlbumTracks> newTracks = AlbumTracksList.FindAll(track => track.AlbumId == albumId);
    listView1.Items.Clear();
    foreach (var track in newTracks)
    {
        string[] row = { track.DiscNumber.ToString(), track.TrackNumber.ToString(), track.TrackPerformer, track.TrackTitle, track.TrackComposer, (track.TrackDuration > 0 ? (track.TrackDuration >= 60 ? (Math.Floor(track.TrackDuration / 60).ToString() + "m " + Math.Round(track.TrackDuration % 60, 3).ToString("0.000") + "s") : Math.Round(track.TrackDuration % 60, 3).ToString("0.000") + "s") : "Unknown"), (track.TrackPregap > 0 ? (track.TrackPregap >= 60 ? (Math.Floor(track.TrackPregap / 60).ToString() + "m " + Math.Round(track.TrackPregap % 60, 3).ToString("0.000") + "s") : Math.Round(track.TrackPregap % 60, 3).ToString("0.000") + "s") : "None"), (track.FileExists ? track.Filename : "Error! File does not exist!"), track.DiscComment, track.DiscIdentifier };
        var listViewItem = new ListViewItem(row);
        listView1.Items.Add(listViewItem);
    }
    for (int loop = 0; loop < listView1.Items.Count; loop++)
    {
        foreach (int index in tracksIndices)
        {
            if (loop == index)
            {
                listView1.Items[loop + 1].Selected = true;
            }
        }
    }
    listView1.Select();
}

Poniżej podaję jak wygląda świeżo wczytany do pamięci dane jednego w/w albumu w formacie JSON do listy AlbumTracksList (każdy utwór to kolejny element w tablicy):

[
  {
    "TrackListIndex": 0,
    "TrackId": 192,
    "AlbumId": 19,
    "DiscId": 19,
    "CueFilePath": "D:\\Kitaro\\DOMO\\Kitaro - Tamayura [2013]\\01 - Kitaro - Tamayura.cue",
    "MusicFilePath": "D:\\Kitaro\\DOMO\\Kitaro - Tamayura [2013]\\01 - Kitaro - Tamayura.flac",
    "FileExists": true,
    "Filename": "01 - Kitaro - Tamayura.flac",
    "FileType": "WAVE",
    "TrackNumber": 1,
    "DiscNumber": 1,
    "TrackTitle": "Introduction",
    "TrackPerformer": "Kitaro",
    "TrackComposer": "",
    "DiscComment": "ExactAudioCopy v1.6",
    "DiscIdentifier": "500F2807",
    "TrackDuration": 157.46666666666667,
    "TrackPregap": 0
  },
  {
    "TrackListIndex": 1,
    "TrackId": 193,
    "AlbumId": 19,
    "DiscId": 19,
    "CueFilePath": "D:\\Kitaro\\DOMO\\Kitaro - Tamayura [2013]\\01 - Kitaro - Tamayura.cue",
    "MusicFilePath": "D:\\Kitaro\\DOMO\\Kitaro - Tamayura [2013]\\01 - Kitaro - Tamayura.flac",
    "FileExists": true,
    "Filename": "01 - Kitaro - Tamayura.flac",
    "FileType": "WAVE",
    "TrackNumber": 2,
    "DiscNumber": 1,
    "TrackTitle": "Magma",
    "TrackPerformer": "Kitaro",
    "TrackComposer": "",
    "DiscComment": "ExactAudioCopy v1.6",
    "DiscIdentifier": "500F2807",
    "TrackDuration": 550.1466666666666,
    "TrackPregap": 0
  },
  {
    "TrackListIndex": 2,
    "TrackId": 194,
    "AlbumId": 19,
    "DiscId": 19,
    "CueFilePath": "D:\\Kitaro\\DOMO\\Kitaro - Tamayura [2013]\\01 - Kitaro - Tamayura.cue",
    "MusicFilePath": "D:\\Kitaro\\DOMO\\Kitaro - Tamayura [2013]\\01 - Kitaro - Tamayura.flac",
    "FileExists": true,
    "Filename": "01 - Kitaro - Tamayura.flac",
    "FileType": "WAVE",
    "TrackNumber": 3,
    "DiscNumber": 1,
    "TrackTitle": "Mercury",
    "TrackPerformer": "Kitaro",
    "TrackComposer": "",
    "DiscComment": "ExactAudioCopy v1.6",
    "DiscIdentifier": "500F2807",
    "TrackDuration": 740.44,
    "TrackPregap": 0
  },
  {
    "TrackListIndex": 3,
    "TrackId": 195,
    "AlbumId": 19,
    "DiscId": 19,
    "CueFilePath": "D:\\Kitaro\\DOMO\\Kitaro - Tamayura [2013]\\01 - Kitaro - Tamayura.cue",
    "MusicFilePath": "D:\\Kitaro\\DOMO\\Kitaro - Tamayura [2013]\\01 - Kitaro - Tamayura.flac",
    "FileExists": true,
    "Filename": "01 - Kitaro - Tamayura.flac",
    "FileType": "WAVE",
    "TrackNumber": 4,
    "DiscNumber": 1,
    "TrackTitle": "Sitara",
    "TrackPerformer": "Kitaro",
    "TrackComposer": "",
    "DiscComment": "ExactAudioCopy v1.6",
    "DiscIdentifier": "500F2807",
    "TrackDuration": 578.32,
    "TrackPregap": 0
  },
  {
    "TrackListIndex": 4,
    "TrackId": 196,
    "AlbumId": 19,
    "DiscId": 19,
    "CueFilePath": "D:\\Kitaro\\DOMO\\Kitaro - Tamayura [2013]\\01 - Kitaro - Tamayura.cue",
    "MusicFilePath": "D:\\Kitaro\\DOMO\\Kitaro - Tamayura [2013]\\01 - Kitaro - Tamayura.flac",
    "FileExists": true,
    "Filename": "01 - Kitaro - Tamayura.flac",
    "FileType": "WAVE",
    "TrackNumber": 5,
    "DiscNumber": 1,
    "TrackTitle": "Nageki",
    "TrackPerformer": "Kitaro",
    "TrackComposer": "",
    "DiscComment": "ExactAudioCopy v1.6",
    "DiscIdentifier": "500F2807",
    "TrackDuration": 383.84,
    "TrackPregap": 0
  },
  {
    "TrackListIndex": 5,
    "TrackId": 197,
    "AlbumId": 19,
    "DiscId": 19,
    "CueFilePath": "D:\\Kitaro\\DOMO\\Kitaro - Tamayura [2013]\\01 - Kitaro - Tamayura.cue",
    "MusicFilePath": "D:\\Kitaro\\DOMO\\Kitaro - Tamayura [2013]\\01 - Kitaro - Tamayura.flac",
    "FileExists": true,
    "Filename": "01 - Kitaro - Tamayura.flac",
    "FileType": "WAVE",
    "TrackNumber": 6,
    "DiscNumber": 1,
    "TrackTitle": "Beam Wave",
    "TrackPerformer": "Kitaro",
    "TrackComposer": "",
    "DiscComment": "ExactAudioCopy v1.6",
    "DiscIdentifier": "500F2807",
    "TrackDuration": 682.0266666666666,
    "TrackPregap": 0
  },
  {
    "TrackListIndex": 6,
    "TrackId": 198,
    "AlbumId": 19,
    "DiscId": 19,
    "CueFilePath": "D:\\Kitaro\\DOMO\\Kitaro - Tamayura [2013]\\01 - Kitaro - Tamayura.cue",
    "MusicFilePath": "D:\\Kitaro\\DOMO\\Kitaro - Tamayura [2013]\\01 - Kitaro - Tamayura.flac",
    "FileExists": true,
    "Filename": "01 - Kitaro - Tamayura.flac",
    "FileType": "WAVE",
    "TrackNumber": 7,
    "DiscNumber": 1,
    "TrackTitle": "Uranus",
    "TrackPerformer": "Kitaro",
    "TrackComposer": "",
    "DiscComment": "ExactAudioCopy v1.6",
    "DiscIdentifier": "500F2807",
    "TrackDuration": 787.77333333333,
    "TrackPregap": 0
  }
]

Zechce ktoś pomóc? Będę wdzięczny ;) Pozdrawiam.

0
Qbelek napisał(a):

https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.listview.listviewitemsorter?view=windowsdesktop-8.0&viewFallbackFrom=net-8.0#system-windows-forms-listview-listviewitemsorter

Dzięki za odpowiedź, jednak nie tego szukałem. Tak jak wspomniałem wcześniej najpierw chcę przesuwać miejscami elementy w tablicy (a właściwie liście) AlbumTracksList potem wyczyścić cały listView za pomocą listView1.Items.Clear(); i dopiero wtedy zapełnić tą kontrolkę nowymi wierszami - coś w rodzaju: dane w pamięci w postaci tablicy a ich widok za każdym razem od nowa generowany z poziomu kodu.

1

Tyle kodu aby przenieść coś o jedną pozycje, dużo szybciej i czytelniej będzie zrobić kopię elementu i usunąć z listy, potem wstawić o jeden indeks wyżej.

Dla czytelności lista ładowana jest w zmiennej Tracks, oczywiście należy dodać obsługę tego czy jest coś zaznaczone, no i czy nie jesteśmu na ostatnim elemencie

//pobierz indeks zaznaczonego elelemntu
var si = listView1.SelectedIndices[0];

//zrób kopie elementu do przeniesienia
AlbumTrack TrackToMove = Tracks[si];
Tracks.RemoveAt(si);

//wstaw z si+1
Tracks.Insert(si+1, TrackToMove);

//załaduj do listview aktualizując TrackNumber
listView1.Items.Clear();
var i = 1;
foreach (AlbumTrack track in Tracks)
{
    track.TrackNumber = i;
    string[] row = { track.DiscNumber.ToString(), track.TrackNumber.ToString(), track.TrackPerformer, track.TrackTitle, track.TrackComposer, (track.TrackDuration > 0 ? (track.TrackDuration >= 60 ? (Math.Floor(track.TrackDuration / 60).ToString() + "m " + Math.Round(track.TrackDuration % 60, 3).ToString("0.000") + "s") : Math.Round(track.TrackDuration % 60, 3).ToString("0.000") + "s") : "Unknown"), (track.TrackPregap > 0 ? (track.TrackPregap >= 60 ? (Math.Floor(track.TrackPregap / 60).ToString() + "m " + Math.Round(track.TrackPregap % 60, 3).ToString("0.000") + "s") : Math.Round(track.TrackPregap % 60, 3).ToString("0.000") + "s") : "None"), (track.FileExists ? track.Filename : "Error! File does not exist!"), track.DiscComment, track.DiscIdentifier };
    var listViewItem = new ListViewItem(row);
    listView1.Items.Add(listViewItem);
    i++;
}

//Zaznacz przeniesiony element na liście
listView1.Items[si+1].Selected = true;
0

Witam z powrotem. Dzisiaj wróciłem do mojego problemu i udało mi się go rozwiązać prawie do końca. Kod, który podał Panczo jest dobry, ale mi rówież zależało na możliwości przesuwania elementów listy (utworów w albumie) nawet gdy jest ich zaznaczonych kilka (zakładając, że wszystkie one sąsiadują ze sobą).

Kod który przesuwa element(y) o jeden w górę:

private void MoveUpSelectedTrack()
{
    int currentTrackListIndex = -1;
    int lastTrackListIndex = -1;
    int preTrackListIndex = -1;
    int prelastTrackNumber = -1;
    int albumIndex = listBox1.SelectedIndex;
    int albumId = AlbumsList[albumIndex].AlbumId;
    List<AlbumTracks> tracks = AlbumTracksList.FindAll(track => track.AlbumId == albumId);
    var selectedIndices = listView1.SelectedIndices;
    preTrackListIndex = AlbumTracksList.FindIndex(track => track.AlbumId == albumId && track.TrackListIndex == listView1.SelectedIndices[0] - 1);
    lastTrackListIndex = AlbumTracksList.FindIndex(track => track.AlbumId == albumId && track.TrackListIndex == listView1.SelectedIndices[listView1.SelectedIndices.Count - 1]) + 1;
    prelastTrackNumber = AlbumTracksList[lastTrackListIndex - 1].TrackNumber;
    for (int loop = 0; loop < listView1.SelectedIndices.Count; loop++)
    {
        currentTrackListIndex = AlbumTracksList.FindIndex(track => track.AlbumId == albumId && track.TrackListIndex == listView1.SelectedIndices[loop]);
        AlbumTracksList[currentTrackListIndex].TrackNumber = tracks.Find(track => track.TrackListIndex == listView1.SelectedIndices[loop]).TrackNumber - 1;
    }
    AlbumTracksList[preTrackListIndex].TrackNumber = prelastTrackNumber;
    List<AlbumTracks> newTracks = AlbumTracksList.FindAll(track => track.AlbumId == albumId);
    List<AlbumTracks> sortedTracks = newTracks.OrderBy(o => o.TrackNumber).ToList();
    for (int loop = 0; loop < sortedTracks.Count; loop++)
    {
        sortedTracks[loop].TrackListIndex = loop;
    }
    for (int loop = 0; loop < sortedTracks.Count; loop++)
    {
        AlbumTracksList.Find(track => track.AlbumId == albumId && track.TrackListIndex == loop).TrackListIndex = sortedTracks[loop].TrackListIndex;
    }
    listView1.Items.Clear();
    foreach (var track in sortedTracks)
    {
        string[] row = { track.DiscNumber.ToString(), track.TrackNumber.ToString(), track.TrackPerformer, track.TrackTitle, track.TrackComposer, (track.TrackDuration > 0 ? (track.TrackDuration >= 60 ? (Math.Floor(track.TrackDuration / 60).ToString() + "m " + Math.Round(track.TrackDuration % 60, 3).ToString("0.000") + "s") : Math.Round(track.TrackDuration % 60, 3).ToString("0.000") + "s") : "Unknown"), (track.TrackPregap > 0 ? (track.TrackPregap >= 60 ? (Math.Floor(track.TrackPregap / 60).ToString() + "m " + Math.Round(track.TrackPregap % 60, 3).ToString("0.000") + "s") : Math.Round(track.TrackPregap % 60, 3).ToString("0.000") + "s") : "None"), (track.FileExists ? track.Filename : "Error! File does not exist!"), track.DiscComment, track.DiscIdentifier };
        var listViewItem = new ListViewItem(row);
        listView1.Items.Add(listViewItem);
    }
// ten poniższy kod nie wykonuje się:
    for (int loop = 0; loop < listView1.Items.Count; loop++)
    {
        foreach (var selectedIndex in selectedIndices)
        {
            if (loop == Int32.Parse(selectedIndex.ToString()))
            {
                listView1.Items[loop - 1].Selected = true;
            }
        }
    }
    listView1.Select();
}

Jednak po kliknięciu przycisku przesuwającego elementy listy zostają one automatycznie odznaczane (w końcu cały listView za każdym razem jest czyszczony i tworzony od nowa) a to mi się nie podoba. Z tego powodu napisałem dodatkowy kod ponownie zaznaczający elementy, które zostały przesunięte (widać to pod koniec powyższego listingu). No i nie mam pojęcia dlaczego to nie działa. O dziwo zmienna var selectedIndices jest zerowana i tak jakby program zapominał, które wiersze były wcześniej zaznaczone. Wie ktoś dlaczego tak się dzieje i jak rozwiązać ten problem?

0

O dziwo zmienna var selectedIndices jest zerowana i tak jakby program zapominał, które wiersze były wcześniej zaznaczone. Wie ktoś dlaczego tak się dzieje i jak rozwiązać ten problem?

Kiedy zmienna jest zerowa? Jak się dzieje? Ten problem? Ze program zapomniał?
Nie mówie tego złośliwie, tak jak się wysławiasz tak myslisz, tak jak myslisz tak programujesz. Obydwie aktywności to przelewanie mysli na słowa.

if (loop == Int32.Parse(selectedIndex.ToString()))

Co to jest? Dlaczego parsujesz inta do stringa żeby zrobić znowu z niego inta?

for (int loop = 0; loop < sortedTracks.Count; loop++)

Dlaczego w pętlach używasz zmiennej loop a nie i?

Tak jak ktoś zauważył to bardzo dużo kodu do zamiany 2 wartości miejscami. Jesteś pewien ze nie przekobinowjesz, i nie da sie tego uprościć?

0

Rozdziel to na kilka funkcji, albo na mniejsze problemy.

Jeżeli chcesz przesuwać kilka na raz, to w przypadku przenoszania "niżej" musisz po kolei przesunąć o 1 w dół, zaczynając od tego najniżej, i w sumie nawet nie muszą być pokolei, ważne żeby sprawdzić czy ten zaznaczony najniżej nie wychodził poza zakres tablicy (nie był ostatnim)

W punktach zrobiłbym to tak

  1. pobrał listę zazaznaczonych
  2. w przypadku przenoszenia w dół odwrócił kolejność listy
  3. przeiterował listę i przesuwał po kolei elementy zapamietując listę elementów do zaznaczenia
  4. załadował listę do listview zaznaczając elementy z pkt3

W Kodzie można tak (nie ma tu sprawdzenia czy operacja jest mozliwa

        private void MoveUp_Click(object sender, EventArgs e)
        {
            var si = listView1.SelectedIndices.Cast <int>().ToList();
            List<int> ItemsToSelect = new List<int>();
            si.Reverse();
            foreach (int i in si)
            {
                MoveItem(i, 1);
                int itemID = i+1;
                ItemsToSelect.Add(itemID);
            }

            LoadListItem(ItemsToSelect);

        }
        private void MoveDown_Click(object sender, EventArgs e)
        {
            var si = listView1.SelectedIndices.Cast<int>().ToList();
            List<int> ItemsToSelect = new List<int>();
            foreach (int i in si)
            {
                MoveItem(i, -1);
                int itemID = i - 1;
                ItemsToSelect.Add(itemID);
            }

            LoadListItem(ItemsToSelect);

        }
        private void MoveItem(int ItemID, int step)
        {
            AlbumTrack TrackToMove = Tracks[ItemID];
            Tracks.RemoveAt(ItemID);
            Tracks.Insert(ItemID + step, TrackToMove);

        }
        private void LoadListItem(List<int> ItemsToSelect)
        {
            listView1.Items.Clear();
            var i = 1;
            foreach (AlbumTrack track in Tracks)
            {
                track.TrackNumber = i;
                string[] row = { track.DiscNumber.ToString(), track.TrackNumber.ToString(), track.TrackPerformer, track.TrackTitle, track.TrackComposer, (track.TrackDuration > 0 ? (track.TrackDuration >= 60 ? (Math.Floor(track.TrackDuration / 60).ToString() + "m " + Math.Round(track.TrackDuration % 60, 3).ToString("0.000") + "s") : Math.Round(track.TrackDuration % 60, 3).ToString("0.000") + "s") : "Unknown"), (track.TrackPregap > 0 ? (track.TrackPregap >= 60 ? (Math.Floor(track.TrackPregap / 60).ToString() + "m " + Math.Round(track.TrackPregap % 60, 3).ToString("0.000") + "s") : Math.Round(track.TrackPregap % 60, 3).ToString("0.000") + "s") : "None"), (track.FileExists ? track.Filename : "Error! File does not exist!"), track.DiscComment, track.DiscIdentifier };
                var listViewItem = new ListViewItem(row);
                listViewItem.Selected = ItemsToSelect.Contains(i-1);
                listView1.Items.Add(listViewItem);
                i++;
            }
        }

1 użytkowników online, w tym zalogowanych: 0, gości: 1