-
Reflection, create a class with dynamically named properties
Hi,
Strange request here, and before everyone starts shouting "That's not what you should do", let my explain myself first.
I'm trying to make a very simple audio tag editor. The idea is that the user can select as many audio files as he likes from a ListView, and a PropertyGrid will then display their tags + their values.
When multiple files are selected, I want the property grid to display the values of the tags that all files have in common, and display empty values for tags that the files do not have in common.
As an example, look at the properties list in your Visual Studio IDE when you select multiple controls (of the same type for this matter). Select a few Button controls, for example, and notice that the properties whose values are the same (often things like Font, ForeColor, etc) are displayed. Also notice that the values for properties that are not the same (Text, Name, etc) are empty.
So, suppose the user selects a bunch of files from the same album. The Album entry in my Property Grid should display the name of that album, (and probably the Artist of the album) while the Title entry should be empty (because the songs obviously will have different titles).
I am reading and writing the tags using the TagLibSharp library which seems to work just fine.
But here's the catch: I don't want to 'hard-code' which tags my PropertyGrid should display.
Right now, I am using Reflection to loop through every tag property of every selected file, and I put them in a List(Of List(Of Object)):
vb.net Code:
Dim allTags = New List(Of List(Of Object))
' Get the type of the Tag property, which contains all tag properties
Dim tagType As Type = tagFiles.First().Tag.GetType()
' Loop through every property in the Tag type
For Each pi As PropertyInfo In tagType.GetProperties()
Dim list As New List(Of Object)
' Loop through every file
For Each file As TagLib.File In tagFiles
' add the value of the property represented by "pi" to the list
list.Add(pi.GetValue(file, Nothing))
Next
allTags.Add(list)
Next
(Side-node, the Tag property here is not of type Object as usual in .NET, but it's a class with a property for each tag)
The code might be a little hard to understand, but basically it loops through every property of the Tag property (things such as Title, Artist, Album, etc) using Reflection. It then loops through every file in the selected files list (tagFiles), and uses Reflection again to get the value of the current Tag property (pi) of the current file. It adds that value to the "list" list, and after every file has been processed, it adds the entire "list" to the "allTags" list.
So, what I end up with is a list that contains a list of tag values for every file.
Finally, I want to display one instance of this list (meaning: one instance of a list containing all tag values) in the property grid.
To be able to display something in the property grid, I need to set the SelectedObject property to an instance of a class. It will then display the properties of that class with their values.
The obvious problem: there is no class with all the tag values. I can also not create one right now, because I am looping through every tag property dynamically: I don't know which properties it will have!
So, finally my question: I need to create a class, dynamically, with dynamically named properties (one property for every tag property), and set their values. Even better would be if I could set attributes for those properties (I think the Description attribute controls the way the property is named in the property grid?), but I can't even do the first one...
How can I generate a class dynamically, probably using Reflection if it's possible at all..?
In pseudo-.NET code it would probably look something like this
Code:
Dim c As New Reflection.DynamicClass("AudioTags")
c.AddProperty("Title", "Talk")
c.AddProperty("Album", "X&Y")
c.AddProperty("Artist", "Coldplay")
and then, during run-time, it would generate this class
Code:
Public Class AudioTags
Public Property Title As String
Public Property Album As String
Public Property Artist As String
End Class
and then create an instance of this, and set its values
Code:
Dim tags As New AudioTags()
tags.Title = "Talk"
tags.Album = "X&Y"
tags.Artist = "Coldplay"
So that I can display and instance of this class in my property grid.
I think I could do it somehow by generating this code, exactly as I wrote it, in string format, and compiling it on the fly, but I don't really want to unless there is no other option... As far as I know, that method is really slow and very error prone.
Is there any other way?
EDIT
Just to clarify, the reason I need to generate the property names dynamically is of course because I'm showing them in the property grid, so the user will see them. I can't have "property1", "property2", etc, because that's meaningless to the user; it has to be "Title", "Album", etc.
That's also the reason I would like to use the Description attribute. Many properties have multiple words ("DiscCount" or "BeatsPerMinute") and I would like to display them as "Disc Count" or "Beats per Minute" (or even "BPM") to the user.
-
Re: Reflection, create a class with dynamically named properties
Use a Dictionary(Of String, Object); this avoids reflection, too, because you can loop through the dictionary's Keys property.
Code:
Dim dict As New Dictionary(Of String,Object)
dict.Add("Title","Talk")
dict("Title") = "Talk"
For Each k As String In dict.Keys
'Do something with value in dict(k)
Next
-
Re: Reflection, create a class with dynamically named properties
See, that won't work. I really need either a class or a structure, so I can assign an instance of it to the SelectedObject property of the property grid. I don't know of any other way to display a list of values in a PropertyGrid control, so if you do, please tell me... As far as I know, a dictionary is useless to me :(
It's easy to add the tags + values, but I cannot use them after that.
Well not in the property grid anyway... Perhaps I should use some other way, dynamically creating textboxes or something...
I just thought a property grid was convenient for this, much easier at least than maintaining a dynamic list of textboxes of which I've no idea how large it will be...
-
Re: Reflection, create a class with dynamically named properties
Does it really seem that convenient now? :)
It's your only option, really.
-
Re: Reflection, create a class with dynamically named properties
I see no obvious way to do this which isn't a total hack. The total hack that comes to mind is to create an object that has a superset of all possible properties (that assumes that you know what that super set consists of, which may not be the case at all), then hide the ones that are not to be shown. That's almost too ugly for words, though.
Another possibility is the SelectedObjects property. That seems to do something close to what you want, but just a bit too far off. It would require having an object that exposed whatever set of properties you want. Technically, you could do that using some really goofy inheritance where you created a bunch of different objects that all derived similarly back to some base that had the minimal set of properties. You would then need a class factory kind of a thing that would create an instance of the correct type for the set of properties you need. This could get almost unbelievably ugly, though, unless the set of combinations is relatively manageable.
The other solution that I see would be to not use that particular control, but roll your own. It just doesn't seem like the control, as it stands, is likely to show what you want.
-
Re: Reflection, create a class with dynamically named properties
Yes, definitely make your own. Like you said, you can swap out text boxes for the labels. It's not actually that hard.
-
Re: Reflection, create a class with dynamically named properties
This would be so easy in JavaScript! :)
Is there a control that could be bound to a Dictionary?
-
Re: Reflection, create a class with dynamically named properties
Using the dictionary idea, could you not create a custom editor to use in the property grid? Someone has an example like so: custom form for property grid
but I have seen others without an external form ie colour, font etc. Maybe there is a way to create an inline custom control?
-
Re: Reflection, create a class with dynamically named properties
-
Re: Reflection, create a class with dynamically named properties
Oooh, so that's what a PropertyGrid is! It should be easy to make one for use with a Dictionary; just add textboxes with the BorderStyle set to FixedSingle and Labels for the other side, again with BorderStyles set to FixedSingle and AutoSize = False.
-
Re: Reflection, create a class with dynamically named properties
Thanks guys. I actually did found something (the Reflection.Emit namespace, allows you to create dynamic classes), but I think you need to know MSIL to use it properly, which I don't...
Anyway, I think you've convinced me that creating my own user control would be easier. I could use like a table layout panel (or a flowlayout panel dunno) and add rows dynamically as I go, putting a label and a textbox in each row.
The hard part would be to write the changed tags back to the file, which would have been much easier using a property grid (as it actually changes the class instance you assigned to the SelectedObject).
Oh well, I'll see how far I can get.
-
Re: Reflection, create a class with dynamically named properties
Did you look at the site I suggested, you can make your own custom viewer for your data.
-
Re: Reflection, create a class with dynamically named properties
Quote:
Originally Posted by
Grimfort
Did you look at the site I suggested, you can make your own custom viewer for your data.
As far as I can tell, both links are about the property grid itself, and how you can create your own property editors for it (such as the Dock property editor, with the 5 or 6 buttons). That's not relevant (yet), because I can't even load the data into the property grid in the first place.
-
Re: Reflection, create a class with dynamically named properties
Using the info in Post2 you can put your dynamic info into a dictionary type object. Sub-class it to allow you to change its description/attributes for use in the property grid and add it as a property of your data class. You can then see this in the propertygrid and from there on comes the customisation part.
-
Re: Reflection, create a class with dynamically named properties
I've no idea what you're talking about, sorry...
I can put the data in a dictionary, but I can't display that dictionary in the property grid...
"Sub-class it...". Sub-class what?
"add it as a property of your data class". What data class? The problem is that I have no class, because it needs to have dynamic properties.
-
Re: Reflection, create a class with dynamically named properties
I specifically said dictionary type as I did not want to suggest the dictionary directly. You have others for example using generics that could be used.
To go for a simple explaination, sub-class the dictionary. You do this because you can then modify the atttributes of the class to help with your display in the property grid.
You create an instance of this class within your 'data' class (er...Audio Tags?) and add a property for it.
When your 'data' class is then set to the property grid, you should see this sub-classed dictionary and be able to start your custom design.
Your 'data' class, has to exist, you must have something that explains what the object is, even if it is just a list of tags. A class called Song for example, could have at least a filename property and now you have this dictionary type property which is the list of tags.
-
Re: Reflection, create a class with dynamically named properties
That wouldn't work, at least not in the way he wants it to. Making a custom control for this is easy and is the best way.
-
Re: Reflection, create a class with dynamically named properties
Hmmm, I'm sure you could make it work. Otherwise heres another project that does something similar:
http://www.codeproject.com/KB/dotnet...pertyGrid.aspx