Version 2.2.0:

- IMapElement interface with readonly ParentMap property and SetParentMap method that is explicitly implemented
- Removed MetersPerDegree from MapBase
- Set StrokeThickness instead of Stroke in MapGraticule ctor in SL/WinRT
This commit is contained in:
ClemensF 2014-07-22 20:02:30 +02:00
parent 4e0253aa70
commit 8917e1d4cb
19 changed files with 131 additions and 139 deletions

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.1.0")] [assembly: AssemblyVersion("2.2.0")]
[assembly: AssemblyFileVersion("2.1.0")] [assembly: AssemblyFileVersion("2.2.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.1.0")] [assembly: AssemblyVersion("2.2.0")]
[assembly: AssemblyFileVersion("2.1.0")] [assembly: AssemblyFileVersion("2.2.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -6,6 +6,8 @@ namespace MapControl
{ {
public interface IMapElement public interface IMapElement
{ {
MapBase ParentMap { get; set; } MapBase ParentMap { get; }
void SetParentMap(MapBase parentMap);
} }
} }

View file

@ -22,6 +22,11 @@ namespace MapControl
public ImageFileCache(IStorageFolder folder) public ImageFileCache(IStorageFolder folder)
{ {
if (folder == null)
{
throw new ArgumentNullException("The parameter folder must not be null.");
}
rootFolder = folder; rootFolder = folder;
} }

View file

@ -198,16 +198,6 @@ namespace MapControl
set { SetValue(TargetHeadingProperty, value); } set { SetValue(TargetHeadingProperty, value); }
} }
/// <summary>
/// Gets the scaling factor from cartesian map coordinates to viewport coordinates.
/// </summary>
public double ViewportScale { get; private set; }
/// <summary>
/// Gets the scaling factor from meters to viewport coordinate units (pixels) at the Center location.
/// </summary>
public double CenterScale { get; private set; }
/// <summary> /// <summary>
/// Gets the transformation from geographic coordinates to cartesian map coordinates. /// Gets the transformation from geographic coordinates to cartesian map coordinates.
/// </summary> /// </summary>
@ -249,23 +239,21 @@ namespace MapControl
} }
/// <summary> /// <summary>
/// Gets the conversion factor from longitude degrees to meters, at latitude = 0. /// Gets the scaling factor from cartesian map coordinates to viewport coordinates.
/// </summary> /// </summary>
public double MetersPerDegree public double ViewportScale { get; private set; }
{
get /// <summary>
{ /// Gets the scaling factor from meters to viewport coordinate units (pixels) at the Center location.
return (TileLayer != null && TileLayer.TileSource != null) ? /// </summary>
TileLayer.TileSource.MetersPerDegree : (TileSource.EarthRadius * Math.PI / 180d); public double CenterScale { get; private set; }
}
}
/// <summary> /// <summary>
/// Gets the map scale at the specified location as viewport coordinate units (pixels) per meter. /// Gets the map scale at the specified location as viewport coordinate units (pixels) per meter.
/// </summary> /// </summary>
public double GetMapScale(Location location) public double GetMapScale(Location location)
{ {
return mapTransform.RelativeScale(location) * Math.Pow(2d, ZoomLevel) * TileSource.TileSize / (MetersPerDegree * 360d); return mapTransform.RelativeScale(location) * Math.Pow(2d, ZoomLevel) * TileSource.TileSize / (TileSource.MetersPerDegree * 360d);
} }
/// <summary> /// <summary>
@ -446,7 +434,12 @@ namespace MapControl
break; break;
} }
UpdateTileLayer(); var firstTileLayer = TileLayers.FirstOrDefault();
if (TileLayer != firstTileLayer)
{
TileLayer = firstTileLayer;
}
} }
private void TileLayersPropertyChanged(TileLayerCollection oldTileLayers, TileLayerCollection newTileLayers) private void TileLayersPropertyChanged(TileLayerCollection oldTileLayers, TileLayerCollection newTileLayers)
@ -462,9 +455,18 @@ namespace MapControl
{ {
newTileLayers.CollectionChanged += TileLayerCollectionChanged; newTileLayers.CollectionChanged += TileLayerCollectionChanged;
tileContainer.AddTileLayers(0, newTileLayers); tileContainer.AddTileLayers(0, newTileLayers);
}
UpdateTileLayer(); var firstTileLayer = TileLayers.FirstOrDefault();
if (TileLayer != firstTileLayer)
{
TileLayer = firstTileLayer;
}
}
else
{
TileLayer = null;
}
} }
private void TileLayerPropertyChanged(TileLayer tileLayer) private void TileLayerPropertyChanged(TileLayer tileLayer)
@ -517,21 +519,6 @@ namespace MapControl
} }
} }
private void UpdateTileLayer()
{
TileLayer tileLayer = null;
if (TileLayers != null)
{
tileLayer = TileLayers.FirstOrDefault();
}
if (TileLayer != tileLayer)
{
TileLayer = tileLayer;
}
}
private void InternalSetValue(DependencyProperty property, object value) private void InternalSetValue(DependencyProperty property, object value)
{ {
internalPropertyChange = true; internalPropertyChange = true;
@ -828,7 +815,7 @@ namespace MapControl
} }
} }
CenterScale = ViewportScale * mapTransform.RelativeScale(center) / MetersPerDegree; // Pixels per meter at center latitude CenterScale = ViewportScale * mapTransform.RelativeScale(center) / TileSource.MetersPerDegree; // Pixels per meter at center latitude
SetTransformMatrixes(); SetTransformMatrixes();
OnViewportChanged(); OnViewportChanged();

View file

@ -31,7 +31,7 @@ namespace MapControl
public MapGraticule() public MapGraticule()
{ {
IsHitTestVisible = false; IsHitTestVisible = false;
Stroke = new SolidColorBrush(Color.FromArgb(127, 0, 0, 0)); StrokeThickness = 0.5;
path = new Path path = new Path
{ {

View file

@ -86,13 +86,12 @@ namespace MapControl
{ {
var p1 = ParentMap.MapTransform.Transform(new Location(south, west)); var p1 = ParentMap.MapTransform.Transform(new Location(south, west));
var p2 = ParentMap.MapTransform.Transform(new Location(north, east)); var p2 = ParentMap.MapTransform.Transform(new Location(north, east));
var arc = TileSource.EarthRadius * Math.PI / 180d;
uri = uri. uri = uri.
Replace("{W}", (arc * p1.X).ToString(CultureInfo.InvariantCulture)). Replace("{W}", (TileSource.MetersPerDegree * p1.X).ToString(CultureInfo.InvariantCulture)).
Replace("{S}", (arc * p1.Y).ToString(CultureInfo.InvariantCulture)). Replace("{S}", (TileSource.MetersPerDegree * p1.Y).ToString(CultureInfo.InvariantCulture)).
Replace("{E}", (arc * p2.X).ToString(CultureInfo.InvariantCulture)). Replace("{E}", (TileSource.MetersPerDegree * p2.X).ToString(CultureInfo.InvariantCulture)).
Replace("{N}", (arc * p2.Y).ToString(CultureInfo.InvariantCulture)); Replace("{N}", (TileSource.MetersPerDegree * p2.Y).ToString(CultureInfo.InvariantCulture));
} }
else else
{ {

View file

@ -65,48 +65,44 @@ namespace MapControl
private Binding foregroundBinding; private Binding foregroundBinding;
private Binding strokeBinding; private Binding strokeBinding;
public override MapBase ParentMap protected override void SetParentMapOverride(MapBase parentMap)
{ {
get { return base.ParentMap; } if (foregroundBinding != null)
set
{ {
if (foregroundBinding != null) foregroundBinding = null;
{ ClearValue(ForegroundProperty);
foregroundBinding = null;
ClearValue(ForegroundProperty);
}
if (strokeBinding != null)
{
strokeBinding = null;
ClearValue(StrokeProperty);
}
if (value != null)
{
if (Foreground == null)
{
foregroundBinding = new Binding
{
Source = value,
Path = new PropertyPath("Foreground")
};
SetBinding(ForegroundProperty, foregroundBinding);
}
if (Stroke == null)
{
strokeBinding = new Binding
{
Source = value,
Path = new PropertyPath("Foreground")
};
SetBinding(StrokeProperty, strokeBinding);
}
}
base.ParentMap = value;
} }
if (strokeBinding != null)
{
strokeBinding = null;
ClearValue(StrokeProperty);
}
if (parentMap != null)
{
if (Foreground == null)
{
foregroundBinding = new Binding
{
Source = parentMap,
Path = new PropertyPath("Foreground")
};
SetBinding(ForegroundProperty, foregroundBinding);
}
if (Stroke == null)
{
strokeBinding = new Binding
{
Source = parentMap,
Path = new PropertyPath("Foreground")
};
SetBinding(StrokeProperty, strokeBinding);
}
}
base.SetParentMapOverride(parentMap);
} }
} }
} }

View file

@ -44,23 +44,29 @@ namespace MapControl
private MapBase parentMap; private MapBase parentMap;
public virtual MapBase ParentMap public MapBase ParentMap
{ {
get { return parentMap; } get { return parentMap; }
set }
void IMapElement.SetParentMap(MapBase map)
{
SetParentMapOverride(map);
}
protected virtual void SetParentMapOverride(MapBase map)
{
if (parentMap != null && parentMap != this)
{ {
if (parentMap != null && parentMap != this) parentMap.ViewportChanged -= OnViewportChanged;
{ }
parentMap.ViewportChanged -= OnViewportChanged;
}
parentMap = value; parentMap = map;
if (parentMap != null && parentMap != this) if (parentMap != null && parentMap != this)
{ {
parentMap.ViewportChanged += OnViewportChanged; parentMap.ViewportChanged += OnViewportChanged;
OnViewportChanged(); OnViewportChanged();
}
} }
} }
@ -108,7 +114,7 @@ namespace MapControl
if (mapElement != null) if (mapElement != null)
{ {
mapElement.ParentMap = e.NewValue as MapBase; mapElement.SetParentMap(e.NewValue as MapBase);
} }
} }

View file

@ -20,11 +20,12 @@ namespace MapControl
public MapBase ParentMap public MapBase ParentMap
{ {
get { return parentMap; } get { return parentMap; }
set }
{
parentMap = value; void IMapElement.SetParentMap(MapBase map)
UpdateData(); {
} parentMap = map;
UpdateData();
} }
protected virtual void UpdateData() protected virtual void UpdateData()
@ -33,10 +34,10 @@ namespace MapControl
protected override Size MeasureOverride(Size constraint) protected override Size MeasureOverride(Size constraint)
{ {
// base.MeasureOverride in WPF and Windows Runtime sometimes return a Size with zero // base.MeasureOverride in WPF and Windows Runtime sometimes return a Size
// width or height, whereas base.MeasureOverride in Silverlight occasionally // with zero width or height, whereas in Silverlight it occasionally throws
// throws an ArgumentException, as it tries to create a Size from a negative // an ArgumentException, as it tries to create a Size from a negative width
// width or height, apparently resulting from a transformed Geometry. // or height, apparently resulting from a transformed Geometry.
// In either case it seems to be sufficient to simply return a non-zero size. // In either case it seems to be sufficient to simply return a non-zero size.
return new Size(1, 1); return new Size(1, 1);
} }

View file

@ -14,8 +14,8 @@ using System.Windows;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.1.0")] [assembly: AssemblyVersion("2.2.0")]
[assembly: AssemblyFileVersion("2.1.0")] [assembly: AssemblyFileVersion("2.2.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -19,14 +19,13 @@ namespace MapControl
public partial class TileSource public partial class TileSource
{ {
public const int TileSize = 256; public const int TileSize = 256;
public const double EarthRadius = 6378137d; // WGS 84 semi major axis public const double MetersPerDegree = 6378137d * Math.PI / 180d; // WGS 84 semi major axis
private Func<int, int, int, Uri> getUri; private Func<int, int, int, Uri> getUri;
private string uriFormat = string.Empty; private string uriFormat = string.Empty;
public TileSource() public TileSource()
{ {
MetersPerDegree = EarthRadius * Math.PI / 180d;
} }
public TileSource(string uriFormat) public TileSource(string uriFormat)
@ -35,8 +34,6 @@ namespace MapControl
UriFormat = uriFormat; UriFormat = uriFormat;
} }
public double MetersPerDegree { get; protected set; }
public string UriFormat public string UriFormat
{ {
get { return uriFormat; } get { return uriFormat; }
@ -44,7 +41,7 @@ namespace MapControl
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
throw new ArgumentException("The value of the UriFormat property must not be null or empty or white-space only."); throw new ArgumentException("The value of the UriFormat property must not be null or empty or white-space only.", "value");
} }
uriFormat = value; uriFormat = value;
@ -164,12 +161,11 @@ namespace MapControl
private Uri GetBoundingBoxUri(int x, int y, int zoomLevel) private Uri GetBoundingBoxUri(int x, int y, int zoomLevel)
{ {
var m = MetersPerDegree;
var n = (double)(1 << zoomLevel); var n = (double)(1 << zoomLevel);
var x1 = m * ((double)x * 360d / n - 180d); var x1 = MetersPerDegree * ((double)x * 360d / n - 180d);
var x2 = m * ((double)(x + 1) * 360d / n - 180d); var x2 = MetersPerDegree * ((double)(x + 1) * 360d / n - 180d);
var y1 = m * (180d - (double)(y + 1) * 360d / n); var y1 = MetersPerDegree * (180d - (double)(y + 1) * 360d / n);
var y2 = m * (180d - (double)y * 360d / n); var y2 = MetersPerDegree * (180d - (double)y * 360d / n);
return new Uri(uriFormat. return new Uri(uriFormat.
Replace("{W}", x1.ToString(CultureInfo.InvariantCulture)). Replace("{W}", x1.ToString(CultureInfo.InvariantCulture)).

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.1.0")] [assembly: AssemblyVersion("2.2.0")]
[assembly: AssemblyFileVersion("2.1.0")] [assembly: AssemblyFileVersion("2.2.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.1.0")] [assembly: AssemblyVersion("2.2.0")]
[assembly: AssemblyFileVersion("2.1.0")] [assembly: AssemblyFileVersion("2.2.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.1.0")] [assembly: AssemblyVersion("2.2.0")]
[assembly: AssemblyFileVersion("2.1.0")] [assembly: AssemblyFileVersion("2.2.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.1.0")] [assembly: AssemblyVersion("2.2.0")]
[assembly: AssemblyFileVersion("2.1.0")] [assembly: AssemblyFileVersion("2.2.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -22,12 +22,12 @@
<map:TileLayer SourceName="MapQuest OSM" Description="MapQuest OSM - © {y} MapQuest &amp; OpenStreetMap Contributors"> <map:TileLayer SourceName="MapQuest OSM" Description="MapQuest OSM - © {y} MapQuest &amp; OpenStreetMap Contributors">
<map:TileSource UriFormat="http://otile{n}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png"/> <map:TileSource UriFormat="http://otile{n}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png"/>
</map:TileLayer> </map:TileLayer>
<map:TileLayer SourceName="Bing Maps" Description="Bing Maps - © {y} Microsoft Corporation" MinZoomLevel="1" MaxZoomLevel="19"> <!--<map:TileLayer SourceName="Bing Maps" Description="Bing Maps - © {y} Microsoft Corporation" MinZoomLevel="1" MaxZoomLevel="19">
<map:TileSource UriFormat="http://ecn.t{i}.tiles.virtualearth.net/tiles/r{q}.png?g=0&amp;stl=h"/> <map:TileSource UriFormat="http://ecn.t{i}.tiles.virtualearth.net/tiles/r{q}.png?g=0&amp;stl=h"/>
</map:TileLayer> </map:TileLayer>
<map:TileLayer SourceName="Bing Images" Description="Bing Maps - © {y} Microsoft Corporation" MinZoomLevel="1" MaxZoomLevel="19" Background="#FF3F3F3F" Foreground="White"> <map:TileLayer SourceName="Bing Images" Description="Bing Maps - © {y} Microsoft Corporation" MinZoomLevel="1" MaxZoomLevel="19" Background="#FF3F3F3F" Foreground="White">
<map:TileSource UriFormat="http://ecn.t{i}.tiles.virtualearth.net/tiles/a{q}.jpeg?g=0"/> <map:TileSource UriFormat="http://ecn.t{i}.tiles.virtualearth.net/tiles/a{q}.jpeg?g=0"/>
</map:TileLayer> </map:TileLayer>-->
<map:TileLayer SourceName="Seamarks" Description="© {y} OpenSeaMap Contributors, CC-BY-SA" MinZoomLevel="10" MaxZoomLevel="18"> <map:TileLayer SourceName="Seamarks" Description="© {y} OpenSeaMap Contributors, CC-BY-SA" MinZoomLevel="10" MaxZoomLevel="18">
<map:TileSource UriFormat="http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png"/> <map:TileSource UriFormat="http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png"/>
</map:TileLayer> </map:TileLayer>
@ -196,8 +196,8 @@
<ComboBoxItem>OCM Transport</ComboBoxItem> <ComboBoxItem>OCM Transport</ComboBoxItem>
<ComboBoxItem>OCM Landscape</ComboBoxItem> <ComboBoxItem>OCM Landscape</ComboBoxItem>
<ComboBoxItem>MapQuest OSM</ComboBoxItem> <ComboBoxItem>MapQuest OSM</ComboBoxItem>
<ComboBoxItem>Bing Maps</ComboBoxItem> <!--<ComboBoxItem>Bing Maps</ComboBoxItem>
<ComboBoxItem>Bing Images</ComboBoxItem> <ComboBoxItem>Bing Images</ComboBoxItem>-->
</ComboBox> </ComboBox>
</StackPanel> </StackPanel>
</Grid> </Grid>

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.1.0")] [assembly: AssemblyVersion("2.2.0")]
[assembly: AssemblyFileVersion("2.1.0")] [assembly: AssemblyFileVersion("2.2.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.1.0")] [assembly: AssemblyVersion("2.2.0")]
[assembly: AssemblyFileVersion("2.1.0")] [assembly: AssemblyFileVersion("2.2.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]