CONTENT
 PRODUCTS
 ARTICLES
 DOWNLOAD
 SOURCE CODE
 BOOK
 ABOUT
 HOME

MVP

ADS




Smartphone File Manager with .NET CF
The File Manager for Smartphone is a .NET CF application that is provided as open source written in C#. It provides the basic file management functionality that can be used on both the emulator and a physical device.

As a Smartphone developer, I was missing a file manager for the Smartphone emulator. Although there are many both commercial and free file managers out there, I couldn't find a single one that was distributed for the emulator (x86 processors). Even if my physical Smartphone comes with a file manager, I often want to do application testing on the emulator as it provides more speed and therefore makes debugging more efficient. When doing application development, file management is essential. Inspired by Christoph Wille's excellent initiative to publish an open source .NET CF registry editor for Smartphone, I decided to do the same for file management.

File Manager
File Manager
Get the sample source code!

User Interface
The Smartphone File Manager application was built for .NET CF with Visual Studio .NET 2003 and when installed in the Start Menu's Accessories folder, it looks like this:

File Manager icon in Start Menu
Figure 1. File Manager icon in Start Menu

When the File Manager icon is selected in the Start Menu, the file manager is started with the default folder (\Storage\My Documents) opened that looks like this:

Folder and file list
Figure 2. Folder and file list

The user interface is very simple and the look and feel is aligned with the standard Smartphone applications. No non-standard title bar, status bar, splitters, or other alien user interface elements. Folder navigation is done by simply selecting a subfolder or the standard "move up" (the two dots) folder. Selecting the Done soft key ends the file manager, and selecting the menu opens up the following menu options:

Menu options
Figure 3. Menu options

The menu offers the standard file management functionality with cut, copy, paste, rename, and delete. A common function addition (borrowed from both the desktop Explorer and the Pocket PC File Manager) is the ability to paste a shortcut to a file. To use that function, you first select the file (usually an executable), select Copy in the menu, navigate to the desired target location (usually somewhere beneath \Storage\windows\Start Menu), and select Paste Shortcut. When the menu option Properties is selected, the following page is shown:

Properties page
Figure 4. Properties page

Here the information about the file can be found, like size and creation date, and if the page is scrolled down, it looks like this:

Properties page (cont.)
Figure 5. Properties page (continued)

The attributes for the file is shown, and can also be modified.

That concludes the walkthrough of the user interface, so let's move on and look at some of the code.

Code
When the main form (MainForm) is loaded, the following code executes:
  string bs = Path.DirectorySeparatorChar.ToString();
  this.path = bs + "Storage" + bs + "My Documents" + bs; 
  fillList();
The default folder is set and the private method (fillList) to fill the ListView is called. The code in this method looks like this:
  ListViewItem lvi;
  listView.BeginUpdate();
  listView.Items.Clear();

  // If not root
  if(this.path.Length > 1)
  {
    // Add "up" directory
    lvi = new ListViewItem(UPDIR);
    lvi.ImageIndex = 0;
    listView.Items.Add(lvi);
  }

  // Add directories
  string[] dirs = Directory.GetDirectories(this.path);
  ArrayList list = new ArrayList(dirs.Length);
  for(int i = 0; i < dirs.Length; i++) 
    list.Add(dirs[i]);
  list.Sort(new SimpleComparer());
  foreach(string dir in list)
  {
    lvi = new ListViewItem(Path.GetFileName(dir));
    lvi.ImageIndex = 0;
    listView.Items.Add(lvi);
  }

  // Add files
  string[] files = Directory.GetFiles(this.path);
  list = new ArrayList(files.Length);
  for(int i = 0; i < files.Length; i++) 
    list.Add(files[i]);
  list.Sort(new SimpleComparer());
  foreach(string file in list)
  {
    lvi = new ListViewItem(Path.GetFileName(file));
    lvi.ImageIndex = 1;
    listView.Items.Add(lvi);
  }
  listView.EndUpdate();

  // Select first item
  listView.Items[0].Selected = true;
  listView.Items[0].Focused = true;
After the list is cleared, a check is made if this is the root folder. If not, a "move up" (the two dots) folder is added. Then all the directories are added after they are sorted, and the same is done for the files. Finally, the first item in the list is selected. The ListView can be navigated as usual with the keypad, and when the Action key is pressed, the following code is run:
  ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];
  bool isFolder = lvi.ImageIndex == 0;
  if(lvi.Text == UPDIR)
  {
    this.path = this.path.Substring(0, this.path.Substring(
      0, this.path.Length - 1).LastIndexOf(
      Path.DirectorySeparatorChar) + 1);
    fillList();
  }
  else if(isFolder)
  {
    this.path += lvi.Text + Path.DirectorySeparatorChar;
    fillList();
  }
  else
    ShellExecute.Start(this.path + lvi.Text);
The currently selected item is retrieved, and if it's the "move up" folder, the current path is changed to the parent folder. If it's a folder, the path is changed to that folder, and if it's a file, an attempt is made to launch the file. The file is launched depending on how it is associated (a voice recording file is launched with Voice Notes, and so on).

Moving on to the menu options, this is the code for the Cut menu option:
  ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];
  clipboardFileName = this.path + lvi.Text;
  clipboardAction = ClipboardAction.Cut;
The path to the currently selected file is saved with the desired action, and the corresponding code for the Copy menu option looks like this:
  ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];
  clipboardFileName = this.path + lvi.Text;
  clipboardAction = ClipboardAction.Copy;
Again, the current file is saved with the desired action. The more interesting stuff happen in the code for the Paste menu option:
  // Check if file exist
  string dest = this.path + Path.GetFileName(clipboardFileName);
  if(File.Exists(dest))
  {
    if(MessageBox.Show("Destination already exist," +
        " overwrite?", this.Text,
        MessageBoxButtons.YesNo, MessageBoxIcon.Question,
        MessageBoxDefaultButton.Button2) == DialogResult.Yes)
      File.Delete(dest);
    else
      return;
  }
  // Move or copy
  string s = this.path.Substring(0, this.path.Length - 1);
  switch(clipboardAction)
  {
    case ClipboardAction.Cut:
      File.Move(clipboardFileName, dest);
      break;

    case ClipboardAction.Copy:
      File.Copy(clipboardFileName, dest, false);
      break;
  }
  clipboardAction = ClipboardAction.None;
  clipboardFileName = string.Empty;
  fillList();
If the destination file exist, a confirmation is required before the file is overwritten (actually, it is first deleted), and then the desired action (cut or copy) is performed. The code to Paste a Shortcut looks like this:
  int i = 2;
  string s = string.Empty;
  string dest;
  while(true)
  {
    dest = this.path + "Shortcut" + s + " to " + Path.GetFileName(Path.GetFileNameWithoutExtension(clipboardFileName) + ".lnk");
    if(!File.Exists(dest))
      break;
    s = " (" + i.ToString() + ")";
    i++;
  }
  StreamWriter sw = new StreamWriter(dest);
  s = clipboardFileName;
  if(s.IndexOf(" ") > 0)
    s = "\"" + s + "\"";
  s = s.Length.ToString() + "#" + s;
  sw.WriteLine(s);
  sw.Close();
  fillList();
The shortcut filename is iterated until it becomes unique, and then the shortcut file is written. The code for the Rename menu option looks like this:
  ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];
  bool isFolder = lvi.ImageIndex == 0;
  string s;
  if(isFolder)
    s = "Folder";
  else
    s = "File";
  NameForm nameForm = new NameForm(this, "Rename " + s,
    lvi.Text, new SetNameDelegate(SetRename));
  if(nameForm.ShowDialog() == DialogResult.OK)
    fillList();
The currently selected item is retrieved, and depending on if it's a file or a folder, the corresponding caption is passed to the rename form (NameForm). To that form is also passed a delegate that it will use to send back the new name. The code for the delegate method (SetRename) method looks like this (name is a string parameter):
  ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];
  bool isFolder = lvi.ImageIndex == 0;
  string itemName = this.path + lvi.Text;
  string destName = Path.GetDirectoryName(itemName) +
    Path.DirectorySeparatorChar.ToString() + name;
  if(isFolder)
    Directory.Move(itemName, destName);
  else
    File.Move(itemName, destName);
After the selected item is retrieved, it is renamed (actually Moved) depending on if it's a file or a folder. The menu option to Delete a file is implemented with this code:
  ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];
  bool isFolder = lvi.ImageIndex == 0;
  string s = "Are you sure you want to delete " + lvi.Text;
  if(isFolder)
    s += " and all its content";
  s += "?";
  if(MessageBox.Show(s, this.Text,
    MessageBoxButtons.YesNo, MessageBoxIcon.Question,
    MessageBoxDefaultButton.Button2) == DialogResult.Yes)
  {
    if(isFolder)
      Directory.Delete(this.path + lvi.Text, true);
    else
      File.Delete(this.path + lvi.Text);
    fillList();
  }
When the selected item is retrieved, a confirmation is required before the file or folder is deleted. To create a New Folder, the following code is run:
  ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];
  NameForm nameForm = new NameForm(this, "New Folder", "",
    new SetNameDelegate(SetNewName));
  if(nameForm.ShowDialog() == DialogResult.OK)
    fillList();
  listView.Focus();
The currently selected item is retrieved, and the name form (NameForm) is shown with the title "New Folder". To that form is also passed a delegate that it will use to send back the new name. The code for the delegate method (SetNewName) method looks like this (name is a string parameter):
  Directory.CreateDirectory(this.path + name);
The directory is created with the name specified. Let's have a look at the code for the Properties menu option:
  ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];
  FileInfo fi = new FileInfo(this.path + lvi.Text);
  PropertiesForm propertiesForm = new PropertiesForm(this, fi,
    new SetNameDelegate(SetRename),
    new SetAttributesDelegate(SetAttributes));
  if(propertiesForm.ShowDialog() == DialogResult.OK)
    fillList();
  listView.Focus();
The currently selected item is retrieved, and the properties form (PropertiesForm) is passed the information (FileInfo) about the item, and two delegates that it will use to send back a changed name and update the file attributes. The first delegate method is the same as is used by the rename functionality (see above). The code for the second delegate method (SetAttributes) method looks like this (fileAttributes is a parameter of type FileAttributes):
  ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];
  bool isFolder = lvi.ImageIndex == 0;
  if(isFolder)
  {
    DirectoryInfo di = new DirectoryInfo(this.path + lvi.Text);
    di.Attributes = fileAttributes;
  }
  else
  {
    FileInfo fi = new FileInfo(this.path + lvi.Text);
    fi.Attributes = fileAttributes;
  }
Depending on if it's a file or a folder, the attributes are set.

To finish off, let's look at some of the tweaking needed. Before the main form is shown, the following line of code is run:
  ListViewHelper.SetGradient(listView);
This line will activate the gradient background pattern on the ListView as seen in other Smartphone applications. To do this, some P/Invoking is needed:
  public static void SetGradient(ListView listView)
  {
    listView.Capture = true;
    IntPtr hList = GetCapture();
    SendMessage(hList, LVM_SETEXTENDEDLISTVIEWSTYLE, 0,
      LVS_EX_GRADIENT);
    listView.Capture = false;
    listView.Refresh();
  }

  // Native API stuff
  private const uint LVM_SETEXTENDEDLISTVIEWSTYLE = 0x1000 + 54;
  private const int LVS_EX_GRADIENT = 0x20000000;

  [DllImport("coredll.dll")]
  private static extern IntPtr GetCapture(); 

  [DllImport("coredll.dll")]
  private static extern int SendMessage(IntPtr hWnd,
    uint wMsg, int wParam, int lParam);
After the window handle of the ListView is retrieved, a message is sent to the ListView window to activate the gradient background fill.

For a more complete example, see the sample source code.

Conclusion
The power of the .NET Compact Framework is truly shown when creating simple but powerful applications like this. The built-in functionality for file management (in the System.IO namespace) provide the required functionality, and the user interface elements only need minimal tweaking to make the user interface conform completely with the recommendations.

Any comments?


©2001-2009 Christian Forsberg & Andreas Sjöström