Optical drive eject action now functional.
authorStephane Lenclud
Fri, 19 Aug 2016 17:12:54 +0200
changeset 243cc2251d065db
parent 242 0a956121c273
child 244 2e4d2558bb21
Optical drive eject action now functional.
Server/FormEditObject.cs
Server/FormMain.Designer.cs
Server/FormMain.cs
SharpLibEar/ActionOpticalDriveEject.cs
SharpLibEar/Event.cs
SharpLibEar/Manager.cs
SharpLibEar/Object.cs
SharpLibEar/PropertyComboBox.cs
SharpLibEar/SharpLibEar.csproj
SharpLibEar/packages.config
     1.1 --- a/Server/FormEditObject.cs	Thu Aug 18 20:14:30 2016 +0200
     1.2 +++ b/Server/FormEditObject.cs	Fri Aug 19 17:12:54 2016 +0200
     1.3 @@ -104,22 +104,25 @@
     1.4          private void FetchPropertiesValue(T aObject)
     1.5          {
     1.6              int ctrlIndex = 0;
     1.7 +            //For each of our properties
     1.8              foreach (PropertyInfo pi in aObject.GetType().GetProperties())
     1.9              {
    1.10 -                AttributeObjectProperty[] attributes =
    1.11 -                    ((AttributeObjectProperty[]) pi.GetCustomAttributes(typeof(AttributeObjectProperty), true));
    1.12 +                //Get our property attribute
    1.13 +                AttributeObjectProperty[] attributes = ((AttributeObjectProperty[]) pi.GetCustomAttributes(typeof(AttributeObjectProperty), true));
    1.14                  if (attributes.Length != 1)
    1.15                  {
    1.16 +                    //No attribute, skip this property then.
    1.17                      continue;
    1.18                  }
    1.19 -
    1.20                  AttributeObjectProperty attribute = attributes[0];
    1.21  
    1.22 +                //Check that we support this type of property
    1.23                  if (!IsPropertyTypeSupported(pi))
    1.24                  {
    1.25                      continue;
    1.26                  }
    1.27  
    1.28 +                //Now fetch our property value
    1.29                  GetPropertyValueFromControl(iTableLayoutPanel.Controls[ctrlIndex+1], pi, aObject); //+1 otherwise we get the label
    1.30  
    1.31                  ctrlIndex+=2; //Jump over the label too
    1.32 @@ -163,6 +166,14 @@
    1.33                  PropertyFile value = new PropertyFile {FullPath=ctrl.Text};
    1.34                  aInfo.SetValue(aObject, value);
    1.35              }
    1.36 +            else if (aInfo.PropertyType == typeof(PropertyComboBox))
    1.37 +            {
    1.38 +                ComboBox ctrl = (ComboBox)aControl;
    1.39 +                string currentItem = ctrl.SelectedItem.ToString();
    1.40 +                PropertyComboBox pcb = (PropertyComboBox)aInfo.GetValue(aObject);
    1.41 +                pcb.CurrentItem = currentItem;
    1.42 +            }
    1.43 +
    1.44              //TODO: add support for other types here
    1.45          }
    1.46  
    1.47 @@ -191,21 +202,21 @@
    1.48              {
    1.49                  //Enum properties are using combo box
    1.50                  ComboBox ctrl = new ComboBox();
    1.51 -                ctrl.AutoSize = true;                
    1.52 -                ctrl.Sorted = true;                
    1.53 +                ctrl.AutoSize = true;
    1.54 +                ctrl.Sorted = true;
    1.55                  ctrl.DropDownStyle = ComboBoxStyle.DropDownList;
    1.56                  //Data source is fine but it gives us duplicate entries for duplicated enum values
    1.57                  //ctrl.DataSource = Enum.GetValues(aInfo.PropertyType);
    1.58  
    1.59                  //Therefore we need to explicitly create our items
    1.60 -                Size cbSize = new Size(0,0);
    1.61 +                Size cbSize = new Size(0, 0);
    1.62                  foreach (string name in aInfo.PropertyType.GetEnumNames())
    1.63                  {
    1.64                      ctrl.Items.Add(name.ToString());
    1.65                      Graphics g = this.CreateGraphics();
    1.66                      //Since combobox autosize would not work we need to get measure text ourselves
    1.67 -                    SizeF size=g.MeasureString(name.ToString(), ctrl.Font);
    1.68 -                    cbSize.Width = Math.Max(cbSize.Width,(int)size.Width);
    1.69 +                    SizeF size = g.MeasureString(name.ToString(), ctrl.Font);
    1.70 +                    cbSize.Width = Math.Max(cbSize.Width, (int)size.Width);
    1.71                      cbSize.Height = Math.Max(cbSize.Height, (int)size.Height);
    1.72                  }
    1.73  
    1.74 @@ -225,7 +236,7 @@
    1.75                  CheckBox ctrl = new CheckBox();
    1.76                  ctrl.AutoSize = true;
    1.77                  ctrl.Text = aAttribute.Description;
    1.78 -                ctrl.Checked = (bool)aInfo.GetValue(aObject);                
    1.79 +                ctrl.Checked = (bool)aInfo.GetValue(aObject);
    1.80                  return ctrl;
    1.81              }
    1.82              else if (aInfo.PropertyType == typeof(string))
    1.83 @@ -263,8 +274,27 @@
    1.84  
    1.85                  return ctrl;
    1.86              }
    1.87 +            else if (aInfo.PropertyType == typeof(PropertyComboBox))
    1.88 +            {
    1.89 +                //ComboBox property
    1.90 +                ComboBox ctrl = new ComboBox();
    1.91 +                ctrl.AutoSize = true;
    1.92 +                ctrl.Sorted = true;
    1.93 +                ctrl.DropDownStyle = ComboBoxStyle.DropDownList;
    1.94 +                //Data source is such a pain to set the current item
    1.95 +                //ctrl.DataSource = ((PropertyComboBox)aInfo.GetValue(aObject)).Items;                
    1.96 +
    1.97 +                PropertyComboBox pcb = ((PropertyComboBox)aInfo.GetValue(aObject));
    1.98 +                foreach (string item in pcb.Items)
    1.99 +                {
   1.100 +                    ctrl.Items.Add(item);
   1.101 +                }
   1.102 +
   1.103 +                ctrl.SelectedItem = ((PropertyComboBox)aInfo.GetValue(aObject)).CurrentItem;
   1.104 +                //
   1.105 +                return ctrl;
   1.106 +            }
   1.107              //TODO: add support for other control type here
   1.108 -
   1.109              return null;
   1.110          }
   1.111  
   1.112 @@ -294,6 +324,11 @@
   1.113              {
   1.114                  return true;
   1.115              }
   1.116 +            else if (aInfo.PropertyType == typeof(PropertyComboBox))
   1.117 +            {
   1.118 +                return true;
   1.119 +            }
   1.120 +
   1.121              //TODO: add support for other type here
   1.122  
   1.123              return false;
     2.1 --- a/Server/FormMain.Designer.cs	Thu Aug 18 20:14:30 2016 +0200
     2.2 +++ b/Server/FormMain.Designer.cs	Fri Aug 19 17:12:54 2016 +0200
     2.3 @@ -247,7 +247,7 @@
     2.4              this.toolStripStatusLabelSpring,
     2.5              this.toolStripStatusLabelPower,
     2.6              this.toolStripStatusLabelFps});
     2.7 -            this.statusStrip.Location = new System.Drawing.Point(0, 539);
     2.8 +            this.statusStrip.Location = new System.Drawing.Point(0, 540);
     2.9              this.statusStrip.Name = "statusStrip";
    2.10              this.statusStrip.RenderMode = System.Windows.Forms.ToolStripRenderMode.Professional;
    2.11              this.statusStrip.Size = new System.Drawing.Size(784, 22);
    2.12 @@ -1124,7 +1124,7 @@
    2.13              // buttonEventEdit
    2.14              // 
    2.15              this.buttonEventEdit.Enabled = false;
    2.16 -            this.buttonEventEdit.Location = new System.Drawing.Point(6, 35);
    2.17 +            this.buttonEventEdit.Location = new System.Drawing.Point(6, 187);
    2.18              this.buttonEventEdit.Name = "buttonEventEdit";
    2.19              this.buttonEventEdit.Size = new System.Drawing.Size(96, 23);
    2.20              this.buttonEventEdit.TabIndex = 29;
    2.21 @@ -1135,7 +1135,7 @@
    2.22              // buttonEventDelete
    2.23              // 
    2.24              this.buttonEventDelete.Enabled = false;
    2.25 -            this.buttonEventDelete.Location = new System.Drawing.Point(6, 64);
    2.26 +            this.buttonEventDelete.Location = new System.Drawing.Point(6, 216);
    2.27              this.buttonEventDelete.Name = "buttonEventDelete";
    2.28              this.buttonEventDelete.Size = new System.Drawing.Size(96, 23);
    2.29              this.buttonEventDelete.TabIndex = 28;
    2.30 @@ -1145,7 +1145,7 @@
    2.31              // 
    2.32              // buttonEventAdd
    2.33              // 
    2.34 -            this.buttonEventAdd.Location = new System.Drawing.Point(6, 6);
    2.35 +            this.buttonEventAdd.Location = new System.Drawing.Point(6, 158);
    2.36              this.buttonEventAdd.Name = "buttonEventAdd";
    2.37              this.buttonEventAdd.Size = new System.Drawing.Size(96, 23);
    2.38              this.buttonEventAdd.TabIndex = 27;
    2.39 @@ -1156,7 +1156,7 @@
    2.40              // buttonEventTest
    2.41              // 
    2.42              this.buttonEventTest.Enabled = false;
    2.43 -            this.buttonEventTest.Location = new System.Drawing.Point(6, 93);
    2.44 +            this.buttonEventTest.Location = new System.Drawing.Point(6, 245);
    2.45              this.buttonEventTest.Name = "buttonEventTest";
    2.46              this.buttonEventTest.Size = new System.Drawing.Size(96, 23);
    2.47              this.buttonEventTest.TabIndex = 26;
    2.48 @@ -1167,7 +1167,7 @@
    2.49              // buttonActionEdit
    2.50              // 
    2.51              this.buttonActionEdit.Enabled = false;
    2.52 -            this.buttonActionEdit.Location = new System.Drawing.Point(6, 190);
    2.53 +            this.buttonActionEdit.Location = new System.Drawing.Point(6, 35);
    2.54              this.buttonActionEdit.Name = "buttonActionEdit";
    2.55              this.buttonActionEdit.Size = new System.Drawing.Size(96, 23);
    2.56              this.buttonActionEdit.TabIndex = 25;
    2.57 @@ -1200,7 +1200,7 @@
    2.58              // buttonActionTest
    2.59              // 
    2.60              this.buttonActionTest.Enabled = false;
    2.61 -            this.buttonActionTest.Location = new System.Drawing.Point(6, 248);
    2.62 +            this.buttonActionTest.Location = new System.Drawing.Point(6, 93);
    2.63              this.buttonActionTest.Name = "buttonActionTest";
    2.64              this.buttonActionTest.Size = new System.Drawing.Size(96, 23);
    2.65              this.buttonActionTest.TabIndex = 22;
    2.66 @@ -1211,7 +1211,7 @@
    2.67              // buttonActionDelete
    2.68              // 
    2.69              this.buttonActionDelete.Enabled = false;
    2.70 -            this.buttonActionDelete.Location = new System.Drawing.Point(6, 219);
    2.71 +            this.buttonActionDelete.Location = new System.Drawing.Point(6, 64);
    2.72              this.buttonActionDelete.Name = "buttonActionDelete";
    2.73              this.buttonActionDelete.Size = new System.Drawing.Size(96, 23);
    2.74              this.buttonActionDelete.TabIndex = 21;
    2.75 @@ -1222,9 +1222,9 @@
    2.76              // buttonActionAdd
    2.77              // 
    2.78              this.buttonActionAdd.Enabled = false;
    2.79 -            this.buttonActionAdd.Location = new System.Drawing.Point(6, 157);
    2.80 +            this.buttonActionAdd.Location = new System.Drawing.Point(6, 6);
    2.81              this.buttonActionAdd.Name = "buttonActionAdd";
    2.82 -            this.buttonActionAdd.Size = new System.Drawing.Size(96, 27);
    2.83 +            this.buttonActionAdd.Size = new System.Drawing.Size(96, 23);
    2.84              this.buttonActionAdd.TabIndex = 20;
    2.85              this.buttonActionAdd.Text = "Add Action";
    2.86              this.buttonActionAdd.UseVisualStyleBackColor = true;
    2.87 @@ -1370,7 +1370,7 @@
    2.88              // 
    2.89              this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
    2.90              this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    2.91 -            this.ClientSize = new System.Drawing.Size(784, 561);
    2.92 +            this.ClientSize = new System.Drawing.Size(784, 562);
    2.93              this.Controls.Add(this.labelFontHeight);
    2.94              this.Controls.Add(this.labelFontWidth);
    2.95              this.Controls.Add(this.labelWarning);
     3.1 --- a/Server/FormMain.cs	Thu Aug 18 20:14:30 2016 +0200
     3.2 +++ b/Server/FormMain.cs	Fri Aug 19 17:12:54 2016 +0200
     3.3 @@ -151,7 +151,7 @@
     3.4                  // We loaded events and actions from our settings
     3.5                  // Internalizer apparently skips constructor so we need to initialize it here
     3.6                  // Though I reckon that should only be needed when loading an empty EAR manager I guess.
     3.7 -                Properties.Settings.Default.EarManager.Init();
     3.8 +                Properties.Settings.Default.EarManager.Construct();
     3.9              }
    3.10              iSkipFrameRendering = false;
    3.11              iClosing = false;
     4.1 --- a/SharpLibEar/ActionOpticalDriveEject.cs	Thu Aug 18 20:14:30 2016 +0200
     4.2 +++ b/SharpLibEar/ActionOpticalDriveEject.cs	Fri Aug 19 17:12:54 2016 +0200
     4.3 @@ -1,12 +1,308 @@
     4.4 -using System;
     4.5 +using Microsoft.Win32.SafeHandles;
     4.6 +using System;
     4.7  using System.Collections.Generic;
     4.8 +using System.Diagnostics;
     4.9 +using System.IO;
    4.10  using System.Linq;
    4.11 +using System.Runtime.Serialization;
    4.12  using System.Text;
    4.13  using System.Threading.Tasks;
    4.14 +using SharpLib.Win32;
    4.15 +using System.ComponentModel;
    4.16 +using System.Runtime.InteropServices;
    4.17  
    4.18  namespace SharpLib.Ear
    4.19  {
    4.20 -    class ActionOpticalDriveEject
    4.21 +    [DataContract]
    4.22 +    [AttributeObject(Id = "Action.OpticalDrive.Eject", Name = "Eject", Description = "Eject media from an optical drive.")]
    4.23 +    public class ActionOpticalDriveEject : Action
    4.24      {
    4.25 +        [DataMember]
    4.26 +        [AttributeObjectProperty
    4.27 +            (
    4.28 +            Id = "Action.OpticalDrive.Eject.Drive",
    4.29 +            Name = "Drive to eject",
    4.30 +            Description = "Select the drive you want to eject."
    4.31 +            )
    4.32 +        ]
    4.33 +        public PropertyComboBox Drive { get; set; } = new PropertyComboBox();
    4.34 +
    4.35 +
    4.36 +        protected override void DoConstruct()
    4.37 +        {
    4.38 +            base.DoConstruct();
    4.39 +            PopulateOpticalDrives();
    4.40 +            CheckCurrentItem();
    4.41 +        }
    4.42 +
    4.43 +
    4.44 +        public override string Brief()
    4.45 +        {
    4.46 +            return Name + " " + Drive.CurrentItem ;
    4.47 +        }
    4.48 +
    4.49 +        public override bool IsValid()
    4.50 +        {   
    4.51 +            //This object is valid if our current item is contained in our drive list
    4.52 +            return Drive.Items.Contains(Drive.CurrentItem);
    4.53 +        }
    4.54 +
    4.55 +        protected override void DoExecute()
    4.56 +        {
    4.57 +            DriveEject(Drive.CurrentItem);
    4.58 +        }
    4.59 +
    4.60 +
    4.61 +        private void CheckCurrentItem()
    4.62 +        {
    4.63 +            if (!Drive.Items.Contains(Drive.CurrentItem) && Drive.Items.Count>0)
    4.64 +            {
    4.65 +                //Current item unknown, reset it then
    4.66 +                Drive.CurrentItem = Drive.Items[0];
    4.67 +            }
    4.68 +        }
    4.69 +
    4.70 +        /// <summary>
    4.71 +        /// 
    4.72 +        /// </summary>
    4.73 +        private void PopulateOpticalDrives()
    4.74 +        {
    4.75 +            //Reset our list of drives
    4.76 +            Drive.Items = new List<string>();
    4.77 +            //Go through each drives on our system and collected the optical ones in our list
    4.78 +            DriveInfo[] allDrives = DriveInfo.GetDrives();
    4.79 +            foreach (DriveInfo d in allDrives)
    4.80 +            {
    4.81 +                Debug.WriteLine("Drive " + d.Name);
    4.82 +                Debug.WriteLine("  Drive type: {0}", d.DriveType);
    4.83 +
    4.84 +                if (d.DriveType == DriveType.CDRom)
    4.85 +                {
    4.86 +                    //This is an optical drive, add it now
    4.87 +                    Drive.Items.Add(d.Name.Substring(0, 2));
    4.88 +                }
    4.89 +            }
    4.90 +        }
    4.91 +
    4.92 +
    4.93 +        /// <summary>
    4.94 +        /// 
    4.95 +        /// </summary>
    4.96 +        /// <param name="aPrefix"></param>
    4.97 +        static private void CheckLastError(string aPrefix)
    4.98 +        {
    4.99 +            string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
   4.100 +            Debug.WriteLine(aPrefix + Marshal.GetLastWin32Error().ToString() + ": " + errorMessage);
   4.101 +        }
   4.102 +
   4.103 +        /// <summary>
   4.104 +        /// 
   4.105 +        /// </summary>
   4.106 +        /// <param name="data"></param>
   4.107 +        /// <returns></returns>
   4.108 +        static private IntPtr MarshalToPointer(object data)
   4.109 +        {
   4.110 +            IntPtr buf = Marshal.AllocHGlobal(
   4.111 +                Marshal.SizeOf(data));
   4.112 +            Marshal.StructureToPtr(data,
   4.113 +                buf, false);
   4.114 +            return buf;
   4.115 +        }
   4.116 +
   4.117 +        /// <summary>
   4.118 +        /// 
   4.119 +        /// </summary>
   4.120 +        /// <returns></returns>
   4.121 +        static private SafeFileHandle OpenVolume(string aDriveName)
   4.122 +        {
   4.123 +            return Function.CreateFile("\\\\.\\" + aDriveName,
   4.124 +                               SharpLib.Win32.FileAccess.GENERIC_READ,
   4.125 +                               SharpLib.Win32.FileShare.FILE_SHARE_READ | SharpLib.Win32.FileShare.FILE_SHARE_WRITE,
   4.126 +                               IntPtr.Zero,
   4.127 +                               CreationDisposition.OPEN_EXISTING,
   4.128 +                               0,
   4.129 +                               IntPtr.Zero);
   4.130 +        }
   4.131 +
   4.132 +        /// <summary>
   4.133 +        /// 
   4.134 +        /// </summary>
   4.135 +        /// <param name="aVolume"></param>
   4.136 +        /// <returns></returns>
   4.137 +        static private bool LockVolume(SafeFileHandle aVolume)
   4.138 +        {
   4.139 +            //Hope that's doing what I think it does
   4.140 +            IntPtr dwBytesReturned = new IntPtr();
   4.141 +            //Should not be needed but I'm not sure how to pass NULL in there.
   4.142 +            OVERLAPPED overlapped = new OVERLAPPED();
   4.143 +
   4.144 +            int tries = 0;
   4.145 +            const int KMaxTries = 100;
   4.146 +            const int KSleepTime = 10;
   4.147 +            bool success = false;
   4.148 +
   4.149 +            while (!success && tries < KMaxTries)
   4.150 +            {
   4.151 +                success = Function.DeviceIoControl(aVolume, Const.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   4.152 +                System.Threading.Thread.Sleep(KSleepTime);
   4.153 +                tries++;
   4.154 +            }
   4.155 +
   4.156 +            CheckLastError("Lock volume: ");
   4.157 +
   4.158 +            return success;
   4.159 +        }
   4.160 +
   4.161 +        /// <summary>
   4.162 +        /// 
   4.163 +        /// </summary>
   4.164 +        /// <param name="aVolume"></param>
   4.165 +        /// <returns></returns>
   4.166 +        static private bool DismountVolume(SafeFileHandle aVolume)
   4.167 +        {
   4.168 +            //Hope that's doing what I think it does
   4.169 +            IntPtr dwBytesReturned = new IntPtr();
   4.170 +            //Should not be needed but I'm not sure how to pass NULL in there.
   4.171 +            OVERLAPPED overlapped = new OVERLAPPED();
   4.172 +
   4.173 +            bool res = Function.DeviceIoControl(aVolume, Const.FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   4.174 +            CheckLastError("Dismount volume: ");
   4.175 +            return res;
   4.176 +        }
   4.177 +
   4.178 +
   4.179 +
   4.180 +        /// <summary>
   4.181 +        /// 
   4.182 +        /// </summary>
   4.183 +        /// <param name="aVolume"></param>
   4.184 +        /// <param name="aPreventRemoval"></param>
   4.185 +        /// <returns></returns>
   4.186 +        static private bool PreventRemovalOfVolume(SafeFileHandle aVolume, bool aPreventRemoval)
   4.187 +        {
   4.188 +            //Hope that's doing what I think it does
   4.189 +            IntPtr dwBytesReturned = new IntPtr();
   4.190 +            //Should not be needed but I'm not sure how to pass NULL in there.
   4.191 +            OVERLAPPED overlapped = new OVERLAPPED();
   4.192 +            //
   4.193 +            PREVENT_MEDIA_REMOVAL preventMediaRemoval = new PREVENT_MEDIA_REMOVAL();
   4.194 +            preventMediaRemoval.PreventMediaRemoval = Convert.ToByte(aPreventRemoval);
   4.195 +            IntPtr preventMediaRemovalParam = MarshalToPointer(preventMediaRemoval);
   4.196 +
   4.197 +            bool result = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_MEDIA_REMOVAL, preventMediaRemovalParam, Convert.ToUInt32(Marshal.SizeOf(preventMediaRemoval)), IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   4.198 +            CheckLastError("Media removal: ");
   4.199 +            Marshal.FreeHGlobal(preventMediaRemovalParam);
   4.200 +
   4.201 +            return result;
   4.202 +        }
   4.203 +
   4.204 +        /// <summary>
   4.205 +        /// Eject optical drive media opening the tray if any.
   4.206 +        /// </summary>
   4.207 +        /// <param name="aVolume"></param>
   4.208 +        /// <returns></returns>
   4.209 +        static private bool MediaEject(SafeFileHandle aVolume)
   4.210 +        {
   4.211 +            //Hope that's doing what I think it does
   4.212 +            IntPtr dwBytesReturned = new IntPtr();
   4.213 +            //Should not be needed but I'm not sure how to pass NULL in there.
   4.214 +            OVERLAPPED overlapped = new OVERLAPPED();
   4.215 +
   4.216 +            bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   4.217 +            CheckLastError("Media eject: ");
   4.218 +            return res;
   4.219 +        }
   4.220 +
   4.221 +        /// <summary>
   4.222 +        /// Close an optical drive tray.
   4.223 +        /// </summary>
   4.224 +        /// <param name="aVolume"></param>
   4.225 +        /// <returns></returns>
   4.226 +        static private bool MediaLoad(SafeFileHandle aVolume)
   4.227 +        {
   4.228 +            //Hope that's doing what I think it does
   4.229 +            IntPtr dwBytesReturned = new IntPtr();
   4.230 +            //Should not be needed but I'm not sure how to pass NULL in there.
   4.231 +            OVERLAPPED overlapped = new OVERLAPPED();
   4.232 +
   4.233 +            bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_LOAD_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   4.234 +            CheckLastError("Media load: ");
   4.235 +            return res;
   4.236 +        }
   4.237 +
   4.238 +        /// <summary>
   4.239 +        /// 
   4.240 +        /// </summary>
   4.241 +        /// <param name="aVolume"></param>
   4.242 +        /// <returns></returns>
   4.243 +        static private bool StorageCheckVerify(SafeFileHandle aVolume)
   4.244 +        {
   4.245 +            //Hope that's doing what I think it does
   4.246 +            IntPtr dwBytesReturned = new IntPtr();
   4.247 +            //Should not be needed but I'm not sure how to pass NULL in there.
   4.248 +            OVERLAPPED overlapped = new OVERLAPPED();
   4.249 +
   4.250 +            bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_CHECK_VERIFY2, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   4.251 +
   4.252 +            CheckLastError("Check verify: ");
   4.253 +
   4.254 +            return res;
   4.255 +        }
   4.256 +
   4.257 +
   4.258 +        /// <summary>
   4.259 +        /// Perform media ejection.
   4.260 +        /// </summary>
   4.261 +        static private void DriveEject(string aDrive)
   4.262 +        {
   4.263 +            string drive = aDrive;
   4.264 +            if (drive.Length != 2)
   4.265 +            {
   4.266 +                //Not a proper drive spec.
   4.267 +                //Probably 'None' selected.
   4.268 +                return;
   4.269 +            }
   4.270 +
   4.271 +            SafeFileHandle handle = OpenVolume(drive);
   4.272 +            if (handle.IsInvalid)
   4.273 +            {
   4.274 +                CheckLastError("ERROR: Failed to open volume: ");
   4.275 +                return;
   4.276 +            }
   4.277 +
   4.278 +            if (LockVolume(handle) && DismountVolume(handle))
   4.279 +            {
   4.280 +                Debug.WriteLine("Volume was dismounted.");
   4.281 +
   4.282 +                if (PreventRemovalOfVolume(handle, false))
   4.283 +                {
   4.284 +                    //StorageCheckVerify(handle);
   4.285 +
   4.286 +                    DateTime before;
   4.287 +                    before = DateTime.Now;
   4.288 +                    bool ejectSuccess = MediaEject(handle);
   4.289 +                    double ms = (DateTime.Now - before).TotalMilliseconds;
   4.290 +
   4.291 +                    //We assume that if it take more than a certain time to for eject to execute it means we actually ejected.
   4.292 +                    //If our eject completes too rapidly we assume the tray is already open and we will try to close it. 
   4.293 +                    if (ejectSuccess && ms > 100)
   4.294 +                    {
   4.295 +                        Debug.WriteLine("Media was ejected");
   4.296 +                    }
   4.297 +                    else if (MediaLoad(handle))
   4.298 +                    {
   4.299 +                        Debug.WriteLine("Media was loaded");
   4.300 +                    }
   4.301 +                }
   4.302 +            }
   4.303 +            else
   4.304 +            {
   4.305 +                Debug.WriteLine("Volume lock or dismount failed.");
   4.306 +            }
   4.307 +
   4.308 +            //This is needed to make sure we can open the volume next time around
   4.309 +            handle.Dispose();
   4.310 +        }
   4.311 +
   4.312      }
   4.313  }
     5.1 --- a/SharpLibEar/Event.cs	Thu Aug 18 20:14:30 2016 +0200
     5.2 +++ b/SharpLibEar/Event.cs	Fri Aug 19 17:12:54 2016 +0200
     5.3 @@ -18,16 +18,22 @@
     5.4                  Description = "When enabled an event instance can be triggered."
     5.5              )
     5.6          ]
     5.7 -        public bool Enabled { get; set; }
     5.8 +        public bool Enabled { get; set; } = true;
     5.9  
    5.10          [DataMember]
    5.11          public List<Action> Actions = new List<Action>();
    5.12  
    5.13  
    5.14 +        protected override void DoConstruct()
    5.15 +        {
    5.16 +            base.DoConstruct();
    5.17  
    5.18 -        protected Event()
    5.19 -        {
    5.20 -            Enabled = true;
    5.21 +            // TODO: Construct properties too
    5.22 +            foreach (Action a in Actions)
    5.23 +            {
    5.24 +                a.Construct();
    5.25 +            }
    5.26 +
    5.27          }
    5.28  
    5.29  
     6.1 --- a/SharpLibEar/Manager.cs	Thu Aug 18 20:14:30 2016 +0200
     6.2 +++ b/SharpLibEar/Manager.cs	Fri Aug 19 17:12:54 2016 +0200
     6.3 @@ -15,8 +15,7 @@
     6.4      /// Users can implement their own events and actions.
     6.5      /// </summary>
     6.6      [DataContract]
     6.7 -    [KnownType("DerivedTypes")]
     6.8 -    public class Manager
     6.9 +    public class Manager: Object
    6.10      {
    6.11          /// <summary>
    6.12          /// Our events instances.
    6.13 @@ -24,23 +23,24 @@
    6.14          [DataMember]
    6.15          public List<Event> Events;
    6.16  
    6.17 -        /// <summary>
    6.18 -        /// Constructor
    6.19 -        /// </summary>
    6.20 -        public Manager()
    6.21 -        {
    6.22 -            Init();
    6.23 -        }
    6.24  
    6.25          /// <summary>
    6.26          /// Executes after internalization took place.
    6.27          /// </summary>
    6.28 -        public void Init()
    6.29 +        protected override void DoConstruct()
    6.30          {
    6.31 +            base.DoConstruct();
    6.32 +
    6.33              if (Events == null)
    6.34              {
    6.35                  Events = new List<Event>();
    6.36              }
    6.37 +
    6.38 +            // TODO: Object properties should be constructed too
    6.39 +            foreach (Event e in Events)
    6.40 +            {
    6.41 +                e.Construct();
    6.42 +            }
    6.43              
    6.44          }
    6.45  
    6.46 @@ -86,15 +86,5 @@
    6.47                  }
    6.48              }
    6.49          }
    6.50 -
    6.51 -        /// <summary>
    6.52 -        /// Allow extending our data contract.
    6.53 -        /// See KnownType above.
    6.54 -        /// </summary>
    6.55 -        /// <returns></returns>
    6.56 -        private static IEnumerable<Type> DerivedTypes()
    6.57 -        {
    6.58 -            return SharpLib.Utils.Reflection.GetDerivedTypes<Manager>();
    6.59 -        }
    6.60      }
    6.61  }
    6.62 \ No newline at end of file
     7.1 --- a/SharpLibEar/Object.cs	Thu Aug 18 20:14:30 2016 +0200
     7.2 +++ b/SharpLibEar/Object.cs	Fri Aug 19 17:12:54 2016 +0200
     7.3 @@ -21,6 +21,33 @@
     7.4      [KnownType("DerivedTypes")]
     7.5      public abstract class Object: IComparable
     7.6      {
     7.7 +        private bool iConstructed = false;
     7.8 +
     7.9 +        public Object()
    7.10 +        {
    7.11 +            Construct();
    7.12 +        }
    7.13 +
    7.14 +        /// <summary>
    7.15 +        /// Needed as our constructor is not called following internalization.
    7.16 +        /// </summary>
    7.17 +        public void Construct()
    7.18 +        {
    7.19 +            if (!iConstructed)
    7.20 +            {
    7.21 +                DoConstruct();
    7.22 +                iConstructed = true;
    7.23 +            }
    7.24 +        }
    7.25 +
    7.26 +        /// <summary>
    7.27 +        /// 
    7.28 +        /// </summary>
    7.29 +        protected virtual void DoConstruct()
    7.30 +        {
    7.31 +
    7.32 +        }
    7.33 +
    7.34          /// <summary>
    7.35          /// Static object name.
    7.36          /// </summary>
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/SharpLibEar/PropertyComboBox.cs	Fri Aug 19 17:12:54 2016 +0200
     8.3 @@ -0,0 +1,22 @@
     8.4 +using System;
     8.5 +using System.Collections.Generic;
     8.6 +using System.Linq;
     8.7 +using System.Runtime.Serialization;
     8.8 +using System.Text;
     8.9 +using System.Threading.Tasks;
    8.10 +
    8.11 +namespace SharpLib.Ear
    8.12 +{
    8.13 +    /// <summary>
    8.14 +    /// ComboBox property
    8.15 +    /// </summary>
    8.16 +    [DataContract]
    8.17 +    [AttributeObject(Id = "Property.ComboBox", Name = "ComboBox", Description = "ComboBox property.")]
    8.18 +    public class PropertyComboBox : Object
    8.19 +    {
    8.20 +        public IList<string> Items { get; set; } = new List<string>();
    8.21 +
    8.22 +        [DataMember]
    8.23 +        public string CurrentItem { get; set; }
    8.24 +    }
    8.25 +}
     9.1 --- a/SharpLibEar/SharpLibEar.csproj	Thu Aug 18 20:14:30 2016 +0200
     9.2 +++ b/SharpLibEar/SharpLibEar.csproj	Fri Aug 19 17:12:54 2016 +0200
     9.3 @@ -48,6 +48,10 @@
     9.4      <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
     9.5    </PropertyGroup>
     9.6    <ItemGroup>
     9.7 +    <Reference Include="SharpLibWin32, Version=1.0.0.0, Culture=neutral, processorArchitecture=x86">
     9.8 +      <HintPath>..\packages\SharpLibWin32.0.0.9\lib\net20\SharpLibWin32.dll</HintPath>
     9.9 +      <Private>True</Private>
    9.10 +    </Reference>
    9.11      <Reference Include="System" />
    9.12      <Reference Include="System.Core" />
    9.13      <Reference Include="System.Runtime.Serialization" />
    9.14 @@ -73,6 +77,7 @@
    9.15      <Compile Include="Manager.cs" />
    9.16      <Compile Include="Object.cs" />
    9.17      <Compile Include="Properties\AssemblyInfo.cs" />
    9.18 +    <Compile Include="PropertyComboBox.cs" />
    9.19      <Compile Include="PropertyFile.cs" />
    9.20    </ItemGroup>
    9.21    <ItemGroup>
    9.22 @@ -81,6 +86,9 @@
    9.23        <Name>SharpLibUtils</Name>
    9.24      </ProjectReference>
    9.25    </ItemGroup>
    9.26 +  <ItemGroup>
    9.27 +    <None Include="packages.config" />
    9.28 +  </ItemGroup>
    9.29    <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
    9.30    <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
    9.31         Other similar extension points exist, see Microsoft.Common.targets.
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/SharpLibEar/packages.config	Fri Aug 19 17:12:54 2016 +0200
    10.3 @@ -0,0 +1,4 @@
    10.4 +<?xml version="1.0" encoding="utf-8"?>
    10.5 +<packages>
    10.6 +  <package id="SharpLibWin32" version="0.0.9" targetFramework="net46" />
    10.7 +</packages>
    10.8 \ No newline at end of file