Grooper Scripting - CSS Utilities

From Grooper Wiki

This article was migrated from an older version and has not been updated for the current version of Grooper.

This tag will be removed upon article review and update.

This article is about the current version of Grooper.

Note that some content may still need to be updated.

2025 2023.1

Gain familiarity with the Grooper object model by scripting real world examples and see how commonly used namespaces and their members are leveraged.

You may download the ZIP below and upload it into your own Grooper environment (version 2023.1). It contains a folder to be imported into the "Projects" folder in the node tree. There are two Projects in this folder: an "incomplete" version for use in following along with the walkthrough of this article, and a "complete" version you can use if you want to quickly test the end result. The complete version has a compiled solution with the appropriate .dll and .xml files attached to the Object Library. If you are going to follow along with the walkthrough, delete the "complete" version. If you want to test the results of the completed Object Library you will need to recycle the "GrooperAppPool" in IIS after import for the object command to be available.

About

IMPORTANT


Before we begin, it is highly recommend to review the contents of the Scripting Setup article. In that article you will learn how to configure your environment so that you can debug on your local machine (instead of the server hosting production Grooper environment). You will also see how the Grooper SDK extension is used to push the files of your local solution to the target object in Grooper so that it may be properly compiled within the application.


The purpose of this article is to walk you through an example of creating a simple object command that will set CSS styling in the Style Sheet property of "DataFieldContainer" objects (in code "DataFieldContainer" is the base class for Data Model, Data Section, Data Table objects). In so doing we will get exposure to the powerful Object Library object and its useful solution template generation functionality. This solution will then be used in Visual Studio 2019 to write the necessary code to accomplish the desired task. This process will reveal namespaces and their members that are commonly used to expand the capabilities of Grooper via scripting.

The script we create will be fairly simple, but it will give you a foundation upon which you could very easily expand to increase its capabilities and usefulness. In so doing you can make a powerful and easy to use tool you may end up using in your regular Grooper designing.

Getting Started

In this section we will get the necessary components created from Grooper to begin setup for our object command.

Import Project Files and Create Object Library

The system this work will be done on is configured appropriately to debug and push files to Grooper via the Grooper SDK. Given that, the following is true:


  1. Open a compatible web browser (in this case Google Chrome is being used)
  2. In the URL box type:
    localhost/Grooper/Design
  3. Select the "Projects" folder in the node tree
  4. Click the "Upload" button
  5. In the "ZIP File Import" dialog box click the "Choose File" button
  6. Browse to where you downloaded the Grooper ZIP file provided for this article, select the file, and click "Open"
  7. Back in the "ZIP File Import" dialog box click the "Upload" button to complete the upload


A "completed" version of the work we'll be doing is provided. It contains a compiled version of the solution attached to the Object Library we will create. If you wanted to use this you could simply refresh the "GrooperAppPool" in IIS and the object command would be available. However, for the purposes of this walkthrough it will be removed.

  1. Expand the folder of the imported object
  2. Right-click the 'completed' Project and select "Delete"
  3. In the "Delete" dialog box click the "Execute" button


  1. Expand the folder provided from the ZIP import
  2. Right-click the "Object Libraries" folder
  3. In the pop-out menu select Add > Object Library
  4. In the "Add" dialog box use the name:
    CssUtilities
  5. Click the "Execute" button


  1. With the Object Library added we can see it in the node tree

Create and Download C# Solution Files

Moving forward in this walkthrough we will be using C# for our .NET coding language as it by far the most popular choice. If enough requests for VB variations of the code come in we could update this walkthrough to have both.

  1. Select the newly created Object Library
  2. Click the "Scripting" tab.
  3. Click the "Create" button.
    • This button will be grayed out if you are not using "localhost" in your URL.
    • Refer to the Remote Scripting Setup article for information on setting up your environment if you are working from a computer that is not the computer where Grooper and the Grooper Web Client is installed.
  4. Choose C# from the drop-down menu.


  1. Executing the previous command will create several files from a template that will be the foundation of the solution we will edit in Visual Studio.
  2. You can select individual files and view their contents in the Document Viewer.
  3. You can download individual files or even make edits here, although this may not be the best interface for doing edits.
  4. The "ScriptingSession.cs" file has a couple of important comments.
    • Please note that compiling this script from Visual Studio will not add/update the ObjectLibrary DLL in Grooper. To add/update this DLL in Grooper and make the functionality in this project visible to Grooper, the Compile button must be used from the Script tab of the Grooper node in the Grooper Web Client.
    • You can debug with the GrooperSDK. Find the GrooperSDK in Visual Studio by going to Extensions..Manage Extensions..Online and searching for 'Grooper'. In addition to debugging, the GrooperSDK exposes commands such as Save, Save and Compile, and Get Latest.


The solution files that were just created are attached to the "CssUtilities" Object Library in the "Grooper Filestore". In order to properly edit this solution in Visual Studio we need to download a local copy of these files.

  1. Click the "Advanced" tab.
  2. Here you can see the files made are attached to this object in the "Grooper Filestore". We need to download local copies of these files to edit them.


  1. Click back on the "Scripting" tab.
  2. In the "Working Directory" dialog box, enter a path. The default path provided will suffice.
    C:\GrooperScripts
  3. Click the "OK" button.
  4. In the "Edit Script" dialog box a message confirms the files were saved and put in a sub-directory named after the selected object.
  5. Click the "OK" button.


While we're still in Grooper let's download an image that will be used as an "IconResource" in our solution.

  1. Expand the "Resources" folder and select "CssBadge.png".
  2. Right-click the image in the Document Viewer and select "Save image as".
  3. Choose a directory to save. The "Downloads" folder will suffice.
  4. Name the file:
    CssBadge.png
  5. Click the "Save" button.

Coding

Now for the fun part! In this section we will be writing the code for our "CssUtilities" namespace by defining the class and methods that will execute our object command.

NOTE: It's a good idea, when you can, to run Visual Studio with elevated permissions.

IconResource

We will start by adding our first piece of Grooper code: an attribute class called "IconResourceAttribute", but we'll call it simply as "IconResource".

Definition

The "IconResourceAttribute" is a class that allows you to decorate classes, methods, or properties with metadata specifying an icon resource. This icon can be used in various parts of an application to visually represent the annotated element.

Key Components

  • Namespace: Grooper
  • Assembly: Grooper.dll

Attribute Targets

  • Targets: Class, Method, Property

Enum: IconStyle

  • Normal: Standard icon without any overlay.
  • OverlayOnFolder: Icon overlaid on a folder image.
  • Add, Delete, Edit, Error, Go, Back, Lightning, Link, Save, GreenDot, RedDot, Search, Check, Folder, Archive, Wrench, Refresh: Various predefined styles for the icon.

Fields

  • IconResource: string - The name of the image resource.
  • Style: IconStyle - The style of the icon.
  • AssemblyName: string - The name of the assembly where the resource is located (optional).

Properties

  • ErrorImage: Bitmap - A static property that returns an error image if the specified icon resource is not found.

Methods

  • Constructors:
    • IconResourceAttribute(string IconResource, IconStyle Style = IconStyle.Normal)
    • IconResourceAttribute(string AssemblyName, string IconResource, IconStyle Style = IconStyle.Normal)
  • GetBitmap(Type tpe): Returns the bitmap image of the icon for the given type.
  • GetBitmap(Assembly Assembly): Returns the bitmap image from the specified assembly.
  • CreateIcon(Type tpe): Creates an icon handle from the bitmap.
  • GetIconKey(Type tpe): Generates a key for the icon based on type.
  • GetIconStream(Type tpe): Returns a stream of the icon image.
  • Static Methods:
    • GetBlankIcon(): Returns a blank icon.
    • GetBlankStream(): Returns a stream for a blank icon.
    • GetIconKey(Assembly Assm, string IconResource, IconStyle Style): Generates a key for the icon based on assembly and style.
    • GetBitmap(Assembly Assm, string IconResource, IconStyle Style): Retrieves the bitmap image from the assembly with specified style.

Caching

  • IconCache: A dictionary that caches icon images to avoid redundant loading.

Private Helpers

  • NamedAssembly: Returns the assembly based on the AssemblyName field.
  • GetAssembly(Type Type): Returns the assembly for the given type or the named assembly.
  • GetIconInternal(Assembly Assm, string IconResource, IconStyle Style): Retrieves the icon internally, considering the style.
  • GetOverlayIcon(Assembly Assm, string IconResource, string OverlayName): Retrieves an overlay icon.
  • GetIcon(Assembly Assm, string IconResource): Retrieves the icon from the assembly.
  • LoadResource(Assembly Assm, string IconResource): Loads the resource from the assembly.
  • GetResourceStream(Assembly Assm, string IconResource): Gets a stream for the resource.
  • OverlayIcon(Bitmap BgImage, Bitmap FgImage, int X, int Y): Overlays one icon on another.

Usage

This attribute can be used to decorate classes, methods, or properties, providing a way to associate them with a specific icon resource. The icons can be used in UI elements, documentation, or any other place where a visual representation of the code element is useful.

Example

<syntaxhighlight lang="csharp"> [IconResource("MyIconResource", IconResourceAttribute.IconStyle.Normal)] public class MyClass {

   // Class implementation

} </syntaxhighlight>

This example specifies that MyClass should be represented by the MyIconResource icon in its normal style. The second argument, however, is optional, and "Normal" is the default, so it could be written simply as:

<syntaxhighlight lang="csharp"> [IconResource("MyIconResource")] public class MyClass {

   // Class implementation

} </syntaxhighlight>

Applying IconResource to our Code

With an understanding of what "IconResourceAttribute" is we can now go about applying this attribute class to our code.

  1. Launch Visual Studio as admin and select "Open a Project or Solution".
  2. Navigate to and select the solution file.
  3. Click the "Open" button.


Before we add the attribute to our main class we need to add the icon as a resource file in our project.

  1. Right-click the project in the solution explorer
  2. Select "Properties" from the pop-out menu.
  3. In the project properties select "Resources".
  4. A resources file does not exist, so we need to create one.


  1. With the default resources file added...
  2. ...click the "Add Resource" drop-down and select "Add Existing File".
  3. Navigate to and select the "CssBadge.png" file saved out of Grooper.
  4. Click the "Open" button.


  1. A "Resources" folder and the "CssBadge.png" file have been added to our project.
  2. You can see the file represented here.
  3. Close the properties of the project.
  4. Save the changes when prompted.


  1. Right-click the project in the solution explorer.
  2. In the pop-out menu select "Add > New Item".
  3. In the "Add New Item" window select "C# Class".
  4. Name the class:
    SetStyles.cs
  5. Click the "Add" button.


The "IconResource" attribute class is part of the "Grooper" assembly. In order to use this class we will need to add a using directive for "Grooper".

Before adding "Grooper" as a using directive it needs to be referenced in our project. Thankfully, the solution we created out of Grooper is based on a template that has done this work for us. The .csproj file that was created as part of our solution is an XML formatted file that has defined several references for us. As a result, there are several references already built into our project. With that understanding, let's continue forward.


  1. Add the using directive:
    <syntaxhighlight lang="csharp" line start="6">using Grooper;</syntaxhighlight>
    If you type the code out instead of copy/pasting from here you will notice the linter will recognize the "Grooper" assembly because it is in your references, which will allow you to auto-complete if you so choose by pressing tab. The same will be true now for members of the "Grooper" namespace (such as the following "IconResource" class) as you continue to write code.
  2. On the line above the main class add the "IconResource" attribute:
    <syntaxhighlight lang="csharp" line start="11">[IconResource("CssBadge")]</syntaxhighlight>
    Again, note the "IconStyle" parameter is optional and not including it will use the default "Normal" style. Feel free to experiment with the options available from the enumeration.
  3. Note that the .png extension is not needed

Inspecting and Searching

Now that we have added our first piece of Grooper code it might be useful to take a quick aside and get exposure to a couple of tools that you may find useful moving forward. Understanding where to get more information can often prove more useful than just being handed the definition of something.

  1. Mouse-over the "IconResource" class to get more information.
    • You can see information regarding it's parent namespace as well as comments written by the developers.


The mouse-over information can be useful, but seeing the de-compiled source code can explain even more. Keep in mind that Visual Studio's ability to de-compile is reliant on you agreeing to their legal terms about the copy-right of source code.

  1. Right-click the "IconResource" class and choose "Go to Definition" from the pop-out menu.


  1. A separate tab will open and show you the source.
    • The de-compilation will occur in the language you are working in. The project we are working in is in C#, so the code will be shown in C#, even though most of Grooper's code was written in VB.NET.
  2. You will even be taken to the specific line where this class is defined.


If you want to do some real digging, you can view the contents of an assembly in the "Object Browser".

  1. Right-click the "Grooper" assembly in the "References" section of the Solution Explorer and choose "View in Object Library".


  1. A separate tab will open and show the "Object Browser".
  2. Expand the assembly you wish to explore.
  3. Choose a type you wish to inspect.
  4. You can see information about the selection.
  5. You can also select members of a type and see information about them as well.

DataContract and DataMember

The object command we are creating will consist of a dialog box with a property grid. The properties we define will ultimately apply a configuration to the Set Styles property of "DataFieldContainer" objects. Grooper stores properties of objects as serialized JSON. As a result, we will now learn about and apply an important attribute classes known as "DataContract" and its related "DataMember".

Definition

The .NET namespace "System.Runtime.Serialization" includes the "DataContract" class, which is crucial for enabling serialization and deserialization of data. Here's an explanation of what the "DataContract" does and its significance:

Purpose

  • The "DataContract" attribute is used to define a type that can be serialized or deserialized. Serialization is the process of converting an object into a form that can be easily transported (for example, into XML or JSON). Deserialization is the reverse process, converting serialized data back into an object.

Usage

  • You apply the "DataContract" attribute to a class or structure to specify that it is serializable. You can also use the "DataMember" attribute to mark the fields or properties of the class that you want to be serialized.

Serialization Process

  • When a class is marked with "DataContract", only members marked with "DataMember" are serialized. This allows for precise control over what data is serialized, which can be important for ensuring data security and reducing the amount of data transmitted.

Example

Here's a simple example to illustrate how "DataContract" and "DataMember" work:

<syntaxhighlight lang="csharp"> using System; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.IO;

[DataContract] public class Person {

   [DataMember]
   public string FirstName { get; set; }
   [DataMember]
   public string LastName { get; set; }
   // This field will not be serialized because it does not have the DataMember attribute.
   public int Age { get; set; }

}

class Program {

   static void Main()
   {
       Person person = new Person() { FirstName = "John", LastName = "Doe", Age = 30 };
       // Serializing the person object to JSON
       DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(Person));
       using (MemoryStream stream = new MemoryStream())
       {
           serializer.WriteObject(stream, person);
           stream.Position = 0;
           StreamReader reader = new StreamReader(stream);
           string json = reader.ReadToEnd();
           Console.WriteLine("Serialized JSON: " + json);
       }
       // Deserializing from JSON back to a Person object
       string jsonData = "{\"FirstName\":\"John\",\"LastName\":\"Doe\"}";
       using (MemoryStream stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(jsonData)))
       {
           Person deserializedPerson = (Person)serializer.ReadObject(stream);
           Console.WriteLine($"Deserialized Person: {deserializedPerson.FirstName} {deserializedPerson.LastName}");
       }
   }

} </syntaxhighlight>

Explanation of the Example

  • DataContract Attribute: The "Person" class is marked with the "DataContract" attribute, indicating it can be serialized.
  • DataMember Attribute: The "FirstName" and "LastName" properties are marked with the "DataMember" attribute, so they will be included in the serialization process. The "Age" property is not marked with "DataMember", so it will not be serialized.
  • Serialization: The "DataContractJsonSerializer" is used to serialize the "Person" object to JSON format.
  • Deserialization: The same serializer is used to deserialize the JSON data back into a "Person" object.

Key Points

  • Selective Serialization: You can choose which members of a class to serialize, which is useful for controlling the data exposed and transferred.
  • Interoperability: Using "DataContract" ensures compatibility with other platforms and technologies, as it can serialize to standard formats like XML and JSON.
  • Version Tolerance: "DataContract" supports versioning, allowing new members to be added to the contract without breaking existing clients.

In summary, the "DataContract" attribute in .NET provides a way to define which classes and members are serializable, enabling controlled and flexible data serialization and deserialization.

Applying DataContract and DataMember to our Code

As the previous definition states, the "DataContract" and associated "DataMember" attribute classes are in the "System.Runtime.Serialization" namespace, so we will need to include a using directive for them. "System.Runtime.Serialization" is already an assembly in our project's references, so adding it will be simple. Once added, "DataContract" can be easily be added as an attribute to our main class. The same is true for "DataMember" once we create our first property.

  1. Note that "System.Runtime.Serialization" is already in our project's references.
  2. Add the using directive:
    <syntaxhighlight lang="csharp" line start="7">using System.Runtime.Serialization;</syntaxhighlight>
  3. Modify the attribute line by adding "DataContract":
    <syntaxhighlight lang="csharp" line start="11">[DataContract, IconResource("CssBadge")]</syntaxhighlight>


Before we step inside our main class and add properties, and attributes to those properties, let's set a "public" access modifier to our class declaration to make it accessible from any other code in the same assembly or another assembly that references it.

  1. Add access modifier to class declaration:
  2. <syntaxhighlight lang="csharp" line start="12">public class SetStyles</syntaxhighlight>
  3. Making this class public has generated a warning for us to address.


To address the warning offered by Visual Studio we need to add XML tags for C# documentation comments. You can choose to ignore this warning, as it won't cause compile issues, but commenting code is a good practice. Additionally, XML doc. comments supply not only supply mouse-over tooltips when in Visual Studio, but Grooper leverages these comments and displays them within the "help" of Grooper (we'll observe this later).

  1. Return the linve above the class attributes and insert:
  2. <syntaxhighlight lang="csharp" line start="11">///</syntaxhighlight>
    Visual Studio will recognize this syntax and insert the following:
    <syntaxhighlight lang="csharp" line start="11">/// <summary>

/// /// </summary></syntaxhighlight>


Feel free to add your own summary comment, come back to this part later when you have a better idea of what you'd want to write, or as stated previously you can not add the comment block at all (again, not having it won't affect compilation.)

Or, if you want, you can add the comments I have created: <syntaxhighlight lang="csharp" line start="11"> /// <summary> /// Easily set CSS styles in a dialog box with properties /// </summary> /// <remarks> /// This will add an object command to "DataFieldContainer" objects that will open a dialog box to allow a user to easily set CSS styles. /// Upon execute the property values will be inserted as CSS into the "Style Sheet" property of the respective Data Element. /// </remarks> </syntaxhighlight>

Moving forward, the assumption will be that you have added my comments, therefore line numbering will reflect as much.


We now need to create a member of our class that will have the "DataMember" attribute class applied to it. In our case we will work with a property that will ultimately end up in the "property grid" of the dialog box that our object command will generate.

We will start with a property we will call "LabelWidth". It will define the width of the labels of the child "DataField" objects (in code "DataField" is the base class for both Data Field and Data Column objects) within our target "DataFieldContainer".

  1. Inside the "SetStyles" class define a property as follows:
  2. <syntaxhighlight lang="csharp" line start="24">public string LabelWidth { get; set; }</syntaxhighlight>
  3. To clear the XML comment warning add the following summary remarks:
    <syntaxhighlight lang="csharp" line start="21">/// <summary>

/// A property to set the CSS "width" setting of child Data Elements /// </summary></syntaxhighlight>


  1. Insert a line above the property and add the attribute:
  2. <syntaxhighlight lang="csharp" line start="24">[DataMember]</syntaxhighlight>

Category and DisplayName

Now that we've added our first property and applied the attribute classes necessary to serialize the output, let's learn about a couple of other commonly used attribute classes, "Category" and "DisplayName", and apply them as well.

Definition

The "Category" and "DisplayName" attribute classes are part of the System.ComponentModel namespace in .NET and are primarily used for enhancing the design-time experience, particularly in property grids. Property grids are featured very prominently in Grooper.

"Category" Attribute

The "Category" attribute is used to group properties of a class into categories within a property grid. This makes it easier to organize and locate properties when inspecting or editing them in a visual designer.

<syntaxhighlight lang="csharp"> using System; using System.ComponentModel;

public class MySettings {

   [Category("Appearance")]
   public string BackgroundColor { get; set; }
   [Category("Appearance")]
   public string Font { get; set; }
   [Category("Behavior")]
   public bool IsResizable { get; set; }
   [Category("Behavior")]
   public bool IsDraggable { get; set; }

} </syntaxhighlight>

In this example:

  • Properties "BackgroundColor" and "Font" are grouped under the "Appearance" category.
  • Properties "IsResizable" and "IsDraggable" are grouped under the "Behavior" category.
  • When displayed in a property grid, these properties will be organized under their respective categories.

"DisplayName" Attribute

The "DisplayName" attribute is used to specify a custom display name for a property. This is useful when you want to present a more user-friendly or descriptive name for a property than what is provided by its identifier.

<syntaxhighlight lang="csharp"> using System; using System.ComponentModel;

public class MySettings {

   [DisplayName("Background Color")]
   public string BackgroundColor { get; set; }
   [DisplayName("Font Family")]
   public string Font { get; set; }
   [DisplayName("Resizable")]
   public bool IsResizable { get; set; }
   [DisplayName("Draggable")]
   public bool IsDraggable { get; set; }

} </syntaxhighlight>

In this example:

  • The "BackgroundColor" property will be displayed as "Background Color".
  • The "Font" property will be displayed as "Font Family".
  • The "IsResizable" property will be displayed as "Resizable".
  • The "IsDraggable" property will be displayed as "Draggable".

Usage in Property Grid

When these attributes are applied to properties in a class, and this class is then used in a property grid within a development environment or custom application, the property grid will:

  • Group properties according to the "Category" attribute.
  • Display properties using the names specified by the "DisplayName" attribute.

This improves the usability and readability of the properties for developers and end-users, making it easier to configure and manage properties within the UI.

Summary

  • Category Attribute: Groups properties into categories for better organization in property grids.
  • DisplayName Attribute: Specifies a custom, user-friendly name for a property to enhance readability and understanding.

Both of these attributes enhance the design-time and runtime experience by making property management more intuitive and user-friendly.

Applying Category and DisplayName to our Code

As the previous definition states, the "Category" and "DisplayName" attribute classes are in the "System.ComponentModel" namespace, so we will need to include a using directive for that. It is defined within the "System" assembly that is in our project's references, so adding the required using directive will be simple. Once added, "Category" and "DisplayName" can easily be leveraged as attributes on our property.

  1. "System.ComponentModel" is a namespace defined in the "System" assembly that is already in our references.
  2. "System" is defined as a using directive, however...
  3. ...an explicit using directive for "System.ComponentModel" must be used.
  4. Modify the attribute line of the "LabelWidth" property by adding the new attribute classes and a string argument for their names:
    <syntaxhighlight lang="csharp" line start="25">[DataMember, Category("Label"), DisplayName("Width")]</syntaxhighlight>

TypeConverter and Grooper Attributes

The remaining attribute classes we will add to the "LabelWidth" property consist of two self explanatory classes defined in the "Grooper" assembly, and another important class defined in the "System.ComponentModel" namespace:

  • Grooper
    • Viewable
    • DV (which stands for default value)
  • System.ComponentModel
    • TypeConverter

In learning about the "TypeConverter" attribute class we will see that custom type converters can be defined. Considering that, we will also learn about a class defined in the "Grooper.IP" assembly that defines a custom type conversion:

  • Grooper.IP
    • LogicalValue

We will ultimately end up using a member of this class:

  • LogicalValue
    • LogicalValue.UniversalConverter

TypeConverter Definition

The "TypeConverter" attribute class in the "System.ComponentModel" namespace is used to specify a custom converter for a property, event, or an entire class. A type converter is a class that provides a way to convert values between different data types, and to assist with property configuration at design-time.

Key Features of "TypeConverter"

  • Conversion:
    • Convert values between different data types (e.g., string to integer, object to string).
    • Convert to and from common types (e.g., string representations for user interfaces).
  • Support for Design-Time Tools:
    • Provide custom conversion logic for properties when displayed in design-time environments like Visual Studio.
    • Help property grids and other tools to edit and display property values correctly.
  • Custom Type Converters:
    • Developers can create custom type converters by inheriting from the "TypeConverter" class and overriding its methods.

Applying the "TypeConverter" Attribute The "TypeConverter" attribute is applied to a class or a property to indicate which "TypeConverter" should be used.

  • Using a Custom Type Converter
    1. Create a Custom Type Converter:
    <syntaxhighlight lang="csharp">using System;

using System.ComponentModel; using System.Globalization;

public class Temperature {

   public double Value { get; set; }
   public string Unit { get; set; } // "C" for Celsius, "F" for Fahrenheit

}

public class TemperatureConverter : TypeConverter {

   public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
   {
       if (sourceType == typeof(string))
       {
           return true;
       }
       return base.CanConvertFrom(context, sourceType);
   }
   public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
   {
       if (value is string strValue)
       {
           string[] parts = strValue.Split(' ');
           double tempValue = double.Parse(parts[0]);
           string unit = parts[1];
           return new Temperature { Value = tempValue, Unit = unit };
       }
       return base.ConvertFrom(context, culture, value);
   }
   public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
   {
       if (destinationType == typeof(string))
       {
           return true;
       }
       return base.CanConvertTo(context, destinationType);
   }
   public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
   {
       if (destinationType == typeof(string) && value is Temperature temp)
       {
           return $"{temp.Value} {temp.Unit}";
       }
       return base.ConvertTo(context, culture, value, destinationType);
   }

} </syntaxhighlight>

    1. Apply the TypeConverter Attribute:
    <syntaxhighlight lang="csharp">using System;

using System.ComponentModel;

public class WeatherReport {

   [TypeConverter(typeof(TemperatureConverter))]
   public Temperature CurrentTemperature { get; set; }

}

class Program {

   static void Main()
   {
       var weather = new WeatherReport
       {
           CurrentTemperature = (Temperature)new TemperatureConverter().ConvertFromString("25 C")
       };
       string tempStr = new TemperatureConverter().ConvertToString(weather.CurrentTemperature);
       Console.WriteLine($"Current Temperature: {tempStr}");
   }

} </syntaxhighlight>

Key Methods in "TypeConverter"

  • ConvertFrom: Converts from a given type to the type the converter is associated with.
  • ConvertTo: Converts from the associated type to a given type.
  • CanConvertFrom: Determines if the converter can convert from a specific type.
  • CanConvertTo: Determines if the converter can convert to a specific type.

Summary

  • Purpose: The "TypeConverter" attribute specifies a custom converter for types and properties, enabling custom logic for type conversion and enhancing design-time support.
  • Usage: Apply the "TypeConverter" attribute to a class or property to use a specific "TypeConverter" class.
  • Custom Type Converters: Create custom type converters by inheriting from "TypeConverter" and overriding methods like "ConvertFrom", "ConvertTo", "CanConvertFrom", and "CanConvertTo".

The "TypeConverter" attribute and the associated "TypeConverter" class are powerful tools for managing type conversions and providing enhanced support for property configuration in design-time environments.

LogicalValue Definition

Having gained an understanding of the "TypeConverter" attribute class, let's now take a look at the custom type converter from Grooper code.

The "LogicalValue" class in the "Grooper.IP" namespace provides functionality for handling and converting different unit values such as inches, points, millimeters, centimeters, pixels, and percentages. It includes methods for converting these values to inches and pixels, validating unit strings, and providing display values. The class also contains nested type converters to facilitate the conversion from string representations of values.

Nested Classes

  • SimpleConverter
    • Purpose: Converts simple values (e.g., inches, points, millimeters, centimeters) to normalized unit values.
    • Overrides:
      • "CanConvertFrom(ITypeDescriptorContext context, Type sourceType)": Determines if the source type can be converted from a string.
      • "ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)": Converts a string value to a normalized unit value after validation.
  • UniversalConverter
    • Purpose: Converts a wider range of values (including percentages and pixels) to normalized unit values.
    • Overrides:
      • "CanConvertFrom(ITypeDescriptorContext context, Type sourceType)": Determines if the source type can be converted from a string.
      • "ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)": Converts a string value to a normalized unit value after validation.

Methods

  • "ToInX(string TextValue, GrooperImage Image)":
    • Converts a value to inches using the logical width of the provided image.
  • "ToInY(string TextValue, GrooperImage Image)":
    • Converts a value to inches using the logical height of the provided image.
  • "ToPxX(string TextValue, GrooperImage Image)":
    • Converts a value to pixels using the logical width and resolution of the provided image.
  • "ToPxY(string TextValue, GrooperImage Image)":
    • Converts a value to pixels using the logical height and resolution of the provided image.
  • "ToIn(string TextValue, double PageSize)":
    • Converts a value to inches based on a page size. Supports inches, points, millimeters, centimeters, and percentages. Throws an exception for pixel units.
  • "ToIn(string TextValue)":
    • Converts a value to inches. Supports inches, points, millimeters, and centimeters. Throws an exception for invalid size specifications.
  • "Validate(string TextValue, bool AllowPhysical)":
    • Validates a unit string. Returns an error message if the value is invalid or if pixel units are not allowed.
  • "GetDisplayValue(string StringValue)":
    • Appends "in" to numeric string values for display purposes.

Summary

The "LogicalValue" class provides robust functionality for converting and validating various unit values, facilitating the handling of measurements in different formats within Grooper. The nested type converters "SimpleConverter" and "UniversalConverter" (the one we're about to use) assist in converting string representations of unit values to normalized values for further processing.

Applying Viewable, DV, and TypeConverter to our Code

We already have the appropriate using directives for "Viewable" and "DV" (both defines in "Grooper). TypeConverter is defined in "System.ComponentModel", which we have also declared. "LogicalValue", however, is in a different assembly, and is not in our current project references, so we will first need to add it.

  1. Right-click on "References" in the Solution Explorer and choose "Add References" in the drop-down menu.
  2. Click in the "Browse" section of the "References Manager".
  3. Click the "Browse" button.
  4. In the Explorer window, navigate to the installation directory of Grooper.
  5. Select the "Grooper.IP.dll".
  6. Click the "Add" button.
  7. Back in the "References Manager" window, click the "OK" button.


  1. Add a using directive for "Grooer.IP"
  2. <syntaxhighlight lang="csharp" line start="9">using Grooper.IP;</syntaxhighlight>
  3. Modify the attribute line of the "LabelWidth" property by adding the new attributes:
    <syntaxhighlight lang="chsarp" line start="26">[DataMember, Viewable, Category("Label"), DisplayName("Width"), DV("100px"), TypeConverter(typeof(LogicalValue.UniversalConverter))]</syntaxhighlight>

Color Structure Type and New Properties

Next we will add two more properties to our "SetStyles" class, and in so doing we will define their type by leveraging a structure type from the "System.Drawing" namespace known as "Color".

Color Definition

The "Color" structure in the "System.Drawing" namespace is a part of the .NET Framework that represents colors in terms of their alpha, red, green, and blue (ARGB) components. It provides a wide range of predefined colors and methods for creating custom colors.

Key Features of Color

  • Predefined Colors:
    • The "Color" structure includes a set of predefined colors. These colors are accessible as static properties of the "Color" structure, such as "Color.Red", "Color.Blue", "Color.Green", etc.
  • Custom Colors:
    • You can create custom colors by specifying the ARGB values. The ARGB values represent the alpha (transparency), red, green, and blue components of the color.
  • Color Components:
    • The Color structure provides properties to get the individual components of a color, such as "A" (alpha), "R" (red), "G" (green), and "B" (blue).
  • Conversion and Manipulation:
    • Methods are available for converting colors to other representations, such as hexadecimal strings, and for manipulating color properties.

Usage

  • Creating and Using Predefined Colors
    <syntaxhighlight lang="csharp">using System;

using System.Drawing;

public class ColorExample {

   public static void Main()
   {
       Color redColor = Color.Red;
       Console.WriteLine($"Red Color - ARGB: ({redColor.A}, {redColor.R}, {redColor.G}, {redColor.B})");
       Color blueColor = Color.Blue;
       Console.WriteLine($"Blue Color - ARGB: ({blueColor.A}, {blueColor.R}, {blueColor.G}, {blueColor.B})");
   }

} </syntaxhighlight>

  • Creating Custom Colors
    <syntaxhighlight lang="csharp">using System;

using System.Drawing;

public class CustomColorExample {

   public static void Main()
   {
       Color customColor = Color.FromArgb(255, 128, 0, 128); // Fully opaque purple
       Console.WriteLine($"Custom Color - ARGB: ({customColor.A}, {customColor.R}, {customColor.G}, {customColor.B})");
   }

} </syntaxhighlight>

  • Accessing Color Components
    <syntaxhighlight lang="csharp">using System;

using System.Drawing;

public class ColorComponentsExample {

   public static void Main()
   {
       Color color = Color.FromArgb(255, 128, 0, 128); // Fully opaque purple
       Console.WriteLine($"Alpha: {color.A}, Red: {color.R}, Green: {color.G}, Blue: {color.B}");
   }

} </syntaxhighlight> Important Members of "Color"

  • Static Properties: Provide access to predefined colors.
    • Example: "Color.Black", "Color.White", "Color.Red", etc.
  • FromArgb Method: Creates a color from ARGB values.
    • Example: "Color.FromArgb(255, 0, 0, 255)" creates a fully opaque blue color.
  • A, R, G, B Properties: Get the alpha, red, green, and blue components of the color, respectively.
  • Name Property: Gets the name of the color.
    • Example: "color.Name" might return "Red" for "Color.Red".

Summary

The "Color" structure in the "System.Drawing" namespace is a versatile tool for handling colors in .NET applications. It provides a wide array of predefined colors and allows for the creation of custom colors through ARGB values. With properties to access individual color components and methods for color manipulation, it is an essential part of any graphics-related programming in .NET.

Adding New Properties with the Color Structure Type to our Code

As the previous definition states, the "Color" structure type is in the "System.Drawing" namespace, so we will need to include a using directive for them. "System.Drawing" is already an assembly in our project's references, so adding it will be simple. Once added, "Color" can be easily be used as a type definition in the signature of our new "LabelForegroundColor" property.

  1. Note that "System.Drawing" is already in our project's references.
  2. Add the using directive:
    <syntaxhighlight lang="csharp" line start="10">using System.Drawing;</syntaxhighlight>
  3. Add a new property with the new type, familiar attributes, and summary comments:
    <syntaxhighlight lang="csharp" line start="30">/// <summary>

/// A property to set the CSS "color" setting of child Data Elements' labels /// </summary> [DataMember, Viewable, Category("Label"), DisplayName("Color"), DVColor("#b0b0b0")] public Color LabelForegroundColor { get; set; } </syntaxhighlight>

  1. Because the "Color" structure type is being leveraged in the signature of this property, the "TypeConverter" attribute will not be used.
  2. "DVColor" is another self-explanatory Grooper attribute class.

  1. Add a new property with the new type, familiar attributes, and summary comments:
  2. <syntaxhighlight lang="csharp" line start="36">/// <summary>

/// A property to set the CSS "color" setting of child Data Elements' inputs /// </summary> [DataMember, Viewable, Category("Input"), DisplayName("Color"), DVColor("#ffffff")] public Color InputForegroundColor { get; set; } </syntaxhighlight>

ConvertToString Method

The last two properties we added are using color values that we need to convert to string values. We need to write a method that will handle this conversion.

Definition

The "ConvertToString" method we will write converts a "Color" object to a string suitable for CSS. If the color is represented as RGB values, it formats the string as rgb(r,g,b). Otherwise, it returns the color name directly.

Full Code <syntaxhighlight lang="csharp"> public static string ConvertToString(Color value) {

   var converted = TypeDescriptor.GetConverter(typeof(Color)).ConvertToString(value);
   if (converted.Contains(","))
       converted = $"rgb({converted})";
   return converted;

} </syntaxhighlight> Detailed Breakdown

  • Method Signature:
    <syntaxhighlight lang="csharp">public static string ConvertToString(Color value){}</syntaxhighlight>
    • "public": The method is accessible from outside the class.
    • "static": The method belongs to the class itself, not an instance of the class.
    • "string": The method returns a string.
    • "ConvertToString": The name of the method.
    • "Color value": The method takes a single parameter of type Color.
  • Conversion to String:
    <syntaxhighlight lang="csharp">var converted = TypeDescriptor.GetConverter(typeof(Color)).ConvertToString(value);

</syntaxhighlight>

    • "TypeDescriptor.GetConverter(typeof(Color))": This gets a type converter for the "Color" type. As we learned earlier, a type converter is a component that can convert values between different types.
    • "ConvertToString(value)": This uses the type converter to convert the "Color" object value to its string representation. For example, the "Color" object representing pure red might convert to "255,0,0".
  • Handling RGB Format
    <syntaxhighlight lang="csharp">if (converted.Contains(","))
   converted = $"rgb({converted})";

</syntaxhighlight>

    • "if (converted.Contains(","))": This checks if the converted string contains a comma, which indicates that it is in the format of RGB values (e.g., "255,0,0").
    • "converted = $"rgb({converted})"": If the string is in RGB format, it wraps the string with rgb(), resulting in "rgb(255,0,0)".
  • Return Statement
    <syntaxhighlight lang="csharp">return converted;

</syntaxhighlight>

    • The method returns the final string, either in its original form (if it didn't contain commas) or wrapped in "rgb()".

Applying the "ConvertToString" Method to our Code

With an understanding of what this new method does we can now place the code with confidence.

  • Add the "ConvertToStrong" method and its summary comments:
    <syntaxhighlight lang="csharp" line start="42">/// <summary>

/// Converts values from ARGB color pickers to strings that can be parsed by the CSS style sheet /// </summary> /// <param name="value">stores ARGB color from System.Drawing.Color method</param> /// <returns>string interpolated conversion of ARGB color value for use in CSS style sheet</returns> public static string ConvertToString(Color value) { var converted = TypeDescriptor.GetConverter(typeof(Color)).ConvertToString(value); if (converted.Contains(",")) converted = $"rgb({converted})"; return converted; } </syntaxhighlight>

Inheriting from "ObjectCommand<DataFieldContainer>"

The next major component of our code will be to make our "SetStyles" class inherit from a Grooper abstract base class that will allow it to function as an object command. This class is known as "ObjectCommand" and is given a generic type that tells it what object the command can execute against.

ObjectCommand<ObjectType> Definition

The ObjectCommand<ObjectType> class is an abstract base class designed for creating commands that operate on objects in Grooper. It provides mechanisms for determining whether the command can execute on the selected items and for executing the command on each item. It is part of the "Grooper" namespace and is used to define commands that act on objects of type "ObjectType". This class is designed to be inherited by other classes (like our "SetStyles" class) that need to implement specific command functionality.

Key Features

  • Namespace: "Grooper"
  • Inheritance: Inherits from "ObjectCommand"
    • In our case we will inherit from "DataFieldContainer"

Attributes

  • [DataContract]: Indicates that the class is serializable for data contracts.

Type Parameters

  • ObjectType: The type of object the command applies to.

Properties

  • AllowMultiSelect:
    • Type: "bool"
    • Default: "true"
    • Description: Determines if the command should be enabled when multiple items are selected. Can be overridden by derived classes.
  • AllowPartialExecute:
    • Type: "bool"
    • Default: "true"
    • Description: Controls how the enabled state is determined when multiple items are selected. If true, the command is enabled if "CanExecute()" returns true for any selected item. If false, "CanExecute()" must return true for all selected items. Can be overridden by derived classes.

Constructors

  • ObjectCommand(ConnectedObject Owner): Initializes the command with an owner.
  • ObjectCommand(): Default constructor with no parameters.

Methods

  • CanExecute(ObjectType Item):
    • Type: "bool"
    • Description: Determines if the command is enabled for the given item. The default implementation always returns true. Derived classes can override this method to implement custom logic.
  • Execute(ObjectType Item):
    • Abstract method
    • Description: Executes the command logic on the given item. Must be implemented by derived classes.
  • OnInitialize(IEnumerable<ObjectType> Items):
    • Type: "bool"
    • Default: Returns true
    • Description: Called after the command is created and before any properties are displayed. Can be overridden to initialize properties or display a custom UI. If it returns false, command execution is canceled.
  • Initialize(IEnumerable<object> Items):
    • Overrides "ObjectCommand.Initialize"
    • Description: Calls "OnInitialize" with the list of items cast to ObjectType.
  • OnCanExecute(IEnumerable<object> Items):
    • Internal override
    • Description: Determines if the command can execute based on the selected items and the properties "AllowMultiSelect" and "AllowPartialExecute".
  • OnExecute(IEnumerable<object> Items):
    • Internal override
    • Description: Executes the command on each item in the list. Uses "RuntimeHelpers.GetObjectValue" to get the value of each item and calls the "Execute" method if the item is of type "ObjectType".

Usage

To create a specific command, you would inherit from "ObjectCommand<ObjectType>" and implement the "Execute" method: <syntaxhighlight lang="csharp"> public class MyCommand : ObjectCommand<MyObjectType> {

   protected override bool CanExecute(MyObjectType item)
   {
       // Custom logic to determine if the command can execute
       return item != null && item.IsValid;
   }
   protected override void Execute(MyObjectType item)
   {
       // Custom command execution logic
       item.PerformAction();
   }
   protected override bool OnInitialize(IEnumerable<MyObjectType> items)
   {
       // Custom initialization logic, if necessary
       return base.OnInitialize(items);
   }

} </syntaxhighlight> This example demonstrates how to create a command that can only execute on valid items of type "MyObjectType" and performs a specific action on each item. The "OnInitialize" method is overridden to include any additional initialization logic needed before the command is executed.

DataFieldContainer Definition

The "DataFieldContainer" class in the "Grooper.Core" namespace is an abstract class that serves as a container for other Data Elements within a hierarchical data structure in Grooper. It provides properties and methods to manage child elements, define appearance styles using CSS, perform lookup operations, and validate data according to specified rules.

Key Components

  • Nested Classes
    • "DataFieldContainer_Properties"
      • Purpose: Defines properties for a "DataFieldContainer".
      • Fields:
        • "LookupSpecs": A list of lookup specifications.
        • "ValidateRuleId": The ID of the validation rule.
        • "CSS": CSS styles for the container.
        • Variables: A list of variable definitions.
      • Constructor: Accepts an Owner parameter of type "GrooperNode".
    • "RuleEditor"
      • Purpose: Provides a custom editor for selecting data rules.
      • Methods:
        • "GetBaseNodes(Type PropertyType, ConnectedObject ConnectedItem)": Returns base nodes for the property type.
        • "GetSelectableTypes(Type PropertyType)": Returns selectable types for the property.
    • CssEditor
      • Purpose: Provides a custom editor for editing CSS styles.
      • Properties:
        • "Highlighter": Defines the code highlighter for CSS.
        • "ShowControlChars": Determines whether control characters are shown.
      • Constructor: Initializes the highlighter and control characters.
      • Methods:
        • "ConfigureEditor(CodeEditor Editor, DataFieldContainer Instance, PropertyDescriptor pd)": Configures the code editor.
        • "GetCompletionProvider(object Instance, PropertyDescriptor pd)": Returns a completion provider for CSS.
    • "LookupsConverter"
      • Purpose: Provides a custom converter for lookup collections.
      • Methods:
        • "GetPropertiesSupported(ITypeDescriptorContext context)": Returns whether properties are supported (always false).
    • ImportSchema
      • Purpose: Imports child data elements from an external source.
      • Fields:
        • "Source": The schema importer source.
        • "RemoveExisting": Indicates whether existing child data elements should be removed.
      • Methods:
        • "Execute(DataFieldContainer Item)": Executes the import schema command.
  • Properties
    • "CSS (Category: Appearance)"
      • Purpose: Defines CSS styles for the element and its descendants.
      • Get/Set: Gets or sets the CSS string.
    • "Lookups (Category: Behavior)"
      • Purpose: A list of lookup operations for child elements.
      • Get/Set: Gets or sets the list of "LookupSpecification" objects.
    • "Variables (Category: Behavior)"
      • Purpose: Defines variable computations for the container.
      • Get/Set: Gets or sets the list of "VariableDefinition" objects.
    • "ValidateRule (Category: Behavior)"
      • Purpose: Specifies a validation rule to execute during validation events.
      • Get/Set: Gets or sets the "DataRule" object.
    • "DataRefs"
      • Purpose: Returns a collection of data references used by the container.
      • Get: Retrieves unique data reference IDs.
    • "SelfAndDescendants"
      • Purpose: Returns this data element and all its descendant data elements.
      • Get: Retrieves a collection of "DataElement" objects.
    • "DescendantElements"
      • Purpose: Returns all descendant data elements.
      • Get: Iterates through runtime data elements and their descendants.
    • "AllValues"
      • Purpose: Returns all descendant fields and columns.
      • Get: Iterates through runtime data elements and their values.
    • "IsDataModel"
      • Purpose: Indicates whether the container is a data model.
      • Get: Returns a boolean.
    • "AllSingleInstanceFields"
      • Purpose: Returns single instance fields relative to the container.
      • Get: Retrieves single instance fields including those of ancestor elements.
    • "SingleInstanceFields"
      • Purpose: Returns single instance fields for the container.
      • Get: Iterates through runtime data elements to find single instance fields.
    • "SingleInstanceElements"
      • Purpose: Returns single instance elements within the container.
      • Get: Iterates through runtime data elements to find single instance elements.
    • "SourceFieldNames"
      • Purpose: Returns the fully-qualified names of single-instance fields.
      • Get: Retrieves names from variables and source fields.
  • Methods
    • Constructors
      • "DataFieldContainer(GrooperDb gdb)": Initializes with a Grooper database.
      • "DataFieldContainer(GrooperDb gdb, NodeData Data): Initializes with a Grooper database and node data.
    • "ValidateProperties"
      • Purpose: Validates the properties of the data field container.
      • Return: Returns a list of validation errors.
    • "GetVariableDefinition(string Name)"
      • Purpose: Retrieves a variable definition by name.
      • Return: Returns a "VariableDefinition" object.
    • "GetFromPath(IEnumerable<string> Path)"
      • Purpose: Finds a descendant element by its code path.
      • Return: Returns the found DataElement.
    • "GetPathTo(DataElement Element, bool UseCodeNames, string Delimiter)"
      • Purpose: Returns the path from this container to a descendant element.
      • Return: Returns the path as a string.
    • "GetPathIds(DataElement Descendant)"
      • Purpose: Returns the IDs of elements in the path to a descendant.
      • Return: Returns a set of GUIDs.
    • "GetPathElements(DataElement Descendant)"
      • Purpose: Returns elements in the path to a descendant.
      • Return: Returns a list of "DataElement" objects.
    • "IsSingleInstance(DataElement Descendant)"
      • Purpose: Determines if a descendant is a single instance.
      • Return: Returns a boolean.
    • "GetSourceFields()"
      • Purpose: Returns source fields for the container.
      • Return: Returns a dictionary of "DataField" and their paths.

Summary

The "DataFieldContainer" class is a fundamental component of Grooper, designed to manage hierarchical data structures. It provides extensive functionality for handling child elements, applying styles, performing lookups, and validating data, making it a versatile base class for more specialized data elements.

Applying ObjectCommand<DataFieldContainer> to our Code

Our usage of this class will be fairly straightforward. We will have the "SetStyles" class inherit from it, and eventually make a simple modification to the required "Execute" method.

As the definition stated, the <DataFieldContainer> generic type passed to the "ObjectCommand" base class is defined in the "Grooper.Core" namespace, so we will need to include a using directive for it.

  1. Note that "Grooper.Core" is already in our project's references.
  2. Add the using directive:
    <syntaxhighlight lang="csharp" line start="11">using Grooper.Core;</syntaxhighlight>
  3. Modify "SetStyles" class to allow for inheritance from the base class:
    <syntaxhighlight lang="csharp" line start="23">public class SetStyles : ObjectCommand<DataFieldContainer></syntaxhighlight>
  4. Notice after setting inheritance for the "SetStyle" class there is an error. Mouse over "SetStyles" for details.


  1. In the error pop-out, mouse-over the drop-down for the lightbulb icon.
  2. Mouse over "Implement abstract class".
  3. Click "Preview changes" in the pop-out window.
  4. Click "Apply" in the "Preview Changes" window.


  1. Notice the code inserted on lines 56-60. We will change this soon.
    • If we left this code and ran the solution as is, it would run. However, if we attempted to use the object command it would simply return an exception on execution.

Understanding the StringBuilder Class

The newly added "Execute" method will ultimately rely on an instantiation of the "StringBuilder" class. Considering that, let's get an understanding of this class and its use.

StringBuilder Definition

The "StringBuilder" class in the "System.Text" namespace is a mutable string of characters, designed to efficiently perform string manipulations. Unlike the "String" class in .NET, which creates a new object in memory each time its value is modified, StringBuilder can change its contents without creating new instances. This makes StringBuilder particularly useful for scenarios where extensive string manipulation is required, such as in loops or complex concatenations.

Key Features of "StringBuilder"

  • Mutable Strings:
    • "StringBuilder" allows modification of the string content without creating new string instances, thus providing better performance for repeated modifications.
  • Dynamic Size:
    • It can dynamically grow as needed, handling changes in the string size automatically.
  • Efficient Operations:
    • Provides methods for appending, inserting, removing, and replacing characters or strings, which are more efficient compared to similar operations on String.
  • Capacity Management:
    • Allows setting the capacity (the maximum number of characters it can contain) and the maximum capacity, optimizing memory usage.

Example Usage <syntaxhighlight lang="csharp">using System; using System.Text;

class Program {

   static void Main()
   {
       // Initialize a new StringBuilder
       StringBuilder sb = new StringBuilder("Hello");
       // Append strings
       sb.Append(", ");
       sb.Append("World!");
       Console.WriteLine(sb.ToString()); // Output: Hello, World!
       // Insert a string at a specified index
       sb.Insert(5, " beautiful");
       Console.WriteLine(sb.ToString()); // Output: Hello beautiful, World!
       // Replace a substring with another string
       sb.Replace("beautiful", "wonderful");
       Console.WriteLine(sb.ToString()); // Output: Hello wonderful, World!
       // Remove a substring
       sb.Remove(5, 10);
       Console.WriteLine(sb.ToString()); // Output: Hello, World!
   }

} </syntaxhighlight> Key Methods and Properties

  • Constructors:
    • "StringBuilder()": Initializes a new instance with default capacity.
    • "StringBuilder(string)": Initializes a new instance with the specified string.
    • "StringBuilder(int capacity)": Initializes a new instance with the specified capacity.
    • "StringBuilder(string, int capacity)": Initializes a new instance with the specified string and capacity.
  • Properties:
    • "Capacity": Gets or sets the number of characters the current instance can hold.
    • "Length": Gets or sets the length of the string in the current instance.
    • "MaxCapacity": Gets the maximum number of characters that the instance can hold.
  • Methods:
    • "Append": Appends the string representation of a specified object to this instance.
    • "Insert": Inserts the string representation of a specified object at the specified character position.
    • "Remove": Removes the specified range of characters from this instance.
    • "Replace": Replaces all occurrences of a specified character or string in this instance with another specified character or string.
    • "ToString": Converts the value of this instance to a String.

Summary The "StringBuilder" class is a highly efficient way to manipulate strings in .NET, especially useful in scenarios requiring frequent and extensive string modifications. By reducing the overhead of creating new string instances and managing memory more effectively, "StringBuilder" offers significant performance benefits over using the immutable "String" class for complex string operations.

Adjusting the Execute Abstract Method

Because the "SetStyles" class is inheriting from the "ObjectCommand<DataFieldContainer>" class the "Execute" method must be defined. Lets get an understanding of how we will implement this method to accomplish our goal.

Execute Method Definition

This method will be designed to generate CSS styles dynamically based on the properties of the "SetStyles" class and apply these styles to a "DataFieldContainer" object. Here is the complete method code for we will be implementing: <syntaxhighlight lang="csharp"> protected override void Execute(DataFieldContainer Item) {

   var css = new StringBuilder();
   css.AppendLine($".DataField > label {{");
   css.AppendLine($"  width: {LabelWidth};");
   css.AppendLine($"  color: {ConvertToString(LabelForegroundColor)};");
   css.AppendLine($"}}");
   css.AppendLine();
   css.AppendLine($".DataField > input {{");
   css.AppendLine($"  color: {ConvertToString(InputForegroundColor)};");
   css.AppendLine($"}}");
   Item.CSS = css.ToString();

} </syntaxhighlight>

Detailed Breakdown

  • Method Signature:
    <syntaxhighlight lang="csharp">protected override void Execute(DataFieldContainer Item)</syntaxhighlight>
    • "protected": The method is accessible within its class and by derived class instances.
    • "override": This indicates that the method is overriding a base class method.
    • "void": The method does not return any value.
    • "Execute": The name of the method.
    • "DataFieldContainer Item": The method takes a single parameter of type DataFieldContainer, representing the object on which the CSS will be applied.
  • StringBuilder initialization:
    <syntaxhighlight lang="csharp">var css = new StringBuilder();</syntaxhighlight>
    • "StringBuilder" is used to construct the CSS string efficiently.
  • Building CSS for Label:
    <syntaxhighlight lang="csharp">css.AppendLine($".DataField > label {{");

css.AppendLine($" width: {LabelWidth};"); css.AppendLine($" color: {ConvertToString(LabelForegroundColor)};"); css.AppendLine($"}}"); css.AppendLine(); </syntaxhighlight>

    • "css.AppendLine($".DataField > label {{");": Starts a CSS rule for labels within elements of class DataField.
    • "css.AppendLine($" width: {LabelWidth};");": Adds a line setting the label's width using the LabelWidth property.
    • "css.AppendLine($" color: {ConvertToString(LabelForegroundColor)};");": Adds a line setting the label's color using the LabelForegroundColor property, converted to a CSS-compatible string.
    • "css.AppendLine($"}}");": Closes the CSS rule.
    • "css.AppendLine();": Adds an empty line for better readability.
  • Building CSS for Input:
    <syntaxhighlight lang="csharp">css.AppendLine($".DataField > input {{");

css.AppendLine($" color: {ConvertToString(InputForegroundColor)};"); css.AppendLine($"}}"); </syntaxhighlight>

    • "css.AppendLine($".DataField > input {{");": Starts a CSS rule for input elements within elements of class DataField.
    • "css.AppendLine($" color: {ConvertToString(InputForegroundColor)};");": Adds a line setting the input's color using the InputForegroundColor property, converted to a CSS-compatible string.
    • "css.AppendLine($"}}");": Closes the CSS rule.
  • Assigning the CSS to the Item
    <syntaxhighlight lang="csharp">Item.CSS = css.ToString();</syntaxhighlight>
    • Converts the constructed "StringBuilder" object to a string and assigns it to the "CSS" property of the "DataFieldContainer" object.

Summary The "Execute" method constructs CSS styles dynamically based on the properties "LabelWidth", "LabelForegroundColor", and "InputForegroundColor" of the "SetStyles" class. It uses a "StringBuilder" to create the CSS rules and then assigns the resulting CSS string to the "CSS" property of the "DataFieldContainer" object. This method ensures that the labels and input fields within "DataField" elements are styled according to the specified properties.

Applying Changes to the Execute Method

  1. The required using directive for "StringBuilder" is already declared.


  1. Apply the changes tot he "Execute" method and add remarks
  2. <syntaxhighlight lang="csharp" line start="56">/// <summary>

/// A required abstract method that defines what occurs on 'execute' of the "ObjectCommand" base class /// </summary> /// <param name="Item">Stores the object against which the "ObjectCommand" base class is being executed</param> protected override void Execute(DataFieldContainer Item) { var css = new StringBuilder(); css.AppendLine($".DataField > label {{"); css.AppendLine($" width: {LabelWidth};"); css.AppendLine($" color: {ConvertToString(LabelForegroundColor)};"); css.AppendLine($"}}"); css.AppendLine(); css.AppendLine($".DataField > input {{"); css.AppendLine($" color: {ConvertToString(InputForegroundColor)};"); css.AppendLine($"}}"); Item.CSS = css.ToString(); }</syntaxhighlight>

Adding our Code to Grooper

Our code is now complete. Now we can push our code to Grooper and compile it. This is easily accomplished with the Grooper SDK.

  1. Start by saving your work.


  1. In th e"Extensions" menu, in the "Grooper" section choose "Save and Compile".
  2. Click "Yes" in the confirmation window to overwrite the files of the Object Library in Grooper.
  3. When the process is complete a window will confirm the successful save and compile.


  1. Recycle the "GrooperAppPool" in the IIS Manager.


  1. In Grooper, right-click on a "DataFieldContainer" object, in this case a "Data Model", and you will see the new object command.
  2. The properties we defined in code will be displayed in the property grid of the corresponding dialog box.


  1. Execution of the command will set the CSS styles for the Style Sheet property.


  1. On the root node...
  2. ...click on the "Scripts" tab.
  3. Any objects that contain solution files related to scripting will be listed here along with their compiled status.
  4. You can even compile them from here using the "Compile" button..