kasiapoleszak 05-28-2008, 06:08 AM Hiya all
Is there any way to retrieve and later restore the settings on the "Find and Replace" dialogue? I'm using the Find function in my code and it overwrites the settings in the dialogue.
I don't think there is a straightforward way to do this, but there must be a workaround, there always is!
Thanks in advance
Kasia
Colin Legg 05-28-2008, 07:41 AM Hiya all
Is there any way to retrieve and later restore the settings on the "Find and Replace" dialogue? I'm using the Find function in my code and it overwrites the settings in the dialogue.
I don't think there is a straightforward way to do this, but there must be a workaround, there always is!
Thanks in advance
Kasia
Hi Kasia,
You're right, there isn't a straightforward way to do this!
I think your easiest option would be just to reset the dialogue box to the default settings, as shown at the bottom of this link:
http://www.thecodenet.com/articles.php?id=18
Would that be satisfactory for you?
Colin
kasiapoleszak 05-28-2008, 10:21 AM Thanks Colin, this seems like an easy way to reinstate the settings.
But I still would like to find a way of reading them before I use the Find funciton. Wouldn't it be possible to somehow invoke the dialogue and read off the values from the controls?
Kasia
Colin Legg 05-28-2008, 10:48 AM Hello Kasia,
Possible? Yes I would think so.
Invoking the dialog box is straightforward:
Dim cb As CommandBarButton
'invoke the find and replace dialog
Set cb = Application.CommandBars.FindControl(ID:=1849)
cb.Execute
But I think you would need to use API calls to read the values from the controls. Is it really worth it?
Colin
A really convoluted and terrible idea:
Get .FindNext.Value (e.g. "abc")
Create variations of that value (e.g. "ABC", "a", "b", "c", "zabc", "abcd")
Put those variations in various places
.FindNext over those places and collect the variations that are picked up
Use complex conditional logic to infer combination of settings that gives you those variations but no others.
See, told you it was terrible. :p
Having said that - if one were interested in a single specific setting, it would be quite workable. Still very much a hack, though.
Colin Legg 05-29-2008, 02:17 AM That's a good bit of thinking, Cas. :cool:
A question though.... I can't think how one could use it to determine the value in the "What" paramater? It's overwritten as soon as you enter something to search for.... :(
I think that, since the settings are reset everytime the Excel instance is closed, either idea is a lot of effort compared with the straightforward 'reset default settings' possiblility.
I can't think how one could use it to determine the value in the "What" paramater? It's overwritten as soon as you enter something to search for.... :(
Right, that's why your code needs to do all this before ever using .Find, so that the .FindNext calls inplicitly refer to the last value entered by the user.
I think that, since the settings are reset everytime the Excel instance is closed, either idea is a lot of effort compared with the straightforward 'reset default settings' possiblility.
Yeah, no doubt. :rolleyes:
Colin Legg 05-29-2008, 06:24 AM Right, that's why your code needs to do all this before ever using .Find, so that the .FindNext calls inplicitly refer to the last value entered by the user.
Yes I followed that point previously.... but by using FindNext, to ever know what you are actually searching for, you would have to be lucky enough to have that value in a cell to be found - and within the confines of the other parameters? And what if the wildcard * is being used? I don't see how that can be done?
Good points. If the search doesn't yield any results in the workbook, one would have to create new sheets and fill them with string permutations, and then search that. Which limits the complexity of the value to about 5 characters even under ideal conditions (26^n vs Sheet.Cells.Count).
As to wildcards, that's not quite as bad, one should be able to get at them by stepwise refinement:
"abc" -> match
"ab" -> no match
"bc" -> no match
"adc" -> match
"addc" -> match
more test to verify "a" and "c" as opposed to "?"
-> What = "a*c"
As I said, the required conditional logic is anything but straightforward.
ETA: Just realized that things are really indistinguishable at a certain point - there's no way to tell the difference between searching for "*a*" with "match entire cell contents" in effect and "a" without. Sorry, took me a while... :)
Colin Legg 05-29-2008, 07:26 AM I was hoping you had an ace up your sleeve! ;)
It was a very shrewd idea though.... :)
Timbo 05-29-2008, 09:29 AM It's all very frustrating; somewhere in Excel, the list of previously searched for values are being stored, as evidenced by the dialog's drop-down list. I doubt it's stored elsewhere, as Colin pointed out, the list is cleared after Excel is closed.
I spent too long just now Googling the topic for clues, but nothing surrendered as yet, but I'm hopeful!
This would make a great "challenge" ;)
Brute-force proof-of-concept - finds search key assuming standard settings (case-insensitive, match parts), where key can be up to 3 chars long and may contain both letters and the "?" wildcard.
In other words, it should cover 99.99% of all searches... ;)
Needs a sheet codenamed sheetFind. You can change nMaxLen to 4 or 5, but it gets painfully slow after 3.
Option Explicit
Private Const nMaxLen As Long = 3
Private Const nMaxCol As Long = 2 ^ 8
Private Const nMaxRow As Long = ((27 ^ nMaxLen) \ nMaxCol) + 1
Private strSheet(1 To nMaxRow, 1 To nMaxCol) As String
Public Sub reconstructKey()
Call fillSheet
'simulate manual search
Call sheetFind.Cells.Find("a?z")
Dim allMatches As Collection
Set allMatches = findAll
Debug.Print buildKey(allMatches, minLen(allMatches))
End Sub
Private Function buildKey(whichMatches As Collection, whichLen As Long) As String
Dim strChars() As String: ReDim strChars(1 To whichLen)
Dim nChar As Long, strChar As String
For nChar = 1 To whichLen
strChars(nChar) = "_"
Next nChar
Dim varMatch As Variant
For Each varMatch In whichMatches
If VBA.Len(varMatch) = whichLen Then
For nChar = 1 To whichLen
strChar = VBA.Mid$(varMatch, nChar, 1)
Select Case strChars(nChar)
Case "_": strChars(nChar) = strChar
Case strChar: 'no change
Case Else: strChars(nChar) = "?"
End Select
Next nChar
End If
Next
buildKey = VBA.Join(strChars, "")
End Function
Private Function minLen(whichMatches As Collection) As Long
Dim nMinLen As Long: nMinLen = nMaxLen
Dim varMatch As Variant
For Each varMatch In whichMatches
If VBA.Len(varMatch) < nMinLen Then nMinLen = VBA.Len(varMatch)
Next
minLen = nMinLen
End Function
Private Function findAll() As Collection
Dim allMatches As Collection: Set allMatches = New Collection
Dim firstMatch As Range, thisMatch As Range
With Range(sheetFind.Rows(1), sheetFind.Rows(nMaxRow))
Set firstMatch = .FindNext
Set thisMatch = firstMatch
Do
Set thisMatch = .FindNext(thisMatch)
Call allMatches.Add(thisMatch.Value)
Loop Until thisMatch.Address = firstMatch.Address
End With
Set findAll = allMatches
End Function
Private Sub fillSheet()
Dim nTotal As Long: nTotal = 0
Dim nLen As Long
For nLen = 1 To nMaxLen
nTotal = buildText("", nLen, nTotal)
Next nLen
Dim oldXLC As XlCalculation: oldXLC = Application.Calculation
Application.Calculation = xlCalculationManual
With Range(sheetFind.Rows(1), sheetFind.Rows(nMaxRow))
.Clear
.Value = strSheet
End With
Application.Calculation = oldXLC
End Sub
Private Function buildText(whichText As String, whichLen As Long, whichTotal As Long) As Long
Dim nTotal As Long: nTotal = whichTotal
Dim doPlace As Boolean
If VBA.Len(whichText) = (whichLen - 1) Then doPlace = True _
Else doPlace = False
Dim nChar As Long, strText As String
For nChar = VBA.Asc("a") To VBA.Asc("z")
strText = whichText & VBA.Chr$(nChar)
If doPlace Then nTotal = placeText(strText, nTotal) _
Else nTotal = buildText(strText, whichLen, nTotal)
Next nChar
buildText = nTotal
End Function
Private Function placeText(whichText As String, whichTotal As Long) As Long
strSheet(((whichTotal \ nMaxCol) + 1), ((whichTotal Mod nMaxCol) + 1)) = whichText
placeText = whichTotal + 1
End Function
Colin Legg 05-30-2008, 04:12 AM It's all very frustrating; somewhere in Excel, the list of previously searched for values are being stored, as evidenced by the dialog's drop-down list. I doubt it's stored elsewhere, as Colin pointed out, the list is cleared after Excel is closed.
I spent too long just now Googling the topic for clues, but nothing surrendered as yet, but I'm hopeful!
This would make a great "challenge" ;)
Indeed it would! :)
My googling didn't turn up any new information - just a number of threads in various forums containing discussions very similar to this one, eg:
http://www.ozgrid.com/forum/showthread.php?t=90763
I had a good look through the object browser and I can't see any available application object properties which look like possible contenders. :(
In terms of the API approach, I used the recursive procedure by Mark here to get some handle/class/caption information from the find+replace dialog window:
http://www.vbaexpress.com/kb/getarticle.php?kb_id=52
This is what it gave:
12518008 EDTBX Łaa
10617496 EDTBX N/A
5112478 XLTFRCLASS N/A 3867098 SysListView32 N/A 9568710 SysHeader32 N/A
4719238 XLTFSTATCLASS N/A 2493646 msctls_statusbar32 0 cell(s) found
Timbo 05-30-2008, 04:46 AM Ooh... we're going API on it now are we? :cool:
Not sure what Mark's code did, but Spy++ shows the 'Find and Replace' window classname to be "bosa_sdm_XL9", but Google shows this is probably a generic window superclass. However we can build a 'Office.CommandBarButton' event sink to launch a timer proc which captures the window the millisecond it appears.
After that, just find its combobox child window (classname "EDTBX") and let 'SendMessage' work its magic:
http://vbnet.mvps.org/index.html?code/listapi/listcopy.htm
Should be quite straight-forward, but a bit of a hack and I can't help thinking we've missed a trick. Could the values in the dialog drop-down be hidden somewhere in the registry?...
Race you to code the above ;)
Colin Legg 05-30-2008, 04:51 AM I am very much an API n00b... so you'll win hands down!!
The information I retrieved using Mark's procedure was returned by feeding in the handle of the find+replace dialog window which I determined using the FindWindow API function, as in:
Public Sub GetWindows()
Dim cb As CommandBarButton
Dim hWnd As Long
x = 0
winOutputType.winHandle = 0
winOutputType.winClass = 1
winOutputType.winTitle = 2
winOutputType.winHandleClass = 3
winOutputType.winHandleTitle = 4
winOutputType.winHandleClassTitle = 5
'invoke the find and replace dialog
Set cb = Application.CommandBars.FindControl(ID:=1849)
cb.Execute
Set cb = Nothing
hWnd = FindWindow(vbNullString, "Find and Replace")
GetWinInfo hWnd, 0, winOutputType.winHandleClassTitle
End Sub
My attention was drawn to the SysListView Classname? So I don't think that this is quite as straightforward as one might hope?
Could the values in the dialog drop-down be hidden somewhere in the registry?
I think that's unlikely, the point of putting things into the registry is to make them persistent, but as you've said these values aren't restored when the app is re-launched.
Timbo 05-30-2008, 07:25 AM Ok, you got me - I just couldn't articulate where Excel might actually be storing the list ;)
However, I've attached a fun toy for everyone to play with :)
I should say I refer to it as a "toy" because it doesn't capture the previously serached-for text if the Find/Replace dialog was invoked using the "CTRL+F" hotkey. Otherwise, it's fine!
Realised I'd left some redundant stuff in there! Dealt with now :)
Colin Legg 05-30-2008, 11:34 AM Very nice work Timbo! :cool:
A simple thought.... to handle the CTRL+F, how about including this?
ThisWorkbook Class Module
Private Sub Workbook_Open()
Application.OnKey "^f", "LaunchFindReplace"
Application.OnKey "^F", "LaunchFindReplace"
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Application.OnKey "^f"
Application.OnKey "^F"
End Sub
basFindReplaceHistory Module
Sub LaunchFindReplace()
Application.CommandBars.FindControl(ID:=1849).Execute
End Sub
Timbo 05-31-2008, 10:48 AM Thanks! I suppose the thing to do really is to throw it all into an Addin, then it can all be intialized at startup, including the hotkey overrides you've shown there :)
Colin Legg 06-01-2008, 08:42 AM Not sure what Mark's code did, but Spy++ shows the 'Find and Replace' window classname to be "bosa_sdm_XL9", but Google shows this is probably a generic window superclass. However we can build a 'Office.CommandBarButton' event sink to launch a timer proc which captures the window the millisecond it appears.
After that, just find its combobox child window (classname "EDTBX") and let 'SendMessage' work its magic:
http://vbnet.mvps.org/index.html?code/listapi/listcopy.htm
Hi Timbo,
Being a bit of an API n00b, the part of this challenge that stumped me was the sequence of observations and logic you used here. Using the code I posted, I was unable to see the combobox child window: can you explain what I was seeing (what is actually happening) and how you deducted this workaround in order to retrieve the information that we actually wanted?
Many thanks,
Colin
Timbo 06-02-2008, 02:58 AM Until I have time to run Mark's code myself, I can't be sure what was being picked up, but I can tell you that the "bosa_sdm_XL9" window class is a generic Windows dialog, and there were several open while I was trying to identify the 'Find and Replace' dialog. In the end I discovered I was looking in the wrong place as it was a child of the Windows desktop (the parent of all windows) and not "XLMAIN", suggesting it runs in another thread altogether.
If you can get your hands on SPY++ or similar this will all probably make more sense :)
http://www.dailysofts.com/program/709/15840/WinID.html
|