|
|
||||||||||||||||||||||||
|
MusicLibrary SGrid Sample
This sample uses SGrid 2.0 along with the MP3 Tags code to load, display and persist a Music Library. It allows library import, sorting, grouping and in the future should be upgraded to include tag editing and play functions. It has been successfully used with over 7,000 tracks. About The Music Library
This sample was created for two reasons: firstly,
during development of SGrid 2.0, I wanted to be sure that it was going to provide
the necessary performance to be a useful control. Secondly, I wanted something
better than the useless MusicMatch which came with my iPod, and Media Player.
(The licence for the latest version of Media Player appears to require you to accept
that Microsoft are in complete control of your PC, you, your family and
that you specifically allow Microsoft to use you at any point in time to perform
menial and/or degrading tasks for them at your expense. This is surely reasonable since
in return Microsoft gracefully offer no warranty, liability or any sort of responsiblity
for any problems or issues that arise through use of the software, up to and including
inadvertent triggering of nuclear war, famine, the end of the universe etc,
etc. Anyway, as it stands this application doesn't fulfil the second point (for a start, "playing" is just enqueueing to WinAmp, and there's no Find facility nor tag editing yet) but it is fine for the first. Inside the Music LibraryThe application itself is a fairly simple implementation of the grid. The first thing it does is to set up a colour theme for the grid, using the default properties. You could use the Hue, Luminance and Saturation code here to provide a neat user option to customise these colours. The grid is set up with a moss-green theme, alternate rows being slightly highlighted and group rows having a darker shade. The highlight colour is set to white, and then this is alpha-blended with the background colour of the grid. Finally, the grid is set to multi-select row mode and hot-tracking.
With grdLib
.BackColor = RGB(80, 120, 100)
.AlternateRowBackColor = RGB(84, 126, 105)
.GroupRowBackColor = RGB(56, 84, 70)
.GroupingAreaBackColor = .BackColor
.ForeColor = RGB(243, 247, 245)
.GroupRowForeColor = .ForeColor
.HighlightForeColor = vbWindowText
.HighlightBackColor = RGB(255, 255, 255)
.NoFocusHighlightBackColor = RGB(200, 200, 200)
.SelectionAlphaBlend = True
.SelectionOutline = True
.DrawFocusRectangle = False
.HighlightSelectedIcons = False
.HotTrack = True
.RowMode = True
.MultiSelect = True
End With
Next, the columns are added. The control is configured to sort most tags alphabetically without case-sensitivity, with track number and year set to sort numerically (if these tags do not contain a number, the item will be sorted to the end of the list). The numeric fields are also set to right-align in the cells, and the last column is stretched to fit the available grid size (note that the StretchLastColumnToFit should ideally always be set after adding the columns, to prevent columns being incorrectly sized during set up).
With grdLib
' Add the columns:
.AddColumn "Title", "Title", _
lColumnWidth:=192, eSortType:=CCLSortStringNoCase
.AddColumn "Artist", "Artist", _
lColumnWidth:=128, eSortType:=CCLSortStringNoCase
.AddColumn "Album", "Album", _
lColumnWidth:=192, eSortType:=CCLSortStringNoCase
.AddColumn "Track", "Track", _
eAlign:=ecgHdrTextALignRight, eSortType:=CCLSortNumeric
.AddColumn "Genre", "Genre", _
bVisible:=False, eSortType:=CCLSortStringNoCase
.AddColumn "Filename", "Filename", _
eSortType:=CCLSortStringNoCase
.AddColumn "TagType", "TagType", _
eAlign:=ecgHdrTextALignRight, bVisible:=False, _
eSortType:=CCLSortStringNoCase
.AddColumn "Year", "Year", _
eAlign:=ecgHdrTextALignRight, bVisible:=False, _
eSortType:=CCLSortNumeric
.AddColumn "LastImportFileDate", "", , , , bVisible:=False
.AddColumn "Comments", "Comments", bRowTextColumn:=True
.StretchLastColumnToFit = True
End With
The invisible "LastImportFileDate" is used to allow the library to track the file date of each MP3 file that is imported; this allows future imports to detect whether the tags need to be rechecked. Once the grid is configured, we can load and save the library. For performance, the grid's internal persistence mechanism (LoadGridData and SaveGridData) is used. For a 7,000 track library, the data file is typically 3-4Mb, and loads and saves take around a second on an Athlon 2000XP machine. The code also calls the SelectionChange event after loading to ensure that the menus reflect no selection:
Private Sub loadLibrary()
Dim sFile As String
sFile = musicLibFile()
If (fileExists(sFile)) Then
grdLib.LoadGridData sFile
grdLib_SelectionChange 0, 0
End If
End Sub
Private Sub saveLibrary()
Dim sFile As String
Dim sBackFile As String
sFile = musicLibFile()
If (fileExists(sFile)) Then
' Make a backup:
sBackFile = musicLibBackupFile()
killFileIfExists sBackFile
FileCopy sFile, sBackFile
killFileIfExists sFile
End If
If (grdLib.Rows > 0) Then
grdLib.SaveGridData musicLibFile()
Else
killFileIfExists sFile
End If
End Sub
Now data can be added to the grid. Data is added directly from the results of the cMP3ID3v1 or cMP3ID3v2 data for each MP3 file that's imported into the library, and the cell format is set to truncate the text and either align it right- or left- depending on the column type. Note that DT_PATH_ELLIPSIS is supported, which truncates file names whilst attempting to preserve as much of the information about the file as possible (for more information, see also the tip on compacting paths to fit a given space):
Private Sub processMp3File(ByVal sFile As String)
Dim c1 As New cMP3ID3v1
Dim c2 As New cMP3ID3v2
Dim sType As String
Dim lRow As Long
Dim dFileLastImport As Date
Dim dFileDateTime As Date
dFileDateTime = FileDateTime(sFile)
lRow = findMp3Row(sFile)
If (lRow > 0) Then
' Check whether need to process this file or not:
dFileLastImport = grdLib.CellText(lRow, 9)
If (dFileDateTime <= dFileLastImport) Then
' unchanged
Exit Sub
End If
Else
' Add a new row
grdLib.AddRow
lRow = grdLib.Rows
End If
c1.MP3File = sFile
If (c1.HasID3v1Tag) Then
sType = "1"
End If
c2.MP3File = sFile
If (c2.HasID3v2Tag) Then
If (Len(sType) > 0) Then
sType = sType & ","
End If
sType = sType & "2"
End If
If (c2.HasID3v2Tag) Then
grdLib.CellText(lRow, 1) = c2.Title
grdLib.CellText(lRow, 2) = c2.Artist
grdLib.CellText(lRow, 3) = c2.Album
grdLib.CellText(lRow, 4) = c2.Track
grdLib.CellText(lRow, 5) = c2.GenreName(c2.Genre)
grdLib.CellText(lRow, 8) = c2.Year
grdLib.CellText(lRow, 10) = c2.Comment
ElseIf (c1.HasID3v1Tag) Then
grdLib.CellText(lRow, 1) = c1.Title
grdLib.CellText(lRow, 2) = c1.Artist
grdLib.CellText(lRow, 3) = c1.Album
grdLib.CellText(lRow, 4) = c1.Track
grdLib.CellText(lRow, 5) = c1.GenreName(c1.Genre)
grdLib.CellText(lRow, 8) = c1.Year
grdLib.CellText(lRow, 10) = c1.Comment
Else
grdLib.CellText(lRow, 1) = fileNameOf(sFile)
End If
grdLib.CellText(lRow, 6) = sFile
grdLib.CellText(lRow, 7) = sType
grdLib.CellText(lRow, 9) = dFileDateTime
grdLib.CellTextAlign(lRow, 6) = DT_LEFT Or DT_SINGLELINE Or DT_PATH_ELLIPSIS
grdLib.CellTextAlign(lRow, 4) = DT_RIGHT Or DT_SINGLELINE Or DT_END_ELLIPSIS
grdLib.CellTextAlign(lRow, 7) = DT_RIGHT Or DT_SINGLELINE Or DT_END_ELLIPSIS
grdLib.CellTextAlign(lRow, 8) = DT_RIGHT Or DT_SINGLELINE Or DT_END_ELLIPSIS
End Sub
The import function uses the filename part of the file to import to match with an existing item in the grid; if it is already there the code checks the file date time against the last import file date time to confirm whether there has been a change not. Once some data has been loaded, you can start sorting, grouping and working with the cells as the user selects them. Firstly, sorting. The grid responds to column click events and configures the SortObject to sort the data. Any groupings that have been configured in the grid are preserved in the SortObject by using the ClearNongrouped method to initialise the sort object so it only contains sort information about the grouped rows. Once a sort has been chosen, the column is used to cache the order and a header icon set to show the sort direction:
Dim iCol As Long
Dim iSortCol As Long
With grdLib.SortObject
.ClearNongrouped
iSortCol = .IndexOf(lCol)
If (iSortCol <= 0) Then
iSortCol = .Count + 1
End If
.SortColumn(iSortCol) = lCol
If (grdLib.ColumnSortOrder(lCol) = CCLOrderNone) Or _
(grdLib.ColumnSortOrder(lCol) = CCLOrderDescending) Then
.SortOrder(iSortCol) = CCLOrderAscending
Else
.SortOrder(iSortCol) = CCLOrderDescending
End If
grdLib.ColumnSortOrder(lCol) = .SortOrder(iSortCol)
.SortType(iSortCol) = grdLib.ColumnSortType(lCol)
' Place ascending/descending icon:
For iCol = 1 To grdLib.Columns
If (iCol <> lCol) Then
If Not (grdLib.ColumnIsGrouped(iCol)) Then
If grdLib.ColumnImage(iCol) > -1 Then
grdLib.ColumnImage(iCol) = -1
End If
End If
ElseIf grdLib.ColumnHeader(iCol) <> "" Then
grdLib.ColumnImageOnRight(iCol) = True
If (.SortOrder(iSortCol) = CCLOrderAscending) Then
grdLib.ColumnImage(iCol) = 0
Else
grdLib.ColumnImage(iCol) = 1
End If
End If
Next iCol
End With
Screen.MousePointer = vbHourglass
grdLib.Sort
Screen.MousePointer = vbDefault
Grouping is enabled either through drag-drop or through a context menu displayed when right-clicking on a column. Header right-clicks are notified to the application by the SGrid HeaderRightClick event, which passes the coordinates of the mouse click. You then use the ColumnHeaderFromPoint function to determine which (if any) column header was clicked; this function returns zero if the user clicked on a non-column part of the header control. Once the column is determined, the context menu is set up to show allowable options, including an option at index 3 to allow the column to be grouped or removed from the grouping and the popip menu is shown at the right point by adjusting the click coordinates to the coordinates of the form:
Private Sub grdLib_HeaderRightClick( _
ByVal x As Single, ByVal y As Single _
)
Dim lCol As Long
lCol = grdLib.ColumnHeaderFromPoint(x, y)
If (lCol > 0) Then
mnuHeaderCtx(0).Enabled = True
mnuHeaderCtx(1).Enabled = True
mnuHeaderCtx(3).Enabled = True
mnuHeaderCtx(3).Caption = IIf( _
grdLib.ColumnIsGrouped(lCol), _
"Don't Group By This Field", _
"Group By This Field")
mnuHeaderCtx(6).Enabled = True
mnuHeaderCtx(0).Checked = _
(grdLib.ColumnSortOrder(lCol) = CCLOrderAscending)
mnuHeaderCtx(1).Checked = _
(grdLib.ColumnSortOrder(lCol) = CCLOrderDescending)
Else
mnuHeaderCtx(0).Enabled = False
mnuHeaderCtx(1).Enabled = False
mnuHeaderCtx(3).Enabled = False
mnuHeaderCtx(6).Enabled = False
mnuHeaderCtx(0).Checked = False
mnuHeaderCtx(1).Checked = False
End If
x = (x + grdLib.ScrollOffsetX) * Screen.TwipsPerPixelX _
+ grdLib.Left
y = y * Screen.TwipsPerPixelY + grdLib.TOp
' Cache column in the tag of the menu:
mnuHeaderCtxTOP.Tag = lCol
Me.PopupMenu mnuHeaderCtxTOP, , x, y
End Sub
Finally, the ability to act on selected items in the grid control can be added. For this application, the available commands depend upon whether the selection is a single file, or multiple files. Selection of a group row is taken to mean selection of all items within the group. The grid's SelectionChange event is used to configure the application menus for the selection (although note there is a bug with multiple selections which means Selection Change isn't always fired when an item is added to the selection, so this is augmented by responding to MouseUp events too):
Private Sub grdLib_SelectionChange(ByVal lRow As Long, ByVal lCol As Long)
Dim iSelCount As Long
iSelCount = grdLib.SelectionCount
If (lRow > 0) And (lCol > 0) Then
If (grdLib.RowIsGroup(lRow) Or (iSelCount > 1)) Then
' Multi-selection logic here
Else
' Single selection logic here
End If
Else
' No selection logic here
End If
End Sub
Private Sub grdLib_MouseUp( _
Button As Integer, Shift As Integer, _
x As Single, y As Single)
'
' TODO: bug for multi select: doesn't always raise selection
' changed...
If (Button = vbLeftButton) Or (Button = vbRightButton) Then
Dim lRow As Long
Dim lCol As Long
grdLib.CellFromPoint _
x \ Screen.TwipsPerPixelX, y \ Screen.TwipsPerPixelY, _
lRow, lCol
grdLib_SelectionChange lRow, lCol
End If
End Sub
Timing DataHere are some timings taken from importing files an AthlonXP/2000 with 512Mb RAM from a 60Gb/5400rpm Western Digital Hard Drive (Athlon64/3400/SATA timings coming soon!). All tasks shown for 4,200 MP3 files:
ConclusionThis article provides a Music Library sample for SGrid 2.0 which although incomplete provides a good starting point and also demonstrates the grid's performance.
|
|||||||||||||||||||||||
|
|
||||||||||||||||||||||||