Introduction
WPF provide many ways to theme the application, i provide a new way to theme the application dynamically, neither AppDomain, nor loose xaml
Background
Make sure you are familar with the common WPF theme, maybe you can refer to the other great article at first
Using the code
Before present my theme way, let's see what's cons & pros on the classic theme way
Theme strategy
|
Load Speed
|
Native support( not require additional code)
|
Support code themed(bundle code & resource into single file)
|
Support resource hierarchy navigation
|
Load dynamically
|
Unload dynamically
|
Easily edit and compile(not require VS installed)
|
Direct Load
|
Fast
|
Y
|
Y
|
Y
|
Y
|
N
|
N
|
Loose Xaml
|
Low
|
Y
|
N
|
Y
|
Y
|
Y
|
Y
|
AppDomain
|
Fast
|
N
|
Y
|
N
|
Y
|
Y
|
N
|
Let's see some fact
- Xaml support pack protocol Uri
- pack protocol support custom parser( System.IO.Packaging.PackageStore)
- WPF support Zip file parser ( System.IO.Packaging.ZipPackage)
Now let's combine these fact into a new theme way
- add/remove custom parser for pack protocol
PackageStore.AddPackage(new Uri("customtheme://"), Package.Open("123.zip"));
PackageStore.RemovePackage(new Uri("customtheme://"));
- embed resources files/folders to single zip file, i use the 7za.exe (from 7zip command line tools )
7za.exe
|
a
|
-tzip
|
theme1.customtheme
|
.\Themes\theme1\*
|
-mm=copy
|
|
Add/ Replace files in zip
|
Use zip compress algorithm
|
Destination zip file, extension can be changed
|
The source files to compressed
|
The compress strategy, just copy , don’t do compress to
improve the decompress speed
|
-
Because ZipPackage is designed for XPS, and XPS
require to include a file named [Content_types].xml to identify the content type, so
make sure always present a valid [Content_types].xml besides the resource
files in the destination zip file
="1.0" ="utf-8"
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="png" ContentType="image/png" />
<Default Extension="tiff" ContentType="image/tiff" />
<Default Extension="jpg" ContentType="image/jpeg" />
<Default Extension="gif" ContentType="image/gif" />
<Default Extension="bmp" ContentType="image/bmp" />
<Default Extension="txt" ContentType="text/plain" />
<Default Extension="xaml" ContentType="application/xaml+xml" />
<Default Extension="xml" ContentType="application/xml" />
<Default Extension="avi" ContentType="video/avi" />
<Default Extension="mp4" ContentType="video/mp4" />
<Default Extension="mp3" ContentType="audio/mp3" />
</Types>
- i use MemoryMappedFile to improve IO access speed, because there are many little-size IO access when Xaml load theme files
Stream fileMapViewStream;
try
{
fileMapViewStream = MemoryMappedFile.OpenExisting(item.Replace("\\", ":"), MemoryMappedFileRights.Read).CreateViewStream(0, new FileInfo(item).Length, MemoryMappedFileAccess.Read);
}
catch(IOException)
{
fileMapViewStream = MemoryMappedFile.CreateFromFile(item, FileMode.Open, item.Replace("\\", ":"), new FileInfo(item).Length, MemoryMappedFileAccess.Read).CreateViewStream(0, new FileInfo(item).Length, MemoryMappedFileAccess.Read);
}
- At last, we got the most important code for this article
private void cmbThemes_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.RemovedItems != null)
{
foreach (var item in e.RemovedItems.OfType<string>())
{
PackageStore.RemovePackage(new Uri(System.IO.Path.GetFileName(item) + "://"));
}
}
if (e.AddedItems != null)
{
foreach (var item in e.AddedItems.OfType<string>())
{
Stream fileMapViewStream;
try
{
fileMapViewStream = MemoryMappedFile.OpenExisting(item.Replace("\\", ":"), MemoryMappedFileRights.Read).CreateViewStream(0, new FileInfo(item).Length, MemoryMappedFileAccess.Read);
}
catch(IOException)
{
fileMapViewStream = MemoryMappedFile.CreateFromFile(item, FileMode.Open, item.Replace("\\", ":"), new FileInfo(item).Length, MemoryMappedFileAccess.Read).CreateViewStream(0, new FileInfo(item).Length, MemoryMappedFileAccess.Read);
}
PackageStore.AddPackage(new Uri(System.IO.Path.GetFileName(item) + ": Application.Current.Resources.MergedDictionaries[0] = new ResourceDictionary() { Source = new Uri("pack://" + System.IO.Path.GetFileName(item) + ":,,,/ResourceDictionary.xaml") };
}
}
}
At last, let's see the comparison between the three classic theme ways
Theme strategy
|
Load Speed
|
Native support( not require additional code)
|
Support code themed(bundle code & resource into single file)
|
Support resource hierarchy navigation
|
Load dynamically
|
Unload dynamically
|
Easily edit and compile(not require VS installed)
|
Direct Load
|
Fast
|
Y
|
Y
|
Y
|
Y
|
N
|
N
|
Loose Xaml
|
Low
|
Y
|
N
|
Y
|
Y
|
Y
|
Y
|
AppDomain
|
Fast
|
N
|
Y
|
N
|
Y
|
Y
|
N
|
Zip file
|
Fast(*)
|
N
|
N
|
Y
|
Y
|
Y
|
Y
|
* Though Zip file don't compile the Xaml to baml, but consider the MemoryMappedFile, so will require less IO operation theoretically, speed should be faster
History