Sometimes the only user interface you need is a message box. Maybe you just need to alert the user to the completion of a process; or maybe you need to ask a question to which the user responds Yes or No. You'd rather not resort to a console window; but neither do you want to load an entire GUI library to display a simple message box. "Dave", you say, "there must be a better way."
Indeed, there is. Use the DL library to call the MessageBoxA Windows API function.
First, require the dl library:
require 'dl'
We'll feed the function our message text, a dialog box title, and an integer that defines what buttons to display. Let's define some meaningful constants to represent the possible button values:
BUTTONS_OK = 0
BUTTONS_OKCANCEL = 1
BUTTONS_ABORTRETRYIGNORE = 2
BUTTONS_YESNO = 4
The function will return an integer representing the button that the user clicked, so let's define some meaningful constants to represent the possible return code values:
CLICKED_OK = 1
CLICKED_CANCEL = 2
CLICKED_ABORT = 3
CLICKED_RETRY = 4
CLICKED_IGNORE = 5
CLICKED_YES = 6
CLICKED_NO = 7
Here's our method to call the MessageBoxA function from the user32 DLL:
def message_box(txt='', title='', buttons=0)
user32 = DL.dlopen('user32')
msgbox = user32['MessageBoxA', 'ILSSI']
r, rs = msgbox.call(0, txt, title, buttons)
return r
end
Here's how we call it display a message box with an OK button:
message_box("Hello, World!", "Hi!", BUTTONS_OK)
Here's how we call it to display a message box with 'Yes' and 'No' buttons, and process the response:
response = message_box("Are you sure you want to proceed?", "Proceed?", BUTTONS_YESNO)
if response == CLICKED_YES
# insert your code here
end
Finally, here it is in its entirety:
require 'dl'
# button constants
BUTTONS_OK = 0
BUTTONS_OKCANCEL = 1
BUTTONS_ABORTRETRYIGNORE = 2
BUTTONS_YESNO = 4
# return code constants
CLICKED_OK = 1
CLICKED_CANCEL = 2
CLICKED_ABORT = 3
CLICKED_RETRY = 4
CLICKED_IGNORE = 5
CLICKED_YES = 6
CLICKED_NO = 7
def message_box(txt, title=APP_TITLE, buttons=BUTTONS_OK)
user32 = DL.dlopen('user32')
msgbox = user32['MessageBoxA', 'ILSSI']
r, rs = msgbox.call(0, txt, title, buttons)
return r
end
response = message_box("Are you sure you want to proceed?", "Proceed?", BUTTONS_YESNO)
if response == CLICKED_YES
# insert your code here
end
There you have it. As always, let me know if you have questions, comments, or suggestions.
Thanks for stopping by!
16 comments:
Hi David,
excellent blog, very interesting. That's stuff you don't find easily elsewhere. I'm glad I stepped by.
Congratulations and keep on!
Marco
Thanks, Marco!
"That's stuff you don't find easily elsewhere. I'm glad I stepped by."
Then I have done my job. I want this blog to be a valuable resource for Ruby developers, and to demonstrate the immense value that Ruby offers for the Windows platform.
David
Yo David!!
Excellent... You've given me a path to a solution to what I was trying to do. Keep'em coming.
Thanks a lot
Vijay
Anyone know how to monitor Mass Storage Insert events (eg. When someone inserts a USB drive) from Ruby?
@Anko: You probably won't get a very detailed account, but just know that much of that stuff that is very `Windowsy' will require you to use something like Win32 OLE (dave has written up something here).
In particular, if you did some stuff in those schweet days of Classic ASP, that stuff can almost all happen in Ruby with Win32 OLE stuff. Like sending mail, et cetera. So, if you find a solution based on Win32 OLE, you can translate well. I think.
Plus, thanks, Dave, for this one. I am a bit too Unix, for one, and Win32 is scary territory, but I guess it will hurt less with you around. :-)
@Anko:
I have just posted a new article related to your question here. I hope that helps.
David
Hi David, I have never done any windows programming, so I have no prior knowledge of windows dll's. Any sites I have found for the Win API is for C/C++, which I have no experience in. Do you have any suggestion for sites to go to or books to get that will explain all the dll's available and how to use them in Ruby?
Great, just what I needed.
Only one disadvantage: there's no icon on the message box. To use the Windows standard icons add the following code:
--------8<--------------
ICON_HAND = 16
ICON_QUESTION = 32
ICON_EXCLAMATION = 48
ICON_ASTERISK = 64
def message_box(txt='', title='', buttons=0, icon=0)
user32 = DL.dlopen('user32')
msgbox = user32['MessageBoxA', 'ILSSI']
r, rs = msgbox.call(0, txt, title, buttons+icon)
return r
end
--------8<--------------
Use it like that:
response = message_box("Are you sure you want to proceed?", "Proceed?", BUTTONS_YESNO, ICON_QUESTION)
Great stuff! Please update the site!
Something I observe when scripting a spreadsheet program which is *not* MS-Excel. I hope, though, it is the same there:
The method Sheets() of the workbook-object works differently, when parameterized with a String, that starts with a "cypher", i.e.
'02_2008'. I am unable to activate a sheet with such a name until I prefix it with another character, like this: 'M02_2008'.
The same works flawlessly in VB. I cannot but assume, that something special to Win32OLE is responsible.
TY anyway. ;-)
Hi, thanks for the excellent info.
One problem I have though is that the dialog box can quite easily get lost, simply because the user is doing something else while the script is running the background and their window takes focus over the dialog.
Do you know of a way to make thie Ruby dialog have properties such as always on top etc.?
I know in the Win32 API there is a owner window, so could it possible be hooked into that?
One other thought is that I am calling the script from a C# application. Is it possible to use the Window Handle of that in the Ruby script?
Thanks
Lee
Excellent post! I've been looking for a simple way to do this. Never thought of using the windows API. Glad I found this!
Thank you sooooo much for this code snippet. It was a simple cut an paste for me!!! If you don't mind me asking, how did you know about this library? It is italized on the Ruby Standard Library Documentation meaning it is not documented. This is a skill I would like to have.
Thank you,
Bobby Washington
For Ruby 1.9 this produces an error:
message_box.rb:20:in `[]': wrong number of arguments(2 for 1) (ArgumentError)
from message_box.rb:20:in `message_box'
from message_box.rb:25:in `'
The solution is to replace lines 20 & 21 by:
msgbox = DL::CFunc.new(user32['MessageBoxA'], DL::TYPE_LONG, 'MessageBox')
r, rs = msgbox.call([0, txt, title, 0].pack('L!ppL!').unpack('L!*'))
Source: http://www.ruby-forum.com/topic/138277
OK
so i'm really new to ruby here
stand on the shoulders of giants
but i'd like to suggest:
r, rs = msgbox.call([0, txt, title, buttons].pack('L!ppL!').unpack('L!*'))
instead....the "buttons" variable was accidentally left as 0
there is a gem for this:
http://www.rubydoc.info/gems/messagebox/0.1.0
Post a Comment