[SS10 VBA] Retain existing selection set

I have noticed that if I have an existing selection set of elements and I then create a new instance of a class which uses ILocateCommandEvents, my previously selected elements are no longer selected.

Is there a way that I can retain the selection and add to it with ILocateCommandEvents?

  • Hi Barry,

    my previously selected elements are no longer selected.

    Yes, it is standard behavior of MicroStation VBA API.

    Is there a way that I can retain the selection and add to it with ILocateCommandEvents?

    You have to do it yourself: When selection set is active, remember them before the command is stated, pass them to the command and create the same selection set again.

    Regards,

      Jan

  • A solution I theorised has worked but I would still like to hear if there is a simpler method:

    1. Using GetSelectedElements, enumerate through the selected elements and store their element ID's in a dynamic 2D DLong array
    2. Do the tasks where ILocateCommandEvents is used
    3. In the ILocateCommandEvents_Reset event, do this:
      Private Sub ILocateCommandEvents_LocateReset()
          Dim i                                        As Integer
          For i = LBound(arrElementIds) To UBound(arrElementIds)
              ActiveModelReference.SelectElement ActiveModelReference.GetElementByID(arrElementIds(i)), True
          Next i
          FrmMain.Show
      End Sub

    Its a bit of a hack as a user could potentially select an element that was previously selected, but it works.

  • Hi Barry,

    GetSelectedElements, enumerate through the selected elements and store their element ID's

    Why to store IDs, when enumerator exists already? It holds all necessary information about selected elements.

    To memorize IDs just wastes memory. Plus, it does not work (even worse, it leads to wrong results) when elements from reference(s) are selected.

    In the ILocateCommandEvents_Reset event, do this

    I thought you asked about something different originally: Is there a way that I can retain the selection and add to it with ILocateCommandEvents?

    I interpret it as how to work with the selection set inside location code, when the tool is started, but to select elements when Reset is used does not make any sense to me. Why to select elements when the code is reset?

    I would still like to hear if there is a simpler method:

    The simplest method is to check, if selection set is active before CommandState.StartLocate is called, and to pass this information to the tool code, when the class instance is created. After that, e.g. in Start method, key-in "choose previous" can be used, so the tool will start with the same selection set active.

    If you want to implement more specific functionality, VBA is bad choice and other API (in V8i it's C/C++) has to be used, not "pre-cooked user-oriented VBA classes".

    With regards,

      Jan

  • Hi Jan,

    Why to store IDs, when enumerator exists already? It holds all necessary information about selected elements.

    My experience with the ElementEnumerator is somewhat limited, typically using it with scancriteria for accessing specific element types so I'm not familiar with its full functionality. What you need to appreciate is that I don't have your knowledge and expertise so when faced with a problem, I'm not going to just sit and wait and expect someone to provide me with a written solution. Instead, I will use the knowledge I have and try to formulate a logical approach to solve the problem. In this instance, when I noticed my preselected elements were deselecting, the only way I could think of reselecting them was to use their ID's and append the selection set created by ILocateCommandEvents.

    To memorize IDs just wastes memory. Plus, it does not work (even worse, it leads to wrong results) when elements from reference(s) are selected.

    No references are used in this scenario, just active elements. As far as memory goes, I have yet to encounter any such issues.

    I thought you asked about something different originally: Is there a way that I can retain the selection and add to it with ILocateCommandEvents?

    What I was aiming for was the selection set to remain highlighted whilst ILocateCommandEvents is used however I did not know how to do that and could not find anything in the help file to assist.

    Why to select elements when the code is reset?

    As you know, when ILocateCommandEvents is active it awaits element selection from the user. When I am finished selecting elements, the reset event is triggered and a userform shows. At this point, I don't want just the elements selected via ILocateCommandEvents to be selected, but the initial selection set (if one exists) as well.

    The simplest method is to check, if selection set is active before CommandState.StartLocate is called, and to pass this information to the tool code, when the class instance is created.

    What would be the most suitable way to Pass it?

    After that, e.g. in Start method, key-in "choose previous" can be used, so the tool will start with the same selection set active.

    I'm not a fan of using keyins and prefer to avoid it unless there is no other option.

    If you want to implement more specific functionality, VBA is bad choice and other API (in V8i it's C/C++) has to be used, not "pre-cooked user-oriented VBA classes".

    That may be true but as I do not know C/C++, that's not an option for me.

  • so I'm not familiar with its full functionality

    ElementEnumerator is one from the core mechanisms in MicroStation VBA API, and, in wider context, enumeration (of enumerable collections) is a standard feature of many languages like C++ (where it's called iterator), Java (offering both enumerator and iterator ;-), C#, VB.NET, TypeScript etc.

    In general, enumerator does not offer anything else than described in MicroStation VBA help: A standard way how to go through a collection of (in the discussed case) elements. Nothing more. The only crucial thing is to understand (as explained in the documentation) that enumerator is a way how to access the elements, but it is not the element collection itself (the enumerator of course knows what data it should access and enumerate).

    In fact, the only situation, that can look like a black magic (but it's not ;-) is scanning: Because enumerator only "mediates the access" to elements, it cannot react anyhow when the source collection is modified. So, when during scanning an element is modified in such way its size growths and it must to be moved to the end of the file, it's enumerated again, because enumerator work until end of the scanned model is reached.

    I will use the knowledge I have and try to formulate a logical approach to solve the problem.

    It's the reason why I am surprised ElementEnumerator is not used directly ;-)

    When any result is received, the "logical approach" is to try to use it without any further conversion or processing, because it always require more steps, potentially may lead to more bugs, makes code more complex and requires more memory.

    And even when ElementEnumertor is not accepted choice, Element object is the right short-time representation of particular element (moreover, array of elements can be obtained from enumerator directly). Element ID is long-term element identification, but cannot be used in API directly and must be typically converted into Element (again).

    As far as memory goes, I have yet to encounter any such issues.

    That's about a perspective and priorities: "Until a problem is visible" approach is fine when in-house macro is developed, because any such problem can be fixed easily. For me, expected memory consumption is one from things I check always, because I never know, whether the code will be used with a few or millions elements. To consume more memory is not about resources only, but also about performance.

    On the other hand: Probably anything, that can be done using VBA, don't have to take care about memory at all ;-)

    Margin note: It's a sad reality that 90% of developers today are "resources blind", because we have a lot of memory and fast processors. The only professionals, used to think about limited resources (especially memory) are low-level programmer (IoT and chips like Arduino) and people working with big data (like AI models, where 1 TB dataset is a standard size ;-).

    What I was aiming for was the selection set to remain highlighted whilst ILocateCommandEvents is used

    As I wrote, the selection set should be recreated, when Start method is called. In such case, the "selection set reset" is not recognized by a user.

    What would be the most suitable way to Pass it?

    I think public variable, defined by the class, is enough in this case. Alternatively, public Set property can be used too.

    The code workflow should be: To instantiate the locate object, assign the enumerator (or array or any other data) to the object, and to use CadInputQueue to start the tool (using the object, that already knows about previous selection set).

    I'm not a fan of using keyins and prefer to avoid it unless there is no other option.

    In this case it's not about "no other option", but about performance. To add an element to selection set is the slowest operation I know in MicroStation VBA API. This issue (feature?) was discussed many times. Not visible when just a few elements are added to the selection set, but when there are more, the delay is longer and longer, as elements are added one by one (can be tens of seconds for large selections).

    In contrast to it, the key-in effect is immediate (complete list of previously selected elements is maintained by MicroStation internally, and applied at once).

    That may be true but as I do not know C/C++, that's not an option for me.

    One from the core rules of software development is that to solve particular problem, the right tool must be used. I do not know details of the discussed code and overall context, so I can not say anything here. But I remember (not so small) amount of discussion with my customers, where my advice was "cut the required functionality to a half and you can do it in VBA, or choose another API / tool".

    The problem is that especially when tools are implemented, available features are, when compared to flexibility of C/C++ (in V8i) and also NET (in CE), just a fraction, so some workflows and procedures, following MicroStation standards, cannot be implemented in VBA easily.

    With regards,

      Jan

  • I noticed my preselected elements were deselecting, the only way I could think of reselecting them was to use their ID's and append the selection set created by ILocateCommandEvents

    Something like this...

    ' your locator class
    Implements ILocateCommandEvents
    Private m_oSelection As ElementEnumerator
    Public Property Set ExistingSelection (ByVal oSelection As ElementEnumerator)
      Set m_oSelection = oSelection
    End Property
    ... ILocateCommandEvents methods
    

    Usage...

    Dim oLocator As New clsLocator
    Set oLocator.ExistingSelection = ActiveModelReference.GetSelectedElements
    CadCommandQueue.StartLocateCommand oLocator
    CadCommandQueue.StartLocate oLocator
    

    Now your locator class can add to the selection set.

    Named Groups

    The selection set logic is limited when compared to the newer Named Groups.  Consider copying the selection set to a Named Group, then add to that group in your locator logic.  You can create a temporary Named Group that you discard, or a permanent group that can be used another day (which you can't do with a selection set).

     
    Regards, Jon Summers
    LA Solutions

  • Thanks Jon,  I've persisted with the Element Enumerator approach instead of the named group. Your sample code was giving an error on this line:

    CadCommandQueue.StartLocateCommand oLocator

    so I switched it to my normal approach which worked fine:

    CommandState.StartLocate oLocator

  • It's the reason why I am surprised ElementEnumerator is not used directly ;-)

    The criticial information which was not mentioned previously is its method BuildArrayFromContents. I could not recall using the method previously (but apparently have done so some years ago) however it was quite simple to achieve what I wanted with the sample snippet Jon provided and by calling this procedure from ILocateCommandEvents_Start

    Public Sub GetSelectionSet()
        Dim i                                        As Integer
        Dim arrElements() As Element
        arrElements() = m_oSelection.BuildArrayFromContents
        For i = LBound(arrElements) To UBound(arrElements)
        ActiveModelReference.SelectElement arrElements(i), True
        Next i
    End Sub

  • Your sample code was giving an error on this line:

    Oops! 

     
    Regards, Jon Summers
    LA Solutions

  • I have another idea for your reference.

    To keep the existing selection set, you can replace ILocateCommandEvents with IPrimitiveCommandEvents which can retain selection set.