Channels ▼
RSS

.NET

Your Own MP3 Duration Calculator: Part 2


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.

[Click image to view at full size]
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.

[Click image to view at full size]
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).

[Click image to view at full size]
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>


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video