{"id":1461,"date":"2016-03-06T09:07:41","date_gmt":"2016-03-06T09:07:41","guid":{"rendered":"http:\/\/mindfusion.eu\/blog\/?p=1461"},"modified":"2021-01-20T17:27:23","modified_gmt":"2021-01-20T17:27:23","slug":"creating-custom-compositenode-components","status":"publish","type":"post","link":"https:\/\/mindfusion.dev\/blog\/creating-custom-compositenode-components\/","title":{"rendered":"Creating custom CompositeNode components"},"content":{"rendered":"<p>In this post we&#8217;ll examine how <a href=\"http:\/\/www.mindfusion.dev\/onlinehelp\/flowchartnet\/index.htm?T_MindFusion_Diagramming_CompositeNode.htm\">CompositeNode<\/a> components work in MindFusion.Diagramming for Windows Forms, and in the process create a custom radio button component. You can find the completed sample project here: <a href=\"https:\/\/mindfusion.dev\/_samples\/RadioComponent.zip\">RadioComponent.zip<\/a><\/p>\n<p>CompositeNode was created as alternative of the <a href=\"http:\/\/www.mindfusion.dev\/onlinehelp\/flowchartnet\/index.htm?T_MindFusion_Diagramming_WinForms_ControlNode.htm\">ControlNode<\/a> class, which lets you present any Windows Forms control as a diagram node. ControlNode has many advantages, such as letting you design the hosted user controls using Visual Studio designer, reusing them in other parts of the user interface, and including complex framework or third-party controls as their children. From the fact that each user control creates a hierarchy of Win32 windows come some disadvantages too:<\/p>\n<ul>\n<li>ControlNodes cannot mix with other diagram elements in the Z order but are always drawn on top<\/li>\n<li>performance deteriorates if showing hundreds of nodes<\/li>\n<li>mouse events might not reach the diagram if hosted controls capture mouse input<\/li>\n<li>print and export might not be able to reproduce the appearance of hosted controls without additional work (handling PaintControl event)<\/li>\n<\/ul>\n<p>On the other hand, CompositeNode does all its drawing in DiagramView control&#8217;s canvas and is not affected by the issues listed above. CompositeNode lets you build node&#8217;s UI by composing hierarchy of components derived from <a href=\"http:\/\/www.mindfusion.dev\/onlinehelp\/flowchartnet\/index.htm?T_MindFusion_Diagramming_Components_ComponentBase.htm\">ComponentBase<\/a> class. Pre-defined components include layout panels, read-only or editable text fields, images, borders, buttons, check-boxes and sliders. If the UI component you need isn&#8217;t provided out of the box, you could still implement it as a custom class that derives from ComponentBase or more specific type and overriding the GetDesiredSize, ArrangeComponents and Draw methods. Lets see how that works using a RadioButtonComponent as an example.<\/p>\n<p>Derive RadioButtonComponent from <a href=\"http:\/\/www.mindfusion.dev\/onlinehelp\/flowchartnet\/index.htm?T_MindFusion_Diagramming_Components_CheckBoxComponent.htm\">CheckBoxComponent<\/a> so we reuse its IsChecked and Content properties:<\/p>\n<pre>class RadioButtonComponent : CheckBoxComponent\n{\n}\n<\/pre>\n<p>CompositeNode relies on a dynamic layout system that lets components determine their size by overriding GetDesiredSize method, and arranging children in allocated size by means of ArrangeComponents method. For radio button we&#8217;ll call its base class to measure content size and add enough space for drawing the radio graphics element (a circle) horizontally, while fitting it in measured height:<\/p>\n<pre>float RadioSize(SizeF size)\n{\n\treturn Math.Min(size.Width, size.Height);\n}\n\npublic override SizeF GetDesiredSize(SizeF availableSize, IGraphics graphics)\n{\n\tvar s = base.GetDesiredSize(availableSize, graphics);\n\ts.Width += RadioSize(s);\n\treturn s;\n}\n<\/pre>\n<p>ArrangeComponents calls the base class to arrange its content on the right side of available space:<\/p>\n<pre>public override void ArrangeComponents(RectangleF availableSpace, IGraphics graphics)\n{\n\tvar radioSize = RadioSize(availableSpace.Size);\n\tavailableSpace.X += radioSize;\n\tavailableSpace.Width -= radioSize;\n\tbase.ArrangeComponents(availableSpace, graphics);\n}\n<\/pre>\n<p>Now override Draw and render standard radio button graphics on the left side of the component, and content on the right side:<\/p>\n<pre>public override void Draw(IGraphics graphics, RenderOptions options)\n{\n\tvar radioSize = RadioSize(Bounds.Size);\n\tvar r = radioSize \/ 2 - 1;\n\tvar cr = r - 1;\n\n\tgraphics.FillEllipse(Brushes.White, Bounds.X + 1, Bounds.Y + 1, 2 * r, 2 * r);\n\tusing (var pen = new System.Drawing.Pen(Color.Black, 0.1f))\n\t\tgraphics.DrawEllipse(pen, Bounds.X + 1, Bounds.Y + 1, 2 * r, 2 * r);\n\tif (IsChecked)\n\t\tgraphics.FillEllipse(Brushes.Black, Bounds.X + 2, Bounds.Y + 2, 2 * cr, 2 * cr);\n\n\tGraphicsState s = graphics.Save();\n\tgraphics.TranslateTransform(radioSize - 1 + Bounds.X, Bounds.Y);\n\tContent.Draw(graphics, options);\n\tgraphics.Restore(s);\n}\n<\/pre>\n<p>We&#8217;ll want only one radio from a group to be selected. For our purposes we can count all radio buttons placed inside same stack panel as part of same group. Override the OnClick method to unselect all buttons in parent panel and select the clicked one:<\/p>\n<pre>protected override void OnClicked(EventArgs e)\n{\n\tvar parentStack = Parent as StackPanel;\n\tif (parentStack != null)\n\t{\n\t\tforeach (var child in parentStack.Components)\n\t\t{\n\t\t\tvar radio = child as RadioButtonComponent;\n\t\t\tif (radio != null)\n\t\t\t\tradio.IsChecked = false;\n\t\t}\n\t}\n\tthis.IsChecked = true;\n}\n<\/pre>\n<p>That&#8217;s it, the radio button component is ready with just a screenful of code \ud83d\ude42 Let&#8217;s check how it works by creating an OptionNode class that shows a group of radio buttons and exposes a property to access or change selected one:<\/p>\n<pre>class OptionNode : CompositeNode\n{\n}\n<\/pre>\n<p>You could create the stack panel and radio buttons from code if you need more dynamic configuration, e.g. one with variable number of radio buttons. For this example we&#8217;ll just load a fixed template consisting of four buttons from XML:<\/p>\n<pre id=\"line1\">const string Template = @\"\n<span id=\"line319\"><\/span>\t&lt;<span class=\"start-tag\">simplepanel<\/span>&gt;\n<span id=\"line320\"><\/span>\n<span id=\"line321\"><\/span>        &lt;<span class=\"start-tag\">shape<\/span> <span class=\"attribute-name\">name<\/span>=\"\" <span class=\"attribute-name error error\" title=\"Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.\nQuote in attribute name. Probable cause: Matching quote missing somewhere earlier.\">shape\"\"<\/span>=\"\" <span class=\"attribute-name\">shape<\/span>=\"\" <span class=\"attribute-name error error\" title=\"Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.\nQuote in attribute name. Probable cause: Matching quote missing somewhere earlier.\">roundrect\"\"<\/span>=\"\"&gt;\n<span id=\"line322\"><\/span>\n<span id=\"line323\"><\/span>\t\t&lt;<span class=\"start-tag\">border<\/span> <span class=\"attribute-name\">padding<\/span>=\"\" <span class=\"attribute-name error error\" title=\"Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.\nQuote in attribute name. Probable cause: Matching quote missing somewhere earlier.\">2\"\"<\/span>=\"\"&gt;\n<span id=\"line324\"><\/span>\n<span id=\"line325\"><\/span>\t\t\t&lt;<span class=\"start-tag\">stackpanel<\/span> <span class=\"attribute-name\">name<\/span>=\"\" <span class=\"attribute-name error error\" title=\"Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.\nQuote in attribute name. Probable cause: Matching quote missing somewhere earlier.\">radiogroup\"\"<\/span>=\"\" <span class=\"attribute-name\">orientation<\/span>=\"\" <span class=\"attribute-name error error\" title=\"Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.\nQuote in attribute name. Probable cause: Matching quote missing somewhere earlier.\">vertical\"\"<\/span>=\"\" <span class=\"attribute-name\">spacing<\/span>=\"\" <span class=\"attribute-name error error\" title=\"Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.\nQuote in attribute name. Probable cause: Matching quote missing somewhere earlier.\">1\"\"<\/span>=\"\" <span class=\"attribute-name\">horizontalalignment<\/span>=\"\" <span class=\"attribute-name error error\" title=\"Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.\nQuote in attribute name. Probable cause: Matching quote missing somewhere earlier.\">center\"\"<\/span>=\"\"&gt;\n<span id=\"line326\"><\/span>\t\t\t\t&lt;<span class=\"start-tag\">radiobuttoncomponent<\/span> <span class=\"attribute-name\">padding<\/span>=\"\" <span class=\"attribute-name error error\" title=\"Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.\nQuote in attribute name. Probable cause: Matching quote missing somewhere earlier.\">2\"\"<\/span>=\"\"&gt;\n<span id=\"line327\"><\/span>\t\t\t\t\t&lt;<span class=\"start-tag\">radiobuttoncomponent.content<\/span>&gt;\n<span id=\"line328\"><\/span>\t\t\t\t\t\t&lt;<span class=\"start-tag\">text<\/span> <span class=\"attribute-name\">text<\/span>=\"\" <span class=\"attribute-name\">option<\/span>=\"\" <span class=\"attribute-name error error\" title=\"Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.\nQuote in attribute name. Probable cause: Matching quote missing somewhere earlier.\">1\"\"<\/span>=\"\" <span class=\"attribute-name\">font<\/span>=\"\" <span class=\"attribute-name\">verdana,<\/span>=\"\" <span class=\"attribute-name\">3world,<\/span>=\"\" <span class=\"attribute-name\">style<\/span>=\"<a class=\"attribute-value\">Bold<span class=\"entity\">&amp;quot;<\/span><span class=\"entity\">&amp;quot;<\/span><\/a>\"&gt;\n<span id=\"line329\"><\/span>\t\t\t\t\t&lt;\/<span class=\"end-tag\">text<\/span>&gt;&lt;\/<span class=\"end-tag\">radiobuttoncomponent.content<\/span>&gt;\n<span id=\"line330\"><\/span>\t\t\t\t&lt;\/<span class=\"end-tag\">radiobuttoncomponent<\/span>&gt;\n<span id=\"line331\"><\/span>\t\t\t\t&lt;<span class=\"start-tag\">radiobuttoncomponent<\/span> <span class=\"attribute-name\">padding<\/span>=\"\" <span class=\"attribute-name error error\" title=\"Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.\nQuote in attribute name. Probable cause: Matching quote missing somewhere earlier.\">2\"\"<\/span>=\"\"&gt;\n<span id=\"line332\"><\/span>\t\t\t\t\t&lt;<span class=\"start-tag\">radiobuttoncomponent.content<\/span>&gt;\n<span id=\"line333\"><\/span>\t\t\t\t\t\t&lt;<span class=\"start-tag\">text<\/span> <span class=\"attribute-name\">text<\/span>=\"\" <span class=\"attribute-name\">option<\/span>=\"\" <span class=\"attribute-name error error\" title=\"Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.\nQuote in attribute name. Probable cause: Matching quote missing somewhere earlier.\">2\"\"<\/span>=\"\" <span class=\"attribute-name\">font<\/span>=\"\" <span class=\"attribute-name\">verdana,<\/span>=\"\" <span class=\"attribute-name\">3world,<\/span>=\"\" <span class=\"attribute-name\">style<\/span>=\"<a class=\"attribute-value\">Bold<span class=\"entity\">&amp;quot;<\/span><span class=\"entity\">&amp;quot;<\/span><\/a>\"&gt;\n<span id=\"line334\"><\/span>\t\t\t\t\t&lt;\/<span class=\"end-tag\">text<\/span>&gt;&lt;\/<span class=\"end-tag\">radiobuttoncomponent.content<\/span>&gt;\n<span id=\"line335\"><\/span>\t\t\t\t&lt;\/<span class=\"end-tag\">radiobuttoncomponent<\/span>&gt;\n<span id=\"line336\"><\/span>\t\t\t\t&lt;<span class=\"start-tag\">radiobuttoncomponent<\/span> <span class=\"attribute-name\">padding<\/span>=\"\" <span class=\"attribute-name error error\" title=\"Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.\nQuote in attribute name. Probable cause: Matching quote missing somewhere earlier.\">2\"\"<\/span>=\"\"&gt;\n<span id=\"line337\"><\/span>\t\t\t\t\t&lt;<span class=\"start-tag\">radiobuttoncomponent.content<\/span>&gt;\n<span id=\"line338\"><\/span>\t\t\t\t\t\t&lt;<span class=\"start-tag\">text<\/span> <span class=\"attribute-name\">text<\/span>=\"\" <span class=\"attribute-name\">option<\/span>=\"\" <span class=\"attribute-name error error\" title=\"Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.\nQuote in attribute name. Probable cause: Matching quote missing somewhere earlier.\">3\"\"<\/span>=\"\" <span class=\"attribute-name\">font<\/span>=\"\" <span class=\"attribute-name\">verdana,<\/span>=\"\" <span class=\"attribute-name\">3world,<\/span>=\"\" <span class=\"attribute-name\">style<\/span>=\"<a class=\"attribute-value\">Bold<span class=\"entity\">&amp;quot;<\/span><span class=\"entity\">&amp;quot;<\/span><\/a>\"&gt;\n<span id=\"line339\"><\/span>\t\t\t\t\t&lt;\/<span class=\"end-tag\">text<\/span>&gt;&lt;\/<span class=\"end-tag\">radiobuttoncomponent.content<\/span>&gt;\n<span id=\"line340\"><\/span>\t\t\t\t&lt;\/<span class=\"end-tag\">radiobuttoncomponent<\/span>&gt;\n<span id=\"line341\"><\/span>\t\t\t\t&lt;<span class=\"start-tag\">radiobuttoncomponent<\/span> <span class=\"attribute-name\">padding<\/span>=\"\" <span class=\"attribute-name error error\" title=\"Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.\nQuote in attribute name. Probable cause: Matching quote missing somewhere earlier.\">2\"\"<\/span>=\"\"&gt;\n<span id=\"line342\"><\/span>\t\t\t\t\t&lt;<span class=\"start-tag\">radiobuttoncomponent.content<\/span>&gt;\n<span id=\"line343\"><\/span>\t\t\t\t\t\t&lt;<span class=\"start-tag\">text<\/span> <span class=\"attribute-name\">text<\/span>=\"\" <span class=\"attribute-name\">option<\/span>=\"\" <span class=\"attribute-name error error\" title=\"Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.\nQuote in attribute name. Probable cause: Matching quote missing somewhere earlier.\">4\"\"<\/span>=\"\" <span class=\"attribute-name\">font<\/span>=\"\" <span class=\"attribute-name\">verdana,<\/span>=\"\" <span class=\"attribute-name\">3world,<\/span>=\"\" <span class=\"attribute-name\">style<\/span>=\"<a class=\"attribute-value\">Bold<span class=\"entity\">&amp;quot;<\/span><span class=\"entity\">&amp;quot;<\/span><\/a>\"&gt;\n<span id=\"line344\"><\/span>\t\t\t\t\t&lt;\/<span class=\"end-tag\">text<\/span>&gt;&lt;\/<span class=\"end-tag\">radiobuttoncomponent.content<\/span>&gt;\n<span id=\"line345\"><\/span>\t\t\t\t&lt;\/<span class=\"end-tag\">radiobuttoncomponent<\/span>&gt;\n<span id=\"line346\"><\/span>\t\t\t&lt;\/<span class=\"end-tag\">stackpanel<\/span>&gt;\n<span id=\"line347\"><\/span>\n<span id=\"line348\"><\/span>\t\t&lt;\/<span class=\"end-tag\">border<\/span>&gt;\n<span id=\"line349\"><\/span>\n<span id=\"line350\"><\/span>    &lt;\/<span class=\"end-tag\">shape<\/span>&gt;&lt;\/<span class=\"end-tag\">simplepanel<\/span>&gt;\";<\/pre>\n<p>The template can be loaded using the <a href=\"http:\/\/www.mindfusion.dev\/onlinehelp\/flowchartnet\/index.htm?T_MindFusion_Diagramming_Components_XmlLoader.htm\">XmlLoader<\/a> class. We&#8217;ll also store a reference to the stack panel so we can access its child radio buttons:<\/p>\n<pre>public OptionNode()\n{\n\tLoad();\n}\n\npublic OptionNode(Diagram d)\n\t: base(d)\n{\n\tLoad();\n}\n\nprivate void Load()\n{\n\tComponents.Add(XmlLoader.Load(Template, this, null));\n\n\tradioGroup = FindComponent(\"RadioGroup\") as StackPanel;\n}\n\nStackPanel radioGroup;\n<\/pre>\n<p>Now implement a SelectedOption property that lets us select a radio button by its index. Define it as nullable integer so we can represent missing select too:<\/p>\n<pre>public int? SelectedOption\n{\n\tget\n\t{\n\t\tfor (int i = 0; i &lt; radioGroup.Components.Count; i++)\n\t\t{\n\t\t\tvar radioButton = (RadioButtonComponent)radioGroup.Components[i];\n\t\t\tif (radioButton.IsChecked)\n\t\t\t\treturn i;\n\t\t}\n\t\treturn null;\n\t}\n\tset\n\t{\n\t\tfor (int i = 0; i &lt; radioGroup.Components.Count; i++)\n\t\t{\n\t\t\tvar radioButton = (RadioButtonComponent)radioGroup.Components[i];\n\t\t\tradioButton.IsChecked = value == i;\n\t\t}\n\t}\n}\n<\/pre>\n<p>Let&#8217;s try it &#8211; create a few nodes and run the application, you&#8217;ll see the screen shown below:<\/p>\n<pre>var node1 = new OptionNode();\nnode1.Bounds = new RectangleF(20, 20, 30, 40);\nnode1.SelectedOption = 0;\ndiagram.Nodes.Add(node1);\n\nvar node2 = new OptionNode();\nnode2.Bounds = new RectangleF(90, 20, 30, 40);\nnode2.SelectedOption = 1;\ndiagram.Nodes.Add(node2);\n\nvar node3 = new OptionNode();\nnode3.Bounds = new RectangleF(20, 80, 30, 40);\nnode3.SelectedOption = null;\ndiagram.Nodes.Add(node3);\n\nvar node4 = new OptionNode();\nnode4.Bounds = new RectangleF(90, 80, 30, 40);\nnode4.SelectedOption = 3;\ndiagram.Nodes.Add(node4);\n\nfor (int i = 0; i &lt; diagram.Nodes.Count - 1; i++)\n\tdiagram.Factory.CreateDiagramLink(\n\t\tdiagram.Nodes[i], diagram.Nodes[i + 1]);\n<\/pre>\n<p><img decoding=\"async\" src=\"http:\/\/mindfusion.dev\/_samples\/radio_buttons_diagram.png\" alt=\"Radio buttons in MindFusion diagram nodes\" \/><\/p>\n<p>To be fair, this kind of nodes is simple enough to implement using standard <a href=\"http:\/\/www.mindfusion.dev\/onlinehelp\/flowchartnet\/index.htm?T_MindFusion_Diagramming_TableNode.htm\">TableNode<\/a> class where radio button graphics are either custom drawn or set as Image inside table cells in first column, and text displayed in second column. However the radio buttons can be mixed with other components in CompositeNodes to implement more complex user interfaces than ones possible with tables.<\/p>\n<p>For more information on MindFusion flow diagramming libraries for various desktop, web and mobile platforms, see <a href=\"http:\/\/www.mindfusion.dev\/diagramming-pack.html\">MindFusion.Diagramming Pack<\/a> page.<\/p>\n<p>Enjoy!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this post we&#8217;ll examine how CompositeNode components work in MindFusion.Diagramming for Windows Forms, and in the process create a custom radio button component. You can find the completed sample project here: RadioComponent.zip CompositeNode was created as alternative of the &hellip; <a href=\"https:\/\/mindfusion.dev\/blog\/creating-custom-compositenode-components\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[95,74,343],"tags":[264,3,4,384,29],"class_list":["post-1461","post","type-post","status-publish","format-standard","hentry","category-diagramming-2","category-sample-code","category-ui","tag-c","tag-diagram","tag-flowchart","tag-radio","tag-winforms"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p3RlKs-nz","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/posts\/1461","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/comments?post=1461"}],"version-history":[{"count":6,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/posts\/1461\/revisions"}],"predecessor-version":[{"id":2582,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/posts\/1461\/revisions\/2582"}],"wp:attachment":[{"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/media?parent=1461"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/categories?post=1461"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/tags?post=1461"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}