0

I made a music player in C# and the only problem is the playlists context menu strip when you right click on the track. When the music is in a playlist, it checks; when it is not in a playlist, it is not checked.

Note: the items are dynamically added when the user right click on the music.

My solutions:

  1. Tried to change render mode, didn't change a single thing
  2. Tried to force check the checkbox using the method but still does not work so it goes next method I tried
  3. Tried to force check only using a normal line of code
  4. Tried to change method to check the items

Here's the code:

public static void PlaylistsOpening(object sender, CancelEventArgs e, object instance, AudioFileInfo music)
{
    if (instance is Form1 form1)
    {
        if (form1.cmsPlaylists.InvokeRequired)
        {
            form1.cmsPlaylists.Invoke(new MethodInvoker(delegate { form1.cmsPlaylists.Items.Clear(); }));
        }
        else
        {
            form1.cmsPlaylists.Items.Clear();
        }

        Debug.WriteLine(sender.GetType().Name);
        IEnumerable<Playlist> playlists = Playlist.GetAllPlaylists();
        Debug.WriteLine(playlists.Count());
        foreach (Playlist playlist in playlists.ToList())
        {

            ToolStripMenuItem item = new ToolStripMenuItem
            {
                Tag = playlist,
                Text = playlist.Name
            };

            if (Playlist.GetPlaylistsWithAudioFile(music).Contains(playlist))
            {
                Debug.WriteLine("Contains");
                item.Checked = true;
            }

            item.Click += delegate
            {
                Debug.WriteLine("Clicked");
                var nItem = form1.cmsPlaylists.GetItemsFromTag(playlist)[0];
                if (nItem.CheckState == CheckState.Checked)
                {
                    Debug.WriteLine("Checked");
                    music.RemoveFromPlaylist((Playlist)nItem.Tag);
                    nItem.CheckState = CheckState.Unchecked;
                }
                else
                {
                    Debug.WriteLine("Unchecked");
                    music.AddToPlaylist((Playlist)nItem.Tag);
                    nItem.CheckState = CheckState.Checked;
                }
            };
            music.Volume = form1.SoundVolume;

            form1.cmsPlaylists.Items.Add(item);
        }
                
        if (form1.cmsPlaylists.InvokeRequired)
        {
            ToolStripMenuItem item = new ToolStripMenuItem()
            {
               Text = "Create a new playlist..."
            };

            item.Click += delegate
            {
                using (DialogPlaylist dialogPlaylist = DialogPlaylist.CreatePlaylist())
                {
                    if (dialogPlaylist.ShowDialog() == DialogResult.OK)
                    {
                        form1.cmsPlaylists.Invoke(new MethodInvoker(delegate
                        {
                            ToolStripMenuItem nItem = new ToolStripMenuItem
                            {
                                Text = dialogPlaylist.Tag.Name,
                                Tag = dialogPlaylist.Tag,
                                CheckOnClick = true,
                                Checked = true
                            };

                            nItem.Click += delegate
                            {
                                if (nItem.Checked)
                                {
                                    music.RemoveFromPlaylist((Playlist)nItem.Tag);
                                }
                                else
                                {
                                    music.AddToPlaylist((Playlist)nItem.Tag);
                                        }
                                    };

                                    form1.cmsPlaylists.Items.Add(nItem);
                                }));
                            }
                        }
                    };
                }
                else
                {
                    void AddPlaylist(object sender1, EventArgs e1)
                    {
                        using (DialogPlaylist dialogPlaylist = DialogPlaylist.CreatePlaylist())
                        {
                            if (dialogPlaylist.ShowDialog() == DialogResult.OK)
                            {
                                ToolStripMenuItem nItem = new ToolStripMenuItem()
                            {
                            Text = dialogPlaylist.Tag.Name,
                            Tag = dialogPlaylist.Tag,
                            CheckOnClick = true,
                            Checked = true
                        };

                        nItem.Click += delegate
                        {
                            if (nItem.Checked)
                            {
                                music.RemoveFromPlaylist((Playlist)nItem.Tag);
                            }
                            else
                            {
                                music.AddToPlaylist((Playlist)nItem.Tag);
                            }
                        };

                        form1.cmsPlaylists.Items.Add(nItem);
                    }
                }
            }

            ToolStripMenuItem item = new ToolStripMenuItem()
            {
                Text = "Create a new playlist..."
            };

            item.Click += (s, e2) => AddPlaylist(s, e2);

            form1.cmsPlaylists.Items.Add(item);
        }
    }
    else
    {
        return;
    }
}```

And here's how to use that method: ```
private void PlaylistsContextMenuStrip_Opening(object sender, System.ComponentModel.CancelEventArgs e)
        {
            Debug.WriteLine("Opening...");
            MusicPlayer.Events.PlaylistsOpening(sender, e, this, currentFile);
        }```
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Error404
  • 55
  • 8

1 Answers1

0

As I understand it, you're repopulating a context menu every time a music item gets a right click and the context menu is shown. Since you're creating new ToolStripMenuItem instances anyway, consider providing all the information needed for Add/Remove operations for the music item at the time of creation. One way to do this in an efficient manner is to make a custom TSMI class:

class PlaylistToolStripItem : ToolStripMenuItem
{
    public PlaylistToolStripItem(Playlist playlist, Music music)
    {
        Playlist = playlist;
        Music = music;
        Text = playlist.Name;
        Checked = playlist.Contains(music);
        base.Click += Click;            
    }
    public Playlist Playlist { get; }
    public Music Music { get; }
    public static new event EventHandler Click;
}

Making the click event static means main form subscribes to it just once.

PlaylistToolStripItem.Click += (sender, e) =>
{
    if (sender is PlaylistToolStripItem tsmiPlus)
    {
        Debug.WriteLine($"Clicked Playlist: {tsmiPlus.Playlist.Name} Music: {tsmiPlus.Music}");
        if(tsmiPlus.Checked) tsmiPlus.Playlist.Remove(tsmiPlus.Music);
        else tsmiPlus.Playlist.Add(tsmiPlus.Music);
    }
};

Context Menu Example Code

The context menu accurately depicts the playlists that contain the right-clicked item and the add/remove behavior works as expected.

screenshot

dataGridViewMusic.CellMouseDown += (sender, e) =>
{
    if (e.Button.Equals(MouseButtons.Right) && !e.RowIndex.Equals(-1))
    {
        var menu = new ContextMenuStrip();
        var music = _allMusic[e.RowIndex];
        dataGridViewMusic.ClearSelection();
        dataGridViewMusic.Rows[e.RowIndex].Selected = true;
        foreach (Playlist playlist in _allPlaylists)
        {
            menu.Items.Add(new PlaylistToolStripItem(playlist, music));
        }
        menu.Show(new Point(MousePosition.X + 30, MousePosition.Y - 30));
    }
};
IVSoftware
  • 5,732
  • 2
  • 12
  • 23
  • [Clone](https://github.com/IVSoftware/playlist-compare.git) the full demo example. – IVSoftware Apr 01 '23 at 17:35
  • Now I just realized that the event raises... 5 times on a single click on the item. So it does false -> true -> false -> true -> false (for Checked property). So it does not add to the playlist. I cloned your demo example, it works as it should but when I add your code into my project it raises that click event 5 times. What can cause that issue? – Error404 Apr 02 '23 at 09:32
  • When you say the event is _raised_ multiple times I assure you it's only _raised_ one time per click but the behavior you're describing can happen when that single event has been inadvertently _subscribed to_ multiple times with `item.Click += ()=>{ /*Do Something*/}`. If you're trying to adapt my sample, you shouldn't see `item.Click +=` at all because the `Click` event has been routed to one central handler for any and all `PlaylistToolStripItem.Click` and this happens in the main form load. Does that help? – IVSoftware Apr 02 '23 at 13:40
  • Thank, the event raises only once because I forgot to remove from the event method but now, the item won't check and I still don't know why. – Error404 Apr 02 '23 at 13:56
  • Just so I understand, you say this is something that works OK in the demo but doesn't work when you apply it in your own code? – IVSoftware Apr 02 '23 at 13:58
  • Yes. I did exactly how you did. I don't understand why it always returns false... I tried to simply put another event on the base.Click (`base.Click += (sender, e) => { Checked = Playlist.Musics.Contains(Music); };` (Playlist.Music in an IEnumerable (works same as music but with more data). Music I changed it as AudioFileInfo – Error404 Apr 02 '23 at 14:01
  • Then the fault must lie in the `Contains` expression you're using. Make sure you understand how `Contains` works. Refer to this answer: https://stackoverflow.com/a/3638114/5438626. You may want to use **System.Linq.Any()** instead like `Checked = playlist.Any(_=>_.Name.Equals(music.Name))` _or_ implement `IEquatable` for the class that represents `music`. – IVSoftware Apr 02 '23 at 14:11
  • For example, if `Contains` is allowed to use default `Equals` then it could return `false` for a `music` where `Name` and `Artist` are the same, but the `Volume` is different! Obviously you will want to tailor that behavior. – IVSoftware Apr 02 '23 at 16:43
  • I don't know, I'm gonna check this. I made a custom equals and GetHashcode method. They do not compare volume and playlists. – Error404 Apr 02 '23 at 16:55
  • Everything works but... The `Checked` property value does not set, never sets. I don't understand. The playlist is not null, the methods return `true`, all methods. In your project this does not happen. – Error404 Apr 02 '23 at 17:22
  • If I understand, the "smoking line" has to be `Checked = playlist.Contains(music)`. If it were me, I'd want to make a little Unit Test or equivalent to test the `playlist.Contains(music)` expression with some known values for `music` in isolation from the other code. Also make sure it's really hitting your custom `Compare` method. Also make sure that `GetHashCode` isn't factoring in some non-essential property. I really _do_ want to see you get this working... – IVSoftware Apr 02 '23 at 17:29
  • It returns true, but it does not check on the UI. As you may know, the user can not know if it was added to the playlist. Maybe because I'm on .NET Framework 4.7.2? You are on .NET Core 3.1, it changes a lot of thing in the code. Is it a good idea to take all the code and put it on a .NET Core 3.1 project? – Error404 Apr 02 '23 at 20:44