Skip to main content Skip to footer

The Making of WorkSpace Part 4: Recent Documents List

In this post I cover how the list of recent documents is created and managed in the ComponentOne WorkSpace app. From the home page, users can open a document in one of two ways: by browsing their machine or by selecting a recent document from the list on the left. Every time the user opens or saves a new file it gets added to the top of this list. I already discussed how files are opened in part 2 of this series, so what’s left to cover is how the recent document list is managed and how we can persist it between runs of the app. MakingWorkSpace4_1 The recent document list needs to be stored between runs of the app. Application settings are a nice built-in mechanism for saving simple key/value type of information. To store a list of items you may consider serializing the list to an XML file and storing it in the app’s local folder. That would be a decent option for saving our recent documents if only there wasn’t a major limitation in WinRT: You can’t just open any file on disk without the user’s permission. Typically we show file pickers that grant our app permission to access files. It works fine if the user picks a file on Monday using this approach, but if we need to load this file again on Tuesday from the recent file list, we will hit a roadblock. We don’t want to show another file picker. We need to remind Windows that the user has previously granted access to this file, so why not again today too? Fortunately, Microsoft realized this file access corner they were painting us into and gave us a way out – with the StorageApplicationPermissions class. The StorageApplicationPermissions class makes managing a recent file list super easy. It provides a couple of manageable lists that an app can use to track recently accessed files or locations to access in the future. Basically, it remembers files and locations that the user has previously granted to our app so we can access them again in the future without hassle. There are two lists:

  • FutureAccessList – Store files and folders for future access
  • MostRecentlyUsedList – Store files and folders that the user recently accessed

Both lists do basically the same thing. Here are some differences:

  • MostRecentlyUsedList has a limit of 25 items, whereas FutureAccessList has a limit of 1000.
  • FutureAccessList will actually keep track if the user moves a file. So you can access the file in the future even if the user moves it to a different location on their device.

For WorkSpace, the benefit of still being able to open files that were moved is a good enough reason to choose FutureAccessList. Although, a 1,000 item limit is way too high for my purpose. So I will need to limit it to a much smaller number like 15 in my own code. So I wrote a DocumentDataStorage class that would encapsulate all of the logic for getting and setting the recent documents. It’s in this class that I can handle my own max limit (15) by removing the oldest item when the list overflows.


public class DocumentDataStorage  
{  

    public static void SaveToRecentDocuments(StorageFile file)  
    {  
        StorageApplicationPermissions.FutureAccessList.AddOrReplace(EncodeFilePath(file.Path), file);  
        while (StorageApplicationPermissions.FutureAccessList.Entries.Count > 15)  
        {  
            // remove oldest file  
            StorageApplicationPermissions.FutureAccessList.Remove(StorageApplicationPermissions.FutureAccessList.Entries[StorageApplicationPermissions.FutureAccessList.Entries.Count-1].Token);  
        }  
    }  

    public static void RemoveFromRecentDocuments(string filePath)  
    {  
        StorageApplicationPermissions.FutureAccessList.Remove(EncodeFilePath(filePath));  
    }  

    public static void ClearRecentDocuments()  
    {  
        StorageApplicationPermissions.FutureAccessList.Clear();  
    }  

    public static async Task<List<DocumentViewModel>> LoadRecentDocuments()  
    {  
        List<DocumentViewModel> recentDocuments = new List<DocumentViewModel>();  
        foreach (AccessListEntry entry in StorageApplicationPermissions.FutureAccessList.Entries)  
        {  
            try  
            {  
                DocumentViewModel document = new DocumentViewModel();  
                StorageFile file = await StorageApplicationPermissions.FutureAccessList.GetFileAsync(entry.Token);  
                document.SetFileInformation(file);  
                recentDocuments.Add(document);  
            }  
            catch (Exception ex)  
            {  
                // file not found  

            }  
        }  
        return recentDocuments;  
    }  

    public static string EncodeFilePath(string originalPath)  
    {  
        return originalPath.Replace("\\\", "%92");  
    }  

    public static string DecodeFilePath(string originalPath)  
    {  
        return originalPath.Replace("%92", "\\\");  
    }  
}  

When you add a file to the FutureAccessList or MostRecentlyUsedList, you must also provide a unique token for the file so that you can later retrieve the file. In this case, I chose to use the file path as the token considering that users may have multiple files with the same name in different locations (readme.txt!). Since the file path contains slashes, I had to strip them out using the EncodeFilePath/DecodeFilePath methods. My DocumentDataStorage class can now be called conveniently in my ViewModel whenever necessary. For instance, I have to manage a collection of recent documents that my View can bind to.


private ObservableCollection<DocumentViewModel> _recentDocuments;  
public ObservableCollection<DocumentViewModel> RecentDocuments  
{  
    get  
    {  
        if (_recentDocuments == null)  
        {  
            _recentDocuments = new ObservableCollection<DocumentViewModel>();  
            LoadRecentDocuments();  
        }  
        return this._recentDocuments;  
    }  
}  

When the collection is initialized I will load the files from the FutureAccessList. All I do here is call the LoadRecentDocuments method from my DocumentDataStorage class and put them into a RecentDocuments collection.


private async void LoadRecentDocuments()  
{  
    List<DocumentViewModel> recentDocuments = new List<DocumentViewModel>();  
    recentDocuments = (await DocumentDataStorage.LoadRecentDocuments()).OrderByDescending(x => x.DateModified).ToList();  
    int count = recentDocuments.Count;  

    for (int i = 0; i < count; i++)  
    {  
        this.RecentDocuments.Add(recentDocuments[i]);  
    }  

    OnPropertyChanged("HasRecentDocuments");  
}  

Then throughout the ViewModel logic there are several places where I need to add a document to the recent list (ie when the user opens a file or saves a new file). At these places I call the AddToRecentDocuments method in the MainViewModel. This is responsible for inserting the document to the top of the RecentDocuments collection (it removes it first if it already exists to avoid duplicate entries). Finally, it makes a call to the DocumentDataStorage class to update the FutureAccessList.


private void AddToRecentDocuments(DocumentViewModel document)  
{  
    if (this.RecentDocuments.Contains<DocumentViewModel>(document, _documentComparer))  
    {  
        // if recent documents contains the document already, remove it  
        this.RecentDocuments.Remove(document);  
        // add it again so it's on top  
        this.RecentDocuments.Insert(0, document);  
    }  
    else  
    {  
        this.RecentDocuments.Insert(0, document);  
    }  

    // store to recently used list  
    if (document.File != null)  
    {  
        DocumentDataStorage.SaveToRecentDocuments(document.File);  
    }  

}  

Conclusion

The key thing I focused on is how you can persist a list of recently accessed files between runs of your app and access them again in the future. You can take my DocumentDataStorage class, rename and reuse it in your own application. The last two code snippets are just example uses of this class within a ViewModel. The StorageApplicationPermissions class is the key underlying class that you should take advantage of and I listed a couple differences between the two different lists provided. Here are links to the previous parts in this series.

  1. App View and ViewModel structure
  2. Opening and saving Files
  3. Using the C1RadialMenu control to format content
  4. Recent documents list

There may be more parts in the future such how to publish to the Windows Store. If you have any questions or topics you’d like discussed, please let me know in the comments section.

ComponentOne Product Manager Greg Lutz

Greg Lutz

comments powered by Disqus