I want to stop people sending us messages including the word "telegram".
I have a very old script in VB that uses sendmail. It has always worked fine. Now I am trying to add this:
Sub CheckTelegram()
'check for Telegram
If messagetext.Contains("telegram") Then
messagetext.BorderColor = Drawing.Color.Red
Else
But I receive an error:
Compiler Error Message: BC30456: 'Contains' is not a member of 'System.Web.UI.WebControls.TextBox'.
Can I not use .Contains() this way? I was expecting the border to go red if "telegram" was within the text box.
the TextBox class itself is not a text. You must access the Text property which is of type String:
If messagetext.Text.Contains("telegram") Then
' ^^^^
Because Contains is a method of the String class, not a method of the TextBox class.
Note that older VB versions (VB 6.0, VBA) had the concept of default properties. You could effectively call s = Me!TextBox1 and if s was typed as String, VB was smart enough to see that a TextBox could not be assigned to s and the Text or Value property was automatically retrieved.
VB.NET has a stronger typing and also wants you to do more things explicitly (especially if you have Option Strict On, what I urge you to do). This is better for the understanding of the code and leads to less programing errors. E.g., if you have a declaration Dim obj As Object then what should obj = Me!TextBox1 do? Assign the TextBox or TextBox.Text? Such an automatism can be very confusing.
Related
I'm trying to move the OnClick code from one of the buttons on a form to a function, but I'm having some issues. The button in question, called "Reset", changes the properties of most of the objects on the form. Stuff like:
Me.btnName.Caption = "Caption"
Me.btnName.Visible = True
Me.btnName.Top = 123
Me.btnName.Height = 456
'Etc
When moving this over to a function I can't seem to get this to work. I've tried a few different ways of writing it that I found while searching around, but none of them seem to work. I'm messing with some objects in the main form and some in the subform, so I'll show a few examples of both. Edited to include error messages
Forms("FormName").btnName.Caption = "Caption"
'Application-defined or Object-defined error
Forms("FormName").Controls("btnName").Caption = "Caption"
'Ms Access cannot find the referanced form (I've double and triple checked that it is correct)
Forms("FormName").SubFormName.Form.btnName.Caption = "Caption"
'Application-defined or Object-defined error
Forms!FormName!btnName.Caption = "Caption"
'Cannot find the referenced form
Forms!FormName!SubFormName!btnName.Caption = "Caption"
'Cannot find the field reffered to in your expression
Forms!FormName.Controls!btnName.Caption = "Caption"
'Cannot find the referanced form
Forms!FormName!SubFormName!Form.btnName.Caption = "Caption"
'MS Access can't find the field 'SubFormName'
No matter what I try of these I can't seem to get it to work. Maybe I'm just doing something simple wrong, or maybe you can't change properties like this from a function. Regardless, if anyone knows, I would appreciate the help.
Note that the function I'm trying to use is in a separate module, not in the code behind the form.
So you have a OnClick handler on some form's code-behind, responsible for assigning a bunch of properties for objects that live on that form.
What you have done is called encapsulation: outside code doesn't need to care about the Top and Height properties (and others) of the form's btnName button - truth is, outside code shouldn't even need to care that there's a button on the form.
So you're taking this nicely encapsulated object, and moving code around for no apparent reason.
If you need that functionality to be invoked from the outside, then yes, move it out of the OnClick handler.. but not outside the form's code-behind.
Move it to some Public Sub OnReset() procedure, and if outside code needs to invoke that logic, then have it call theForm.OnReset.
Private Sub ResetButton_Click()
OnReset
End Sub
Public Sub OnReset()
Me.btnName.Caption = "Caption"
Me.btnName.Visible = True
Me.btnName.Top = 123
Me.btnName.Height = 456
'Etc
End sub
That way you leave the implementation details of the form within the form itself, while giving outside code an abstraction to say "I don't care what your buttons are named; I don't care what size they are or what their captions are - but when I say 'Reset', you shall re-initialize whatever values you've got for them" - outside code doesn't need to know what Reset does specifically.
Put it this way: the day you rename that btnName button, would you rather need to simply adjust the form's code-behind, or hunt down every possible place in the entire project that could possibly be changing that button's Caption or whatever other property value?
FYI Foo!Bar.Something is late-bound code, shorthand for Foo.Item("Bar").Something: neither Bar nor Something are validated at compile-time. Contrast with Me.btnName.Whatever, where a typo is immediately picked up by the compiler: early-bound code that the compiler is able to validate should always be preferred.
You need to set the property to something:
Forms!FormName!btnName.Caption = "New Caption"
or, if on a subform:
Forms!FormName!<NameOfSubformCONTROL>.Form!btnName.Caption = "New Caption"
I'm trying to track down the cause of an annoying interface bug in an app that was recently upgraded from VS2003 to VS2008 (the bug did not exist pre-migration).
What happens is this :
1) User clicks in textbox containing a date.
2) User clears date
3) User tries to move to another field, but can't. No error messages appear - it's as if the validation failed.
Further info :
1) The textbox's Text property is bound to a dataview which uses a datatable as its source. The bound field is a nullable datetime field with no constraints or default.
2) The Validating event fires and the CancelEventArgs property is not set to Cancel. The Validated, LostFocus and Leave events all fire as well, going LostFocus > Leave > Validating
3) I can't see any code changes relating to the control or the datasource with a couple of exceptions. The first is that this :
Me.txtRangeEnd.DataBindings.Add(New System.Windows.Forms.Binding("Text", Me.dvClientNos, "RangeEnd"))
has now changed to this :
Me.txtRangeEnd.DataBindings.Add(New System.Windows.Forms.Binding("Text", Me.dvClientNos, "RangeEnd", True))
The second is that this :
Me.dcolRangeEnd.DataType = GetType(System.DateTime)
has now changed to this :
Me.dcolRangeEnd.DataType = GetType(Date)
There is also this, which has been in the code since day one :
AddHandler txtRangeEnd.DataBindings("Text").Format, AddressOf FormatBoxToDate
Private Sub FormatBoxToDate(ByVal sender As Object, ByVal e As ConvertEventArgs)
Try
If Not e.Value Is DBNull.Value Then
e.Value = Format(e.Value, "d")
End If
End Try
End Sub
Now, if I remove the ", True" from the adding of the databinding then I can exit the control with a blank value, but it then reverts to the original value. Removing the date formatting appears to make no difference to this (it just reverts to showing 06/01/2011 00:00:00 rather than the desired 06/01/2010). No other code refers to that textbox at all. I'm thinking something must have changed in validation of databound controls between VS2003 and VS2008, but it's just as likely I'm missing something mind-numbingly obvious.
Any ideas?
The reason that you're seeing the observed behaviour is to do with how Windows Forms and it's Data Binding handles NULL database values.
The TL;DR reason:
See this Microsoft Connect suggestion: Provide better databinding support for nullable types
The long version:
What is essentially happening is that as you clear the Textbox (to an empty string) and subsequently tab away, the binding is converting your empty string to a DBNull value which is then propagated to the data source however the binding, since it is two-way, then attempts to re-populate the bound control (the Textbox) with appropriate formatting, and fails, causing the Textbox to display the strange behaviour of not allowing the focus to be removed from it!
This is happening due to the DataSourceNullValue property of the Binding class. This can be set using one of the Binding classes constructor overloads, or set separately via a property setting, however, if you do not explicitly set this property, it is important to note that:
The default is DBNull for value types
and null for non-value types.
It appears that you're not explicitly setting this, so the default is applying, and with your DateTime being a value type, it is using DBNull.
Once the data source has been updated (to DBNull), the binding mechanism will attempt to then repopulate the Textbox with the newly updated data source value. When the underlying data source value is DBNull, the value used for the bound control is governed by the Binding class's NullValue property. Again, if this property is not explicitly set either via the relevant overloaded constructor argument or via the property setting itself, the default value will apply, which is:
The Object to be set as the control
property when the data source contains
a DBNull value. The default is null.
Of course, a Textbox's Text property can only be set to an object of type System.String and not a null value (Nothing in VB), so the TextBox fails to bind the representative value (null/nothing) of the data source's value (DBNull) to the bound control.
The way to correct this behaviour is to ensure that the Binding class's NullValue property is explicitly set to a suitable value. In this case, a zero-length string will suffice to correct the problem.
One way to achieve this is to change the line:
Me.txtRangeEnd.DataBindings.Add(New System.Windows.Forms.Binding("Text", Me.dvClientNos, "RangeEnd", True))
to:
Me.txtRangeEnd.DataBindings.Add(New System.Windows.Forms.Binding("Text", Me.dvClientNos, "RangeEnd", True, DataSourceUpdateMode.OnValidation, ""))
The key here is the very last parameter, which is the NullValue, set to a zero-length string (The DataSourceUpdateMode is also explicitly specified due to the arguments of the constructor, but it's being set to it's default value anyway).
Despite all of this, it does appear to be somewhat "odd" behaviour, if not an actual bug. This is also evidenced by others who appear to be experiencing the same issue (which is still prevalent in Visual Studio 2010/.NET 4.0!). This thread on the social.msdn.microsoft.com forums contains someone experiencing the same issue with some interesting possible explanations as to why this happens, and why Microsoft designed it this way.
There is also a Microsoft Connect suggestion that was reported back in 2005 that highlights the issue. This suggestion has been "Closed as Postponed". It appears that Microsoft do not consider it a bug, as a very reasonable workaround exists (the explicit setting of the Binding's NullValue property) which, arguably, should be done anyway for readability's sake. They will apparently consider the suggestion in the future.
Going back to why this didn't exist pre-.NET 2.0 (Visual Studio 2005) seems to be due to the fact that the entire data binding mechanism was completely revamped for the release of .NET Framework 2.0. Your original solution, being a VS2003 project was using .NET Framework 1.1 which did not have as rich a data binding feature-set. Although I no longer have a copy of VS2003 to hand to test this, I'm assuming the binding mechanism in .NET 1.1 made much more use of implicit conversions between the control's value and the data source's value. This appears to be supported when you examine the Binding class from .NET 1.1, compared with .NET 2.0 (or higher). For example, there was no way to (easily) control the actual two-way binding itself (and how values are converted between the form and the data source) or the formatting of said values.
I have had this type of error before and I had to ensure that the underlying data source (in my case it was a dataset) was not set to read only and that the column allowed `null' values.
Once I had done this everything worked fine. It seemed like that the error that was being thrown in the Data Bindings was swallowed up somewhere and didn't propagate up.
Consider the following vb.net code for an office add-in (for access):
td = db.TableDefs(objectName)
For Each fld In td.Fields
For Each fldprp In fld.Properties
Debug.Print(fldprp.Value.ToString())
Next
Next
the variable "db" is a .net representation of the access vba return result from "Application.CurrentDB()". "td" is of type "DAO.TableDefClass".
This code throws an exception of type "InvalidOperationException" when the value of the fldprp.value property cannot be determined (in Visual Studio, it shows the value as {"Invalid Operation."} in the watch window). fldprp.name, however, is available.
There are only a few properties which this occurs on. I'd like to be able to loop through all the fld.properties and output the values, but ONLY if it is not an exception.
I am pretty sure why it is happening (certain properties are not available in this context). What I need to know is how to detect this at run-time so i can skip the property.
I can't seem to find a solution that will work. Help would be greatly appreciated.
Inside the body of the inner loop, put this at the top:
If TypeOf fldprp Is InvalidOperationException Then Continue
http://msdn.microsoft.com/en-us/library/0ec5kw18%28VS.80%29.aspx
I don't know your specific situation, but what I would suggest is using an explicit filter for the object types you want to include instead of filtering out the ones you don't want to include.
I've added some form controls to a collection and can retrieve their properties when I refer to the members by index.
However, when I try to use any properties by referencing members of the collection I see a 'Could not set the ControlSource property. Member not found.' error in the Locals window.
Here is a simplified version of the code:
'Add controls to collection'
For x = 0 To UBound(tabs)
activeTabs.Add Item:=Form.MultiPage.Pages(Val(tabs(x, 1))), _
key:=Form.MultiPage.Pages(Val(tabs(x, 1))).Caption
Next x
'Check name using collection index'
For x = 0 To UBound(tabs)
Debug.Print "Tab name from index: " & activeTabs(x + 1).Caption
Next x
'Check name using collection members'
For Each formTab In activeTabs
Debug.Print "Tab name from collection: " & formTab.Caption
Next formTab
The results in the Immediate window are:
Tab name from index: Caption1
Tab name from index: Caption2
Tab name from collection:
Tab name from collection:
Why does one method work and the other fail?
This is in a standard code module, but I have similar code working just fine from within form modules. Could this have anything to do with it?
Edited to add
formTab was declared as a Control, but I find that if it is declared as an Object then the code works.
This will probably solve my problem, but in the interests of furthering my knowledge I would be grateful for any explanation of this behaviour, particularly with regard to the difference in running the code in the different types of module.
This is a really great question. Your edit at the end of the post reveals a lot about how VBA works and what's going on here. I'm not 100% this what's going on, but I'll explain what I think is happening.
A Collection in VBA (and VB6, for that matter; same code base) is not strongly typed. This means that everything in a collection is technically an "object." In the .NET world (as of .NET 2.0), it's possible to have strongly typed collections so that you could say "everything in this collection is a Control object." In VBA, this isn't possible with a Collection.
In your first iteration, where you are referring to the item indexed in the activeTabs collection, activeTabs(x + 1) is referring to an object. When you tell VBA to look up .Caption of that object, it doesn't know what the underlying type is (I think), so it has to simply look to see if the underlying object type contains a property or method called Caption. As you can see, Tab controls do in fact contain a property called Caption.
In your second interation, where you are doing a For Each loop, I think the problem is that the Control type probably doesn't have a property called Caption, though different types of controls probably do. For example, a text box control probably doesn't have a Caption property whereas as label control does have a Caption property.
You have a few options to fix your second loop. 1) You could declare formTab as a Tab control (I'm not sure exactly what it's called). The Tab control should have a Caption property. 2) If every control in activeTabs is not specifically a Tab control (in which case, you should probably call it activeControls instead of activeTabs), you could check within your loop to see if the formTab is actually a Tab control. If it is, cast it as a Tab control and then call .Caption. Until you cast it as a Tab control, VBA won't know that it has a Caption property since a regular Control object doesn't have a caption property.
In the end, you can get away with using objects as in your first loop and letting the runtime figure out what to do, but that can give really bad performance. In general, it's better to work with your specific types in a strongly-typed language. It also helps to show in your code that you know specifically what you're working with rather than leaving it to the runtime to decide what properties and methods you can work with.
I'm trying to obfuscate some VB.NET 2003 app.
The resulting assemblyes are obfuscated and "run" with some errors.
I cleaned all potential reflection problems, but I'm not being able to read the selected value of a combobox.
I load the Combobox using their Datasource properties, using a collection of "VTPair" (a class created by me with 2 properties: one of string type and other of object type to store the value)
This combobox handle pairs like "Male | M" or "Female | F".
When trying to see what is selected, I use if mycombo1.SelectedValue = "M" then
This code, after obfuscation, throws me an exception that cannot cast type "XX" to string "M".
So, I changed the code to something more correct, explicitly casting the selected value to String:
if ctype(mycombo1.SelectedValue,string) = "M" then
But the error is the same.
Debugin my original code, the SelectedValue property is of type "Object" but it is a string.
I tried using the ComboBox.SelectedItem property that is also an object but this time what is inside is of type "VTPair" (my own class) and then trying to access its "Value" property (which is of type Object) and trying to cast to string fails again.
Does anyone have an idea to "translate" this piece of code to work OK after Dotfucate it?
Thanks!
From MSDN:
ListControl.SelectedValue Property:
Gets or sets the value of the member property specified by the ValueMember property.
So whatever property NAME you set for the ValueMember property will be used when you use the SelectedValue property. So you may need to exclude from obfuscation, the property which you specify via the ComboBox.ValueMember property.
Not sure of the VB Syntax but in C# I think you would want something where the right-hand side is typeof(MyType). This way the type will be obfuscated to match the renamed type.