How to send message/activate a SysTray application? - winforms

We are trying to setup a SysTray application which can be activated from elsewhere. To be more specific the activation will come from a third party app which we cannot modify but allows us to activate our own app via its path (plus a parameter/argument).
When it gets activated we want to put up a BalloonText, there are to be no forms involved.
Therefore we have two problems to solve:
Make our SysTray application single instance (since it's no good generating multiple instances).
Allow this other app to activate our application with arguments
Lots of help out there to help learners create simple SysTray applications (and indeed we've done it ourselves as part of a solution to an unconnected project).
However we've never tried to make it single instance before.
Lots of help out there to help learners create single instance Winforms applications (again we've done this as part of other projects) but always simple applications with conventional forms (not SysTray). We use the VisualBasic WindowsFormsApplicationBase method.
Can't seem to combine these two approaches into a single solution.
Update:
Hans answer below nails it (and especially his comment):
This is already taken care of with a NotifyIcon, drop it on the form.
And the "Make single instance application" checkbox. And the
StartupNextInstance event. You'll need to stop assuming there's
anything special about this

As far as your first question about checking for other instances this seems to work. I used the CodeProject example as a baseline. In your Sub Main routine you can check for other instances by using the GetProcessesByName Method of the Process class. Something like this:
Public Sub Main()
'Turn visual styles back on
Application.EnableVisualStyles()
'Run the application using AppContext
Dim p() As Process
p = Process.GetProcessesByName("TrayApp") 'Your application name here
If UBound(p) >= 0 Then
End
End If
Application.Run(New AppContext)
End Sub
For the second question if your SysTray application is already running you might want to give this article on .Net Interprocess Communication a try. Otherwise parse the CommandLine arguments in yourSub Main as it is created.
From above article:
The XDMessaging library provides an easy-to-use, zero-configuration solution to same-box cross-AppDomain communications. It provides a simple API for sending and receiving targeted string messages across application boundaries. The library allows the use of user-defined pseudo 'channels' through which messages may be sent and received. Any application can send a message to any channel, but it must register as a listener with the channel in order to receive. In this way, developers can quickly and programmatically devise how best their applications can communicate with each other and work in harmony.

Everything becomes trivial when you actually do use a form. It is simple to put your app together with the designer, simple to get your app to terminate, simple to avoid a ghost icon in the tray, simple to create a context menu, simple to add popups if you ever need them.
The only un-simple thing is getting the form to not display. Paste this code in the form's class:
Protected Overrides Sub SetVisibleCore(ByVal value As Boolean)
If Not Me.IsHandleCreated Then
Me.CreateHandle()
value = False
End If
MyBase.SetVisibleCore(value)
End Sub
The "Exit" command in the context menu is now simply:
Private Sub ExitToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ExitToolStripMenuItem.Click
Me.Close()
End Sub

Related

Exception: The Calling thread cannot access this object because a different thread owns it

I'm converting a UI from windows forms to WPF. I'm getting the following exception "The Calling thread cannot access this object because a different thread owns it" whenever I try to call anything on this new WPF window I created.
I referred stack overflow and many websites to find out that I should use Dispatcher.CheckAccess() or somethings similar to dispatcher and check access. I tried many such things
This is one of the things that I have used
Private Delegate Sub ShowSkinInvoked()
If (Dispatcher.CheckAccess()) Then
Me.Show()
Else
Dim d As ShowSkinInvoked = New ShowSkinInvoked(AddressOf ShowSkin)
Dispatcher.Invoke(d)
End If
This has removed the exception and while debugging the error is gone but it freezes the application and I cannot do anything other than terminate it. It doesn't even show the window after "Me.Show".
Also, if I compile the program and then make the calling module use this compiled exe by specifying path to exe then for some reason it works perfect.
If this sounds confusing then what I mean is, I have multiple forms. If I call the code in module A to load and display module B then it gives me the exception but if I call the code in module A to run the compiled exe of module B then it runs perfectly.
Any suggestions?
When WPF creates a user interface it created a thread that is responsible for handling all the user interaction events and scheduling the rendering. This is called the dispatcher thread. Many of the objects that it creates are sub classes of DispatcherObject.
You can't call methods on a DispatcherObject from threads other then the Dispatcher thread that created them. The reasons why are complicated but relate to COM interop.
When you are in a Buttons click event you are running on dispatcher thread.
If you are coming from another thread you must get your work to be performed on the dispatcher thread. It can typically be found by accessing the static current dispatcher Dispatcher.CurrentDispatcher, unless your creating multiple dispatcher threads.
However I would suggest explaining your problem in terms of what work your trying to do with regards to having one form show ui on another. There are multiple ways like an EventAggregator to communicate between ui that might be more appropriate.

Is this a safe way to use Async with events

I've previously used the Task Parallel Library to keep the UI thread responsive, but I'm trying to switch to async/await to simplify the code. The project I'm working on is in WinForms. There is a presentation layer that communicates with view interfaces, and an assembly that contains winforms forms that implement these interfaces, with dependency injection binding everything together. In the specific example below, the form has a menu/button/etc that sends a request to the presentation layer for new data. I've ignored exception handling to keep the example simple.
In the form code we have an event handler like this:
private async sub DataRequest(sender as object, e as EventArgs) handles SomeButton.click
Dim etask = New Threading.Tasks.Task(Sub() RaiseEvent DataRefreshRequest(Me, EventArgs.Empty))
etask.Start()
Await etask
end sub
Not sure if there's a neater way of awaiting an event being raised, the above seems a bit clumsy
The presentation layer responds to this event with code like this:
Private Async Sub HandleDataRequest() Handles _view.DataRefreshRequest
Dim gdtask = New Tasks.Task(Of IEnumerable(Of Summary))(Function() GetDataTask())
gdtask.Start()
_view.Data = Await gdtask
End Sub
We are stuck with .Net 4 on this project so we are using Microsoft.BCL.Async. GetDataTask is effectively a Linq query on entities exposed by the business layer, but without .Net 4.5 we can't execute it using .ToListAsync
The bit I'm a little unsure of is the way I'm raising the event in the winforms assembly. Is that the right way to do it? It seems to work okay, but I'm concerned about any unforeseen problems of doing it this way. Better to do it right from the start then find there's a problem once this pattern is in common use.
Using Task.Run is better than the Task constructor with Task.Start.
Other than that, I'd just recommend some comments. As you noted, the ideal solution would be to use ToListAsync and make it truly asynchronous, but that's not possible given your platform.
Task.Run is normally used for running CPU-bound code on a background thread, and this code is not CPU-bound. So I'd recommend a comment for your future self so that when you do upgrade to .NET 4.5, you can know those Task.Run calls are no longer necessary.

Detect if single entity needs saving in EF4 and WPF

I am struggling with detecting whether an entity in EF4 has changes that need saving.
After a few years away from .NET rusty is definitely the word I'd use to describe where I am right now. I am trying to learn to use EF4 and WPF while reacquainting myself with .NET. I've followed a number of tutorials on Drag & Drop Databinding with the Entity Framework and WPF and built an app with a few Windows that is getting my knowledge up little by little.
I am using the simplest part of my Model for my training exercises, The model has entities: Network and Laboratory, there is a many-to-many link between Networks and Labs, namely NetworkLabs, the relationship is not particularly important right now as I am still at the very basics.
I have a window that displays a list of Networks in a listbox, with a DataGrid next to it showing the Laboratories in the Network. I was able to do that fairly easily following the tutorials and I ended up with code like:
Public Class NetworkListWindow
Private Function GetNetworksQuery(entities As UKNEQASEntities) As ObjectQuery(Of Network)
Dim networksQuery As ObjectQuery(Of Network) = entities.Networks
' Update the query to include NetworkLabs data in Networks.
networksQuery = networksQuery.Include("NetworkLabs")
' Returns an ObjectQuery
Return networksQuery
End Function
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
Dim entities As UKNEQASEntities = New UKNEQASEntities()
' Load data into Networks.
Dim networksViewSource As CollectionViewSource = CType(Me.FindResource("UKNEQASEntitiesNetworksViewSource"), CollectionViewSource)
Dim networksQuery As ObjectQuery(Of Network) = GetNetworksQuery(entities)
networksViewSource.Source = networksQuery.Execute(MergeOption.AppendOnly)
End Sub
End Class
That window is for viewing only, the user can click an edit button to edit the selected network. That second window is where I am hitting problems, on that window I dragged the network entity over from the Data Sources window to create a details screen (a grid with labels and textboxes in the rows and columns). I ended up with code like:
Public Class NetworkWindow
Private m_id As Integer
Private m_db As New UKNEQASEntities
Public Sub New(id As Integer)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
m_id = id
End Sub
Private Function GetNetworkQuery() As ObjectQuery(Of Network)
Dim networkQuery As ObjectQuery(Of Network) = m_db.Networks
' Update the query to include only the Network we are editing
networkQuery = networkQuery.Where(Function(net) net.Id = m_id)
' Update the query to include NetworkLabs data in Networks.
networkQuery = networkQuery.Include("NetworkLabs")
' Returns an ObjectQuery
Return networkQuery
End Function
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
' Load data into Networks.
Dim networkViewSource As CollectionViewSource = CType(Me.FindResource("UKNEQASEntitiesNetworksViewSource"), CollectionViewSource)
Dim networksQuery As ObjectQuery(Of Network) = GetNetworkQuery()
networkViewSource.Source = networksQuery.Execute(MergeOption.AppendOnly)
' Get laboratories that are not in any networks
Dim labResult = From laboratory In m_db.Laboratories _
Where _
Not _
(From networklab In m_db.NetworkLabs _
Select networklab.Laboratory.Id).Contains(laboratory.Id) _
Select laboratory
Dim laboratoriesViewSource As CollectionViewSource = CType(Me.FindResource("UKNEQASEntitiesLaboratoriesViewSource"), CollectionViewSource)
laboratoriesViewSource.Source = labResult.ToList
End Sub
End Class
And that works fine for showing the network that was selected on the previous screen, I put a Save button on a toolbar which simply calls
m_db.SaveChanges()
To save the changes and that works fine as well. My problem comes when catering for when the user edits the data and closes the window, I want to detect whether the current network needs saving back to the database so I can prompt the user but I don't know how to get hold of the network to check.
I suspect it is something to do with code like:
Dim networkViewSource As CollectionViewSource = CType(Me.FindResource("UKNEQASEntitiesNetworksViewSource"), CollectionViewSource)
Dim entry As ObjectStateEntry = m_db.ObjectStateManager.GetObjectStateEntry(....)
but I don't know how to get the network to pass to GetObjectStateEntry.
On my previous list screen I was able to get the selected network by getting the SelectedItem from the listbox but I can't find anything that would help me on my single entry window.
Am I going about this the right way? For the single entry edit screen I am still using CollectionViewSource like I did for the list screen, is that the best way or is there something for single entities?
I have been looking for lots of tutorials and the majority I find are all about displaying the data for editing in DataGrids which is not what I am looking for. I am struggling to find any help on making screens for editing single entities so don't know how to pick up a reference to the entity the user is editing.
Any help is greatly appreciated as I am a novice at this EF and XAML lark.
You are looking at it from the wrong perspective. Several things that you need to consider when you start using EF:
You hardly need to look at
ObjectStateManager, this is something
EF is supposed to do for you. For example if you
want to close your Window and save
everything, just call
context.SaveChanges() and let EF to
the work of finding out what needs
saving and what not. There are cases where you need to use the ObjectStateManager, but these are specific and usually associated with very custom things.
You are not seeing many examples,
because people that are building WPF
and Silverlight applications aren't
connected to Data Base engines,
instead they connect to one or more
layers of Web Services and interact
with them. When you do this, the
first thing it happens is that you no
longer work with the "normal" EF
entities, these weren't made to be
serialized. You will work with either
POCO Entities of Self Tracking
Entities.
If you are using the normal Entities,
they are designed to allways work
attached to a context (in your case
it would be: UKNEQASEntities), this
means that if you attempt to use this
in a client application you will want
to make sure you allways use the same
context, so either have a reference
to it on a Singleton class or inject
it in a Dependency Injection
container. If you use multiple
contexts you will come across a bunch
of problems related with the
EntityKeys which are given to objects
in relation to the context they exist
in (consider the context a memory
copy of your Data Base, this isn't
true in practice, it works more like
an interface that you use to
auto-generate sql queries against
your Data Base.
In the end, if you want to build a good WPF client application you need to also use:
Dependency Injection container:
either MEF (which is deployed on
a .Net deployment) or Unity
(which is part of the Enterprise
Library).
EF4 Self Tracking Entities (I
personally perfer these over POCO
objects, because you get a lot of
powerfull functionality for free).
You can also poke me directly if you need more specific help, I've been working with this stuff for a while now, in professional development teams on medium to large scale projects. However I have never worked in Visual Basic, so I have a bit of pain when I have to read VB code!

New to MVVM - Best practices for separating Data processing thread and UI Thread?

I have started messing around with the MVVP pattern, and I am having some problems with UI responsiveness versus data processing.
I have a program that tracks packages. Shipment and package entities are persisted in SQL database, and are displayed in a WPF view. Upon initial retrieval of the records, there is a noticeable pause before displaying the new shipments view, and I have not even implemented the code that counts shipments that are overdue/active yet (which will necessitate a tracking check via web service, and a lot of time).
I have built this with the Ocean framework, and all appears to be doing well, except when I first started my foray into multi-threading. It broke, and it appeared to break something in Ocean... Here is what I did:
Private QueryThread As New System.Threading.Thread(AddressOf GetShipments)
Public Sub New()
' Insert code required on object creation below this point.
Me.New(ViewManagerService.CreateInstance, ViewModelUIService.CreateInstance)
'Perform initial query of shipments
'QueryThread.Start()
GetShipments()
Console.WriteLine(Me.Shipments.Count)
End Sub
Public Sub New(ByVal objIViewManagerService As IViewManagerService, ByVal objIViewModelUIService As IViewModelUIService)
MyBase.New(objIViewModelUIService)
End Sub
Public Sub GetShipments()
Dim InitialResults = From shipment In db.Shipment.Include("Packages") _
Select shipment
Me.Shipments = New ShipmentsCollection(InitialResults, db)
End Sub
So I declared a new Thread, assigned it the GetShipments method and instanced it in the default constructor. Ocean freaks out at this, so there must be a better way of doing it.
I have not had the chance to figure out the usage of the SQL ORM thing in Ocean so I am using Entity Framework (perhaps one of these days i will look at NHibernate or something too).
I have looked at a number of articles and they all have examples of simple uses. Some have mentioned the Dispatcher, but none really go very far into how it is used.
The way I do it is, using a backgroundworker. Then I use MVVM Light to send a message to the main ui to update progress.

Best way to test a MS Access application?

With the code, forms and data inside the same database I am wondering what are the best practices to design a suite of tests for a Microsoft Access application (say for Access 2007).
One of the main issues with testing forms is that only a few controls have a hwnd handle and other controls only get one they have focus, which makes automation quite opaque since you cant get a list of controls on a form to act on.
Any experience to share?
1. Write Testable Code
First, stop writing business logic into your Form's code behind. That's not the place for it. It can't be properly tested there. In fact, you really shouldn't have to test your form itself at all. It should be a dead dumb simple view that responds to User Interaction and then delegates responsibility for responding to those actions to another class that is testable.
How do you do that? Familiarizing yourself with the Model-View-Controller pattern is a good start.
It can't be done perfectly in VBA due to the fact that we get either events or interfaces, never both, but you can get pretty close. Consider this simple form that has a text box and a button.
In the form's code behind, we'll wrap the TextBox's value in a public property and re-raise any events we're interested in.
Public Event OnSayHello()
Public Event AfterTextUpdate()
Public Property Let Text(value As String)
Me.TextBox1.value = value
End Property
Public Property Get Text() As String
Text = Me.TextBox1.value
End Property
Private Sub SayHello_Click()
RaiseEvent OnSayHello
End Sub
Private Sub TextBox1_AfterUpdate()
RaiseEvent AfterTextUpdate
End Sub
Now we need a model to work with. Here I've created a new class module named MyModel. Here lies the code we'll put under test. Note that it naturally shares a similar structure as our view.
Private mText As String
Public Property Let Text(value As String)
mText = value
End Property
Public Property Get Text() As String
Text = mText
End Property
Public Function Reversed() As String
Dim result As String
Dim length As Long
length = Len(mText)
Dim i As Long
For i = 0 To length - 1
result = result + Mid(mText, (length - i), 1)
Next i
Reversed = result
End Function
Public Sub SayHello()
MsgBox Reversed()
End Sub
Finally, our controller wires it all together. The controller listens for form events and communicates changes to the model and triggers the model's routines.
Private WithEvents view As Form_Form1
Private model As MyModel
Public Sub Run()
Set model = New MyModel
Set view = New Form_Form1
view.Visible = True
End Sub
Private Sub view_AfterTextUpdate()
model.Text = view.Text
End Sub
Private Sub view_OnSayHello()
model.SayHello
view.Text = model.Reversed()
End Sub
Now this code can be run from any other module. For the purposes of this example, I've used a standard module. I highly encourage you to build this yourself using the code I've provided and see it function.
Private controller As FormController
Public Sub Run()
Set controller = New FormController
controller.Run
End Sub
So, that's great and all but what does it have to do with testing?! Friend, it has everything to do with testing. What we've done is make our code testable. In the example I've provided, there is no reason what-so-ever to even try to test the GUI. The only thing we really need to test is the model. That's where all of the real logic is.
So, on to step two.
2. Choose a Unit Testing Framework
There aren't a lot of options here. Most frameworks require installing COM Add-ins, lots of boiler plate, weird syntax, writing tests as comments, etc. That's why I got involved in building one myself, so this part of my answer isn't impartial, but I'll try to give a fair summary of what's available.
AccUnit
Works only in Access.
Requires you to write tests as a strange hybrid of comments and code. (no intellisense for the comment part.
There is a graphical interface to help you write those strange looking tests though.
The project has not seen any updates since 2013.
VB Lite Unit
I can't say I've personally used it. It's out there, but hasn't seen an update since 2005.
xlUnit
xlUnit isn't awful, but it's not good either. It's clunky and there's lots of boiler plate code. It's the best of the worst, but it doesn't work in Access. So, that's out.
Build your own framework
I've been there and done that. It's probably more than most people want to get into, but it is completely possible to build a Unit Testing framework in Native VBA code.
Rubberduck VBE Add-In's Unit Testing Framework
Disclaimer: I'm one of the co-devs.
I'm biased, but this is by far my favorite of the bunch.
Little to no boiler plate code.
Intellisense is available.
The project is active.
More documentation than most of these projects.
It works in most of the major office applications, not just Access.
It is, unfortunately, a COM Add-In, so it has to be installed onto your machine.
3. Start writing tests
So, back to our code from section 1. The only code that we really needed to test was the MyModel.Reversed() function. So, let's take a look at what that test could look like. (Example given uses Rubberduck, but it's a simple test and could translate into the framework of your choice.)
'#TestModule
Private Assert As New Rubberduck.AssertClass
'#TestMethod
Public Sub ReversedReversesCorrectly()
Arrange:
Dim model As New MyModel
Const original As String = "Hello"
Const expected As String = "olleH"
Dim actual As String
model.Text = original
Act:
actual = model.Reversed
Assert:
Assert.AreEqual expected, actual
End Sub
Guidelines for Writing Good Tests
Only test one thing at a time.
Good tests only fail when there is a bug introduced into the system or the requirements have changed.
Don't include external dependencies such as databases and file systems. These external dependencies can make tests fail for reasons outside of your control. Secondly, they slow your tests down. If your tests are slow, you won't run them.
Use test names that describe what the test is testing. Don't worry if it gets long. It's most important that it is descriptive.
I know that answer was a little long, and late, but hopefully it helps some people get started in writing unit tests for their VBA code.
I appreciated knox's and david's answers. My answer will be somewhere between theirs: just make forms that do not need to be debugged!
I think that forms should be exclusively used as what they are basically, meaning graphic interface only, meaning here that they do not have to be debugged! The debugging job is then limited to your VBA modules and objects, which is a lot easier to handle.
There is of course a natural tendency to add VBA code to forms and/or controls, specially when Access offers you these great "after Update" and "on change" events, but I definitely advise you not to put any form or control specific code in the form's module. This makes further maintenance and upgrade very costy, where your code is split between VBA modules and forms/controls modules.
This does not mean you cannot use anymore this AfterUpdate event! Just put standard code in the event, like this:
Private Sub myControl_AfterUpdate()
CTLAfterUpdate myControl
On Error Resume Next
Eval ("CTLAfterUpdate_MyForm()")
On Error GoTo 0
End sub
Where:
CTLAfterUpdate is a standard procedure run each time a control is updated in a form
CTLAfterUpdateMyForm is a specific procedure run each time a control is updated on MyForm
I have then 2 modules. The first one is
utilityFormEvents
where I will have my CTLAfterUpdate generic event
The second one is
MyAppFormEvents
containing the specific code of all specific forms of the MyApp application
and including the CTLAfterUpdateMyForm procedure. Of course, CTLAfterUpdateMyForm
might not exist if there are no specific code to run. This is why we turn the
"On error" to "resume next" ...
Choosing such a generic solution means a lot. It means you are reaching a high level of code normalization (meaning painless maintenance of code). And when you say that you do not have any form-specific code, it also means that form modules are fully standardized, and their production can be automated: just say which events you want to manage at the form/control level, and define your generic/specific procedures terminology.
Write your automation code, once for all.
It takes a few days of work but it give exciting results. I have been using this solution for the last 2 years and it is clearly the right one: my forms are fully and automatically created from scratch with a "Forms Table", linked to a "Controls Table".
I can then spend my time working on the specific procedures of the form, if any.
Code normalization, even with MS Access, is a long process. But it is really worth the pain!
Another advantage of Access being a COM application is that you can create an .NET application to run and test an Access application via Automation. The advantage of this is that then you can use a more powerful testing framework such as NUnit to write automated assert tests against an Access app.
Therefore, if you are proficient in either C# or VB.NET combined with something like NUnit then you can more easily create greater test coverage for your Access app.
Although that being a very old answer:
There is AccUnit, a specialized Unit-Test framework for Microsoft Access.
I've taken a page out of Python's doctest concept and implemented a DocTests procedure in Access VBA. This is obviously not a full-blown unit-testing solution. It's still relatively young, so I doubt I've worked out all the bugs, but I think it's mature enough to release into the wild.
Just copy the following code into a standard code module and press F5 inside the Sub to see it in action:
'>>> 1 + 1
'2
'>>> 3 - 1
'0
Sub DocTests()
Dim Comp As Object, i As Long, CM As Object
Dim Expr As String, ExpectedResult As Variant, TestsPassed As Long, TestsFailed As Long
Dim Evaluation As Variant
For Each Comp In Application.VBE.ActiveVBProject.VBComponents
Set CM = Comp.CodeModule
For i = 1 To CM.CountOfLines
If Left(Trim(CM.Lines(i, 1)), 4) = "'>>>" Then
Expr = Trim(Mid(CM.Lines(i, 1), 5))
On Error Resume Next
Evaluation = Eval(Expr)
If Err.Number = 2425 And Comp.Type <> 1 Then
'The expression you entered has a function name that '' can't find.
'This is not surprising because we are not in a standard code module (Comp.Type <> 1).
'So we will just ignore it.
GoTo NextLine
ElseIf Err.Number <> 0 Then
Debug.Print Err.Number, Err.Description, Expr
GoTo NextLine
End If
On Error GoTo 0
ExpectedResult = Trim(Mid(CM.Lines(i + 1, 1), InStr(CM.Lines(i + 1, 1), "'") + 1))
Select Case ExpectedResult
Case "True": ExpectedResult = True
Case "False": ExpectedResult = False
Case "Null": ExpectedResult = Null
End Select
Select Case TypeName(Evaluation)
Case "Long", "Integer", "Short", "Byte", "Single", "Double", "Decimal", "Currency"
ExpectedResult = Eval(ExpectedResult)
Case "Date"
If IsDate(ExpectedResult) Then ExpectedResult = CDate(ExpectedResult)
End Select
If (Evaluation = ExpectedResult) Then
TestsPassed = TestsPassed + 1
ElseIf (IsNull(Evaluation) And IsNull(ExpectedResult)) Then
TestsPassed = TestsPassed + 1
Else
Debug.Print Comp.Name; ": "; Expr; " evaluates to: "; Evaluation; " Expected: "; ExpectedResult
TestsFailed = TestsFailed + 1
End If
End If
NextLine:
Next i
Next Comp
Debug.Print "Tests passed: "; TestsPassed; " of "; TestsPassed + TestsFailed
End Sub
Copying, pasting, and running the above code from a module named Module1 yields:
Module: 3 - 1 evaluates to: 2 Expected: 0
Tests passed: 1 of 2
A few quick notes:
It has no dependencies (when used from within Access)
It makes use of Eval which is a function in the Access.Application object model; this means you could use it outside of Access but it would require creating an Access.Application object and fully qualifying the Eval calls
There are some idiosyncrasies associated with Eval to be aware of
It can only be used on functions that return a result that fits on a single line
Despite its limitations, I still think it provides quite a bit of bang for your buck.
Edit: Here is a simple function with "doctest rules" the function must satisfy.
Public Function AddTwoValues(ByVal p1 As Variant, _
ByVal p2 As Variant) As Variant
'>>> AddTwoValues(1,1)
'2
'>>> AddTwoValues(1,1) = 1
'False
'>>> AddTwoValues(1,Null)
'Null
'>>> IsError(AddTwoValues(1,"foo"))
'True
On Error GoTo ErrorHandler
AddTwoValues = p1 + p2
ExitHere:
On Error GoTo 0
Exit Function
ErrorHandler:
AddTwoValues = CVErr(Err.Number)
GoTo ExitHere
End Function
I would design the application to have as much work as possible done in queries and in vba subroutines so that your testing could be made up of populating test databases, running sets of the production queries and vba against those databases and then looking at the output and comparing to make sure the output is good. This approach doesn't test the GUI obviously, so you could augment the testing with a series of test scripts (here I mean like a word document that says open form 1, and click control 1) that are manually executed.
It depends on the scope of the project as the level of automation necessary for the testing aspect.
If your interested in testing your Access application at a more granular level specifically the VBA code itself then VB Lite Unit is a great unit testing framework for that purpose.
There are good suggestions here, but I'm surprised no one mentioned centralized error processing. You can get addins that allow for quick function/sub templating and for adding line numbers (I use MZ-tools). Then send all errors to a single function where you can log them. You can also then break on all errors by setting a single break point.
I find that there are relatively few opportunities for unit testing in my applications. Most of the code that I write interacts with table data or the filing system so is fundamentally hard to unit test. Early on, I tried an approach that may be similar to mocking (spoofing) where I created code that had an optional parameter. If the parameter was used, then the procedure would use the parameter instead of fetching data from the database. It is quite easy to set up a user defined type that has the same field types as a row of data and to pass that to a function. I now have a way of getting test data into the procedure that I want to test. Inside each procedure there was some code that swapped out the real data source for the test data source. This allowed me to use unit testing on a wider variety of function, using my own unit testing functions. Writing unit test is easy, it is just repetitive and boring. In the end, I gave up with unit tests and started using a different approach.
I write in-house applications for myself mainly so I can afford wait till issues find me rather than having to have perfect code. If I do write applications for customers, generally the customer is not fully aware of how much software development costs so I need a low cost way of getting results. Writing unit tests is all about writing a test that pushes bad data at a procedure to see if the procedure can handle it appropriately. Unit tests also confirm that good data is handled appropriately. My current approach is based on writing input validation into every procedure within an application and raising a success flag when the code has completed successfully. Each calling procedure checks for the success flag before using the result. If an issue occurs, it is reported by way of an error message. Each function has a success flag, a return value, an error message, a comment and an origin. A user defined type (fr for function return) contains the data members. Any given function many populate only some of the data members in the user defined type. When a function is run, it usually returns success = true and a return value and sometimes a comment. If a function fails, it returns success = false and an error message. If a chain of functions fails, the error messages are daisy changed but the result is actually a lot more readable that a normal stack trace. The origins are also chained so I know where the issue occurred. The application rarely crashes and accurately reports any issues. The result is a hell of a lot better than standard error handling.
Public Function GetOutputFolder(OutputFolder As eOutputFolder) As FunctRet
'///Returns a full path when provided with a target folder alias. e.g. 'temp' folder
Dim fr As FunctRet
Select Case OutputFolder
Case 1
fr.Rtn = "C:\Temp\"
fr.Success = True
Case 2
fr.Rtn = TrailingSlash(Application.CurrentProject.path)
fr.Success = True
Case 3
fr.EM = "Can't set custom paths – not yet implemented"
Case Else
fr.EM = "Unrecognised output destination requested"
End Select
exitproc:
GetOutputFolder = fr
End Function
Code explained.
eOutputFolder is a user defined Enum as below
Public Enum eOutputFolder
eDefaultDirectory = 1
eAppPath = 2
eCustomPath = 3
End Enum
I am using Enum for passing parameters to functions as this creates a limited set of known choices that a function can accept. Enums also provide intellisense when entering parameters into functions. I suppose they provide a rudimentary interface for a function.
'Type FunctRet is used as a generic means of reporting function returns
Public Type FunctRet
Success As Long 'Boolean flag for success, boolean not used to avoid nulls
Rtn As Variant 'Return Value
EM As String 'Error message
Cmt As String 'Comments
Origin As String 'Originating procedure/function
End Type
A user defined type such as a FunctRet also provides code completion which helps. Within the procedure, I usually store internal results to an anonymous internal variable (fr) before assigning the results to the return variable (GetOutputFolder). This makes renaming procedures very easy as only the top and bottom have be changed.
So in summary, I have developed a framework with ms-access that covers all operations that involve VBA. The testing is permanently written into the procedures, rather than a development time unit test. In practice, the code still runs very fast. I am very careful to optimise lower level functions that can be called ten thousand times a minute. Furthermore, I can use the code in production as it is being developed. If an error occurs, it is user friendly and the source and reason for the error are usually obvious. Errors are reported from the calling form, not from some module in the business layer, which is an important principal of application design. Furthermore, I don't have the burden of maintaining unit testing code, which is really important when I am evolving a design rather than coding a clearly conceptualised design.
There are some potential issues. The testing is not automated and new bad code is only detected when the application is run. The code does not look like standard VBA code (it is usually shorter). Still, the approach has some advantages. It is far better that using an error handler just to log an error as the users will usually contact me and give me a meaningful error message. It can also handle procedures that work with external data. JavaScript reminds me of VBA, I wonder why JavaScript is the land of frameworks and VBA in ms-access is not.
A few days after writing this post, I found an article on The CodeProject that comes close to what I have written above. The article compares and contrasts exception handling and error handling. What I have suggested above is akin to exception handling.
I have not tried this, but you could attempt to publish your access forms as data access web pages to something like sharepoint or just as web pages and then use an tool such as selenium to drive the browser with a suite of tests.
Obviously this is not as ideal as driving the code directly through unit tests, but it may get you part of the way. good luck
Access is a COM application. Use COM, not Windows API. to test things in Access.
The best Test environment for an Access Application is Access. All of your Forms/Reports/Tables/Code/Queries are available, there is a scripting language similar to MS Test (Ok, you probably don't remember MS Test), there is database environment for holding your test scripts and test results, and the skills you build here are transferable to your application.
Data Access Pages have been deprecated by MS for quite some time, and never really worked in the first place (they were dependent on the Office Widgets being installed, and worked only in IE, and only badly then).
It is true that Access controls that can get focus only have a window handle when they have the focus (and those that can't get focus, such as labels, never have a window handle at all). This makes Access singularly inappropriate to window handle-driven testing regimes.
Indeed, I question why you want to do this kind of testing in Access. It sounds to me like your basic Extreme Programming dogma, and not all of the principles and practices of XP can be adapted to work with Access applications -- square peg, round hole.
So, step back and ask yourself what you're trying to accomplish and consider that you may need to utilize completely different methods than those that are based on the approaches that just can't work in Access.
Or whether that kind of automated testing is valid at all or even useful with an Access application.

Resources