Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Building a simple Isolated Storage explorer system in a Windows Phone 7 application

DZone's Guide to

Building a simple Isolated Storage explorer system in a Windows Phone 7 application

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

Applications are using Isolated Storage for a multitude of reasons, including temporary storage (cached content) and permanent storage (reusable content and databases). Sometimes, it is hard to figure out what's where, especially when the developer operates with lots of files and folders. My solution was to build a small explorer sub-system (if you want to call it so) that will make it a bit easier to go through existing content. The ultimate goal is to build a custom full-fledged control, but for now - enjoy this functional proof of concept.

Let's take a look at the basic structure I need.

I am binding the ObservableCollection that keeps a list of StorageItem instances to the visual list, represented by a ListBox with a custom DataTemplate. So let's take a look at the XAML part that is the foundation of the UI:

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="600"/>
            <RowDefinition Height="80"></RowDefinition>
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Button Click="Button_Click" Grid.Row="1" Width="109" Content="^" HorizontalAlignment="Left"></Button>
        <TextBlock Grid.Row="1" Margin="140,0,0,0" Text="{Binding ElementName=ExplorerPage,Path=currentPath}"></TextBlock>
        <ListBox x:Name="StorageList" ItemsSource="{Binding ElementName=ExplorerPage,Path=storageItems}" SelectionChanged="StorageList_SelectionChanged">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid Height="39">
                        <Image Source="{Binding ContentType,Converter={StaticResource ConverterMain}}" Height="32" Width="32" HorizontalAlignment="Left" Margin="10,0,0,0"></Image>
                        <TextBlock Margin="45,0,0,0" Height="Auto" Text="{Binding Name}" Width="370"></TextBlock>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Click="Button_Click_1" Grid.Row="2" Content="Refresh"></Button>
    </Grid>

Notice that the DataTemplate I am using for ListBox.ItemTemplate is composed of a single grid with an Image and a Button. You might be wondering - why I need an image when listing storage content? That will be the indicator, showing whether the entity listed is a file or a folder. Ignore the bound converter at this point. 

So the ListBox is bound to the collection in the code-behind, which is referenced through a DependencyProperty:

ObservableCollection<StorageItem> storageItems
        {
            get
            {
                return (ObservableCollection<StorageItem>)GetValue(_ST_ITEMS);
            }
            set
            {
                SetValue(_ST_ITEMS, value);
            }
        }
public DependencyProperty _ST_ITEMS = DependencyProperty.Register("storageItems", typeof(ObservableCollection<StorageItem>),
    typeof(PhoneApplicationPage),
    new PropertyMetadata(new ObservableCollection<StorageItem>()));

A StorageItem only haas two properties - a ContentType and a Name.

public class StorageItem
{
    public StorageType ContentType { get; set; }
    public string Name { get; set; }
}

The ContentType is nothing but a flag determined through an enum:

public enum StorageType
{
    File,
    Folder
}

While working with all the content inside the Isolated Storage, it is safe to assume that not everything will be located in the root folder, and there might be secondary folder structures. To keep track of it, I introduced a currentPath property, also backed by a DependencyProperty, since I am binding to it in the UI:

string currentPath
{
    get
    {
        return (string)GetValue(_ST_CURPATH);
    }
    set
    {
        SetValue(_ST_CURPATH, value);
    }
}
public DependencyProperty _ST_CURPATH = DependencyProperty.Register("currentPath", typeof(string),
    typeof(PhoneApplicationPage),
    new PropertyMetadata(string.Empty));

I am getting the data regarding the Isolated Storage contents with the help of a GetStorageItems method:

public void GetStorageItems()
{
    storageItems = new ObservableCollection<StorageItem>();

    string[] files = file.GetFileNames(currentPath + @"\*.*");
    string[] directories = file.GetDirectoryNames(currentPath + @"\*.*");

    foreach (string directory in directories)
    {
        storageItems.Add(new StorageItem() { ContentType = StorageType.Folder, Name = directory });
    }

    foreach (string _file in files)
    {
        storageItems.Add(new StorageItem() { ContentType = StorageType.File, Name = _file });
    }
}

The \*.* suffix is added to the path to query against all content, no matter what the extension or name is, both for files and for folders. For now, the path does not change, but it should. So when the selection changes in the list, I am dynamically modifying the path, and in case the user selected a folder, I need to get the content from that specific folder.

private void StorageList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (e.AddedItems.Count != 0)
    {
        StorageItem item = (StorageItem)e.AddedItems[0];
        if (item.ContentType == StorageType.Folder)
        {
            currentPath += @"\" + item.Name;
            GetStorageItems();
        }
    }
}

Now back to the converter that was bound to the Image in the DataTemplate for custom list items. I need to convert the ContentType property to an icon. There are only two choices I have - it's either going to be a file or a folder, so I have two predefined icons located in the Images folder inside my application - file.png and folder.png.

The converter looks like this:

public class StorageTypeConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if ((StorageType)value == StorageType.Folder)
        {
            return new BitmapImage(new Uri("Images/folder.png", UriKind.Relative));
        }
        else
        {
            return new BitmapImage(new Uri("Images/file.png", UriKind.Relative));
        }

    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

It is declared as a static resource in the main application page:

<phone:PhoneApplicationPage.Resources>
    <local:StorageTypeConverter x:Key="ConverterMain"></local:StorageTypeConverter>
</phone:PhoneApplicationPage.Resources>

The icon is shown correctly, so is the path and the contents of each and every folder. What to do in case the user wants to navigate back, to a folder that is the container for the current file/folder set? All that needs to be done is path editing to the last folder separator and invoking the GetStorageItems method again:

currentPath = currentPath.Remove(currentPath.LastIndexOf(@"\"));
GetStorageItems();

That is it! Once you run the application, you should see something similar to this:

Some ideas I will be working on to extend this to a full-fledged control:

  • Ability to navigate by entering the path manually
  • Add file operations (rename, change extension, open, delete, copy, move)
  • A visually appealing toolbar
  • Add size information for files and folders

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}