Server/FormEditObject.cs
author StephaneLenclud
Wed, 31 Aug 2016 17:28:30 +0200
changeset 264 4a08e1b7ba64
parent 250 b2121d03f6f0
child 266 b11d7ebbdc7f
permissions -rw-r--r--
EAR: Actions now support multiple iterations.
     1 using System;
     2 using System.Collections.Generic;
     3 using System.ComponentModel;
     4 using System.Data;
     5 using System.Diagnostics;
     6 using System.Drawing;
     7 using System.Linq;
     8 using System.Text;
     9 using System.Threading.Tasks;
    10 using System.Windows.Forms;
    11 using SharpLib.Display;
    12 using SharpLib.Ear;
    13 using System.Reflection;
    14 using Microsoft.VisualBasic.CompilerServices;
    15 using SharpLib.Utils;
    16 using CodeProject.Dialog;
    17 using System.IO;
    18 
    19 namespace SharpDisplayManager
    20 {
    21     /// <summary>
    22     /// Object edit dialog form.
    23     /// </summary>
    24     public partial class FormEditObject<T> : Form where T: SharpLib.Ear.Object
    25     {
    26         public T Object = null;
    27 
    28         public FormEditObject()
    29         {
    30             InitializeComponent();
    31         }
    32 
    33         /// <summary>
    34         /// 
    35         /// </summary>
    36         /// <param name="sender"></param>
    37         /// <param name="e"></param>
    38         private void FormEditAction_Load(object sender, EventArgs e)
    39         {
    40             // Populate registered object types
    41             IEnumerable < Type > types = Reflection.GetConcreteClassesDerivedFrom<T>();
    42             foreach (Type type in types)
    43             {
    44                 ItemObjectType item = new ItemObjectType(type);
    45                 iComboBoxObjectType.Items.Add(item);
    46             }
    47 
    48             if (Object == null)
    49             {
    50                 // Creating new issue, select our first item
    51                 iComboBoxObjectType.SelectedIndex = 0;
    52             }
    53             else
    54             {
    55                 // Editing existing object
    56                 // Look up our item in our object type combobox
    57                 foreach (ItemObjectType item in iComboBoxObjectType.Items)
    58                 {
    59                     if (item.Type == Object.GetType())
    60                     {
    61                         iComboBoxObjectType.SelectedItem = item;
    62                     }
    63                 }
    64 
    65             }
    66         }
    67 
    68         /// <summary>
    69         /// 
    70         /// </summary>
    71         /// <param name="sender"></param>
    72         /// <param name="e"></param>
    73         private void buttonOk_Click(object sender, EventArgs e)
    74         {
    75             FetchPropertiesValue(Object);
    76             if (!Object.IsValid())
    77             {
    78                 // Tell closing event to cancel
    79                 DialogResult = DialogResult.None;
    80             }
    81         }
    82 
    83 
    84         /// <summary>
    85         /// 
    86         /// </summary>
    87         /// <param name="sender"></param>
    88         /// <param name="e"></param>
    89         private void FormEditObject_FormClosing(object sender, FormClosingEventArgs e)
    90         {
    91             //Check if we need to cancel the closing of our form.
    92             e.Cancel = DialogResult == DialogResult.None;
    93 
    94             if (!e.Cancel)
    95             {
    96                 //Exit edit mode
    97                 Object.CurrentState = SharpLib.Ear.Object.State.Rest;
    98                 Object.PropertyChanged -= PropertyChangedEventHandlerThreadSafe;
    99             }
   100         }
   101 
   102         /// <summary>
   103         /// 
   104         /// </summary>
   105         /// <param name="sender"></param>
   106         /// <param name="e"></param>
   107         private void comboBoxActionType_SelectedIndexChanged(object sender, EventArgs e)
   108         {
   109             //Instantiate an action corresponding to our type
   110             Type objectType = ((ItemObjectType) iComboBoxObjectType.SelectedItem).Type;
   111             //Create another type of action only if needed
   112             if (Object == null || Object.GetType() != objectType)
   113             {
   114                 if (Object != null)
   115                 {
   116                     // Make sure we exit edit mode and unhook from events
   117                     Object.CurrentState = SharpLib.Ear.Object.State.Rest;
   118                     Object.PropertyChanged -= PropertyChangedEventHandlerThreadSafe;
   119                     Object = null;
   120                 }
   121                 Object = (T)Activator.CreateInstance(objectType);
   122             }
   123 
   124             //Create input fields
   125             UpdateControls();
   126         }
   127 
   128 
   129         /// <summary>
   130         /// Get properties values from our generated input fields
   131         /// </summary>
   132         private void FetchPropertiesValue(T aObject)
   133         {
   134             int ctrlIndex = 0;
   135             //For each of our properties
   136             foreach (PropertyInfo pi in aObject.GetType().GetProperties())
   137             {
   138                 //Get our property attribute
   139                 AttributeObjectProperty[] attributes = ((AttributeObjectProperty[]) pi.GetCustomAttributes(typeof(AttributeObjectProperty), true));
   140                 if (attributes.Length != 1)
   141                 {
   142                     //No attribute, skip this property then.
   143                     continue;
   144                 }
   145                 AttributeObjectProperty attribute = attributes[0];
   146 
   147                 //Check that we support this type of property
   148                 if (!IsPropertyTypeSupported(pi))
   149                 {
   150                     continue;
   151                 }
   152 
   153                 //Now fetch our property value
   154                 GetPropertyValueFromControl(iTableLayoutPanel.Controls[ctrlIndex+1], pi, aObject); //+1 otherwise we get the label
   155 
   156                 ctrlIndex+=2; //Jump over the label too
   157             }
   158         }
   159 
   160         /// <summary>
   161         /// Extend this function to support reading new types of properties.
   162         /// </summary>
   163         /// <param name="aObject"></param>
   164         private void GetPropertyValueFromControl(Control aControl, PropertyInfo aInfo, T aObject)
   165         {
   166             if (aInfo.PropertyType == typeof(int))
   167             {
   168                 NumericUpDown ctrl=(NumericUpDown)aControl;
   169                 aInfo.SetValue(aObject,(int)ctrl.Value);
   170             }
   171             else if (aInfo.PropertyType.IsEnum)
   172             {
   173                 // Instantiate our enum
   174                 object enumValue= Activator.CreateInstance(aInfo.PropertyType);
   175                 // Parse our enum from combo box
   176                 enumValue = Enum.Parse(aInfo.PropertyType,((ComboBox)aControl).SelectedItem.ToString());
   177                 //enumValue = ((ComboBox)aControl).SelectedValue;
   178                 // Set enum value
   179                 aInfo.SetValue(aObject, enumValue);
   180             }
   181             else if (aInfo.PropertyType == typeof(bool))
   182             {
   183                 CheckBox ctrl = (CheckBox)aControl;
   184                 aInfo.SetValue(aObject, ctrl.Checked);
   185             }
   186             else if (aInfo.PropertyType == typeof(string))
   187             {
   188                 TextBox ctrl = (TextBox)aControl;
   189                 aInfo.SetValue(aObject, ctrl.Text);
   190             }
   191             else if (aInfo.PropertyType == typeof(PropertyFile))
   192             {
   193                 Button ctrl = (Button)aControl;
   194                 PropertyFile value = new PropertyFile {FullPath=ctrl.Text};
   195                 aInfo.SetValue(aObject, value);
   196             }
   197             else if (aInfo.PropertyType == typeof(PropertyComboBox))
   198             {
   199                 ComboBox ctrl = (ComboBox)aControl;
   200                 string currentItem = ctrl.SelectedItem.ToString();
   201                 PropertyComboBox value = (PropertyComboBox)aInfo.GetValue(aObject);
   202                 value.CurrentItem = currentItem;
   203                 //Not strictly needed but makes sure the set method is called
   204                 aInfo.SetValue(aObject, value);                
   205             }
   206             else if (aInfo.PropertyType == typeof(PropertyButton))
   207             {
   208                 Button ctrl = (Button)aControl;
   209                 PropertyButton value = new PropertyButton { Text = ctrl.Text };
   210                 aInfo.SetValue(aObject, value);
   211             }
   212 
   213             //TODO: add support for other types here
   214         }
   215 
   216 
   217         /// <summary>
   218         /// Create a control for the given property.
   219         /// </summary>
   220         /// <param name="aInfo"></param>
   221         /// <param name="aAttribute"></param>
   222         /// <param name="aObject"></param>
   223         /// <returns></returns>
   224         private Control CreateControlForProperty(PropertyInfo aInfo, AttributeObjectProperty aAttribute, T aObject)
   225         {
   226             if (aInfo.PropertyType == typeof(int))
   227             {
   228                 //Integer properties are using numeric editor
   229                 NumericUpDown ctrl = new NumericUpDown();
   230                 ctrl.AutoSize = true;
   231                 ctrl.Minimum = Int32.Parse(aAttribute.Minimum);
   232                 ctrl.Maximum = Int32.Parse(aAttribute.Maximum);
   233                 ctrl.Increment = Int32.Parse(aAttribute.Increment);
   234                 ctrl.Value = (int)aInfo.GetValue(aObject);
   235                 // Hook-in change notification after setting the value 
   236                 ctrl.ValueChanged += ControlValueChanged;
   237                 return ctrl;
   238             }
   239             else if (aInfo.PropertyType.IsEnum)
   240             {
   241                 //Enum properties are using combo box
   242                 ComboBox ctrl = new ComboBox();
   243                 ctrl.AutoSize = true;
   244                 ctrl.Sorted = true;
   245                 ctrl.DropDownStyle = ComboBoxStyle.DropDownList;
   246                 //Data source is fine but it gives us duplicate entries for duplicated enum values
   247                 //ctrl.DataSource = Enum.GetValues(aInfo.PropertyType);
   248 
   249                 //Therefore we need to explicitly create our items
   250                 Size cbSize = new Size(0, 0);
   251                 foreach (string name in aInfo.PropertyType.GetEnumNames())
   252                 {
   253                     ctrl.Items.Add(name.ToString());
   254                     Graphics g = this.CreateGraphics();
   255                     //Since combobox autosize would not work we need to get measure text ourselves
   256                     SizeF size = g.MeasureString(name.ToString(), ctrl.Font);
   257                     cbSize.Width = Math.Max(cbSize.Width, (int)size.Width);
   258                     cbSize.Height = Math.Max(cbSize.Height, (int)size.Height);
   259                 }
   260 
   261                 //Make sure our combobox is large enough
   262                 ctrl.MinimumSize = cbSize;
   263 
   264                 // Instantiate our enum
   265                 object enumValue = Activator.CreateInstance(aInfo.PropertyType);
   266                 enumValue = aInfo.GetValue(aObject);
   267                 //Set the current item
   268                 ctrl.SelectedItem = enumValue.ToString();
   269                 // Hook-in change notification after setting the value 
   270                 ctrl.SelectedIndexChanged += ControlValueChanged;
   271 
   272                 return ctrl;
   273             }
   274             else if (aInfo.PropertyType == typeof(bool))
   275             {
   276                 CheckBox ctrl = new CheckBox();
   277                 ctrl.AutoSize = true;
   278                 ctrl.Text = aAttribute.Description;
   279                 ctrl.Checked = (bool)aInfo.GetValue(aObject);
   280                 // Hook-in change notification after setting the value 
   281                 ctrl.CheckedChanged += ControlValueChanged;
   282                 return ctrl;
   283             }
   284             else if (aInfo.PropertyType == typeof(string))
   285             {
   286                 TextBox ctrl = new TextBox();
   287                 ctrl.AutoSize = true;
   288                 ctrl.Text = (string)aInfo.GetValue(aObject);
   289                 // Hook-in change notification after setting the value 
   290                 ctrl.TextChanged += ControlValueChanged;
   291                 return ctrl;
   292             }
   293             else if (aInfo.PropertyType == typeof(PropertyFile))
   294             {
   295                 // We have a file property
   296                 // Create a button that will trigger the open file dialog to select our file.
   297                 Button ctrl = new Button();
   298                 ctrl.AutoSize = true;
   299                 ctrl.Text = ((PropertyFile)aInfo.GetValue(aObject)).FullPath;
   300                 // Add lambda expression to Click event
   301                 ctrl.Click += (sender, e) =>
   302                 {
   303                     // Create open file dialog
   304                     OpenFileDialog ofd = new OpenFileDialog();
   305                     ofd.RestoreDirectory = true;
   306                     // Use file filter specified by our property
   307                     ofd.Filter = aAttribute.Filter;
   308                     // Show our dialog
   309                     if (DlgBox.ShowDialog(ofd) == DialogResult.OK)
   310                     {
   311                         // Fetch selected file name
   312                         ctrl.Text = ofd.FileName;
   313                     }
   314                 };
   315 
   316                 // Hook-in change notification after setting the value 
   317                 ctrl.TextChanged += ControlValueChanged;
   318                 return ctrl;
   319             }
   320             else if (aInfo.PropertyType == typeof(PropertyComboBox))
   321             {
   322                 //ComboBox property
   323                 ComboBox ctrl = new ComboBox();
   324                 ctrl.AutoSize = true;
   325                 ctrl.Sorted = true;
   326                 ctrl.DropDownStyle = ComboBoxStyle.DropDownList;
   327                 //Data source is such a pain to set the current item
   328                 //ctrl.DataSource = ((PropertyComboBox)aInfo.GetValue(aObject)).Items;                
   329 
   330                 PropertyComboBox pcb = ((PropertyComboBox)aInfo.GetValue(aObject));
   331                 foreach (string item in pcb.Items)
   332                 {
   333                     ctrl.Items.Add(item);
   334                 }
   335 
   336                 ctrl.SelectedItem = ((PropertyComboBox)aInfo.GetValue(aObject)).CurrentItem;
   337                 //
   338                 return ctrl;
   339             }
   340             else if (aInfo.PropertyType == typeof(PropertyButton))
   341             {
   342                 // We have a button property
   343                 // Create a button that will trigger the custom action.
   344                 Button ctrl = new Button();
   345                 ctrl.AutoSize = true;
   346                 ctrl.Text = ((PropertyButton)aInfo.GetValue(aObject)).Text;
   347                 // Hook in click event
   348                 ctrl.Click += ((PropertyButton)aInfo.GetValue(aObject)).ClickEventHandler;
   349                 // Hook-in change notification after setting the value 
   350                 ctrl.TextChanged += ControlValueChanged;
   351                 return ctrl;
   352             }
   353 
   354             //TODO: add support for other control type here
   355             return null;
   356         }
   357 
   358         /// <summary>
   359         /// Don't forget to extend that one and adding types
   360         /// </summary>
   361         /// <returns></returns>
   362         private bool IsPropertyTypeSupported(PropertyInfo aInfo)
   363         {
   364             if (aInfo.PropertyType == typeof(int))
   365             {
   366                 return true;
   367             }
   368             else if (aInfo.PropertyType.IsEnum)
   369             {
   370                 return true;
   371             }
   372             else if (aInfo.PropertyType == typeof(bool))
   373             {
   374                 return true;
   375             }
   376             else if (aInfo.PropertyType == typeof(string))
   377             {
   378                 return true;
   379             }
   380             else if (aInfo.PropertyType == typeof(PropertyFile))
   381             {
   382                 return true;
   383             }
   384             else if (aInfo.PropertyType == typeof(PropertyComboBox))
   385             {
   386                 return true;
   387             }
   388             else if (aInfo.PropertyType == typeof(PropertyButton))
   389             {
   390                 return true;
   391             }
   392 
   393             //TODO: add support for other type here
   394 
   395             return false;
   396         }
   397 
   398         /// <summary>
   399         /// Update our table layout.
   400         /// Will instantiated every field control as defined by our object.
   401         /// </summary>
   402         /// <param name="aLayout"></param>
   403         private void UpdateControls()
   404         {
   405 
   406             toolTip.RemoveAll();
   407             //Debug.Print("UpdateTableLayoutPanel")
   408             //First clean our current panel
   409             iTableLayoutPanel.Controls.Clear();
   410             iTableLayoutPanel.RowStyles.Clear();
   411             iTableLayoutPanel.ColumnStyles.Clear();
   412             iTableLayoutPanel.RowCount = 0;
   413 
   414             //We always want two columns: one for label and one for the field
   415             iTableLayoutPanel.ColumnCount = 2;
   416             iTableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
   417             iTableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
   418 
   419 
   420             if (Object == null)
   421             {
   422                 //Just drop it
   423                 return;
   424             }
   425 
   426             UpdateStaticControls();
   427 
   428             //IEnumerable<PropertyInfo> properties = aObject.GetType().GetProperties().Where(
   429             //    prop => Attribute.IsDefined(prop, typeof(AttributeObjectProperty)));
   430 
   431             //TODO: Do this whenever a field changes
   432             iLabelBrief.Text = Object.Brief();
   433 
   434 
   435             foreach (PropertyInfo pi in Object.GetType().GetProperties())
   436             {
   437                 AttributeObjectProperty[] attributes = ((AttributeObjectProperty[])pi.GetCustomAttributes(typeof(AttributeObjectProperty), true));
   438                 if (attributes.Length != 1)
   439                 {
   440                     continue;
   441                 }
   442 
   443                 AttributeObjectProperty attribute = attributes[0];
   444 
   445                 //Before anything we need to check if that kind of property is supported by our UI
   446                 //Create the editor
   447                 Control ctrl = CreateControlForProperty(pi, attribute, Object);
   448                 if (ctrl == null)
   449                 {
   450                     //Property type not supported
   451                     continue;
   452                 }
   453 
   454                 //Add a new row
   455                 iTableLayoutPanel.RowCount++;
   456                 iTableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
   457                 //Create the label
   458                 Label label = new Label();
   459                 label.AutoSize = true;
   460                 label.Dock = DockStyle.Fill;
   461                 label.TextAlign = ContentAlignment.MiddleCenter;
   462                 label.Text = attribute.Name;
   463                 toolTip.SetToolTip(label, attribute.Description);
   464                 iTableLayoutPanel.Controls.Add(label, 0, iTableLayoutPanel.RowCount-1);
   465 
   466                 //Add our editor to our form
   467                 iTableLayoutPanel.Controls.Add(ctrl, 1, iTableLayoutPanel.RowCount - 1);
   468                 //Add tooltip to editor too
   469                 toolTip.SetToolTip(ctrl, attribute.Description);
   470 
   471             }
   472 
   473             //Entrer object edit mode
   474             Object.CurrentState = SharpLib.Ear.Object.State.Edit;
   475             Object.PropertyChanged += PropertyChangedEventHandlerThreadSafe;
   476         }
   477 
   478         /// <summary>
   479         /// 
   480         /// </summary>
   481         /// <param name="sender"></param>
   482         /// <param name="e"></param>
   483         void PropertyChangedEventHandlerThreadSafe(object sender, PropertyChangedEventArgs e)
   484         {
   485             if (this.InvokeRequired)
   486             {
   487                 //Not in the proper thread, invoke ourselves
   488                 PropertyChangedEventHandler d = new PropertyChangedEventHandler(PropertyChangedEventHandlerThreadSafe);
   489                 this.Invoke(d, new object[] { sender, e });
   490             }
   491             else
   492             {
   493                 // We could test the name of the property that has changed as follow
   494                 // It's currently not needed though
   495                 //if (e.PropertyName == "Brief")
   496 
   497                 // Our object has changed behind our back.
   498                 // That's currently only the case for HID events that are listening for inputs.
   499                 if (Object is EventHid)
   500                 {
   501                     //HID can't do full control updates for some reason
   502                     //We are getting spammed with HID events after a few clicks
   503                     //We need to investigate, HID bug?
   504                     UpdateStaticControls();
   505                 }
   506                 else
   507                 {
   508                     UpdateControls();
   509                 }
   510             }
   511         }
   512 
   513         private void buttonTest_Click(object sender, EventArgs e)
   514         {
   515             FetchPropertiesValue(Object);
   516 
   517             //If our object has a test method with no parameters just run it then
   518             MethodInfo info = Object.GetType().GetMethod("Test");
   519             if ( info != null && info.GetParameters().Length==0)
   520             {
   521                 info.Invoke(Object,null);
   522             }
   523 
   524         }
   525 
   526 
   527         /// <summary>
   528         /// 
   529         /// </summary>
   530         /// <param name="sender"></param>
   531         /// <param name="e"></param>
   532         private void ControlValueChanged(object sender, EventArgs e)
   533         {
   534             UpdateObject();
   535         }
   536 
   537         /// <summary>
   538         /// 
   539         /// </summary>
   540         private void UpdateObject()
   541         {
   542             // Update our object with the content of our controls
   543             FetchPropertiesValue(Object);
   544 
   545             UpdateStaticControls();
   546             //
   547             //PerformLayout();
   548         }
   549 
   550         /// <summary>
   551         /// 
   552         /// </summary>
   553         private void UpdateStaticControls()
   554         {
   555             // Update OK and test button status
   556             iButtonOk.Enabled = Object.IsValid();
   557             iButtonTest.Enabled = iButtonOk.Enabled;
   558 
   559             // Update brief title
   560             iLabelBrief.Text = Object.Brief();
   561 
   562             // Update object description
   563             iLabelDescription.Text = Object.AttributeDescription;
   564         }
   565 
   566         /// <summary>
   567         /// 
   568         /// </summary>
   569         /// <param name="sender"></param>
   570         /// <param name="e"></param>
   571         private void iComboBoxObjectType_KeyPress(object sender, KeyPressEventArgs e)
   572         {
   573             //Special case for HID events
   574             if (Object is EventHid)
   575             {
   576                 //Disable handling of key input as we are using key input for changing our event
   577                 e.Handled = true;
   578             }
   579         }
   580 
   581         private void iComboBoxObjectType_Enter(object sender, EventArgs e)
   582         {
   583             //Only edit HID event when our type combo box has the focus
   584             // TODO: That's an ugly workaround, fix that somehow. Maybe by only doing HID scan when a button property has the focus
   585             if (Object is EventHid)
   586             {
   587                 Object.CurrentState = SharpLib.Ear.Object.State.Edit;
   588             }
   589         }
   590 
   591         private void iComboBoxObjectType_Leave(object sender, EventArgs e)
   592         {
   593             //Only edit HID event when our type combo box has the focus
   594             // TODO: That's an ugly workaround, fix that somehow. Maybe by only doing HID scan when a button property has the focus
   595             if (Object is EventHid)
   596             {
   597                 Object.CurrentState = SharpLib.Ear.Object.State.Rest;
   598             }
   599         }
   600     }
   601 }