/// <summary> /// Create a control for the given property. /// TODO: Handle cases where a property value is null. That can be the case when extending an existing class and loading it from an older save. /// Though I guess that should really be taken care of in Ear.Object.Construct which is called once the object was internalized. /// </summary> /// <param name="aInfo"></param> /// <param name="aAttribute"></param> /// <param name="aObject"></param> /// <returns></returns> private Control CreateControlForProperty(PropertyInfo aInfo, AttributeObjectProperty aAttribute, T aObject) { if (aInfo.PropertyType == typeof(int) || aInfo.PropertyType == typeof(float)) { //Integer properties are using numeric editor NumericUpDown ctrl = new NumericUpDown(); ctrl.AutoSize = true; //ctrl.Dock = DockStyle.Fill; // Fill the whole table cell ctrl.Minimum = (decimal)aAttribute.Minimum; ctrl.Maximum = (decimal)aAttribute.Maximum; ctrl.Increment = (decimal)aAttribute.Increment; ctrl.DecimalPlaces = aAttribute.DecimalPlaces; ctrl.Value = decimal.Parse(aInfo.GetValue(aObject).ToString()); // Hook-in change notification after setting the value ctrl.ValueChanged += ControlValueChanged; return(ctrl); } else if (aInfo.PropertyType.IsEnum) { //Enum properties are using combo box ComboBox ctrl = new ComboBox(); ctrl.AutoSize = true; ctrl.Sorted = true; ctrl.DropDownStyle = ComboBoxStyle.DropDownList; //Data source is fine but it gives us duplicate entries for duplicated enum values //ctrl.DataSource = Enum.GetValues(aInfo.PropertyType); //Therefore we need to explicitly create our items Size cbSize = new Size(0, 0); foreach (string name in aInfo.PropertyType.GetEnumNames()) { ctrl.Items.Add(name.ToString()); Graphics g = this.CreateGraphics(); //Since combobox autosize would not work we need to measure text ourselves SizeF size = g.MeasureString(name.ToString(), ctrl.Font); cbSize.Width = Math.Max(cbSize.Width, (int)size.Width); cbSize.Height = Math.Max(cbSize.Height, (int)size.Height); } cbSize.Width += 10; // Account for margin //Make sure our combobox is large enough ctrl.MinimumSize = cbSize; // Instantiate our enum object enumValue = Activator.CreateInstance(aInfo.PropertyType); enumValue = aInfo.GetValue(aObject); //Set the current item ctrl.SelectedItem = enumValue.ToString(); // Hook-in change notification after setting the value ctrl.SelectedIndexChanged += ControlValueChanged; return(ctrl); } else if (aInfo.PropertyType == typeof(bool)) { CheckBox ctrl = new CheckBox(); ctrl.AutoSize = true; ctrl.Text = aAttribute.Description; ctrl.Checked = (bool)aInfo.GetValue(aObject); // Hook-in change notification after setting the value ctrl.CheckedChanged += ControlValueChanged; return(ctrl); } else if (aInfo.PropertyType == typeof(string)) { TextBox ctrl = new TextBox(); ctrl.AutoSize = true; ctrl.Dock = DockStyle.Fill; // Fill the whole table cell ctrl.Text = (string)aInfo.GetValue(aObject); // Multiline setup ctrl.Multiline = aAttribute.Multiline; if (ctrl.Multiline) { ctrl.AcceptsReturn = true; ctrl.ScrollBars = ScrollBars.Vertical; // Adjust our height as needed Size size = ctrl.Size; size.Height += ctrl.Font.Height * (aAttribute.HeightInLines - 1); ctrl.MinimumSize = size; } // Hook-in change notification after setting the value ctrl.TextChanged += ControlValueChanged; return(ctrl); } else if (aInfo.PropertyType == typeof(PropertyFile)) { // We have a file property // Create a button that will trigger the open file dialog to select our file. Button ctrl = new Button(); ctrl.AutoSize = true; ctrl.Text = ((PropertyFile)aInfo.GetValue(aObject)).FullPath; // Add lambda expression to Click event ctrl.Click += (sender, e) => { // Create open file dialog OpenFileDialog ofd = new OpenFileDialog(); ofd.RestoreDirectory = true; // Use file filter specified by our property ofd.Filter = aAttribute.Filter; // Show our dialog if (SharpLib.Forms.DlgBox.ShowDialog(ofd) == DialogResult.OK) { // Fetch selected file name ctrl.Text = ofd.FileName; } }; // Hook-in change notification after setting the value ctrl.TextChanged += ControlValueChanged; return(ctrl); } else if (aInfo.PropertyType == typeof(PropertyComboBox)) { //ComboBox property PropertyComboBox property = ((PropertyComboBox)aInfo.GetValue(aObject)); ComboBox ctrl = new ComboBox(); ctrl.AutoSize = true; //ctrl.Sorted = property.Sorted; // Sorted is not supported and does not work using DataSource, can result in the wrong item being selected ctrl.DropDownStyle = ComboBoxStyle.DropDownList; // Use DataSource with optional DisplayMember and ValueMember, that also works for plain string collection ctrl.DisplayMember = property.DisplayMember; ctrl.ValueMember = property.ValueMember; //ctrl.DataSource = ((PropertyComboBox)aInfo.GetValue(aObject)).Items; UpdateControlFromProperty(ctrl, aInfo, aObject); return(ctrl); } else if (aInfo.PropertyType == typeof(PropertyButton)) { // We have a button property // Create a button that will trigger the custom action. Button ctrl = new Button(); ctrl.AutoSize = true; ctrl.Text = ((PropertyButton)aInfo.GetValue(aObject)).Text; // Hook in click event ctrl.Click += ((PropertyButton)aInfo.GetValue(aObject)).ClickEventHandler; // Hook-in change notification after setting the value ctrl.TextChanged += ControlValueChanged; return(ctrl); } else if (aInfo.PropertyType == typeof(PropertyCheckedListBox)) { //CheckedListBox property PropertyCheckedListBox property = ((PropertyCheckedListBox)aInfo.GetValue(aObject)); CheckedListBox ctrl = new CheckedListBox(); ctrl.AutoSize = true; ctrl.Sorted = property.Sorted; ctrl.CheckOnClick = true; // Populate our box with list items foreach (string item in property.Items) { int index = ctrl.Items.Add(item); // Check items if needed if (property.CheckedItems.Contains(item)) { ctrl.SetItemChecked(index, true); } } // // Hook-in change notification after setting the value // That's essentially making sure title/brief gets updated as items are checked or unchecked // This looks convoluted as we are working around the fact that ItemCheck event is triggered before the model is updated. // See: https://stackoverflow.com/a/48645552/3969362 ctrl.ItemCheck += (s, e) => BeginInvoke((MethodInvoker)(() => ControlValueChanged(s, e))); return(ctrl); } //TODO: add support for other control type here return(null); }
/// <summary> /// Update a control states from an edited object property. /// This notably enables refresh from our HID device property when the user is recording an event. /// /// TODO: Add support for needed property types. /// </summary> /// <param name="aCtrl"></param> /// <param name="aInfo"></param> /// <param name="aObject"></param> private void UpdateControlFromProperty(Control aCtrl, PropertyInfo aInfo, T aObject) { if (aCtrl == null || aInfo == null || aObject == null) { // No much we can do then return; } if (aInfo.PropertyType == typeof(PropertyComboBox)) { PropertyComboBox property = ((PropertyComboBox)aInfo.GetValue(aObject)); ComboBox ctrl = (ComboBox)aCtrl; // Disable object update to prevent endless update notification circle ctrl.SelectedIndexChanged -= ControlValueChanged; // In case out items changed ctrl.DataSource = property.Items; // When using DataSource we need to wait until our items are create to proceed further BeginInvoke((MethodInvoker)(() => { // Now our items must have been created from our data source Graphics g = ctrl.CreateGraphics(); // Make sure our ComboBox is large enough to display its items Size cbSize = new Size(0, 0); foreach (object item in ctrl.Items) { //Since ComboBox AutoSize would not work we need to measure text ourselves SizeF size = g.MeasureString(string.IsNullOrEmpty(property.DisplayMember) ? // Typically the case for string collection DataSource item.ToString() : // DataSource is a collection of unknown objects, using reflection we fetch the property referred to by DisplayMember then item.GetType().GetProperty(property.DisplayMember).GetValue(item).ToString(), ctrl.Font); cbSize.Width = Math.Max(cbSize.Width, (int)size.Width); cbSize.Height = Math.Max(cbSize.Height, (int)size.Height); } cbSize.Width += 10; // Account for margin //Make sure our combobox is large enough ctrl.MinimumSize = cbSize; // if (!string.IsNullOrEmpty(property.CurrentItem)) // Defensive, I guess { //if (!property.CurrentItemNotFound) { // We can't set SelectedValue when no ValueMember if (string.IsNullOrEmpty(property.ValueMember)) { // Use plain SelectedItem when no ValueMember, typically the case when simply using a string collection as DataSource ctrl.SelectedItem = property.CurrentItem; } else { // We have a ValueMember typically the case when our DataSource is a collection of unknown objects // We need to set our current item using value rather then display name ctrl.SelectedValue = property.CurrentItem; } } } // Hook-in change notification after setting the value // That makes sure object is updated when user changes our control ctrl.SelectedIndexChanged += ControlValueChanged; })); } }
/// <summary> /// Set an object property value from the state of the given control. /// /// Extend this function to support reading new types of properties. /// </summary> /// <param name="aObject"></param> private static void SetObjectPropertyValueFromControl(T aObject, PropertyInfo aInfo, Control aControl) { if (aInfo.PropertyType == typeof(int)) { NumericUpDown ctrl = (NumericUpDown)aControl; aInfo.SetValue(aObject, (int)ctrl.Value); } else if (aInfo.PropertyType == typeof(float)) { NumericUpDown ctrl = (NumericUpDown)aControl; aInfo.SetValue(aObject, (float)ctrl.Value); } else if (aInfo.PropertyType.IsEnum) { // Instantiate our enum object enumValue = Activator.CreateInstance(aInfo.PropertyType); // Parse our enum from combo box enumValue = Enum.Parse(aInfo.PropertyType, ((ComboBox)aControl).SelectedItem.ToString()); //enumValue = ((ComboBox)aControl).SelectedValue; // Set enum value aInfo.SetValue(aObject, enumValue); } else if (aInfo.PropertyType == typeof(bool)) { CheckBox ctrl = (CheckBox)aControl; aInfo.SetValue(aObject, ctrl.Checked); } else if (aInfo.PropertyType == typeof(string)) { TextBox ctrl = (TextBox)aControl; aInfo.SetValue(aObject, ctrl.Text); } else if (aInfo.PropertyType == typeof(PropertyFile)) { Button ctrl = (Button)aControl; PropertyFile value = new PropertyFile { FullPath = ctrl.Text }; aInfo.SetValue(aObject, value); } else if (aInfo.PropertyType == typeof(PropertyComboBox)) { ComboBox ctrl = (ComboBox)aControl; if (ctrl.SelectedItem != null) { // Apparently you can still get the SelectedValue even when no ValueMember was set string currentItem = ctrl.SelectedValue.ToString(); PropertyComboBox value = (PropertyComboBox)aInfo.GetValue(aObject); // Make sure the value actually changed before doing anything // Shall we do that for every control? if (value.CurrentItem != currentItem) { value.CurrentItem = currentItem; //Not strictly needed but makes sure the set method is called aInfo.SetValue(aObject, value); // aObject.OnPropertyChanged(aInfo.Name); } } } else if (aInfo.PropertyType == typeof(PropertyButton)) { Button ctrl = (Button)aControl; PropertyButton value = new PropertyButton { Text = ctrl.Text }; aInfo.SetValue(aObject, value); } else if (aInfo.PropertyType == typeof(PropertyCheckedListBox)) { CheckedListBox ctrl = (CheckedListBox)aControl; PropertyCheckedListBox value = (PropertyCheckedListBox)aInfo.GetValue(aObject); List <string> checkedItems = new List <string>(); foreach (string item in ctrl.CheckedItems) { checkedItems.Add(item); } value.CheckedItems = checkedItems; //value.CurrentItem = currentItem; //Not strictly needed but makes sure the set method is called aInfo.SetValue(aObject, value); // //aObject.OnPropertyChanged(aInfo.Name); } //TODO: add support for other types here }