Best, Better, or Simpler Way to Get the Label for a Content-Type's Field - dotnetnuke

I thought I was doing something easy, but it got so complicated that I feel like I am missing something. We have a Content-Type (CT) that moves through a workflow. In each phase of the workflow, the output changes which fields are visible.
We want to easily access both the record's Value and Label. In other words, lets say we have a CT named EventInstance and one of fields is Url. So in code we normally just do #Content.Url. However, for table headings (and in other output) we want to display "Website Link (Url)" instead of "Url". So in the CT we simply changed the Name* (Label):
So then I went looking for how to get that "Label" field (2) instead of the Name (1). What I came up with was this. But it seems crazy complicated...
public string GetLabelForField(dynamic de, string fieldName)
{
var attributes = AsEntity(de).Type.Attributes as IEnumerable<IContentTypeAttribute>;
return attributes.First(t => t.Name == fieldName).Metadata.GetBestValue<string>("Name", "#All");
}
And I call it like this
var ev = listEvent.First();
<pre>
help.GetLabelForField(ev): #help.GetLabelForField(ev, "Url")
</pre>
And that outputs what I want:
help.GetLabelForField: Website Link (Url)
I realize I should be satisfied because it works, but it seems like a lot of work and a steep learning curve. Is there a better way to get access to this (seemingly common) property?

These APIs are not documented much, because accessing input-field configuration used in the Edit-UI isn't that common.
But: I believe you could also do this (shorter, but probably not perfect):
var attribute = AsEntity(de).Type[fieldName];
var name = attribute.Metadata.GetBestValue<string>("Name");

Related

Winforms ObjectListView: inner OLVColumn instances Name property is empty string so I cannot show/hide columns by name

This question is an offshoot of: Localizing ObjectListView OLVColumn, impossible due to Empty Name property
For simplicity's sake, let's say my ObjectListView contains car information. User A wants to display only Make and Model columns. User B only wants to display Model and Year columns. These preferences would be saved to/loaded from an .ini file on the users' local machines.
I cannot loop through the columns of the ObjectListView and do if (col.Name == colNameFromIni) { col.Visible == true; } because the .Name property of every column is an empty string ("") and does not get serialized to the designer codebehind file. This never happens with any other Winforms control (Label, Button, etc.) They always get their .Name written to the designer codebehind.
In some sense, this is a flaw in Winforms itself, because OLVColumn inherits from System.Windows.Forms.ColumnHeader, and a traditional ListView has exactly the same problem. .Name is always an empty string for all columns.
I would like to patch our local build of ObjectListView.dll to force populate the .Name property, but I can't figure out how Winforms automagically knows the name of every control on the form. It somehow(?) knows the names of the OLVColumn objects since it can display them in the Edit Columns... dialog on the ObjectListView's context menu. I'm also a little fuzzy on where the best spot is to plug this in.
(Yes, per linked question at top I know that as a last resort, I can hardcode colXX.Name = "colXX"; for all columns in my source code, but future column additions are likely to get overlooked and a programmatic solution is much preferred.)
(See also: https://sourceforge.net/p/objectlistview/bugs/160/ : the ObjectListView author declared this a wont-fix so it is up to me (or us), I guess.)
As you point out, this is a bug which is not with the ObjectListView, but the underlying component. And a bug which is around since at least 2008! Therefore, I doubt it will ever be fixed by MS.
Actually, it is a problem with the Autogenerated code in the designer.
If you look at other components such as a button, then the autogenerated code adds a name such as this;
//
// button2
//
this.button2.Location = new System.Drawing.Point(458, 199);
this.button2.Name = "button2";
...
But for ColumnHeader (Listview) and OLVColumn (ObjectListView), then this is not done, so then you end up with this.
//
// olvColumn1
//
this.olvColumn1.AspectName = "Name";
this.olvColumn1.Text = "Name";
If you manually add the line
this.olvColumn1.Text = "olvColumn1";
Then the "problem" is solved.
Of course, you can't do this, because the designer will override the autogenerated code when you make any changes, and then you will lose these manually added lines. It is also not sustainable.
So I'm afraid you need to code around this with some kind of ugly solution. Some options are:
Use the Tag to store the name and compare against this.
Use the text instead of the name (not possible if you have multi
language support!)
Code the names column manually in the Constructor
Set the Text to be something like "ColName;ColText" and then in your
code separate these out.
I have done option 3 in the past, but only I was maintaining the code, so this was easy.
What you could do to ensure you don't have discrepancies is to add a check in your constructor to compare the actual number of columns with the number you expect (hard coded for), and throw an exception if they don't match. Also, not the best, but another way to highlight and reduce errors.
The workaround for this is to get the OLVColumns via reflection and set their column's Name property at runtime. Every OLVColumn is a form-level field, so just pick them out of the list returned by GetFields().
Dim allFieldInfos As FieldInfo() = GetType(FrmMain).GetFields(BindingFlags.NonPublic or BindingFlags.Instance)
For Each fi As FieldInfo In allFieldInfos
If fi.FieldType Is GetType(OLVColumn) Then
Dim instance As OLVColumn = fi.GetValue(Me)
For Each col As OLVColumn In fdlvMain.AllColumns
If ReferenceEquals(col, instance) Then
col.Name = fi.Name
End If
Next
End If
Next

MFC MDI CMFCPropertyGridProperty adding Array for dropdown list merging MP4 tag data

I need some guidance on how to add a drop down list from an array of data after the read info from the MP4 Tag data is parsed. The mechanism I'm using is 100% operational, this is a creature feature addition. The MP4 tag I'm working with is Genre using the ID3V1 standard. There are 191 choices. The way my app was inherited, there are 2 columns, property/value and multiple rows. All of that works. The Genre tag was setup willy nilly so you could basically type whatever and it would store it. I want to remove that and have the 191 elements in the array to choose from using the drop down list. Part of the loading process is that it will pull in whatever was in the MP4 file. So, I want the user to be able to leave as is (most likely tagged by something that supports ID3V2), or select from the populated 191 elements in the dropdown list.
The object looks like this information.h:
protected:
CMFCPropertyGridCtrl m_wndProperties;
The information.cpp looks like this:
void CInformationView::OnInitialUpdate()
{
// create property grid
VERIFY(m_wndProperties.Create(WS_CHILD|WS_VISIBLE|WS_TABSTOP| WS_BORDER, CRect(0,0,0,0), this, 0));
// get document
CMovieDoc *lpkDoc = GetDocument();
ASSERT_VALID_PTR(lpkDoc);
// add properties //Information ORDER Loading <<<<< List shortened Stack overflow question
m_wndProperties.AddProperty(lpkDoc->m_pkArtist);
m_wndProperties.AddProperty(lpkDoc->m_pkTempo);
m_wndProperties.AddProperty(lpkDoc->m_pkGenre);
CView::OnInitialUpdate();
}
The way it pulls the data in from mp4.cpp:
// Genre
m_pkGenre = new CMFCPropertyGridProperty(_T("Genre"),
COleVariant(AfxStringFromUtf8(lptTags->genre), VT_BSTR));
The pointers in mp4.h:
CMFCPropertyGridProperty *m_pkArtist;
CMFCPropertyGridProperty *m_pkTempo;
CMFCPropertyGridProperty *m_pkGenre;
Now I know that pull downs in the 2nd column (Values) can be done because other tags have simple TRUE/FALSE that can be selected, so that tells me it should be possible to create the drop down list I'm looking to do. An example of the TRUE/FALSE looks like this:
// Compilation
m_pkCompilation = new CMFCPropertyGridProperty(_T("Compilation"),
COleVariant((!VALID_PTR(lptTags->compilation)) ? (short)0 : (short)*lptTags->compilation, VT_BOOL));
I've done arrays in C for things like microcontrollers, but not entirely sure if it is the same in C++. I'm thinking it should look like this:
// Initialize Genre Array
const char *genre[4] = { "Rock", "Rap", "Soul", "House" };
The questions are:
How do I create an the array (or does my example above look correct?) to house fixed strings like "Rock", "Rap", "Soul", etc. etc?
How to modify the VALUE row to have the pull down that contains the parsed Genre tag present and then when opened, show the 191 Genre tags where one can be selected to choose from (and ultimately save which is already working).
Actual code, not references to the learn.microsoft.com, the few things I've tried crashes when I try to alter the AddProperties I assume because of the lpkDoc pointers being used.
You should not use a plain old C-style array if you do not have a strong reason to. Use a std::vector instead. You don't even need to indicate the [size].
The same goes for char *. Use a CString or astd::string instead.
const std::vector<CString> = { L"Rock", L"Rap", L"Soul", L"House" };
Don't make your life harder than it needs to be.
2.
for (size_t i= 0; i < genre.size(); i++)
{
auto gnr= genre[i];
lpkDoc->m_pkGenre->AddOption(gnr);
}
or even better
for (auto it : genre)
{
lpkDoc->m_pkGenre->AddOption(it);
}
Important note: You should not have code about properties in your doc object. You are mixing business logic with user interaction logic. Your code in the future will be a nightmare to maintain.
I do not see your lpkDoc->m_pk variable init'ed anywhere, and I bet those pointers are pointing to no man's land.

peewee get_or_create and then save: error binding

Is there an easy way to update a field on a get of a get_or_create?
I have a class ItemCategory and I want to either create a new entry or get the already created entry and update a field (update_date).
What I do is:
item,created= ItemCategory.get_or_create(cat=cat_id,item=item_id)
if created == True:
print "created"
else:
item.update_date = datetime.now
item.somethingelse = 'aaa'
item.save()
This works for a while in my loop. But after 50-100 get/create it crashes:
peewee.InterfaceError: Error binding parameter 4 - probably unsupported type.
Maybe I should use upsert(), I tried but wasn't able to get anything working. Also it's not probably the best solution, since it makes a replace of the whole row instead of just a field.
I like peewee, it's very easy and fast to use, but I can't find many full examples and that's a pity
Newbie mistake
item.update_date = datetime.now()
I am not 100% sure this is the only answer though. I modified my code so many times that it might be also something else.
Regarding my question about create_or_update , I've done this:
try:
Item.create(...)
except IntegrityError:
Item.update(...)
peewee is really great, I wonder why no one ever asked for a create_or_update.

CakePHP: Use Inflector Class over whole array

I need to use Inflector::slug() over all results fetched from my database, which are, of course, retrieved in an array. Is it possible somehow, or I'll need to loop each result and slugify it?
Thanks!
PHP's array_map() function might do what you need (although it assumes a simple indexed array).
array_map( 'Inflector::slug', $your_result )
If you're looking at something more complex, CakePHP's Set utility class may be helpful in a multi-step implementation.
I haven't tried this in a CakePHP context (i.e. mapping through a CakePHP class method), but I can't think of any reason it wouldn't work off the top of my head. Maybe it'll at least get you started.
Depending on the array you can use array_walk or array_walk_recursive.
Something like this should work.
This is for 5.3+;
array_walk_recursive($posts, function(&$value) {
$value = Inflector::slug($value);
});
If you wanted to limit it to a certain field you could also do something like this:
array_walk_recursive($posts, function(&$value, $key) {
if ($key == 'title') {
$value = Inflector::slug($value);
}
});
I haven't used Cake in a while but like Rob Wilkerson said, you might find that the Set class could make lighter work of this.

C#, Linq data from EF troubles

I don't have much experience with programming. I am working (to learn) on a project. I am using C# 4.0 and WPF 4 with EF (SQLite). I am having problemw with LINQ.
Here is the code in question (I hope this is enough, let me know if more is needed)
private void cboSelectCompany_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
using (ShippingEntities context = new ShippingEntities())
{
var companies = from company in context.Deliveries
where company.State == cboSelectCompany.SelectedItem.ToString()
select company;
txtDeliveryName.Text = companies.Name;
txtDeliveryState.Text = companies.State;
}
The last two lines don't work. Am I misunderstanding what LINQ is returning? I just get this error
Error 5 'System.Linq.IQueryable<SqliteDemo.Delivery>' does not contain a definition for 'State' and no extension method 'State' accepting a first argument of type 'System.Linq.IQueryable<SqliteDemo.Delivery>' could be found (are you missing a using directive or an assembly reference?) c:\users\dan\documents\visual studio 2010\Projects\SqliteDemo\SqliteDemo\DeliveryCompanies.xaml.cs 49 51 SqliteDemo
If anyone could give me some pointers or to a good reference I would appreciate it
You're close!
Linq is returning a Queryable set of Deliveries, which hasn't executed yet. My guess based on reading your code is that you're expecting at most one result, and then you want to put those values into a UI of some sort. In that case what you'll want to do is:
Take your IQueryable and execute it
Make sure you grab the first (or, alternatively enforce that there is only one) row
Set the appropriate values in the UI.
So, leave the line with the query there, and then change the rest to something like this:
var company = companies.FirstOrDefault();
txtDeliveryName.Text = company.Name;
txtDeliveryState.Text = company.State;
Insert null-checking as appropriate, and go check out the differences between First, FirstOrDefault, Single, and SingleOrDefault to see which one seems most correct for your particular situation.
Well, looks like companies.State doesn't compile, because companies is a list of SqliteDemo.Delivery (and not just a SqliteDemo.Delivery).
What do you want to achieve? Imagine that your LINQ query returns several results, is it possible?
Just use companies.FirstOrDefault() or companies.Single() to access first item becouse by default LINQ returns collection of items not just single item.

Resources