Fully implemented SelectionMode.Extended in MapItemsControl, observe maximum number of download threads in TileImageLoader.

This commit is contained in:
ClemensF 2012-08-24 18:10:12 +02:00
parent 35c561d8aa
commit e7f78df179
5 changed files with 145 additions and 94 deletions

View file

@ -43,16 +43,9 @@ namespace MapControl
IsEnabledChanged += IsEnabledPropertyChanged;
}
public event RoutedEventHandler Selected
public Map ParentMap
{
add { AddHandler(SelectedEvent, value); }
remove { RemoveHandler(SelectedEvent, value); }
}
public event RoutedEventHandler Unselected
{
add { AddHandler(UnselectedEvent, value); }
remove { RemoveHandler(UnselectedEvent, value); }
get { return MapPanel.GetParentMap(this); }
}
public bool IsSelected
@ -66,9 +59,16 @@ namespace MapControl
get { return MapItemsControl.GetIsCurrent(this); }
}
public Map ParentMap
public event RoutedEventHandler Selected
{
get { return MapPanel.GetParentMap(this); }
add { AddHandler(SelectedEvent, value); }
remove { RemoveHandler(SelectedEvent, value); }
}
public event RoutedEventHandler Unselected
{
add { AddHandler(UnselectedEvent, value); }
remove { RemoveHandler(UnselectedEvent, value); }
}
protected override void OnMouseEnter(MouseEventArgs e)

View file

@ -40,6 +40,11 @@ namespace MapControl
Items.CurrentChanged += OnCurrentItemChanged;
}
public Map ParentMap
{
get { return MapPanel.GetParentMap(this); }
}
public SelectionMode SelectionMode
{
get { return (SelectionMode)GetValue(SelectionModeProperty); }
@ -74,7 +79,7 @@ namespace MapControl
public IList GetItemsInGeometry(Geometry geometry)
{
return GetItemsInGeometry(geometry, new ArrayList(Items.Count), Items.Count);
return GetItemsInGeometry(geometry, new ArrayList());
}
protected override bool IsItemItsOwnContainerOverride(object item)
@ -111,15 +116,39 @@ namespace MapControl
{
e.Handled = true;
UIElement container = (UIElement)sender;
UIElement selectedContainer;
if (SelectionMode == SelectionMode.Extended &&
(Keyboard.Modifiers & (ModifierKeys.Control | ModifierKeys.Shift)) == 0)
if (SelectionMode != SelectionMode.Extended || (Keyboard.Modifiers & ModifierKeys.Control) != 0)
{
Selector.SetIsSelected(container, !Selector.GetIsSelected(container));
}
else if ((Keyboard.Modifiers & ModifierKeys.Shift) == 0)
{
SelectedItem = GetItem(container);
}
else
else if ((selectedContainer = GetContainer(SelectedItem)) != null)
{
Selector.SetIsSelected(container, !Selector.GetIsSelected(container));
ViewportPosition p1 = MapPanel.GetViewportPosition(selectedContainer);
ViewportPosition p2 = MapPanel.GetViewportPosition(container);
if (p1 != null && p2 != null)
{
Rect rect = new Rect(p1.Position, p2.Position);
BeginUpdateSelectedItems();
SelectedItems.Clear();
SelectedItems.Add(SelectedItem);
foreach (object item in Items)
{
if (item != SelectedItem && IsItemInRect(item, rect))
{
SelectedItems.Add(item);
}
}
EndUpdateSelectedItems();
}
}
}
@ -161,46 +190,68 @@ namespace MapControl
{
if (SelectionMode == SelectionMode.Single)
{
IList items = GetItemsInGeometry(geometry, new ArrayList(1), 1);
SelectedItem = items.Count > 0 ? items[0] : null;
SelectedItem = GetFirstItemInGeometry(geometry);
}
else
{
BeginUpdateSelectedItems();
GetItemsInGeometry(geometry, SelectedItems, Items.Count);
SelectedItems.Clear();
GetItemsInGeometry(geometry, SelectedItems);
EndUpdateSelectedItems();
}
}
}
private IList GetItemsInGeometry(Geometry geometry, IList items, int maxItems)
private object GetFirstItemInGeometry(Geometry geometry)
{
items.Clear();
if (!geometry.IsEmpty())
{
foreach (object item in Items)
{
UIElement container = GetContainer(item);
if (container != null)
if (IsItemInGeometry(item, geometry))
{
ViewportPosition viewportPosition = MapPanel.GetViewportPosition(container);
return item;
}
}
}
if (viewportPosition != null && geometry.FillContains(viewportPosition.Position))
{
items.Add(item);
return null;
}
if (items.Count >= maxItems)
{
break;
}
}
private IList GetItemsInGeometry(Geometry geometry, IList items)
{
if (!geometry.IsEmpty())
{
foreach (object item in Items)
{
if (IsItemInGeometry(item, geometry))
{
items.Add(item);
}
}
}
return items;
}
private bool IsItemInGeometry(object item, Geometry geometry)
{
UIElement container = GetContainer(item);
ViewportPosition viewportPosition;
return container != null
&& (viewportPosition = MapPanel.GetViewportPosition(container)) != null
&& geometry.FillContains(viewportPosition.Position);
}
private bool IsItemInRect(object item, Rect rect)
{
UIElement container = GetContainer(item);
ViewportPosition viewportPosition;
return container != null
&& (viewportPosition = MapPanel.GetViewportPosition(container)) != null
&& rect.Contains(viewportPosition.Position);
}
}
}

View file

@ -23,6 +23,11 @@ namespace MapControl
typeof(Pushpin), new FrameworkPropertyMetadata(typeof(Pushpin)));
}
public Map ParentMap
{
get { return MapPanel.GetParentMap(this); }
}
public Location Location
{
get { return (Location)GetValue(LocationProperty); }

View file

@ -19,10 +19,11 @@ namespace MapControl
/// <summary>
/// Loads map tile images by their URIs and optionally caches the images in an ObjectCache.
/// </summary>
public class TileImageLoader : DispatcherObject
public class TileImageLoader
{
private readonly TileLayer tileLayer;
private readonly ConcurrentQueue<Tile> pendingTiles = new ConcurrentQueue<Tile>();
private int downloadThreadCount;
/// <summary>
/// Default Name of an ObjectCache instance that is assigned to the Cache property.
@ -90,7 +91,7 @@ namespace MapControl
newTiles.ForEach(tile =>
{
Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => tile.Image = imageTileSource.GetImage(tile.XIndex, tile.Y, tile.ZoomLevel)));
tileLayer.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => tile.Image = imageTileSource.GetImage(tile.XIndex, tile.Y, tile.ZoomLevel)));
});
}
else
@ -106,19 +107,19 @@ namespace MapControl
newTiles.ForEach(tile =>
{
string key = CacheKey(tile);
byte[] imageBuffer = Cache.Get(key) as byte[];
byte[] buffer = Cache.Get(key) as byte[];
if (imageBuffer == null)
if (buffer == null)
{
pendingTiles.Enqueue(tile);
}
else if (!CreateTileImage(tile, imageBuffer))
else if (!CreateTileImage(tile, buffer))
{
// got corrupted buffer from cache
Cache.Remove(key);
pendingTiles.Enqueue(tile);
}
else if (IsCacheOutdated(imageBuffer))
else if (IsCacheOutdated(buffer))
{
// update cached image
outdatedTiles.Add(tile);
@ -128,28 +129,31 @@ namespace MapControl
outdatedTiles.ForEach(tile => pendingTiles.Enqueue(tile));
}
int numDownloads = Math.Min(pendingTiles.Count, tileLayer.MaxParallelDownloads);
while (--numDownloads >= 0)
while (downloadThreadCount < Math.Min(pendingTiles.Count, tileLayer.MaxParallelDownloads))
{
Interlocked.Increment(ref downloadThreadCount);
ThreadPool.QueueUserWorkItem(DownloadTiles);
}
}
}
private void DownloadTiles(object o)
{
Tile tile;
while (pendingTiles.TryDequeue(out tile))
{
tile.Uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
byte[] imageBuffer = DownloadImage(tile);
byte[] buffer = DownloadImage(tile.Uri);
if (imageBuffer != null && CreateTileImage(tile, imageBuffer) && Cache != null)
if (buffer != null && CreateTileImage(tile, buffer) && Cache != null)
{
Cache.Set(CacheKey(tile), imageBuffer, new CacheItemPolicy { SlidingExpiration = CacheExpiration });
Cache.Set(CacheKey(tile), buffer, new CacheItemPolicy { SlidingExpiration = CacheExpiration });
}
}
Interlocked.Decrement(ref downloadThreadCount);
}
private string CacheKey(Tile tile)
@ -157,18 +161,39 @@ namespace MapControl
return string.Format("{0}/{1}/{2}/{3}", tileLayer.Name, tile.ZoomLevel, tile.XIndex, tile.Y);
}
private byte[] DownloadImage(Tile tile)
private bool CreateTileImage(Tile tile, byte[] buffer)
{
BitmapImage bitmap = new BitmapImage();
try
{
using (Stream stream = new MemoryStream(buffer, 8, buffer.Length - 8, false))
{
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
bitmap.Freeze();
}
}
catch (Exception ex)
{
Trace.TraceWarning("Creating tile image failed: {0}", ex.Message);
return false;
}
tileLayer.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => tile.Image = bitmap));
return true;
}
private static byte[] DownloadImage(Uri uri)
{
HttpWebRequest request = null;
byte[] buffer = null;
try
{
TraceInformation("{0} - Requesting", tile.Uri);
request = (HttpWebRequest)WebRequest.Create(tile.Uri);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.UserAgent = typeof(TileImageLoader).ToString();
request.KeepAlive = true;
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (Stream responseStream = response.GetResponseStream())
@ -185,67 +210,36 @@ namespace MapControl
}
}
TraceInformation("{0} - Completed", tile.Uri);
Trace.TraceInformation("Downloaded {0}", uri);
}
catch (WebException ex)
{
if (ex.Status == WebExceptionStatus.ProtocolError)
{
TraceInformation("{0} - {1}", tile.Uri, ((HttpWebResponse)ex.Response).StatusCode);
HttpStatusCode statusCode = ((HttpWebResponse)ex.Response).StatusCode;
if (statusCode != HttpStatusCode.NotFound)
{
Trace.TraceInformation("Downloading {0} failed: {1}", uri, ex.Message);
}
}
else
{
TraceWarning("{0} - {1}", tile.Uri, ex.Status);
Trace.TraceWarning("Downloading {0} failed with {1}: {2}", uri, ex.Status, ex.Message);
}
}
catch (Exception ex)
{
TraceWarning("{0} - {1}", tile.Uri, ex.Message);
Trace.TraceWarning("Downloading {0} failed: {1}", uri, ex.Message);
}
return buffer;
}
private bool IsCacheOutdated(byte[] imageBuffer)
private static bool IsCacheOutdated(byte[] buffer)
{
long creationTime = BitConverter.ToInt64(imageBuffer, 0);
long creationTime = BitConverter.ToInt64(buffer, 0);
return DateTime.FromBinary(creationTime) + CacheUpdateAge < DateTime.UtcNow;
}
private bool CreateTileImage(Tile tile, byte[] imageBuffer)
{
BitmapImage bitmap = new BitmapImage();
try
{
using (Stream stream = new MemoryStream(imageBuffer, 8, imageBuffer.Length - 8, false))
{
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
bitmap.Freeze();
}
}
catch (Exception ex)
{
TraceWarning("Creating tile image failed: {0}", ex.Message);
return false;
}
Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => tile.Image = bitmap));
return true;
}
private static void TraceWarning(string format, params object[] args)
{
Trace.TraceWarning("[{0:00}] {1}", Thread.CurrentThread.ManagedThreadId, string.Format(format, args));
}
private static void TraceInformation(string format, params object[] args)
{
//Trace.TraceInformation("[{0:00}] {1}", Thread.CurrentThread.ManagedThreadId, string.Format(format, args));
}
}
}

View file

@ -133,7 +133,8 @@ namespace MapControl
drawingContext.DrawRectangle(tile.Brush, null, tileRect);
//if (tile.ZoomLevel == zoomLevel)
// drawingContext.DrawText(new FormattedText(string.Format("{0}-{1}-{2}", tile.ZoomLevel, tile.X, tile.Y), System.Globalization.CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface("Segoe UI"), 14, Brushes.Black), tileRect.TopLeft);
// drawingContext.DrawText(new FormattedText(string.Format("{0}-{1}-{2}", tile.ZoomLevel, tile.X, tile.Y),
// System.Globalization.CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface("Segoe UI"), 14, Brushes.Black), tileRect.TopLeft);
});
}
}