Code: Accessing Windows Clipboard
Several years ago, I found some code to access the Win32 functions for the Windows Clipboard. I don't remember where I found it, who wrote it or if it was VB code that I modified or already written for Lotusscript. I rewrote the the code as a class and put it in a script library called "Class.Win32.ClipBoard". The complete code is listed below. In my next blog entry I will describe how I am using this class for some very convenient functions.
Option Public
Option Declare
Declare Private Function GetClipboardData Lib "User32" (Byval wFormat As Long) As Long
Declare Private Function SetClipboardData Lib "user32" (Byval wFormat As Long, Byval hData As Long) As Long
Declare Private Function OpenClipboard Lib "User32" Alias "OpenClipboard" (Byval hwnd As Long) As Long
Declare Private Function CloseClipboard Lib "User32" Alias "CloseClipboard" () As Long
Declare Private Function GlobalLock Lib "kernel32" Alias "GlobalLock" (Byval hMem As Long) As Long
Declare Private Function GlobalUnlock Lib "kernel32" Alias "GlobalUnlock" (Byval hMem As Long) As Long
Declare Private Function GlobalAlloc Lib "kernel32" (Byval wFlags As Long, Byval dwBytes As Long) As Long
Declare Private Function GlobalFree Lib "kernel32" (Byval hMem As Long) As Long
Declare Private Function EmptyClipboard Lib "user32" () As Long
Declare Private Function lstrcpyLP2Str Lib "kernel32" Alias "lstrcpyA" (Byval lpString1 As String, _
Byval lpString2 As Long) As Long
Declare Private Function lstrlenLP Lib "kernel32" Alias "lstrlenA" (Byval lpString As Long) As Long
Declare Private Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (Byval strDest As Any, _
Byval lpSource As Any, Byval Length As Any)
Declare Private Function GetFocus Lib "User32" Alias "GetFocus" () As Long
Private Const CF_TEXT = 1
Private Const GMEM_MOVABLE = &H2&
Private Const GMEM_DDESHARE = &H2000&
Class WindowsClipboard
Public Property Get Contents As String
Dim hClipboard As Long
Dim LpStrl As Long
Dim Resultl As Long
Dim Clipboardstr As String
If (OpenClipboard(0&) <> 0) Then
hClipboard = GetClipboardData(CF_TEXT)
If (hClipboard <> 0) Then
LpStrl = GlobalLock(hClipboard)
Clipboardstr = Space$(lstrlenLP(LpStrl))
Resultl = lstrcpyLP2Str(Clipboardstr, LpStrl)
GlobalUnlock(hClipboard)
Else
Clipboardstr = "NULL"
End If
Call CloseClipboard()
&nbs
p; Else
Clipboardstr = ""
End If
Contents = Clipboardstr
End Property ' Ends the "Get" method for the "Contents" property
Public Property Set Contents As String
Dim lSize As Long
Dim hMem As Long
Dim pMemory As Long
Dim temp As Variant
lSize = Len(Contents)+1
hMem = GlobalAlloc(GMEM_MOVABLE Or GMEM_DDESHARE, lSize)
If hMem = 0 Or Isnull(hMem) Then Exit Property
pMemory = GlobalLock(hMem)
If pMemory = 0 Or Isnull(pMemory) Then
GlobalFree(hMem)
Exit Property
End If
Call MoveMemory(pMemory, Contents, lSize)
Call GlobalUnlock(hMem)
If (OpenClipboard(0&) <> 0) Then
If (EmptyClipboard() <> 0) Then
temp = SetClipboardData(CF_TEXT, hMem)
End If
temp = CloseClipboard()
End If
GlobalFree(hMem)
End Property ' Ends the "Set" method for the "Contents" property
End Class
14 Years Ago…
This Sunday it was 14 years ago I stepped onto an airplane in Stockholm, Sweden. In the morning hourse of January 1, 1998, my dad and my sister took me to the airport for my move to the US. I had married Angie back in August, and soon after that she told me she wanted to move back to the US. I applied for and got a resident visa (green card) and got a job with IDG in Boston. Initially we had planned to move to Seattle, but when I was offered the job in Boston I took that one instead.
Some 18 hours later I landed in Spokane, WA, where Angie met me. She had been staying with her mom, who lived in northern Idaho, while I got our appartment in Sweden packed up in 13 moving boxes (that is what I could afford to ship) and I got everything finished at my previous job at IDG in Sweden. My last task was to build an editorial system in Lotus Notes 4.6, that I built in 3 weeks. By the way, this system is still in use today, having survived several attempts to have it replaced with different other publishing systems.
After four days of driving cross-country from Idaho to Boston, we got an apartment and I started working as a full-time Lotus Notes developer on January 7.
Much have happened since. In 2000 we had our son Erik, in May 2002 we moved to Texas and I started working at Deep South as a Sr. Lotus Notes developer, in late summer of 2002 we moved into our first house, and then in July 2003 Angie and I separated and finally divorced aftyer 6 years of marriage.
It is interesting that I now have been working with Lotus Notes almost three times the time I was married, despite all the claims I have seen for many years that "Notes is Dead"…
As always, it will be exciting to see what the coming year brings me, both on a personal and professional level. Perhaps I will finally get the time to learn Xpages and/or Java?
Free Tool: Clean your NAB
As a follow-up to my previous tool that let you analyze the ACL of a database, I built another tool for my admin. For different reasons, we need to keep the mailbox of terminated users, sometimes for a shorter time but sometimes for long periods of time. As far as I understand it, if a traditional approach was used to remove a user from the system, the mail file would also be deleted. So the admin put the terminated user in the Deny Access group and change the ACL of the mailfile to include a manager, supervisor or replacement.
But because of this process, AdminP will not remove the terminated user from all the groups he/she is listed in. When you have hundreds of groups, many of them nested, this could be a real headache. So I was asked to build something simple that allows us to remove one or more specified users from all groups in the Domino Directory. Below is the result. Enjoy!
Update: I tweaked the code slightly, to avoid three separate calls to GetItemValue() and to make a line shorter. The modified code is in the end, where I update the deletelog list.
First I created a form with 3 fields:
‘SaveOptions’ has a default value of “0” (to prevent the form from being saved).
‘Users’ is a Names field, getting it’s values using the addresses dialog. The field is multi-value and using New Line as separator.
‘LogResult’ is a multi-value text field, again with New Line as separator.
Finally I added a button to the action bar to remove the user(s). The Lotusscript code is listed below. It is using my class for mail notifications that I blogged about in November, to send a confirmation to the user running the agent. This is useful for example when you need to log all data changes done to a system.
Use "Class.MailNotification" Sub Click(Source As Button) Dim ws As New NotesUIWorkspace Dim uidoc As NotesUIDocument Dim session As New NotesSession Dim nab As NotesDatabase Dim view As NotesView Dim doc As NotesDocument Dim members As Variant Dim newmembers List As String Dim delmembers List As String Dim users As Variant Dim userlist List As String Dim user As NotesName Dim nmcnt As Integer Dim newarray() As String Dim ret As Integer Dim removelog List As String Dim userarr As Variant Dim mail As NotesMail Dim mailtext As String Dim listname as String Dim updated As Boolean ' *** Make sure the operator is sure ret = Msgbox("Are you sure?",4+32+256,"WARNING") If ret = 7 Then Exit Sub End If ' *** Get a list of users in field 'Users' Set uidoc = ws.CurrentDocument users = Split(uidoc.FieldGetText("Users"),Chr$(13)) Forall u In users Set user = New NotesName(u) userlist(Fulltrim(user.Common)) = Fulltrim(user.Common) End Forall ' *** Get all groups in NAB and process them one by one Set nab = New NotesDatabase(session.CurrentDatabase.Server,"names.nsf") Set view = nab.GetView("Groups") Set doc = view.GetFirstDocument Do While Not doc Is Nothing Print "Processing " & doc.GetItemValue("Listname")(0) Erase newmembers Erase delmembers updated = False ' *** Get members in the group and create a list of the ones to keep members = doc.GetItemValue("Members") nmcnt = 0 Forall m In members Set user = New NotesName(m) If Iselement(userlist(Fulltrim(user.Common))) = False Then ' User is not among the ones to delete newmembers(Fulltrim(user.Common)) = Fulltrim(m) nmcnt = nmcnt + 1 Else delmembers(Fulltrim(user.Common)) = Fulltrim(m) updated = True End If End Forall ' *** Build array of members to keep Redim newarray(nmcnt) As String nmcnt = 0 Forall nm In newmembers newarray(nmcnt) = nm nmcnt = nmcnt + 1 End Forall ' *** Write array of new members back to document and save it If updated = True Then Call doc.ReplaceItemValue("Members", Fulltrim(newarray)) Call doc.Save(True,False) listname = doc.GetItemValue("Listname")(0) Print "Updating " & listname End If Forall dm In delmembers removelog(listname) = removelog(listname) & dm & ";" End Forall Set doc = view.GetNextDocument(doc) Loop ' *** We are all done mailtext = "" Forall rl In removelog Call uidoc.FieldAppendText("LogResult", "Group '" & Listtag(rl) & "':" & Chr$(10)) mailtext = mailtext & "Group '" & Listtag(rl) & "':" & Chr$(10) userarr = Split(Cstr(rl),";") Forall u In userarr Set user = New NotesName(u) Call uidoc.FieldAppendText("LogResult", user.Common & Chr$(10)) mailtext = mailtext & user.Common & Chr$(10) End Forall ' Call uidoc.FieldAppendText("LogResult", Chr$(10)) End Forall Set mail = New NotesMail() mail.MailTo = session.CommonUserName mail.Subject = "[Notification] - Users removed from NAB" Call mail.AppendText(mailtext) mail.Principal = "IT Programs" Call mail.Send() Msgbox "Done removing specified user(s) from Domino Directory.",64,"Finished" End Sub
Norwegian TV pwned
The news in the Norwegian TV channel NRK recently talked about testing the vision for elderly, the illustration for the test was picked from teh Internet. Obviously nobody looked closer at it before the broadcast…
I am sure most readers of this blog understand what's so funny… The news anchors did not.
Ben Langhinrichs does it again (for the 12th time): LS Session database
For the 12th year, Ben Langhinrichs at Genii Software has created his brilliant Lotusphere Session database.
You can download it here. There are also mobile version in the works from Tim Davis at The Turtle Partnership.
Why my next phone will not be a Blackberry
I like my Blackberry. I currently have a Blackberry Bold 9700, and I am happy with many things. But it is almost 2 years old and it is time to look for a replacement. And after almost 4 years of being a Blackberry user, my next phone will probably be something else.
But why? The Bold has been praised multiple times by people like Vowe. The screen is excellent. It has a real keyboard, and most people (including me) think that is the best physical keyboard on the market. Well, there are several reasons.
I have upgraded the operating system from version 6 5 to version 7 6. However, the new version is not as good as the older one. Some functions are very nice, but at the same time, it uses more memory, and I suspect it got memory leaks left and right. I constantly run out of memory when loading webpages, and I see the clock/hourglass icon way too often. It also locks up frequently, sometimes just for a few seconds, sometimes so hard I have to pull the battery. Talking about pulling the battery, Blackberry users are used to do that in a nearly daily basis, especially if you want to clear the memory. But there is an app for that.
This brings us to another big issue with Blackberry. Apps. Or rather the lack of apps. Yes, there are a number of apps in the Blackberry App World. But if you look at different websites (everything from news to specialized services), chances are that they have an app for iPhone and one for Android. But very few places have an app for Blackberry. Next time you visit a website that offers apps, see what your options are. One app I use all the time is SocialScope, previously only available for Blackberry. But now there is an Android version available as well.
Other issues with Blackberry 7 6 is that the UI looks old. There were no big changes between version 6 5 and 7 6, the icons still looks boring. In BB5 there were a number of themes one could download, and also a tool that let you customize your screen/look (if you were a graphics designer). There are themes for BB6 as well, but my favourite themes did not work after the upgrade and have not been updated.
Then we have posts like this one by Darren Duke and this one by Vowe. The question is if RIM will still be around for the next two years, if I get a new phone from them now…
So it is a combination of all this, together with a wish to get a bigger screen and better performance that makes me consider a different smartphone platform. Personally I do not like being locked in to the Apple eco system. I don't have an iPod, but a MP3 player which can be connected as any other USB device, and my files (in many formats, like mp3, flac, wmv, avi, divx and mpg) can simply be dragged over. I want that for my phone too, to have the freedom to do what I want, use the files I already have.
So my next phone will most probably be an Android. I have been looking at (and reading about) the Google Nexus, but there are a few things that I don't like with it, most of all the lack of expansion (no DS-card support). Samsung Galaxy S II Skyrocket is interesting, as is the new LG Nitro. But I want the latest version of Android, Ice Cream Sandwich, which is still not available on very many phones. So it will be very interestingto see what the lineup looks like after New Years…
Update: I had the wrong OS numbers, it has now been corrected.
Script Library Issue
For a product that just turned 21, one would expect that all childhood issues were gone. Of course, I am (for now) stuck with a 17 year old slightly temperamental teenager… Anyway, I just ran into a strange issue, related to classes and code inherited from other templates.
Template A contains a script library with a class I wrote, called "AgentLog". It is used to log information about agents running in different databases. So obviously the script library need to reside in each database where I want to do the logging. Let’s say there are two, based on Template B and C. In an attempt to be clever, I simply copied the script library from Template A to Template B and C, and answered "yes" on the question if I wanted to inherit changes when the code in Template A is changed.
I then add some calls to the script library in template B and C, and push the changes out. Everything works great. A week or two later, I decide that I want to add some more info to the logging. I add one public string variable to the class, add a reference to it in one method called Terminate() and add a new method called AddMessage(). The new code is marked in red.
Class AgentLog
Private session As NotesSession
Private logdb As NotesDatabase
Private logdoc As NotesDocument
Private running As Integer
Private tstart As Long ‘ Ticks at start
Private tend As Long ‘ Ticks at end
Private tps As Long ‘ Ticks per second (normally 1000)
Private message As String ‘ Text to store in log entry, e.g. number of docs processed
Public Sub AddMessage(txt As String)
Me.message = Me.message + txt
End Sub
Public Sub Terminate()
Dim seconds As Integer
If logdoc Is Nothing Then
Exit Sub
End If
tend = Getthreadinfo( THREAD_TICKS )
seconds = (tend – tstart) / tps
Call logdoc.ReplaceItemValue("EndTime", Now() )
Call logdoc.ReplaceItemValue("Seconds", seconds )
Call logdoc.ReplaceItemValue("Message", message )
If running = True Then ‘ Check if not terminated gracefully
Call logdoc.ReplaceItemValue("Terminated", "Yes" )
End If
Call logdoc.Save(True,True)
End Sub
Nothing special, right? I did not even modify New() at all. All this was done in anticipation of adding the additional logging later in Application B and C (based on the templates B and C). The updated Template A was put in production, and the next night the update process refreshed the design of Template B and C with the new code, and they in turn updated Application B and C.
Now the users suddenly got an error message when code using the script library was launched: "Type mismatch on external name: AGENTLOG". I tested it and got the same error. I decided to turn on the debugger to see where exactly it happened. And of course then it worked! Debugger turned off, error again. I recompiled all Lotusscript in the application, and it worked with no debugger on. I then recompiled all code in Template A, refreshed design of Template B and C and recompiled all code in Template B and C. Despite this, the next day we had the same problem…
Finally I ended up not linking the script library from Template A to Template B and C. I will just have to copy it over again every time I make a change to every template where I use it. Anyone got any ideas what the problem might be?.
Re: Using Lotusscript lists to increase performance
In response to: Using Lotusscript lists to increase performance
I now modified the code to use NotesViewEntry.ColumnValues() to get the value I am looking for. I also had to add a column displaying that field in my view.
The code actually took longer, 370 seconds, but all that was in the calls to doc.Save(). The actual lookup went from a total of 15 seconds down to just below 5 seconds.
So let’s remove the time-consuming doc.Save() command, just to see what the actual execution time is. Here is the result for my four versions of the same import agent:
Agent | Time |
---|---|
Individual lookups for each entry imported | 370 sec |
Building list of documents using view lookup/doc.GetItemValue() before importing | 55 sec |
Building list of documents at first use, using view lookup/doc.GetItemValue() | 54 sec |
Building list of documents at first use, using viewentry lookup/entry.ColumnValues() | 36 sec |
By removing the print statement that give feedback about the progress (printing every 100 entries), additional 3 seconds would be saved.
So there is a substantial performance benefit when you don’t have to open the actual document and read a value, but can use a view column value instead. If you need multiple values from one document, one easy way is to combine all values into one column, separated with a character that you will never enjounter in the data (I am partial to the ~ character, it also makes it easy to read) and then use Split() to get the individual values.
Trend Micro: Google is most insecure
According to Trend Micro's Third Quarter Threat Report, Google had 82 vulnerabilities during the third quarter 2011. Oracle had 63, followed by Microsoft with 58, Apple with 49, Adobe with 42 and Mozilla and IBM both with 39 during the same period.
In other news: University of Dayton and General Motors are moving from Lotus Notes to Google Mail, while the CIO at the City of Los Angeles demands that Groupwise "be hauled out of its coffin, given a quick slap and defibrillation, and pressed back into service" to replace Google Mail and Apps. Link to letter here, another article here.
According to Consumer Watchdog, the rollout [in Los Angeles] has hit snags, with the police deciding that security on the vaunted platform isn’t adequate for users in the City Attorney Criminal Branch, fire department arson investigations, public safety users, parking police, street services investigations, parks rangers, and “any other City entities that access criminal history data”.