// Copyright 2005, 2006 - Morten Nielsen (www.iter.dk) // // This file is part of SharpMap. // SharpMap is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // SharpMap is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // You should have received a copy of the GNU Lesser General Public License // along with SharpMap; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Imaging; using System.Linq; using DelftTools.Utils.Aop; using DelftTools.Utils.Aop.Markers; using DelftTools.Utils.Diagnostics; using GeoAPI.CoordinateSystems; using GeoAPI.CoordinateSystems.Transformations; using GeoAPI.Extensions.Feature; using GeoAPI.Geometries; using GisSharpBlog.NetTopologySuite.Geometries; using log4net; using NetTopologySuite.Extensions.Features; using SharpMap.Api; using SharpMap.Api.Editors; using SharpMap.Api.Layers; using SharpMap.CoordinateSystems.Transformations; using SharpMap.Data.Providers; using SharpMap.Data.Providers.EGIS.ShapeFileLib; using SharpMap.Rendering.Thematics; using SharpMap.Styles; namespace SharpMap.Layers { /// /// Abstract class for common layer properties /// Implement this class instead of the ILayer interface to save a lot of common code. /// [Entity(FireOnCollectionChange = false)] public abstract class Layer : ILayer { #region Delegates /// /// EventHandler for event fired when the layer has been rendered /// /// Layer rendered /// Reference to graphics object used for rendering public delegate void LayerRenderedEventHandler(Layer layer, Graphics g); #endregion /// /// Event fired when the layer has been rendered /// public virtual event LayerRenderedEventHandler LayerRendered; private static readonly ILog log = LogManager.GetLogger(typeof(Layer)); private static readonly IGeometryFactory geometryFactory = new GeometryFactory(); protected string name; private bool visible = true; private double maxVisible = double.MaxValue; private int srsWkt = -1; private IList customRenderers; private Image image; private double lastRenderDuration; [NoNotifyPropertyChange] private IMap map; private bool renderRequired; private bool showInLegend = true; private bool showInTreeView = true; private ILabelLayer labelLayer; //TEMP! private bool showAttributeTable = true; private IFeatureProvider dataSource; private QuadTree tree; private ICoordinateTransformation coordinateTransformation; protected Layer() { Opacity = 1.0f; Selectable = true; ((INotifyPropertyChanged) this).PropertyChanged += LayerPropertyChanged; customRenderers = new List(); renderRequired = true; themeIsDirty = true; UseQuadTree = false; } /// /// Gets or sets the applied /// to this vectorlayer prior to rendering /// public virtual ICoordinateTransformation CoordinateTransformation { get { return coordinateTransformation; } set { tree = null; // reset quad tree coordinateTransformation = value; } } public virtual string ThemeAttributeName { get { return theme != null ? theme.AttributeName : "Value"; } } [NoNotifyPropertyChange] public virtual bool ThemeIsDirty { get { return themeIsDirty; } set { themeIsDirty = value; } } public virtual ILabelLayer LabelLayer { get { // initialize if (labelLayer == null && !(this is LabelLayer)) { labelLayer = new LabelLayer { Visible = false, Parent = this, Map = map }; } return labelLayer; } set { labelLayer = value; if (labelLayer != null) { labelLayer.Parent = this; labelLayer.Map = map; } } } public virtual void OnRender(Graphics g, IMap map) {} /// /// Returns the name of the layer. /// /// public override string ToString() { return Name; } protected virtual void OnLayerPropertyChanged(object sender, PropertyChangedEventArgs e) { if (sender is ITheme || sender is IThemeItem) { return; // no infinite loops! } if (e.PropertyName == "Name") { return; } OnChanged(); } private void LayerPropertyChanged(object sender, PropertyChangedEventArgs e) { if (updatingTheme) { return; } //provide some excludes on which we dont have to render OnLayerPropertyChanged(sender, e); } private void OnChanged() { if (map != null && map.IsDisposing) { return; } if (AutoUpdateThemeOnDataSourceChanged) { themeIsDirty = true; } var attributeName = theme != null ? theme.AttributeName : "Value"; minMaxCache = new LayerAttribute(this, attributeName); if (!string.IsNullOrEmpty(ThemeGroup)) { if (Map != null) { ((Map) Map).OnThemeGroupDataChanged(ThemeGroup, attributeName); } } if (!RenderRequired) { RenderRequired = true; } } ~Layer() { ClearImage(); } #region ILayer Members /// /// Clones the layer. /// /// cloned object public virtual object Clone() { //make sure you have a parameterless constructor to be cloneable var clone = (Layer) Activator.CreateInstance(GetType(), true); clone.Name = Name; clone.DataSource = DataSource; clone.Theme = theme != null ? (ITheme) theme.Clone() : null; clone.ThemeGroup = ThemeGroup; clone.CustomRenderers = CustomRenderers; clone.CoordinateTransformation = CoordinateTransformation; clone.Visible = Visible; clone.RenderOrder = RenderOrder; clone.Selectable = Selectable; clone.ReadOnly = ReadOnly; clone.CanBeRemovedByUser = canBeRemovedByUser; clone.ShowAttributeTable = ShowAttributeTable; clone.ShowInLegend = ShowInLegend; clone.ShowInTreeView = ShowInTreeView; clone.MinVisible = MinVisible; clone.MaxVisible = MaxVisible; clone.NameIsReadOnly = NameIsReadOnly; clone.AutoUpdateThemeOnDataSourceChanged = AutoUpdateThemeOnDataSourceChanged; clone.ExcludeFromMapExtent = true; if (labelLayer != null) { clone.labelLayer = (LabelLayer) labelLayer.Clone(); clone.labelLayer.Parent = clone; clone.ShowLabels = ShowLabels; } return clone; } public virtual Image Image { get { return image; } } /// /// Gets or sets the name of the layer /// public virtual string Name { get { return name; } set { if (NameIsReadOnly) { throw new ReadOnlyException("Property Name of Layer is not editable because NameIsReadOnly is true."); } name = value; } } [Aggregation] public virtual IMap Map { get { return map; } set { map = value; // set coordinate transformation UpdateCoordinateTransformation(); if (labelLayer != null) { labelLayer.Map = map; } } } [NoNotifyPropertyChange] public virtual bool ShowLabels { get { return labelLayer != null && labelLayer.Visible; } set { // if setting to true, create a labellayer or just set // if setting to false, only set if the labelLayer already exists if (value || labelLayer != null) { LabelLayer.Visible = value; } } } public virtual bool ShowInLegend { get { return showInLegend; } set { showInLegend = value; } } public virtual bool ShowInTreeView { get { return showInTreeView; } set { showInTreeView = value; } } public virtual bool ShowAttributeTable { get { return showAttributeTable; } set { showAttributeTable = value; } } public virtual int RenderOrder { get; set; } public virtual bool CanBeRemovedByUser { get { return canBeRemovedByUser; } set { canBeRemovedByUser = value; } } /// /// Defines the layer opacity, expressed as a value between 0.0 and 1.0. A value of 0.0 indicates fully transparent. /// public virtual float Opacity { get; set; } public virtual bool ReadOnly { get; set; } /// /// Performance. When true - very small features are not rendered (if implemented by feature povider). /// public virtual bool SkipRenderingOfVerySmallFeatures { get; set; } public virtual ICoordinateSystem CoordinateSystem { get { return dataSource != null ? dataSource.CoordinateSystem : null; } set { if (dataSource != null) { dataSource.CoordinateSystem = value; } } } public virtual bool UseQuadTree { get; set; } public virtual bool UseSimpleGeometryForQuadTree { get; set; } public virtual QuadTree QuadTree { get { return tree; } } public virtual IEnumerable GetFeatures(IGeometry geometry, bool useCustomRenderers = true) { if (useCustomRenderers) { var customRenderer = CustomRenderers.FirstOrDefault(); if (customRenderer != null) { foreach (var feature in customRenderer.GetFeatures(geometry, this)) { yield return feature; } yield break; } } var getAllMapFeatures = map == null || map.Envelope.Equals(geometry.EnvelopeInternal); // when all map features are asked - avoid time consuming geometry x geometry checks if (UseQuadTree) { var features = GetFeaturesUsingQuadTree(geometry.EnvelopeInternal); foreach (var feature in features) { // check if we're selecting subset of features, if so - use more robust intersection check (non-evelope based) if (!getAllMapFeatures) { // log.DebugFormat("Checking geometry intersection for feature: " + feature.Attributes["NAME"]); var g = feature.Geometry; if (CoordinateTransformation != null) { g = GeometryTransform.TransformGeometry(g, CoordinateTransformation.MathTransform); } if (g.Intersects(geometry)) { // log.DebugFormat("Intersected geometry found for feature: " + feature.Attributes["NAME"]); yield return feature; } } else { yield return feature; } } } else { var features = Enumerable.Empty(); var e = geometry.EnvelopeInternal; if (CoordinateTransformation != null) { // BUG: this will not work, migrate it features = DataSource.Features.Cast(); foreach (var feature in features) { var g = GeometryTransform.TransformGeometry(feature.Geometry, CoordinateTransformation.MathTransform); if (g == null || !g.EnvelopeInternal.Intersects(geometry.EnvelopeInternal)) { continue; } if (!getAllMapFeatures) { if (g.Intersects(geometry)) { yield return feature; } } else { yield return feature; } } } else { if (DataSource == null || DataSource.Features == null) { yield break; } features = DataSource.Features.OfType().Where(feature => feature.Geometry != null && feature.Geometry.EnvelopeInternal.Intersects(e)); foreach (var feature in features) { if (!getAllMapFeatures) { if (feature.Geometry.Intersects(geometry)) { yield return feature; } } else { yield return feature; } } } } } public virtual IEnumerable GetFeatures(IEnvelope envelope) { return GetFeatures(geometryFactory.ToGeometry(envelope)); // TODO: ... this can run even faster, otherwise remove this method ... } private IEnumerable GetFeaturesUsingQuadTree(IEnvelope envelope) { if (DataSource.GetFeatureCount() == 0) { return Enumerable.Empty(); } var maximumFeatureSize = SkipRenderingOfVerySmallFeatures ? map.PixelSize*0.9 : 0; // use quad tree to query features. if (tree == null) { BuildQuadTree(); } var rect = new RectangleF((float) envelope.MinX, (float) envelope.MinY, (float) envelope.Width, (float) envelope.Height); var indices = tree.GetIndices(ref rect, (float) maximumFeatureSize); #region debugging bool wholeMap = envelope.Equals(map.Envelope); if (RenderQuadTreeEnvelopes && !wholeMap) { foreach (IFeature feature in quadTreeEnvelopesLayer.DataSource.Features) { feature.Attributes["IsSelected"] = indices.Contains((int) feature.Attributes["ID"]) ? "true" : "false"; } quadTreeEnvelopesLayer.RenderRequired = true; } #endregion return indices.Select(i => DataSource.GetFeature(i)); } private void BuildQuadTree() { #region debugging RemoveQuadTreeLayers(); RemoveQuadTreeEnvelopesLayer(); #endregion var featureCount = DataSource.GetFeatureCount(); var maxLevels = (int) Math.Ceiling(0.4*Math.Log(featureCount, 2)); var isPoint = featureCount != 0 && DataSource.GetFeature(0).Geometry is IPoint; if (CoordinateTransformation == null) { var envelope = DataSource.GetExtents(); var rectangleF = new RectangleF((float) envelope.MinX, (float) envelope.MinY, (float) envelope.Width, (float) envelope.Height); tree = new QuadTree(rectangleF, maxLevels, isPoint); for (var i = 0; i < featureCount; i++) { if (!UseSimpleGeometryForQuadTree) { tree.Insert(i, ToRectangleF(DataSource.GetBounds(i))); // use envelope } else { var g = DataSource.GetGeometryByID(i); var gc = g as IGeometryCollection; if (gc != null) { foreach (IGeometry sg in gc.Geometries) { tree.Insert(i, ToRectangleF(sg.EnvelopeInternal)); // use simple geometry envelope } } else { tree.Insert(i, ToRectangleF(DataSource.GetBounds(i))); // use envelope } } } } else { IEnvelope envelope = new Envelope(); var count = DataSource.GetFeatureCount(); var geometrys = new IGeometry[count]; for (int i = 0; i < count; i++) { var g = DataSource.GetGeometryByID(i); if (g == null) { continue; } g = GeometryTransform.TransformGeometry(g, CoordinateTransformation.MathTransform); envelope.ExpandToInclude(g.EnvelopeInternal); geometrys[i] = g; } var rectangleF = new RectangleF((float) envelope.MinX, (float) envelope.MinY, (float) envelope.Width, (float) envelope.Height); tree = new QuadTree(rectangleF, maxLevels, isPoint); for (int i = 0; i < featureCount; i++) { var g = geometrys[i]; if (g == null) { continue; } if (!UseSimpleGeometryForQuadTree) { tree.Insert(i, ToRectangleF(g.EnvelopeInternal)); } else { var gc = g as IGeometryCollection; if (gc != null) { foreach (IGeometry sg in gc.Geometries) { tree.Insert(i, ToRectangleF(sg.EnvelopeInternal)); // use simple geometry envelope } } else { tree.Insert(i, ToRectangleF(g.EnvelopeInternal)); // use envelope } } } } if (RenderQuadTree) { AddQuadTreeQuads(tree.RootNode, map); } if (RenderQuadTreeEnvelopes) { AddQuadTreeEnvelopes(tree.RootNode, map); log.DebugFormat("Added {0} quad tree envelope features", quadTreeEnvelopesLayer.DataSource.Features.Count); } } private RectangleF ToRectangleF(IEnvelope envelope) { return new RectangleF((float) envelope.MinX, (float) envelope.MinY, (float) envelope.Width, (float) envelope.Height); } #region debugging private void RemoveQuadTreeLayers() { if (map == null) { return; } foreach (var kv in quadTreeQuadLayers) { map.Layers.Remove(kv.Value); } quadTreeQuadLayers.Clear(); } private void RemoveQuadTreeEnvelopesLayer() { if (map == null || quadTreeEnvelopesLayer == null) { return; } map.Layers.Remove(quadTreeEnvelopesLayer); quadTreeEnvelopesLayer = null; } // quad tree layers are used for debugging purposes private VectorLayer quadTreeEnvelopesLayer; private readonly IDictionary quadTreeQuadLayers = new Dictionary(); private void AddQuadTreeQuads(QTNode node, IMap map) { var r = node.Bounds; var feature = new Feature { Geometry = geometryFactory.ToGeometry(new Envelope(r.X, r.X + r.Width, r.Y, r.Y + r.Height)) }; VectorLayer vectorLayer = null; if (!quadTreeQuadLayers.TryGetValue((int) node.Level, out vectorLayer)) { var featureCollection = new FeatureCollection { Features = new List { feature } }; vectorLayer = new VectorLayer { DataSource = featureCollection, Style = { Fill = Brushes.Transparent, Outline = { Width = 2, Color = Color.FromArgb(100, 100, 100, 200) } }, Name = Name + " quad tree, level: " + node.Level, Selectable = false }; map.Layers.Insert(0, vectorLayer); map.BringToFront(vectorLayer); quadTreeQuadLayers[node.Level] = vectorLayer; } else { vectorLayer.DataSource.Features.Add(feature); } if (node.Children != null) { AddQuadTreeQuads(node.Children[0], map); AddQuadTreeQuads(node.Children[1], map); AddQuadTreeQuads(node.Children[2], map); AddQuadTreeQuads(node.Children[3], map); } } private void AddQuadTreeEnvelopes(QTNode node, IMap map) { if (node.indexList != null) { //log.Debug(node.Bounds + ": " + string.Join(", ", node.indexList)); var i = 0; foreach (var bound in node.boundsList) { var id = node.indexList[i]; if (quadTreeEnvelopesLayer == null) { var featureCollection = new FeatureCollection { Features = new List {} }; quadTreeEnvelopesLayer = new VectorLayer { DataSource = featureCollection, Style = { Fill = Brushes.Transparent, Outline = { Width = 1, Color = Color.DarkGreen } }, Name = Name + " quad tree, level: " + node.Level, Selectable = false, //UseQuadTree = true }; var theme = new CategorialTheme("IsSelected", null); theme.AddThemeItem(new CategorialThemeItem("true", new VectorStyle { Fill = Brushes.Transparent, Outline = { Width = 3, Color = Color.FromArgb(200, 200, 100, 100) } }, null)); theme.AddThemeItem(new CategorialThemeItem("false", new VectorStyle { Fill = Brushes.Transparent, Outline = { Width = 3, Color = Color.FromArgb(50, 100, 200, 100) } }, null)); quadTreeEnvelopesLayer.Theme = theme; map.Layers.Insert(0, quadTreeEnvelopesLayer); map.BringToFront(quadTreeEnvelopesLayer); } //if (!quadTreeEnvelopesLayer.DataSource.Features.Cast().Any(f => f.Attributes["ID"].Equals(id))) { var feature = new Feature { Geometry = geometryFactory.ToGeometry(new Envelope(bound.X, bound.X + bound.Width, bound.Y, bound.Y + bound.Height)) }; feature.Attributes = new DictionaryFeatureAttributeCollection(); feature.Attributes["IsSelected"] = "false"; feature.Attributes["ID"] = id; quadTreeEnvelopesLayer.DataSource.Features.Add(feature); } i++; } } if (node.Children != null) { AddQuadTreeEnvelopes(node.Children[0], map); AddQuadTreeEnvelopes(node.Children[1], map); AddQuadTreeEnvelopes(node.Children[2], map); AddQuadTreeEnvelopes(node.Children[3], map); } } private bool renderQuadTree; public virtual bool RenderQuadTree { get { return renderQuadTree; } set { renderQuadTree = value; if (value && quadTreeQuadLayers.Count == 0 && tree != null) { AddQuadTreeQuads(tree.RootNode, map); } if (!value) { RemoveQuadTreeLayers(); } } } private bool renderQuadTreeEnvelopes; public virtual bool RenderQuadTreeEnvelopes { get { return renderQuadTreeEnvelopes; } set { renderQuadTreeEnvelopes = value; if (value && quadTreeEnvelopesLayer == null && tree != null) { AddQuadTreeEnvelopes(tree.RootNode, map); } if (!value) { RemoveQuadTreeEnvelopesLayer(); } } } #endregion public virtual IFeatureProvider DataSource { get { return dataSource; } set { if (dataSource != null) { dataSource.FeaturesChanged -= DataSourceFeaturesChanged; dataSource.CoordinateSystemChanged -= DataSourceCoordinateSystemChanged; } dataSource = value; if (dataSource != null) { dataSource.FeaturesChanged += DataSourceFeaturesChanged; dataSource.CoordinateSystemChanged += DataSourceCoordinateSystemChanged; if (dataSource.AddNewFeatureFromGeometryDelegate == null) { dataSource.AddNewFeatureFromGeometryDelegate = AddNewFeatureFromGeometryDelegate; } UpdateCoordinateTransformation(); if (dataSource is ShapeFile) // HACK: temporary enable QuadTree only for shapefiles { UseQuadTree = true; UseSimpleGeometryForQuadTree = true; SkipRenderingOfVerySmallFeatures = true; } } } } private void DataSourceCoordinateSystemChanged(object sender, EventArgs eventArgs) { UpdateCoordinateTransformation(); } protected void UpdateCoordinateTransformation() { if (map == null) { return; } var hasCoordinateSystem = SharpMap.Map.CoordinateSystemFactory != null && CoordinateSystem != null; CoordinateTransformation = map.CoordinateSystem != null && hasCoordinateSystem ? SharpMap.Map.CoordinateSystemFactory.CreateTransformation(CoordinateSystem, map.CoordinateSystem) : null; } private IFeature AddNewFeatureFromGeometryDelegate(IFeatureProvider featureProvider, IGeometry geometry) { if (FeatureEditor != null) { return FeatureEditor.AddNewFeatureByGeometry(this, geometry); } throw new NotImplementedException(); } public virtual IFeatureEditor FeatureEditor { get; set; } protected virtual void DataSourceFeaturesChanged(object sender, EventArgs e) { // TODO: do we still need Update CS here?? if ((CoordinateTransformation == null && (CoordinateSystem != null && Map != null && Map.CoordinateSystem != null)) || (CoordinateTransformation != null && CoordinateTransformation.SourceCS != CoordinateSystem)) { UpdateCoordinateTransformation(); } OnChanged(); } private LayerAttribute minMaxCache; public virtual string ThemeGroup { get; set; } public virtual double MinDataValue { get { return minMaxCache == null || double.IsInfinity(minMaxCache.MinNumValue) || double.IsNaN(minMaxCache.MinNumValue) ? 0.0 : minMaxCache.MinNumValue; } } public virtual double MaxDataValue { get { return minMaxCache == null || double.IsInfinity(minMaxCache.MaxNumValue) || double.IsNaN(minMaxCache.MaxNumValue) ? 0.0 : minMaxCache.MaxNumValue; } } private bool updatingTheme; protected ITheme theme; public virtual ITheme Theme { get { if (themeIsDirty) { updatingTheme = true; UpdateCurrentTheme(); updatingTheme = false; themeIsDirty = false; } return theme; } set { theme = value; } } /// /// Updates the current theme for min and max /// /// protected virtual void UpdateCurrentTheme() {} //public abstract SharpMap.CoordinateSystems.CoordinateSystem CoordinateSystem { get; set; } public virtual bool NameIsReadOnly { get; set; } public virtual void ClearImage() { if (image != null) { image.Dispose(); ResourceMonitor.OnResourceDeallocated(this, image); image = null; } } public virtual bool ExcludeFromMapExtent { get; set; } public virtual void Render() { var t = DateTime.Now; if (image != null && Map != null && (Map.Size.Width != image.Width || Map.Size.Height != image.Height)) // check if we need to re-create bitmap { ClearImage(); } if (Map == null) { if (image != null) { ClearImage(); } return; } if (image == null) { image = new Bitmap(Map.Size.Width, Map.Size.Height, PixelFormat.Format32bppPArgb); ResourceMonitor.OnResourceAllocated(this, image); } if (!Visible || MaxVisible < Map.Zoom || MinVisible > Map.Zoom) { return; } Graphics graphics = Graphics.FromImage(image); graphics.Transform = Map.MapTransform.Clone(); graphics.Clear(Color.Transparent); graphics.PageUnit = GraphicsUnit.Pixel; // call virtual implementation which renders layer OnRender(graphics, Map); if (LabelLayer != null && LabelLayer.Visible) { ((LabelLayer) LabelLayer).OnRender(graphics, map); } // fire event if (LayerRendered != null) { LayerRendered(this, graphics); } graphics.Dispose(); lastRenderDuration = (DateTime.Now - t).Milliseconds; RenderRequired = false; } /// /// Custom renderers which can be added to the layer and used to render something in addition to / instead of default rendering. /// public virtual IList CustomRenderers { get { return customRenderers; } set { customRenderers = value; } } [NoNotifyPropertyChange] public virtual bool RenderRequired { get { return renderRequired; } set { renderRequired = value; } } [NoNotifyPropertyChange] public virtual double LastRenderDuration { get { return lastRenderDuration; } } /// /// Returns the extent of the layer /// /// Bounding box corresponding to the extent of the features in the layer /// /// Returns the extent of the layer /// /// Bounding box corresponding to the extent of the features in the layer public virtual IEnvelope Envelope { get { if (DataSource == null) { return null; } if (CoordinateTransformation == null) { return DataSource.GetExtents(); } if (UseQuadTree) { if (tree == null) { BuildQuadTree(); } var rectangleF = tree.RootNode.Bounds; return new Envelope(rectangleF.Left, rectangleF.Right, rectangleF.Top, rectangleF.Bottom); } var envelope = new Envelope(); var count = DataSource.GetFeatureCount(); for (int i = 0; i < count; i++) { var g = DataSource.GetGeometryByID(i); g = GeometryTransform.TransformGeometry(g, CoordinateTransformation.MathTransform); if (g == null) { continue; } envelope.ExpandToInclude(g.EnvelopeInternal); } return envelope; } } /// /// Minimum visibility zoom, including this value /// public virtual double MinVisible { get; set; } /// /// Maximum visibility zoom, excluding this value /// public virtual double MaxVisible { get { return maxVisible; } set { maxVisible = value; } } /// /// Specified whether the layer is rendered or not /// public virtual bool Visible { get { return visible; } set { visible = value; } } public virtual bool IsSelectable { get { if (!Selectable) { return false; } return Visible; } } protected bool themeIsDirty; private bool canBeRemovedByUser = true; public virtual bool Selectable { get; set; } /// /// Determines whether the current theme should be updated when the datasouce changes /// [NoNotifyPropertyChange] //don't notify..messes up serialization public virtual bool AutoUpdateThemeOnDataSourceChanged { get; set; } #endregion #region IDisposable Members /// /// Disposes the object (when overriding override ) /// public virtual void Dispose() { Dispose(true); } public virtual void Dispose(bool disposeDataSource) { if (dataSource != null) { dataSource.FeaturesChanged -= DataSourceFeaturesChanged; dataSource.CoordinateSystemChanged -= DataSourceCoordinateSystemChanged; } if (DataSource != null) { if (disposeDataSource) { DataSource.Dispose(); } DataSource = null; } } #endregion } }