public static Variable GetItem(VariableProperty attribute, string value) { switch ( attribute ) { case VariableProperty.Name: return GetItemByName(value); case VariableProperty.ShortName: return GetItemByShortName(value); case VariableProperty.Url: return GetItemByUrl(value); default: return null; } }
/// <summary> /// User has changed the value of a cell. Validate the change /// apply the change. /// </summary> /// <param name="sender">Sender object</param> /// <param name="args">Event parameters</param> private void OnCellsChanged(object sender, GridCellsChangedArgs args) { List <ChangeProperty.Property> changes = new List <ChangeProperty.Property>(); // If all cells are being set to null, and the number of changed // cells is a multiple of the number of properties, we could be // deleting an entire row (or rows). In this case, we need // different logic, to resize all of the arrays. bool deletedRow = false; if (args.ChangedCells.All(c => c.NewValue == null) && args.ChangedCells.Count % properties.Where(p => !p.IsReadOnly).Count() == 0) { // Get list of distinct rows which have been changed. int[] changedRows = args.ChangedCells.Select(c => c.RowIndex).Distinct().ToArray(); List <int> rowsToDelete = new List <int>(); foreach (int row in changedRows) { // Get columns which have been changed in this row. var changesInRow = args.ChangedCells.Where(c => c.RowIndex == row); int[] columns = changesInRow.Select(c => c.ColIndex).ToArray(); // If all non-readonly properties have been set to null in this row, // delete the row. bool deleteRow = true; for (int i = 0; i < properties.Count; i++) { if (!properties[i].IsReadOnly && !columns.Contains(i)) { deleteRow = false; } } if (deleteRow) { // We can't delete the row now - what if the user has deleted // multiple rows at once (this is possible via shift-clicking). // We need one change property command per property. Otherwise, // only the last command will have an effect. deletedRow = true; rowsToDelete.Add(row); } } if (rowsToDelete.Count > 0) { // This assumes that only consecutive rows can be deleted together. // ie the user can shift click multiple rows and hit delete to delete // more than 1 row. They cannot ctrl click to select non-adjacent rows. int from = rowsToDelete.Min(); int to = rowsToDelete.Max(); changes.AddRange(DeleteRows(from, to)); // Remove cells in deleted rows from list of changed cells, // as we've already dealt with them. args.ChangedCells = args.ChangedCells.Where(c => !rowsToDelete.Contains(c.RowIndex)).ToList(); } } foreach (var column in args.ChangedCells.GroupBy(c => c.ColIndex)) { VariableProperty property = properties[column.Key]; if (property == null || property.IsReadOnly) { continue; } // Get a deep copy of the property value. Array newArray = property.Value as Array; if (newArray == null && property.Value != null) { continue; } newArray = Clone(newArray, property.DataType.GetElementType()); // It's possible to change multiple values in the same column // simultaneously via multi-selection. If we just add a change // property command for each individual change, later changes // would overwrite the earlier changes. We need to merge all // changes to a single column into a single command then move // onto the next column. foreach (GridCellChangedArgs change in column) { if (change.NewValue == change.OldValue) { continue; // silently fail } // If the user has entered data into a new row, we will need to // resize all of the array properties. changes.AddRange(CheckArrayLengths(change)); object element = ReflectionUtilities.StringToObject(property.DataType.GetElementType(), change.NewValue); if (newArray.Length <= change.RowIndex) { Resize(ref newArray, change.RowIndex + 1); } newArray.SetValue(element, change.RowIndex); } changes.Add(new ChangeProperty.Property(property.Object, property.Name, newArray)); } // Apply all changes to the model in a single undoable command. SetPropertyValue(new ChangeProperty(changes)); // Update the value shown in the grid. This needs to happen after // we have applied changes to the model for obvious reasons. foreach (GridCellChangedArgs cell in args.ChangedCells) { // Add new rows to the view's grid if necessary. while (grid.RowCount <= cell.RowIndex + 1) { grid.RowCount++; } grid.DataSource.Rows[cell.RowIndex][cell.ColIndex] = GetCellValue(cell.RowIndex, cell.ColIndex); } // If the user deleted an entire row, do a full refresh of the // grid. Otherwise, only refresh read-only columns (PAWC). if (deletedRow) { PopulateGrid(model); } else { UpdateReadOnlyProperties(); } }
/// <summary> /// The view is asking for variable names for its intellisense. /// </summary> /// <param name="atype">Data type for which we want completion options.</param> /// <param name="properties">If true, property suggestions will be generated.</param> /// <param name="methods">If true, method suggestions will be generated.</param> /// <param name="events">If true, event suggestions will be generated.</param> /// <returns>List of completion options.</returns> public static List <ContextItem> ExamineTypeForContextItems(Type atype, bool properties, bool methods, bool events) { List <ContextItem> allItems = new List <ContextItem>(); // find the properties and methods if (atype != null) { if (properties) { foreach (PropertyInfo property in atype.GetProperties(BindingFlags.Instance | BindingFlags.Public)) { VariableProperty var = new VariableProperty(atype, property); NeedContextItemsArgs.ContextItem item = new NeedContextItemsArgs.ContextItem(); item.Name = var.Name; item.IsProperty = true; item.IsEvent = false; item.IsWriteable = !var.IsReadOnly; item.TypeName = var.DataType.Name; item.Descr = var.Description; item.Units = var.Units; allItems.Add(item); } } if (methods) { foreach (MethodInfo method in atype.GetMethods(BindingFlags.Instance | BindingFlags.Public)) { if (!method.Name.StartsWith("get_") && !method.Name.StartsWith("set_")) { DescriptionAttribute descriptionAttribute = ReflectionUtilities.GetAttribute(atype, typeof(DescriptionAttribute), false) as DescriptionAttribute; NeedContextItemsArgs.ContextItem item = new NeedContextItemsArgs.ContextItem(); item.Name = method.Name; item.IsProperty = false; item.IsEvent = true; item.IsWriteable = false; item.TypeName = method.ReturnType.Name; if (descriptionAttribute != null) { item.Descr = descriptionAttribute.ToString(); } item.Units = string.Empty; // build a parameter string representation ParameterInfo[] allparams = method.GetParameters(); StringBuilder paramText = new StringBuilder("( "); if (allparams.Count() > 0) { for (int p = 0; p < allparams.Count(); p++) { ParameterInfo parameter = allparams[p]; paramText.Append(parameter.ParameterType.Name + " " + parameter.Name); if (p < allparams.Count() - 1) { paramText.Append(", "); } } } paramText.Append(" )"); item.ParamString = paramText.ToString(); allItems.Add(item); } } } if (events) { foreach (EventInfo evnt in atype.GetEvents(BindingFlags.Instance | BindingFlags.Public)) { NeedContextItemsArgs.ContextItem item = new NeedContextItemsArgs.ContextItem(); item.Name = evnt.Name; item.IsProperty = true; item.IsEvent = true; item.IsWriteable = false; item.TypeName = evnt.ReflectedType.Name; item.Descr = ""; item.Units = ""; allItems.Add(item); } } } allItems.Sort(delegate(ContextItem c1, ContextItem c2) { return(c1.Name.CompareTo(c2.Name)); }); return(allItems); }
/// <summary> /// The view is asking for variable names for its intellisense. /// </summary> /// <param name="atype">Data type for which we want completion options.</param> /// <param name="properties">If true, property suggestions will be generated.</param> /// <param name="methods">If true, method suggestions will be generated.</param> /// <param name="publishedEvents">If true, published events will be returned.</param> /// <param name="subscribedEvents">If true, subscribed events will be returned.</param> /// <returns>List of completion options.</returns> public static List <ContextItem> ExamineTypeForContextItems(Type atype, bool properties, bool methods, bool publishedEvents, bool subscribedEvents) { List <ContextItem> allItems = new List <ContextItem>(); // find the properties and methods if (atype != null) { if (properties) { foreach (PropertyInfo property in atype.GetProperties(BindingFlags.Instance | BindingFlags.Public)) { VariableProperty var = new VariableProperty(atype, property); ContextItem item = new ContextItem { Name = var.Name, IsProperty = true, IsEvent = false, IsWriteable = !var.IsReadOnly, TypeName = GetTypeName(var.DataType), Descr = GetDescription(property), Units = var.Units }; allItems.Add(item); } } if (methods) { foreach (MethodInfo method in atype.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy)) { if (!method.Name.StartsWith("get_") && !method.Name.StartsWith("set_") && !method.Name.StartsWith("add_") && !method.Name.StartsWith("remove_")) { ContextItem item = new ContextItem { Name = method.Name, IsProperty = false, IsEvent = false, IsMethod = true, IsWriteable = false, TypeName = method.ReturnType.Name, Descr = GetDescription(method), Units = string.Empty }; // build a parameter string representation ParameterInfo[] allparams = method.GetParameters(); StringBuilder paramText = new StringBuilder("( "); if (allparams.Count() > 0) { for (int p = 0; p < allparams.Count(); p++) { ParameterInfo parameter = allparams[p]; paramText.Append(parameter.ParameterType.Name + " " + parameter.Name); if (parameter.DefaultValue != DBNull.Value) { paramText.Append(" = " + parameter.DefaultValue?.ToString() ?? "null"); } if (p < allparams.Count() - 1) { paramText.Append(", "); } } } paramText.Append(" )"); item.ParamString = paramText.ToString(); allItems.Add(item); } } } if (publishedEvents) { foreach (EventInfo evnt in atype.GetEvents(BindingFlags.Instance | BindingFlags.Public)) { NeedContextItemsArgs.ContextItem item = new NeedContextItemsArgs.ContextItem(); item.Name = evnt.Name; item.IsProperty = true; item.IsEvent = true; item.IsMethod = false; item.IsWriteable = false; item.TypeName = evnt.ReflectedType.Name; item.Descr = GetDescription(evnt); item.Units = ""; allItems.Add(item); } } if (subscribedEvents) { foreach (MethodInfo method in atype.GetMethods(/*BindingFlags.Instance |*/ BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)) { EventSubscribeAttribute subscriberAttribute = (EventSubscribeAttribute)ReflectionUtilities.GetAttribute(method, typeof(EventSubscribeAttribute), false); if (subscriberAttribute != null) { NeedContextItemsArgs.ContextItem item = new NeedContextItemsArgs.ContextItem(); item.Name = subscriberAttribute.ToString(); item.IsProperty = false; item.IsEvent = false; item.IsMethod = true; item.IsWriteable = false; item.TypeName = atype.Name; item.Descr = GetDescription(atype); item.Units = ""; allItems.Add(item); } } } } allItems.Sort(delegate(ContextItem c1, ContextItem c2) { return(c1.Name.CompareTo(c2.Name)); }); return(allItems); }
/// <summary> /// Format the grid. /// </summary> protected virtual void FormatGrid() { for (int i = 0; i < properties.Count; i++) { IGridCell cell = grid.GetCell(1, i); if (properties[i] is VariableObject) { cell.EditorType = EditorTypeEnum.TextBox; grid.SetRowAsSeparator(i, true); } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.TableName) { cell.EditorType = EditorTypeEnum.DropDown; cell.DropDownStrings = storage.Reader.TableNames.ToArray(); } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.CultivarName) { cell.EditorType = EditorTypeEnum.DropDown; IPlant crop; if (properties[i].Display.PlantName != null) { crop = Apsim.FindAll(model, typeof(IPlant)).FirstOrDefault(p => p.Name == properties[i].Display.PlantName) as IPlant; } else { crop = GetCrop(properties); } if (crop != null) { cell.DropDownStrings = GetCultivarNames(crop); } } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.FileName) { cell.EditorType = EditorTypeEnum.Button; } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.FileNames) { cell.EditorType = EditorTypeEnum.MultiFiles; } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.FieldName) { cell.EditorType = EditorTypeEnum.DropDown; List <string> fieldNames = GetFieldNames(); if (fieldNames != null) { fieldNames.Insert(0, string.Empty); cell.DropDownStrings = fieldNames.ToArray(); } } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.ResidueName && model is SurfaceOrganicMatter) { cell.EditorType = EditorTypeEnum.DropDown; string[] fieldNames = GetResidueNames(); if (fieldNames != null) { cell.DropDownStrings = fieldNames; } } else if (properties[i].Display != null && (properties[i].Display.Type == DisplayType.CLEMResourceName)) { cell.EditorType = EditorTypeEnum.DropDown; List <string> fieldNames = new List <string>(); fieldNames.AddRange(this.GetCLEMResourceNames(this.properties[i].Display.CLEMResourceNameResourceGroups)); // add any extras elements provided to the list. if (this.properties[i].Display.CLEMExtraEntries != null) { fieldNames.AddRange(this.properties[i].Display.CLEMExtraEntries); } if (fieldNames.Count != 0) { cell.DropDownStrings = fieldNames.ToArray(); } } else if (properties[i].Display != null && (properties[i].Display.Type == DisplayType.CLEMCropFileName)) { cell.EditorType = EditorTypeEnum.DropDown; List <string> fieldNames = new List <string>(); Simulation clemParent = Apsim.Parent(this.model, typeof(Simulation)) as Simulation; // get crop file names fieldNames.AddRange(Apsim.ChildrenRecursively(clemParent, typeof(FileCrop)).Select(a => a.Name).ToList()); fieldNames.AddRange(Apsim.ChildrenRecursively(clemParent, typeof(FileSQLiteCrop)).Select(a => a.Name).ToList()); if (fieldNames.Count != 0) { cell.DropDownStrings = fieldNames.ToArray(); } } else if (properties[i].Display != null && (properties[i].Display.Type == DisplayType.CLEMGraspFileName)) { cell.EditorType = EditorTypeEnum.DropDown; List <string> fieldNames = new List <string>(); Simulation clemParent = Apsim.Parent(this.model, typeof(Simulation)) as Simulation; // get GRASP file names fieldNames.AddRange(Apsim.ChildrenRecursively(clemParent, typeof(FileGRASP)).Select(a => a.Name).ToList()); fieldNames.AddRange(Apsim.ChildrenRecursively(clemParent, typeof(FileSQLiteGRASP)).Select(a => a.Name).ToList()); if (fieldNames.Count != 0) { cell.DropDownStrings = fieldNames.ToArray(); } } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.Model) { cell.EditorType = EditorTypeEnum.DropDown; string[] modelNames = GetModelNames(properties[i].Display.ModelType); if (modelNames != null) { cell.DropDownStrings = modelNames; } } else { object cellValue = properties[i].ValueWithArrayHandling; if (cellValue is DateTime) { cell.EditorType = EditorTypeEnum.DateTime; } else if (cellValue is bool) { cell.EditorType = EditorTypeEnum.Boolean; } else if (cellValue.GetType().IsEnum) { cell.EditorType = EditorTypeEnum.DropDown; cell.DropDownStrings = VariableProperty.EnumToStrings(cellValue); Enum cellValueAsEnum = cellValue as Enum; if (cellValueAsEnum != null) { cell.Value = VariableProperty.GetEnumDescription(cellValueAsEnum); } } else if (cellValue.GetType() == typeof(IPlant)) { cell.EditorType = EditorTypeEnum.DropDown; List <string> cropNames = new List <string>(); foreach (Model crop in Apsim.FindAll(model, typeof(IPlant))) { cropNames.Add(crop.Name); } cell.DropDownStrings = cropNames.ToArray(); } else if (properties[i].DataType == typeof(IPlant)) { List <string> plantNames = Apsim.FindAll(model, typeof(IPlant)).Select(m => m.Name).ToList(); cell.EditorType = EditorTypeEnum.DropDown; cell.DropDownStrings = plantNames.ToArray(); } else if (!string.IsNullOrWhiteSpace(properties[i].Display?.Values)) { explorerPresenter.ApsimXFile.Links.Resolve(model, allLinks: true, throwOnFail: false); MethodInfo method = model.GetType().GetMethod(properties[i].Display.Values); string[] values = ((IEnumerable <object>)method.Invoke(model, null))?.Select(v => v?.ToString())?.ToArray(); cell.EditorType = EditorTypeEnum.DropDown; cell.DropDownStrings = values; } else { cell.EditorType = EditorTypeEnum.TextBox; } } cell.IsRowReadonly = !IsPropertyEnabled(i); } IGridColumn descriptionColumn = grid.GetColumn(0); descriptionColumn.Width = -1; descriptionColumn.ReadOnly = true; IGridColumn valueColumn = grid.GetColumn(1); valueColumn.Width = -1; }
/// <summary> /// Find all properties from the model and fill this.properties. /// </summary> /// <param name="model">The mode object</param> protected virtual void FindAllProperties(IModel model) { this.model = model; bool filterByCategory = !((this.CategoryFilter == "") || (this.CategoryFilter == null)); bool filterBySubcategory = !((this.SubcategoryFilter == "") || (this.SubcategoryFilter == null)); if (this.model != null) { var orderedMembers = GetMembers(model); properties.Clear(); foreach (MemberInfo member in orderedMembers) { IVariable property = null; if (member is PropertyInfo) { property = new VariableProperty(this.model, member as PropertyInfo); } else if (member is FieldInfo) { property = new VariableField(this.model, member as FieldInfo); } if (property != null && property.Description != null && property.Writable) { // Only allow lists that are double[], int[], string[] or DateTime[] bool includeProperty = true; if (property.DataType.GetInterface("IList") != null) { includeProperty = true; } if (Attribute.IsDefined(member, typeof(SeparatorAttribute))) { SeparatorAttribute[] separators = Attribute.GetCustomAttributes(member, typeof(SeparatorAttribute)) as SeparatorAttribute[]; foreach (SeparatorAttribute separator in separators) { properties.Add(new VariableObject(separator.ToString())); // use a VariableObject for separators } } //If the above conditions have been met and, //If a CategoryFilter has been specified. //filter only those properties with a [Catagory] attribute that matches the filter. if (includeProperty && filterByCategory) { bool hasCategory = Attribute.IsDefined(member, typeof(CategoryAttribute), false); if (hasCategory) { CategoryAttribute catAtt = (CategoryAttribute)Attribute.GetCustomAttribute(member, typeof(CategoryAttribute)); if (catAtt.Category == this.CategoryFilter) { if (filterBySubcategory) { //the catAtt.Subcategory is by default given a value of //"Unspecified" if the Subcategory is not assigned in the Category Attribute. //so this line below will also handle "Unspecified" subcategories. includeProperty = (catAtt.Subcategory == this.SubcategoryFilter); } else { includeProperty = true; } } else { includeProperty = false; } } else { //if we are filtering on "Unspecified" category then there is no Category Attribute // just a Description Attribute on the property in the model. //So we still may need to include it in this case. if (this.CategoryFilter == "Unspecified") { includeProperty = true; } else { includeProperty = false; } } } if (includeProperty) { properties.Add(property); } if (property.DataType == typeof(DataTable)) { grid.DataSource = property.Value as DataTable; } } } } }
protected override void DrawVariableSpecificInspector() { SerializedProperty defaultValueProperty = VariableProperty.FindPropertyRelative("DefaultValue"); EditorGUILayout.PropertyField(defaultValueProperty); }
set => SetValue(VariableProperty, value);
/// <summary> /// Set the value of the specified property /// </summary> /// <param name="property">The property to set the value of</param> /// <param name="value">The value to set the property to</param> private void SetPropertyValue(Model childmodel, VariableProperty property, object value) { Commands.ChangeProperty cmd = new Commands.ChangeProperty(childmodel, property.Name, value); this.explorerPresenter.CommandHistory.Add(cmd, true); }
/// <summary> /// Format the grid. /// </summary> protected virtual void FormatGrid() { for (int i = 0; i < properties.Count; i++) { IGridCell cell = grid.GetCell(1, i); if (properties[i] is VariableObject) { cell.EditorType = EditorTypeEnum.TextBox; grid.SetRowAsSeparator(i, true); } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.TableName) { cell.EditorType = EditorTypeEnum.DropDown; if (storage == null) { storage = model.FindInScope <IDataStore>(); } cell.DropDownStrings = storage.Reader.TableNames.ToArray(); } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.CultivarName) { cell.EditorType = EditorTypeEnum.DropDown; IPlant crop; if (properties[i].Display.PlantName != null) { crop = model.FindInScope <IPlant>(properties[i].Display.PlantName); } else { crop = GetCrop(properties); } if (crop != null) { cell.DropDownStrings = PropertyPresenterHelpers.GetCultivarNames(crop); } } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.LifeCycleName) { cell.EditorType = EditorTypeEnum.DropDown; Zone zone = model.FindInScope <Zone>(); if (zone != null) { cell.DropDownStrings = PropertyPresenterHelpers.GetLifeCycleNames(zone); } } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.LifePhaseName) { cell.EditorType = EditorTypeEnum.DropDown; LifeCycle lifeCycle = GetLifeCycle(properties); if (lifeCycle != null) { cell.DropDownStrings = PropertyPresenterHelpers.GetPhaseNames(lifeCycle); } } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.FileName) { cell.EditorType = EditorTypeEnum.Button; } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.FileNames) { cell.EditorType = EditorTypeEnum.MultiFiles; } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.DirectoryName) { cell.EditorType = EditorTypeEnum.DirectoryChooser; } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.FieldName) { cell.EditorType = EditorTypeEnum.DropDown; List <string> fieldNames = GetFieldNames(); if (fieldNames != null) { fieldNames.Insert(0, string.Empty); cell.DropDownStrings = fieldNames.ToArray(); } } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.ResidueName && model is SurfaceOrganicMatter) { cell.EditorType = EditorTypeEnum.DropDown; string[] fieldNames = GetResidueNames(); if (fieldNames != null) { cell.DropDownStrings = fieldNames; } } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.Model) { cell.EditorType = EditorTypeEnum.DropDown; string[] modelNames = GetModelNames(properties[i].Display.ModelType); if (modelNames != null) { cell.DropDownStrings = modelNames; } } else { object cellValue = properties[i].ValueWithArrayHandling; if (cellValue is DateTime) { cell.EditorType = EditorTypeEnum.DateTime; } else if (cellValue is bool) { cell.EditorType = EditorTypeEnum.Boolean; } else if (cellValue.GetType().IsEnum) { cell.EditorType = EditorTypeEnum.DropDown; cell.DropDownStrings = VariableProperty.EnumToStrings(cellValue); Enum cellValueAsEnum = cellValue as Enum; if (cellValueAsEnum != null) { cell.Value = VariableProperty.GetEnumDescription(cellValueAsEnum); } } else if (cellValue.GetType() == typeof(IPlant)) { cell.EditorType = EditorTypeEnum.DropDown; List <string> cropNames = new List <string>(); foreach (Model crop in model.FindAllInScope <IPlant>()) { cropNames.Add(crop.Name); } cell.DropDownStrings = cropNames.ToArray(); } else if (properties[i].DataType == typeof(IPlant)) { cell.EditorType = EditorTypeEnum.DropDown; cell.DropDownStrings = model.FindAllInScope <IPlant>().OfType <IModel>().Select(m => m.Name).ToArray(); } else if (!string.IsNullOrWhiteSpace(properties[i].Display?.Values)) { explorerPresenter.ApsimXFile.Links.Resolve(model, allLinks: true, throwOnFail: false); BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy; MethodInfo method = model.GetType().GetMethod(properties[i].Display.Values, flags); string[] values = ((IEnumerable <object>)method.Invoke(model, properties[i].Display.ValuesArgs))?.Select(v => v?.ToString())?.ToArray(); cell.EditorType = EditorTypeEnum.DropDown; cell.DropDownStrings = values; } else { cell.EditorType = EditorTypeEnum.TextBox; } } cell.IsRowReadonly = !IsPropertyEnabled(i); } IGridColumn descriptionColumn = grid.GetColumn(0); descriptionColumn.Width = -1; descriptionColumn.ReadOnly = true; IGridColumn valueColumn = grid.GetColumn(1); valueColumn.Width = -1; }
/// <summary> /// Instantiates a Property object by reading metadata about /// the given property. /// </summary> /// <param name="obj">The object reference used to fetch the current value of the property.</param> /// <param name="metadata">Property metadata.</param> public Property(object obj, PropertyInfo metadata) { IModel model = obj as IModel; ID = Guid.NewGuid(); Name = metadata.GetCustomAttribute <DescriptionAttribute>()?.ToString(); if (string.IsNullOrEmpty(Name)) { Name = metadata.Name; } Tooltip = metadata.GetCustomAttribute <TooltipAttribute>()?.Tooltip; Separators = metadata.GetCustomAttributes <SeparatorAttribute>()?.Select(s => s.ToString())?.ToList(); Value = metadata.GetValue(obj); if (metadata.PropertyType == typeof(DateTime) || (metadata.PropertyType == typeof(DateTime?) && Value != null)) { // Note: ToShortDateString() uses the current culture, which is what we want in this case. Value = ((DateTime)Value).ToShortDateString(); } // ?else if property type isn't a struct? else if (Value != null && typeof(IModel).IsAssignableFrom(Value.GetType())) { Value = ((IModel)Value).Name; } else if (metadata.PropertyType.IsEnum) { Value = VariableProperty.GetEnumDescription((Enum)Enum.Parse(metadata.PropertyType, Value?.ToString())); } else if (metadata.PropertyType != typeof(bool) && metadata.PropertyType != typeof(System.Drawing.Color)) { Value = ReflectionUtilities.ObjectToString(Value, CultureInfo.CurrentCulture); } // fixme - need to fix this unmaintainable mess brought across from the old PropertyPresenter DisplayAttribute attrib = metadata.GetCustomAttribute <DisplayAttribute>(); DisplayType displayType = attrib?.Type ?? DisplayType.None; // For compatibility with the old PropertyPresenter, assume a default of // DisplayType.DropDown if the Values property is specified. if (displayType == DisplayType.None && !string.IsNullOrEmpty(attrib?.Values)) { displayType = DisplayType.DropDown; } switch (displayType) { case DisplayType.None: if (metadata.PropertyType.IsEnum) { // Enums use dropdown DropDownOptions = Enum.GetValues(metadata.PropertyType).Cast <Enum>() .Select(e => VariableProperty.GetEnumDescription(e)) .ToArray(); DisplayMethod = PropertyType.DropDown; } else if (typeof(IModel).IsAssignableFrom(metadata.PropertyType)) { // Model selector - use a dropdown containing names of all models in scope. DisplayMethod = PropertyType.DropDown; DropDownOptions = model.FindAllInScope() .Where(m => metadata.PropertyType.IsAssignableFrom(m.GetType())) .Select(m => m.Name) .ToArray(); } else if (metadata.PropertyType == typeof(bool)) { DisplayMethod = PropertyType.Checkbox; } else if (metadata.PropertyType == typeof(System.Drawing.Color)) { DisplayMethod = PropertyType.Colour; } else { DisplayMethod = PropertyType.SingleLineText; } break; case DisplayType.FileName: DisplayMethod = PropertyType.File; break; case DisplayType.FileNames: DisplayMethod = PropertyType.Files; break; case DisplayType.DirectoryName: DisplayMethod = PropertyType.Directory; break; case DisplayType.DropDown: string methodName = metadata.GetCustomAttribute <DisplayAttribute>().Values; if (methodName == null) { throw new ArgumentNullException($"When using DisplayType.DropDown, the Values property must be specified."); } BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy; MethodInfo method = model.GetType().GetMethod(methodName, flags); object[] args = metadata.GetCustomAttribute <DisplayAttribute>().ValuesArgs; // Attempt to resolve links - populating the dropdown may // require access to linked models. Simulations sims = model.FindAncestor <Simulations>(); if (sims != null) { sims.Links.Resolve(model, allLinks: true, throwOnFail: false); } DropDownOptions = ((IEnumerable <object>)method.Invoke(model, args))?.Select(v => v?.ToString())?.ToArray(); DisplayMethod = PropertyType.DropDown; break; case DisplayType.CultivarName: DisplayMethod = PropertyType.DropDown; IPlant plant = null; PropertyInfo plantProperty = model.GetType().GetProperties().FirstOrDefault(p => typeof(IPlant).IsAssignableFrom(p.PropertyType)); if (plantProperty != null) { plant = plantProperty.GetValue(model) as IPlant; } else { plant = model.FindInScope <IPlant>(); } if (plant != null) { DropDownOptions = PropertyPresenterHelpers.GetCultivarNames(plant); } break; case DisplayType.TableName: DisplayMethod = PropertyType.DropDown; DropDownOptions = model.FindInScope <IDataStore>()?.Reader?.TableNames?.ToArray(); break; case DisplayType.FieldName: DisplayMethod = PropertyType.DropDown; IDataStore storage = model.FindInScope <IDataStore>(); PropertyInfo tableNameProperty = model.GetType().GetProperties().FirstOrDefault(p => p.GetCustomAttribute <DisplayAttribute>()?.Type == DisplayType.TableName); string tableName = tableNameProperty?.GetValue(model) as string; if (storage != null && storage.Reader.TableNames.Contains(tableName)) { DropDownOptions = storage.Reader.ColumnNames(tableName).ToArray(); } break; case DisplayType.LifeCycleName: DisplayMethod = PropertyType.DropDown; Zone zone = model.FindInScope <Zone>(); if (zone != null) { DropDownOptions = PropertyPresenterHelpers.GetLifeCycleNames(zone); } break; case DisplayType.LifePhaseName: DisplayMethod = PropertyType.DropDown; LifeCycle lifeCycle = null; if (attrib.LifeCycleName != null) { lifeCycle = model.FindInScope <LifeCycle>(attrib.LifeCycleName); } else { foreach (PropertyInfo property in model.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (property.PropertyType == typeof(string)) { string value = property.GetValue(model) as string; LifeCycle match = model.FindInScope <LifeCycle>(value); if (match != null) { lifeCycle = match; break; } } } } if (lifeCycle != null) { DropDownOptions = PropertyPresenterHelpers.GetPhaseNames(lifeCycle).ToArray(); } break; case DisplayType.Model: DisplayMethod = PropertyType.DropDown; DropDownOptions = model.FindAllInScope().Where(m => metadata.PropertyType.IsAssignableFrom(m.GetType())) .Select(m => m.Name) .ToArray(); break; case DisplayType.ResidueName: if (model is SurfaceOrganicMatter surfaceOM) { DisplayMethod = PropertyType.DropDown; DropDownOptions = surfaceOM.ResidueTypeNames().ToArray(); break; } else { throw new NotImplementedException($"Display type {displayType} is only supported on models of type {typeof(SurfaceOrganicMatter).Name}, but model is of type {model.GetType().Name}."); } case DisplayType.MultiLineText: DisplayMethod = PropertyType.MultiLineText; if (Value is IEnumerable enumerable && metadata.PropertyType != typeof(string)) { Value = string.Join(Environment.NewLine, ((IEnumerable)metadata.GetValue(obj)).ToGenericEnumerable()); } break; // Should never happen - presenter should handle this(?) //case DisplayType.SubModel: default: throw new NotImplementedException($"Unknown display type {displayType}"); } // If the list of dropdown options doesn't contain the actual value of the // property, add that value to the list of valid options. if (DisplayMethod == PropertyType.DropDown && Value != null) { if (DropDownOptions == null) { DropDownOptions = new string[1] { Value.ToString() } } ; else if (!DropDownOptions.Contains(Value.ToString())) { List <string> values = DropDownOptions.ToList(); values.Add(Value.ToString()); DropDownOptions = values.ToArray(); } } }
/// <summary> /// Format the grid. /// </summary> private void FormatGrid() { for (int i = 0; i < this.properties.Count; i++) { IGridCell cell = this.grid.GetCell(1, i); if (this.properties[i] is VariableObject) { cell.EditorType = EditorTypeEnum.TextBox; grid.SetRowAsSeparator(i, true); } else if (this.properties[i].Display != null && this.properties[i].Display.Type == DisplayType.TableName) { cell.EditorType = EditorTypeEnum.DropDown; cell.DropDownStrings = this.storage.TableNames.ToArray(); } else if (this.properties[i].Display != null && this.properties[i].Display.Type == DisplayType.CultivarName) { cell.EditorType = EditorTypeEnum.DropDown; IPlant crop = this.GetCrop(this.properties); if (crop != null) { cell.DropDownStrings = this.GetCultivarNames(crop); } } else if (this.properties[i].Display != null && this.properties[i].Display.Type == DisplayType.FileName) { cell.EditorType = EditorTypeEnum.Button; } else if (this.properties[i].Display != null && this.properties[i].Display.Type == DisplayType.FieldName) { cell.EditorType = EditorTypeEnum.DropDown; string[] fieldNames = this.GetFieldNames(); if (fieldNames != null) { cell.DropDownStrings = fieldNames; } } else if (this.properties[i].Display != null && this.properties[i].Display.Type == DisplayType.ResidueName && this.model is Models.Surface.SurfaceOrganicMatter) { cell.EditorType = EditorTypeEnum.DropDown; string[] fieldNames = GetResidueNames(); if (fieldNames != null) { cell.DropDownStrings = fieldNames; } } else if (this.properties[i].Display != null && properties[i].Display.Type == DisplayType.Model) { cell.EditorType = EditorTypeEnum.DropDown; string[] modelNames = GetModelNames(properties[i].Display.ModelType); if (modelNames != null) { cell.DropDownStrings = modelNames; } } else { object cellValue = this.properties[i].ValueWithArrayHandling; if (cellValue is DateTime) { cell.EditorType = EditorTypeEnum.DateTime; } else if (cellValue is bool) { cell.EditorType = EditorTypeEnum.Boolean; } else if (cellValue.GetType().IsEnum) { cell.EditorType = EditorTypeEnum.DropDown; cell.DropDownStrings = VariableProperty.EnumToStrings(cellValue); Enum cellValueAsEnum = cellValue as Enum; if (cellValueAsEnum != null) { cell.Value = VariableProperty.GetEnumDescription(cellValueAsEnum); } } else if (cellValue.GetType() == typeof(IPlant)) { cell.EditorType = EditorTypeEnum.DropDown; List <string> cropNames = new List <string>(); foreach (Model crop in Apsim.FindAll(this.model, typeof(IPlant))) { cropNames.Add(crop.Name); } cell.DropDownStrings = cropNames.ToArray(); } else if (this.properties[i].DataType == typeof(IPlant)) { List <string> plantNames = Apsim.FindAll(this.model, typeof(IPlant)).Select(m => m.Name).ToList(); cell.EditorType = EditorTypeEnum.DropDown; cell.DropDownStrings = plantNames.ToArray(); } else { cell.EditorType = EditorTypeEnum.TextBox; } } } IGridColumn descriptionColumn = this.grid.GetColumn(0); descriptionColumn.Width = -1; descriptionColumn.ReadOnly = true; IGridColumn valueColumn = this.grid.GetColumn(1); valueColumn.Width = -1; }
/// <summary> /// User has changed the value of a cell. Validate the change /// apply the change. /// </summary> /// <param name="sender">Sender object</param> /// <param name="args">Event parameters</param> private void OnCellsChanged(object sender, GridCellsChangedArgs args) { List <ChangeProperty.Property> changes = new List <ChangeProperty.Property>(); foreach (var column in args.ChangedCells.GroupBy(c => c.ColIndex)) { VariableProperty property = properties[column.Key]; if (property == null || property.IsReadOnly) { continue; } // Get a deep copy of the property value. Array newArray = property.Value as Array; if (newArray == null && property.Value != null) { continue; } newArray = Clone(newArray, property.DataType.GetElementType()); // It's possible to change multiple values in the same column // simultaneously via multi-selection. If we just add a change // property command for each individual change, later changes // would overwrite the earlier changes. We need to merge all // changes to a single column into a single command then move // onto the next column. foreach (GridCellChangedArgs change in column) { if (change.NewValue == change.OldValue) { continue; // silently fail } if (change.RowIndex >= newArray.Length && string.IsNullOrEmpty(change.NewValue)) { continue; } // If the user has entered data into a new row, we will need to // resize all of the array properties. changes.AddRange(CheckArrayLengths(change)); // Need to convert user input to a string using the current // culture. object element = ReflectionUtilities.StringToObject(property.DataType.GetElementType(), change.NewValue, CultureInfo.CurrentCulture); if (newArray.Length <= change.RowIndex) { Resize(ref newArray, change.RowIndex + 1); } newArray.SetValue(element, change.RowIndex); } changes.Add(new ChangeProperty.Property(property.Object, property.Name, newArray)); } // Apply all changes to the model in a single undoable command. SetPropertyValue(new ChangeProperty(changes)); // Update the value shown in the grid. This needs to happen after // we have applied changes to the model for obvious reasons. foreach (GridCellChangedArgs cell in args.ChangedCells) { // Add new rows to the view's grid if necessary. while (grid.RowCount <= cell.RowIndex + 1) { grid.RowCount++; } grid.DataSource.Rows[cell.RowIndex][cell.ColIndex] = GetCellValue(cell.RowIndex, cell.ColIndex); } // If the user deleted an entire row, do a full refresh of the // grid. Otherwise, only refresh read-only columns (PAWC). if (RemoveEmptyRows()) { PopulateGrid(model); } else { UpdateReadOnlyProperties(); } }
/// <summary> /// Format the grid based on the data in the specified table. /// </summary> /// <param name="table">The table to use to format the grid.</param> private void FormatGrid(DataTable table) { Color[] cropColors = { Color.FromArgb(173, 221, 142), Color.FromArgb(247, 252, 185) }; Color[] predictedCropColors = { Color.FromArgb(233, 191, 255), Color.FromArgb(244, 226, 255) }; int cropIndex = 0; int predictedCropIndex = 0; Color foregroundColour = Color.Black; Color backgroundColour = Color.White; for (int col = 0; col < this.propertiesInGrid.Count; col++) { VariableProperty property = this.propertiesInGrid[col]; string columnName = property.Description; // crop colours if (property.CropName != null) { if (property.Metadata.Contains("Estimated")) { backgroundColour = predictedCropColors[predictedCropIndex]; foregroundColour = Color.Gray; if (columnName.Contains("XF")) { predictedCropIndex++; } if (predictedCropIndex >= predictedCropColors.Length) { predictedCropIndex = 0; } } else { backgroundColour = cropColors[cropIndex]; if (columnName.Contains("XF")) { cropIndex++; } if (cropIndex >= cropColors.Length) { cropIndex = 0; } } } // tool tips string[] toolTips = null; if (property.IsReadOnly) { foregroundColour = Color.Gray; toolTips = StringUtilities.CreateStringArray("Calculated", this.view.ProfileGrid.RowCount); } else { foregroundColour = Color.Black; toolTips = property.Metadata; } string format = property.Format; if (format == null || format == string.Empty) { format = "N3"; } IGridColumn gridColumn = this.view.ProfileGrid.GetColumn(col); gridColumn.Format = format; gridColumn.BackgroundColour = backgroundColour; gridColumn.ForegroundColour = foregroundColour; gridColumn.ReadOnly = property.IsReadOnly; for (int rowIndex = 0; rowIndex < toolTips.Length; rowIndex++) { IGridCell cell = this.view.ProfileGrid.GetCell(col, rowIndex); cell.ToolTip = toolTips[rowIndex]; } // colour the column headers of total columns. try { if (!double.IsNaN(property.Total)) { gridColumn.HeaderForegroundColour = Color.Red; } } catch (Exception err) { explorerPresenter.MainPresenter.ShowError(err); } } this.view.ProfileGrid.RowCount = 100; }
/// <summary> /// Format the grid. /// </summary> private void FormatGrid() { for (int i = 0; i < properties.Count; i++) { IGridCell cell = grid.GetCell(1, i); if (properties[i] is VariableObject) { cell.EditorType = EditorTypeEnum.TextBox; grid.SetRowAsSeparator(i, true); } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.TableName) { cell.EditorType = EditorTypeEnum.DropDown; cell.DropDownStrings = storage.TableNames.ToArray(); } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.CultivarName) { cell.EditorType = EditorTypeEnum.DropDown; IPlant crop = GetCrop(properties); if (crop != null) { cell.DropDownStrings = GetCultivarNames(crop); } } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.FileName) { cell.EditorType = EditorTypeEnum.Button; } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.FieldName) { cell.EditorType = EditorTypeEnum.DropDown; List <string> fieldNames = GetFieldNames(); if (fieldNames != null) { fieldNames.Insert(0, string.Empty); cell.DropDownStrings = fieldNames.ToArray(); } } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.ResidueName && model is SurfaceOrganicMatter) { cell.EditorType = EditorTypeEnum.DropDown; string[] fieldNames = GetResidueNames(); if (fieldNames != null) { cell.DropDownStrings = fieldNames; } } else if (properties[i].Display != null && (properties[i].Display.Type == DisplayType.CLEMResourceName)) { cell.EditorType = EditorTypeEnum.DropDown; List <string> fieldNames = new List <string>(); fieldNames.AddRange(this.GetCLEMResourceNames(this.properties[i].Display.CLEMResourceNameResourceGroups)); // add any extras elements provided to the list. if (this.properties[i].Display.CLEMExtraEntries != null) { fieldNames.AddRange(this.properties[i].Display.CLEMExtraEntries); } if (fieldNames.Count != 0) { cell.DropDownStrings = fieldNames.ToArray(); } } else if (properties[i].Display != null && (properties[i].Display.Type == DisplayType.CLEMCropFileName)) { cell.EditorType = EditorTypeEnum.DropDown; List <string> fieldNames = new List <string>(); Simulation clemParent = Apsim.Parent(this.model, typeof(Simulation)) as Simulation; // get crop file names fieldNames.AddRange(Apsim.ChildrenRecursively(clemParent, typeof(FileCrop)).Select(a => a.Name).ToList()); if (fieldNames.Count != 0) { cell.DropDownStrings = fieldNames.ToArray(); } } else if (properties[i].Display != null && (properties[i].Display.Type == DisplayType.CLEMGraspFileName)) { cell.EditorType = EditorTypeEnum.DropDown; List <string> fieldNames = new List <string>(); Simulation clemParent = Apsim.Parent(this.model, typeof(Simulation)) as Simulation; // get GRASP file names fieldNames.AddRange(Apsim.Children(clemParent, typeof(FileGRASP)).Select(a => a.Name).ToList()); fieldNames.AddRange(Apsim.Children(clemParent, typeof(FileSQLiteGRASP)).Select(a => a.Name).ToList()); if (fieldNames.Count != 0) { cell.DropDownStrings = fieldNames.ToArray(); } } else if (properties[i].Display != null && properties[i].Display.Type == DisplayType.Model) { cell.EditorType = EditorTypeEnum.DropDown; string[] modelNames = GetModelNames(properties[i].Display.ModelType); if (modelNames != null) { cell.DropDownStrings = modelNames; } } else { object cellValue = properties[i].ValueWithArrayHandling; if (cellValue is DateTime) { cell.EditorType = EditorTypeEnum.DateTime; } else if (cellValue is bool) { cell.EditorType = EditorTypeEnum.Boolean; } else if (cellValue.GetType().IsEnum) { cell.EditorType = EditorTypeEnum.DropDown; cell.DropDownStrings = VariableProperty.EnumToStrings(cellValue); Enum cellValueAsEnum = cellValue as Enum; if (cellValueAsEnum != null) { cell.Value = VariableProperty.GetEnumDescription(cellValueAsEnum); } } else if (cellValue.GetType() == typeof(IPlant)) { cell.EditorType = EditorTypeEnum.DropDown; List <string> cropNames = new List <string>(); foreach (Model crop in Apsim.FindAll(model, typeof(IPlant))) { cropNames.Add(crop.Name); } cell.DropDownStrings = cropNames.ToArray(); } else if (properties[i].DataType == typeof(IPlant)) { List <string> plantNames = Apsim.FindAll(model, typeof(IPlant)).Select(m => m.Name).ToList(); cell.EditorType = EditorTypeEnum.DropDown; cell.DropDownStrings = plantNames.ToArray(); } else { cell.EditorType = EditorTypeEnum.TextBox; } } } IGridColumn descriptionColumn = grid.GetColumn(0); descriptionColumn.Width = -1; descriptionColumn.ReadOnly = true; IGridColumn valueColumn = grid.GetColumn(1); valueColumn.Width = -1; }
/// <summary> /// Attach the model to the view. /// </summary> /// <param name="model">The underlying model we are to use</param> /// <param name="view">The underlying view we are to attach to</param> /// <param name="explorerPresenter">Our parent explorerPresenter</param> public void Attach(object model, object view, ExplorerPresenter explorerPresenter) { this.model = model as Model; this.view = view as IProfileView; profileGrid.Attach(model, this.view.ProfileGrid, explorerPresenter); this.explorerPresenter = explorerPresenter; this.view.ShowView(false); // Setup the property presenter and view. Hide the view if there are no properties to show. this.propertyPresenter = new PropertyPresenter(); this.propertyPresenter.Attach(this.model, this.view.PropertyGrid, this.explorerPresenter); // Create a list of profile (array) properties. Create a table from them and // hand the table to the profile grid. this.FindAllProperties(this.model); // Populate the grid this.PopulateGrid(); // Populate the graph. this.graph = Utility.Graph.CreateGraphFromResource(model.GetType().Name + "Graph"); if (this.graph == null) { this.view.ShowGraph(false); } else { this.parentForGraph = this.model.Parent as IModel; if (this.parentForGraph != null) { this.parentForGraph.Children.Add(this.graph); this.graph.Parent = this.parentForGraph; this.view.ShowGraph(true); int padding = (this.view as ProfileView).MainWidget.Allocation.Width / 2 / 2; this.view.Graph.LeftRightPadding = padding; this.graphPresenter = new GraphPresenter(); for (int col = 0; col < this.propertiesInGrid.Count; col++) { VariableProperty property = this.propertiesInGrid[col]; string columnName = property.Description; // crop colours if (property.CropName != null && columnName.Contains("LL")) { Series cropLLSeries = new Series(); cropLLSeries.Name = columnName; cropLLSeries.Colour = ColourUtilities.ChooseColour(this.graph.Children.Count); cropLLSeries.Line = LineType.Solid; cropLLSeries.Marker = MarkerType.None; cropLLSeries.Type = SeriesType.Scatter; cropLLSeries.ShowInLegend = true; cropLLSeries.XAxis = Axis.AxisType.Top; cropLLSeries.YAxis = Axis.AxisType.Left; cropLLSeries.YFieldName = "[Soil].DepthMidPoints"; cropLLSeries.XFieldName = "[" + (property.Object as Model).Name + "]." + property.Name; cropLLSeries.Parent = this.graph; this.graph.Children.Add(cropLLSeries); } } this.graphPresenter.Attach(this.graph, this.view.Graph, this.explorerPresenter); } } // Trap the invoking of the ProfileGrid 'CellValueChanged' event so that // we can save the contents. this.view.ProfileGrid.CellsChanged += this.OnProfileGridCellValueChanged; // Trap the right click on column header so that we can potentially put // units on the context menu. this.view.ProfileGrid.GridColumnClicked += this.OnGridColumnClicked; // Trap the model changed event so that we can handle undo. this.explorerPresenter.CommandHistory.ModelChanged += this.OnModelChanged; this.view.ShowView(true); }
/// <summary> /// Instantiates a Property object by reading metadata about /// the given property. /// </summary> /// <param name="obj">The object reference used to fetch the current value of the property.</param> /// <param name="metadata">Property metadata.</param> public Property(object obj, PropertyInfo metadata) { IModel model = obj as IModel; ID = Guid.NewGuid(); Name = metadata.GetCustomAttribute <DescriptionAttribute>()?.ToString(); if (string.IsNullOrEmpty(Name)) { Name = metadata.Name; } Tooltip = metadata.GetCustomAttribute <TooltipAttribute>()?.Tooltip; Separators = metadata.GetCustomAttributes <SeparatorAttribute>()?.Select(s => s.ToString())?.ToList(); Value = metadata.GetValue(obj); if (metadata.PropertyType == typeof(DateTime) || (metadata.PropertyType == typeof(DateTime?) && Value != null)) { // Note: ToShortDateString() uses the current culture, which is what we want in this case. Value = ((DateTime)Value).ToShortDateString(); } // ?else if property type isn't a struct? else if (Value != null && typeof(IModel).IsAssignableFrom(Value.GetType())) { Value = ((IModel)Value).Name; } else if (metadata.PropertyType != typeof(bool) && metadata.PropertyType != typeof(System.Drawing.Color)) { Value = ReflectionUtilities.ObjectToString(Value, CultureInfo.CurrentCulture); } // fixme - need to fix this unmaintainable mess brought across from the old PropertyPresenter DisplayAttribute attrib = metadata.GetCustomAttribute <DisplayAttribute>(); DisplayType displayType = attrib?.Type ?? DisplayType.None; switch (displayType) { case DisplayType.None: if (metadata.PropertyType.IsEnum) { // Enums use dropdown DropDownOptions = Enum.GetValues(metadata.PropertyType).Cast <Enum>() .Select(e => VariableProperty.GetEnumDescription(e)) .ToArray(); DisplayMethod = PropertyType.DropDown; } else if (typeof(IModel).IsAssignableFrom(metadata.PropertyType)) { // Model selector - use a dropdown containing names of all models in scope. DisplayMethod = PropertyType.DropDown; DropDownOptions = model.FindAllInScope() .Where(m => metadata.PropertyType.IsAssignableFrom(m.GetType())) .Select(m => m.Name) .ToArray(); } else if (metadata.PropertyType == typeof(bool)) { DisplayMethod = PropertyType.Checkbox; } else if (metadata.PropertyType == typeof(System.Drawing.Color)) { DisplayMethod = PropertyType.Colour; } else { DisplayMethod = PropertyType.SingleLineText; } break; case DisplayType.FileName: DisplayMethod = PropertyType.File; break; case DisplayType.FileNames: DisplayMethod = PropertyType.Files; break; case DisplayType.DirectoryName: DisplayMethod = PropertyType.Directory; break; case DisplayType.DropDown: string methodName = metadata.GetCustomAttribute <DisplayAttribute>().Values; MethodInfo method = model.GetType().GetMethod(methodName); DropDownOptions = ((IEnumerable <object>)method.Invoke(model, null))?.Select(v => v?.ToString())?.ToArray(); DisplayMethod = PropertyType.DropDown; break; case DisplayType.CultivarName: DisplayMethod = PropertyType.DropDown; IPlant plant = null; PropertyInfo plantProperty = model.GetType().GetProperties().FirstOrDefault(p => typeof(IPlant).IsAssignableFrom(p.PropertyType)); if (plantProperty != null) { plant = plantProperty.GetValue(model) as IPlant; } else { plant = model.FindInScope <IPlant>(); } DropDownOptions = PropertyPresenterHelpers.GetCultivarNames(plant); break; case DisplayType.TableName: DisplayMethod = PropertyType.DropDown; DropDownOptions = model.FindInScope <IDataStore>()?.Reader?.TableNames?.ToArray(); break; case DisplayType.FieldName: DisplayMethod = PropertyType.DropDown; IDataStore storage = model.FindInScope <IDataStore>(); PropertyInfo tableNameProperty = model.GetType().GetProperties().FirstOrDefault(p => p.GetCustomAttribute <DisplayAttribute>()?.Type == DisplayType.TableName); string tableName = tableNameProperty?.GetValue(model) as string; if (storage != null && storage.Reader.TableNames.Contains(tableName)) { DropDownOptions = storage.Reader.ColumnNames(tableName).ToArray(); } break; case DisplayType.LifeCycleName: DisplayMethod = PropertyType.DropDown; Zone zone = model.FindInScope <Zone>(); if (zone != null) { DropDownOptions = PropertyPresenterHelpers.GetLifeCycleNames(zone); } break; case DisplayType.LifePhaseName: DisplayMethod = PropertyType.DropDown; LifeCycle lifeCycle = null; if (attrib.LifeCycleName != null) { lifeCycle = model.FindInScope <LifeCycle>(attrib.LifeCycleName); } else { foreach (PropertyInfo property in model.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (property.PropertyType == typeof(string)) { string value = property.GetValue(model) as string; LifeCycle match = model.FindInScope <LifeCycle>(value); if (match != null) { lifeCycle = match; break; } } } } if (lifeCycle != null) { DropDownOptions = PropertyPresenterHelpers.GetPhaseNames(lifeCycle).ToArray(); } break; case DisplayType.Model: DisplayMethod = PropertyType.DropDown; DropDownOptions = model.FindAllInScope().Where(m => metadata.PropertyType.IsAssignableFrom(m.GetType())) .Select(m => m.Name) .ToArray(); break; case DisplayType.ResidueName: if (model is SurfaceOrganicMatter surfaceOM) { DisplayMethod = PropertyType.DropDown; DropDownOptions = surfaceOM.ResidueTypeNames().ToArray(); break; } else { throw new NotImplementedException($"Display type {displayType} is only supported on models of type {typeof(SurfaceOrganicMatter).Name}, but model is of type {model.GetType().Name}."); } // Should never happen - presenter should handle this(?) //case DisplayType.SubModel: default: throw new NotImplementedException($"Unknown display type {displayType}"); } }