Pages

Sunday, April 24, 2011

Working with Isolated Storage for Windows Phone 7

During the last few days, Microsoft is getting more and more inclined towards improving the user experience in more than  a number of technologies. Silverlight being one of the major forerunner on this moving good in Web by providing Rich Internet Applications for end users. But not only for Web, Silverlight is a language being used for Windows Phone 7 as well, which gives the silverlight developers a chance to move over to Windows Phone easily. Being a WPF developer, I am totally freaked out on watching stuffs related to Windows Phone as richness in UI always attracts me. I have learned a few about Windows Phone 7 as well, but never got a chance to speak about it. Lets speak a little about the use of Isolated Storage (if you have already read my article here),  in Windows Phone 7 in this article.

Isolated Storage

We say Files and Folders are the building blocks for any application. We need to store data in our Phones to persist data when the application is not running. In case of Windows Phone 7 microsoft provides a secure way to store data into Isolated Store. Isolated Storage, as the name suggests is a special virtualized file system that every application can access for standard IO operations yet the file is unavailable to any other application. Hence the files stored by one application is isolated from another.

Each application has a root of the store of this Virtualized File system. You can use the store to create folders and files. The main advantage of the Isolated store is independence between the actual file system and the application. Hence it gives a strong decoupling between the actual physical architecture of the system and the application. To understand, lets see the image below  :



Here we have three applications running on Windows Phone, and each accesses its own root in the Virtualized file system for data storage. The application API cannot access the physical file system, but interacts with the Virtualized File system for its I/O operations. Thus if our physical file system changes, our stores will still be available and our application will run independently.



Now lets start creating an application and store files and folders through it.

Start Visual Studio and Create a new project.
After you create an application lets create a Folder inside it and put some Icons. You can use the Icons already available with SDK.  To find them, navigate to :

Drive\\ProgramFiles\MicrosoftSDK\Windows Mobile\Icons.

Make sure after you add the icons on the project you change the File Build Action to Content.  If you keep it as Resource, the files will not be deployed into the device.  To do this, Select the File, and go to Properties and Change Build Action.

Now the main classes that you need to know about are :
1. IsolatedStorageFile
2. IsolatedStorageFileStream
3. IsolatedStorageSettings

IsolatedStorageFile


This is the main class that allows you to get the informations about the storage, gets files residing inside it and also lets you to work on basic IO operation on the files. Before you start working with IsolatedStorage, the first thing that you need to do is to get the storage for the application. Now to do this, you need to call

public IsolatedStorageFile Store
 {
    get
    {
       this.currentStore = this.currentStore ?? IsolatedStorageFile.GetUserStoreForApplication();
       return this.currentStore;
    }
}

IsolatedStorageFile.GetUserStoreForApplication is the only way to create an object of IsolatedStorageFile. The command will give you a pointer to the root of your application store. As Windows Phone does not allow you to share your files with other application there is no other way to get the pointer to store than this method.

The IsolatedStorageFile has a few Properties that needs to be addressed. Before doing this lets put a nice XAML for our application :

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" DataContext="{StaticResource vmDiskModel}">
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            
            <TextBlock Text="Available Free Space" />
            <TextBlock Text="{Binding Store.AvailableFreeSpace}" Grid.Column="1" />
            <TextBlock Text="Quota" Grid.Row="1" />
            <TextBlock Text="{Binding Store.Quota}" Grid.Row="1" Grid.Column="1"/>
            
            <ListBox ItemsSource="{Binding Files}" Grid.Row="2" Grid.ColumnSpan="2" >
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <CheckBox IsChecked="{Binding IsChecked}" Content="{Binding FileName}" />
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>

So we have created a ListBox which will show up a CheckBox for each of the files in the Root directory. There are two rows which will show the AvailableFreeSpace (returns the amount of free space available for the device in bytes) and Quota (represents the maximum amount of storage available for the isolated storage).

Now the model looks like :

public class FileItem : ModelBase
    {
        public bool isChecked;
        public bool IsChecked
        {
            get { return this.isChecked; }
            set
            {
                this.isChecked = value;
                this.OnPropertyChanged("IsChecked");
            }
        }

        public string FileName { get; set; }
    }
    public class ViewDiskModel : ModelBase
    {
        private IsolatedStorageFile currentStore;
        public IsolatedStorageFile Store
        {
            get
            {
                this.currentStore = this.currentStore ?? IsolatedStorageFile.GetUserStoreForApplication();
                return this.currentStore;
            }
        }

        private ObservableCollection<FileItem> _files;
        public ObservableCollection<FileItem> Files
        {
            get
            {
                this._files = this._files ?? this.LoadFiles();
                return this._files;
            }
        }

        private ObservableCollection<FileItem> LoadFiles()
        {
            ObservableCollection<FileItem> files = new ObservableCollection<FileItem>();

            foreach (string filePath in this.Store.GetFileNames())
                files.Add(new FileItem { FileName = filePath });
            return files;
        }

        private ICommand deleteSelectedFiles;
        public ICommand DeleteSelectedFiles
        {
            get
            {
                this.deleteSelectedFiles = this.deleteSelectedFiles ?? new DelegateCommand(this.OnDeleteSelected);
                return this.deleteSelectedFiles;
            }
        }

        private void OnDeleteSelected()
        {
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
            List<FileItem> removedItems = new List<FileItem>();
            foreach (var item in this.Files)
            {
                if (item.IsChecked)
                    if (storage.FileExists(item.FileName))
                    {
                        storage.DeleteFile(item.FileName);
                        removedItems.Add(item);
                    }
            }

            foreach (var item in removedItems)
                this.Files.Remove(item);
        }
    }

The GetFileNames lets you get all the files that are present in the root directory. You can use CreateDirectory / DeleteDirectory to create and delete a directory from the store. Here I have left out the usage of directory rather I have listed all the Files that are available in the store. Hence when you run the application, you will see the files that are already added to the storage.




Now to delete a File, as you can see I have used command interface, you can either use a Button inside your Xaml and use Command binding on the property or you can use ApplicationBar to do this.

<phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <shell:ApplicationBarIconButton IconUri="/Images/appbar.add.rest.png" Text="Add" Click="Add_Click"/>
            <shell:ApplicationBarIconButton IconUri="/Images/appbar.delete.rest.png" Text="Delete" Click="Delete_Click"/>
            <shell:ApplicationBar.MenuItems>
                <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
                <shell:ApplicationBarMenuItem Text="MenuItem 2"/>
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>

You should remember that ApplicationBar of Windows Phone 7 does not support Command Bindings. So you need to use Click eventhandler to invoke the command.


private void Add_Click(object sender, EventArgs e)
        {
            NavigationService.Navigate(new Uri("/AddNewFile.xaml", UriKind.Relative));
        }

        private void Delete_Click(object sender, EventArgs e)
        {
            ViewDiskModel model = this.LayoutRoot.DataContext as ViewDiskModel;
            model.DeleteSelectedFiles.Execute(null);

            MessageBox.Show("Files Successfully Deleted");
        }

The First Button will navigate to another Page. We use NavigationService.Navigate to navigate.

As you can see the DataContext is associated with LayoutRoot element, you can easily get it from the Eventhandler. The command Execute is invoked to delete all the files that are checked in the list.

Now before you run the application, lets change the initial page to MainPage. To do that, go to Properties => WMAppManifest.xaml and change the DefaultTask navigationPage property to your page name. This is the startup page for your application.


Once you are done, start your application. You can use Emulator to start the application, so that your application is deployed to the emulator rather than the actual device.

IsolatedStorageFileStream

Adding a new file is just what you need next. To do this, lets add a new Blank page to the project and name it to AddNewFile.xaml.  Lets put a xaml for the file.

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            
            <TextBlock Text="FileName" />
            <TextBox Text="{Binding FileName, Mode=TwoWay}" Grid.Column="1" />

            <TextBlock Text="FileText" Grid.Row="1" />
            <TextBox Text="{Binding FileText, Mode=TwoWay}" Grid.Column="1" Grid.Row="1" />
        </Grid>

So basically we add two TextBoxes to the Grid. You should note, you need to specify the Mode attribute to TextBox bindings, as the default is OneWay for Windows Phone 7.

I have also added one ApplicationBar to ensure we call save command. Lets see how the model looks like :

public class AddFileModel : ModelBase
    {
        private string _filename;
        public string FileName
        {
            get
            {
                return this._filename;
            }
            set
            {
                this._filename = value;
                this.OnPropertyChanged("FileName");   
            }
        }

        private string _filetext;
        public string FileText
        {
            get
            {
                return this._filetext;
            }
            set
            {
                this._filetext = value;
                this.OnPropertyChanged("FileText");
            }
        }

        private ICommand _saveFile;
        public ICommand SaveFile
        {
            get
            {
                this._saveFile = this._saveFile ?? new DelegateCommand(this.OnSaveFile);
                return this._saveFile;
            }
        }

        private void OnSaveFile()
        {
            if (!string.IsNullOrEmpty(this.FileName))
            {
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    if (store.FileExists(FileName))
                        store.DeleteFile(FileName);

                    using(StreamWriter writer = new StreamWriter(new IsolatedStorageFileStream(FileName, FileMode.OpenOrCreate, store)))
                    {
                        writer.WriteLine(this.FileText);
                        writer.Close();
                    }
                }
            }
        }
    }

Here the SaveFile command uses StreamWriter to write a file to the storage. The IsolatedStorageFileStream allows you to open a file from the store and you can do the rest normally.

IsolatedStorageSettings

IsolatedStorageSettings is another class that lets you store settings for the application. The main advantage of IsolatedStorageSettings class is everything that you store using this will automatically gets stored in the user settings and will be available when you open the application again. Lets look into the code :


public class SetingsModel :ModelBase
    {
        private string _key;
        public string Key
        {
            get
            {
                return this._key;
            }
            set {
                this._key = value;
                this.OnPropertyChanged("Key");
            }
        }
        private string _value;
        public string Value
        {
            get
            {
                return this._value;
            }
            set
            {
                this._value = value;
                this.OnPropertyChanged("Value");
            }
        }

        ObservableCollection<KeyValuePair<string, object>> appsettings;
        public ObservableCollection<KeyValuePair<string, object>> Settings
        {
            get
            {
                this.appsettings = this.appsettings ?? this.LoadSettings();
                return this.appsettings;
            }
        }

        private void SaveKeyValue()
        {
            IsolatedStorageSettings.ApplicationSettings[this.Key] = this.Value;
        }

        private ObservableCollection<KeyValuePair<string, object>> LoadSettings()
        {
            ObservableCollection<KeyValuePair<string, object>> settings = new ObservableCollection<KeyValuePair<string, object>>();

            foreach (var setting in IsolatedStorageSettings.ApplicationSettings)
            {
                settings.Add(setting);
            }
            return settings;
        }

    }

Settings file gives you a KeyValuePair collection called ApplicationSettings which you can use to add an element to persist the data. To add an element to the settings is very easy. You just need to either use Add method for the collection or you can use the indexer associated with the property (what shown in the class above).

Closing note

Working with Windows Phone 7 is a real fun. I have worked on Windows Phone 7 for quite a few times, but never got a chance to write something about it. I hope this gives you a start on the device.

You can download the sample application from :
Download Sample - 256KB

More to come about windows phone 7. Stay tune.
Thank you for reading.

2 comments:

  1. For IsolatedStorageSettings usage, method SaveKeyValue of SetingsModel class - don't forget as a best practice to explicitelly save the settings once you change them :
    IsolatedStorageSettings.ApplicationSettings.Save()

    Regards,

    ReplyDelete

Please make sure that the question you ask is somehow related to the post you choose. Otherwise you post your general question in Forum section.