mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2025-12-06 07:12:04 +01:00
Fully implemented SelectionMode.Extended in MapItemsControl, observe maximum number of download threads in TileImageLoader.
This commit is contained in:
parent
35c561d8aa
commit
e7f78df179
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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); }
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue