Сохранение индекса Item из Listbox WinForms

есть у меня ListBox с песнями. Хочу реализовать поиск, но пока он только оставляет названия песен, при выборе песен из поиска, программа выбирает песню из основного listbox, а не отфильтрованного. Скорее это связано с тем, что я не оставляю еще индекс из реального listbox, если это так, то я не знаю решения. Как правильно реализовать?

 private void My_Music_Load(object sender, EventArgs e)
 {
     ActiveControl = SearchTextBox;
     AudioList = vk.GetAudio();
     foreach (Thread item in ThreadsList)
     {
         item.Start();
     }
     foreach (var audio in AudioList)
     {
         Music_List.Items.Add(audio.Artist + " - " + audio.Title);
     }
 }

 
private void Music_List_Click(object sender, EventArgs e)
{
    int selectedIndex = Music_List.SelectedIndex;
    VkNet.Model.Attachments.Audio audio = AudioList[selectedIndex];
    string Subtitle = audio.Subtitle;
    Invoke(() => TitleBox.Text = audio.Title + " " + audio.Subtitle);
    Invoke(() => ArtistBox.Text = audio.Artist);
    if (audio.Album != null)
    {
        Invoke(() => AlbumImage.Image = System.Drawing.Image.FromFile($"Albums\\{audio.Album.Id}.png"));
    }
    else
    {
        AlbumImage.Image = Properties.Resources.music;
    }
}

private void Music_List_DoubleClick(object sender, EventArgs e)
{
    int selectedIndex = Music_List.SelectedIndex;
    if (selectedIndex != -1)
    {
        Task.Run(() => PlayMusic(selectedIndex));
    }

}

private void SearchTextBox_TextChanged(object sender, EventArgs e)
 {
     Music_List.Items.Clear();
     
     foreach (var audio in AudioList)
     {
         if (audio.Artist.ToLower().Contains(SearchTextBox.Text.ToLower()) || audio.Title.ToLower().Contains(SearchTextBox.Text.ToLower()))
         {
             Music_List.Items.Add(audio.Artist + " - " + audio.Title);
         }
     }
 }

 
private async void PlayMusic(int ID)
{

    Invoke(() => Player.Stop());
    VkNet.Model.Attachments.Audio audio = AudioList[ID];
    CurrentTrackID = ID;
    if (RandomPlay)
    {
        RandomCurrentTrackID = RandomTrackIDList.IndexOf(ID);
    }
    string Subtitle = audio.Subtitle;
    Invoke(() => TitleBox.Text = audio.Title + " " + audio.Subtitle);
    Invoke(() => ArtistBox.Text = audio.Artist);    

    if (audio.Album != null)
    {
        Invoke(() => AlbumImage.Image = System.Drawing.Image.FromFile($"Albums\\{audio.Album.Id}.png"));
    }
    else
    {
        AlbumImage.Image = Properties.Resources.music;
    }
    await Task.Run(() => LoadMusic(ID));
    if (CurrentTrackID == ID)
    {
        try
        {
            Invoke(() => Player.Open(new Uri($"{Directory.GetCurrentDirectory()}\\Music\\{audio.Title}_{Subtitle}_{audio.Artist}.mp3")));
            Invoke(() => Player.Play());
        }
        catch { }
    }
}

public static async Task LoadMusic(int ID)
{
    VkNet.Model.Attachments.Audio audio = AudioList[ID];
    string Subtitle = audio.Subtitle;
    string Title = audio.Title;
    string Artist = audio.Artist;
    string FileName;
    if (!File.Exists($"Music\\{Title}_{Subtitle}_{Artist}.mp3"))
    {
        try
        {
            FileName = $"{Title.Substring(0, 2)}{Artist.Substring(0, 2)}{random.Next(0, 999999)}";
        }
        catch
        {
            FileName = $"{random.Next(0, 999999)}";
        }
        ExecuteCommand($"streamlink {audio.Url} live --output {FileName}.ts");
        ExecuteCommand($"ffmpeg -i {FileName}.ts -vn -ar 44100 -ac 2 -b:a 192k {FileName}.mp3");
        try
        {
            File.Delete($"{FileName}.ts");
        }
        catch { }
        try
        {
            File.Move($"{FileName}.mp3", $"Music\\{Title}_{Subtitle}_{Artist}.mp3");
        }
        catch { }
    }
}

введите сюда описание изображения


Ответы (2 шт):

Автор решения: rotabor

Не понятно, в чём проблема, но код можно улучшить:

private void SearchTextBox_TextChanged(object sender, EventArgs e) {
    Music_List.Items.Clear();
    var st = SearchTextBox.Text.ToLower();
    Music_List.Items.AddRange(AudioList
            .Where(w => w.Artist.ToLower().Contains(st) || w.Title.ToLower().Contains(st))
            .Select(s => s.Artist + " - " + s.Title));
}

Если нужно идентифицировать выбранный трек, то стоит добавить какой-нибудь Id, если он есть:

            .Select(s => [s.Artist + " - " + s.Title, s.Id]));

Или пронумеровать элементы AudioList:

    var i = 0;
    Music_List.Items.AddRange(AudioList.Select(s => new {A = s, N = i++ })
            .Where(w => w.A.Artist.ToLower().Contains(st) || w.A.Title.ToLower().Contains(st))
            .Select(s => new object[] {s.A.Artist + " - " + s.A.Title, s.N}));

Потом по клику в Music_List получаем N кликнутого и по нему выбираем трек из AudioList:

void Music_List_SelectedIndexChanged(object sender, System.EventArgs e) { 
    var audio = AudioList[(int)((object [])Music_List.SelectedItem)[1]];
                       // ^ тут нужо уточнить, как к номеру добраться
}
→ Ссылка
Автор решения: aepot

Так как сторонний класс Audio не переписать, а нужен источник данных для отображения, создам класс-обёртку.

using VkNet.Model.Attachments;
public class AudioContainer
{
    public Audio Audio { get; }
    public string DisplayTitle { get; }

    public AudioContainer(Audio audio)
    {
        Audio = audio;
        DisplayTitle = $"{audio.Artist} - {audio.Title}";
    }
}

Поменяю тип AudioList, кстати использование суффиксов list и array неудобно, давайте сразу назовём массив правильно.

private AudioContainer[] Audios { get; set; }

Теперь метод загрузки, здесь какрй-то пуск потоков, я его уберу так как оно к делу не относится.

private void My_Music_Load(object sender, EventArgs e)
{
    var audios = vk.GetAudio();
    
    Audios = audios.Select(x => new AudioContainer(x)).ToArray();
    Music_List.DataSource = Audios;
    Music_List.DisplayMember = nameof(AudioContainer.DisplayTitle);
}

ListBox загружен.

Если смущает Select, можно сделать жто циклом

int count = (int)audios.TotalCount;
Audios = new AudioContainer[count];
for (int i = 0; i < count; i++)
{
    Audios[i] = new AudioContainer(audios[i]);
}

Но Select.ToArray выглядит короче, верно? Поизучайте Linq.

Теперь при вводе текста в фильтр делаем так

private void SearchTextBox_TextChanged(object sender, EventArgs e)
{
    TextBox box = (TextBox)sender;
    Music_List.DataSource = Audios
        .Where(x => x.DisplayTitle.Contains(box.Text, StringComparison.CurrentCultureIgnoreCase))
        .ToArray();
}

Теперь перепишем PlayMusic, чтобы он принимал Audio, а не индекс. Кстати, етод асинзронный, асинзронность будем использовать правильно.

private async Task PlayMusic(AudioContainer container)
{
    Player.Stop();
    var audio = container.Audio;
    TitleBox.Text = audio.Title + " " + audio.Subtitle;
    ArtistBox.Text = audio.Artist;    

    if (audio.Album != null)
    {
        AlbumImage.Image = System.Drawing.Image.FromFile(Path.Combine("Albums", $"{audio.Album.Id}.png"));
    }
    else
    {
        AlbumImage.Image = Properties.Resources.music;
    }

    string path = Path.Combine(Environment.CurrentDirectory, "Music", $"{audio.Title}_{audio.Subtitle}_{audio.Artist}.mp3");
    await Task.Run(() => LoadMusic(audio, path));

    Player.Open(new Uri(path));
    Player.Play();
}

Почистил лишние инвоки.

Теперь вызов. Нет никакого смысла здесь в Task.Run.

private async void Music_List_DoubleClick(object sender, EventArgs e)
{
    var container = Music_List.SelectedItem as AudioContainer;
    try
    {
        if (container != null)
        {
            await PlayMusic(container.Audio);
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

За компанию, вот вам клик

private void Music_List_Click(object sender, EventArgs e)
{
    var container = Music_List.SelectedItem as AudioContainer;
    if (container == null)
        return;

    var audio = container.Audio;
    TitleBox.Text = audio.Title + " " + audio.Subtitle;
    ArtistBox.Text = audio.Artist;

    if (audio.Album != null)
    {
        AlbumImage.Image = System.Drawing.Image.FromFile(Path.Combine("Albums", "{audio.Album.Id}.png");
    }
    else
    {
        AlbumImage.Image = Properties.Resources.music;
    }
}

Метод загрузки немного упростим

public static void LoadMusic(Audio audio, string path)
{
    if (!File.Exists(path))
    {
        string tmpFile = Path.GetTempFileName();
        ExecuteCommand($"streamlink {audio.Url} live --output {tmpFile}.ts");
        ExecuteCommand($"ffmpeg -i {tmpFile}.ts -vn -ar 44100 -ac 2 -b:a 192k {tmpFile}.mp3");
        File.Delete($"{tmpFile}.ts");
        File.Move($"{tmpFile}.mp3", path);
    }
}

Теперь переключение между треками, покажу фрагменты кода.

private AudioContainer CurrentTrack;

Текущий

CurrentTrack = Audios[0];

Сбросить

CurrentTrack = null;

Следующий без зацикливания

int index = Array.IndexOf(Audios, CurrentTrack);
if (index >= Audios.Length - 1 || index < 0)
    CurrentTrack = null;
else
    CurrentTrack = Audios[index + 1];

Следующий с зацикливанием

if (index < 0)
    CurrentTrack = null;
else
    CurrentTrack = Audios[(index + 1) % Audios.Length];

Случайный

if (index >= 0 && Audios.Length > 1)
{
    while (true)
    {
        int rnd = Random.Shared.Next(Audios.Length);
        if (rnd != index)
        {
            CurrentTrack = Audios[rnd];
            break;
        }
    }
}
else
    CurrentTrack = null;
→ Ссылка