MP3 Duration Calculator 2.0
Version 2.0 is usually where you discover if your system is well designed. If your design had too many implicit assumptions about what it should support, then you will have a hard time accommodating the changes and new requirements for version 2.0. In general, there are two types of requirements that are difficult: Changes to existing functionality, and the addition of new functionality. In a well-designed application, adding new functionality should be straightforward and have little impact on the existing code. The complete source code and related files MDC Version 2.0 are available here.
In this case most of the requirements are for new functionality. The changes to existing functionality are making the total duration more prominent and changing the way songs are selected.
Make total duration more prominent
In Version 1.0 the total duration text box was at bottom of the window squished between the select/unselect buttons and the status text box; see Figure 1.
In Version 2.0, it moved to the top-right corner of the window and its text became blue and bold; see Figure 2.
WPF was designed to support this kind of changes where you move elements around and modify some properties without any code changes (unless you think of XAML as code, which a perfectly valid point of view). All I had to do modify the XAML a bit. Here is the code for the bottom strip in Version 1.0:
<DockPanel
DockPanel.Dock="Bottom"
LastChildFill="True"
Height="30"
Margin="0" VerticalAlignment="Stretch"
>
<Button DockPanel.Dock="Left" Margin="3" Click="SelectAll_Click">
Select All
</Button>
<Button DockPanel.Dock="Left" Margin="3" Click="UnselectAll_Click">
Unselect All
</Button>
<TextBox DockPanel.Dock="Left" Height="22"
Name="total" Width="60" Margin="3"
Text="{Binding Path=Self, Converter={StaticResource DurationConverter}}">
</TextBox>
<TextBox Height="22" Name="status" MinWidth="100" Margin="3" />
</DockPanel>
In Version 2.0 it is in the top strip. Note that the font size is 14 compared to 11 of all the other controls and the foreground color is Blue. That was trivial to do in the properties pane of Visual Studio.
<DockPanel
DockPanel.Dock="Top"
LastChildFill="True"
Height="30"
Margin="0" VerticalAlignment="Stretch"
>
<Button DockPanel.Dock="Left" Height="22" Name="btnSelectFolder" Width="84" Margin="3" Click="btnSelectFolder_Click">
Browse...
</Button>
<TextBox DockPanel.Dock="Right" Margin="3" Height="22" Name="total"
Width="60" Text="{Binding Path=Self, Converter={StaticResource DurationConverter}}"
Foreground="Blue" TextDecorations="None" FontWeight="Bold" FontSize="14" Background="White" TextAlignment="Center" VerticalContentAlignment="Center"
>
</TextBox>
<TextBox Height="22" Name="tbTargetFolder" MinWidth="258" Margin="3"
TextChanged="tbTargetFolder_TextChanged"
>
</TextBox>
</DockPanel>
The data binding that populates the total field doesn't really care where it is located and how big is the font and continues to work just fine.
Check boxes for selection (one song toggle)
It was straightforward to add the check boxes (see Figure 3).
Remember that WPF and XAML are all about composition and nesting. But making the checkbox of each item in the list reflect the selection state is not trivial. This is a classic use case for two-way binding. If the check box is clicked and become checked you want the item to be selected (one way) and if the item is selected through some other mechanism like the user navigating with the arrow keys and hitting the space bar you want the check box to become checked (the other way). After some "binging" I discovered it requires a data template in XAML (of course you can do anything in code too). You define the data template in resources section of the main window and bind the isChecked property of the check box to the isSelected property of its containing ListViewItem. It sounds elegant, but the arcane incantations it requires can't be discovered easily just by reading the documentation. Here is the data template:
<DataTemplate x:Key="FirstCell">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected,
Mode=TwoWay,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListViewItem}}}"
/>
</StackPanel>
</DataTemplate>
Once the data template is defined it is simple to extend the list view to have a check box next to every item. All it takes is adding a new column to GridView.Columns (the first column):
<GridView.Columns>
<GridViewColumn
CellTemplate="{StaticResource FirstCell}"
Width="30"
/>
<GridViewColumn
Header="Filename"
DisplayMemberBinding="{Binding Path=Name}"
Width="Auto"
/>
<GridViewColumn
Header="Duration"
DisplayMemberBinding="{Binding Path=Duration,
Converter={StaticResource DurationConverter}}"
Width="Auto"
/>
</GridView.Columns>
That wasn't good enough though. I had my nifty little check boxes that were nicely synchronized with the selected items, but the basic problem remained if you select an item without holding control all your previous selections were still replaced by the new item. The check boxes didn't enforce the behavior I wanted (item select/unselect toggles just the current item). I tried various solutions with ever more desperation but I couldn't override fully the selection behavior of the list view (I tried hooking into the routed event processing and directly manipulating the selected items). Eventually, I noticed that the list view has a little property called SelectionMode. The MSDN documentation is a little confused about it and doesn't mention it, but it does mention that it's a property of a list box (a list view is derived from list box and just extends it with a view). Anyway, the SelectionMode is set to "extended" by default, which is the bad behavior, but it has also a value of "Multiple", which is exactly the behavior I was after. There is a "Single" value to that allows you to select at most one item at a time. So, I set the SelectionMode to "Multiple" and my work was done (including seamless integration with the check boxes).
Here is the XAML if you must:
<ListView
MinHeight="223"
Name="files"
MinWidth="355"
Background="White"
SelectionChanged="files_SelectionChanged"
SelectionMode="Multiple"
PreviewMouseRightButtonDown="files_PreviewMouseRightButtonDown"
>
...
</ListView>



