/// <summary> /// Write member information into <see cref="Option"/>s. /// <remarks> /// The function basically dispatches between different visitor functions. /// </remarks> /// </summary> /// <param name="optionGroup">The OptionGroup to contain the configuration.</param> /// <param name="config">The input configuration.</param> private void VisitMembers(OptionGroup optionGroup, object config) { foreach (var member in toplevelItems) { VisitMember(member, optionGroup, config); } foreach (var method in methods) { VisitMethod(method, optionGroup, config); } }
/// <summary> /// Convert the input configuration into an <see cref="OptionGroup"/> usable by an option editor. /// </summary> /// <remarks> /// First, all members are *collected* and stored in their respective maps and lists. /// Next, members are sorted by the position information contained in the corresponding <see cref="OptionGroupAttribute"/>. /// Finally, all members are visited and the extracted data relevant to build ui components are written to /// an <see cref="Option"/>. /// </remarks> /// <param name="config">The input configuration written by the developer.</param> /// <returns>The output configuration usable by an option editor.</returns> public OptionGroup Convert(object config) { var type = config.GetType(); // collect members CollectMembers(type, config); // sort members toplevelItems.Sort(new MemberComparer()); foreach (KeyValuePair <string, List <MemberInfo> > entry in groupMapping) { entry.Value.Sort(new MemberComparer()); } // visit members var topLevelGroup = new OptionGroup(); SetLabel(type, topLevelGroup); VisitMembers(topLevelGroup, config); return(topLevelGroup); }
/// <summary> /// Visit a member. /// </summary> /// <remarks> /// The function dispatches between different visit functions based on type of the member. /// </remarks> /// <param name="member">The member to visit.</param> /// <param name="optionGroup">The output data object.</param> /// <param name="config">The input configuration.</param> private void VisitMember(MemberInfo member, OptionGroup optionGroup, object config) { if (ShouldIgnoreMember(member)) { return; } if (IsOptionGroup(member)) { optionGroup.ChildOptions.Add(VisitGroup(member, config)); } else { if (member is FieldInfo) { optionGroup.ChildOptions.Add(VisitField((FieldInfo)member, config)); } else if (member is PropertyInfo) { optionGroup.ChildOptions.Add(VisitProperty((PropertyInfo)member, config)); } } }
/// <summary> /// Creates a group box and it's children /// </summary> /// <param name="optionGroup"></param> /// <returns></returns> private Control CreateGroupBox(OptionGroup optionGroup) { GroupBox box = new GroupBox(); box.Text = optionGroup.Label; box.Width = rootPanel.Width - (gbPaddingLeft + gbPaddingRight + topLevelGroupPaddingLeft + topLevelGroupPaddingRight); var Y = box.Location.Y + gbPaddingTop; foreach (var child in optionGroup.ChildOptions) { Control groupChild = CreateControl(child); groupChild.Location = new Point(gbPaddingLeft, Y); groupChild.Width = rootPanel.Width - (gbPaddingLeft + gbPaddingRight + topLevelGroupPaddingLeft + topLevelGroupPaddingRight); box.Controls.Add(groupChild); Y += groupChild.Height; } box.Height = Y + gbPaddingBottom; return(box); }
/// <summary> /// Visit a group and return an <see cref="OptionGroup"/> with all relevant information to build a component. /// </summary> /// <remarks> /// The following information is written: /// - group name /// - text label /// - custom attributes /// <para> /// If the <see cref="groupMapping"/> contains child members of this groups, these are <see cref="VisitMember">visited</see> as well. /// </para> /// </remarks> /// <param name="groupMember">The group to process.</param> /// <param name="config">The input configuration.</param> private OptionGroup VisitGroup(MemberInfo groupMember, object config) { var g = new OptionGroup(); g.Name = groupMember.Name; g.ComponentType = ComponentTypes.OptionGroup; SetLabel(groupMember, g); object[] attrs = groupMember.GetCustomAttributes(false); foreach (var attribute in attrs) { VisitAttribute((Attribute)attribute, g); } var name = g.Name.ToLower(); if (groupMapping.ContainsKey(name)) { var memberInfos = groupMapping[name]; foreach (var memberInfo in memberInfos) { VisitMember(memberInfo, g, config); } } return(g); }
private static void VisitMethod(MethodInfo method, OptionGroup optionGroup, object config) { // methods aren't used right now }
/// <summary> /// Creates a foldable top-level grouping and it's children /// </summary> /// <param name="optionGroup"></param> private SplitContainer AddTopLevelOptionGroup(OptionGroup optionGroup) { SplitContainer splitContainer = new SplitContainer(); splitContainer.Orientation = Orientation.Horizontal; splitContainer.BackColor = Color.LightGray; splitContainer.Width = rootPanel.Width - (rootPanel.Width - (topLevelGroupPaddingLeft + topLevelGroupPaddingRight + 100)); splitContainer.IsSplitterFixed = true; var groupLabelButton = new Label() { Text = "▶ " + optionGroup.Label, BackColor = Color.Gray, ForeColor = Color.White, Dock = DockStyle.Fill, TextAlign = ContentAlignment.MiddleLeft, AutoSize = false }; groupLabelButton.Click += (sender, args) => { toggleHeaderCollapse(splitContainer); }; splitContainer.Panel1.Click += (sender, args) => { toggleHeaderCollapse(splitContainer); }; splitContainer.Panel1.Controls.Add(groupLabelButton); splitContainer.Panel1.BackColor = Color.Gray; splitContainer.Panel1.Size = new Size(rootPanel.Width - (topLevelGroupPaddingLeft + topLevelGroupPaddingRight), 30); splitContainer.Panel1.AutoSize = true; var Y = topLevelGroupPaddingTop; foreach (var option in (optionGroup).ChildOptions) { // a group containing more elements if (option.ComponentType == ComponentTypes.OptionGroup) { var optionControl = CreateGroupBox(option as OptionGroup); optionControl.Location = new Point(topLevelGroupPaddingLeft, Y); splitContainer.Panel2.Controls.Add(optionControl); Y += optionControl.Height; } else { // single elements var optionControl = CreateControl(option); splitContainer.Panel2.Controls.Add(optionControl); optionControl.Location = new Point(topLevelGroupPaddingLeft, Y); Y += optionControl.Height; } } splitContainer.Panel2.BackColor = Color.White; splitContainer.Panel1MinSize = 30; splitContainer.Panel2MinSize = 1; splitContainer.Panel2.AutoSize = true; splitContainer.FixedPanel = FixedPanel.Panel1; splitContainer.SplitterDistance = 10; splitContainer.MinimumSize = new Size(rootPanel.Width, 10); splitContainer.Size = new Size(rootPanel.Width, 10); splitContainer.Panel2Collapsed = true; if (optionGroup.Label == "Description") { if (splitContainer.Panel2.Controls[0] is RichTextBox) { // rtf boxes can be isolated RichTextBox text = (RichTextBox)splitContainer.Panel2.Controls[0]; text.Width = rootPanel.Width - (topLevelGroupPaddingLeft + topLevelGroupPaddingRight); text.BorderStyle = BorderStyle.None; text.ScrollBars = RichTextBoxScrollBars.None; var content = text.Rtf; text.Text = String.Empty; ContentsResizedEventHandler text_OnContentsResized = null; text_OnContentsResized = delegate(object o, ContentsResizedEventArgs args) { text.Height = args.NewRectangle.Height; splitContainer.Height = topLevelGroupPaddingBottom + topLevelGroupPaddingTop + args.NewRectangle.Height; text.ContentsResized -= text_OnContentsResized; }; text.ContentsResized += text_OnContentsResized; text.Rtf = content; toggleHeaderCollapse(splitContainer); } } return(splitContainer); }