Announcement

Collapse

Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

Topmost toggle pin

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Topmost toggle pin

    I have been sticking pins into my client area.

    A topmost feature usually finds itself included in the System Menu of most of my apps but I thought it was time to add a 'Toggle pin' - it is convenient and I'll know the topmost status without moving my hands; provided I'm not asleep on my keyboard. Have you noticed that the older you get the mirrored qwerty indents on your forehead take longer to go when you wake up?

    Anyway, I toyed with putting a pin in the Title Bar but some software put icons there and there would be a conflict. Putting a pin into the client area should be as unobtrusive as possible. I ended up with a 16x16 black foreground, transparent background icon just underneath the Close button on the Title bar. Added: It will be a bit further away when an app menu is employed.

    Now, whatever we use in the client area will be of no use if an app is in untopped mode and sitting fully behind another app in topped mode. The toggle pin then is not a replacement for the System Menu approach since we can get at the System Menu via a right click on the Taskbar entry.

    Here are a couple of screenshots of a demo showing the Systen Menu and toggle pin addition.



    The attached zipped TopmostDemo folder includes TopmostDemo.bas, TopmostDemo.exe, TopmostDemo.rc, TopmostDemo.pbr, Top.ico and NotTop.ico.

    This is TopmostDemo.bas with PB 9.01.

    Needless to say the System Menu entry and the toggle pin are in sync and the toggle pin takes its lead from the System Menu entry.
    Code:
    #Compile Exe
    #Dim All
    #Register None
    #Tools Off
    
    %NOMMIDS = 1
    %NOGDI   = 1
    
    #Include "WIN32API.inc" ' 27 January 2005
    #Resource "TopmostDemo.pbr"
    
    %IDTopMost = 100
    %TopmostIcon = 110
    %Lable     = 120
    
    '------------------------------------------------------------------
    Function PBMain()
    Local hDlg, SysMenu, Result As Long
    Dim ac(0) As ACCELAPI
    Local hAccel As Dword
    
      CreateForm( hDlg )
      
      SysMenu = GetSystemMenu( hDlg, %FALSE )
      Menu Add String, SysMenu, "-", 0, 0
      Menu Add String, SysMenu, "Undo TopMost" + $Tab + "Ctrl+T", %IDTopmost, %MF_ENABLED
      ac(0).fVirt = %FCONTROL Or %FVIRTKEY
      ac(0).KEY = %VK_T
      ac(0).cmd = %IDTopmost
      Accel Attach hDlg, ac() To hAccel 
    
      Dialog Show Modal hDlg, Call CBF_Dialog To Result
      
      Function = Result
    
    End Function
    
    '------------------------------------------------------------------
    
    CallBack Function CBF_Dialog
    
      Select Case As Long CB.Msg
      
        Case %WM_NCACTIVATE
        
          Static hWndSaveFocus As Dword
          If IsFalse CB.wParam Then
          ' Save control focus
            hWndSaveFocus = GetFocus()
          ElseIf hWndSaveFocus Then
          ' Restore control focus
            SetFocus(hWndSaveFocus)
            hWndSaveFocus = 0
          End If
          
        Case %WM_SYSCOMMAND
        
          If CB.Ctl = %IDTopmost Then
            TopMost( CB.Hndl )
            Function = 1
          End If
        
      End Select
        
    End Function
    
    '------------------------------------
    
    CallBack Function CBF_TopButton
    
      If CB.CtlMsg = %STN_CLICKED Then
        If CB.Ctl = %TopmostIcon Then
          TopMost( CB.Hndl )
          Function = 1
        End If
      End If
    
    End Function
    
    '--------------------------------------------------------------------------------
    
    Sub TopMost( hWindow As Long )
    Local SysMenu As Dword
    Local SysMenuTopmostText As String
    
      SysMenu = GetSystemMenu( hWindow, %FALSE )
      Menu Get Text SysMenu, ByCmd %IDTopmost To SysMenuTopmostText
      If SysMenuTopmostText   = "Do TopMost" + $Tab + "Ctrl+T" Then
        SetWindowPos hWindow, %HWND_TOPMOST, 0, 0, 0, 0, %SWP_NOMOVE Or %SWP_NOSIZE
        Menu Set Text SysMenu, ByCmd %IDTopmost, "Undo TopMost" + $Tab + "Ctrl+T"
        Control Set Image hWindow, %TopmostIcon, "Top"
      Else
        SetWindowPos hWindow, %HWND_NOTOPMOST, 0, 0, 0, 0, %SWP_NOMOVE Or %SWP_NOSIZE
        Menu Set Text SysMenu, ByCmd %IDTopmost, "Do TopMost" + $Tab + "Ctrl+T"
        Control Set Image hWindow, %TopmostIcon, "NotTop"
      End If
      
    End Sub
    
    '----------------------------------------------------------------------------------------------------------
    
    Sub CreateForm( hDlg As Long )
    
    Local FormWidth As Long
      
      ' Form in Dialog Units
    
      FormWidth = 130
    
      Dialog New 0, " Topmost Demo",,, FormWidth, 50, %WS_BORDER Or %WS_DLGFRAME Or %WS_SYSMENU _
        Or %WS_MINIMIZEBOX Or %DS_MODALFRAME, %WS_EX_WINDOWEDGE To hDlg
        
      ' I never use Set Color but some of you might
      'Dialog Set Color hDlg, -1&, %LtGray
      
      SetWindowPos hDlg, %HWND_TOPMOST, 0, 0, 0, 0, %SWP_NOMOVE Or %SWP_NOSIZE ' Top on opening
        
      ' To tuck at top right of client area use a client area x co-ord of 'FormWidth - 12' and a y co-ord of 0.
      Control Add Image, hDlg, %TopmostIcon, "Top", FormWidth - 12, 0, 10, 10, %SS_NOTIFY, , Call CBF_TopButton
      
      'Control Set Color hDlg, %TopmostIcon, -1&, %LtGray
      
      Control Add Label, hDlg, %Lable, "Right click left end of Title bar to get System Menu.", 5, 20, 100, 16
      
      'Control Set Color hDlg, %Lable, -1&, %LtGray
      
    End Sub
    
    '----------------------------------------------------------------------------------------------------------
    Added: '&' removed from the SysMenu texts - Attach Accel isn't being used here.
    Added: It is now - see blue text and next post.
    Attached Files
    Last edited by David Roberts; 21 May 2009, 11:59 AM. Reason: Added a keyboard accelerator

  • #2
    A keyboard accelerator has been added - see blue text in post #1.

    With the demo in focus, Ctrl+T, Caps Lock on or off, will toggle Topmost and this is reflected in both the System Menu and the toggle pin.

    I'm a bit pushed for time and have not updated the zip file - so, hit the keyboard.

    Comment


    • #3
      I'm not happy with the icon placement when implementing an app menu. Few menu structures will embrace the whole width of a form so our toggle pin looks a tad adrift from the Close button. We could do with the toggle pin being placed upon the apps menu bar. However, this is non-client area so we'd have to 'drop' down from DDT to SDK and get involved in Device Contexts. Just the mention of Device Contexts sees my head dropping toward the keyboard.

      PB9 saw the introduction of 'Control Add StatusBar' which is drawn in the client area but remains client area. We could the drop an icon onto it.

      However, it looks much better when an icon is dropped onto a designated 'Part'; after the StatusBar is drawn, of course.

      Windows does not like this and when we click on the icon it will use the StatusBar callback and not the Icon callback even though the icon has the higher z-order.

      The SDK helps us out here - see the StatusBar callback below.

      There was a wrinkle. This confirms Windows contempt at the proceedings. If the icon was no longer visible on untopping then a subsequent topping did not see the icon redrawn - the 'Part' was seemingly empty. Various workarounds were attempted but the only successful one was a Control %SW_HIDE followed by a %SW_SHOW.

      Here are a couple of screenshots of a demo showing the System Menu and toggle pin in the StatusBar.



      The icons were designed for placement at the top right of the client area and the foreground is nearly at the top of the icon grid. We could do with the foreground dropping a little so that it is central in the StatusBar 'Part'. An icon editor can fix that.

      The first part of the StatusBar is, of course, available for use - you were thinking of adding a StatusBar anyway - right?

      I am not the world's best at implementing ideas and something, don't know what, tells me that there may be a better way to code this so I have started a comment thread in the Windows forum.

      In the meantime here is a revised version of TopmostDemo.bas. The rc file, and so on, is in post #1's Zip.

      Code:
      #Compile Exe
      #Dim All
      #Register None
      #Tools Off
      
      %NOMMIDS = 1
      %NOGDI   = 1
      
      #Include "WIN32API.inc" ' 27 January 2005
      #Resource "TopmostDemo.pbr"
      
      %IDTopMost = 100
      %TopmostIcon = 110
      %Lable     = 120
      %SBar = 130
      
      '------------------------------------------------------------------
      Function PBMain()
      Local hDlg, SysMenu, Result As Long
      Dim ac(0) As ACCELAPI
      Local hAccel As Dword
      
        CreateForm( hDlg )
        
        SysMenu = GetSystemMenu( hDlg, %FALSE )
        Menu Add String, SysMenu, "-", 0, 0
        Menu Add String, SysMenu, "Undo TopMost"+$Tab+"Ctrl+T", %IDTopmost, %MF_ENABLED
        
        ac(0).Fvirt = %FCONTROL Or %FVIRTKEY
        ac(0).Key = %VK_T
        ac(0).Cmd = %IDTopmost
        Accel Attach hDlg, ac() To hAccel
            
        Dialog Show Modal hDlg, Call CBF_Dialog To Result
        
        Function = Result
      
      End Function
      
      '------------------------------------------------------------------
      
      CallBack Function CBF_Dialog
      
        Select Case As Long CB.Msg
        
          Case %WM_NCACTIVATE
          
            Static hWndSaveFocus As Dword
            If IsFalse CB.wParam Then
            ' Save control focus
              hWndSaveFocus = GetFocus()
            ElseIf hWndSaveFocus Then
            ' Restore control focus
              SetFocus(hWndSaveFocus)
              hWndSaveFocus = 0
            End If
      
          Case %WM_SYSCOMMAND
          
            If CB.Ctl = %IDTopmost Then
              TopMost( CB.Hndl )
              ' Topping from behind a topped form sees icon vanish without the following
              ' Redundant procedure otherwise
              Control Show State CB.Hndl, %TopmostIcon, %SW_HIDE
              Control Show State CB.Hndl, %TopmostIcon, %SW_SHOW
              Function = 1
            End If
          
        End Select
          
      End Function
      
      '--------------------------------------------------------------------------------
      
      CallBack Function CBF_SBar
      Local hWinTopmostIcon As Long
      Local TopMostIconRect As Rect
      Local CursorPos As PointApi
        
        If CB.Ctl = %SBar Then
          ' Get Windows handle of TopmostIcon
          Control Handle CB.Hndl, %TopmostIcon To hWinTopmostIcon
          ' Get the four corners
          GetClientRect hWinTopmostIcon, TopMostIconRect
          ' and the mouse is where on screen?
          GetCursorPos CursorPos
          ' Replace screen co-ords with client-area co-ords
          ScreenToClient hWinTopmostIcon, CursorPos
          ' Are we on TopmostIcon?
          If PtInRect(TopMostIconRect, CursorPos.x, CursorPos.y) Then
            TopMost( CB.Hndl )
            Function = 1
          End If
        End If
      
      End Function
      
      '------------------------------------------------------------------
      
      Sub TopMost( ByVal hWindow As Long )
      Local SysMenu As Dword
      Local SysMenuTopmostText As String
      
        SysMenu = GetSystemMenu( hWindow, %FALSE )
        Menu Get Text SysMenu, ByCmd %IDTopmost To SysMenuTopmostText
        If SysMenuTopmostText   = "Do TopMost" + $Tab + "Ctrl+T" Then
          SetWindowPos hWindow, %HWND_TOPMOST, 0, 0, 0, 0, %SWP_NOMOVE Or %SWP_NOSIZE
          Menu Set Text SysMenu, ByCmd %IDTopmost, "Undo TopMost" + $Tab + "Ctrl+T"
          Control Set Image hWindow, %TopmostIcon, "Top"
        Else
          SetWindowPos hWindow, %HWND_NOTOPMOST, 0, 0, 0, 0, %SWP_NOMOVE Or %SWP_NOSIZE
          Menu Set Text SysMenu, ByCmd %IDTopmost, "Do TopMost" + $Tab + "Ctrl+T"
          Control Set Image hWindow, %TopmostIcon, "NotTop"
        End If
        
      End Sub
      
      '----------------------------------------------------------------------------------------------------------
      
      Sub CreateForm( hDlg As Long )
      
      Local FormWidth, FormHeight As Long
        
        ' Form in Dialog Units
      
        FormWidth = 130
        FormHeight = 60
      
        Dialog New 0, " Topmost Demo SB",,, FormWidth, FormHeight, %WS_BORDER Or %WS_DLGFRAME Or %WS_SYSMENU _
          Or %WS_MINIMIZEBOX Or %DS_MODALFRAME, %WS_EX_WINDOWEDGE To hDlg
        
        SetWindowPos hDlg, %HWND_TOPMOST, 0, 0, 0, 0, %SWP_NOMOVE Or %SWP_NOSIZE ' Top on opening
          
        Control Add Label, hDlg, %Lable, "Right click left end of Title bar to get System Menu.", 5, 20, 100, 16
        
        Control Add StatusBar, hDlg, %SBar, "", 0,0,0,0, Call CBF_SBar
        StatusBar Set Parts hDlg, %SBar, FormWidth - 14, 9999
          
        ' After the StatusBar
        Control Add Image, hDlg, %TopmostIcon, "Top", FormWidth - 12, FormHeight - 10, 10, 10
                                             
      End Sub
      
      '----------------------------------------------------------------------------------------------------------
      Last edited by David Roberts; 22 May 2009, 12:52 PM. Reason: Corrected English language syntax - my 1st language is Yorkshire <smile>

      Comment


      • #4
        > I am not the world's best at implementing ideas and something, don't know what, tells me that there may be a better way to code this so I have started a comment thread in the Windows forum.

        Found some code by José Roca and adapted it for PB9.

        1) Remove the CBF_SBar callback function
        2) and its reference in 'Control Add StatusBar'
        3) Add the assignments Local ptrNMMOUSE As NMMOUSE Ptr & Local ptrNMHDR As NMHDR Ptr to CBF_Dialog
        4) Add the following to CBF_Dialog.
        Code:
        Case %WM_NOTIFY
            
          If CB.Ctl = %SBar Then
            ptrNMHDR = CB.NmHdr
            If @ptrNMHDR.Code = %NM_CLICK Then
              ptrNMMOUSE = CB.lParam
              If @ptrNMMOUSE.dwItemSpec = 1 Then ' zero based, unlike StatusBar which is 1 based 
                TopMost( CB.Hndl )
                Function = 1
              End If
            End If
          End If
        The above captures the notification issued on clicking 'Part' 2 of the StatusBar.

        Nice one, José.

        Comment


        • #5
          As a final note, if you have a very active status bar and want to give it more prominence you may like this set up.



          This gets the toggle pin to the top right of our form which, IMO, is the best place for it and besides being a clickable item it is a status indicator and in the status bar.

          We could have more than one icon in the status bar indicating various states and playing an active role as well.

          Have fun.

          Comment


          • #6
            I couldn't resist this one.
            Attached Files

            Comment

            Working...
            X