[CONNECT C#] add custom property type to item type array property.

i have an item type that contains a property. this property has Is Array set to true and its type is a custom property type that i made.

trying to figure out how to add a value to this array property. 

Not seeing it in the sdk. i see there is a XmlStringValue that is a get/set. but when i try to set it, it just crashes. assuming i cant use that to add a new value to an array but could use it to change existing values??

Anyways anyone see a way to do this. i hope im just missing it.

EDIT:

adding some code that i have, the array property is named Values, the custom type is named linker. the custom type contains several string properties...

            CustomItemHost host = new CustomItemHost(Views.LinkSetup._MainWindowViewModel.selTextElement, false);
            IList<BD.DgnEC.IDgnECInstance> ItemTypes = host.CustomItems;
            //loop each itemtype attached to element
            foreach (BD.DgnEC.IDgnECInstance curItemType in ItemTypes)
            {
                //find item type with specfied name
                if (curItemType.ClassDefinition.Name.ToUpper().Contains(Globals.itemTypeName.ToUpper()))
                {//found, update
                    //find array property (its named Values)
                    foreach (var curprop in curItemType)
                    {
                        if (curprop.Property.DisplayLabel.ToUpper() == "VALUES")
                        {
                            //add entry to values array
                            //stuck on how to do this..
                            //try and modify xml string to add another value to the VALUES propety (which is a custom propety tyoe named linker)
                            string newxmlstring = curprop.XmlStringValue;
                            newxmlstring.Replace("</Values>", "");
                            newxmlstring += "<linker><isFromReference>False</isFromReference><ElementID>5</ElementID><ModelName>f</ModelName><PropertyName>df</PropertyName><FilePath>test2</FilePath><PropertyType>Element</PropertyType><PropertyValue>df</PropertyValue><AccessString>afsdfdfasf</AccessString><ItemTypeName /></linker></Values>";
                            //causes crash
                            curprop.XmlStringValue = newxmlstring;
                        }
                    }
                }

            }

i was thinking i would need to get the custom property type and populate it with what i want, then add it as a value to the item type property. but see nothing in the SDK for this...i starting to do this to get the property type...

BD.ItemTypeLibrary OHDOTLinkersLib = BD.ItemTypeLibrary.FindByName(Globals.itemTypeLibName, activeFile);
BD.ItemType it_TextFieldLinker = OHDOTLinkersLib.GetItemTypeByName(Globals.itemTypeName);
BD.CustomProperty itProp = it_TextFieldLinker.GetPropertyByName("Values");
BD.CustomPropertyType itPropType = OHDOTLinkersLib.GetCustomTypeByName("linker");

Parents
  • Editing the XML directly sounds like a recipe for disaster...

    All the EC stuff originated as C# code. The API is extremely broad. I believe it is documented separately as part of the ECFramework.

    In this particular case your "values" property is an IECArrayValue. IECArrayValue.Add() appends a new entry to the array and returns it. You can then modify the properties of that returned object (assuming it is a struct type).

    can hopefully tell you where to find the ECFramework documentation.

    Answer Verified By: John Drsek 

  • Editing the XML directly sounds like a recipe for disaster...

    I did not want to edit the xml just wasn't seeing anything else.

    I swear the first thing I tried was exactly what your saying but there was no Add function.

    Just went back and checked and I had it as a ECDArrayValue but casting it to IECArrayValue then gives me the add method.

    I figured it was going to be something simple, cant tell you how long I was messing around with this

    THANK YOU!..ill post some code in a little bit for anyone that might find it beneficial..

    ....side note...

    it appears if the array property does not have an entry yet it wont show up as a property when looping the itemtype properties.

                CustomItemHost host = new CustomItemHost(Views.LinkSetup._MainWindowViewModel.selTextElement, false);
                IList<BD.DgnEC.IDgnECInstance> ItemTypes = host.CustomItems;
                foreach (BD.DgnEC.IDgnECInstance curItemType in ItemTypes)
                {
                    if (curItemType.ClassDefinition.Name.ToUpper().Contains(Globals.itemTypeName.ToUpper()))
                    {//found, update
                        foreach (var curprop in curItemType)
                        {//entpy array property wont show up in this loop!!
                        
                        }
                    }
                }

    have to look into this.. but thinking you need to use something like this

    curItemType.ClassDefinition.FindLocalProperty("Values");
    

    then somehow add it to the item type instance...

  • Hi John,

    I am late in this discussion, so just a few comments ;-)

    There are several topics presented in the discussion:

    • EC API is not always inuitive. In my opinion it's not because of bad design, often when an API needs to work with more data types, types testing and casting is required.
    • Unfortunately there is no good explanation of EC Framework available, even it exists in Bentley product for over 15 years :-( ... The documentation mentioned by Bob helps, but it's just basic, it's not guide or tutorial (and, delivered examples are also not guides because not explained).
    • People (3rd party developers ... us ;-) do not understand EC Framework, ItemTypes subsystem and what are differences. It leads to pain and confusion, but often also to situation when ItemTypes is requested to be extended by functionality, where to create own EC schema is much better solution.
    I figured it was going to be something simple, cant tell you how long I was messing around with this

    I have no CE code available to share, all my production EC Framework code is still V8 based, but concepts and API structure remain the same. When working with ItemTypes (which I do not plan at all, to create custom EC schemas is easier and less limiting for me ;-), it's crucial to understand what is "behind the scene":

    • When custom ItemType property type is created, it's represented by EC class configured to be struct and not to be domain class. This setting allows to reference one class from another (to nest data structures).
    • When ItemType property is created as an array, it's represented by EC property with IsArray == true;

    From code perspective (extending information provided by Paul):

    • Every class instance is represented by IECInstance. Do not confuse EC Class instance (attached to element) with EC Class definition (defined in EC schema).
    • Every property is represented by IECPropertyValue. It has more roles and in fact represents two different perspectives:
      • It publishes information about property characteristics (like what value type, whether it's array etc.) that are defined in EC Class definition and stored in EC schema.
      • It also allow to access the property value (depending on the value type).
    • IECPropertyValue is inherited by other interfaces that define property types closer. It means IECPropertyValue exposes "basic" functionality available in all types, but when anything "more special" is required, casting has to be used ... and you have to ensure you use valid casting ;-)
      In your case the best casting (just my idea ;-) can be explained using 2-dimensional matrix:
      • ItemType custom property is defined using EC struct, so IECPropertyValue should be casted to IECStructaValue.
      • You defined your ItemType to use array, so IECPropertyValue should be casted to IECArrayValue.
      • You can merge both and to case IECPropertyValue to IECStructArrayValue.
    • When accessing array properties, you should be aware that the property represents "array placeholder", not any value from an array. It's more like reference to the array itself.
    it appears if the array property does not have an entry yet it wont show up as a property when looping the itemtype properties.

    I have not tested it, but it seems to be logical: Whereas a property definition exists always in ECClass definition, the value instance existence is not mandatory. I think foreach enumeration can be imagined as enumerating XML structure, so when a node (representing a particular property) does not exists, because its value has not been set, it's not included in the enumeration.

    I think you should use IECInstance.GetEnumerator(bool includeAll) when you want to receive also not existing (not set yet) properties. See ecobjects.chm for more information how to work with temporary IECPropertyValue instances.

    if (curItemType.ClassDefinition.Name.ToUpper().Contains(Globals.itemTypeName.ToUpper()))

    I do not know context (and in fact I do not know CE EC API well enough yet too ;-), but it looks really weird. What is an intention of this test?

    With regards,

      Jan

Reply
  • Hi John,

    I am late in this discussion, so just a few comments ;-)

    There are several topics presented in the discussion:

    • EC API is not always inuitive. In my opinion it's not because of bad design, often when an API needs to work with more data types, types testing and casting is required.
    • Unfortunately there is no good explanation of EC Framework available, even it exists in Bentley product for over 15 years :-( ... The documentation mentioned by Bob helps, but it's just basic, it's not guide or tutorial (and, delivered examples are also not guides because not explained).
    • People (3rd party developers ... us ;-) do not understand EC Framework, ItemTypes subsystem and what are differences. It leads to pain and confusion, but often also to situation when ItemTypes is requested to be extended by functionality, where to create own EC schema is much better solution.
    I figured it was going to be something simple, cant tell you how long I was messing around with this

    I have no CE code available to share, all my production EC Framework code is still V8 based, but concepts and API structure remain the same. When working with ItemTypes (which I do not plan at all, to create custom EC schemas is easier and less limiting for me ;-), it's crucial to understand what is "behind the scene":

    • When custom ItemType property type is created, it's represented by EC class configured to be struct and not to be domain class. This setting allows to reference one class from another (to nest data structures).
    • When ItemType property is created as an array, it's represented by EC property with IsArray == true;

    From code perspective (extending information provided by Paul):

    • Every class instance is represented by IECInstance. Do not confuse EC Class instance (attached to element) with EC Class definition (defined in EC schema).
    • Every property is represented by IECPropertyValue. It has more roles and in fact represents two different perspectives:
      • It publishes information about property characteristics (like what value type, whether it's array etc.) that are defined in EC Class definition and stored in EC schema.
      • It also allow to access the property value (depending on the value type).
    • IECPropertyValue is inherited by other interfaces that define property types closer. It means IECPropertyValue exposes "basic" functionality available in all types, but when anything "more special" is required, casting has to be used ... and you have to ensure you use valid casting ;-)
      In your case the best casting (just my idea ;-) can be explained using 2-dimensional matrix:
      • ItemType custom property is defined using EC struct, so IECPropertyValue should be casted to IECStructaValue.
      • You defined your ItemType to use array, so IECPropertyValue should be casted to IECArrayValue.
      • You can merge both and to case IECPropertyValue to IECStructArrayValue.
    • When accessing array properties, you should be aware that the property represents "array placeholder", not any value from an array. It's more like reference to the array itself.
    it appears if the array property does not have an entry yet it wont show up as a property when looping the itemtype properties.

    I have not tested it, but it seems to be logical: Whereas a property definition exists always in ECClass definition, the value instance existence is not mandatory. I think foreach enumeration can be imagined as enumerating XML structure, so when a node (representing a particular property) does not exists, because its value has not been set, it's not included in the enumeration.

    I think you should use IECInstance.GetEnumerator(bool includeAll) when you want to receive also not existing (not set yet) properties. See ecobjects.chm for more information how to work with temporary IECPropertyValue instances.

    if (curItemType.ClassDefinition.Name.ToUpper().Contains(Globals.itemTypeName.ToUpper()))

    I do not know context (and in fact I do not know CE EC API well enough yet too ;-), but it looks really weird. What is an intention of this test?

    With regards,

      Jan

Children
  • Jan, its been awhile but im getting back to this. first thank you for your input

    I have not tested it, but it seems to be logical: Whereas a property definition exists always in ECClass definition, the value instance existence is not mandatory. I think foreach enumeration can be imagined as enumerating XML structure, so when a node (representing a particular property) does not exists, because its value has not been set, it's not included in the enumeration.

    I think you should use IECInstance.GetEnumerator(bool includeAll) when you want to receive also not existing (not set yet) properties. See ecobjects.chm for more information how to work with temporary IECPropertyValue instances.

    this is exactly whats going on.. i think. but im still having trouble getting this to work. when i use IECArrayValue.Add(). and then check the count on IECArrayValue the count goes up by one (meaning it was added).  but i cant seem to actually get that to apply to the element. if i just use the IECArrayValue.Add() and then finish the method like that, when i go and check the element the additional struct in the property array its not there. Is there something else i need to do to apply or save after i call the Add method??

    the other thing i dont know if im doing right is when i go and set values for properties in that new struct that i just added to the property array. 

    here is my code 

    //get host container for item types on selected element
            CustomItemHost host = new CustomItemHost(_MainWindowViewModel.selElementData.selTextElement, false);
            //get host container for item types on selected element
            IList<BD.DgnEC.IDgnECInstance> ItemTypes = host.CustomItems;
            //loop item types on selected element
            foreach (BD.DgnEC.IDgnECInstance curItemType in ItemTypes)
            {
                //find desired item type instance
                if (curItemType.ClassDefinition.Name.ToUpper() == GlobalVars.LinkeritemTypeName.ToUpper())
                {//found, update
                    //loop each property in item type instance on element
                    foreach (var curprop in curItemType)
                    {
                        if (curprop.Property.DisplayLabel.ToUpper() == "XRef".ToUpper()) 
                        {//found array property, property is a struct (has multiple sub-properties)
                            //add another entry to array
    
                            Bentley.ECObjects.Instance.IECArrayValue curproptest = (Bentley.ECObjects.Instance.IECArrayValue)curprop;
                            Bentley.ECObjects.Instance.IECPropertyValue newentry = curproptest.Add();
                            //get enumerator to get to temporary IECPropertyValues (properties that dont have values)
                            Bentley.ECObjects.Instance.IECValueContainer test = (Bentley.ECObjects.Instance.IECValueContainer)curproptest[newentry.ArrayIndex];
                            IEnumerator<IECPropertyValue> iecPropvals2 = test.GetEnumerator(true);
                            //loop through properties in the new stuct that was added to the property array
                            while (iecPropvals2.MoveNext())
                            {
                                if (iecPropvals2.Current.AccessString.ToUpper().Contains("TestProperty"))
                                {
                                    //cant set value on Temporary IECPropertyValue get permanent one (per ECObjects.chm)
                                    IECPropertyValue permanent = iecPropvals2.Current.Instance[iecPropvals2.Current.AccessString];
                                    permanent.StringValue = "I DID IT";
                                    break;
                                }
                            }
                            break;
                        }
                    }
                    break;
                }
            }


    any help??

  • Hi John,

    can you post your DGN to illustrate, how your ItemType definition looks like? E.g. DGN with ItemType definition plus one element with some data attached?

    Regards,

      Jan

  • yeah its attached,

    4118.testing.dgn

    So basically in SS4 we had a cross reference app.when a plan sheet had a text element that called out to another sheet like "FOR DETAILS SEE SHEET 7" we had an app that would make the the sheet number a data entry region and would store details about the dgn file/model that sheet 7 existed at. then the app  has an update mode. so if at the end of the project the page number changed you could run this app and it would update the text element to show the correct sheet number.

    So im remaking that for CONNECT and thought text fields and item types would be easier.

    basically i want to do exactly what a text field does. but i cant have a text field link to an element property from another file/model thats not referenced. so i made a item type that will get applied to the text element. this item type contains the needed information to find the correct value(sheet number) in another dgn file. The app will add a text field to the text element that represents the sheet number. this text field is linked to the property in the item type that represents the sheet number. 

    the item type property as an array because the text element could have more then one cross reference. Now all the app needs to do to update the values is to just update the item type value and the text field will automatically get updated so i dont actually have to edit the text element itself to update the value.

    hopefully that made sense. but regardless. my example code above is just adding a new struct to an array property then updating the property values for that new struct in the array. dont know why its not working. but it could be applied to numerous cases. 

  • wow i cant believe i forgot to use .WriteChanges() on my item type to apply the changes...ugh that was stupid my code above works.

  • Hi John,

    wow i cant believe i forgot to use

    Welcome to the club! :-))) ... such things happen every day

    here is my code 

    The code looks a bit weird to me, even when I guess functional (when WriteChanges() is used). Maybe it's only because of using full classes names including namespaces (which I treat as very bad habit personally). But e.g. to use DisplayString to evaluate anything is wrong idea generally.

    Not sure whether I will find enough time, but I should think whether better code (whatever it means) can be written.

    So im remaking that for CONNECT and thought text fields and item types would be easier.

    I agree. Not because of text fields and item types specifically, but because of using standard MicroStation features.

    And item types are always good, because when not sufficient, can be extended to general EC data, that can handle ... anything ;-)

    basically i want to do exactly what a text field does.

    Well, you can ... but you can't. The text fields background is (I guess) really complex, implemented using more other technologies (DependencyManager at the first case), so to achieve the same functionality is about to use C++ (NET is not enough) and many months of work. But it's probably not your aim in fact.

    so i dont actually have to edit the text element itself to update the value.

    This behavior makes the whole solution much (much much much) easier :-)

    hopefully that made sense

    Yes, it does :-)

    Of course when I read your explanation, some questions arose, but the main idea is fine I think.

    With regards,

      Jan

  • i want to do exactly what a text field does

    The API for text and text node elements in CONNECT revolves around the TextBlock class.  The C++ MicroStationAPI provides TextBlock.InsertField() etc., but I don't see those methods in the DgnPlatformNet .NET API.

    File a request to have the .NET TextBlock expanded to include TextField manipulation.

    all the app needs to do to update the values is to just update the item type value and the text field will automatically get updated

    Correct!  Unfortunately, C++ is again ahead of .NET.  The MicroStationAPI provides the TextField class, but I can't find that in the .NET documentation (but that could be me — I often fail to find things in the multi-file .NET documentation).

    File another request to have the TextField included in .NET.

    the main idea is fine I think

    I agree with Jan, but you're hamstrung by deficiencies in the .NET API.

     
    Regards, Jon Summers
    LA Solutions

  • The code looks a bit weird to me, even when I guess functional (when WriteChanges() is used). Maybe it's only because of using full classes names including namespaces (which I treat as very bad habit personally).

    i dont normally use full namespaces but when i post code i copy the part and edit it up to either show the using statements or add in the full namespaces to help other users. i guess i could also add what dll they are coming from to be complete. Also this code was after 2 hours of trying many different ways of doing the same thing trying to get it to work so it got a little messy in my frustration (until i remembered the write changes). i have since cleaned it up a little bit.

    But e.g. to use DisplayString to evaluate anything is wrong idea generally.

    i just gravitate towards the display string because i normally try to write the app as dynamic as i can so others can apply it. so ill have the app read a config variable to get names of things like item types. and its easier for others to use the name they see in their item types when setting the config variable. I still have a validation method to ensure the found item type meets the min. requirements. so i see what your saying but i think i built in enough safe guards to use it. so i hardcoded some text with i will be reading in instead in the final app.

    The text fields background is (I guess) really complex, implemented using more other technologies (DependencyManager at the first case), so to achieve the same functionality is about to use C++ (NET is not enough) and many months of work. But it's probably not your aim in fact.

    yeah i have figured that out. i had another post where i posted my c++ function exported to C and then wrapper in c# to just add the text field.for now thats all i need.

    as always thanks for your input

  • Correct!  Unfortunately, C++ is again ahead of .NET.  The MicroStationAPI provides the TextField class, but I can't find that in the .NET documentation (but that could be me — I often fail to find things in the multi-file .NET documentation).

    File another request to have the TextField included in .NET.

    yes i had to export a c++ function to c and then wrap it in C# to add a text field, it was a pain but got it working. thats all i need for now. didnt even try to figure out how to enter the field to a specific spot..maybe version 2.

    i can put in a S.R. for get it added. last time i submitted a defect on an api call can was told all SDK related stuff should be posted to the communities. 

  • Last time I submitted a defect on an API call can was told all SDK related stuff should be posted to the communities

    Others have implied that to summon is our best (and possibly only) recourse.  Prepare a pentagram and light candles.

     
    Regards, Jon Summers
    LA Solutions

  • Prepare a pentagram and light candles.

    Maybe also to make a floral sacrifice and to do some invocation? :-)

    Regards,

      Jan