Index: Core/Components/src/Core.Components.DotSpatial.Forms/Core.Components.DotSpatial.Forms.csproj
===================================================================
diff -u -r1795be9025cb0e054dca86a5275203966c0a9b2a -r675771641656abfb56ef89be86bad7bd21684016
--- Core/Components/src/Core.Components.DotSpatial.Forms/Core.Components.DotSpatial.Forms.csproj (.../Core.Components.DotSpatial.Forms.csproj) (revision 1795be9025cb0e054dca86a5275203966c0a9b2a)
+++ Core/Components/src/Core.Components.DotSpatial.Forms/Core.Components.DotSpatial.Forms.csproj (.../Core.Components.DotSpatial.Forms.csproj) (revision 675771641656abfb56ef89be86bad7bd21684016)
@@ -56,8 +56,10 @@
+
+
Index: Core/Components/src/Core.Components.DotSpatial.Forms/MapControl.cs
===================================================================
diff -u -rd710790572caf63c49afdfb5dd71398966a5cd72 -r675771641656abfb56ef89be86bad7bd21684016
--- Core/Components/src/Core.Components.DotSpatial.Forms/MapControl.cs (.../MapControl.cs) (revision d710790572caf63c49afdfb5dd71398966a5cd72)
+++ Core/Components/src/Core.Components.DotSpatial.Forms/MapControl.cs (.../MapControl.cs) (revision 675771641656abfb56ef89be86bad7bd21684016)
@@ -20,15 +20,18 @@
// All rights reserved.
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using Core.Common.Base;
using Core.Components.DotSpatial.Converter;
using Core.Components.DotSpatial.MapFunctions;
using Core.Components.Gis.Data;
+using Core.Components.Gis.Features;
using Core.Components.Gis.Forms;
using DotSpatial.Controls;
using DotSpatial.Data;
+using DotSpatial.Symbology;
using DotSpatial.Topology;
namespace Core.Components.DotSpatial.Forms
@@ -39,14 +42,15 @@
public sealed class MapControl : Control, IMapControl
{
private readonly Cursor defaultCursor = Cursors.Default;
+ private readonly RecursiveObserver mapDataCollectionObserver;
+ private readonly IList drawnMapDataList = new List();
private Map map;
private MapFunctionPan mapFunctionPan;
private MapFunctionSelectionZoom mapFunctionSelectionZoom;
private MouseCoordinatesMapExtension mouseCoordinatesMapExtension;
+ private MapDataCollection data;
- private readonly RecursiveObserver mapDataObserver;
-
///
/// Creates a new instance of .
///
@@ -55,14 +59,7 @@
InitializeMapView();
TogglePanning();
- Data = new MapDataCollection("Root");
-
- mapDataObserver = new RecursiveObserver(DrawFeatureSets, mdc => mdc.Collection)
- {
- Observable = Data
- };
-
- DrawFeatureSets();
+ mapDataCollectionObserver = new RecursiveObserver(HandleMapDataCollectionChange, mdc => mdc.Collection);
}
public bool IsPanningEnabled { get; private set; }
@@ -71,8 +68,30 @@
public bool IsMouseCoordinatesVisible { get; private set; }
- public MapDataCollection Data { get; private set; }
+ public MapDataCollection Data
+ {
+ get
+ {
+ return data;
+ }
+ set
+ {
+ if (data != null)
+ {
+ ClearMapData();
+ }
+ data = value;
+
+ mapDataCollectionObserver.Observable = data;
+
+ if (data != null)
+ {
+ DrawInitialMapData();
+ }
+ }
+ }
+
public void ZoomToAllVisibleLayers()
{
IEnvelope envelope = CreateEnvelopeForAllVisibleLayers();
@@ -116,16 +135,11 @@
}
}
- public void ResetMapData()
- {
- Data = null;
- }
-
protected override void Dispose(bool disposing)
{
map.Dispose();
mouseCoordinatesMapExtension.Dispose();
- mapDataObserver.Dispose();
+ mapDataCollectionObserver.Dispose();
base.Dispose(disposing);
}
@@ -157,18 +171,6 @@
map.FunctionMode = FunctionMode.None;
}
- private void DrawFeatureSets()
- {
- map.ClearLayers();
- if (Data != null)
- {
- foreach (IMapFeatureLayer mapLayer in MapFeatureLayerFactory.Create(Data))
- {
- map.Layers.Add(mapLayer);
- }
- }
- }
-
private void InitializeMapView()
{
map = new DotSpatialMap
@@ -197,6 +199,136 @@
Controls.Add(map);
}
+ private void DrawInitialMapData()
+ {
+ foreach (var featureBasedMapData in GetFeatureBasedMapDataRecursively(Data))
+ {
+ DrawMapData(featureBasedMapData);
+ }
+ }
+
+ private void ClearMapData()
+ {
+ foreach (DrawnMapData drawnMapData in drawnMapDataList)
+ {
+ drawnMapData.Observer.Dispose();
+ }
+
+ drawnMapDataList.Clear();
+
+ map.ClearLayers();
+ }
+
+ private void HandleMapDataCollectionChange()
+ {
+ var mapDataThatShouldBeDrawn = GetFeatureBasedMapDataRecursively(Data).ToList();
+ var drawnMapDataLookup = drawnMapDataList.ToDictionary(dmd => dmd.FeatureBasedMapData, dmd => dmd);
+
+ DrawMissingMapDataOnCollectionChange(mapDataThatShouldBeDrawn, drawnMapDataLookup);
+ RemoveRedundantMapDataOnCollectionChange(drawnMapDataLookup, mapDataThatShouldBeDrawn);
+
+ drawnMapDataLookup = drawnMapDataList.ToDictionary(le => le.FeatureBasedMapData, le => le);
+
+ MoveMapDataOnCollectionChange(mapDataThatShouldBeDrawn, drawnMapDataLookup);
+ }
+
+ private void DrawMissingMapDataOnCollectionChange(List mapDataThatShouldBeDrawn, Dictionary drawnMapDataLookup)
+ {
+ foreach (var mapDataToDraw in mapDataThatShouldBeDrawn)
+ {
+ if (!drawnMapDataLookup.ContainsKey(mapDataToDraw))
+ {
+ DrawMapData(mapDataToDraw);
+ }
+ }
+ }
+
+ private void RemoveRedundantMapDataOnCollectionChange(Dictionary drawnMapDataLookup, List mapDataThatShouldBeDrawn)
+ {
+ foreach (var featureBasedMapData in drawnMapDataLookup.Keys.Except(mapDataThatShouldBeDrawn))
+ {
+ RemoveMapData(drawnMapDataLookup[featureBasedMapData]);
+ }
+ }
+
+ private void MoveMapDataOnCollectionChange(List mapDataThatShouldBeDrawn, Dictionary drawnMapDataLookup)
+ {
+ for (var i = 0; i < mapDataThatShouldBeDrawn.Count; i++)
+ {
+ map.Layers.Move(drawnMapDataLookup[mapDataThatShouldBeDrawn[i]].MapFeatureLayer, i);
+ }
+ }
+
+ private void DrawMapData(FeatureBasedMapData featureBasedMapData)
+ {
+ var mapFeatureLayer = MapFeatureLayerFactory.Create(featureBasedMapData);
+
+ var drawnMapData = new DrawnMapData
+ {
+ FeatureBasedMapData = featureBasedMapData,
+ Features = featureBasedMapData.Features,
+ MapFeatureLayer = mapFeatureLayer
+ };
+
+ drawnMapData.Observer = new Observer(() =>
+ {
+ var newMapFeatureLayer = MapFeatureLayerFactory.Create(drawnMapData.FeatureBasedMapData);
+
+ if (ReferenceEquals(drawnMapData.FeatureBasedMapData.Features, drawnMapData.Features))
+ {
+ // Only update some properties when the array of drawn features is still the same
+ drawnMapData.MapFeatureLayer.IsVisible = newMapFeatureLayer.IsVisible;
+ ((FeatureLayer) drawnMapData.MapFeatureLayer).Name = ((FeatureLayer) drawnMapData.MapFeatureLayer).Name;
+ drawnMapData.MapFeatureLayer.ShowLabels = newMapFeatureLayer.ShowLabels;
+ drawnMapData.MapFeatureLayer.LabelLayer = newMapFeatureLayer.LabelLayer;
+ drawnMapData.MapFeatureLayer.Symbolizer = newMapFeatureLayer.Symbolizer;
+ }
+ else
+ {
+ // Otherwise redraw the feature layer and update the drawn map data lookup
+ var replaceIndex = map.Layers.IndexOf(drawnMapData.MapFeatureLayer);
+ map.Layers.RemoveAt(replaceIndex);
+ map.Layers.Insert(replaceIndex, newMapFeatureLayer);
+ drawnMapData.Features = drawnMapData.FeatureBasedMapData.Features;
+ drawnMapData.MapFeatureLayer = newMapFeatureLayer;
+ }
+ })
+ {
+ Observable = featureBasedMapData
+ };
+
+ drawnMapDataList.Add(drawnMapData);
+
+ map.Layers.Add(mapFeatureLayer);
+ }
+
+ private void RemoveMapData(DrawnMapData drawnMapDataToRemove)
+ {
+ drawnMapDataToRemove.Observer.Dispose();
+ drawnMapDataList.Remove(drawnMapDataToRemove);
+
+ map.Layers.Remove(drawnMapDataToRemove.MapFeatureLayer);
+ }
+
+ private static IEnumerable GetFeatureBasedMapDataRecursively(MapDataCollection mapDataCollection)
+ {
+ var featureBaseMapDataList = new List();
+
+ foreach (MapData mapData in mapDataCollection.Collection)
+ {
+ var nestedMapDataCollection = mapData as MapDataCollection;
+ if (nestedMapDataCollection != null)
+ {
+ featureBaseMapDataList.AddRange(GetFeatureBasedMapDataRecursively(nestedMapDataCollection));
+ continue;
+ }
+
+ featureBaseMapDataList.Add((FeatureBasedMapData) mapData);
+ }
+
+ return featureBaseMapDataList;
+ }
+
private void MapFunctionActivateFunction(object sender, EventArgs e)
{
map.Cursor = defaultCursor;
@@ -226,5 +358,16 @@
break;
}
}
+
+ private class DrawnMapData
+ {
+ public FeatureBasedMapData FeatureBasedMapData { get; set; }
+
+ public MapFeature[] Features { get; set; }
+
+ public IMapFeatureLayer MapFeatureLayer { get; set; }
+
+ public Observer Observer { get; set; }
+ }
}
}
\ No newline at end of file
Index: Core/Components/src/Core.Components.DotSpatial/Converter/IMapDataConverter.cs
===================================================================
diff -u -red96d38a758365e4c0f117def83a2926c589c18a -r675771641656abfb56ef89be86bad7bd21684016
--- Core/Components/src/Core.Components.DotSpatial/Converter/IMapDataConverter.cs (.../IMapDataConverter.cs) (revision ed96d38a758365e4c0f117def83a2926c589c18a)
+++ Core/Components/src/Core.Components.DotSpatial/Converter/IMapDataConverter.cs (.../IMapDataConverter.cs) (revision 675771641656abfb56ef89be86bad7bd21684016)
@@ -20,10 +20,8 @@
// All rights reserved.
using System;
-using System.Collections.Generic;
using Core.Components.Gis.Data;
using DotSpatial.Controls;
-using DotSpatial.Data;
namespace Core.Components.DotSpatial.Converter
{
@@ -41,12 +39,12 @@
bool CanConvertMapData(MapData data);
///
- /// Creates a one or more based on the that was given.
+ /// Creates a based on the that was given.
///
- /// The data to transform into one or more .
- /// A new of .
+ /// The data to transform into a .
+ /// A new .
/// Thrown when returns false.
/// Thrown when is null.
- IList Convert(MapData data);
+ IMapFeatureLayer Convert(MapData data);
}
}
\ No newline at end of file
Fisheye: Tag 675771641656abfb56ef89be86bad7bd21684016 refers to a dead (removed) revision in file `Core/Components/src/Core.Components.DotSpatial/Converter/MapDataCollectionConverter.cs'.
Fisheye: No comparison available. Pass `N' to diff?
Index: Core/Components/src/Core.Components.DotSpatial/Converter/MapDataConverter.cs
===================================================================
diff -u -rd710790572caf63c49afdfb5dd71398966a5cd72 -r675771641656abfb56ef89be86bad7bd21684016
--- Core/Components/src/Core.Components.DotSpatial/Converter/MapDataConverter.cs (.../MapDataConverter.cs) (revision d710790572caf63c49afdfb5dd71398966a5cd72)
+++ Core/Components/src/Core.Components.DotSpatial/Converter/MapDataConverter.cs (.../MapDataConverter.cs) (revision 675771641656abfb56ef89be86bad7bd21684016)
@@ -34,12 +34,12 @@
namespace Core.Components.DotSpatial.Converter
{
///
- /// The abstract base class for transforming in specific instances.
+ /// Abstract base class for transforming into .
///
public abstract class MapDataConverter : IMapDataConverter where T : MapData
{
// Needed because DotSpatial can't handle special characters.
- // Therefor we create an id as column name for the data table in the featureSet.
+ // Therefore we create an id as column name for the data table in the featureSet.
// We need this lookup to match the selected attribute from the MapData with the created id.
private readonly Dictionary columnLookup;
@@ -56,27 +56,28 @@
return data is T;
}
- public IList Convert(MapData data)
+ public IMapFeatureLayer Convert(MapData data)
{
if (data == null)
{
- throw new ArgumentNullException("data", @"Null data cannot be converted into feature sets.");
+ throw new ArgumentNullException("data", @"Null data cannot be converted into a feature layer.");
}
if (!CanConvertMapData(data))
{
var message = string.Format("The data of type {0} cannot be converted by this converter.", data.GetType());
throw new ArgumentException(message);
}
+
return Convert((T) data);
}
///
- /// Creates one or more based on the that was given.
+ /// Creates a based on the that was given.
///
- /// The data to transform into one or more .
- /// A new of .
- protected abstract IList Convert(T data);
+ /// The data to transform into a .
+ /// A new .
+ protected abstract IMapFeatureLayer Convert(T data);
protected static IEnumerable ConvertPoint2DElementsToCoordinates(IEnumerable points)
{
Index: Core/Components/src/Core.Components.DotSpatial/Converter/MapFeatureLayerFactory.cs
===================================================================
diff -u -rd710790572caf63c49afdfb5dd71398966a5cd72 -r675771641656abfb56ef89be86bad7bd21684016
--- Core/Components/src/Core.Components.DotSpatial/Converter/MapFeatureLayerFactory.cs (.../MapFeatureLayerFactory.cs) (revision d710790572caf63c49afdfb5dd71398966a5cd72)
+++ Core/Components/src/Core.Components.DotSpatial/Converter/MapFeatureLayerFactory.cs (.../MapFeatureLayerFactory.cs) (revision 675771641656abfb56ef89be86bad7bd21684016)
@@ -20,7 +20,6 @@
// All rights reserved.
using System;
-using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Core.Components.Gis.Data;
@@ -34,22 +33,21 @@
public static class MapFeatureLayerFactory
{
///
- /// Creates one or more new from the given .
+ /// Creates a from the given .
///
/// The to base the creation of upon.
- /// A new of .
+ /// A new .
/// Thrown when the given type is not supported.
- public static IList Create(MapData data)
+ public static IMapFeatureLayer Create(MapData data)
{
var converters = new Collection
{
- new MapDataCollectionConverter(),
new MapPointDataConverter(),
new MapLineDataConverter(),
new MapPolygonDataConverter()
};
- foreach (var converter in converters.Where(c => c.CanConvertMapData(data)))
+ foreach (var converter in converters.Where(c => c.CanConvertMapData(data)))
{
return converter.Convert(data);
}
Index: Core/Components/src/Core.Components.DotSpatial/Converter/MapLineDataConverter.cs
===================================================================
diff -u -r1520a1345ab3cbd5214edf0919495563d98b0e53 -r675771641656abfb56ef89be86bad7bd21684016
--- Core/Components/src/Core.Components.DotSpatial/Converter/MapLineDataConverter.cs (.../MapLineDataConverter.cs) (revision 1520a1345ab3cbd5214edf0919495563d98b0e53)
+++ Core/Components/src/Core.Components.DotSpatial/Converter/MapLineDataConverter.cs (.../MapLineDataConverter.cs) (revision 675771641656abfb56ef89be86bad7bd21684016)
@@ -19,7 +19,6 @@
// Stichting Deltares and remain full property of Stichting Deltares at all times.
// All rights reserved.
-using System.Collections.Generic;
using System.Drawing.Drawing2D;
using System.Linq;
using Core.Common.Base.Geometry;
@@ -40,18 +39,13 @@
///
public class MapLineDataConverter : MapDataConverter
{
- protected override IList Convert(MapLineData data)
+ protected override IMapFeatureLayer Convert(MapLineData data)
{
var featureSet = new FeatureSet(FeatureType.Line);
foreach (var mapFeature in data.Features)
{
- var feature = new Feature(GetGeometry(mapFeature), featureSet);
-
- if (data.ShowLabels)
- {
- AddMetaDataAsAttributes(mapFeature, featureSet, feature);
- }
+ AddMetaDataAsAttributes(mapFeature, featureSet, new Feature(GetGeometry(mapFeature), featureSet));
}
featureSet.InitializeVertices();
@@ -66,10 +60,7 @@
CreateStyle(layer, data.Style);
- return new List
- {
- layer
- };
+ return layer;
}
private static IBasicGeometry GetGeometry(MapFeature mapFeature)
@@ -96,7 +87,7 @@
return lineString;
}
- private void CreateStyle(MapLineLayer layer, LineStyle style)
+ private static void CreateStyle(MapLineLayer layer, LineStyle style)
{
if (style != null)
{
Index: Core/Components/src/Core.Components.DotSpatial/Converter/MapPointDataConverter.cs
===================================================================
diff -u -rbea6d03f513b16bb85259b9f3ad9d34f476d8fde -r675771641656abfb56ef89be86bad7bd21684016
--- Core/Components/src/Core.Components.DotSpatial/Converter/MapPointDataConverter.cs (.../MapPointDataConverter.cs) (revision bea6d03f513b16bb85259b9f3ad9d34f476d8fde)
+++ Core/Components/src/Core.Components.DotSpatial/Converter/MapPointDataConverter.cs (.../MapPointDataConverter.cs) (revision 675771641656abfb56ef89be86bad7bd21684016)
@@ -37,18 +37,17 @@
///
public class MapPointDataConverter : MapDataConverter
{
- protected override IList Convert(MapPointData data)
+ protected override IMapFeatureLayer Convert(MapPointData data)
{
var featureSet = new FeatureSet(FeatureType.Point);
foreach (var ringtoetsMapFeature in data.Features)
{
foreach (var feature in GetAllMapFeatureCoordinates(ringtoetsMapFeature)
- .Select(c => new Feature(new Point(c.X, c.Y), featureSet))
- .Where(feature => data.ShowLabels))
- {
- AddMetaDataAsAttributes(ringtoetsMapFeature, featureSet, feature);
- }
+ .Select(c => new Feature(new Point(c.X, c.Y), featureSet)))
+ {
+ AddMetaDataAsAttributes(ringtoetsMapFeature, featureSet, feature);
+ }
}
featureSet.InitializeVertices();
@@ -63,10 +62,7 @@
CreateStyle(layer, data.Style);
- return new List
- {
- layer
- };
+ return layer;
}
private static IEnumerable GetAllMapFeatureCoordinates(MapFeature feature)
Index: Core/Components/src/Core.Components.DotSpatial/Converter/MapPolygonDataConverter.cs
===================================================================
diff -u -r038c9ca2c7731645e350beee83088b4ae20cbc7c -r675771641656abfb56ef89be86bad7bd21684016
--- Core/Components/src/Core.Components.DotSpatial/Converter/MapPolygonDataConverter.cs (.../MapPolygonDataConverter.cs) (revision 038c9ca2c7731645e350beee83088b4ae20cbc7c)
+++ Core/Components/src/Core.Components.DotSpatial/Converter/MapPolygonDataConverter.cs (.../MapPolygonDataConverter.cs) (revision 675771641656abfb56ef89be86bad7bd21684016)
@@ -37,7 +37,7 @@
///
public class MapPolygonDataConverter : MapDataConverter
{
- protected override IList Convert(MapPolygonData data)
+ protected override IMapFeatureLayer Convert(MapPolygonData data)
{
var featureSet = new FeatureSet(FeatureType.Polygon);
@@ -63,12 +63,7 @@
geometryList.Add(polygon);
}
- var feature = new Feature(GetGeometry(geometryList), featureSet);
-
- if (data.ShowLabels)
- {
- AddMetaDataAsAttributes(mapFeature, featureSet, feature);
- }
+ AddMetaDataAsAttributes(mapFeature, featureSet, new Feature(GetGeometry(geometryList), featureSet));
}
featureSet.InitializeVertices();
@@ -83,10 +78,7 @@
CreateStyle(layer, data.Style);
- return new List
- {
- layer
- };
+ return layer;
}
private static IBasicGeometry GetGeometry(List geometryList)
Index: Core/Components/src/Core.Components.DotSpatial/Core.Components.DotSpatial.csproj
===================================================================
diff -u -r39b0ea19fee7f9373ca435ceed191355713d6749 -r675771641656abfb56ef89be86bad7bd21684016
--- Core/Components/src/Core.Components.DotSpatial/Core.Components.DotSpatial.csproj (.../Core.Components.DotSpatial.csproj) (revision 39b0ea19fee7f9373ca435ceed191355713d6749)
+++ Core/Components/src/Core.Components.DotSpatial/Core.Components.DotSpatial.csproj (.../Core.Components.DotSpatial.csproj) (revision 675771641656abfb56ef89be86bad7bd21684016)
@@ -64,7 +64,6 @@
Properties\GlobalAssembly.cs
-
Index: Core/Components/src/Core.Components.Gis.Forms/IMapControl.cs
===================================================================
diff -u -rf04895089d34bddb618db8c7001c574428be10c6 -r675771641656abfb56ef89be86bad7bd21684016
--- Core/Components/src/Core.Components.Gis.Forms/IMapControl.cs (.../IMapControl.cs) (revision f04895089d34bddb618db8c7001c574428be10c6)
+++ Core/Components/src/Core.Components.Gis.Forms/IMapControl.cs (.../IMapControl.cs) (revision 675771641656abfb56ef89be86bad7bd21684016)
@@ -44,9 +44,9 @@
bool IsMouseCoordinatesVisible { get; }
///
- /// Gets the data to show in the .
+ /// Gets or sets the data to show in the .
///
- MapDataCollection Data { get; }
+ MapDataCollection Data { get; set; }
///
/// Zooms to a level so that all visible layers are in view.
@@ -67,10 +67,5 @@
/// Toggles the visibility mouse coordinates of the .
///
void ToggleMouseCoordinatesVisibility();
-
- ///
- /// Sets the data of the map to null.
- ///
- void ResetMapData();
}
}
\ No newline at end of file
Index: Core/Components/test/Core.Components.DotSpatial.Forms.Test/MapControlTest.cs
===================================================================
diff -u -rc09cca89155759682fcb5f11e1c4bf35051795b0 -r675771641656abfb56ef89be86bad7bd21684016
--- Core/Components/test/Core.Components.DotSpatial.Forms.Test/MapControlTest.cs (.../MapControlTest.cs) (revision c09cca89155759682fcb5f11e1c4bf35051795b0)
+++ Core/Components/test/Core.Components.DotSpatial.Forms.Test/MapControlTest.cs (.../MapControlTest.cs) (revision 675771641656abfb56ef89be86bad7bd21684016)
@@ -48,9 +48,7 @@
// Assert
Assert.IsInstanceOf(map);
Assert.IsInstanceOf(map);
- Assert.IsInstanceOf(map.Data);
- Assert.IsNotNull(map.Data);
- CollectionAssert.IsEmpty(map.Data.Collection);
+ Assert.IsNull(map.Data);
Assert.IsTrue(map.IsPanningEnabled);
Assert.IsFalse(map.IsRectangleZoomingEnabled);
Assert.IsTrue(map.IsMouseCoordinatesVisible);
@@ -78,77 +76,88 @@
}
[Test]
- public void Data_NotNull_ReturnsData()
+ public void GivenMapControlWithoutData_WhenDataSetToMapDataCollection_MapControlUpdated()
{
- // Setup
- using (var map = new MapControl())
- {
- var testData = new MapPointData("test data");
-
- // Call
- map.Data.Add(testData);
-
- // Assert
- Assert.AreSame(testData, map.Data.Collection.First());
- }
- }
-
- [Test]
- public void GivenMapControlWithMapData_WhenNestedMapDataAdded_MapControlUpdated()
- {
// Given
using (var map = new MapControl())
{
var mapView = map.Controls.OfType