diff --git a/Build/Plugins/PropertiesDock.dll b/Build/Plugins/PropertiesDock.dll
index b5cf4a461169dc7aedffa36be3973696d681488d..c21b458af23fac405c9014569555b96cb034be13 100644
Binary files a/Build/Plugins/PropertiesDock.dll and b/Build/Plugins/PropertiesDock.dll differ
diff --git a/Build/Plugins/PropertiesDock.pdb b/Build/Plugins/PropertiesDock.pdb
deleted file mode 100644
index e8cbabed6a18cc795839e82d845ada6a3d5bf555..0000000000000000000000000000000000000000
Binary files a/Build/Plugins/PropertiesDock.pdb and /dev/null differ
diff --git a/Source/Plugins/PropertiesDock/BuilderPlug.cs b/Source/Plugins/PropertiesDock/BuilderPlug.cs
index e2bc19deda2ea6291efd10101874f5221456dfac..7790085aab202637fdfee55178e0d76c5da1b9dc 100644
--- a/Source/Plugins/PropertiesDock/BuilderPlug.cs
+++ b/Source/Plugins/PropertiesDock/BuilderPlug.cs
@@ -38,7 +38,9 @@ namespace CodeImp.DoomBuilder.PropertiesDock
 
 		// This is called after a map has been successfully opened
 		public override void OnMapOpenEnd() {
-			if(propertiesDocker == null) {
+            MapElementsData.Init();
+            
+            if(propertiesDocker == null) {
 				propertiesDocker = new PropertiesDocker();
 				docker = new Docker("propertiesdockerpanel", "Properties", propertiesDocker);
 				General.Interface.AddDocker(docker);
diff --git a/Source/Plugins/PropertiesDock/Controls/PropertiesDocker.Designer.cs b/Source/Plugins/PropertiesDock/Controls/PropertiesDocker.Designer.cs
index a5c37c0c99bdfdc331c5f464f8e878b3308c8366..864477dc68114460c45c4fed645e5227fb932833 100644
--- a/Source/Plugins/PropertiesDock/Controls/PropertiesDocker.Designer.cs
+++ b/Source/Plugins/PropertiesDock/Controls/PropertiesDocker.Designer.cs
@@ -29,14 +29,18 @@
             this.tabPage1 = new System.Windows.Forms.TabPage();
             this.propertyGrid1 = new System.Windows.Forms.PropertyGrid();
             this.tabPage2 = new System.Windows.Forms.TabPage();
-            this.textBox1 = new System.Windows.Forms.TextBox();
             this.propertyGrid2 = new System.Windows.Forms.PropertyGrid();
             this.tabPage3 = new System.Windows.Forms.TabPage();
             this.propertyGrid3 = new System.Windows.Forms.PropertyGrid();
+            this.gbCustomFields = new System.Windows.Forms.GroupBox();
+            this.cbFieldType = new System.Windows.Forms.ComboBox();
+            this.bAddField = new System.Windows.Forms.Button();
+            this.tbFieldName = new System.Windows.Forms.TextBox();
             this.tabControl.SuspendLayout();
             this.tabPage1.SuspendLayout();
             this.tabPage2.SuspendLayout();
             this.tabPage3.SuspendLayout();
+            this.gbCustomFields.SuspendLayout();
             this.SuspendLayout();
             // 
             // tabControl
@@ -48,10 +52,10 @@
             this.tabControl.Controls.Add(this.tabPage2);
             this.tabControl.Controls.Add(this.tabPage3);
             this.tabControl.Font = new System.Drawing.Font("Arial", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
-            this.tabControl.Location = new System.Drawing.Point(3, 3);
+            this.tabControl.Location = new System.Drawing.Point(3, 56);
             this.tabControl.Name = "tabControl";
             this.tabControl.SelectedIndex = 0;
-            this.tabControl.Size = new System.Drawing.Size(266, 288);
+            this.tabControl.Size = new System.Drawing.Size(266, 341);
             this.tabControl.TabIndex = 0;
             // 
             // tabPage1
@@ -60,7 +64,7 @@
             this.tabPage1.Location = new System.Drawing.Point(4, 23);
             this.tabPage1.Name = "tabPage1";
             this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
-            this.tabPage1.Size = new System.Drawing.Size(258, 261);
+            this.tabPage1.Size = new System.Drawing.Size(258, 314);
             this.tabPage1.TabIndex = 0;
             this.tabPage1.Text = "tabPage1";
             this.tabPage1.UseVisualStyleBackColor = true;
@@ -73,7 +77,7 @@
             this.propertyGrid1.HelpVisible = false;
             this.propertyGrid1.Location = new System.Drawing.Point(6, 6);
             this.propertyGrid1.Name = "propertyGrid1";
-            this.propertyGrid1.Size = new System.Drawing.Size(246, 249);
+            this.propertyGrid1.Size = new System.Drawing.Size(246, 302);
             this.propertyGrid1.TabIndex = 0;
             this.propertyGrid1.PropertyValueChanged += new System.Windows.Forms.PropertyValueChangedEventHandler(this.propertyGrid1_PropertyValueChanged);
             // 
@@ -83,21 +87,11 @@
             this.tabPage2.Location = new System.Drawing.Point(4, 23);
             this.tabPage2.Name = "tabPage2";
             this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
-            this.tabPage2.Size = new System.Drawing.Size(258, 261);
+            this.tabPage2.Size = new System.Drawing.Size(258, 308);
             this.tabPage2.TabIndex = 1;
             this.tabPage2.Text = "tabPage2";
             this.tabPage2.UseVisualStyleBackColor = true;
             // 
-            // textBox1
-            // 
-            this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
-                        | System.Windows.Forms.AnchorStyles.Right)));
-            this.textBox1.Location = new System.Drawing.Point(3, 293);
-            this.textBox1.Multiline = true;
-            this.textBox1.Name = "textBox1";
-            this.textBox1.Size = new System.Drawing.Size(266, 104);
-            this.textBox1.TabIndex = 1;
-            // 
             // propertyGrid2
             // 
             this.propertyGrid2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
@@ -106,7 +100,7 @@
             this.propertyGrid2.HelpVisible = false;
             this.propertyGrid2.Location = new System.Drawing.Point(6, 6);
             this.propertyGrid2.Name = "propertyGrid2";
-            this.propertyGrid2.Size = new System.Drawing.Size(246, 249);
+            this.propertyGrid2.Size = new System.Drawing.Size(246, 296);
             this.propertyGrid2.TabIndex = 1;
             this.propertyGrid2.PropertyValueChanged += new System.Windows.Forms.PropertyValueChangedEventHandler(this.propertyGrid2_PropertyValueChanged);
             // 
@@ -115,7 +109,7 @@
             this.tabPage3.Controls.Add(this.propertyGrid3);
             this.tabPage3.Location = new System.Drawing.Point(4, 23);
             this.tabPage3.Name = "tabPage3";
-            this.tabPage3.Size = new System.Drawing.Size(258, 261);
+            this.tabPage3.Size = new System.Drawing.Size(258, 308);
             this.tabPage3.TabIndex = 2;
             this.tabPage3.Text = "tabPage3";
             this.tabPage3.UseVisualStyleBackColor = true;
@@ -128,15 +122,61 @@
             this.propertyGrid3.HelpVisible = false;
             this.propertyGrid3.Location = new System.Drawing.Point(6, 6);
             this.propertyGrid3.Name = "propertyGrid3";
-            this.propertyGrid3.Size = new System.Drawing.Size(246, 249);
+            this.propertyGrid3.Size = new System.Drawing.Size(246, 296);
             this.propertyGrid3.TabIndex = 1;
             this.propertyGrid3.PropertyValueChanged += new System.Windows.Forms.PropertyValueChangedEventHandler(this.propertyGrid3_PropertyValueChanged);
             // 
+            // gbCustomFields
+            // 
+            this.gbCustomFields.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+                        | System.Windows.Forms.AnchorStyles.Right)));
+            this.gbCustomFields.Controls.Add(this.tbFieldName);
+            this.gbCustomFields.Controls.Add(this.bAddField);
+            this.gbCustomFields.Controls.Add(this.cbFieldType);
+            this.gbCustomFields.Font = new System.Drawing.Font("Arial", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
+            this.gbCustomFields.Location = new System.Drawing.Point(3, 3);
+            this.gbCustomFields.Name = "gbCustomFields";
+            this.gbCustomFields.Size = new System.Drawing.Size(266, 47);
+            this.gbCustomFields.TabIndex = 1;
+            this.gbCustomFields.TabStop = false;
+            this.gbCustomFields.Text = "Add custom field:";
+            // 
+            // cbFieldType
+            // 
+            this.cbFieldType.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+                        | System.Windows.Forms.AnchorStyles.Right)));
+            this.cbFieldType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cbFieldType.FormattingEnabled = true;
+            this.cbFieldType.Location = new System.Drawing.Point(148, 19);
+            this.cbFieldType.Name = "cbFieldType";
+            this.cbFieldType.Size = new System.Drawing.Size(80, 22);
+            this.cbFieldType.TabIndex = 0;
+            // 
+            // bAddField
+            // 
+            this.bAddField.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.bAddField.Image = global::CodeImp.DoomBuilder.PropertiesDock.Properties.Resources.Add;
+            this.bAddField.Location = new System.Drawing.Point(232, 19);
+            this.bAddField.Name = "bAddField";
+            this.bAddField.Size = new System.Drawing.Size(28, 23);
+            this.bAddField.TabIndex = 1;
+            this.bAddField.UseVisualStyleBackColor = true;
+            this.bAddField.Click += new System.EventHandler(this.bAddField_Click);
+            // 
+            // tbFieldName
+            // 
+            this.tbFieldName.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+                        | System.Windows.Forms.AnchorStyles.Right)));
+            this.tbFieldName.Location = new System.Drawing.Point(10, 20);
+            this.tbFieldName.Name = "tbFieldName";
+            this.tbFieldName.Size = new System.Drawing.Size(134, 20);
+            this.tbFieldName.TabIndex = 3;
+            // 
             // PropertiesDocker
             // 
             this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
-            this.Controls.Add(this.textBox1);
+            this.Controls.Add(this.gbCustomFields);
             this.Controls.Add(this.tabControl);
             this.Font = new System.Drawing.Font("Arial Narrow", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
             this.Name = "PropertiesDocker";
@@ -145,8 +185,9 @@
             this.tabPage1.ResumeLayout(false);
             this.tabPage2.ResumeLayout(false);
             this.tabPage3.ResumeLayout(false);
+            this.gbCustomFields.ResumeLayout(false);
+            this.gbCustomFields.PerformLayout();
             this.ResumeLayout(false);
-            this.PerformLayout();
 
 		}
 
@@ -155,10 +196,13 @@
 		private System.Windows.Forms.TabControl tabControl;
 		private System.Windows.Forms.TabPage tabPage1;
 		private System.Windows.Forms.TabPage tabPage2;
-		private System.Windows.Forms.PropertyGrid propertyGrid1;
-        private System.Windows.Forms.TextBox textBox1;
+        private System.Windows.Forms.PropertyGrid propertyGrid1;
         private System.Windows.Forms.PropertyGrid propertyGrid2;
         private System.Windows.Forms.TabPage tabPage3;
         private System.Windows.Forms.PropertyGrid propertyGrid3;
+        private System.Windows.Forms.GroupBox gbCustomFields;
+        private System.Windows.Forms.Button bAddField;
+        private System.Windows.Forms.ComboBox cbFieldType;
+        private System.Windows.Forms.TextBox tbFieldName;
 	}
 }
diff --git a/Source/Plugins/PropertiesDock/Controls/PropertiesDocker.cs b/Source/Plugins/PropertiesDock/Controls/PropertiesDocker.cs
index 7bac3006c31d7435bc125a8affdb7b454ee94c0d..3b7486db036e80eb900d3aa0ccd5dfc4fe36d042 100644
--- a/Source/Plugins/PropertiesDock/Controls/PropertiesDocker.cs
+++ b/Source/Plugins/PropertiesDock/Controls/PropertiesDocker.cs
@@ -16,12 +16,24 @@ namespace CodeImp.DoomBuilder.PropertiesDock
         private TabPage page3;
 
         private string currentMode;
+        private static PropertiesDocker me;
         
         public PropertiesDocker() {
 			InitializeComponent();
 
+            me = this;
             page2 = tabControl.TabPages[1];
             page3 = tabControl.TabPages[2];
+
+            if (!General.Map.UDMF) {
+                gbCustomFields.Visible = false;
+                tabControl.Top = 3;
+            } else {
+                //todo: add "Delete field" button
+
+                //todo: sort this out...
+                //cbFieldType.Items.AddRange(General.Types.GetCustomUseAttributes());
+            }
 		}
 
 //SHOW HIGHLIGHT INFO
@@ -34,12 +46,13 @@ namespace CodeImp.DoomBuilder.PropertiesDock
         }
 
         public void ShowThingInfo(Thing t) {
-
+            propertyGrid1.SelectedObject = new ThingInfo(t);
+            viewThings(1, t.Index, true);
         }
 
         public void ShowVertexInfo(Vertex v) {
             propertyGrid1.SelectedObject = new VertexInfo(v);
-            viewVertices(true);
+            viewVertices(1, v.Index, true);
         }
 
         public void OnHighlightLost() {
@@ -47,9 +60,28 @@ namespace CodeImp.DoomBuilder.PropertiesDock
         }
 
 //SHOW SELECTION INFO
+        private void showSelectedThingsInfo() {
+            //anything selected?
+            List<Thing> things = (List<Thing>)General.Map.Map.GetSelectedThings(true);
+
+            if (things.Count > 0) {
+                ThingInfo[] infos = new ThingInfo[things.Count];
+                int i = 0;
+
+                foreach (Thing t in things) {
+                    infos[i++] = new ThingInfo(t);
+                }
+
+                propertyGrid1.SelectedObjects = infos;
+                viewThings(things.Count, things.Count == 1 ? things[0].Index : -1, true);
+            } else {
+                viewThings(-1, -1, false);
+            }
+        }
+        
         private void showSelectedVerticesInfo() {
             //anything selected?
-            ICollection<Vertex> verts = General.Map.Map.GetSelectedVertices(true);
+            List<Vertex> verts = (List<Vertex>)General.Map.Map.GetSelectedVertices(true);
 
             if (verts.Count > 0) {
                 VertexInfo[] infos = new VertexInfo[verts.Count];
@@ -60,30 +92,52 @@ namespace CodeImp.DoomBuilder.PropertiesDock
                 }
 
                 propertyGrid1.SelectedObjects = infos;
-                viewVertices(true);
+                viewVertices(verts.Count, verts.Count == 1 ? verts[0].Index : -1, true);
             } else {
-                //propertyGrid1.SelectedObjects = null;
-                viewVertices(false);
+                viewVertices(-1, -1, false);
             }
         }
 
 //PANELS UPDATE
-        private void viewVertices(bool enabled) {
-            propertyGrid1.Enabled = enabled;
-            tabControl.TabPages[0].Text = "Vertex:";
+        private void viewVertices(int count, int index, bool enabled) {
+            updateTabs(enabled, false);
+
+            if(count != -1)
+                tabControl.TabPages[0].Text = count > 1 ? count + " vertices:" : "Vertex "+index+":";
+        }
 
-            if (tabControl.TabPages.Count > 1) {
-                tabControl.TabPages.Remove(page2);
-                tabControl.TabPages.Remove(page3);
+        private void viewThings(int count, int index, bool enabled) {
+            updateTabs(enabled, false);
+
+            if (count != -1)
+                tabControl.TabPages[0].Text = count > 1 ? count + " things:" : "Thing " + index + ":";
+        }
+
+        private void updateTabs(bool enabled, bool showAllTabs) {
+            if (showAllTabs) {
+                if (tabControl.TabPages.Count == 1) {
+                    tabControl.TabPages.Add(page2);
+                    tabControl.TabPages.Add(page3);
+                }
+                propertyGrid2.Enabled = enabled;
+                propertyGrid3.Enabled = enabled;
+
+            } else {
+                if (tabControl.TabPages.Count == 3) {
+                    tabControl.TabPages.Remove(page2);
+                    tabControl.TabPages.Remove(page3);
+                }
             }
+            propertyGrid1.Enabled = enabled;
         }
 
 //util
         public void ChangeEditMode(string name) {
-            textBox1.AppendText("Mode Changed to " + name + Environment.NewLine);
+            //textBox1.AppendText("Mode Changed to " + name + Environment.NewLine);
             
             if (name == "ThingsMode") {
                 currentMode = name;
+                showSelectedThingsInfo();
 
             } else if (name == "SectorsMode") {
                 currentMode = name;
@@ -105,6 +159,12 @@ namespace CodeImp.DoomBuilder.PropertiesDock
             ChangeEditMode(currentMode);
         }
 
+        public static void Refresh() {
+            me.propertyGrid1.Refresh();
+            me.propertyGrid2.Refresh();
+            me.propertyGrid3.Refresh();
+        }
+
         private void saveChanges() {
             if (currentMode == "ThingsMode" && propertyGrid1.Enabled) {
                 applyThingChanges();
@@ -120,7 +180,7 @@ namespace CodeImp.DoomBuilder.PropertiesDock
         }
 
         private void applyThingChanges() {
-            throw new NotImplementedException();
+            //throw new NotImplementedException();
         }
 
         private void applySectorChanges() {
@@ -168,5 +228,26 @@ namespace CodeImp.DoomBuilder.PropertiesDock
         private void propertyGrid3_PropertyValueChanged(object s, PropertyValueChangedEventArgs e) {
             saveChanges();
         }
+
+        private void bAddField_Click(object sender, EventArgs e) {
+            PropertyGrid g = null;
+            
+            if (tbFieldName.Text.Length > 0) {
+                if (tabControl.SelectedIndex == 0) {
+                    g = propertyGrid1;
+                } else if (tabControl.SelectedIndex == 1) {
+                    g = propertyGrid2;
+                } else if (tabControl.SelectedIndex == 2) {
+                    g = propertyGrid3;
+                }
+            }
+
+            if (g != null && g.Enabled && g.SelectedObjects.Length > 0) {
+                foreach (object o in g.SelectedObjects) {
+                    //todo: set correct type
+                    ((IMapElementInfo)o).AddCustomProperty(tbFieldName.Text, typeof(string));
+                }
+            }
+        }
 	}
 }
diff --git a/Source/Plugins/PropertiesDock/Data/MapElementsData.cs b/Source/Plugins/PropertiesDock/Data/MapElementsData.cs
new file mode 100644
index 0000000000000000000000000000000000000000..08ae54687833d35bf1199bfa63c8063c73f62607
--- /dev/null
+++ b/Source/Plugins/PropertiesDock/Data/MapElementsData.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using CodeImp.DoomBuilder.Config;
+
+namespace CodeImp.DoomBuilder.PropertiesDock {
+    public static class MapElementsData {
+
+        public static Dictionary<int, string> ThingTypeDescriptions { get { return thingTypeDescriptions;}}
+        private static Dictionary<int, string> thingTypeDescriptions;
+        
+        public static void Init() {
+            //thing types
+            thingTypeDescriptions = new Dictionary<int, string>();
+
+            foreach (ThingCategory tc in General.Map.Data.ThingCategories) {
+                foreach (ThingTypeInfo ti in tc.Things) {
+                    thingTypeDescriptions.Add(ti.Index, ti.Title);
+                }
+            }
+        }
+    }
+}
diff --git a/Source/Plugins/PropertiesDock/Data/PropertyBag.cs b/Source/Plugins/PropertiesDock/Data/PropertyBag.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0bf7ce03711352d499dd728cd43be6a6cd159512
--- /dev/null
+++ b/Source/Plugins/PropertiesDock/Data/PropertyBag.cs
@@ -0,0 +1,979 @@
+/********************************************************************
+ *
+ *  PropertyBag.cs
+ *  --------------
+ *  Copyright (C) 2002  Tony Allowatt
+ *  Last Update: 12/14/2002
+ * 
+ *  THE SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS", WITHOUT WARRANTY
+ *  OF ANY KIND, EXPRESS OR IMPLIED. IN NO EVENT SHALL THE AUTHOR BE
+ *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING FROM,
+ *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF THIS
+ *  SOFTWARE.
+ * 
+ *  Public types defined in this file:
+ *  ----------------------------------
+ *  namespace Flobbster.Windows.Forms
+ *     class PropertySpec
+ *     class PropertySpecEventArgs
+ *     delegate PropertySpecEventHandler
+ *     class PropertyBag
+ *        class PropertyBag.PropertySpecCollection
+ *     class PropertyTable
+ *
+ ********************************************************************/
+
+using System;
+using System.Collections;
+using System.ComponentModel;
+using System.Drawing.Design;
+
+namespace CodeImp.DoomBuilder.PropertiesDock {
+    /// <summary>
+    /// Represents a single property in a PropertySpec.
+    /// </summary>
+    public class PropertySpec {
+        private Attribute[] attributes;
+        private string category;
+        private object defaultValue;
+        private string description;
+        private string editor;
+        private string name;
+        private string type;
+        private string typeConverter;
+        private object currentValue; //mxd
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">The fully qualified name of the type of the property.</param>
+        public PropertySpec(string name, string type) : this(name, type, null, null, null) { }
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">A Type that represents the type of the property.</param>
+        public PropertySpec(string name, Type type) :
+            this(name, type.AssemblyQualifiedName, null, null, null) { }
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">The fully qualified name of the type of the property.</param>
+        /// <param name="category">The category under which the property is displayed in the
+        /// property grid.</param>
+        public PropertySpec(string name, string type, string category) : this(name, type, category, null, null) { }
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">A Type that represents the type of the property.</param>
+        /// <param name="category"></param>
+        public PropertySpec(string name, Type type, string category) :
+            this(name, type.AssemblyQualifiedName, category, null, null) { }
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">The fully qualified name of the type of the property.</param>
+        /// <param name="category">The category under which the property is displayed in the
+        /// property grid.</param>
+        /// <param name="description">A string that is displayed in the help area of the
+        /// property grid.</param>
+        public PropertySpec(string name, string type, string category, string description) :
+            this(name, type, category, description, null) { }
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">A Type that represents the type of the property.</param>
+        /// <param name="category">The category under which the property is displayed in the
+        /// property grid.</param>
+        /// <param name="description">A string that is displayed in the help area of the
+        /// property grid.</param>
+        public PropertySpec(string name, Type type, string category, string description) :
+            this(name, type.AssemblyQualifiedName, category, description, null) { }
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">The fully qualified name of the type of the property.</param>
+        /// <param name="category">The category under which the property is displayed in the
+        /// property grid.</param>
+        /// <param name="description">A string that is displayed in the help area of the
+        /// property grid.</param>
+        /// <param name="defaultValue">The default value of the property, or null if there is
+        /// no default value.</param>
+        public PropertySpec(string name, string type, string category, string description, object defaultValue) {
+            this.name = name;
+            this.type = type;
+            this.category = category;
+            this.description = description;
+            this.defaultValue = defaultValue;
+            this.attributes = null;
+            this.currentValue = defaultValue; //mxd
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">A Type that represents the type of the property.</param>
+        /// <param name="category">The category under which the property is displayed in the
+        /// property grid.</param>
+        /// <param name="description">A string that is displayed in the help area of the
+        /// property grid.</param>
+        /// <param name="defaultValue">The default value of the property, or null if there is
+        /// no default value.</param>
+        public PropertySpec(string name, Type type, string category, string description, object defaultValue) :
+            this(name, type.AssemblyQualifiedName, category, description, defaultValue) { }
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">The fully qualified name of the type of the property.</param>
+        /// <param name="category">The category under which the property is displayed in the
+        /// property grid.</param>
+        /// <param name="description">A string that is displayed in the help area of the
+        /// property grid.</param>
+        /// <param name="defaultValue">The default value of the property, or null if there is
+        /// no default value.</param>
+        /// <param name="editor">The fully qualified name of the type of the editor for this
+        /// property.  This type must derive from UITypeEditor.</param>
+        /// <param name="typeConverter">The fully qualified name of the type of the type
+        /// converter for this property.  This type must derive from TypeConverter.</param>
+        public PropertySpec(string name, string type, string category, string description, object defaultValue,
+            string editor, string typeConverter)
+            : this(name, type, category, description, defaultValue) {
+            this.editor = editor;
+            this.typeConverter = typeConverter;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">A Type that represents the type of the property.</param>
+        /// <param name="category">The category under which the property is displayed in the
+        /// property grid.</param>
+        /// <param name="description">A string that is displayed in the help area of the
+        /// property grid.</param>
+        /// <param name="defaultValue">The default value of the property, or null if there is
+        /// no default value.</param>
+        /// <param name="editor">The fully qualified name of the type of the editor for this
+        /// property.  This type must derive from UITypeEditor.</param>
+        /// <param name="typeConverter">The fully qualified name of the type of the type
+        /// converter for this property.  This type must derive from TypeConverter.</param>
+        public PropertySpec(string name, Type type, string category, string description, object defaultValue,
+            string editor, string typeConverter) :
+            this(name, type.AssemblyQualifiedName, category, description, defaultValue, editor, typeConverter) { }
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">The fully qualified name of the type of the property.</param>
+        /// <param name="category">The category under which the property is displayed in the
+        /// property grid.</param>
+        /// <param name="description">A string that is displayed in the help area of the
+        /// property grid.</param>
+        /// <param name="defaultValue">The default value of the property, or null if there is
+        /// no default value.</param>
+        /// <param name="editor">The Type that represents the type of the editor for this
+        /// property.  This type must derive from UITypeEditor.</param>
+        /// <param name="typeConverter">The fully qualified name of the type of the type
+        /// converter for this property.  This type must derive from TypeConverter.</param>
+        public PropertySpec(string name, string type, string category, string description, object defaultValue,
+            Type editor, string typeConverter) :
+            this(name, type, category, description, defaultValue, editor.AssemblyQualifiedName,
+            typeConverter) { }
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">A Type that represents the type of the property.</param>
+        /// <param name="category">The category under which the property is displayed in the
+        /// property grid.</param>
+        /// <param name="description">A string that is displayed in the help area of the
+        /// property grid.</param>
+        /// <param name="defaultValue">The default value of the property, or null if there is
+        /// no default value.</param>
+        /// <param name="editor">The Type that represents the type of the editor for this
+        /// property.  This type must derive from UITypeEditor.</param>
+        /// <param name="typeConverter">The fully qualified name of the type of the type
+        /// converter for this property.  This type must derive from TypeConverter.</param>
+        public PropertySpec(string name, Type type, string category, string description, object defaultValue,
+            Type editor, string typeConverter) :
+            this(name, type.AssemblyQualifiedName, category, description, defaultValue,
+            editor.AssemblyQualifiedName, typeConverter) { }
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">The fully qualified name of the type of the property.</param>
+        /// <param name="category">The category under which the property is displayed in the
+        /// property grid.</param>
+        /// <param name="description">A string that is displayed in the help area of the
+        /// property grid.</param>
+        /// <param name="defaultValue">The default value of the property, or null if there is
+        /// no default value.</param>
+        /// <param name="editor">The fully qualified name of the type of the editor for this
+        /// property.  This type must derive from UITypeEditor.</param>
+        /// <param name="typeConverter">The Type that represents the type of the type
+        /// converter for this property.  This type must derive from TypeConverter.</param>
+        public PropertySpec(string name, string type, string category, string description, object defaultValue,
+            string editor, Type typeConverter) :
+            this(name, type, category, description, defaultValue, editor, typeConverter.AssemblyQualifiedName) { }
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">A Type that represents the type of the property.</param>
+        /// <param name="category">The category under which the property is displayed in the
+        /// property grid.</param>
+        /// <param name="description">A string that is displayed in the help area of the
+        /// property grid.</param>
+        /// <param name="defaultValue">The default value of the property, or null if there is
+        /// no default value.</param>
+        /// <param name="editor">The fully qualified name of the type of the editor for this
+        /// property.  This type must derive from UITypeEditor.</param>
+        /// <param name="typeConverter">The Type that represents the type of the type
+        /// converter for this property.  This type must derive from TypeConverter.</param>
+        public PropertySpec(string name, Type type, string category, string description, object defaultValue,
+            string editor, Type typeConverter) :
+            this(name, type.AssemblyQualifiedName, category, description, defaultValue, editor,
+            typeConverter.AssemblyQualifiedName) { }
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">The fully qualified name of the type of the property.</param>
+        /// <param name="category">The category under which the property is displayed in the
+        /// property grid.</param>
+        /// <param name="description">A string that is displayed in the help area of the
+        /// property grid.</param>
+        /// <param name="defaultValue">The default value of the property, or null if there is
+        /// no default value.</param>
+        /// <param name="editor">The Type that represents the type of the editor for this
+        /// property.  This type must derive from UITypeEditor.</param>
+        /// <param name="typeConverter">The Type that represents the type of the type
+        /// converter for this property.  This type must derive from TypeConverter.</param>
+        public PropertySpec(string name, string type, string category, string description, object defaultValue,
+            Type editor, Type typeConverter) :
+            this(name, type, category, description, defaultValue, editor.AssemblyQualifiedName,
+            typeConverter.AssemblyQualifiedName) { }
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpec class.
+        /// </summary>
+        /// <param name="name">The name of the property displayed in the property grid.</param>
+        /// <param name="type">A Type that represents the type of the property.</param>
+        /// <param name="category">The category under which the property is displayed in the
+        /// property grid.</param>
+        /// <param name="description">A string that is displayed in the help area of the
+        /// property grid.</param>
+        /// <param name="defaultValue">The default value of the property, or null if there is
+        /// no default value.</param>
+        /// <param name="editor">The Type that represents the type of the editor for this
+        /// property.  This type must derive from UITypeEditor.</param>
+        /// <param name="typeConverter">The Type that represents the type of the type
+        /// converter for this property.  This type must derive from TypeConverter.</param>
+        public PropertySpec(string name, Type type, string category, string description, object defaultValue,
+            Type editor, Type typeConverter) :
+            this(name, type.AssemblyQualifiedName, category, description, defaultValue,
+            editor.AssemblyQualifiedName, typeConverter.AssemblyQualifiedName) { }
+
+        /// <summary>
+        /// Gets or sets a collection of additional Attributes for this property.  This can
+        /// be used to specify attributes beyond those supported intrinsically by the
+        /// PropertySpec class, such as ReadOnly and Browsable.
+        /// </summary>
+        public Attribute[] Attributes {
+            get { return attributes; }
+            set { attributes = value; }
+        }
+
+        /// <summary>
+        /// Gets or sets the category name of this property.
+        /// </summary>
+        public string Category {
+            get { return category; }
+            set { category = value; }
+        }
+
+        /// <summary>
+        /// Gets or sets the fully qualified name of the type converter
+        /// type for this property.
+        /// </summary>
+        public string ConverterTypeName {
+            get { return typeConverter; }
+            set { typeConverter = value; }
+        }
+
+        /// <summary>
+        /// Gets or sets the default value of this property.
+        /// </summary>
+        public object DefaultValue {
+            get { return defaultValue; }
+            set { defaultValue = value; }
+        }
+
+        //mxd
+        /// <summary>
+        /// Gets or sets the value of this property.
+        /// </summary>
+        public object Value {
+            get { return currentValue; }
+            set { currentValue = value; }
+        }
+
+        /// <summary>
+        /// Gets or sets the help text description of this property.
+        /// </summary>
+        public string Description {
+            get { return description; }
+            set { description = value; }
+        }
+
+        /// <summary>
+        /// Gets or sets the fully qualified name of the editor type for
+        /// this property.
+        /// </summary>
+        public string EditorTypeName {
+            get { return editor; }
+            set { editor = value; }
+        }
+
+        /// <summary>
+        /// Gets or sets the name of this property.
+        /// </summary>
+        public string Name {
+            get { return name; }
+            set { name = value; }
+        }
+
+        /// <summary>
+        /// Gets or sets the fully qualfied name of the type of this
+        /// property.
+        /// </summary>
+        public string TypeName {
+            get { return type; }
+            set { type = value; }
+        }
+    }
+
+    /// <summary>
+    /// Provides data for the GetValue and SetValue events of the PropertyBag class.
+    /// </summary>
+    public class PropertySpecEventArgs : EventArgs {
+        private PropertySpec property;
+        private object val;
+
+        /// <summary>
+        /// Initializes a new instance of the PropertySpecEventArgs class.
+        /// </summary>
+        /// <param name="property">The PropertySpec that represents the property whose
+        /// value is being requested or set.</param>
+        /// <param name="val">The current value of the property.</param>
+        public PropertySpecEventArgs(PropertySpec property, object val) {
+            this.property = property;
+            this.val = val;
+        }
+
+        /// <summary>
+        /// Gets the PropertySpec that represents the property whose value is being
+        /// requested or set.
+        /// </summary>
+        public PropertySpec Property {
+            get { return property; }
+        }
+
+        /// <summary>
+        /// Gets or sets the current value of the property.
+        /// </summary>
+        public object Value {
+            get { return val; }
+            set { val = value; }
+        }
+    }
+
+    /// <summary>
+    /// Represents the method that will handle the GetValue and SetValue events of the
+    /// PropertyBag class.
+    /// </summary>
+    public delegate void PropertySpecEventHandler(object sender, PropertySpecEventArgs e);
+
+    /// <summary>
+    /// Represents a collection of custom properties that can be selected into a
+    /// PropertyGrid to provide functionality beyond that of the simple reflection
+    /// normally used to query an object's properties.
+    /// </summary>
+    public class PropertyBag : ICustomTypeDescriptor {
+        #region PropertySpecCollection class definition
+        /// <summary>
+        /// Encapsulates a collection of PropertySpec objects.
+        /// </summary>
+        [Serializable]
+        public class PropertySpecCollection : IList {
+            private ArrayList innerArray;
+
+            /// <summary>
+            /// Initializes a new instance of the PropertySpecCollection class.
+            /// </summary>
+            public PropertySpecCollection() {
+                innerArray = new ArrayList();
+            }
+
+            /// <summary>
+            /// Gets the number of elements in the PropertySpecCollection.
+            /// </summary>
+            /// <value>
+            /// The number of elements contained in the PropertySpecCollection.
+            /// </value>
+            public int Count {
+                get { return innerArray.Count; }
+            }
+
+            /// <summary>
+            /// Gets a value indicating whether the PropertySpecCollection has a fixed size.
+            /// </summary>
+            /// <value>
+            /// true if the PropertySpecCollection has a fixed size; otherwise, false.
+            /// </value>
+            public bool IsFixedSize {
+                get { return false; }
+            }
+
+            /// <summary>
+            /// Gets a value indicating whether the PropertySpecCollection is read-only.
+            /// </summary>
+            public bool IsReadOnly {
+                get { return false; }
+            }
+
+            /// <summary>
+            /// Gets a value indicating whether access to the collection is synchronized (thread-safe).
+            /// </summary>
+            /// <value>
+            /// true if access to the PropertySpecCollection is synchronized (thread-safe); otherwise, false.
+            /// </value>
+            public bool IsSynchronized {
+                get { return false; }
+            }
+
+            /// <summary>
+            /// Gets an object that can be used to synchronize access to the collection.
+            /// </summary>
+            /// <value>
+            /// An object that can be used to synchronize access to the collection.
+            /// </value>
+            object ICollection.SyncRoot {
+                get { return null; }
+            }
+
+            /// <summary>
+            /// Gets or sets the element at the specified index.
+            /// In C#, this property is the indexer for the PropertySpecCollection class.
+            /// </summary>
+            /// <param name="index">The zero-based index of the element to get or set.</param>
+            /// <value>
+            /// The element at the specified index.
+            /// </value>
+            public PropertySpec this[int index] {
+                get { return (PropertySpec)innerArray[index]; }
+                set { innerArray[index] = value; }
+            }
+
+            /// <summary>
+            /// Adds a PropertySpec to the end of the PropertySpecCollection.
+            /// </summary>
+            /// <param name="value">The PropertySpec to be added to the end of the PropertySpecCollection.</param>
+            /// <returns>The PropertySpecCollection index at which the value has been added.</returns>
+            public int Add(PropertySpec value) {
+                int index = innerArray.Add(value);
+
+                return index;
+            }
+
+            /// <summary>
+            /// Adds the elements of an array of PropertySpec objects to the end of the PropertySpecCollection.
+            /// </summary>
+            /// <param name="array">The PropertySpec array whose elements should be added to the end of the
+            /// PropertySpecCollection.</param>
+            public void AddRange(PropertySpec[] array) {
+                innerArray.AddRange(array);
+            }
+
+            /// <summary>
+            /// Removes all elements from the PropertySpecCollection.
+            /// </summary>
+            public void Clear() {
+                innerArray.Clear();
+            }
+
+            /// <summary>
+            /// Determines whether a PropertySpec is in the PropertySpecCollection.
+            /// </summary>
+            /// <param name="item">The PropertySpec to locate in the PropertySpecCollection. The element to locate
+            /// can be a null reference (Nothing in Visual Basic).</param>
+            /// <returns>true if item is found in the PropertySpecCollection; otherwise, false.</returns>
+            public bool Contains(PropertySpec item) {
+                return innerArray.Contains(item);
+            }
+
+            /// <summary>
+            /// Determines whether a PropertySpec with the specified name is in the PropertySpecCollection.
+            /// </summary>
+            /// <param name="name">The name of the PropertySpec to locate in the PropertySpecCollection.</param>
+            /// <returns>true if item is found in the PropertySpecCollection; otherwise, false.</returns>
+            public bool Contains(string name) {
+                foreach (PropertySpec spec in innerArray)
+                    if (spec.Name == name)
+                        return true;
+
+                return false;
+            }
+
+            /// <summary>
+            /// Copies the entire PropertySpecCollection to a compatible one-dimensional Array, starting at the
+            /// beginning of the target array.
+            /// </summary>
+            /// <param name="array">The one-dimensional Array that is the destination of the elements copied
+            /// from PropertySpecCollection. The Array must have zero-based indexing.</param>
+            public void CopyTo(PropertySpec[] array) {
+                innerArray.CopyTo(array);
+            }
+
+            /// <summary>
+            /// Copies the PropertySpecCollection or a portion of it to a one-dimensional array.
+            /// </summary>
+            /// <param name="array">The one-dimensional Array that is the destination of the elements copied
+            /// from the collection.</param>
+            /// <param name="index">The zero-based index in array at which copying begins.</param>
+            public void CopyTo(PropertySpec[] array, int index) {
+                innerArray.CopyTo(array, index);
+            }
+
+            /// <summary>
+            /// Returns an enumerator that can iterate through the PropertySpecCollection.
+            /// </summary>
+            /// <returns>An IEnumerator for the entire PropertySpecCollection.</returns>
+            public IEnumerator GetEnumerator() {
+                return innerArray.GetEnumerator();
+            }
+
+            /// <summary>
+            /// Searches for the specified PropertySpec and returns the zero-based index of the first
+            /// occurrence within the entire PropertySpecCollection.
+            /// </summary>
+            /// <param name="value">The PropertySpec to locate in the PropertySpecCollection.</param>
+            /// <returns>The zero-based index of the first occurrence of value within the entire PropertySpecCollection,
+            /// if found; otherwise, -1.</returns>
+            public int IndexOf(PropertySpec value) {
+                return innerArray.IndexOf(value);
+            }
+
+            /// <summary>
+            /// Searches for the PropertySpec with the specified name and returns the zero-based index of
+            /// the first occurrence within the entire PropertySpecCollection.
+            /// </summary>
+            /// <param name="name">The name of the PropertySpec to locate in the PropertySpecCollection.</param>
+            /// <returns>The zero-based index of the first occurrence of value within the entire PropertySpecCollection,
+            /// if found; otherwise, -1.</returns>
+            public int IndexOf(string name) {
+                int i = 0;
+
+                foreach (PropertySpec spec in innerArray) {
+                    if (spec.Name == name)
+                        return i;
+
+                    i++;
+                }
+
+                return -1;
+            }
+
+            /// <summary>
+            /// Inserts a PropertySpec object into the PropertySpecCollection at the specified index.
+            /// </summary>
+            /// <param name="index">The zero-based index at which value should be inserted.</param>
+            /// <param name="value">The PropertySpec to insert.</param>
+            public void Insert(int index, PropertySpec value) {
+                innerArray.Insert(index, value);
+            }
+
+            /// <summary>
+            /// Removes the first occurrence of a specific object from the PropertySpecCollection.
+            /// </summary>
+            /// <param name="obj">The PropertySpec to remove from the PropertySpecCollection.</param>
+            public void Remove(PropertySpec obj) {
+                innerArray.Remove(obj);
+            }
+
+            /// <summary>
+            /// Removes the property with the specified name from the PropertySpecCollection.
+            /// </summary>
+            /// <param name="name">The name of the PropertySpec to remove from the PropertySpecCollection.</param>
+            public void Remove(string name) {
+                int index = IndexOf(name);
+                RemoveAt(index);
+            }
+
+            /// <summary>
+            /// Removes the object at the specified index of the PropertySpecCollection.
+            /// </summary>
+            /// <param name="index">The zero-based index of the element to remove.</param>
+            public void RemoveAt(int index) {
+                innerArray.RemoveAt(index);
+            }
+
+            /// <summary>
+            /// Copies the elements of the PropertySpecCollection to a new PropertySpec array.
+            /// </summary>
+            /// <returns>A PropertySpec array containing copies of the elements of the PropertySpecCollection.</returns>
+            public PropertySpec[] ToArray() {
+                return (PropertySpec[])innerArray.ToArray(typeof(PropertySpec));
+            }
+
+            #region Explicit interface implementations for ICollection and IList
+            /// <summary>
+            /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
+            /// </summary>
+            void ICollection.CopyTo(Array array, int index) {
+                CopyTo((PropertySpec[])array, index);
+            }
+
+            /// <summary>
+            /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
+            /// </summary>
+            int IList.Add(object value) {
+                return Add((PropertySpec)value);
+            }
+
+            /// <summary>
+            /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
+            /// </summary>
+            bool IList.Contains(object obj) {
+                return Contains((PropertySpec)obj);
+            }
+
+            /// <summary>
+            /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
+            /// </summary>
+            object IList.this[int index] {
+                get {
+                    return ((PropertySpecCollection)this)[index];
+                }
+                set {
+                    ((PropertySpecCollection)this)[index] = (PropertySpec)value;
+                }
+            }
+
+            /// <summary>
+            /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
+            /// </summary>
+            int IList.IndexOf(object obj) {
+                return IndexOf((PropertySpec)obj);
+            }
+
+            /// <summary>
+            /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
+            /// </summary>
+            void IList.Insert(int index, object value) {
+                Insert(index, (PropertySpec)value);
+            }
+
+            /// <summary>
+            /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
+            /// </summary>
+            void IList.Remove(object value) {
+                Remove((PropertySpec)value);
+            }
+            #endregion
+        }
+        #endregion
+        #region PropertySpecDescriptor class definition
+        private class PropertySpecDescriptor : PropertyDescriptor {
+            private PropertyBag bag;
+            private PropertySpec item;
+
+            public PropertySpecDescriptor(PropertySpec item, PropertyBag bag, string name, Attribute[] attrs) :
+                base(name, attrs) {
+                this.bag = bag;
+                this.item = item;
+            }
+
+            public override Type ComponentType {
+                get { return item.GetType(); }
+            }
+
+            public override bool IsReadOnly {
+                get { return (Attributes.Matches(ReadOnlyAttribute.Yes)); }
+            }
+
+            public override Type PropertyType {
+                get { return Type.GetType(item.TypeName); }
+            }
+
+            public override bool CanResetValue(object component) {
+                if (item.DefaultValue == null)
+                    return false;
+                else
+                    return !this.GetValue(component).Equals(item.DefaultValue);
+            }
+
+            public override object GetValue(object component) {
+                // Have the property bag raise an event to get the current value
+                // of the property.
+
+                PropertySpecEventArgs e = new PropertySpecEventArgs(item, null);
+                bag.OnGetValue(e);
+                return e.Value;
+            }
+
+            public override void ResetValue(object component) {
+                SetValue(component, item.DefaultValue);
+            }
+
+            public override void SetValue(object component, object value) {
+                // Have the property bag raise an event to set the current value
+                // of the property.
+
+                PropertySpecEventArgs e = new PropertySpecEventArgs(item, value);
+                bag.OnSetValue(e);
+            }
+
+            public override bool ShouldSerializeValue(object component) {
+                object val = this.GetValue(component);
+
+                if (item.DefaultValue == null && val == null) 
+                    return false;
+                else if (item.DefaultValue != null && val == null)//mxd
+                    return true;
+                else
+                    return !val.Equals(item.DefaultValue);
+            }
+        }
+        #endregion
+
+        private string defaultProperty;
+        protected PropertySpecCollection properties; //mxd
+
+        /// <summary>
+        /// Initializes a new instance of the PropertyBag class.
+        /// </summary>
+        public PropertyBag() {
+            defaultProperty = null;
+            properties = new PropertySpecCollection();
+        }
+
+        /// <summary>
+        /// Gets or sets the name of the default property in the collection.
+        /// </summary>
+        public string DefaultProperty {
+            get { return defaultProperty; }
+            set { defaultProperty = value; }
+        }
+
+        /// <summary>
+        /// Gets the collection of properties contained within this PropertyBag.
+        /// </summary>
+        public PropertySpecCollection Properties {
+            get { return properties; }
+        }
+
+        /// <summary>
+        /// Occurs when a PropertyGrid requests the value of a property.
+        /// </summary>
+        public event PropertySpecEventHandler GetValue;
+
+        /// <summary>
+        /// Occurs when the user changes the value of a property in a PropertyGrid.
+        /// </summary>
+        public event PropertySpecEventHandler SetValue;
+
+        /// <summary>
+        /// Raises the GetValue event.
+        /// </summary>
+        /// <param name="e">A PropertySpecEventArgs that contains the event data.</param>
+        protected virtual void OnGetValue(PropertySpecEventArgs e) {
+            if (GetValue != null)
+                GetValue(this, e);
+        }
+
+        /// <summary>
+        /// Raises the SetValue event.
+        /// </summary>
+        /// <param name="e">A PropertySpecEventArgs that contains the event data.</param>
+        protected virtual void OnSetValue(PropertySpecEventArgs e) {
+            //mxd
+            e.Property.Value = e.Value;
+
+            if (SetValue != null)
+                SetValue(this, e);
+        }
+
+        #region ICustomTypeDescriptor explicit interface definitions
+        // Most of the functions required by the ICustomTypeDescriptor are
+        // merely pssed on to the default TypeDescriptor for this type,
+        // which will do something appropriate.  The exceptions are noted
+        // below.
+        AttributeCollection ICustomTypeDescriptor.GetAttributes() {
+            return TypeDescriptor.GetAttributes(this, true);
+        }
+
+        string ICustomTypeDescriptor.GetClassName() {
+            return TypeDescriptor.GetClassName(this, true);
+        }
+
+        string ICustomTypeDescriptor.GetComponentName() {
+            return TypeDescriptor.GetComponentName(this, true);
+        }
+
+        TypeConverter ICustomTypeDescriptor.GetConverter() {
+            return TypeDescriptor.GetConverter(this, true);
+        }
+
+        EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() {
+            return TypeDescriptor.GetDefaultEvent(this, true);
+        }
+
+        PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() {
+            // This function searches the property list for the property
+            // with the same name as the DefaultProperty specified, and
+            // returns a property descriptor for it.  If no property is
+            // found that matches DefaultProperty, a null reference is
+            // returned instead.
+
+            PropertySpec propertySpec = null;
+            if (defaultProperty != null) {
+                int index = properties.IndexOf(defaultProperty);
+                propertySpec = properties[index];
+            }
+
+            if (propertySpec != null)
+                return new PropertySpecDescriptor(propertySpec, this, propertySpec.Name, null);
+            else
+                return null;
+        }
+
+        object ICustomTypeDescriptor.GetEditor(Type editorBaseType) {
+            return TypeDescriptor.GetEditor(this, editorBaseType, true);
+        }
+
+        EventDescriptorCollection ICustomTypeDescriptor.GetEvents() {
+            return TypeDescriptor.GetEvents(this, true);
+        }
+
+        EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) {
+            return TypeDescriptor.GetEvents(this, attributes, true);
+        }
+
+        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() {
+            return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]);
+        }
+
+        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) {
+            // Rather than passing this function on to the default TypeDescriptor,
+            // which would return the actual properties of PropertyBag, I construct
+            // a list here that contains property descriptors for the elements of the
+            // Properties list in the bag.
+
+            ArrayList props = new ArrayList();
+
+            foreach (PropertySpec property in properties) {
+                ArrayList attrs = new ArrayList();
+
+                // If a category, description, editor, or type converter are specified
+                // in the PropertySpec, create attributes to define that relationship.
+                if (property.Category != null)
+                    attrs.Add(new CategoryAttribute(property.Category));
+
+                if (property.Description != null)
+                    attrs.Add(new DescriptionAttribute(property.Description));
+
+                if (property.EditorTypeName != null)
+                    attrs.Add(new EditorAttribute(property.EditorTypeName, typeof(UITypeEditor)));
+
+                if (property.ConverterTypeName != null)
+                    attrs.Add(new TypeConverterAttribute(property.ConverterTypeName));
+
+                // Additionally, append the custom attributes associated with the
+                // PropertySpec, if any.
+                if (property.Attributes != null)
+                    attrs.AddRange(property.Attributes);
+
+                Attribute[] attrArray = (Attribute[])attrs.ToArray(typeof(Attribute));
+
+                // Create a new property descriptor for the property item, and add
+                // it to the list.
+                PropertySpecDescriptor pd = new PropertySpecDescriptor(property,
+                    this, property.Name, attrArray);
+                props.Add(pd);
+            }
+
+            // Convert the list of PropertyDescriptors to a collection that the
+            // ICustomTypeDescriptor can use, and return it.
+            PropertyDescriptor[] propArray = (PropertyDescriptor[])props.ToArray(
+                typeof(PropertyDescriptor));
+            return new PropertyDescriptorCollection(propArray);
+        }
+
+        object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) {
+            return this;
+        }
+        #endregion
+    }
+
+    /// <summary>
+    /// An extension of PropertyBag that manages a table of property values, in
+    /// addition to firing events when property values are requested or set.
+    /// </summary>
+    public class PropertyTable : PropertyBag {
+        private Hashtable propValues;
+
+        /// <summary>
+        /// Initializes a new instance of the PropertyTable class.
+        /// </summary>
+        public PropertyTable() {
+            propValues = new Hashtable();
+        }
+
+        /// <summary>
+        /// Gets or sets the value of the property with the specified name.
+        /// <p>In C#, this property is the indexer of the PropertyTable class.</p>
+        /// </summary>
+        public object this[string key] {
+            get { return propValues[key]; }
+            set { propValues[key] = value; }
+        }
+
+        /// <summary>
+        /// This member overrides PropertyBag.OnGetValue.
+        /// </summary>
+        protected override void OnGetValue(PropertySpecEventArgs e) {
+            e.Value = propValues[e.Property.Name];
+            base.OnGetValue(e);
+        }
+
+        /// <summary>
+        /// This member overrides PropertyBag.OnSetValue.
+        /// </summary>
+        protected override void OnSetValue(PropertySpecEventArgs e) {
+            propValues[e.Property.Name] = e.Value;
+            base.OnSetValue(e);
+        }
+    }
+}
diff --git a/Source/Plugins/PropertiesDock/Info/IMapElementInfo.cs b/Source/Plugins/PropertiesDock/Info/IMapElementInfo.cs
index c7e6bd423d2dff9fb5db7ad8398dbe3a53e0bba8..624ca45a88842dd3c9e22de3ab60a52ca7b14a56 100644
--- a/Source/Plugins/PropertiesDock/Info/IMapElementInfo.cs
+++ b/Source/Plugins/PropertiesDock/Info/IMapElementInfo.cs
@@ -6,5 +6,7 @@ using System.Text;
 namespace CodeImp.DoomBuilder.PropertiesDock {
     interface IMapElementInfo {
         void ApplyChanges();
+        void AddCustomProperty(string name, Type type);
+        void RemoveCustomProperty(string name);
     }
 }
diff --git a/Source/Plugins/PropertiesDock/Info/ThingInfo.cs b/Source/Plugins/PropertiesDock/Info/ThingInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f51c1a08586fdcb4fa4fa388ad04efcbaed116fc
--- /dev/null
+++ b/Source/Plugins/PropertiesDock/Info/ThingInfo.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.ComponentModel;
+using CodeImp.DoomBuilder.Map;
+using System.Globalization;
+
+namespace CodeImp.DoomBuilder.PropertiesDock {
+    public class ThingInfo : IMapElementInfo {
+
+        [TypeConverterAttribute(typeof(ThingTypeConverter)), CategoryAttribute("General"), DefaultValueAttribute(0)]
+        public int Type { get { return type; } set { type = value; } }
+        private int type;
+
+        private Thing thing;
+
+        public ThingInfo(Thing t) {
+            thing = t;
+            type = t.Type;
+        }
+
+        public void ApplyChanges() {
+
+        }
+
+        public void AddCustomProperty(string name, Type type) {
+            //properties.Add(new PropertySpec(name + ":", value.GetType(), "Custom properties:"));
+        }
+
+        public void RemoveCustomProperty(string name) {
+            /*string n = name.ToUpperInvariant().Trim();
+            foreach (PropertySpec ps in properties) {
+                string cn = ps.Name.ToUpperInvariant();
+                if (cn.IndexOf(n) == 0 && cn.Length == n.Length + 1) {
+                    properties.Remove(name);
+                    return;
+                }
+            }*/
+        }
+    }
+
+    public class ThingTypeConverter : TypeConverter {
+
+        public override bool CanConvertTo(ITypeDescriptorContext context, System.Type destinationType) {
+            if (destinationType == typeof(int))
+                return true;
+            return base.CanConvertTo(context, destinationType);
+        }
+
+        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, System.Type destinationType) {
+            if (destinationType == typeof(System.String) && value is int) {
+                int type = (int)value;
+                if (MapElementsData.ThingTypeDescriptions.ContainsKey(type)) {
+                    return type + " - " + MapElementsData.ThingTypeDescriptions[type];
+                }
+
+                return type + " - Unknown Thing";
+            }
+            return base.ConvertTo(context, culture, value, destinationType);
+        }
+
+        public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType) {
+            if (sourceType == typeof(string))
+                return true;
+            return base.CanConvertFrom(context, sourceType);
+        }
+
+        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
+            if (value is string) {
+                int type = 0;
+                if (!int.TryParse((string)value, out type)) {
+                    //throw new ArgumentException("'" + (string)value + "' is not a valid Thing type");
+                    General.ShowErrorMessage("'" + (string)value + "' is not a valid Thing type", System.Windows.Forms.MessageBoxButtons.OK);
+                }
+                return type;
+            }
+            return base.ConvertFrom(context, culture, value);
+        }
+    }
+}
diff --git a/Source/Plugins/PropertiesDock/Info/VertexInfo.cs b/Source/Plugins/PropertiesDock/Info/VertexInfo.cs
index e1165ed18909b5fc8f3a839578003d27235ba962..623facbe998c191a3776a77e2be0d0d97645dd60 100644
--- a/Source/Plugins/PropertiesDock/Info/VertexInfo.cs
+++ b/Source/Plugins/PropertiesDock/Info/VertexInfo.cs
@@ -6,29 +6,51 @@ using System.Text;
 using CodeImp.DoomBuilder.Map;
 
 namespace CodeImp.DoomBuilder.PropertiesDock {
-    public class VertexInfo : IMapElementInfo {
-        [CategoryAttribute("Position"), DefaultValueAttribute(0f)]
-        public float X { get { return x; } set { x = value; } }
-        private float x;
+    
+    public class VertexInfo : PropertyBag, IMapElementInfo {
+        /*[CategoryAttribute("Position"), DefaultValueAttribute(0f)]
+        public float X { get { return x; } set { x = value; } }*/
+        //private float x;
 
-        [CategoryAttribute("Position"), DefaultValueAttribute(0f)]
-        public float Y { get { return y; } set { y = value; } }
-        private float y;
+        /*[CategoryAttribute("Position"), DefaultValueAttribute(0f)]
+        public float Y { get { return y; } set { y = value; } }*/
+        //private float y;
+
+        //public PropertyBag Properties { get { return properties; } }
+        //private PropertyBag properties;
 
         private Vertex vertex;
 
-        public VertexInfo(Vertex v) {
+        public VertexInfo(Vertex v) : base() {
             vertex = v;
-            x = v.Position.x;
-            y = v.Position.y;
+            //x = v.Position.x;
+            //y = v.Position.y;
+            //properties = new PropertyBag();
+            properties.Add(new PropertySpec("X:", typeof(float), "Position:", null, v.Position.x));
+            properties.Add(new PropertySpec("Y:", typeof(float), "Position:", null, v.Position.y));
         }
 
         public void ApplyChanges() {
             float min = (float)General.Map.FormatInterface.MinCoordinate;
             float max = (float)General.Map.FormatInterface.MaxCoordinate;
-            vertex.Move(new CodeImp.DoomBuilder.Geometry.Vector2D(General.Clamp(x, min, max), General.Clamp(y, min, max)));
+            vertex.Move(new CodeImp.DoomBuilder.Geometry.Vector2D(General.Clamp((float)properties[0].Value, min, max), General.Clamp((float)properties[1].Value, min, max)));
             
             //todo: add custom fields support
         }
+
+        public void AddCustomProperty(string name, Type type) {
+            properties.Add(new PropertySpec(name + ":", type, "Custom properties:"));
+        }
+
+        public void RemoveCustomProperty(string name){
+            string n = name.ToUpperInvariant().Trim();
+            foreach (PropertySpec ps in properties) {
+                string cn = ps.Name.ToUpperInvariant();
+                if (cn.IndexOf(n) == 0 && cn.Length == n.Length + 1) {
+                    properties.Remove(name);
+                    return;
+                }
+            }
+        }
     }
 }
diff --git a/Source/Plugins/PropertiesDock/Properties/Resources.Designer.cs b/Source/Plugins/PropertiesDock/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6fc6cbb8aea22176d91b1f5821f7206a65124c6d
--- /dev/null
+++ b/Source/Plugins/PropertiesDock/Properties/Resources.Designer.cs
@@ -0,0 +1,77 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     Этот код создан программой.
+//     Исполняемая версия:2.0.50727.4927
+//
+//     Изменения в этом файле могут привести к неправильной работе и будут потеряны в случае
+//     повторной генерации кода.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace CodeImp.DoomBuilder.PropertiesDock.Properties {
+    using System;
+    
+    
+    /// <summary>
+    ///   Класс ресурса со строгой типизацией для поиска локализованных строк и т.д.
+    /// </summary>
+    // Этот класс создан автоматически классом StronglyTypedResourceBuilder
+    // с помощью такого средства, как ResGen или Visual Studio.
+    // Чтобы добавить или удалить член, измените файл .ResX и снова запустите ResGen
+    // с параметром /str или перестройте свой проект VS.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   Возвращает кэшированный экземпляр ResourceManager, использованный этим классом.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CodeImp.DoomBuilder.PropertiesDock.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   Перезаписывает свойство CurrentUICulture текущего потока для всех
+        ///   обращений к ресурсу с помощью этого класса ресурса со строгой типизацией.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+        
+        internal static System.Drawing.Bitmap Add {
+            get {
+                object obj = ResourceManager.GetObject("Add", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
+        internal static System.Drawing.Bitmap Remove {
+            get {
+                object obj = ResourceManager.GetObject("Remove", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+    }
+}
diff --git a/Source/Plugins/PropertiesDock/Properties/Resources.resx b/Source/Plugins/PropertiesDock/Properties/Resources.resx
new file mode 100644
index 0000000000000000000000000000000000000000..00a8973d709217fd1e0285dbb5cf2f7a44bdd040
--- /dev/null
+++ b/Source/Plugins/PropertiesDock/Properties/Resources.resx
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+  <data name="Add" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Add.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="Remove" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Remove.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+</root>
\ No newline at end of file
diff --git a/Source/Plugins/PropertiesDock/PropertiesDock.csproj b/Source/Plugins/PropertiesDock/PropertiesDock.csproj
index 34291d181fdd27c206b561d9bc77d65cc143e0fc..e88d90f6246e9a8011881fdf57f6679a7f92e9ac 100644
--- a/Source/Plugins/PropertiesDock/PropertiesDock.csproj
+++ b/Source/Plugins/PropertiesDock/PropertiesDock.csproj
@@ -50,10 +50,18 @@
     <Compile Include="Controls\PropertiesDocker.Designer.cs">
       <DependentUpon>PropertiesDocker.cs</DependentUpon>
     </Compile>
+    <Compile Include="Data\MapElementsData.cs" />
+    <Compile Include="Data\PropertyBag.cs" />
     <Compile Include="Data\VertexPosition.cs" />
     <Compile Include="Info\IMapElementInfo.cs" />
+    <Compile Include="Info\ThingInfo.cs" />
     <Compile Include="Info\VertexInfo.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DesignTime>True</DesignTime>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\Core\Builder.csproj">
@@ -66,6 +74,16 @@
     <EmbeddedResource Include="Controls\PropertiesDocker.resx">
       <DependentUpon>PropertiesDocker.cs</DependentUpon>
     </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+    </EmbeddedResource>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Resources\Add.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Resources\Remove.png" />
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
diff --git a/Source/Plugins/PropertiesDock/Resources/Add.png b/Source/Plugins/PropertiesDock/Resources/Add.png
new file mode 100644
index 0000000000000000000000000000000000000000..79f062aa94e5c5cff258bb52c2ff79fa1387b6c2
Binary files /dev/null and b/Source/Plugins/PropertiesDock/Resources/Add.png differ
diff --git a/Source/Plugins/PropertiesDock/Resources/Remove.png b/Source/Plugins/PropertiesDock/Resources/Remove.png
new file mode 100644
index 0000000000000000000000000000000000000000..9f5eda7d5d79583d6d7d07b53ddb94181ae3b861
Binary files /dev/null and b/Source/Plugins/PropertiesDock/Resources/Remove.png differ