This tutorial shows how to use a non-persistent memory cache in a WinForms C# application with the Document Viewer. This cache will hold information on files loaded from the internet.
Overview | |
---|---|
Summary | This tutorial covers how to implement a memory cache for the Document Viewer in a C# Windows WinForms Application. |
Completion Time | 30 minutes |
Project | Download tutorial project (14 KB) |
Platform | Windows WinForms C# Application |
IDE | Visual Studio 2022 |
Development License | Download LEADTOOLS |
Get familiar with the basic steps of creating a project and loading an image in the Document Viewer by reviewing the Add References and Set a License and Display Files in the Document Viewer tutorials, before working on the Use Memory Cache with the Document Viewer - WinForms C# tutorial.
Note
For more information on the Document Viewer and its caching capabilities, see Document Toolkit and Caching.
Start with a copy of the project created in the Display Files in the Document Viewer tutorial. If you do not have that project, follow the steps in that tutorial to create it.
The references needed depend upon the purpose of the project. References can be added by one or the other of the following two methods (but not both).
If using NuGet references, this tutorial requires the following NuGet package:
Leadtools.Document.Sdk
Leadtools.Document.Viewer.WinForms
NUnit
If using local DLL references, the following DLLs are needed.
The DLLs are located at <INSTALL_DIR>\LEADTOOLS22\Bin\DotNet4\x64
:
Leadtools.dll
Leadtools.Caching.dll
Leadtools.Codecs.dll
Leadtools.Controls.WinForms.dll
Leadtools.Document.dll
Leadtools.Document.Pdf.dll
Leadtools.Document.Viewer.WinForms.dll
nunit.framework.dll
For a complete list of which DLL files are required for your application, refer to Files to be Included With Your Application.
The License unlocks the features needed for the project. It must be set before any toolkit function is called. For details, including tutorials for different platforms, refer to Setting a Runtime License.
There are two types of runtime licenses:
With the project created, the references added, and the license set, coding can begin.
In the Solution Explorer, right-click on the project file and select Add -> Class. This will bring up the Add New Item dialog, add MemoryCache.cs
for the name. Click Add to add the class to the project.
In the newly added class, add the following using statements to the top
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Leadtools.Caching;
Add MemoryCache
as a derivation of the ObjectCache
class. Add the code below to the MemoryCache
class:
namespace MemoryCaching
{
public class MemoryCache : ObjectCache
{
// The cache. A concurrent dictionary of string|object
private ConcurrentDictionary<string, object> _cache = new ConcurrentDictionary<string, object>();
//
// These members must be implemented by our class and are called by the Document toolkit
//
// Our name
public override string Name
{
get
{
return "Memory Cache";
}
}
// We only support binary serialization. In reality, we do not support policies nor serialization, but we return Binary
// to inform any callers to not bother sending us any JSON data
public override CacheSerializationMode PolicySerializationMode
{
get
{
return CacheSerializationMode.Binary;
}
set
{
throw new NotSupportedException();
}
}
public override CacheSerializationMode DataSerializationMode
{
get
{
return CacheSerializationMode.Binary;
}
set
{
throw new NotSupportedException();
}
}
// We have no special extra support
public override DefaultCacheCapabilities DefaultCacheCapabilities
{
get
{
return DefaultCacheCapabilities.None;
}
}
// This function handles all the backend for adding the document to the cache
// You don't need to call this function manually, it is automatically handled by the
// Document Factory
public override CacheItem<T> AddOrGetExisting<T>(CacheItem<T> item, CacheItemPolicy policy)
{
if (item == null)
throw new ArgumentNullException("item");
Console.WriteLine("Adding to Cache");
// Resolve the key, remember, we do not have regions
var resolvedKey = ResolveKey(item.RegionName, item.Key);
CacheItem<T> oldItem = null;
// Try to get the old value
// Yes, save the old value to return it to the user
object oldPayload;
if (_cache.TryGetValue(resolvedKey, out oldPayload))
oldItem = new CacheItem<T>(item.Key, (T)oldPayload, item.RegionName);
// Set the new data
_cache.TryAdd(resolvedKey, item.Value);
// Return old item
return oldItem;
}
//Function that handles retrieving the cache item from the cache, based on a certain
//region and key
public override CacheItem<T> GetCacheItem<T>(string key, string regionName)
{
// If we have an item with this key, return it. Otherwise, return null
var resolvedKey = ResolveKey(regionName, key);
CacheItem<T> item = null;
object payload;
if (_cache.TryGetValue(resolvedKey, out payload))
item = new CacheItem<T>(key, (T)payload, regionName);
return item;
}
public override bool Contains(string key, string regionName)
{
// Check if the key is in the dictionary
var resolvedKey = ResolveKey(regionName, key);
var exists = _cache.ContainsKey(resolvedKey);
return exists;
}
public override bool UpdateCacheItem<T>(CacheItem<T> item)
{
// Update the item
if (item == null)
throw new ArgumentNullException("item");
var resolvedKey = ResolveKey(item.RegionName, item.Key);
var exists = _cache.ContainsKey(resolvedKey);
if (exists)
_cache[resolvedKey] = item.Value;
return exists;
}
public override T Remove<T>(string key, string regionName)
{
// Removed if exists, return old value
var resolvedKey = ResolveKey(regionName, key);
object payload;
var removed = _cache.TryRemove(resolvedKey, out payload);
return removed ? (T)payload : default(T);
}
public override void DeleteItem(string key, string regionName)
{
// Remove if exists
var resolvedKey = ResolveKey(regionName, key);
object payload;
_cache.TryRemove(resolvedKey, out payload);
}
private static string ResolveKey(string regionName, string key)
{
// Both must me non-empty strings
if (string.IsNullOrEmpty(regionName)) throw new InvalidOperationException("Region name must be a none empty string");
if (string.IsNullOrEmpty(key)) throw new InvalidOperationException("Region key name must be a none empty string");
// We are a simple dictionary with no grouping. regionName might not be unique, key might not be unique, but combine them
// and we are guaranteed a unique key
return regionName + "-" + key;
}
public override void UpdatePolicy(string key, CacheItemPolicy policy, string regionName)
{
// Nothing to do
}
}
}
Add the below code to the bottom of the MemoryCache
class to override functions LEADTOOLS does not support for the Document Viewer. If not added each of these functions will throw a NotSupportedException
.
// These members must be over implemented by our class but are never called by the Documents toolkit
// So just throw a not supported exception
// This is for default region support. We do not have that
public override object this[string key]
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
// Delete a region in one shot. We do not support that
// Note: This is only called if we have DefaultCacheCapabilities.CacheRegions. Since we do not, the caller is responsible for
// calling DeleteAll passing all the items of the region (which in turn will call DeleteItem for each)
public override void DeleteRegion(string regionName)
{
throw new NotSupportedException();
}
// Begin adding an external resource. We do not support that
// Note: This is only called if we have DefaultCacheCapabilities.ExternalResources
public override Uri BeginAddExternalResource(string key, string regionName, bool readWrite)
{
throw new NotSupportedException();
}
// End adding an external resource. We do not support that
// Note: This is only called if we have DefaultCacheCapabilities.ExternalResources
public override void EndAddExternalResource<T>(bool commit, string key, T value, CacheItemPolicy policy, string regionName)
{
throw new NotSupportedException();
}
// Get the item external resource. We do not support that
// Note: This is only called if we have DefaultCacheCapabilities.ExternalResources
public override Uri GetItemExternalResource(string key, string regionName, bool readWrite)
{
throw new NotSupportedException();
}
// Remove the item external resource. We do not support that
// Note: This is only called if we have DefaultCacheCapabilities.ExternalResources
public override void RemoveItemExternalResource(string key, string regionName)
{
throw new NotSupportedException();
}
// Get the item virtual directory path. We do not support that
// Note: This is only called if we have DefaultCacheCapabilities.VirtualDirectory
public override Uri GetItemVirtualDirectoryUrl(string key, string regionName)
{
throw new NotSupportedException();
}
// Getting number of items in the cache. We do not support that
public override long GetCount(string regionName)
{
throw new NotSupportedException();
}
// Statistics. We do not support that
public override CacheStatistics GetStatistics()
{
throw new NotSupportedException();
}
// Statistics. We do not support that
public override CacheStatistics GetStatistics(string key, string regionName)
{
throw new NotSupportedException();
}
// Getting all the values. We do not support that
public override IDictionary<string, object> GetValues(IEnumerable<string> keys, string regionName)
{
throw new NotSupportedException();
}
// Enumeration of the items. We do not support that
protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
throw new NotSupportedException();
}
// Enumeration of the keys. We do not support that
public override void EnumerateKeys(string region, EnumerateCacheEntriesCallback callback)
{
throw new NotSupportedException();
}
// Enumeration of regions. We do not support that
public override void EnumerateRegions(EnumerateCacheEntriesCallback callback)
{
throw new NotSupportedException();
}
MemoryCache
as the Active Cache in the Document ViewerGo to the Form1.cs
class and ensure that the using statements below are included.
using System;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
using Leadtools;
using Leadtools.Document;
using Leadtools.Caching;
using Leadtools.Document.Viewer;
using Leadtools.Controls;
using System.Collections.Generic;
using NUnit.Framework;
Remove the instances of FileCache
and add the global variable below:
// Remove
//private FileCache _cache;
// Add
private MemoryCache _cache;
Adjust the InitDocumentViewer()
method to the code below:
private void InitDocumentViewer()
{
InitUI();
var createOptions = new DocumentViewerCreateOptions();
// Set the UI part where the Document Viewer is displayed
createOptions.ViewContainer = this.Controls.Find("docViewerPanel", false)[0];
// Set the UI part where the Thumbnails are displayed
createOptions.ThumbnailsContainer = this.Controls.Find("thumbPanel", false)[0];
// Not using annotations for now
createOptions.UseAnnotations = false;
// Now create the viewer
_documentViewer = DocumentViewerFactory.CreateDocumentViewer(createOptions);
_documentViewer.View.ImageViewer.Zoom(ControlSizeMode.FitAlways, 1.0, _documentViewer.View.ImageViewer.DefaultZoomOrigin);
_cache = new MemoryCache();
_virtualDocument = DocumentFactory.Create(new CreateDocumentOptions() { Cache = _cache, UseCache = true });
}
In the Solution Explorer, right-click on the project file and select Add -> Form(Windows Forms). This will bring up the Add New Item dialog, put OpenDocumentUrlDialog.cs
for the name and click Add.
Open the OpenDocumentUrlDialog.Desginer.cs
file. Add the below code to the InitializeComponent()
function.
private void InitializeComponent()
{
this._txtBox_Url = new System.Windows.Forms.TextBox();
this._Load = new System.Windows.Forms.Button();
this._UrlLabel = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// _txtBox_Url
//
this._txtBox_Url.Location = new System.Drawing.Point(12, 38);
this._txtBox_Url.Name = "_txtBox_Url";
this._txtBox_Url.Size = new System.Drawing.Size(776, 20);
this._txtBox_Url.TabIndex = 0;
this._txtBox_Url.Text = "http://demo.leadtools.com/images/pdf/leadtools.pdf";
//
// _Load
//
this._Load.Location = new System.Drawing.Point(713, 64);
this._Load.Name = "_Load";
this._Load.Size = new System.Drawing.Size(75, 28);
this._Load.TabIndex = 1;
this._Load.Text = "&Load";
this._Load.UseVisualStyleBackColor = true;
this._Load.Click += (sender, e) => LoadBtn_Click();
//
// _UrlLabel
//
this._UrlLabel.AutoSize = true;
this._UrlLabel.Location = new System.Drawing.Point(12, 22);
this._UrlLabel.Name = "_UrlLabel";
this._UrlLabel.Size = new System.Drawing.Size(20, 13);
this._UrlLabel.TabIndex = 2;
this._UrlLabel.Text = "Url";
//
// OpenDocumentUrlDialog
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 116);
this.Controls.Add(this._UrlLabel);
this.Controls.Add(this._Load);
this.Controls.Add(this._txtBox_Url);
this.Name = "OpenDocumentUrlDialog";
this.Text = "OpenDocumentFromUrlDialog";
this.ResumeLayout(false);
this.PerformLayout();
}
Add the following variables below #endregion
.
private System.Windows.Forms.TextBox _txtBox_Url;
private System.Windows.Forms.Button _Load;
private System.Windows.Forms.Label _UrlLabel;
Right-click on OpenDocumentUrlDialog.cs
and select View Code to bring up the code behind the form. Add the following variable to the top of the class.
public string url { get { return _txtBox_Url.Text; } }
Inside the OpenDocuemntUrlDialog
class, add the below function.
private void LoadBtn_Click()
{
this.Close();
}
Right-click on Form1.cs
and select View Code to bring up the code behind the form. Add and new method to the Form1
class named ShowDocumentCacheInfo(ObjectCache cache, string documentId)
. This method will be called inside the LoadDocumentFromUri
function created in the section below, right before the BeginUpdate()
call. Add the code below to the new method to show the cache information.
private static void ShowDocumentCacheInfo(ObjectCache cache, string documentId)
{
// We know that the document ID is the cache region ID
// We also know that a document may contain more than 1 cache item
// We also know that all the possible items that belong to a document can be obtained with LEADDocument.GetCacheKeys
// First load the document from the cache
var loadFromCacheOptions = new LoadFromCacheOptions();
loadFromCacheOptions.Cache = cache;
loadFromCacheOptions.DocumentId = documentId;
using (var document = DocumentFactory.LoadFromCache(loadFromCacheOptions))
{
// This is to demonstrate that it is not necessary to keep the global "cache" object around, GetCache
// returns it for the document
ObjectCache documentCache = document.GetCache();
Assert.IsNotNull(documentCache);
// Get all the possible cache keys
// These keys may or may not exist in the cache, depending on which part of the document was cached.
// For instance, if DocumentCacheOptions.PageImage was set in the document, and the image was cached, then there is an item for it
Console.WriteLine($"document {documentId} cache policies:");
ISet<string> cacheKeys = document.GetCacheKeys();
foreach (string cacheKey in cacheKeys)
{
// Does it exist?
if (documentCache.Contains(cacheKey, document.DocumentId))
{
// Get the policy
CacheItemPolicy itemPolicy = documentCache.GetPolicy(cacheKey, document.DocumentId);
// This demo sets an absolute expiration, but this is generic code
// than can figure it out by examining the values:
DateTime absoluteExpiration = itemPolicy.AbsoluteExpiration;
TimeSpan slidingExpiration = itemPolicy.SlidingExpiration;
DateTime localTimeExpiration;
if (slidingExpiration != TimeSpan.Zero)
{
// Has sliding expiration, therefore the expiration will be NOW + sliding
localTimeExpiration = DateTime.Now.Add(slidingExpiration);
}
else
{
// Absolute expiration is stored in UTC, convert to local
localTimeExpiration = absoluteExpiration.ToLocalTime();
}
// Show the expiration
Console.WriteLine($" key:{cacheKey} expiry at {localTimeExpiration}");
}
}
}
}
In the Form1
class add a new function named public void LoadDocumentFromUri(Button loadButton)
. Add the the code below to load from a URL.
public void LoadDocumentFromUri(Button loadButton)
{
string url;
using (var dlg = new OpenDocumentUrlDialog())
{
dlg.ShowDialog(this);
url = dlg.url;
LEADDocument document = DocumentFactory.LoadFromUri(new Uri(url), new LoadDocumentOptions { UseCache = true });
_virtualDocument.Pages.Clear();
for (int i = 0; i < document.Pages.Count; i++)
{
_virtualDocument.Pages.Add(document.Pages[i]);
}
ShowDocumentCacheInfo(_cache, _virtualDocument.DocumentId);
_documentViewer.BeginUpdate();
_documentViewer.SetDocument(_virtualDocument);
_documentViewer.View.Invalidate();
if (_documentViewer.Thumbnails != null)
_documentViewer.Thumbnails.Invalidate();
_documentViewer.EndUpdate();
}
}
Run the project by pressing F5, or by selecting Debug -> Start Debugging.
If the steps were followed correctly, the application runs and allows you to load a document into the viewer. When you load the document in the viewer, you can see the cache information in the Output window in Visual Studio.
This tutorial showed how to implement a Memory Cache
for the DocumentViewer
object.