UserControl that embeds another user control, how to access embedded form fields? - user-controls

I have a long form that the user has to fill out.
So I broke the form into logical units and created another user control for some elements (they will be reused elsewhere).
So say the form has these fields:
UserControl3.ascx
Username
password
email -- usercontrol2.ascx
address -- usercontrol2.ascx
city -- usercontrol2.ascx
state -- usercontrol2.ascx
So now in the codebehidn of usercontrol3.ascx, how will I access the usercontrol2.ascx's fields so I can write to the db?

Something like this works, but it is just not elegant:
Dim txtBox as TextBox = Ctype(parentControl.Controls(Index), System.Web.UI.Controls.TextBox)
stringVariable = txtBox.Text
The correct way to do it is to implement properties for your parentControl that access child's controls properties.
Public Property AddressField() as string
Set(byval value as string)
txtAddressField.Text = value
End Set
Get
Return txtAddressField.Text
End Get
End Property

if user control 3 contains user control 2, I would modify the code for user control 2 to expose public properties for the information that you need to retreive.
edit
There are other ways of doing it, but the property route is the safest route, and avoids strong dependencies between the two controls.

Related

How can I create a proper Collection in VBA?

I am trying to convert a large 3 dimensioned Array into a series of class modules. I have each next class stored as an array in the previous class. It goes like Brand -> Products -> Lots.
I have successfully created this interaction and can access them by name like:
Sub test()
Dim MyBrand As Brand
Set MyBrand = New Brand
MyBrand.Name = "Company1"
MyBrand.AddProduct "Shoes"
MyBrand.Products("Shoes").AddLot "240502"
MsgBox MyBrand.Products("Shoes").Lots(0) 'Correctly Displays "240502"
End Sub
But then I wanted to create an object group that can save multiple Brand objects and access them like Brands("Company1").
If I used an array inside a class module, I'd end up with Brands.Brand("Company1").
If I used a Collection, I'd have to use indexes like Brands(1).
Is there a way to create a proper object group so that I can mimic the syntax of groups like Application.Workbooks and refer to members by Name?
A lot of the magic behind custom collections depends on hidden attributes that you cannot edit from within the VBE; you need to export (and remove from the project when prompted) the class module, edit its magic member attributes in Notepad/Notepad++, save changes, and then re-import the module into the project.
That's obviously tedious and error-prone, but there's a (much) better way.
In order to support this:
Set shoesProduct = MyBrand.Products("Shoes")
You can define Products as a Dictionary and call it a day, but then encapsulation as a concept is... well, taking a beating here (whether the internal collection is a Dictionary, a Collection, or a .NET ArrayList should typically be an implementation detail that the rest of the code doesn't need to care about).
I suspect the Brand class has too many responsibilities and "is" the product collection; best practices would be to have the Brand.Products property defined as follows:
Public Property Get Products() As Products
So you'll want to have a Products class (very much like the Workbook.Worksheets and Workbook.Sheets properties both return a Sheets collection object) that encapsulates a private, module-level VBA.Collection field (possibly keyed, but you can't access or iterate the keys of a collection).
The Products custom collection class needs an Item default property (the name Item is a convention); the implementation just pulls the item from the private encapsulated Collection:
'#DefaultMember
Public Property Get Item(ByVal Index As Variant) As Product
Set Item = ThePrivateCollection.Item(Index)
End Property
If you are using Rubberduck, this #DefaultMember annotation/comment is going to trigger an inspection result about the annotation and the corresponding hidden attribute(s) being "out of sync"; right-click that inspection result and pick "Adjust attribute values" to have Rubberduck generate the hidden code for you and deal with the annoying export/delete-edit-reimport cycle.
Otherwise, you'll want to manually edit the hidden VB_UserMemId member attribute that makes it the class' default member:
Public Property Get Item(ByVal Index As Variant) As Product
Attribute Item.VB_UserMemId = 0
Set Item = ThePrivateCollection.Item(Index)
End Property
And with that, MyBrand.Products("Shoes") becomes equivalent to MyBrand.Products.Item("Shoes").
Perhaps you want to iterate all the products in the collection, too?
For Each Product In MyBrand.Products
Debug.Print Product.Name
Next
In order to do this, you need a special "enumerator" member that forwards the enumerator from the encapsulated collection:
'#Enumerator
Public Property Get NewEnum() As IUnknown
Set NewEnum = ThePrivateCollection.[_NewEnum]
End Property
Again, Rubberduck annotations greatly simplify doing this, but everything Rubberduck does, you can also do manually if you like:
Public Property Get NewEnum() As IUnknown
Attribute NewEnum.VB_UserMemId = -4
Set NewEnum = ThePrivateCollection.[_NewEnum]
End Sub
And now For Each iteration works for your custom object collection!
If a Lot was more than just a String value (i.e. an actual object type), then the Product class could use a Lots custom collection too - but since a Lot is really just a String value (or is it?), then Product can simply encapsulate a Dictionary, and have a Lots property that exposes the Items array:
Public Property Get Lots() As Variant
Lots = ThePrivateLotsDictionary.Items
End Property
Note, that's simpler than using a Collection, because with a collection you'd need to iterate it and copy each item to an array in order to return the items without exposing the collection itself (exposing Lots() As Collection makes the AddLot member completely redundant).
As for the Brands collection itself, heed Tim Williams' advice and use a Dictionary data structure.
You can use a Scripting.Dictionary with Name as the key:
Sub test()
Dim MyBrand As Brand
Dim Brands As Object
Set Brands = CreateObject("scripting.dictionary")
Set MyBrand = New Brand
MyBrand.Name = "Company1"
MyBrand.AddProduct "Shoes"
MyBrand.Products("Shoes").AddLot "240502"
Brands.Add MyBrand.Name, MyBrand
MsgBox Brands("Company1").Products("Shoes").Lots(0)
End Sub

How to obtain data type directly? [duplicate]

Is there a way to determine the Object type, when passing a reference to a function?
I'm using a security permissions function, which determines if the user has permission to view/edit the Form passed to it by reference. I'd like to expand this to include reports as well.
To keep the function generic, I'd like to pass a ref for either a Form or a Report as an Object, eg:
function gfSecurity_Permission(obj as Object)
However, I'd need to determine the type of the object within the function.
Does anyone know of a way to do that?
MTIA
Take a look at
typeOf and typeName
Generic object variables (that is, variables you declare as Object)
can hold objects from any class. When using variables of type Object,
you may need to take different actions based on the class of the
object; for example, some objects might not support a particular
property or method. Visual Basic provides two means of determining
which type of object is stored in an object variable: the TypeName
function and the TypeOf...Is operator.
TypeName and TypeOf…Is
The
TypeName function returns a string and is the best choice when you
need to store or display the class name of an object, as shown in the
following code fragment:
Dim Ctrl As Control = New TextBox
MsgBox(TypeName(Ctrl))
The TypeOf...Is operator is the best choice for testing an object's
type, because it is much faster than an equivalent string comparison
using TypeName. The following code fragment uses TypeOf...Is within an
If...Then...Else statement:
If TypeOf Ctrl Is Button Then
MsgBox("The control is a button.")
End If
Simplest way to determine the access type in access is to do an object lookup within the Access' system tables.
Here would be the lookup:
DLookup("Type","MSysObjects","NAME = '" & strObject & "'")
strObject is the name of the object within Access
The result is one of the number below OR NULL if the object does not exist in Access
1 = Access Table
4 = OBDB-Linked Table / View
5 = Access Query
6 = Attached (Linked) File (such as Excel, another Access Table or query, text file, etc.)
-32768 = Access Form
-32764 = Access Report
-32761 = Access Module
so, the dlookup would provide "-32768" for a Form or "-32764" for a Report
Hope that helps

form containing user control not hiding

I am facing some form hiding problem.
Actually I have a form with a user control on it. the user control has submit button, and when I click it I want the user control as well as the form containing it to be vanished.
I can use this.hide(), but it is only hiding the user control and not the form.
I create a
form1 f1 = new form1();
f1.hide();
and even tried
f1.close(),
but nothing is working.
Please help
EDIT:
this is the actual code:
Input inp = new Input();
inp.Show();
it is where the call the input form. In its loading event, I call the usinput user control.
USinput usinp = new USinput();
then in the submit button in the user control, the use the following code:
Input inpt = new Input();
inpt.Hide();
this.Hide();
Companies co = new Companies();
co.Show();
It shows the new form and the user control is gone, but the input form is still there.
Have you tried with that code only?
Have you made sure that your control isn't called f1 and has no name conflict?
Is your control class name form1?
It is definitly something around that.
EDIT:
Your problem is on the first line here.
Input inpt = new Input();
inpt.Hide();
this.Hide();
Companies co = new Companies();
co.Show();
In your UserControl, you create a new form. So you have 2 forms! The second one is never made visible though. You have to send to the UserControl a reference to the form. That way, you always use the same form.
Here's how to create the UserControl from the form:
USinput usinp = new USinput(this); //Will send a reference of the form to the control
The constructor of your control will look like this:
private Form parentForm;
public USinput(Input parentForm)
{
this.parentForm = parentForm;
}
And your submit button code will contain this:
parentForm.hide();

Get SPListItem in another format

I want to get different Rows from an SPListItem. I'll show you my problem with an example.
This code
Console.WriteLine(SPItemName["Created By"]);
or
Console.WriteLine(SPItemName["Created By"].ToString);
returns "8;UserName" (8 is the User ID).
If I look up the row in SharePoint Designer, i can choose even a format for this data field.
So i could get the html code of this field.
How to set the format (like html code or text) of a datafield in c#?
thanks
Use Either SPFieldLookupValue
If you need just the username, use SPFieldLookupValue to seperate id from value:
var userValue = new SPFieldLookupValue(SPItemName["Created By"] as string)
Then you can:
userValue.LookupValue to return UserName
userValue.LookupId to return Id
Or SPFieldUserValue
Or better yet, you can create SPFieldUserValue object to access any other user properties like email, login name, etc..
SPFieldUserValue objUserFieldValue = new SPFieldUserValue(web, SPItemName["Created By"].ToString());
Afterwards you can use:
objUserFieldValue.User.LoginName;
objUserFieldValue.User.Name;
objUserFieldValue.User.ID;
objUserFieldValue.User.Groups;
objUserFieldValue.User.Roles;
objUserFieldValue.User.Email;
objUserFieldValue.User.Sid;
objUserFieldValue.User.UserToken;
http://www.sharepointkings.com/2009/04/spfielduservalue-and.html
Note: to create SPFieldUserValue you must pass reference to web, that's because SharePoint has to get additional user information from user information list to construct SPFieldUserValue object.

Cascading list boxes, with multi-select

I wound up modifying the source from a publically posted POC: http://datacogs.com/datablogs/archive/2007/08/26/641.aspx, which is a custom field definition for cascading drop downs. The modifications were to allow parent-child list boxes where a user can multiselect for filtering and selecting the values to be written back to a SharePoint list. I got the parent-child cascading behavior working, but the save operation only takes the first value that is selected from the list box. I changed the base type for the custom field control from "SPFieldText" to "SPMultiLineText", along with changing the FLD_TYPES field definition values from:
Text to Note and this did not work. So, I changed the field control base type to "SPFieldMultiChoice" and the FLD_TYPES to "MultiChoice" and still get the same result, which is only the first value that's selected, writing to the SharePoint list.
Does anyone have any idea how to get a custom field with multiple selections to write those multiple selections to a SharePoint list?
Thanks for taking the time to read through my post.
Cheers,
~Peter
I was able to accomplish this by inheriting from SPFieldLookup and overriding its internal handling of AllowMultipleValues:
In your FLDTYPES_*.xml set <ParentType>LookupMulti</ParentType>
In your extension of SPFieldLookup, be sure to override AllowMultipleValues (always true), FieldValueType (probably typeof(SPFieldLookupValueCollection)) and FieldRenderingControl. I also set base.LookupField = "LinkTitleNoMenu", though in retrospect I'm not sure why. :)
In your field editor control's OnSaveChange(), set the field's Mult value to true.
To set Mult, you can either string manipulation on field.SchemaXml:
string s = field.SchemaXml;
if (s.IndexOf(" Mult=", StringComparison.OrdinalIgnoreCase) < 0)
field.SchemaXml = s.Insert(s.IndexOf("/>", StringComparison.Ordinal), " Mult=\"TRUE\" ");
Or use reflection:
var type = field.GetType();
var mi = type.GetMethod("SetFieldBoolValue", BindingFlags.Instance | BindingFlags.NonPublic);
mi.Invoke(field, new object[] { "Mult", true });
It's been a while, so I might be forgetting something, but that should be most of it.

Resources