How do I listen for a Project Change event from an MS Project VSTO Add-in? - vsto

My Add-in has an application event ProjectBeforeTaskChange that is available from the ThisAddIn class. What I need is a ProjectAfterTaskChange event, but that doesn't exist. Is there a way to listen for the Project Change event from an application level add-in?
My end goal is to set three number fields when a specific text field changes.

ProjectBeforeTaskChange is the correct event to use to monitor field changes. The word "Before" refers to the fact that the handler can prevent the change by setting the Cancel argument to True. The event is triggered when the user makes a change to a task field.
Here's a vb.net example that increments the Number1 and Number2 fields whenever the Text1 field changes:
Private Sub Application_ProjectBeforeTaskChange(tsk As MSProject.Task, Field As MSProject.PjField, NewVal As Object, ByRef Cancel As Boolean) Handles Application.ProjectBeforeTaskChange
If Field = MSProject.PjField.pjTaskText1 Then
Select Case NewVal
Case Is = "In-work": tsk.Number1 = 50
Case Is = "Complete": tsk.Number1 = 100
Case Else: tsk.Number1 = 0
End Select
End If
End Sub
Here's the MSDN page.
Here's a related post showing the c# framework.

Related

Using PreviousControl property with nested subforms and controls

I am creating a custom floating number pad so that tablet users can more easily tap numerical data into a form. I'm using toggle buttons (the idea is to eventually highlight them briefly). The following code for the number pad works on my main form's controls. When I click on the 1 button, then a 1 is put into the active control of the main form
Private Sub Form_Activate()
FocusForm = Application.Screen.PreviousControl.Parent.Name
FocusControl = Application.Screen.PreviousControl.Name
End Sub
Private Sub Toggle1_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
'Add a 1 to the currently selected control
Dim refControl As Control
If FocusForm = "" Then End
Set refControl = Forms(FocusForm).Controls(FocusControl)
If (IsNull(refControl)) Then
refControl = "1"
Else
refControl.Text = refControl.Text & "1"
End If
refControl.SetFocus
End Sub
However, if I navigate into the subform then the Screen.PreviousControl properties return the main form's name and the subform's name, but I can't then seem to be able to refer to the subform control's name as well. Bascially I'm looking for a line that does the following
FocusForm = Application.Screen.PreviousControl.Parent.Name
FocusSubform = Application.Screen.PreviousControl.Name
FocusSubformControl = ?????
Similarly, my subform has its own nested subform and I would like to do the same with this
FocusForm = Application.Screen.PreviousControl.Parent.Name
FocusSubform = Application.Screen.PreviousControl.Name
FocusSubform2 = ?????
FocusSubform2Control = ?????
The in-built floating number pad on the tablets is very fiddly to use, hence my coding of this custom one, however if someone knows where I might find code for a customisable number pad that writes directly into whatever window is active, this would similarly be much appreciated!
If called from a subform,Screen.PreviousControlreturns the name of the mainforms subfom-control (as mainform needs to be active to get the subform control (nested in mainform) active), not the subform (but they can have same name, just change controls name and you won't get subforms name returned, but subforms control name!).
Now how to get previous control of the subform? By its.ActiveControlproperty, as form was active before, its active control must be the previous control. (if you want to get previous control, when you stay on the same subform, you need to store it in a module variable)
As there may be multiple subforms inside of subforms, we just loop while the control is a subform and set it to the next subform.ActiveControltill finished.
Why bother with names, if you can use references? This enables using multiple instances of a form.
Just store the reference to the control in Form_Activate and use it inMouseUp:
'On top of forms code module
Option Explicit
Private PreviousFormControl As Access.Control
Private Sub Form_Activate()
On Error Resume Next ' ignore error if no previous control availible
Set PreviousFormControl = Application.Screen.PreviousControl ' this stores the reference not the name!
If Err.Number <> 0 Then
'Handle error here, e.g. disable controls, show msg, ...
End If
On Error GoTo 0 'Reset error handler (not needed only to remind you not just to ignore all errors!
Do While TypeOf PreviousFormControl Is SubForm ' Loop till control is not subform
Set PreviousFormControl = PreviousFormControl.Form.ActiveControl ' if subform, previous control of it is its current ActiveControl
Loop
End Sub
Private Sub Toggle1_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
'Add a 1 to the currently selected control
PreviousFormControl.Text = PreviousFormControl.Text & "1" ' concat "1" to controls text, no need to handle empty text as it can only be an vbNullString(""), even if Null, then Null & "1" => "" & "1" => "1"
PreviousFormControl.SetFocus
End Sub
however if someone knows where I might find code for a customisable number pad that writes directly into whatever window is active
Of course you can use any Virtual Keyboard running on windows that fit your needs. But then you may have to handle things like bring keyboard to front on wanted position.
numpad emulator was the first open source app I found on quick search, but looks goods on short test. Of course there may be better solutions out there, just search and test.

How to change font size using VBA in MS Acces

I'm trying to change font size in Ms Access report in section group based on text box value.
I have wrote some code in VBA in event "On Format":
Private Sub Header_OrganizationalUnit_Format(Cancel As Integer, FormatCount As Integer)
If Me.txt_OrganizationalUnit.Value = "HeadQuarter" Then
Me.txt_OrganizationalUnit.FontSize = 24
Else
Me.txt_OrganizationalUnit.FontSize = 12
End If
End Sub
It doesn't change any font size in text box
First of all, you don't need VBA in order to do this. You can use conditional formatting instead.
The event you want to capture is the Change event.
You also need to read the Text property instead of Value, since the value is only updated when the textbox loses focus.
Add this event procedure to your report code:
Private Sub txt_OrganizationalUnit_Change()
With txt_OrganizationalUnit
If .Text = "HeadQuarter" Then
.FontSize = 24
Else
.FontSize = 12
End If
End With
End Sub
Using with block allows you to reduce the number of bound calls VB performs in the background, while making your code look better.

How can cancelling DataGridViewCellValidatingEventArgs replace all event handlers with itself?

I'm having a very strange problem in a VB application. I have a function written like this:
In the innermost condition, two statements are commented out here. These were found to have no effect on the strange behaviour. This is the minimal example I've found causing trouble.
(Note that the names of objects have been changed in this example.)
Private Sub MyForm_CellValidating(ByVal sender As Object, ByVal e As DataGridViewCellValidatingEventArgs) Handles myDGV.CellValidating
Dim dgv As DataGridView = CType(sender, DataGridView)
Select Case dgv.Columns(e.ColumnIndex).Name
Case "uniqueColumn"
' Validate that the values in our unique column are unique.
For i As Integer = 0 To dgv.RowCount - 1
If i <> e.RowIndex Then
' Here i != j, so compare to the value...
If e.FormattedValue = dgv.Rows(i).Cells(e.ColumnIndex).FormattedValue Then
e.Cancel = True
'dgv.ShowRowErrors = True
'dgv.Rows(e.RowIndex).ErrorText = "Data in the unique column must be unique"
End If
End If
Next 'i
Case Else
' Perform no validation.
End Select
End Sub
What trouble, you ask? For some inexplicable reason, whenever the line
e.Cancel = True
is executed, afterwards, nearly all buttons and form widgets in the entire application, including even the close button in its window bar (what a user would use to exit the application) stop doing whatever they previously did and now call this event handler instead.
In other words, commenting out that line (and doing the validation manually when the form is submitted) fixes the problems. I'd like to know why this happens, though. Some pointers:
Here's a list of which things are not affected:
The minimize and maximize button in the top bar.
All objects in its menu bar.
This handler is private to its form class, it's not referenced anywhere else in the application.
I'm at a loss. Just how? What could possibly cause this?
e.Cancel is for stopping the validation when the input is deemed incorrect. This causes the cell to still have focus as the user is expected to correct whatever they did wrong. The CellValidating event will then be raised again whenever the cell is about to lose focus until your code deems the input to be correct.
You can use the Control.CausesValidation property to control whether a control (for instance a button) should raise validation events when it gains focus.

Changing Textbox.DefaultValue in Access

I would like to be able to change the Textbox.DefaultValue during the 'On Load' event of a form such that each time the form is loaded the user is prompted with an InputBox to change a specific TextBox.Default value which in my case the TextBox control on the table is called Stream. I have tried the following code but each time it gives me a
'RunTime Error 3422 Cannot modify table structure. Another user has
the table open'.
Private Sub Form_Load()
CurrentDb.TableDefs("Class 1 Students (C1)").Fields("Stream").DefaultValue = InputBox("Enter Stream Letter:")
End Sub
I am using Microsoft Access
As Doug Glancy said in a comment, don't change the field's default value in table design. Instead change the text box's default value.
This is a critical point in a multi-user database application --- you wouldn't want one user stomping on another's default value choice. But, even if this will always be a single-user application, changing the table design means you can't have the table open in the record source of your form.
Changing the text box default value is easy. I added an unbound text box, txtDefaultStream, to my form's header. And, in its after update event, I change Me.txtStream.DefaultValue. The code is below.
Here is a screenshot of that form in action. I had A as the default when entering the first 2 rows. Then entered B in the default stream text box. Notice the new record has B in its Stream text box.
Private Sub txtDefaultStream_AfterUpdate()
Dim strDefault As String
If Len(Trim(Me.txtDefaultStream & vbNullString)) > 0 Then
strDefault = """" & Me.txtDefaultStream.value & """"
Me.txtStream.DefaultValue = strDefault
Else
Me.txtStream.DefaultValue = vbNullString
End If
End Sub
As Doug Glancy said, use the textbox, so you should try
Private Sub Form_Load()
Me.AText.DefaultValue = "='S'"
End Sub

How to read input from a barcode scanner in vb.net without using a textbox?

My program is already working fine, I use a TextBox to capture the barcode scanner input. The purpose of my program is for time and attendance monitoring, the problem is I want to prevent users from using the keyboard to type in their ID's as it would render the barcode scanner and their ID's with barcodes useless.
*I already tried removing the keyboard from the computer and it did work, but the keyboard must not be removed as a requirement...
Option 1:
Get a barcode-scanner that is connected to a serial-port (raw serial device read by a COM port). As most barcode-scanners emulate keyboard strokes there is no way to directly distinguish a barcode scanner input from a keyboard input (see next option) without going low-level (see last update).
One connected to a serial port (or emulated one via USB as serial-ports are not so common anymore) gives you full control on where the input comes from.
Option 2:
Count number of chars typed by time. Barcode-scanners inject a sequence (line) pretty fast compared to typing. Measuring the time used in the textbox by counting key-presses (use CR+LF as a measure point as these are sent by the scanner as well) can give you one method to distinguish if a human is typing (unless there is one typing fast as f) or the content was injected. If timed-out just reject/clear the input.
In addition the checksum of the barcode (if you use one that contains that) can be used to do an extra validation in addition to time measurement.
(you can detect pasting by overriding the ctrl + v as in the next option).
Option 3:
Combine option 2 but instead of measure in the textbox tap into the ProcessCmdKey() function (by overriding it) and measure there if textbox has focus. This way you can first buffer input, measure time and if within a set time-out value, inject the line into the textbox.
Update:
Option 4: a non-technical approach -
Usability improvements: make it visually very clear that bar-codes must be entered with a scanner and not typed. I am including as an option as it is simple and if made correct also effective (there's no right answer of what is correct unfortunately).
Approached could include f.ex. a watermark in the textbox ("Don't type, scan!" or something in that order). Give it a different color, border, size etc. to distinguish it from normal textboxes, and have a help text associated and available at all time that improves clarity.
I had the same issue and I did the following:
I set an int variable digitsPrevTyped = 0
In the "TextChanged" event of my textbox I added this (the textbox has a maxsize of 17 chars):
Private Sub tbxScannedText_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles tbxScannedText.TextChanged
If tbxScannedText.Text.Length >= 17 Then
SearchFunction(False)
Else
digitsPrevTyped = tbxScannedText.Text.Length
End If
End Sub
Then in my "SearchFunction" I check the following:
Dim inputMethod As Char
If tbxScannedText.TextLength = 17 And digitsPrevTyped = 0 Then
inputMethod = TEXT_SCANNED
Else
inputMethod = TEXT_MANUALLY_ENTERED
End If
If the textbox initially had a length of 0 chars and now has a length of 17 chars it means that the text was scanned. If the length of the previously typed text is less than 17 chars, then the text was typed.
It is very basic but it works for me.
The other possible workaround is to handle keypress event to restrict user input. Do not allow direct input from keyboard and leave the readonly false.
Set following in KeyPress event handler
Private Sub Textbox1_KeyPress(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles Textbox1.KeyPress
e.Handled = True
End Sub
Just disable the keyboard anyway.. when using barcode you can disable the keyboard without using readonly on the textbox..
on keypress event put some code i.e
if e.keychar <> chrw(0) then
e.keychar = chrw(0)
end if
that condition will automatically be trigged when user type anything.. you will forcibly disable any input from user but not from barcode
why not use an "alias" in the bar code like "123##$!" (but make it stupid long) is "JSMITH" and set the font color to the same as the background color in the textbox. The user can't see what they're typing or what the bar code value is when it's scanned.
Super simplistic approach that doesn't really require anything added aside from another field in the the user table.
This is an old post, but it took me some time to figure out a relatively clean way to use a barcode scanner and combobox so this is for future users.
Barcode scanners can often be configured to append carriage return and line feed to the end of the scan. I have a form that can take user input or barcode scanner input into a bound combobox using the _PreviewKeyDown property and trapping on the value "Keys.Enter".
Example:
If ((e.KeyCode = Keys.Enter) Then
'do stuff
Else
'do other stuff
End if
Verifying the data exists in the datasource is a bit trickier because the SelectedValue property of the combobox doesn't update so that event doesn't fire. I used a custom method to verify that the value scanned exists in the datasource. This method uses the .Text property of the combo box. It uses:
Me.combobox.findexactstring(Me.combobox.Text)
If e.KeyCode = Keys.Enter And txt.Text.Length > 0 Then
'To Do
Else
'To Do
End if
All of my scanner input goes into a "hidden" textbox, which then fills the visible ones as needed depending on the input. This, of course, means you need to keep track of where the focus is. Any type of control that can get focus will then make a call in those events to return focus to whatever the "active" textbox is at that time, which is normally the hidden one. For example...
Private Sub buttons_gotFocus(sender As System.Object, e As System.EventArgs) Handles btnPrint.GotFocus, btnInMVPageDown.GotFocus, btnAdv.GotFocus, btnManual.GotFocus, btnResend.GotFocus, dgvInbound.GotFocus, dgvOutbound.GotFocus, TCRole.GotFocus
Try
activeTextbox.Focus()
Catch ex As Exception
'ignore any errors
End Try
End Sub
Most other textboxes are disabled by default, and only enabled under certain conditions. Once that entry is done they are disabled and the hidden one will get focus again. Works like a charm.
There's no need to record previous typed characters.
Here's my solution:
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
If TextBox1.Text.Length >= 17 Then '17 or the number of characters your scanner gets.
MsgBox("scanned")
TextBox1.Clear()
Else
If TextBox1.Text.Length <> 0 Then TextBox1.Clear()
End If
End Sub
This answer will handle any fast typing.
Dim scanner_input As Boolean = False
Dim start_typing As DateTime
Private Sub TextBox_part_number_TextChanged(sender As Object, e As EventArgs) Handles
TextBox_part_number.TextChanged
If (TextBox_part_number.Text.Length = 1) Then
start_typing = DateTime.Now
scanner_input = False
'' MsgBox(start_typing.ToString)
ElseIf (TextBox_part_number.Text.Length > 7) Then
If (calc_typing_time(start_typing) < 500) Then
scanner_input = True
Else
scanner_input = False
End If
End If
End Sub
Function calc_typing_time(time_started As DateTime)
Dim time_finished As DateTime
time_finished = DateTime.Now
Dim duration As TimeSpan = time_finished - time_started
Dim time_diff As String = duration.TotalMilliseconds
Return time_diff
End Function
Most of the scanners has a driver to communicate with (Opos) it has functions to open the scanner port and listen to the scanning , so you take the result and decode it in the background and then display the result in the Textbox... what you need to do it to check your barcode scanner's brand go to it's website and download the driver and its manual.
You should just mark your textbox as readonly.