views:

397

answers:

4

I would like to have a playlist for my own music player in Delphi / Pascal.

I thought that it would be the best solution to have a TStringList with the path of the MP3 file and - additionally - a TListBox with the song names. The matching strings in both lists must be at the same position. So if the user chooses item 5 in TListBox I can just take the path at position 5 in the TStringList.

This works fine.

But now I need a playlist with two columns: "artist" and "song title". You should be able to sort the playlist by artist (ascending and descending) as well as by song title (ascending and descending) - alphabetically, of course.

How could I do this? Having two objects of TStringList - one sorted by artist and one sorted by song title?

+4  A: 

I would do a TSong class containing at least the Artist and Title properties, and a TSongList providing 1 or more sort methods (can be generic) using the proper sort Field(s.
Certainly not maintaining 2 separate StringLists that you have to manage, keep in sync and reshuffle when sorting...

One cheap way to kinda implement that, could be to have an in memory DataSet with a record containing Artist and Path displayed in a grid that you can sort on different columns.
The current row will give both informations directly.

François
The Dataset option is worth looking into in any case, since it's a good idea to persist song information.
mghie
I can't use TDataSet (Delphi doesn't know it!?) but the first paragraph was very helpful. Thank you!
+1  A: 

I've done a few of these "lists" over time and in the end I've always found making the classes rather easy, but storing and especially reading the lists from disk has proven "challenging" to say the least.

The challenge has been in cases were users actually manipulate the lists with external editors, thus making reading the lists error prone.

For a universally accepted playlist format (M3U) have a look at http://schworak.com/programming/music/playlist_m3u.asp.

A VCL component with source that reads multiple formats is available at Torry's called "PlayList v.0.5.1". http://www.torry.net/quicksearchd.php?String=PlayList+v.0.5.1&Title=Yes

K.Sandell
Thank you, interesting links :) But I don't look for a way to import playlist files. Instead, I'm looking for a way to handle playlists in my program (storing, sorting etc).
One way to go would be to use a TMyPlaylist = class(Tlist) object to handle the list itself, and then each entry could be a TMyPlaylistEntry = class(TObject). For sorting etc. you'd need to write your own compare code. Have a look at http://delphi.about.com/od/adptips2003/a/bltip1103_3.htm for details on that.
K.Sandell
Thank you very much! The links about M3U weren't what I was looking for but interesting, though. And your comment is exactly how I did it now.
+1  A: 

If you don't want to build an global object structure, you can allways use TlistView structure in report mode. You have there a list, with subitems. You can sort by column, and save to csv or whatever format. you can easily add icons etc.....

and you have the right events to trigger.

se
TListView has some interesting methods and options, thank you!
+2  A: 

One simple solution would be to implement your song list/song information as a TCollection.

By using collections you can let the VCL handle the loading and saving to disk.

For example:

Please note this is not functionally complete, I'll leave that up to you, and since I wrote this from the top of my head I might have messed something up. It is only an example to get you started.

{...}
interface

Type
  TSongCollectionItem = class(TCollectionItem)
  public
    constructor create(Owner:TCollection); override;
    procedure assign(source : TPersistent); override;
  published
    property FileName : String read fFileName Write fFileName;
    property Artist : string read fArtist write fArtist;
    property Title : string read fTitle write fTitle;
    {...}
    property Album : string read fAlbum write fAlbum;
  end;

  TSongCollection = class(TOwnedCollection)
  private
    function GetItem(Index: Integer): TSongCollectionItem;
    procedure SetItem(Index: Integer; Value: TSongCollectionItem);
  public
    constructor Create(AOwner: TPersistent);
    function Add: TSongCollectionItem;
    property Songs[Index: Integer]: TSongCollectionItem read GetItem write SetItem; default;
  end;

  procedure SaveSongList(Songs : TSongCollection; FileName:string; Binary:boolean);
  procedure LoadSongList(Songs : TSongCollection; FileName:string; Binary:boolean);

{...}

implementation

{...}

type  
  TSongComponent = class(TComponent)
  published
    property SongList : TSongCollection read fsonglist write SetSongList;
  end;

  procedure SaveSongList(Songs : TSongCollection; FileName:string; Binary:boolean);
  var
    wFile : TFileStream;
    wConvert : TMemoryStream;
    wSongList : TSongComponent;
  begin
    RegisterClass(TSongComponent);
    Try
      wConvert := TMemoryStream.Create;
      wFile := TFileStream.Create(filename, fmcreate);
      wSongList := TSongComponent.create(nil);
      try
        wSongList.SongList.Assign(Songs);
        if not Binary then
        begin
          wConvert.WriteComponent(wSongList);
          wConvert.Position := 0;
          ObjectBinaryToText(wConvert, wFile);
        end
        else
          wFile.WriteComponent(wSongList);
      finally
        wConvert.Free;
        wFile.Free;
        wSongList.free;
      end;
    finally
      Unregisterclass(TSongComponent);
    end;
  end;

  procedure LoadSongList(Songs : TSongCollection; FileName:string; Binary:boolean);
  var
    wFile : TFileStream;
    wConvert : TMemoryStream;
    wSongList : TSongComponent;
  begin
    RegisterClass(TSongComponent);
    Try
      wConvert := TMemoryStream.Create;
      wFile := TFileStream.Create(filename, fmOpenRead);
      try
        if not Binary then
        begin
          ObjectTextToBinary(wFile, wConvert);
          wConvert.Position := 0;
          wSongList := TSongComponent(wConvert.ReadComponent(Nil));
        end
        else
          wSongList := TSongComponent(wFile.ReadComponent(Nil));

        if assigned(Songs) and assigned(wSongList) then
          Songs.Assign(wSongList.Songs);

        if assigned(wSongList) then
          wSongList.free; 
      finally
        wConvert.Free;
        wFile.Free;
      end;
    finally
      Unregisterclass(TSongComponent);
    end;
  end;
Ryan J. Mills
Thank you very much, I could use several parts of your code :) Very helpful!