Jump to content

Please review my usage of Scripting Dictionaries


Mbee
 Share

Recommended Posts

Hello, wise and my ever-so-kind advisers!

I have two monitors, and one of them is my HDTV. Sometimes one of them will be dedicated to something I don't want interrupted, such as when I'm watching TV and working on the computer at the same time (for one example). So I want to "block" one of the monitors from new windows.

So I'm developing a script that will detect when a new application -or- popup window has been opened on the blocked monitor, and automatically move it to the non-blocked one. I've spent weeks trying to figure out how to distinguish a main application window from every other type of window, and after reading on sites like Stack Overflow I've learned that this is nearly impossible, and it would take an expert in Windows 10 internals to even attempt it.

This is fortunate, because I've thought of a new and much simpler approach: Simply assume that all the windows and pop-ups currently displayed on the blocked monitor are deliberately left there, and then watch for any new windows or pop-ups that open there. And instead of trying to determine if the new windows are main application windows, I'll look to see if it's ancestors include explorer.exe and if so, assume it's an app window. In the case where it's not a descendant of explorer, I'll traverse all the windows associated with the same PID and look for the window with the largest area and assume that's an application window.

In comparison, pop-ups are much easier.

All I have to do is to first build a record of all the current windows and pop-ups on the blocked monitor, then keep looping and checking to see if there are any windows in subsequent loops that were not in the previous list.

I was initially going to use arrays, but then the issue of fast comparisons came up, so I looked at all the various threads on the subject and came away convinced that the fastest and simplest was to use scripting dictionary objects.

The only problem is that the documentation of them that I've been able to find are, shall we say, inadequate -- and quite confusing (at least to dummies like me). So I thought I'd post the main part of the code that uses them and ask for corrections and suggested alternatives. Note that I didn't feel it at all necessary to post the entire thing with all the functions and declarations, therefore it won't run as is. But since I'm not asking for troubleshooting and instead just advice, I don't believe a runnable script was necessary.

So here we go!

 

$G_MoveTblCount = 0

$G_NumPrevWins = 0
$G_NumPrevPopups = 0

While True

    Global $G_CurWinAra = _WinAPI_EnumWindows( True, Default )              ; Get list of ALL visible windows
    $G_NumCurWins = $G_CurWinAra[0][0]

    Global $G_CurPopupAra = _WinAPI_EnumWindowsPopup()                      ; Get list of all pop-up windows
    $G_NumCurPopups = $G_CurPopupAra[0][0]
    $G_NumCurPopups = UBound( $G_CurPopupAra )

    If $G_NumPrevWins > 0 Then      ; If we've been through this loop more than once, we'll have something to compare against

        If $L_CurWinDictExists Then
            $L_CurWinDict.RemoveAll
            $L_CurWinDict.Init                                  ; ? Does RemoveAll and Imit have the same effect?
        Else
            $L_CurWinDict = ObjCreate("Scripting.Dictionary")
            $L_CurWinDictExists = True
        EndIf
        If $L_PrevWinDictExists Then
            $L_PrevWinDict.RemoveAll
            $L_PrevWinDict.Init
        Else
            $L_PrevWinDict = ObjCreate("Scripting.Dictionary")
            $L_PrevWinDictExists = True
        EndIf


        For $i = 1 To $G_NumCurWins -1
            $L_CurWinDict.ADD(Hex($G_CurWinAra[$i][0]), $G_CurWinAra[$i][1])    ; I'm unclear on the second parameter
            If @error <> 0 Then
                Local $pig = 11                 ; These are just something I can set the debugger to break upon
            EndIf
        Next

        For $i = 1 To $G_NumPrevwins -1
            $L_PrevWinDict.ADD(Hex($G_PrevWinAra[$i][0]), $G_CurWinAra[$i][1])
            If @error <> 0 Then
                Local $cat = 11
            EndIf
        Next

        For $i = 1 To $G_NumCurWins -1
            $L_CurWinHdl = $G_CurWinAra[$i][0]
            $L_CurHexWinHdl = Hex( $L_CurWinHdl )
            If Not $L_PrevWinDict.Exists( $L_CurHexWinHdl ) Then

                If __IsExplorerWindow( $G_CurWinAra[$G_MainWinIdx][0], $G_CurWinAra[$G_MainWinIdx][1] ) Then
                    $G_CurWinIsExplorer = True
                Else
                    $G_CurWinIsExplorer = False
                EndIf

                $L_Stat = __MoveNewWin( $L_CurWinHdl, $G_CurWinAra[$G_MainWinIdx][1], $G_CurWinIsExplorer )

            EndIf

        Next

    EndIf

    If $G_NumPrevWins = 0 Then
        Global $G_PrevWinAra = $G_CurWinAra
        $G_NumPrevWins = $G_NumCurWins
    EndIf

    If $G_NumPrevPopups > 0 Then

        If $L_CurPopDictExists Then
            $L_CurPopDict.RemoveAll
            $L_CurPopDict.Init
        Else
            $L_CurPopDict = ObjCreate("Scripting.Dictionary")
            $L_CurPopDictExists = True
        EndIf

        If $L_PrevPopDictExists Then
            $L_PrevPopDict.RemoveAll
            $L_PrevPopDict.Init
        Else
            $L_PrevPopDict = ObjCreate("Scripting.Dictionary")
            $L_PrevPopDictExists = True
        EndIf


        For $i = 1 To $G_NumPrevPopups -1
            $L_PrevPopupWinHdl = $G_PrevPopupAra[$i][0]
            $L_PrevHexPopupWinHdl = Hex( $L_PrevPopupWinHdl )
            $L_PrevPopDict.ADD($L_PrevHexPopupWinHdl, $G_PrevPopupAra[$i][1])
            If @error <> 0 Then
                Local $catpop = 11
            EndIf
        Next

        For $i = 1 To $G_NumCurPopups -1
            $L_CurPopupWinHdl = $G_CurPopupAra[$i][0]
            $L_CurHexPopupWinHdl = Hex($L_CurPopupWinHdl)
            $L_CurPopDict.ADD($L_CurHexPopupWinHdl, $G_CurPopupAra[$i][1])
            If @error <> 0 Then
                Local $pigpop = 11
            EndIf
        Next

        For $i = 1 To $G_NumCurPopups -1
            $L_CurPopupWinHdl = $G_CurPopupAra[$i][0]
            $L_CurHexPopupWinHdl = Hex($L_CurPopupWinHdl)
            If Not $L_PrevWinDict.Exists( $L_CurHexPopupWinHdl ) Then
                $L_Stat =__MoveNewPopup( $L_CurPopupWinHdl )
            EndIf
        Next

    EndIf

    If $G_NumPrevPopups = 0 Then
        Global $G_PrevPopupAra = $G_CurPopupAra
        $G_NumPrevPopups = $G_NumCurPopups
    EndIf

    Sleep( 1000 )

WEnd

Exit

Thank you very much for your time!

Link to comment
Share on other sites

My 2 cents worth:

There doesn't seem to be an Init method according to https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/dictionary-object

Create the Dictionary object before you start the loop. Once created just fill and empty dictionary as needed.

$L_CurWinDict = ObjCreate("Scripting.Dictionary")
$L_PrevWinDict = ObjCreate("Scripting.Dictionary")
While True
    Global $G_CurWinAra = _WinAPI_EnumWindows( True, Default )              ; Get list of ALL visible windows
    $G_NumCurWins = $G_CurWinAra[0][0]

    Global $G_CurPopupAra = _WinAPI_EnumWindowsPopup()                      ; Get list of all pop-up windows
    $G_NumCurPopups = $G_CurPopupAra[0][0]
    $G_NumCurPopups = UBound( $G_CurPopupAra )

    If $G_NumPrevWins > 0 Then      ; If we've been through this loop more than once, we'll have something to compare against

        $L_CurWinDict.RemoveAll
        $L_PrevWinDict.RemoveAll
        
 ...

 

My UDFs and Tutorials:

Spoiler

UDFs:
Active Directory (NEW 2022-02-19 - Version 1.6.1.0) - Download - General Help & Support - Example Scripts - Wiki
ExcelChart (2017-07-21 - Version 0.4.0.1) - Download - General Help & Support - Example Scripts
OutlookEX (2021-11-16 - Version 1.7.0.0) - Download - General Help & Support - Example Scripts - Wiki
OutlookEX_GUI (2021-04-13 - Version 1.4.0.0) - Download
Outlook Tools (2019-07-22 - Version 0.6.0.0) - Download - General Help & Support - Wiki
PowerPoint (2021-08-31 - Version 1.5.0.0) - Download - General Help & Support - Example Scripts - Wiki
Task Scheduler (NEW 2022-07-28 - Version 1.6.0.1) - Download - General Help & Support - Wiki

Standard UDFs:
Excel - Example Scripts - Wiki
Word - Wiki

Tutorials:
ADO - Wiki
WebDriver - Wiki

 

Link to comment
Share on other sites

You can also use the new AutoIt Map datatype supported by the beta. All quirks have been fixed and Maps work better than scripting dictionaries:

#include <Array.au3>

Local $m[]      ; $m is a Map
$m[0x12345678] = "This 32-bit key works"
$m[0x1234567812345678] = "But does this 64-bit key work?"       ; yes!
_ArrayDisplay(MapKeys($m))

Local $d = ObjCreate("Scripting.Dictionary")
$d.Add(0x12345678, "This 32-bit key works")
$d.Add(0x1234567812345678, "But does this 64-bit key work?")    ; no!

 

This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Link to comment
Share on other sites

  • 2 weeks later...

Thank you most kindly, @jchd !  I apologize for the delay in responding...

I certainly wish to stay up to date, so Maps it is -- I hope!  And I wouldn't have known to travel that route without your always valuable contributions.

But I'm unsure on a few points. First, the data structure I need to use will contain some entries that have the same key but different values (i.e., different key/value combinations).  But since the MapExists() function doesn't allow one to test for the existence of such combos (or at least it doesn't look like it to me), am I going to be able to use Maps after all? I cannot conceive of a way to combine both entities into a single key, since I'll have no idea what possible data values might already exist without looking up the key first! (There's a hole in that bucket...)

Is there a solution to this dilemma using Maps that you know of?

But for now, let's assume there is a solution. Yet because one mustn't duplicate a key if it already exists, do I assume correctly that one has to test using MapExists() first, and if it doesn't exist, what then? Do I simply perform an assignment, or should I use MapAppend()? I can see that MapAppend() is equivalent to ReDim-ming an array and adding a new element, but since these aren't arrays, doesn't an assignment add another element anyway, just like append?

In other words, I fail to grasp when to append and when to simply assign.

Also, do you know if there's a simple way to delete all elements in a Map without looping through them all? If not, that's not a problem, but I'd just like to know. 

Two more questions: (1)L Can I simply copy one Map to another with a single assignment statement like we can with an array? (never mind, that's easy to test).

(2): The keys I will be using are window handles. Should I leave them as integers, or convert them into hex strings first?

If there's someone or somewhere else I should ask these questions (technical subforum?), please let me know.

Thank you enormously once again, sir!

 

Link to comment
Share on other sites

I should have thought longer about my first question, since there's an obvious solution! 

I don't need to have duplicate keys with different values (which probably aren't allowed anyway). All I have to do is to create a compound, delimited string with however many values I need to associate with a specific key (separated by @LFs, for example). Problem solved!

I'd still like your take on the rest, though...

Link to comment
Share on other sites

"Associative arrays" types like Maps, Scripting-dictionaries and whatever they're called in other languages don't cope with dup keys.

If you have to store distinct pairs having the same key, say {keyX, valueA} and {keyX, valueB} I'd propose storing {keyX, arrayV} with arrayV being and array of values for keyA, [valueA, valueB]. Alternatively you can also store multiple values for a given key in a nested Map, with keys 1, 2, 3, ...

This way you can still store any datatype value verbatim without fear it could be destroyed by converting it to string. E.g. you can store objects this way, something you can't by using conversion to string.

This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Link to comment
Share on other sites

Thanks again! I had described in my post just above yours that I figured out that I absolutely do not need duplicate keys, since I can store as much info in the value as I need. Since all value data will be strings anyway, I was planning on building compound strings separated by life feeds and modify them as needed.

But now that I've read your final sentence, I'll simply store them as arrays to obviate the need for using StringSplit(). And to change these arrays' sizes to hold more or less data, I'll just Redim them as needed.

But I'm still wondering if there are faster/easier ways to empty a Map rather than just looping through and using MapRemove() on every element. Maybe I can declare an empty Map and simply assign it to the other Map I want to erase?

If you have any thoughts on such issues, I'd be eager to read them. But it's about time I start experimenting anyway...

Link to comment
Share on other sites

Here's an example (based on nested Maps) you can play with. The function vd() produces a variable dump as the output shows.

Local $m[]      ; $m is a Map
$m["abc"] = "This"
$m["def"] = "That"
$m["key"] = 111
vd($m)      ; till now, everything was added explicitely

; now use a function to insert new value(s) to possibly already existing keys
_Add2Map($m, "ghi", "One more value")       ; key not existing
vd($m)

; several values to existing key
_Add2Map($m, "key", 123)
_Add2Map($m, "key", 456)
_Add2Map($m, "key", 789)
vd($m)

; same value to existing key
_Add2Map($m, "key", 456)

vd($m)

Func _Add2Map(ByRef $map, $key, $value)
    If MapExists($map, $key) Then       ; same as If $m[$key] = Null Then
        If IsMap($map[$key]) Then
            MapAppend($map[$key], $value)
        Else
            Local $m2[]     ; an empty map
            $m2[1] = $map[$key]
            $m2[2] = $value
            $map[$key] = $m2
        EndIf
    Else
        $map[$key] = $value
    EndIf
EndFunc
Map[3]
      ['abc']                => String (4)         'This'
      ['def']                => String (4)         'That'
      ['key']                => Int32              111

Map[4]
      ['abc']                => String (4)         'This'
      ['def']                => String (4)         'That'
      ['key']                => Int32              111
      ['ghi']                => String (14)        'One more value'

Map[4]
      ['abc']                => String (4)         'This'
      ['def']                => String (4)         'That'
      ['key']                => Map[4]
            [1]                    => Int32              111
            [2]                    => Int32              123
            [3]                    => Int32              456
            [4]                    => Int32              789
      ['ghi']                => String (14)        'One more value'

Map[4]
      ['abc']                => String (4)         'This'
      ['def']                => String (4)         'That'
      ['key']                => Map[5]
            [1]                    => Int32              111
            [2]                    => Int32              123
            [3]                    => Int32              456
            [4]                    => Int32              789
            [5]                    => Int32              456
      ['ghi']                => String (14)        'One more value'

Now I don't really get what/when you'll need to remove elements and on which criterion. Anyway, the keys of nested maps in this example are positive increasing integers in insert order. Removing any entry in a Map is faster than removing a row in an array.

This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Link to comment
Share on other sites

  • 2 weeks later...

Well, I've invested considerable effort in getting Maps to work for me, and regrettably I've failed. All the Maps work in my rather basic code for everything other than simply retrieving the data value associated with a key, which always seems to return zero.  I'll post the code that reproduces this behavior if asked, but I'm kinda fed up for the moment...

My question now is: Given the problem I'm having with Maps, what alternative should I use? Scripting dictionaries as in my OP, or perhaps Nutster's Associative Array UDF?

Thanks.

Link to comment
Share on other sites

Post your Map "failing code" for us to fix.

This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Link to comment
Share on other sites

Boy, I had just gotten through apologizing because I was unable to duplicate the problem all of a sudden, and I was extremely embarrassed!!

But just now I realized that the working version was a scripting dictionary version rather than the maps version, so I have to get my excrement in order again. Sorry. I might not have anything to post for a while...

Edited by Mbee
Major screwup
Link to comment
Share on other sites

Hi, @jchd 

Fortunately, it turns out that Map data retrievals are working perfectly!  Woo hoo! 😊

I think I may have discovered why I mistakenly thought that Map data value retrievals were returning zeros: I was using the graphical debugger in Release mode. Happily, that debugger also has a Beta mode, and when I engaged that, the problem disappeared.

Thanks again for your patience and assistance.

Link to comment
Share on other sites

Good.

This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...