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.
Related
I have an unbound textbox to accept the delete older than: number of days. It is in the report header. I set it to 30 days but I want the user to be able to change it. I was banging my head trying to figure out why entering 40 was not being accepted and it reverted back to 30 every time. I finally decided on using the lost_focus event to set .value to .text. That worked.
Further research showed that when the textbox get's focus text and value are both the same, 30 in my case. Changing the number in the text box to 40 shows the values of text at 40 and value at 30. Unless I specifically set Value to the value of text Access changes text to the value of value. This is different behavior than other places in Access such as forms.
Can anyone tell me why this might be? I can't find any setting that might do this. Is it because it's in a report header? what is the difference between this and every other text box I've ever used?
From a "best practices" viewpoint, Access Reports are not intended to be used interactively despite the ability to manipulate some unbound controls. Although workarounds can be implemented that function sufficiently well, such solutions are often incomplete and buggy and function differently depending on the active view: Report View vs. Print Preview. Appropriate design patterns include using Access Forms for specifying report options which then open the Report in a static configuration.
This may not satisfy the question "Why?" if seeking a deeper answer as to why Microsoft implemented inconsistent binding behavior in Access, or why they allowed interactive controls in reports at all if they don't behave the same way as in forms. But Access has plenty of other quirky behaviors that have no known/published explanation.
Regarding the priority of the Value property updating the Text property (and not vice versa): Value is the key field because it contains the actual data for the control (bound or unbound). Although it is natural to have a single control for both display and input (uh, that's how almost all controls work), the processes of displaying data and parsing user input are two distinct functions. The visual representation returned by the Text property can be manipulated using the various formatting properties, and technically could display an incomplete representation of the underlying Value data. If there are any conflicts between the stored Value property and the Text property, it is natural that the existing Value property has precedent.
My guess is that the automatic binding behavior was "relaxed" for reports to allow more flexible custom reporting output. First consider an Access Form in Datasheet view: An unbound Form control shows the same value for all records. Even if the control is edited while on a particular row, the updated value is displayed for all rows. The same control object is essentially repainted for each row and there is no concept of individual instances of the control that can hold different values. Bound controls have built-in code that repaint the control with data from the particular row, but there are still not multiple instances each "holding" the individual values. The visual output differs from an intuitive object-oriented paradigm where our minds what to assign each visual row its own in-memory instance of the controls--it just doesn't work like that in Access.
Unlike the Form behavior just described, the Report's Print Preview (and actual printed output) allows unbound controls to display different data per row using the Detail_Format() event. Within the Detail_Format() event, one can set the Value property of a control at which time the Text property is automatically updated according to various formatting properties. This update Text is then output for the current row. Perhaps (just guessing) that this behavior would not function properly if the Text property updated the value property. I suspect it would cause recursive events during report generation. Because reports are not meant to be interactive, relevant text-input parsing code was "disconnected" so that it doesn't behave like on a form.
All that explanation doesn't make Access any less frustrating nor remove its limitations, but at least learn to adapt and design things in the "Access-esque" way rather than fighting it.
your best bet is to design a form with the unbound combo boxes and have your data displayed in a subreport. I like to design my reports so that when values are updated the query for the recordsource of the report is generated doing this requires 2 queries to exist, one with all data possible and a filtered one as subreport recordsource. This will control the data for printing and also allow users to close or navigate away from the report and return to the data later.
Private Sub ComboBox1_AfterUpdate()
Dim Query1 as Object
Dim Temp_Name as Variant
Temp_Name = SubReport.SourceObject
SubReport.SourceObject = Empty
Set Query1 = Me.Form.Application.DBEngine.Workspaces(0).Databases(0).QueryDefs ("SubReport_Query")
Query1.SQL = "Select * Unfiltered_Query WHERE Field1 <= " ComboBox1 & ";"
SubReport.SourceObject = Temp_Name
End Sub
I inherited a fairly large project at work that is undocumented and written in VB (originally started pre .NET, ended around .NET 2). I'm in the process of updating / refreshing a lot of the code, but have run into an annoying issue that I haven't found the solution for yet. This system utilizes a UI, a Web Service, and a SQL DB.
Problem: I have a Databound Combobox (originally set to DropDownList - I'm changing it to DropDown, which is what started this mess - going back isn't an option) that is tied to a DataSet that comes from a Web Service. When a user types in the item they want manually, the data from the text field doesn't seem to associate itself with the DisplayMember, which forces the WS/SQL query to fail (it is sent a blank value when it's expecting a ValueMember). If the user types in a partial selection and then chooses the value they want from the DisplayMember list using the arrow keys or tab, the query goes off without a problem.
My Question: How do I get the text field to translate to the DisplayMember which will then properly tie itself to the ValueMember which will then allow the query to execute correctly? Sorry for making this sound complicated or convoluted; I'm sure the answer is easy and I'm just glazing over it.
The relevant bit of code is:
With cmbDID
If dtsLU.Tables.Contains(reqTable) = True Then
.DataSource = dtsLU.Tables(reqTable)
.DisplayMember = "zip"
.ValueMember = "gridID"
End If
End With
cmbDID.DataBindings.Clear()
cmbDID.DataBindings.Add("SelectedValue", dtsData, strDT & ".gridID")
I've tried changing "SelectedValue" to "Text", which almost works - but it directly translates to gridID and skips zip which ends up with an incorrect Web Service response since the zip and gridID field values are not synced (zip (DisplayMember) may be 5123 while gridID (ValueMember) may be 6047). I've tried changing "SelectedValue" to "SelectedIndex", and that got me no where.
Any help is greatly appreciated.
EDIT
To add some clarification to the process, the below pseudo code / description is roughly what happens. I could post the whole module, but I feel that would just muddy the whole question even more.
Private Sub A
FormAlpha is created with 1 ComboBox in the form of a DropDown
This DropDown is populated with a DataSet
DataBinding with a blank DataSet is added to the control to keep track of the users input
End Sub
lblSubmit_Click event is triggered on FormAlpha by the user after they have populated the DropDown with their data. lblSubmit_Click calls Private Sub Submit
Private Sub Submit
BindingContext(DropDown DataSet, tableName).EndCurrentEdit() is called
DataSet.HasChanges() is processed
If changes are present, changes are processed
HERE lies the problem
If the user has manually typed in the DropDown field, but not hit an arrow key or tab, then the DataSet registers a change, but returns a null value in all fields - it knows something was entered, but that data apparently didn't pass through the DataSet for the ComboBox (ListItems or SelectedIndex didn't change / fire I'm guessing). If the user selects the item with the arrow keys, the DataSet has the proper input (I'm assuming the Data was validated by the control at this point).
If the processed data is good, a value is entered into the database
If the processed data is bad (empty), an error is returned
End Sub
If the above can't be solved with what I've provided, but someone still knows a better way to handle this type of situation, I'm all ears. Rewriting the module isn't ideal, but fixing this problem is a necessity.
Alright, while this fix may not be ideal, it is a fix none the less.
The bare bones problem was that the text value of the DropDown wasn't causing the data to actually affect the SelectedIndex / SelectedValue of the control unless you interacted with it using the arrow keys or a mouse click. So, while the DropDown would read "1234", in reality the control saw "".
The fix I have in place for this is simply calling comboBox.text = comboBox.text whenever the user hits the submit button.
I have a subform that changes where it gets its data from based on user input by using the Me.SubForm.SourceObject = Query.SomeQuery. It seems that by doing this, I am losing the ability to set the BeforeUpdate property.
The code I am using is as follows:
Forms![PartsDatabase]![RepsSubform].Form![Pack Rank].BeforeUpdate = "=ToTracking()"
I have confirmed that this works before the SubForm.SourceObject is changed, but afterwards it throws the following error: RTE 2455 "You entered an expression that has an invalid reference to the property BeforeUpdate."
I was wondering if this is a known issue or if I just need to modify my code to adjust.
You are getting things muddled up here. You should never be changing Source Objects, rather you should be changing the Record Source. The code involved in the Form is Form level. If you wish to use the Before Update event, it belongs to the Form and not the Recordsource. So you always go to change the RecordSource.
You would use,
Forms!Parentform!SubForm.Form.RecordSource = "SELECT someFields FROM someTable;"
Or,
Forms!ParentForm!SubForm.Form.RecordSource = "yourCompiledQueryName"
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'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.