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