• The Old New Thing

    News flash: Work-at-home job offers are mostly just scams

    • 8 Comments

    McClatchy Newspapers discovers, to everyone's surprise, that work-at-home job offers are mostly just scams. Of course, this is something Rob Cockerham discovered years ago. (He also has a rundown of all his articles on the subject, in case you haven't gotten enough.)

  • The Old New Thing

    Using the MNS_DRAGDROP style: Dragging out

    • 8 Comments

    Windows 2000 introduced the MNS_DRAG­DROP menu style, which permits drag/drop operations in a menu. Nobody uses this style, probably because it's totally undiscoverable by the end-user. But I'll write a sample program anyway.

    Mind you, I knew nothing about the MNS_DRAG­DROP menu style until I started writing this entry. But I simply read the documentation, which says that if you set this style, you will receive WM_MENU­DRAG and WM_MENU­GET­OBJECT messages. The WM_MENU­DRAG message is sent when the user drags a menu item, so let's go with that first. The documentation says that you get information about the item that was dragged, and then you return a code that specifies whether you want the menu to remain up or whether you want it torn down.

    Simple enough. Let's do it.

    Start with the scratch program, add the function Get­UI­Object­Of­File and the class CDrop­Source, and change the calls to Co­Initialize and Co­Uninitialize into Ole­Initialize and Ole­Uninitialize, respectively. Next, define the menu we're going to play with:

    // resource header file
    #define IDM_MAIN 1
    #define IDC_CLOCK 100
    
    // resource file
    IDM_MAIN MENU PRELOAD
    BEGIN
        POPUP "&Test"
        BEGIN
            MENUITEM "&Clock", IDC_CLOCK
        END
    END
    

    Now we can add some new code to our scratch program. First, we add a menu to our window and enable drag/drop on it:

    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
     MENUINFO mi = { sizeof(mi), MIM_STYLE, MNS_DRAGDROP };
     return SetMenuInfo(GetMenu(hwnd), &mi);
    }
    
    // InitApp
     // wc.lpszMenuName = NULL;
     wc.lpszMenuName = MAKEINTRESOURCE(IDM_MAIN);
    

    For both dragging and dropping, we need a way to obtain the COM object associated with a menu item, so I'll put them in this common helper function:

    HRESULT GetMenuObject(HWND hwnd, HMENU hmenu, UINT uPos,
                          REFIID riid, void **ppvOut)
    {
     HRESULT hr = E_NOTIMPL;
     *ppvOut = NULL;
     if (hmenu == GetSubMenu(GetMenu(hwnd), 0)) {
      switch (GetMenuItemID(hmenu, uPos)) {
      case IDC_CLOCK:
       hr = GetUIObjectOfFile(hwnd, L"C:\\Windows\\clock.avi",
                                                 riid, ppvOut);
       break;
      }
     }
     return hr;
    }
    

    If the menu is our "Test" popup menu, then we know how to map the menu items to COM objects. For now, we have only one item, namely Clock, which corresponds to the C:\Windows\clock.avi¹ file.

    Now we can hook up a handler to the WM_MENU­DRAG message:

    #define HANDLE_WM_MENUDRAG(hwnd, wParam, lParam, fn) \
     (fn)((hwnd), (UINT)(wParam), (HMENU)(lParam))
    
    LRESULT OnMenuDrag(HWND hwnd, UINT uPos, HMENU hmenu)
    {
     LRESULT lres = MND_CONTINUE;
     IDataObject *pdto;
     if (SUCCEEDED(GetMenuObject(hwnd, hmenu, uPos,
                                     IID_PPV_ARGS(&pdto)))) {
      IDropSource *pds = new(std::nothrow) CDropSource();
      if (pds) {
       DWORD dwEffect;
       if (DoDragDrop(pdto, pds, DROPEFFECT_COPY | DROPEFFECT_LINK,
                      &dwEffect) == DRAGDROP_S_DROP) {
        lres = MND_ENDMENU;
       }
       pds->Release();
      }
      pdto->Release();
     }
     return lres;
    }
    

    This function is where the magic happens, but it's really not all that magical. We get the data object for the menu item being dragged and tell OLE to do a drag/drop operation with it. Just to make things interesting, I'll say that the menu should be dismissed if the user dropped the object somewhere; otherwise, the menu remains on the screen.

    Finally, we hook up the message handler to our window procedure:

    HANDLE_MSG(hwnd, WM_MENUDRAG, OnMenuDrag);
    

    And there you have it. A program that calls up a menu with drag enabled. If you drag the item labeled Clock, then the drag/drop operation proceeds as if you were dragging the clock.avi file.

    Next time, we'll look at the drop half of drag and drop.

    Footnote

    ¹ I hard-coded the clock.avi file for old time's sake. Yes, I know the file is no longer included with Windows. That'll teach people to use hard-coded paths!

  • The Old New Thing

    Using the MNS_DRAGDROP style: Dropping in

    • 8 Comments

    Last time, we looked at using the MNS_DRAG­DROP style for dragging items out of a menu. Today, we'll look at dropping them in.

    Take the program from last time and make the following additions. First, let's add a second item to the menu.

    // resource header file
    #define IDM_MAIN 1
    #define IDC_CLOCK 100
    #define IDC_WMP 101
    
    // resource file
    IDM_MAIN MENU PRELOAD
    BEGIN
        POPUP "&Test"
        BEGIN
            MENUITEM "&Clock", IDC_CLOCK
            MENUITEM "&WMP", IDC_WMP
        END
    END
    
    // scratch.cpp
    HRESULT GetMenuObject(HWND hwnd, HMENU hmenu, UINT uPos,
                          REFIID riid, void **ppvOut)
    {
     HRESULT hr = E_NOTIMPL;
     *ppvOut = NULL;
     if (hmenu == GetSubMenu(GetMenu(hwnd), 0)) {
      switch (GetMenuItemID(hmenu, uPos)) {
      case IDC_CLOCK:
       hr = GetUIObjectOfFile(hwnd, L"C:\\Windows\\clock.avi",
                                                 riid, ppvOut);
       break;
      case IDC_WMP:
       hr = GetUIObjectOfFile(hwnd, L"C:\\Program Files"
                      L"\\Windows Media Player\\wmplayer.exe",
                                                 riid, ppvOut);
       break;
      }
     }
     return hr;
    }
    

    Yes, I hard-coded another path. This is a demo, not production code.

    Anyway, it's time to hook up the WM_MENU­GET­OBJECT message:

    #define HANDLE_WM_MENUGETOBJECT(hwnd, wParam, lParam, fn) \
     (fn)((hwnd), (MENUGETOBJECTINFO*)(lParam))
    
    LRESULT OnMenuGetObject(HWND hwnd, MENUGETOBJECTINFO *pmgoi)
    {
     LRESULT lres = MNGO_NOINTERFACE;
     if (!(pmgoi->dwFlags & (MNGOF_BOTTOMGAP | MNGOF_TOPGAP)) &&
         SUCCEEDED(GetMenuObject(hwnd, pmgoi->hmenu, pmgoi->uPos,
                   *(IID*)pmgoi->riid, &pmgoi->pvObj))) {
      lres = MNGO_NOERROR;
     }
     return lres;
    }
        HANDLE_MSG(hwnd, WM_MENUGETOBJECT, OnMenuGetObject);
    

    To handle the WM_MENU­GET­OBJECT message, you convert the hmenu, uPos pair into a COM object, requesting the interface provided by the riid member, and putting the result into the pvObj member. (Exercise: Why is the riid member typed as void * rather than REFIID?)

    When the user tries to drop on a menu item, we just give them the corresponding object in the shell namespace. Notice that I filter out the GAP messages, since they indicate that the user is trying to drop between items rather than on them.

    Run this program, open the Test menu, and drag the Clock menu item onto the WMP menu item. If all goes well (assuming you changed the path for clock.avi to some other AVI file), the AVI file will be opened by Windows Media Player, since that's the behavior of Windows Media Player when you drop an AVI file on it.

    So that's menu drag/drop. It's really not all that exciting. Of course, what people tend to be most interested in is not generic drag/drop for menus but menu customization via drag/drop. That's not something that MNS_DRAG­DROP gives you directly; that's something you need to build yourself out of the building blocks provided.

    We'll snap some blocks together next time.

  • The Old New Thing

    A big little program: Monitoring Internet Explorer and Explorer windows, part 2: Tracking navigations

    • 8 Comments

    Okay, it's been a while since we set aside our Little Program to learn a bit about connection points and using dispatch interfaces as connection point interfaces. Now we can put that knowledge to use.

    Internet Explorer and Explorer windows fire a group of events known as DWeb­Browser­Events, so we just need to listen on those events to follow the window as it navigates around.

    Take our scratch program and make these changes:

    #define UNICODE
    #define _UNICODE
    #define STRICT
    #define STRICT_TYPED_ITEMIDS
    #include <windows.h>
    #include <windowsx.h>
    #include <ole2.h>
    #include <commctrl.h>
    #include <shlwapi.h>
    
    #include <shlobj.h>
    #include <atlbase.h>
    #include <atlalloc.h>
    #include <exdisp.h>
    #include <exdispid.h>
    
    ...
    // DispInterfaceBase incorporated by reference
    
    void UpdateText(HWND hwnd, PCWSTR pszText);
    
    class CWebBrowserEventsSink :
        public CDispInterfaceBase<DWebBrowserEvents>
    
    public:
     CWebBrowserEventsSink(HWND hwnd) : m_hwnd(hwnd) { }
    
     IFACEMETHODIMP SimpleInvoke(
        DISPID dispid, DISPPARAMS *pdispparams, VARIANT *pvarResult)
     {
      switch (dispid) {
      case DISPID_NAVIGATECOMPLETE:
       UpdateText(m_hwnd, pdispparams->rgvarg[0].bstrVal);
       break;
    
      case DISPID_QUIT:
       UpdateText(m_hwnd, L"<exited>");
       Disconnect();
       break;
      }
      return S_OK;
     };
    
    private:
     HWND m_hwnd;
    };
    

    Our event sink class listens for DISPID_NAVIGATE­COMPLETE and DISPID_QUIT and updates the text with the new navigation location or the string L"<exited>" if the window exited. In the exit case, we also disconnect from the connection point to break the circular reference.

    The IDL file for Navigate­Complete says

    [id(DISPID_NAVIGATECOMPLETE), helpstring("...")]
    void NavigateComplete([in] BSTR URL );
    

    Therefore, we know that the URL parameter arrives as a VT_BSTR in position zero, so we can access it as pdispparams->rgvarg[0].bstrVal.

    That class is basically the guts of the program. The rest is scaffolding. Like hooking up this guy to a listview item so it can report its findings somewhere.

    struct ItemInfo
    {
     ItemInfo(HWND hwnd, IDispatch *pdisp)
      : hwnd(hwnd) {
      spSink.Attach(new(std::nothrow) CWebBrowsrEventsSink(hwnd));
      if (spSink) spSink->Connect(pdisp);
     }
     ~ItemInfo() { if (spSink) spSink->Disconnect(); }
    
     HWND hwnd;
     CComPtr<CWebBrowserEventsSink> spSink;
    };
    
    ItemInfo *GetItemByIndex(int iItem)
    {
     LVITEM item;
     item.mask = LVIF_PARAM;
     item.iItem = iItem;
     item.iSubItem = 0;
     item.lParam = 0;
     ListView_GetItem(g_hwndChild, &item);
     return reinterpret_cast<ItemInfo *>(item.lParam);
    }
    
    ItemInfo *GetItemByWindow(HWND hwnd, int *piItem)
    {
     int iItem = ListView_GetItemCount(g_hwndChild);
     while (--iItem >= 0) {
      ItemInfo *pii = GetItemByIndex(iItem);
      if (pii->hwnd == hwnd) {
       if (piItem) *piItem = iItem;
       return pii;
      }
     }
     return nullptr;
    }
    
    void UpdateText(HWND hwnd, PCWSTR pszText)
    {
     int iItem;
     if (GetItemByWindow(hwnd, &iItem)) {
      ListView_SetItemText(g_hwndChild, iItem, 0,
                           const_cast<PWSTR>(pszText));
     }
    }
    

    Attached to each listview item is an Item­Info structure which remembers the browser window it is associated with and the event sink that is listening for events.

    // GetLocationFromView, GetLocationFromBrowser, and GetBrowserInfo
    // incorporated by reference
    
    CComPtr<IShellWindows> g_spWindows;
    
    // rename DumpWindows to BuildWindowList
    HRESULT BuildWindowList()
    {
     CComPtr<IUnknown> spunkEnum;
     HRESULT hr = g_spWindows->_NewEnum(&spunkEnum);
     if (FAILED(hr)) return hr;
    
     CComQIPtr<IEnumVARIANT> spev(spunkEnum);
     for (CComVariant svar;
          spev->Next(1, &svar, nullptr) == S_OK;
          svar.Clear()) {
      if (svar.vt != VT_DISPATCH) continue;
    
      HWND hwnd;
      CComHeapPtr<WCHAR> spszLocation;
      if (FAILED(GetBrowserInfo(svar.pdispVal,
                 &hwnd, &spszLocation))) continue;
    
      ItemInfo *pii =
                new(std::nothrow) ItemInfo(hwnd, svar.pdispVal);
      if (!pii) continue;
    
      LVITEM item;
      item.mask = LVIF_TEXT | LVIF_PARAM;
      item.iItem = MAXLONG;
      item.iSubItem = 0;
      item.pszText = spszLocation;
      item.lParam = reinterpret_cast<LPARAM>(pii);
      int iItem = ListView_InsertItem(g_hwndChild, &item);
      if (iItem < 0) delete pii;
     }
     return S_OK;
    }
    

    To build the window list, we enumerate the contents of the IShell­Windows. For each window, we get its window handle and current location and create a listview item for it. The reference data for the listview item is the Item­Info.

    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
     g_hwndChild = CreateWindow(WC_LISTVIEW, 0,
        LVS_LIST | WS_CHILD | WS_VISIBLE |
        WS_HSCROLL | WS_VSCROLL, 0, 0, 0, 0,
        hwnd, (HMENU)1, g_hinst, 0);
     g_spWindows.CoCreateInstance(CLSID_ShellWindows);
     BuildWindowList();
     return TRUE;
    }
    

    Our creation function creates a child listview and fills it with stuff.

    And of course we clean up our objects when the items are deleted and when the window is destroyed.

    LRESULT OnNotify(HWND hwnd, int idFrom, NMHDR *pnm)
    {
     switch (idFrom) {
     case 1:
      switch (pnm->code) {
      case LVN_DELETEITEM:
       {
        auto pnmlv = CONTAINING_RECORD(pnm, NMLISTVIEW, hdr);
        delete reinterpret_cast<ItemInfo *>(pnmlv->lParam);
       }
       break;
      }
     }
     return 0;
    }
    
    void OnDestroy(HWND hwnd)
    {
     g_spWindows.Release();
     PostQuitMessage(0);
    }
    
     HANDLE_MSG(hwnd, WM_NOTIFY, OnNotify);
    

    And there we have it, a program that displays all the Internet Explorer and Explorer windows and updates their locations as you navigate.

    Note, however, that our program doesn't notice when new windows are created. We'll hook that up next time.

  • The Old New Thing

    The posted message queue vs the input queue vs the message queue

    • 8 Comments

    There are multiple ways of viewing the interaction between posted messages and input messages. MSDN prefers to view posted messages and input messages as part of one giant pool of messages in a message queue, with rules about which ones get processed first. I, on the other hand, prefer to think of posted messages and input messages as residing in different queues that are processed in sequence.

    By analogy, consider a business with a policy that loyalty program members are served ahead of regular customers.

    One way of organizing this is to form a single queue, but sorting them so members go to the front. To call the next person in line, you just take whoever is at the head of the queue.

    AddToQueue(Customer c)
    {
     if (c is a member and the queue contains non-members) {
      let n = the first non-member in the queue;
      insert c in front of n;
     } else {
      insert c at the end of the queue;
     }
    }
    
    GetNextCustomer()
    {
     if (there is somebody in the queue) {
      return the first person in the queue;
     }
     // nobody is waiting
     return null;
    }
    

    This approach works fine from a programmatic standpoint, but people might not like it when they see others cutting in front of them. You might therefore choose to create two queues, one for members and one for non-members:

    AddToQueue(Customer c)
    {
     if (c is a member) {
      insert c at end of member queue;
     } else {
      insert c at end of non-member queue;
    }
    
    GetNextCustomerInLine()
    {
     if (there is somebody in the member queue) {
      return the first person in the member queue;
     }
     if (there is somebody in the non-member queue) {
      return the first person in the non-member queue;
     }
     // nobody is waiting
     return null;
    }
    

    Note that this second algorithm serves customers in exactly the same order; the only difference is psychological. Customers might resent the presence of a members line since it reminds them that other people are getting special treatment. Or this algorithm may not be practical because you don't have room in your lobby for two lines. You might choose yet another algorithm, where you select members at the time the customers are served rather than when they arrive. Everybody forms a single queue, but sometimes a customer in the middle of the queue gets pulled out for special treatment. (Maybe they wear red hats so they are easy to spot.)

    AddToQueue(Customer c)
    {
     insert c at end of queue;
    }
    
    GetNextCustomerInLine()
    {
     if (there is a member in the queue) {
      return the first member in the queue;
     }
     // else only non-members remaining
     if (there is somebody in the queue) {
      return the first person in the queue;
     }
     // nobody is waiting
     return null;
    }
    

    (Some banks use a variant of this algorithm to separate potential bank-robbers from the rest of the customers.)

    From the standpoint of determining who gets served in what order, all of these algorithms are equivalent. One may be more efficient, one may be easier to understand, one may be easier to maintain, one may be easier to debug, but they all accomplish the same thing.

    So go ahead and use whatever interpretation of the message queue you like. They are all equivalent.

    But the interpretation that I use is the one I presented several years ago at the PDC, where the posted message queue and input queues are considered separate queues. I prefer that interpretation because it makes input queue attachment easier to understand.

    In the analogy above, I prefer viewing things in terms of the second algorithm, with separate queues; MSDN prefers viewing things in terms of the third algorithm, with a single queue, but with some messages given preferential treatment over others.

    But as I've already noted, the outputs of the algorithms are the same given the same inputs, so it doesn't matter how they are implemented internally, since all you can observe as a program are the inputs and outputs. It's like the multiple interpretations of quantum mechanics: They all attempt to describe the world from a particular viewpoint, but at the end of the day, the world simply is, and a description is just a description.

    Next time, we'll explore some consequences of the interaction between posted messages and input messages.

  • The Old New Thing

    When a CD gets stuck on infinite repeat during the night

    • 8 Comments

    One night, the CD player got stuck due to a scratched disc and ended up repeating the same track over and over again. I dreamed that the song was O(n²) in length,¹ with each new verse one bar longer than the previous one, but unlike a cumulative song,² the location of the inserted bar varied randomly from verse to verse.

    Moral of the story: Don't use CDs that skip for falling-asleep music.

    ¹ Also known as O(√n) in complexity.

    ² "However, the last two lines of every stanza starting from the second stanza onwards except for the last stanza and the last four lines of the last stanza do not match with the lyrics." Dear Wikipedia: Could you try making even less sense?

  • The Old New Thing

    Even though mouse-move, paint, and timer messages are generated on demand, it's still possible for one to end up in your queue

    • 8 Comments

    We all know that the generated-on-demand messages like WM_MOUSE­MOVE, WM_PAINT, and WM_TIMER messages are not posted into the queue when the corresponding event occurs, but rather are generated by Get­Message or Peek­Message when they detect that they are about to conclude that there is no message to return and the generated-on-demand message can be returned. When this happens, the window manager creates the message on the fly, posts it into the queue, and hey, how about that, the Get­Message or Peek­Message function now has a message to return!

    Note that this auto-generate can happen even though the queue is not empty, because the message filters control what messages in the queue can be returned. For example, suppose the message queue contains the following messages:

    • { hwnd1, WM_CLIP­BOARD­UPDATE }
    • { hwnd2, WM_LBUTTON­DOWN }

    (Note that the above diagram is not strictly correct, because the WM_LBUTTON­DOWN message goes into the input queue, not the message queue, but the distinction is not important here.)

    Suppose you now call Get­Message(&msg, hwnd1, WM_MOUSE­FIRST, WM_MOUSE­LAST). None of the messages in the queue satisfy the message filter: The first message meets the window filter, but the message is not in range. The second message meets the message range filter, but does not meet the window filter. The Get­Message function is about to give up and say "I guess I need to wait for a message," but before it finally concedes defeat, it says, "Hang on there. I see a note that tells me that I should auto-generate a WM_MOUSE­MOVE message for window hwnd1. And that message satisfies the message filter. I'll generate it now!"

    The Get­Message function posts the { hwnd1, WM_MOUSE­MOVE } message into the queue (assigning it the current time as the timestamp), and then it says, "Hey, lookie here! A message that satisfies the filter!" It then removes the message from the queue and returns it.

    (Note that this algorithm is conceptual. It doesn't actually work this way internally. In particular, the window manager does not literally talk to itself, at least not out loud.)

    Okay, so in the Get­Message case, even if the message conceptually goes into the queue, it comes right back out immediately, so you never actually observe it there.

    Now repeat the exercise with the Peek­Message function. As before, the WM_MOUSE­MOVE message is posted into the queue with the current time as the timestamp. If the PM_REMOVE flag is passed, then the message is removed from the queue and returned, just like Get­Message. If the PM_NO­REMOVE flag is passed, then things get interesting: The message is returned but not removed from the queue.

    You now have a WM_MOUSE­MOVE message physically residing in the queue!

    This is the answer to the puzzle: If auto-generated messages are generated on demand, how is it possible for them to end up sitting in your message queue?

    I recall a bug investigation from nearly two decades ago which basically boiled down to this issue: Somebody PM_NO­REMOVE'd an auto-generated message and not only left it in the queue, but kept generating new ones without processing the old ones. Eventually, the message queue filled up.

    (Note that this is also the answer to the puzzle: If WM_MOUSE­MOVE is generated on demand, how can it be possible to retrieve a WM_MOUSE­MOVE message with a timestamp different from the current time?)

  • The Old New Thing

    Extending process attribute inheritance beyond its current boundaries

    • 8 Comments

    I dreamed that I had to fix a bug in Create­Process where it failed to inherit the parent process's assault weapons.

  • The Old New Thing

    The tiny table sorter - or - you can write LINQ in JavaScript

    • 8 Comments

    I had a little side project that displayed status information in a table, and I figured, hey, let me add sorting. And it was a lot easier than I thought. I just put the header row in the THEAD and the table contents in the TBODY, then I could use this code to sort the table:

    function sortByColumn(table, sortCol, direction) {
     direction = direction || 1; // default sort ascending
     var tBody = table.tBodies[0];
     Array.prototype.map.call(tBody.rows, function (row) {
       var cell = row.cells[sortCol];
       return { row: row, key: cell.sortKey || cell.innerText };
     }).sort(function (a, b) {
       if (a.key < b.key) return -direction;
       if (a.key > b.key) return direction;
       return 0;
     }).forEach(function (o) {
       tBody.appendChild(o.row);
     });
    }
    

    Each cell can have an optional sort­Key custom attribute which specifies how the item should sort. If there is no sort­Key, then I just use the cell's inner­Text. (My table was constructed at runtime from an Xml­Http­Request, so adding the sort­Key to the date fields was not difficult.)

    One handy thing about the functions in the Array prototype is that as a rule, they do not actually require that the this object be an array. As long as it has a length property and integer subscripts, you can use it as if it were an array. The map function is okay with read-only access; some other function like sort require read-write access. To call a function with a custom this parameter, you use the call method on the function object itself, passing the artificial this as the first parameter, with the remaining parameters following.

    First, the sort­By­Column function takes the rows of the table body and maps each one to a record consisting of the sort key and the original row. The sort key is the sort­Key property, if true-ish, we will use it; otherwise, we use the text of the cell.

    I took a few shortcuts here. Depending on your browser, you may need to use text­Content instead of inner­Text, and you may need to use get­Attribute instead of property notation. And my function doesn't handle the case where the sort key is defined but is false-ish. Here's a more general version:

    var textProperty = table.innerText ? "innerText" : "textContent";
    ...
       return { row: row,
                key: cell.hasAttribute("sortKey") ?
                     cell.getAttribute(sortKey") : 
                     cell[textProperty] };
    ...
    

    Anyway, after we map the rows to an array of sort records, we sort the records by comparing the key, either by string or by number. The code assumes that every column is either all-strings or all-numbers; it doesn't try to handle the mixed case. This is easy to enforce in the code that generates the table because the only way to get a non-string as a sort key is to set it explicitly as the sort­Key attribute.

    Finally, we take the sorted records and insert the sorted rows back into the table.

    This is a common programming pattern: Decorate, operate, undecorate.¹ We started with a bunch of rows, and we wanted to sort them. We can't sort rows directly, so instead we converted the rows into something we can sort, but remembered the row that each converted item came from. We then perform the sort operation, and then recover the original rows from the decoration, now in sorted order, which we can then use for whatever operation we really wanted. I sort of combined the last two step into one. More formally, it would look like this:

    function sortByColumn(table, sortCol, direction) {
     direction = direction || 1; // default sort ascending
     var tBody = table.tBodies[0];
     // decorate: convert the row into a record
     Array.prototype.map.call(tBody.rows, function (row) {
       var cell = row.cells[sortCol];
       return { row: row, key: cell.sortKey || cell.innerText };
     })
     // operate on the record
     .sort(function (a, b) {
       if (a.key < b.key) return -direction;
       if (a.key > b.key) return direction;
       return 0;
     })
     // undecorate: convert the record back into a row
     .map(function (o) {
       return o.row;
     })
     // operate on the sorted rows
     .forEach(function (r) {
       tBody.appendChild(r);
     });
    }
    

    Category theorists I'm sure have some fancy names they can use to describe this concept, like natural transformation and functor category and splitting.

    LINQ also has a fancy name for this: let, which is a special case of select where LINQ generates the record for you.

    LINQ let query from d in data let y = f(d.xValue)
    LINQ query from d in data select new { d = d, y = f(d.xValue) }
    LINQ fluent data.Select(d => new { d = d, y = f(d.xValue) })
    LINQ fluent
    old delegate syntax
    data.Select(delegate(Data d) { return new { d = d, y = f(d.xValue) }; })
    JavaScript data.map(function (d) { return { d: d, y: f(d.xValue) }; })

    JavaScript's map is the same as LINQ's Select, just with different decorative bits.

    data.Select(delegate(Data d) { return new { d = d, y = f(d.xValue) }; })
    data.map   (function(     d) { return     { d:  d, y:  f(d.xValue) }; })

    Similarly, JavaScript's filter is the same as LINQ's Where, JavaScript's some is the same as LINQ's Any, JavaScript's every is the same as LINQ's All, and JavaScript's reduce is the same as LINQ's Aggregate. JavaScript's sort is sort of like LINQ's Sort, except that it modifies the array in place rather than generating a new result.

    Bonus chatter: In theory, I could've just sorted the table directly by doing the sort key extraction inside the comparator:

    function sortByColumn(table, sortCol, direction) {
     direction = direction || 1; // default sort ascending
     var tBody = table.tBodies[0];
     Array.prototype.map.call(tBody.rows, function (r) {
       return r;
     }).sort(function (a, b) {
       var keyA = a.cells[sortCol].sortKey || a.cells[sortCol].innerText;
       var keyB = b.cells[sortCol].sortKey || b.cells[sortCol].innerText;
       if (keyA < keyB) return -direction;
       if (keyA > keyB) return direction;
       return 0;
     }).forEach(function (r) {
       tBody.appendChild(r);
     });
    }
    
    but since I had to convert the rows into an array anyway (since you cannot modify the rows property by subscript assignment), I figured I'd do the extracting while I was there.

    I guess I could've added a LINQy sort method:

    function defaultComparator(a, b) {
      if (a < b) return -1;
      if (a > b) return 1;
      return 0;
    }
    
    Array.prototype.orderBy =
    function Array_orderBy(extractKey, comparator, direction) {
      direction = direction || 1;
      comparator = comparator || defaultComparator;
      return Array.prototype.map.call(this, function (d) {
        return { key: extractKey.call(d), original: d };
      }).sort(function (a, b) {
        return direction * comparator(a.key, b.key);
      }).map(function (r) {
        return r.original;
      });
    };
    

    Then my sort­By­Column function would just be

    function sortByColumn(table, sortCol, direction) {
     direction = direction || 1; // default sort ascending
     var tBody = table.tBodies[0];
     Array.prototype.orderBy.call(tBody.rows, function (r) {
       var cell = r.cells[sortCol];
       return { key: cell.sortKey || cell.innerText, row: r };
     }, direction).forEach(function (r) {
       tBody.appendChild(r);
     });
    }
    

    But if I had done that, I wouldn't have had a cute one-function table sorter!

    ¹ In perl, this pattern is known as the Schwartzian transform. I prefer to think of it as completing the commutative diagram:

    g
    B B
    f f
    A A
    fgf⁻¹

    Mathematicians get all excited when they see something of the form fgf⁻¹: That's the form of a conjugation operation. Which makes sense, because conjugation is a way of looking at an algebraic group through different-colored glasses. In our case, the magic glasses make every row look like its sort key.

    Bonus chatter: $linq is a Javascript LINQ library.

  • The Old New Thing

    Why are my posted messages getting lost when the user drags my window around?

    • 8 Comments

    This question was inspired by an actual customer question, but I changed a lot of it around to make for a more interesting story. (Trust me, the original story was even more boring.)

    A customer's background thread posted a message to the main UI thread to signal something (details not important). They found that the posted message was never received if the user was in the process of dragging the main window around at the time the message was posted. Why would dragging a window cause posted messages to be lost? "We used to post a thread message, but then we saw that thread messages are eaten by modal loops, so we switched to posting a message, as you recommended. But that didn't help."

    Dragging a window doesn't cause messages to be lost. The modal loop created by the window dragging code calls Dispatch­Message to deliver the posted message to its target window's window procedure.

    "Oh, we don't handle the message in the window procedure. We process it here:

    BOOL MyApp::PreTranslateMessage(MSG *pmsg)
    {
      if (pmsg->message == OUR_SPECIAL_MESSAGE) {
        ... special code here ...
        return TRUE; // handled
      }
      return FALSE; // not handled
    }
    

    Could that be the problem?"

    Yes, that's the problem.

    The customer saw the recommendation to use Post­Message instead of Post­Thread­Message but simply blindly followed the advice rather than understanding its rationale so they could apply the advice correctly.

    If you read the original recommendation, you'll see that the problem is that when a modal loop runs, your message loop is no longer in control, and therefore any customizations you've made to your message loop will not be in effect. This is normally a good thing. For example, if a dialog box calls Message­Box, the dialog keyboard shortcuts shouldn't be active while the message box is displayed. It would be very strange if hitting Enter caused the dialog box to invoke its default button while the modal message box is still on the screen. The result would most likely be a dialog box without underlying support, which leads to unhappiness.

    If there is some sort of message processing you want to happen regardless of which message loop is control, then you can't put it in your custom message loop because (tautologically) your custom message loop is not in control when it is not running. But message loops will call Dispatch­Message, and that will deliver the message to your window procedure. (Of course, the converse also applies: If you want the behavior to be suspended when a modal operation is in progress, you can put it in your message loop.)

Page 374 of 434 (4,338 items) «372373374375376»