Jump to content

Handling a class for listbox items in a flexible way


Recommended Posts

I'll just explain my thought process and where I'm at now and you guys can let me know if there is a better or more flexible way to do this....

I have a listbox with items, and each item will have corresponding information. For example, I may have a listbox of test step names and each test step has associated information about that particular step.

So, my initial thought was have a "listbox item" class and as part of its private data it could hold a "listbox item information" object. The problem with this is, if I wanted to reuse this listbox functionality on an application which wasn't configuring test steps, but maybe was a list of users and their associated permissions (a class "user permission info"), I wouldn't be able to reuse the "listbox item" class and pass "user permission info" into its private data unless "user permissions" and "test step info" both inherited from "listbox item information". This doesn't really make sense and is too coupled because now if I want to move the test step information class or user permission information class to another project, they are tied to the listbox item information as their base class.

Hopefully that makes sense up to this point. Lots of words and no picture or UML make it difficult, I know.

So my next thought (and current solution) is to have a "listbox item information" class which is in the private data of the "listbox item" class. But, I'd create a class called "Test Step Listbox Item Info" which inherits from "listbox item info" and in this class's private data it would have a "test step information" object.

Now, any time I want to use different item info with the listbox class I can just create a new "<name> listbox item info" class which holds <name> in its private data and everything else should take care of itself.

(man, UML would help this haha, sorry if you're just as confused at the end as the beginning, I can try to reword...it's late).

Edited by for(imstuck)
Link to comment

My first thought is that a listbox is just a UI element for display purposes, and your real classes are “Users” and “Test Info”, which don’t had any obvious reason to inherit from the same parent class. Design them that way, and include a method in each called “Listbox Entries” that returns the necessary info to fill a listbox.

Link to comment

My first thought is that a listbox is just a UI element for display purposes, and your real classes are “Users” and “Test Info”, which don’t had any obvious reason to inherit from the same parent class. Design them that way, and include a method in each called “Listbox Entries” that returns the necessary info to fill a listbox.

I agree with you on this. But, to clarify a bit further, I want to have a VI that manages the drag and drop of the listbox, items moved, copied, and deleted from the listbox etc. If an item is deleted from the listbox, it needs to delete that item from the array that holds the associated information for that item also. I'd like some methods to do these things (which I was going to put in the listbox class), but then these methods need to accept any array type that may be passed to them as an input. so they can be used across apps. But, in order to do that, any array that is passed to this method needs to be made of items that inherit from whatever type the array is on the connector pane of the method. But, I may not want my "test data" class to be coupled to the class that is on the input of these methods.

Does this help, or make more sense? I can put together what I'm thinking and post it a bit later today if that would help

Link to comment

This is where we blur the edge of LVPOOP and Classic LabVIEW since LVPOOP is kind of a kludge.

What I think are trying to do (as I have done on occasions) is to implement you own TList. (This terminology is a throwback to my Delphi programming).

TList is a class with an array of Objects in the private data. The sort of methods that TList has are things like AddItem, RemoveItem, InsertItem, GetItemByIndex, GetItemByName etc and properties such as Count.

It's arguable whether it is required, but I also have a TListItem class which has ItemName and ItemRef as the sole private data in the cluster (mainly because it fits with the hierarchy that I'm used to, but sometimes I use it to abuse the hierarchy). This is the class that I inherit from so I would have TTestItem cLass and TUserItem class and I would manipulate them with the TList. We could actually argue that what I have created is, in fact, a TCollection since any object can be in the list.

This is of course generic. I could for example inherit TListBox (or TTreeList, TTableList et al.) from TList and enforce strictly typed TListboxItem (the latter of which would inherit from TListItem). But hang on. Don't we already have a TListbox? (the ListBox control). Nope. Because it is "Classic LabVIEW". It's properties and methods are all about the control as an object, not the contents of the Listbox which, in classic LabVIEW, is just an array of strings.

So thats a lot of VI's just to enter and extract an array of strings, but it does mean you can also implement things like Sort and Reverse and claim "encapsulation".

Link to comment

I want to have a VI that manages the drag and drop of the listbox, items moved, copied, and deleted from the listbox etc. If an item is deleted from the listbox, it needs to delete that item from the array that holds the associated information for that item also.

I think I understand what you want the Listbox class to do, but what's not clear is how you expect to use it. Do you plan on wiring a listbox reference into the class and handling all the events internally, or are you going to register the listbox with the event structure on the UI block diagram and call Listbox class methods in the appropriate event case? In general I agree with James and Shaun. You could probably rig up a class to do all the back end work for you, but associating classes with UI elements isn't as tidy as we would like.

...but then these methods need to accept any array type that may be passed to them as an input. so they can be used across apps. But, in order to do that, any array that is passed to this method needs to be made of items that inherit from whatever type the array is on the connector pane of the method. But, I may not want my "test data" class to be coupled to the class that is on the input of these methods.

Yep, that's life when using a strongly typed language that doesn't support generics.

You can do as Shaun suggested, and instead of passing an array of objects into the Listbox class pass a Map (or Dictionary, or TListItem...) object. Another option is rather than passing the data collection into the Listbox object, have it spit out an array of strings when the user changes it and pass the array of strings to the collection object.

Link to comment

I am going to respond to these posts as I listen to the commotion of the little girls birthday party in the apartment across from me. So forgive me if I accidentally throw random words into my response because it's what I'm thinking I want to say to my neighbors :P

This is where we blur the edge of LVPOOP and Classic LabVIEW since LVPOOP is kind of a kludge.

What I think are trying to do (as I have done on occasions) is to implement you own TList....

This is of course generic. I could for example inherit TListBox (or TTreeList, TTableList et al.) from TList and enforce strictly typed TListboxItem (the latter of which would inherit from TListItem). But hang on. Don't we already have a TListbox? (the ListBox control). Nope. Because it is "Classic LabVIEW". It's properties and methods are all about the control as an object, not the contents of the Listbox which, in classic LabVIEW, is just an array of strings.

So thats a lot of VI's just to enter and extract an array of strings, but it does mean you can also implement things like Sort and Reverse and claim "encapsulation".

I think this is pretty much on par with what I want. Thanks for the info. I can read into this more and play around with it to see where I get.

I think I understand what you want the Listbox class to do, but what's not clear is how you expect to use it. Do you plan on wiring a listbox reference into the class and handling all the events internally, or are you going to register the listbox with the event structure on the UI block diagram and call Listbox class methods in the appropriate event case?

Number 2 is what i was thinking

Yep, that's life when using a strongly typed language that doesn't support generics.

You can do as Shaun suggested, and instead of passing an array of objects into the Listbox class pass a Map (or Dictionary, or TListItem...) object. Another option is rather than passing the data collection into the Listbox object, have it spit out an array of strings when the user changes it and pass the array of strings to the collection object.

I'll try different things, see how they work out and post back. Then hopefully you guys can give me more direction then, specific to the code I have written. It's usually easier for me to wrap my head around things once I've coded something up and get feedback on that. Thanks!

Edited by for(imstuck)
Link to comment

You can do as Shaun suggested, and instead of passing an array of objects into the Listbox class pass a Map (or Dictionary, or TListItem...) object.

I think you misunderstood. You don't pass an array of objects into the the List (although you could with a TList.Init). The List IS an array of objects and you add the item using the "AddItem" method. So it is already exactly as you describe.

Link to comment

I think you misunderstood.

Nope, I get it. You're passing in a collection instead of an array of app-specific objects. I think *you* misunderstood what I was saying. :P

"...and instead of passing an array of objects into the Listbox class..." refers to what he was originally planning on doing.

"...pass a Map..." summarizes what you suggested.

Link to comment

I'll try different things, see how they work out and post back.

Are you planning on using an Xcontrol? The thing I’ve done that is most similar (I think) to what you are doing is an Xcontrol that allows the User to configure a list of “Actions” that they want the equipment to perform. The “actions” were an array of objects that were the datatype of the Xcontrol. The Xcontrol, containing a Table rather than a Listbox, allowed the user to configure each action via direct data entry or User menus. I’ll attach it in case it gives you any useful ideas. Note that it was never really polished or tested as the project it was for was discontinued. Probably not commented either. Oh, and working with Xcontrols can be a pain.

— James

post-18176-0-16112500-1347205623.png

ProfileTable.zip

Link to comment

Thanks James. We actually have something like this internally, and I agree it's a really nice way to do configuration. I also agree X Controls can be a huge pain and I have battled with them on multiple occasions. Somehow they always end up working, but are often messier than I'd like. Maybe I will convert it so the information is dumped out into classes, rather than csv files or super clusters.

This is the class that I inherit from so I would have TTestItem cLass and TUserItem class and I would manipulate them with the TList.

With regards to what Shaun said, would there be a TTestItem class that held an array of test items in its private data? Or would it be something different? If this is how it would be configured, excuse my ignorance but I am a bit confused how TList would handle all the adding and removing of items in a the private data of a child class? It seems I would have to provide a protected accessor method to get the array of items for every child class I make, so that no matter the items I use the TList may call the accessor, get the array of private data, and manipulate it. Is this correct, or am I overlooking something?

Edited by for(imstuck)
Link to comment

Thanks James. We actually have something like this internally, and I agree it's a really nice way to do configuration. I also agree X Controls can be a huge pain and I have battled with them on multiple occasions. Somehow they always end up working, but are often messier than I'd like. Maybe I will convert it so the information is dumped out into classes, rather than csv files or super clusters.

With regards to what Shaun said, would there be a TTestItem class that held an array of test items in its private data? Or would it be something different? If this is how it would be configured, excuse my ignorance but I am a bit confused how TList would handle all the adding and removing of items in a the private data of a child class? It seems I would have to provide a protected accessor method to get the array of items for every child class I make, so that no matter the items I use the TList may call the accessor, get the array of private data, and manipulate it. Is this correct, or am I overlooking something?

Well. TTestItem would contain your test info (upper limit, lower limit, measurement etc). If you think about it. What you are asking for is, in fact, a TList of TTestItem. You could, for example, create a TTestItems (note the plural) which would inherit from TList and this could be fed into TTests (which would also inherit from TList). So you would end up with a List, of a List of Testitems. This is how it would work in any other language and it sort of works in LabVIEW :)

The point is. TList doesn't care what is in the list. It just provides the methods to manipulate a "List". The purists would (as I am describing) do all the inheritance then override where necessary in each child class (like with GetItemByName). I'm lazy though (and don't want hundreds of VIs). I just create TLists instances and add objects to them just as if it was an array of variants.That is the point where I stop using LVPOOP and use classic LabVIEW since all I'm interested in is using it instead of an Action Engine.

Edited by ShaunR
Link to comment

Thanks for the clarification. I guess what I'm getting it is for, say, the add method in the base class TList, to make it generic and act similar to an "array of variants" like you suggest, do you just make the input a "LabVIEW object" so it can accept any class you wire to it? If I'm still off, would you mind posting a small example with just one method so I can hopefully understand correctly.

Edit: I'm basically trying to avoid overriding add and delete etc if I don't have to because the base classes implementation will be fine. But, you can't put a TList in its own private data. I keep trying to relate to c++ templates, but with those you can specify the type, and I don't quite see how to do that here. I'm banging my head trying to get this haha. A quick example would probably be the fastest way to clear this up for me, just knowing the way I learn.

Edited by for(imstuck)
Link to comment

I also agree X Controls can be a huge pain and I have battled with them on multiple occasions. Somehow they always end up working, but are often messier than I'd like.

Amen to that.

.... but I am a bit confused how TList would handle all the adding and removing of items in a the private data of a child class? It seems I would have to provide a protected accessor method to get the array of items for every child class I make, so that no matter the items I use the TList may call the accessor, get the array of private data, and manipulate it. Is this correct, or am I overlooking something?

Well this is where I might be able to chime in. I am a firm believer in code generalisation and have often run into this "problem" of having a nice re-usable scnario which gets snagged on the nitty-gritty details fo the actual implementation. One such thing is the "How do I edit the data in my (possibly unknown) objects using a generic interface (Listbox)"?. My answer to this is that the object whould handle this itself. I'm sure I've made references before to ustilising self-displaying objects for exposing their editing capabilities (I think it was in response to a preferences dialog) and I think this is a great way to implement this problem as well.

You can either go via text (using a table) and have the object tell you if the entered data is correct or you can have a sub-panel in your XControl into which the object to be edited places its own UI for editing. This way you can mix and match objects of different types and the editing becomes mush easier. You can expand beyond the originally-concieved functionality and the Listbox XContro becomes truly re-usable. Selecting a new object int he list unloads the ond object and loads the new one into the subpanel. Easy peasy. You can retrieve the new values by either utilising DVRs in the XControl (thus ensuring automatic synchronicity) or via events or via whatever method takes your fancy. You can also just take the return value from the UI when it exits (when the user says they're finished or when another item is selected and the current object is forced to unload). Easy peasy lemon squeezy.

But that's just my opinion man.

Shane

Ps I specifically mean a link to THIS topic.

Edited by shoneill
Link to comment

Shane,

I actually read that topic the first day you posted it, but I will revisit it to see how it may apply. The reason I have the list box is actually so I can do configuration as you suggest. Depending on the type of test each step will be executing, there may be different parameters necessary. So, I was going to have a different self displaying object based on the type of test that was chosen, just like a configuration dialog. But, all of these different test configurations would be test steps. I wanted to put all these test steps in an array -- if the user created a new step, I would just create a new test step object and add it to the array; if the user deleted a step, delete it from the array; moved a step in the listbox, I would move it in the array etc. This is where Shaun's idea comes into play. Then, when the user saves, I would go through all the test steps and get the "next" step in the array and put it in the private data of each current step (the base Test Step class's private data because every test step will need this information).

Each test step would have an associated "do test step" (or some other name) method which would define what would be done for that test step. This way I can just call a test step and when it finishes it will pump out the next step to be enqueued from its private data. It can also have in its private data where to go on different errors for that step etc. This would allow for new test step types etc to be created on the fly and just plugged in to the configuration dialog.

So, this is what I was envisioning, I'm just not quite there yet in terms of handling this add,remove, reorder functionality in a generic way (as I said, so I don't have to use this for test step objects only), mostly because I'm not too familiar with OOP so I have some trouble wrapping my head around a few of the ideas.

I'm sure once these ideas click, it will seem simple.

Edited by for(imstuck)
Link to comment

I actually read that topic the first day you posted it, but I will revisit it to see how it may apply. The reason I have the list box is actually so I can do configuration as you suggest. Depending on the type of test each step will be executing, there may be different parameters necessary. So, I was going to have a different self displaying object based on the type of test that was chosen, just like a configuration dialog. But, all of these different test configurations would be test steps. I wanted to put all these test steps in an array -- if the user created a new step, I would just create a new test step object and add it to the array; if the user deleted a step, delete it from the array; moved a step in the listbox, I would move it in the array etc. This is where Shaun's idea comes into play. Then, when the user saves, I would go through all the test steps and get the "next" step in the array and put it in the private data of each current step (the base Test Step class's private data because every test step will need this information).

This is all quite feasible with what I'm proposing.

Each object in the array can be of a different run-time type (but all interpreted as a common ancestor in the IDE). When you have a new element, just insert into the array. If it's selected, get the object at that index and tell it to display it's editor window in the subpanel. Each object is then responsible for taking care of its own user interaction which aids encapsulation no end. There's no problem with having different kinds of children in a dynamic array at run-time thus mixing and matching test objects as you see fit. The only difference is that instead of calling a single UI, you extract the object at the required index and tell it to show it's UI. Once you've grasped that part (and implemented some working code), the rest is trivial really.

Shane.

Link to comment

This is all quite feasible with what I'm proposing.

Each object in the array can be of a different run-time type (but all interpreted as a common ancestor in the IDE). When you have a new element, just insert into the array. If it's selected, get the object at that index and tell it to display it's editor window in the subpanel. Each object is then responsible for taking care of its own user interaction which aids encapsulation no end. There's no problem with having different kinds of children in a dynamic array at run-time thus mixing and matching test objects as you see fit. The only difference is that instead of calling a single UI, you extract the object at the required index and tell it to show it's UI. Once you've grasped that part (and implemented some working code), the rest is trivial really.

Shane.

I think I'm thinking too much and not coding enough :P . I understand this but still see some potential issues (which may not be issues at all) so I will implement it when I get free time, then post back with specific questions to what I have done. I think that will help get everything cleared up.

Link to comment

Just bear with me as you read this...I think I realized something in the middle of my post.

Shane,

First thanks for the time to draw this up. This is exactly what I was envisioning when reading your explanation, but I still see the issue from my first post. This is forcing my classes to inherit from the same base class, solely for the purpose of managing them both in this one specific user interface. To stick with the classes in your example, lets say I wanted to use "Not so random class" in another project. I am forced to bring along "random class", even if the only purpose for random class and the inheritance I have set up is for the class to work with this one specific UI, and the classes can run on the same wire.

I guess what I'm thinking of is like a template in c++. You'd do List<Not so random class> mylist and you could create this list without dealing with inheritance from any object.

Then you could do (ignore the spaces in variable names)

List<Not so random class> mylist

mylist.push_back(instance of not so random class)

Not so Random class nsrc = mylist.front()

String ListboxItem = Not so random class.GetListboxString()

Now if I wanted to use Other Not So Random Class...

Oh, wait a minute...it just clicked I think. Even with the template method you'd still need the line in red not to be specifically equal to Not So Random Class, but instead equal to Random class (i.e. the base class) for it to work when switching out the data type being used by the template

Therefore, I think I do have to have them both inherit from the same base class to make this generic. I don't know why I was so opposed to this in the first place; I think it just seemed counter intuitive to have two classes which may be totally unrelated, inheriting from the same base class, just so they can run on the same wire in my UI.

Edited by for(imstuck)
Link to comment

Thanks for the clarification. I guess what I'm getting it is for, say, the add method in the base class TList, to make it generic and act similar to an "array of variants" like you suggest, do you just make the input a "LabVIEW object" so it can accept any class you wire to it? If I'm still off, would you mind posting a small example with just one method so I can hopefully understand correctly.

Edit: I'm basically trying to avoid overriding add and delete etc if I don't have to because the base classes implementation will be fine. But, you can't put a TList in its own private data. I keep trying to relate to c++ templates, but with those you can specify the type, and I don't quite see how to do that here. I'm banging my head trying to get this haha. A quick example would probably be the fastest way to clear this up for me, just knowing the way I learn.

Yup. This is where I was talking about type checking which has to be done in the child.

Edited by ShaunR
  • Like 1
Link to comment

Yup. This is where I was talking about type checking which has to be done in the child.

Let there be light! Thanks for your patience and persistence! So, for type checking, the child class's methods would just have the correct class type on the connector pane, then call the parent method inside themselves (assuming they want the parent classes functionality such as with add or delete)? I think I'd prefer to be lazy and just force the programmer to know not to mix types in the list.

Edited by for(imstuck)
Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.