Validate DataGridColumn cells individually - actionscript-3

How can I validate the cells in a DataGridColumn individually? (ActionScript 3.5) The validation is configured per-cell, based on fields in the given row. For example
FIELD VALUE TYPE
age 13 Integer
height 13x3 Integer
registered true Boolean
temperature 98.G6 Float
In this case, of course 13x3 and 98.G6 would be invalid.
It's easy to write a Validator ; and to access the data provider objects.
But how do I get individual access to the GUI cell objects so I can set the errorString on an individual cell, either directly or through a Validator?
The itemRenderer/ TextInput control is re-used across the cells for performance reasons, so accessing the GUI-level objects is tricky.
Edit
Answers:
One way to validate and display the invalidation markings, but not per-cell, is to validate all data-provider objects and then set the errorString on the entire grid.
One way to validate per-cell is on the itemEditEnd event handler. (See these pages A B C D). One disadvantage is that it only allows access to the cells from the "inside", not in an action that validates the grid on command.
A custom itemRenderer is another possibility, as in the answer below, but like 3 above, it only allows access to the cells from the "inside", not in an action that validates the grid on command.
See Richard Haven's answer below.
And here's how to access the GUI objects: The list of relevant GUI objects is a protected field; so you can access it by subclassing, then iterate over the GUI-components which represent the cells and set the errorString on each one.

This website at BigResource asks how to access an individual cell. The third post answers there question and provides a link to a better resource than this. Figured you would want both. Hopefully this helps.

If you are looking for arbitrary validation (e.g. on a button or page navigation) rather than immediate navigation (e.g. on cell exit or end-of-edit), then the data is in the underlying dataProvider. I would do validations there rather than dig around inside the grid.
You can add a flag to the data item so the item renderer displays it as an error (or use an external list to flag it).
Cheers

Are you sure you actually want to access the individual cells' DisplayObjects? The component manages instances so that it only creates as many as it needs to display (so that huge datasets don't require a huge number of DisplayObjects on screen).
I think a better alternative would be to provide your DataGridColumn with a custom itemRenderer. You can write this class to accept a validator and update its appearance, and there are a bunch of great tutorials around about that.

Related

In Access VBA, is there a way to count and refer to objects currently open in the main window?

In Access VBA, is there a centralized way to get a count, and names or references to any objects currently open in the main Access window..?
Access.Application.Screen has the .ActiveDatasheet, .ActiveForm, and .ActiveReport properties, but only one of these will become defined if a valid object becomes active on screen. They do not work with other object types, or non-active objects, meaning those which are further back in the stack of open items.
Access.Application has the .Forms and .Reports properties which do give a count and references of open items, but as above, not for any other object types.
There are the .IsLoaded and .CurrentView properties of the "All-collections", meaning AllForms, AllMacros, etc. These do work, but it seems like looking for a needle in a haystack to loop through all eleven of them to find one or a few items. And if the database has many objects, it might take a while.
Then there's the seemingly antiquated but still effective SysCmd action "acSysCmdGetObjectState". It seems to be the option I seek, especially since it has the nifty ability to work with new/unsaved objects, and also to indicate if they're "dirty", meaning whether they even need to be saved or not. But alas, acSysCmdGetObjectState wants to know the acObjectType and object name, so...back to square one.
So...is there a centralized collection with items currently open on screen, regardless of acObjectType, tab/window position, or new/dirty state..?

SSIS Custom Component: Having any IsSorted Property and output metadata

I have a Custom Synchronous Component that works fine and I use it.
Recently, I sent some Sorted data from a sort component to it (or an IsSorted=true Source Component)
but then i couldn't use the output as the input of a merge join due to not having a IsSorted=true property.
So I have to sort data again and it reduces the package performance too much.
Also I can't have any metadata same as Input, for my output(s) during design time.
I guess when my component is synchronous so it might be sorted as its input
if not, how to make the component output data sorted!
I really wanna know if there is any clever solution to solve this detailed issues about Custom Pipeline Components.
As your component is synchronous, somewhere in your code you are synchronizing the output, IDTSOutput1xx, with the input, IDTSInput1xx, with code like this:
output.SynchronousInputID = input.ID;
In a synchronous component the PipelineBuffer (the one exposed to the output, exposed in ProcessInput) is based upon the input buffer (usually with modified or added columns) respecting the original row ordering.
So, if you check that the input is ordered, you can assure that the output is also ordered. And there is a property that you can use to read this information from the input and set it in the output:
output.IsOrdered = input.IsOrdered
Take into account that you could set this property to true even if the output was not ordered, but in this case, you're relying on the information provided from the input, which should be correct.
You should only change this property explicitly to true in an asynchronous component in which you really sort the rows before returning them. But, as I told, you could lie, and set this property to true without returning ordered rows. In other words, it's informative metadata.
If this doesn't work for you, you'll also have to set the SortKeyPosition of the required columns in output.OutputColumnCollection. This information is also used by Merge Join to ensure that the input apart from being ordered, is ordered by the required columns.
If you want to see how you can do this using the SSIS task editor, instead of doing it "automagically" in your custom component, please, read IsSorted properties in SSIS or SSIS #98 – IsSorted is true, but sorted on what?
The Merge Join Transform in SSIS has a few requirements. To join two data sources,The data sources must be sorted and there must be a key that you can join them with. In some cases, I perform the join in the OLEDB SOURCE from Query.

Three rows of almost the same code behave differently

I have three dropdown boxes on a Main_Form. I will add the chosen content into three fields on the form, Form_Applications.
These three lines are added :
Form_Applications.Classification = Form_Main_Form.Combo43.Value
Form_Applications.Countryname_Cluster = Form_Main_Form.Combo56.Value
Form_Applications.Application = Form_Main_Form.Combo64.Value
The first two work perfectly but the last one gives error code 438!
I can enter in the immediate window :
Form_Applications.Classification = "what ever"
Form_Applications.Countryname_Cluster = "what ever"
but not for the third line. Then, after enter, the Object doesn't support this property or method error appears.
I didn't expect this error as I do exactly the same as in the first two lines.
Can you please help or do you need more info ?
In VBA Application is a special word and should not be used to address fields.
FormName.Application will return an object that points to the application instance that is running that form as opposed to an object within that form.
From the Application object you can do all sorts of other things such as executing external programs and other application level stuff like saving files/
Rename your Application field to something else, perhaps ApplicationCombo and change your line of code to match the new name. After doing this the code should execute as you expect.
Form_Applications.Application is referring to the application itself. It is not a field, so therefore it is not assignable (at least with a string).
You really haven't provided enough code to draw any real conclusions though. But looking at what you have posted, you definitely need to rethink your approach.
It's to say definitely but you are not doing the same. It looks like you are reading a ComboBox value the same (I will assume Combo64 is the same as 43 and 56) but my guess is that what you are assigning that value to is the problem:
Form_Applications.Application =
Application is not assignable. Is there another field you meant to use there?

Is having a single massive class for all data storage OK?

I have created a class that I've been using as the storage for all listings in my applications. The class allows me to "sign" an object to a listing (which can be created on the fly via the sign() method like so):
manager.sign(myObject, "someList");
This stores the index of the element (using it's unique id) in the newly created or previously created listing "someList" as well as the object in a 2D array. So for example, I might end up with this:
trace(_indexes["someList"][objectId]); // 0 - the object is the first in this list
trace(_instances["someList"]); // [object MyObject]
The class has another two methods:
find(signature:String):Array
This method returns an array via slice() containing all of the elements signed with the given signature.
findFirst(signature:String):Object
This method just returns the first object in a given listing
So to retrieve myObject I can either go:
trace(find("someList")[0]); or trace(findFirst("someList"));
Finally, there is an unsign() function which will remove an object from a given listing. This function basically:
Stores the result of pop() in the specified listing against a variable.
Uses the stored index to quickly replace the specified object with the pop()'d item.
Deletes the stored index for the specified object and updates the index for the pop()'d item.
Through all this, using unsign() will remove an object extremely quickly from a listing of any size.
Now this is all well and good, but I've had some thoughts which are making me consider how good this really is? I mean being able to easily list, remove and access lists of anything I want throughout the application like this is awesome - but is there a catch?
A couple of starting thoughts I have had are:
So far I haven't implemented support for listings that are private and only accessible via a given class.
Memory - this doesn't seem very memory efficient. Then again, neither is creating arrays for everything I want to store individually either. Just seems.. Larger.. Somehow.
Any insights?
I've uploaded the class here in case the above doesn't make much sense: https://projectavian.com/AviManager.as
Your solution seems pretty solid. If you're looking to modify it to be a bit more extensible and handle rights management, you might consider moving all those individually indexed properties to a value object for your AV elements. You could perform operations like "sign" and "unsign" internally in the VOs, or check for access rights. Your management class could monitor the collection of these VOs, pass them around, perform the method calls, and the objects would hold the state in a bit more readable format.
Really, though, this is entering into a coding style discussion. Your method works and it's not particularly inefficient. Just make sure the code is readable, encapsulated, and extensible and you're good.

Bang Notation and Dot Notation in VBA and MS-Access

While perusing an application that I'm documenting, I've run across some examples of bang notation in accessing object properties/methods, etc. and in other places they use dot notation for what seems like the same purpose.
Is there a difference or preference to using one or the other? Some simple googling only reveals limited information on the subject with some people actually using it in opposite cases. Perhaps there is a coding standards section from MS somewhere that indicates the method of madness?
Despite the (formerly) accepted answer to this question, the bang is not in fact a member or collection access operator. It does one simple and specific thing: The bang operator provides late-bound access to the default member of an object, by passing the literal name following the bang operator as a string argument to that default member.
That's it. The object doesn't have to be a collection. It doesn't have to have a method or property called Item. All it needs is a Property Get or Function which can accept a string as the first argument.
For much more detail and proof, see my blog post discussing this: The Bang! (Exclamation Operator) in VBA
The bang operator (!) is shorthand for accessing members of a Collection or other enumerable object, such as the Fields property of an ADODB.Recordset.
For example, you can create a Collection and add a few keyed items to it:
Dim coll As Collection
Set coll = New Collection
coll.Add "First Item", "Item1"
coll.Add "Second Item", "Item2"
coll.Add "Third Item", "Item3"
You can access an item in this collection by its key in three ways:
coll.Item("Item2")
This is the most explicit form.
coll("Item2")
This works because Item is the default method of the Collection class, so you can omit it.
coll!Item2
This is short-hand for both of the above forms. At run-time, VB6 takes the text after the bang and passes it as a parameter to the Item method.
People seem to make this more complicated than it should be, which is why it's hard to find a straightforward explanation. Usually the complications or "reasons not to use the bang operator" stem from a misunderstanding of how simple it actually is. When someone has a problem with the bang operator, they tend to blame it instead of the real cause of the problem they are having, which is often more subtle.
For example, some people recommend not using the bang operator to access controls on a form. Thus, Me.txtPhone is preferred over Me!txtPhone. The "reason" this is seen as bad is that Me.txtPhone will be checked at compile-time for correctness, but Me!txtPhone won't.
In the first case, if you mistype the code as Me.txtFone and there is no control with that name, your code won't compile. In the second case, if you wrote Me!txtFone, you won't get a compile error. Instead, your code will blow up with a run-time error if it reaches the line of code that used Me!txtFone.
The problem with the argument against the bang operator is that this problem has nothing to do with the bang operator itself. It's behaving exactly the way it's supposed to.
When you add a control to a form, VB automatically adds a property to your form with the same name as the control you added. This property is part of the form's class, so the compiler can check for typos at compile-time if you access controls using the dot (".") operator (and you can access them using the dot operator precisely because VB created a named control property for you).
Since Me!ControlName is actually short-hand for Me.Controls("ControlName")1, it should not be suprising that you don't get any compile-time checks against mistyping the control name.
Put another way, if the bang operator is "bad" and the dot operator is "good", then you might think
Me.Controls("ControlName")
is better than
Me!ControlName
because the first version uses a dot, but in this case, the dot isn't any better at all, since you are still accessing the control name via a parameter. It's only "better" when there is an alternative way to write the code such that you will get compile-time checking. This happens to be the case with controls due to VB creating properties for each control for you, and this is why Me.ControlName is sometimes recommended over Me!ControlName.
I had originally stated that the Controls property was the default property of the Form class, but David pointed out in the comments that Controls isn't the default property of Form. The actual default property returns a collection that includes the contents of Me.Controls, which is why the bang short-hand still works.
Couple gotchas to serve as addenda to the two exceptional answers already posted:
Accessing recordset fields in forms vs. reports
The default item of Form objects in Access is a union of the form's Controls collection and the form recordset's Fields collection. If the name of a control conflicts with the name of a field, I'm not sure which object is actually returned. Since the default property of both a field and a control is their .Value, it's often a "distinction without a difference." In other words, one normally doesn't care which it is because the values of the field and control are often the same.
Beware of naming conflicts!
This situation is exacerbated by Access's Form and Report designer defaulting to naming bound controls the same as the recordset field to which they are bound. I've personally adopted the convention of renaming controls with their control type prefix (e.g., tbLastName for the text box bound to the LastName field).
Report recordset fields aren't there!
I said earlier the Form object's default item is a collection of Controls and Fields. However, the Report object's default item is only its collection of Controls. So if one wants to refer to a recordset field using the bang operator, one needs to include that field as the source for a (hidden, if desired) bound control.
Beware conflicts with explicit form/report properties
When one adds controls to a form or report, Access automatically creates properties that refer to these controls. For example, a control named tbLastName would be available from a form's code module by referring to Me.tbLastName. However, Access will not create such a property if it conflicts with an existing form or report property. For example, assume one adds a control named Pages. Referring to Me.Pages in the form's code module will return the form's Pages property, not the control named "Pages".
In this example, one could access the "Pages" control explicitly using Me.Controls("Pages") or implicitly using the bang operator, Me!Pages. Be aware, though, that using the bang operator means that Access might instead return a field named "Pages" if one exists in the form's recordset.
What about .Value?
Though not explicitly mentioned in the question, this topic came up in the above comments. The default property for Field objects and most "data-bindable"¹ Control objects is .Value. Since this is the default property, VBA will implicitly return the .Value property's value when it does not make sense to return the object itself. Thus, it's common practice to do this...
Dim EmployeeLastName As String
EmployeeLastName = Me.tbLastName
...instead of this...
EmployeeLastName = Me.tbLastName.Value
The above two statements produce identical results because EmployeeLastName is a string.
Beware the subtle .Value bug when keying dictionaries
There are some cases where this convention can cause subtle bugs. The most notable--and, if memory serves, only--one I've actually run into in practice is when using the value of a Field/Control as a Dictionary key.
Set EmployeePhoneNums = CreateObject("Scripting.Dictionary")
Me.tbLastName.Value = "Jones"
EmployeePhoneNums.Add Key:=Me.tbLastName, Item:="555-1234"
Me.tbLastName.Value = "Smith"
EmployeePhoneNums.Add Key:=Me.tbLastName, Item:="555-6789"
One would likely expect that the above code creates two entries in the EmployeePhoneNums dictionary. Instead, it throws an error on the last line because we are trying to add a duplicate key. That is, the tbLastName Control object itself is the key, not the value of the control. In this context, the control's value does not even matter.
In fact, I expect that the object's memory address (ObjPtr(Me.tbLastName)) is likely what's being used behind the scenes to index the dictionary. I did a quick test that seems to bear this out.
'Standard module:
Public testDict As New Scripting.Dictionary
Sub QuickTest()
Dim key As Variant
For Each key In testDict.Keys
Debug.Print ObjPtr(key), testDict.Item(key)
Next key
End Sub
'Form module:
Private Sub Form_Current()
testDict(Me.tbLastName) = Me.tbLastName.Value
Debug.Print ObjPtr(Me.tbLastName); "..."; Me.tbLastName
End Sub
When running the above code, exactly one dictionary item is added each time the form is closed and re-opened. Moving from record to record (and thus causing multiple calls to the Form_Current routine) does not add new dictionary items, because it is the Control object itself indexing the dictionary, and not the Control's value.
My personal recommendations/coding conventions
Over the years, I've adopted the following practices, YMMV:
Prefix Form/Report control names with control type indicators (e.g., tbTextBox, lblLabel, etc.)
Refer to Form/Report controls in code using Me. notation (e.g., Me.tbLastName)
Avoid creating table/query fields with problematic names in the first place
Use Me! notation when there are conflicts, such as with legacy applications (e.g., Me!Pages)
Include hidden report controls to gain access to report Recordset field values
Explicitly include .Value only when the situation warrants the added verbosity (e.g., Dictionary keys)
¹ What's a "data-bindable" control?
Basically, a control with a ControlSource property, such as a TextBox or ComboBox. A non-bindable control would be something like a Label or CommandButton. The default property of both a TextBox and ComboBox is .Value; Labels and CommandButtons have no default property.