From 2fd3f5f8f679d57e0a595a09328957abb7b80a0b Mon Sep 17 00:00:00 2001 From: ClemensF Date: Fri, 20 Jul 2012 21:57:29 +0200 Subject: [PATCH] Simplified TileImageLoader and TileSource, added ImageTileSource, fixed FileDbCache --- Caching/FileDbCache/FileDbCache.cs | 90 +++++---- MapControl/Map.cs | 2 +- MapControl/Tile.cs | 27 ++- MapControl/TileImageLoader.cs | 143 ++++++++------- MapControl/TileLayer.cs | 10 +- MapControl/TileSource.cs | 172 ++++++++++-------- SampleApps/SampleApplication/App.config | 2 +- .../SampleApplication/ImageTileSource.cs | 13 ++ SampleApps/SampleApplication/MainWindow.xaml | 9 + .../Properties/Settings.Designer.cs | 4 +- .../Properties/Settings.settings | 2 +- .../SampleApplication.csproj | 1 + 12 files changed, 280 insertions(+), 195 deletions(-) create mode 100644 SampleApps/SampleApplication/ImageTileSource.cs diff --git a/Caching/FileDbCache/FileDbCache.cs b/Caching/FileDbCache/FileDbCache.cs index 84398fd1..5cf5531d 100644 --- a/Caching/FileDbCache/FileDbCache.cs +++ b/Caching/FileDbCache/FileDbCache.cs @@ -77,13 +77,12 @@ namespace Caching try { fileDb.Open(path, false); + Trace.TraceInformation("FileDbCache: Opened database with {0} cached items in {1}", fileDb.NumRecords, path); } catch { - CreateDatebase(); + CreateDatabase(); } - - Trace.TraceInformation("FileDbCache: Opened database with {0} cached items in {1}", fileDb.NumRecords, path); } public bool AutoFlush @@ -139,19 +138,14 @@ namespace Caching { count = fileDb.NumRecords; } - catch (Exception ex1) + catch (Exception ex) { - Trace.TraceWarning("FileDbCache: FileDb.NumRecords failed: {0}", ex1.Message); + Trace.TraceWarning("FileDbCache: FileDb.NumRecords failed: {0}", ex.Message); - try + if (RepairDatabase()) { - fileDb.Reindex(); count = fileDb.NumRecords; } - catch (Exception ex2) - { - Trace.TraceWarning("FileDbCache: FileDb.Reindex() failed: {0}", ex2.Message); - } } } @@ -168,19 +162,14 @@ namespace Caching { record = fileDb.GetRecordByKey(key, new string[] { valueField }, false); } - catch (Exception ex1) + catch (Exception ex) { - Trace.TraceWarning("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex1.Message); + Trace.TraceWarning("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message); - try + if (RepairDatabase()) { - fileDb.Reindex(); record = fileDb.GetRecordByKey(key, new string[] { valueField }, false); } - catch (Exception ex2) - { - Trace.TraceWarning("FileDbCache: FileDb.Reindex() failed: {0}", ex2.Message); - } } } @@ -228,7 +217,7 @@ namespace Caching } catch (Exception ex1) { - Trace.TraceWarning("FileDbCache: Failed deserializing item \"{0}\": {1}", key, ex1.Message); + Trace.TraceWarning("FileDbCache: Deserializing item \"{0}\" failed: {1}", key, ex1.Message); try { @@ -298,7 +287,7 @@ namespace Caching } catch (Exception ex) { - Trace.TraceWarning("FileDbCache: Failed serializing item \"{0}\": {1}", key, ex.Message); + Trace.TraceWarning("FileDbCache: Serializing item \"{0}\" failed: {1}", key, ex.Message); } if (valueBuffer != null) @@ -318,19 +307,12 @@ namespace Caching { AddOrUpdateRecord(key, valueBuffer, expires); } - catch (Exception ex1) + catch (Exception ex) { - Trace.TraceWarning("FileDbCache: FileDb.UpdateRecordByKey(\"{0}\") failed: {1}", key, ex1.Message); + Trace.TraceWarning("FileDbCache: FileDb.UpdateRecordByKey(\"{0}\") failed: {1}", key, ex.Message); - try + if (RepairDatabase()) { - fileDb.Reindex(); - AddOrUpdateRecord(key, valueBuffer, expires); - } - catch (Exception ex2) - { - Trace.TraceWarning("FileDbCache: FileDb.Reindex() failed: {0}, creating new database", ex2.Message); - CreateDatebase(); AddOrUpdateRecord(key, valueBuffer, expires); } } @@ -422,30 +404,62 @@ namespace Caching } catch { - fileDb.Reindex(); } fileDb.Close(); } } - private void CreateDatebase() + private bool RepairDatabase() { - if (File.Exists(path)) + try { - File.Delete(path); + fileDb.Reindex(); } - else + catch (Exception ex) { - Directory.CreateDirectory(Path.GetDirectoryName(path)); + Trace.TraceWarning("FileDbCache: FileDb.Reindex() failed: {0}", ex.Message); + return CreateDatabase(); } - fileDb.Create(path, new Field[] + return true; + } + + private bool CreateDatabase() + { + if (fileDb.IsOpen) + { + fileDb.Close(); + } + + try + { + if (File.Exists(path)) + { + File.Delete(path); + } + else + { + Directory.CreateDirectory(Path.GetDirectoryName(path)); + } + + fileDb.Create(path, + new Field[] { new Field(keyField, DataTypeEnum.String) { IsPrimaryKey = true }, new Field(valueField, DataTypeEnum.Byte) { IsArray = true }, new Field(expiresField, DataTypeEnum.DateTime) }); + + Trace.TraceInformation("FileDbCache: Created database {0}", path); + } + catch (Exception ex) + { + Trace.TraceWarning("FileDbCache: Creating database failed: {0}", ex.Message); + return false; + } + + return true; } private void AddOrUpdateRecord(string key, object value, DateTime expires) diff --git a/MapControl/Map.cs b/MapControl/Map.cs index c7df4c83..2ef0d6e7 100644 --- a/MapControl/Map.cs +++ b/MapControl/Map.cs @@ -115,7 +115,7 @@ namespace MapControl MainTileLayer = new TileLayer { Description = "© {y} OpenStreetMap Contributors, CC-BY-SA", - TileSource = new OpenStreetMapTileSource { UriFormat = "http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png" } + TileSource = new TileSource("http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png") }; } }; diff --git a/MapControl/Tile.cs b/MapControl/Tile.cs index 84f5f213..2f5ef2e0 100644 --- a/MapControl/Tile.cs +++ b/MapControl/Tile.cs @@ -5,6 +5,7 @@ using System; using System.Windows.Media; using System.Windows.Media.Animation; +using System.Windows.Media.Imaging; namespace MapControl { @@ -42,11 +43,35 @@ namespace MapControl { if (Brush.ImageSource == null) { - Brush.BeginAnimation(ImageBrush.OpacityProperty, opacityAnimation); + BitmapImage bitmap = value as BitmapImage; + + if (bitmap != null && bitmap.IsDownloading) + { + bitmap.DownloadCompleted += BitmapDownloadCompleted; + bitmap.DownloadFailed += BitmapDownloadFailed; + } + else + { + Brush.BeginAnimation(ImageBrush.OpacityProperty, opacityAnimation); + } } Brush.ImageSource = value; } } + + private void BitmapDownloadCompleted(object sender, EventArgs e) + { + ((BitmapImage)sender).DownloadCompleted -= BitmapDownloadCompleted; + ((BitmapImage)sender).DownloadFailed -= BitmapDownloadFailed; + Brush.BeginAnimation(ImageBrush.OpacityProperty, opacityAnimation); + } + + private void BitmapDownloadFailed(object sender, ExceptionEventArgs e) + { + ((BitmapImage)sender).DownloadCompleted -= BitmapDownloadCompleted; + ((BitmapImage)sender).DownloadFailed -= BitmapDownloadFailed; + Brush.ImageSource = null; + } } } diff --git a/MapControl/TileImageLoader.cs b/MapControl/TileImageLoader.cs index 5050b6eb..6af4a58b 100644 --- a/MapControl/TileImageLoader.cs +++ b/MapControl/TileImageLoader.cs @@ -37,7 +37,6 @@ namespace MapControl private readonly TileLayer tileLayer; private readonly Queue pendingTiles = new Queue(); private readonly HashSet currentRequests = new HashSet(); - private int numDownloads; /// /// The ObjectCache used to cache tile images. @@ -115,12 +114,12 @@ namespace MapControl this.tileLayer = tileLayer; } - internal void BeginDownloadTiles(ICollection tiles) + internal void BeginGetTiles(ICollection tiles) { - ThreadPool.QueueUserWorkItem(BeginDownloadTilesAsync, new List(tiles.Where(t => t.Image == null && t.Uri == null))); + ThreadPool.QueueUserWorkItem(BeginGetTilesAsync, new List(tiles.Where(t => t.Image == null && t.Uri == null))); } - internal void CancelDownloadTiles() + internal void CancelGetTiles() { lock (pendingTiles) { @@ -136,77 +135,90 @@ namespace MapControl } } - private void BeginDownloadTilesAsync(object newTilesList) + private void BeginGetTilesAsync(object newTilesList) { List newTiles = (List)newTilesList; + ImageTileSource imageTileSource = tileLayer.TileSource as ImageTileSource; - lock (pendingTiles) + if (imageTileSource != null) { - if (Cache == null) + newTiles.ForEach(tile => { - newTiles.ForEach(tile => pendingTiles.Enqueue(tile)); - } - else + Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => tile.Image = imageTileSource.GetImage(tile.XIndex, tile.Y, tile.ZoomLevel))); + }); + } + else + { + lock (pendingTiles) { - List outdatedTiles = new List(newTiles.Count); - - newTiles.ForEach(tile => + if (Cache == null) { - string key = CacheKey(tile); - CachedImage cachedImage = Cache.Get(key) as CachedImage; + newTiles.ForEach(tile => pendingTiles.Enqueue(tile)); + } + else + { + List outdatedTiles = new List(newTiles.Count); - if (cachedImage == null) + newTiles.ForEach(tile => { - pendingTiles.Enqueue(tile); - } - else if (!CreateTileImage(tile, cachedImage.ImageBuffer)) - { - // got corrupted buffer from cache - Cache.Remove(key); - pendingTiles.Enqueue(tile); - } - else if (cachedImage.CreationTime + CacheUpdateAge < DateTime.UtcNow) - { - // update cached image - outdatedTiles.Add(tile); - } - }); + string key = CacheKey(tile); + CachedImage cachedImage = Cache.Get(key) as CachedImage; - outdatedTiles.ForEach(tile => pendingTiles.Enqueue(tile)); + if (cachedImage == null) + { + pendingTiles.Enqueue(tile); + } + else if (!CreateTileImage(tile, cachedImage.ImageBuffer)) + { + // got corrupted buffer from cache + Cache.Remove(key); + pendingTiles.Enqueue(tile); + } + else if (cachedImage.CreationTime + CacheUpdateAge < DateTime.UtcNow) + { + // update cached image + outdatedTiles.Add(tile); + } + }); + + outdatedTiles.ForEach(tile => pendingTiles.Enqueue(tile)); + } + + int numDownloads = Math.Min(pendingTiles.Count, tileLayer.MaxParallelDownloads); + + while (--numDownloads >= 0) + { + ThreadPool.QueueUserWorkItem(DownloadTiles); + } + } + } + } + + private void DownloadTiles(object o) + { + while (pendingTiles.Count > 0) + { + Tile tile; + + lock (pendingTiles) + { + if (pendingTiles.Count == 0) + { + break; + } + + tile = pendingTiles.Dequeue(); } - DownloadNextTiles(null); - } - } - - private void DownloadNextTiles(object o) - { - while (pendingTiles.Count > 0 && numDownloads < tileLayer.MaxDownloads) - { - Tile tile = pendingTiles.Dequeue(); tile.Uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel); - numDownloads++; + byte[] imageBuffer = DownloadImage(tile); - ThreadPool.QueueUserWorkItem(DownloadTileAsync, tile); - } - } - - private void DownloadTileAsync(object t) - { - Tile tile = (Tile)t; - byte[] imageBuffer = DownloadImage(tile); - - if (imageBuffer != null && - CreateTileImage(tile, imageBuffer) && - Cache != null) - { - Cache.Set(CacheKey(tile), new CachedImage(imageBuffer), new CacheItemPolicy { SlidingExpiration = CacheExpiration }); - } - - lock (pendingTiles) - { - numDownloads--; - DownloadNextTiles(null); + if (imageBuffer != null && + CreateTileImage(tile, imageBuffer) && + Cache != null) + { + Cache.Set(CacheKey(tile), new CachedImage(imageBuffer), new CacheItemPolicy { SlidingExpiration = CacheExpiration }); + } } } @@ -256,7 +268,7 @@ namespace MapControl { TraceInformation("{0} - {1}", tile.Uri, ((HttpWebResponse)ex.Response).StatusCode); } - else if (ex.Status == WebExceptionStatus.RequestCanceled) // by HttpWebRequest.Abort in CancelDownloadTiles + else if (ex.Status == WebExceptionStatus.RequestCanceled) // by HttpWebRequest.Abort in CancelGetTiles { TraceInformation("{0} - {1}", tile.Uri, ex.Status); } @@ -285,10 +297,10 @@ namespace MapControl private bool CreateTileImage(Tile tile, byte[] buffer) { + BitmapImage bitmap = new BitmapImage(); + try { - BitmapImage bitmap = new BitmapImage(); - using (Stream stream = new MemoryStream(buffer)) { bitmap.BeginInit(); @@ -297,8 +309,6 @@ namespace MapControl bitmap.EndInit(); bitmap.Freeze(); } - - Dispatcher.BeginInvoke((Action)(() => tile.Image = bitmap)); } catch (Exception ex) { @@ -306,6 +316,7 @@ namespace MapControl return false; } + Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => tile.Image = bitmap)); return true; } diff --git a/MapControl/TileLayer.cs b/MapControl/TileLayer.cs index 8325c1d6..9ebd4db0 100644 --- a/MapControl/TileLayer.cs +++ b/MapControl/TileLayer.cs @@ -30,14 +30,14 @@ namespace MapControl Name = string.Empty; MinZoomLevel = 1; MaxZoomLevel = 18; - MaxDownloads = 8; + MaxParallelDownloads = 8; } public string Name { get; set; } public TileSource TileSource { get; set; } public int MinZoomLevel { get; set; } public int MaxZoomLevel { get; set; } - public int MaxDownloads { get; set; } + public int MaxParallelDownloads { get; set; } public bool HasDarkBackground { get; set; } public string Description @@ -57,21 +57,21 @@ namespace MapControl this.grid = grid; this.zoomLevel = zoomLevel; - tileImageLoader.CancelDownloadTiles(); + tileImageLoader.CancelGetTiles(); if (VisualParent != null && TileSource != null) { SelectTiles(); RenderTiles(); - tileImageLoader.BeginDownloadTiles(tiles); + tileImageLoader.BeginGetTiles(tiles); } } internal void ClearTiles() { tiles.Clear(); - tileImageLoader.CancelDownloadTiles(); + tileImageLoader.CancelGetTiles(); } private void SelectTiles() diff --git a/MapControl/TileSource.cs b/MapControl/TileSource.cs index 52e7c66b..b7d2c2d9 100644 --- a/MapControl/TileSource.cs +++ b/MapControl/TileSource.cs @@ -7,48 +7,100 @@ using System.ComponentModel; using System.Globalization; using System.Text; using System.Windows; +using System.Windows.Media; namespace MapControl { /// - /// Defines the URI of a map tile. + /// Provides the URI of a map tile. /// [TypeConverter(typeof(TileSourceTypeConverter))] public class TileSource { - public string UriFormat { get; set; } + private Func getUri; + private string uriFormat = string.Empty; + private int hostIndex = -1; + + public TileSource() + { + } + + public TileSource(string uriFormat) + { + UriFormat = uriFormat; + } + + public string UriFormat + { + get { return uriFormat; } + set + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("The value of the UriFormat proprty must not be null or empty or white-space only."); + } + + if (value.Contains("{x}") && value.Contains("{y}") && value.Contains("{z}")) + { + if (value.Contains("{c}")) + { + getUri = GetOpenStreetMapUri; + } + else if (value.Contains("{i}")) + { + getUri = GetGoogleMapsUri; + } + else if (value.Contains("{n}")) + { + getUri = GetMapQuestUri; + } + else + { + getUri = GetDefaultUri; + } + } + else if (value.Contains("{q}")) // {i} is optional + { + getUri = GetQuadKeyUri; + } + else if (value.Contains("{w}") && value.Contains("{s}") && value.Contains("{e}") && value.Contains("{n}")) + { + getUri = GetBoundingBoxUri; + } + else + { + throw new ArgumentException("The specified UriFormat is not supported."); + } + + uriFormat = value; + } + } public virtual Uri GetUri(int x, int y, int zoomLevel) { - return new Uri(UriFormat. - Replace("{x}", x.ToString()). - Replace("{y}", y.ToString()). - Replace("{z}", zoomLevel.ToString())); + return getUri != null ? getUri(x, y, zoomLevel) : null; } - } - public class OpenStreetMapTileSource : TileSource - { - private static string[] hostChars = { "a", "b", "c" }; - private int hostChar = -1; - - public override Uri GetUri(int x, int y, int zoomLevel) + private Uri GetDefaultUri(int x, int y, int zoomLevel) { - hostChar = (hostChar + 1) % 3; - return new Uri(UriFormat. - Replace("{c}", hostChars[hostChar]). Replace("{x}", x.ToString()). Replace("{y}", y.ToString()). Replace("{z}", zoomLevel.ToString())); } - } - public class GoogleMapsTileSource : TileSource - { - private int hostIndex = -1; + private Uri GetOpenStreetMapUri(int x, int y, int zoomLevel) + { + hostIndex = (hostIndex + 1) % 3; - public override Uri GetUri(int x, int y, int zoomLevel) + return new Uri(UriFormat. + Replace("{c}", "abc".Substring(hostIndex, 1)). + Replace("{x}", x.ToString()). + Replace("{y}", y.ToString()). + Replace("{z}", zoomLevel.ToString())); + } + + private Uri GetGoogleMapsUri(int x, int y, int zoomLevel) { hostIndex = (hostIndex + 1) % 4; @@ -58,27 +110,19 @@ namespace MapControl Replace("{y}", y.ToString()). Replace("{z}", zoomLevel.ToString())); } - } - public class MapQuestTileSource : TileSource - { - private int hostNumber; - - public override Uri GetUri(int x, int y, int zoomLevel) + private Uri GetMapQuestUri(int x, int y, int zoomLevel) { - hostNumber = (hostNumber % 4) + 1; + hostIndex = (hostIndex % 4) + 1; return new Uri(UriFormat. - Replace("{n}", hostNumber.ToString()). + Replace("{n}", hostIndex.ToString()). Replace("{x}", x.ToString()). Replace("{y}", y.ToString()). Replace("{z}", zoomLevel.ToString())); } - } - public class QuadKeyTileSource : TileSource - { - public override Uri GetUri(int x, int y, int zoomLevel) + private Uri GetQuadKeyUri(int x, int y, int zoomLevel) { StringBuilder key = new StringBuilder { Length = zoomLevel }; @@ -91,11 +135,8 @@ namespace MapControl Replace("{i}", key.ToString(key.Length - 1, 1)). Replace("{q}", key.ToString())); } - } - public class BoundingBoxTileSource : TileSource - { - public override Uri GetUri(int x, int y, int zoomLevel) + private Uri GetBoundingBoxUri(int x, int y, int zoomLevel) { MercatorTransform t = new MercatorTransform(); double n = 1 << zoomLevel; @@ -114,57 +155,28 @@ namespace MapControl } } + /// + /// Provides the image of a map tile. ImageTileSource bypasses downloading + /// and caching of tile images that is performed by TileImageLoader. + /// + public abstract class ImageTileSource : TileSource + { + public abstract ImageSource GetImage(int x, int y, int zoomLevel); + } + + /// + /// Converts from string to TileSource. + /// public class TileSourceTypeConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { - return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); + return sourceType == typeof(string); } public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - string uriFormat = value as string; - - if (uriFormat != null) - { - TileSource tileSource = null; - - if (uriFormat.Contains("{x}") && uriFormat.Contains("{y}") && uriFormat.Contains("{z}")) - { - if (uriFormat.Contains("{c}")) - { - tileSource = new OpenStreetMapTileSource(); - } - else if (uriFormat.Contains("{i}")) - { - tileSource = new GoogleMapsTileSource(); - } - else if (uriFormat.Contains("{n}")) - { - tileSource = new MapQuestTileSource(); - } - else - { - tileSource = new TileSource(); - } - } - else if (uriFormat.Contains("{q}")) - { - tileSource = new QuadKeyTileSource(); - } - else if (uriFormat.Contains("{w}") && uriFormat.Contains("{s}") && uriFormat.Contains("{e}") && uriFormat.Contains("{n}")) - { - tileSource = new BoundingBoxTileSource(); - } - - if (tileSource != null) - { - tileSource.UriFormat = uriFormat; - return tileSource; - } - } - - return base.ConvertFrom(context, culture, value); + return new TileSource(value as string); } } } diff --git a/SampleApps/SampleApplication/App.config b/SampleApps/SampleApplication/App.config index 19261611..bdcb4591 100644 --- a/SampleApps/SampleApplication/App.config +++ b/SampleApps/SampleApplication/App.config @@ -8,7 +8,7 @@ - True + False diff --git a/SampleApps/SampleApplication/ImageTileSource.cs b/SampleApps/SampleApplication/ImageTileSource.cs new file mode 100644 index 00000000..7815e32e --- /dev/null +++ b/SampleApps/SampleApplication/ImageTileSource.cs @@ -0,0 +1,13 @@ +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace SampleApplication +{ + public class ImageTileSource : MapControl.ImageTileSource + { + public override ImageSource GetImage(int x, int y, int zoomLevel) + { + return new BitmapImage(GetUri(x, y, zoomLevel)); + } + } +} diff --git a/SampleApps/SampleApplication/MainWindow.xaml b/SampleApps/SampleApplication/MainWindow.xaml index 49113660..781c0f98 100644 --- a/SampleApps/SampleApplication/MainWindow.xaml +++ b/SampleApps/SampleApplication/MainWindow.xaml @@ -128,6 +128,7 @@ TileSource="http://{c}.tile3.opencyclemap.org/landscape/{z}/{x}/{y}.png"/> + + + + + + + + + diff --git a/SampleApps/SampleApplication/Properties/Settings.Designer.cs b/SampleApps/SampleApplication/Properties/Settings.Designer.cs index 3e61f19c..4a720455 100644 --- a/SampleApps/SampleApplication/Properties/Settings.Designer.cs +++ b/SampleApps/SampleApplication/Properties/Settings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.225 +// Runtime Version:4.0.30319.269 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -25,7 +25,7 @@ namespace SampleApplication.Properties { [global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("True")] + [global::System.Configuration.DefaultSettingValueAttribute("False")] public bool UsePersistentCache { get { return ((bool)(this["UsePersistentCache"])); diff --git a/SampleApps/SampleApplication/Properties/Settings.settings b/SampleApps/SampleApplication/Properties/Settings.settings index 81f05344..889a6332 100644 --- a/SampleApps/SampleApplication/Properties/Settings.settings +++ b/SampleApps/SampleApplication/Properties/Settings.settings @@ -3,7 +3,7 @@ - True + False \ No newline at end of file diff --git a/SampleApps/SampleApplication/SampleApplication.csproj b/SampleApps/SampleApplication/SampleApplication.csproj index f8ae4383..1807aac9 100644 --- a/SampleApps/SampleApplication/SampleApplication.csproj +++ b/SampleApps/SampleApplication/SampleApplication.csproj @@ -54,6 +54,7 @@ MSBuild:Compile Designer + True True