xsel for windows equivalent API or command line? - windows

Is there an API or command line utility that returns the currently selected text from either active window or even globally, like the linux utility "xsel" ?
I don't mind getting less than 100% success.
i know that each window can have its own text selection, but it's negligible for now.
For now the only solution/workaround i have is to sendkeys "ctrl+c" and read from clipboard, but that's bad solution for 2 obvious reasons.
I know how to do it on MS-Word, but that's 1% of the cases.
thanks
edit
From this discussion i learn that there are too many technologies to select text. so i'll fall back to using clipboard. thanks anyway.
i'm leaving this question open for a while in case somebody have a miracle.

this is a solution i compiled from several sources.
it will work only on simple text controls. for the other types of text there are other solutions, but for simplicity i'll use the clipboard.
(for a complete code, declarations and dependencies, google for "SendMessage hWndCaret")
If hWndCaret <> 0 Then
'first, get all text
nLength = SendMessage(hWndCaret, WM_GETTEXTLENGTH, 0&, ByVal 0&)
If nLength <> 0 Then
buff = Space$(nLength + 1)
res = SendMessage(hWndCaret, WM_GETTEXT, nLength + 1, ByVal buff)
If res <> 0 Then Txt = Left$(buff, res)
End If
' then
If nLength <> 0 Then
buff = Space$(nLength + 1)
res = SendMessage(hWndCaret, EM_GETSEL, VarPtr(StartPos), EndPos)
selection = Mid(Txt, StartPos + 1, EndPos - StartPos)
End If
End If

Check if the program supports accessible interface like IAccessible/IAccClientDocMgr or TextPattern_GetSelection/TextRange_GetText. Many software need to implement accessible to sell to the US government because of the Americans with Disabilities Act. You can call AccessibleObjectFromWindow or AutomationElement::FromHandle on a window.
It looks like nobody documents their accessible object tree, and if there is an API exists, then the API is the preferred way to get information from the program. E.g. you should use Q249232 to get IHTMLDocumnent2 if the application is IE. There are significant changes in IE7 and IE8's assessible tree when inspected in UI spy.
For other programs you may be out of luck. I cannot find the selection in the login email edit when using UISpy. Accessiblility depends on the good will of programmers to implement accessibility in their programs.

Related

Odd problem. Step through works, runtime doesn't

Morning all.
I've seen numerous posts about this problem on here, but nothing specific to my situation so would appreciate some assistance with this.
Essentially, I'm attempting to rearrange the order of tabpages on a tabcontrol, based on the order of entries in a listbox (lbxBuildings). The number of pages always matches the number of listbox entries, and their text values also match up.
Now I've written the following code which works perfectly when I step through it, but doesn't work (or error) at runtime.
Private Sub cmdBlgSetDown_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdBlgSetDown.Click
'Make sure our item is not the last one on the list.
If lbxBuildings.SelectedIndex < lbxBuildings.Items.Count - 1 Then
'Insert places items above the index you supply, since we want
'to move it down the list we have to do + 2
Dim I = lbxBuildings.SelectedIndex + 2
Dim j As Integer = lbxBuildings.SelectedIndex
Dim NTP As TabPage = TabControl2.TabPages(j)
TabControl2.TabPages.Insert(I, NTP)
TabControl2.SelectedIndex = I - 1
TabControl2.TabPages.Remove(NTP)
lbxBuildings.Items.Insert(I, lbxBuildings.SelectedItem)
lbxBuildings.Items.RemoveAt(lbxBuildings.SelectedIndex)
lbxBuildings.SelectedIndex = I - 1
End If
End Sub
The rearranging of the listbox is controlled by two buttons (one up and one down) and that all works fine, so I now want the same buttons to rearrange the tabpages as well. There may well be information already entered on these pages prior to their being rearranged, so deleting one and adding a new one won't work for me (as far as I can tell).
My 'solution' of adding in a copy in the correct place then deleting the original makes perfect sense to me and as I say, it works when I step through. But at runtime, it seems to skip right over the 'insert' line and just deletes the orignal tab.
All suggestions welcome.
Many thanks!
Well after a load more digging I found mention of this specific problem on a C# site. More digging still and it turns out that there's a bug in the program with the following workaround.
Dim h As IntPtr = TabControl2.Handle
Call this 'before' doing anything with the tabcontrol and it works as expected. It's a bit odd but there it is.

Remove incompatible spaces from propertybag to solve error when saving form with ActiveX control

I am looking for a way to have VB6 edit a controls' propertybag right before or while the Form is being saved..
We're using an activeX control (AxisMediaControl) that seemingly uses Spaces in it's propertybag names which throws an error when trying to save the form the control is on.
System Error &H80070057 (-2147024809)
The same problem is described here:
Source of a similar problem, but i don't have *.h files ()
but since we use the compiled ActiveX i can't edit the way it handles writing properties, and we don't have access to the source of the ActiveX..
I've managed to create a workaround loading the control at runtime,as an Array of objects, but this way i wont have them indexed to capture events by index.
Dim Axis(1) As AxisMediaControl
For i% = 0 To 1
Set Axis(i%) = Controls.Add("AxisMediaControl.AxisMediaControl.1", "AMC" & i%)
Axis(i%).Tag = "CAM" + Format$(i%)
Next i%
' Play Video
With Axis(0)
.Top = 600
.Left = 240
.Width = 9135
.Height = 5535
.Visible = True
.StretchToFit = True
.MaintainAspectRatio = True
.EnableContextMenu = True
.MediaFile = VideoFile$
.play
End With
' Play Stream
With Axis(1)
.Top = 6840
.Left = 240
.Width = 9135
.Height = 5535
.Visible = True
.StretchToFit = True
.MaintainAspectRatio = True
.EnableContextMenu = True
.MediaFile = VideoStream$
.play
End With
The ActiveX is working fine with younger versions of Visual Basic like express 2005 or vb.net and only occurs under vb6.
Are there ways to 'edit' the way the properties are saved to the propertybag?
Can i edit the dll or ocx to behave differently and have a working up-to-date version of an ActiveX that was previously incompatible? I've tried using Resource Hacker on the dll files but to no avail since my knowledge there is limited..
Since you mentioned the control works in dotnet versions of VB, a workaround could be to write a wrapper in VB.NET, and then use that wrapper from VB6. This wrapper would provide a standard "ActiveX" (COM) interface and internally just call the original control. You would only need to expose the bare minimum of functionality in the wrapper, just to minimize the effort involved.
(Note - while this might work technically it may not be the best overall solution. For instance, if upgrading to a more modern control is possible that might be a better investment of time.)
Edit 1: A wrapper would impose some additional overhead, that is certain. But this overhead might not be important at all. If you only have to make a few calls for instance the runtime effect is probably irrelevant. OTOH if you have to call this thing within the innermost loop of some algorithm (doubtful for a UI control but...) that could be more of a problem.
Edit 2: If you don't already have any dotnet components in your app it might not be worth adding that dependency just for this issue. Or maybe it would be, if this truly is a showstopper; but that could be an additional consideration to think through carefully.

Handling of Automation errors in VB

EDIT#1
I am developing a VB6 EXE application intended to output some special graphics to the Adobe Illustrator.
The example code below draws the given figure in the Adobe Illustrator as a dashed polyline.
' Proconditions:
' ai_Doc As Illustrator.Document is an open AI document
' Point_Array represented as "array of array (0 to 1)" contains point coordinates
'
Private Sub Draw_AI_Path0(ByRef Point_Array As Variant)
Dim New_Path As Illustrator.PathItem
Set New_Path = ai_Doc.PathItems.Add
New_Path.SetEntirePath Point_Array
New_Path.Stroked = True
New_Path.StrokeDashes = Array(2, 1)
End Sub
This simple code, however, can raise a variety of run-time automation errors caused by:
Incorrect client code (for example, assigning a value other than Array to the
New_Path.StrokeDashes)
Incorrect client data (for example, passing too large Point_Array to New_Path.SetEntirePath)
Unavailability of some server functions (for example when the current layer of the AI is locked)
Unexpected server behavior
EDIT#2
Unfortunately, since such errors are raised by the server app (AI, in our case) their descriptions are often inadequate, poor and misleading. The error conditions may depend on AI version, installed apps, system resources etc. A single problem can lead to different errors. Example passing too large Point_Array to New_Path.SetEntirePath (Windows XP SP3, Adobe Illustrator CS3):
For array size of 32767 and above, the error is -2147024809 (&H80070057) "Illegal Argument"
For array size of 32000 to 32766, the error is -2147212801 (&H800421FF) "cannot insert more segments in path. 8191 is maximum"
END OF EDIT#2
The traditional error handling can be used to prevent the client crash and to display the error details as shown below:
Private Sub Draw_AI_Path1(ByRef Point_Array As Variant)
Dim New_Path As Illustrator.PathItem
On Error GoTo PROCESS_ERROR
Set New_Path = ai_Doc.PathItems.Add
New_Path.SetEntirePath Point_Array
New_Path.Stroked = True
New_Path.StrokeDashes = Array(2, 1)
Exit Sub
PROCESS_ERROR:
MsgBox "Failed somewhere in Draw_AI_Path1 (" & Format(Err.Number) & ")" _
& vbCrLf & Err.Description
End Sub
As you can see, the error number and error description can be accessed easily. However, I need to know also what call causes the error. This can be very useful for large and complex procedures containing many calls to the automation interface. So, I need to know:
What error happened?
What call caused it?
In what client function it happened?
Objective #3 can be satisfied by techniques described here. So, let’s focus on objectives #1 and 2. For now, I can see two ways to detect the failed call:
1) To “instrument” each call to the automation interface by hardcoding the description:
Private Sub Draw_AI_Path2(ByRef Point_Array As Variant)
Dim New_Path As Illustrator.PathItem
Dim Proc As String
On Error GoTo PROCESS_ERROR
Proc = "PathItems.Add"
Set New_Path = ai_Doc.PathItems.Add
Proc = "SetEntirePath"
New_Path.SetEntirePath Point_Array
Proc = "Stroked"
New_Path.Stroked = True
Proc = "StrokeDashes"
New_Path.StrokeDashes = Array(2, 1)
Exit Sub
PROCESS_ERROR:
MsgBox "Failed " & Proc & " in Draw_AI_Path2 (" & Format(Err.Number) & ")" _
& vbCrLf & Err.Description
End Sub
Weak points:
Code becomes larger and less readable
Incorrect cause can be specified due to copypasting
Strong points
Both objectives satisfied
Minimal processing speed impact
2) To “instrument” all calls together by designing a function that invokes any automation interface call:
Private Function Invoke( _
ByRef Obj As Object, ByVal Proc As String, ByVal CallType As VbCallType, _
ByVal Needs_Object_Return As Boolean, Optional ByRef Arg As Variant) _
As Variant
On Error GoTo PROCESS_ERROR
If (Needs_Object_Return) Then
If (Not IsMissing(Arg)) Then
Set Invoke = CallByName(Obj, Proc, CallType, Arg)
Else
Set Invoke = CallByName(Obj, Proc, CallType)
End If
Else
If (Not IsMissing(Arg)) Then
Invoke = CallByName(Obj, Proc, CallType, Arg)
Else
Invoke = CallByName(Obj, Proc, CallType)
End If
End If
Exit Function
PROCESS_ERROR:
MsgBox "Failed " & Proc & " in Draw_AI_Path3 (" & Format(Err.Number) & ")" _
& vbCrLf & Err.Description
If (Needs_Object_Return) Then
Set Invoke = Nothing
Else
Invoke = Empty
End If
End Function
Private Sub Draw_AI_Path3(ByRef Point_Array As Variant)
Dim Path_Items As Illustrator.PathItems
Dim New_Path As Illustrator.PathItem
Set Path_Items = Invoke(ai_Doc, "PathItems", VbGet, True)
Set New_Path = Invoke(Path_Items, "Add", VbMethod, True)
Call Invoke(New_Path, "SetEntirePath", VbMethod, False, Point_Array)
Call Invoke(New_Path, "Stroked", VbSet, False, True)
Call Invoke(New_Path, "StrokeDashes", VbSet, False, Array(2, 1))
End Sub
Weak points:
Objective #1 is not satisfied since Automation error 440 is always raised by CallByName
Need to split expressions like PathItems.Add
Significant (up to 3x) processing speed drop for some types of automation interface calls
Strong points
Compact and easy readable code with no repeated on error statements
Is there other ways of handling automation errors?
Is there a workaround for the Weak point #1 for 2)?
Can the given code be improved?
Any idea is appreciated! Thanks in advance!
Serge
Think of why it is you might want to know where an error has been raised from. One reason is for simple debugging purposes. Another, more important, reason is that you want to do something specific to handle specific errors when they occur.
The right solution for debugging really depends on the problem you're trying to solve. Simple Debug.Print statements might be all you need if this is a temporary bug hunt and you're working interactively. Your solution #1 is fine if you only have a few routines that you want granular error identification for, and you can tolerate having message boxes pop up. However, like you say, it's kind of tedious and error prone so it's a bad idea to make that into boilerplate or some kind of "standard practice".
But the real red flag here is your statement that you have "large and complex procedures containing many calls to the automation interface", plus a need to handle or at least track errors in a granular way. The solution to that is what it always is - break up your large and complex procedures into a set of simpler ones!
For example, you might have a routine that did something like:
Sub SetEntirePath(New_Path As Illustrator.PathItem, ByRef Point_Array As Variant)
On Error Goto EH
New_Path.SetEntirePath Point_Array
Exit Sub
EH:
'whatever you need to deal with "set entire path" errors
End Sub
You basically pull whatever would be line-by-line error handling in your large procedure into smaller, more-focused routines and call them. And you get the ability to "trace" your errors for free. (And if you have some kind of systematic tracing system such as the one I described here - https://stackoverflow.com/a/3792280/58845 - it fits right in.)
In fact, depending on your needs, you might wind up with a whole class just to "wrap" the methods of the library class you're using. This sort of thing is actually quite common when a library has an inconvenient interface for whatever reason.
What I would not do is your solution #2. That's basically warping your whole program just for the sake of finding out where errors occur. And I guarantee the "general purpose" Invoke will cause you problems later. You're much better off with something like:
Private Sub Draw_AI_Path4(ByRef Point_Array As Variant)
...
path_wrapper.SetEntirePath Point_Array
path_wrapper.Stroked = True
path_wrapper.StrokeDashes = Array(2, 1)
...
End Sub
I probably wouldn't bother with a wrapper class just for debugging purposes. Again, the point of any wrapper, if you use one, is to solve some problem with the library interface. But a wrapper also makes debugging easier.
One would run it in the VB6 debugger. If compiled without optimisation (you won't recognise your code if optimised) you can also get a stack trace from WinDbg or WER (use GFlags to set it up). HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug is where settings are stored.
You can also start in a debugger.
windbg or ntsd (ntsd is a console program and maybe installed). Both are also from Debugging Tools For Windows.
Download and install Debugging Tools for Windows
http://msdn.microsoft.com/en-us/windows/hardware/hh852363
Install the Windows SDK but just choose the debugging tools.
Create a folder called Symbols in C:\
Start Windbg. File menu - Symbol File Path and enter
srv*C:\symbols*http://msdl.microsoft.com/download/symbols
then
windbg -o -g -G c:\windows\system32\cmd.exe /k batfile.bat
You can press F12 to stop it and kb will show the call stack (g continues the program). If there's errors it will also stop and show them.
Type lm to list loaded modules, x *!* to list the symbols and bp symbolname to set a breakpoint
da displays the ascii data found at that address
dda displaysthe value of the pointer
kv 10 displays last 10 stack frames
lm list modules
x *!* list all functions in all modules
p Step
!sysinfo machineid
If programming in VB6 then this environmental variable link=/pdb:none stores the symbols in the dll rather than seperate files. Make sure you compile the program with No Optimisations and tick the box for Create Symbolic Debug Info. Both on the Compile tab in the Project's Properties.
Also CoClassSyms (microsoft.com/msj/0399/hood/hood0399.aspx) can make symbols from type libraries.

Confusing with Approaching If statements and Debugging

I'm extremely new to Visual Studio 2010.
For my class, I need to make a calculator. The requirement for this problem is that if A or B is zero, then for the program to not actually calculate A or B; instead, the program assigns the value of the variable that is not zero to eh result, and if they're both zero, then just make the answer zero.
Here is my code:
Public Class Form1
Private Sub AddBox_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles AddButton.Click
Dim A As Decimal
Dim B As Decimal
Dim Result As Decimal
A = ABox.Text
B = BBox.Text
A = Decimal.Parse(ABox.Text)
B = Decimal.Parse(BBox.Text)
If (ABox.Text = 0) Then
ResultLabel.Text = BBox.Text
End If
If (BBox.Text = 0) Then
ResultLabel.Text = ABox.Text
End If
If (BBox.Text = 0 And ABox.Text = 0) Then
ResultLabel.Text = 0
End If
Result = A + B
ResultLabel.Text = Result.ToString("N2")
End Sub
My questions are as follows:
Are if statements good for this, or would Try/Catch be better?
Since the answer will automatically be right, even if the code is incorrect (e.g. 9+0 will be nine regardless, whether or not the If or Try/Catch actually works), they key is proper step by step debugging. What's the optimal way to do this? I had the one menu displaying everything step by step with how the program functioned before, but for some reason, I can't seem to find the window after I closed it. I want to see it step by step as I debug with break points.
Any other tips on good syntax for these type of conditional operators?
Sorry for sounding stupid; I couldn't find the topics on here that would cover this type of problem.
OK...first off, you don't really need the Ifs at all. (Identity Property of Addition: For any x, x + 0 = x.) Any error that's going to happen is going to happen before you even get to them, so the only thing you've done is say that if ABox parses to zero and BBox's value is something like "5.000000", all those zeros will get copied into the result. Likewise if the boxes are reversed.
Also, if you use Decimal.TryParse instead of Decimal.Parse, un-number-like stuff in the input boxes won't kill your program. They'll just get turned into 0.
You'd get similar results (minus the extra zeros) with some code like
Private Sub AddBox_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles AddButton.Click
Dim A as Decimal, B as Decimal
Decimal.TryParse(ABox.Text, A)
Decimal.TryParse(BBox.Text, B)
ResultBox.Text = (A + B).ToString("N2")
End Sub
If for some stupid reason you really, really, really need all that If crap, you will want to use ElseIf to ensure that the default case doesn't run.
Private Sub AddBox_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles AddButton.Click
Dim A as Decimal
Dim B as Decimal
Decimal.TryParse(ABox.Text, A)
Decimal.TryParse(BBox.Text, B)
If A = 0 then
ResultBox.Text = BBox.Text
ElseIf B = 0 then
ResultBox.Text = ABox.Text
Else
Dim result as Decimal = A + B
ResultBox.Text = result.ToString("N2")
End If
End Sub
But it's a waste -- and what's worse, if the point is to add two numbers, it's incorrect. Someone could put "I Like Cheese" in the first box and "YAAAAAAAAY!!!" in the other, and the result would be "YAAAAAAAAY!!!". Clearly not what should happen.
Private Sub AddBox_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles AddButton.Click
Dim A as Decimal
Dim B as Decimal
Try
A = Decimal.Parse(ABox.Text)
B = Decimal.Parse(BBox.Text)
ResultBox.Text = (A + B).ToString("N2")
Catch ex as FormatException
MessageBox.Show("Exactly two numbers (one per box), please!")
End Try
End Sub
But i detest doing that for user input. You should expect users to be malicious and/or idiotic, and count on them doing everything they can to mess up your app. It'll save you lots of debugging later on. But if we assume the worst, is malformed input really an exceptional condition? I say no. (Others may disagree. But they're wrong. :) )
If you want to notify the user of an input error, Decimal.TryParse also returns a boolean (true/false) saying whether it succeeded. If it failed, you can show a message telling the user to quit being an idiot. (Slightly more diplomatically, if you want.)
Secondly...turn Option Strict on. Please. All this implicit casting gives me the willies. Note that that will probably add like a dozen errors to your list...but that's a good thing. One should be aware when they're trying to put a number in a string variable and so on, cause lots of magic happens there that (in my opinion) people need to be more aware of.
K, now, as for debugging...if you've followed my advice up to this point, you shouldn't really need to debug. The exceptions are gone, and the code's going to be more correct before you can even compile it. But if you still have to, one way to go step by step through the program is to set a breakpoint on either the Function line itself, or the first line that isn't a declaration. (Click the left margin next to the line. A red ball should appear in the margin about where you clicked. That lets you know there's a breakpoint there.) You can probably try to set a breakpoint on the declarations too, but i seem to remember that causing the breakpoint to be either on the next line that actually does something, or on the beginning of the function. I forget which, and don't have VS on this machine to check.)
When you run the program from VS, the program will start, and when it hits the breakpoint, VS will pop back into the foreground with a yellow arrow where the red ball is. That yellow arrow points at the next line to be executed. From there, you can use the debug toolbar (look in your toolbars; you should have extra buttons, blue i think, that look like play/stop/etc buttons) to step through the code. You should also see windows labeled 'Locals', 'Watches', and other windows you'd expect to see in a worthwhile debugger. :)
Just be aware that while the program's stopped at a breakpoint, or stepping through the code, the app will appear frozen. UI updates probably will not appear right away, and consequently, you won't be able to type anything in the input boxes or hit the button til you resume (hit the "play' button).

How do you make a MsgBox with a "Do Not Ask This Again" or "Don't Ask Me Again" Checkbox in VB6?

I ask this partly because I want to know the best practice way of doing this, and partly because the top google result I got was a forum thread from 2002 in which the question wasn't even answered.
I inherited some VB6 code, and there are some MsgBox calls in said code, many of which display messages that end users would probably find very annoying after a short time (e.g. "Printing Complete," "Record Added," etc.)
I would like to add the standard user interface control of a checkbox on the MsgBox saying "Don't ask me this again", so that when checked and OK is clicked, a setting is saved that lets the program know, you know...to never ask that again. Pretty standard control, the idea is fairly self-explanatory.
What I would like to know is what is the best practice way of doing this in VB6. There is the obvious way of just making a new form for these type of msgboxen and replacing the old MsgBox call with a .Show on that form, but do the VB6 gurus on Stack Overflow have a better way?
Thanks in advance
As far as I know, there is no other way. You need to make your own message box form with the check box. Of course, you'll also need to modify the code to store and retrieve this setting (and acting appropriately based on the setting).
I have done this in my own application many times. One thing to think about.... suppose the user checks the box "don't show me this again". In my opinion, there should be a way to reset the setting. Since the message box form won't show again, I added this to the configuration form (for my app).
One thing you may want to consider is sub-classing the MSGBOX function. You could create a function within your app that has a similar parameter list, but with a couple extra. If the extra parameters are missing, simply call vba.MsgBox (to get the standard behaviour). If you pass in extra parameters, you could call your new form instead.
Well... You are not absolutely correct guys ;)
Starting from Win2000, there's SHMessageBoxCheck function that does the trick.
VB6 declaration:
Private Declare Function SHMessageBoxCheck Lib "shlwapi" Alias "#185" (ByVal hWnd As Long, ByVal lpszText As String, ByVal lpszTitle As String, ByVal dwType As VbMsgBoxStyle, ByVal iDefault As Long, ByVal lpszId As String) As Long
For everything else follow the link :)
If you provide such a functionality, it might require "turning-on" of showing messagebox.
i.e. user should have an option to see the msgbox again using some setting.
Instead of that, you could use statusbar to display notifications OR have a label with the notification messages and turn it off after a few seconds.
This was my down and dirty solution:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
myfile = Workbooks.Application.ActiveWorkbook.Path & "\noprompt.txt"
If Dir(myfile) <> "" Then Exit Sub
exportData = MsgBox("Export data?" & vbCrLf & "Select Cancel (or × top right) to prevent this prompt from displaying again.", vbYesNoCancel, "Close Workbook")
If exportData = vbYes Then
Call ExportValues 'a separate function...
ElseIf exportData = vbCancel Then
'create file noprompt.txt
Open myfile For Output As #1
Write #1, "Delete this file to restore prompt to Export Data when workbook is closed."
Close #1
Exit Sub
ElseIf exportData = vbNo Then
Exit Sub
End If
End Sub
Documentation for my app explains that deleting the file restores the prompt.

Resources