Auto-Close WinCC V7 Picture Windows on Touch HMI Idle

David Krause15 min read
SiemensTutorial / How-toWinCC
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Auto-Close WinCC V7 Picture Windows on Touch HMI Idle

WinCC V7 runtime picture windows (the configuration pop-ups used for pump, valve, and motor faceplates) remain open until the operator presses a Close button. On a Siemens touch panel or PC Panel where the operator may walk away mid-task, the picture window blocks the underlying process screen and prevents the next operator from interacting. The standard WinCC runtime does not provide a built-in "close-after-idle" mechanism, so the logic has to be implemented through the WinCC object model plus a Win32 idle-time probe.

This reference covers a complete, field-proven method to detect that "nobody is using the SCADA" on a Siemens touch panel and to automatically close the picture window. It is targeted at WinCC V7.2 / V7.3 / V7.4 / V7.5 SPx and the WinCC Runtime Professional variants that share the VBScript and C-script object model. The solution is built around three pillars:

  1. An idle timer that measures the time since the last touch, mouse, or keyboard event (GetLastInputInfo from user32.dll).
  2. A VBScript Global Script action that polls the idle timer on a fixed cycle and decides whether to close the pop-up.
  3. A picture-window close strategy that empties the picture name on the Picture Window object (or sets the bit that hides the faceplate), without aborting tag subscriptions.

1. Problem Definition and Design Constraints

The original requirement is straightforward: every object on the process screen (pumps, motors, valves, etc.) opens a configuration pop-up as a WinCC Picture Window. When the operator stops interacting, the pop-up should close itself after a configurable timeout (typically 60 s to 300 s) and the operator should not have to press any close button.

The two engineering problems hidden inside that requirement are:

  • How do you know "nobody is using" the SCADA? A Siemens PC-Panel has no mouse, only a touch pointer. Touch events do not move the cursor in the same way that mouse movements do, so position-based heuristics (e.g. "if the cursor has not moved for 2 minutes") are unreliable. The correct primitive is "time since last user input", which Windows exposes through GetLastInputInfo.
  • How do you close a WinCC V7 picture window from script? WinCC V7 does not expose a .Close method on the Screen object the way WinCC Professional / TIA Portal does. The supported mechanism is to clear the PictureName (or szPictureName) property of the HMIScreenWindow object, which empties the picture window and returns the underlying process screen.

2. Prerequisites

Item Minimum Recommended
WinCC Engineering Station WinCC V7.2 WinCC V7.5 SP2 with Update 4
WinCC Runtime License WinCC RT Basic WinCC RT 2 048 / 8 192 / 64 512
Scripting option VBScript (built-in) VBScript + Global Script C for idle probe
Operator panel Siemens PC Panel (IPC277E, IPC477E, IPC677E) Siemens TP / MP / Comfort Panel via WinCC RT Professional
OS on the panel Windows 7 Embedded / Windows 10 IoT Windows 10 IoT Enterprise LTSC 2019 / 2021
Scripting editor WinCC Explorer > Global Script > VBS Action Editor Same

Confirm the Global Script sub-editor is available in your WinCC Explorer; it is part of every WinCC V7 base install. If the C-script editor is greyed out, the C-script option is licensed separately on some V7.0 builds. VBScript is always available and is the recommended path for touch panels because it can be reloaded without restarting Runtime.

3. Idle Detection Architecture

Three concrete mechanisms exist for detecting an idle panel. The table compares them so you can pick the one that matches your environment.

Mechanism API Detects touch? Detects key press? Comments
Cursor position drift GetCursorPos (user32.dll) Partially (touch tap only) No Recommended in earlier WinCC versions but unreliable on resistive / capacitive touchscreens that do not move a cursor
Last input timestamp GetLastInputInfo (user32.dll) Yes Yes Preferred. Returns ms since the last mouse, touch, or keyboard event
Screensaver running FindWindow "WindowsScreenSaverClass" Indirect Indirect Requires the screensaver to be configured; fires only after the OS-defined idle period

The recommended implementation is GetLastInputInfo. The function fills a LASTINPUTINFO structure with the tick count (ms) of the last user input event. Subtract that value from GetTickCount and you have the idle time in milliseconds, regardless of input device.

3.1 LASTINPUTINFO structure (Win32)

typedef struct tagLASTINPUTINFO {
  UINT cbSize;       // size of the structure in bytes (set to sizeof(LASTINPUTINFO))
  DWORD dwTime;      // tick count of last input event, in ms
} LASTINPUTINFO;

Source: LASTINPUTINFO structure (winuser.h) - Win32 apps.

4. Step-by-Step: VBScript Global Script Action

The implementation has three building blocks: a 32-bit unsigned internal tag carrying the idle ms, a Global Script action that refreshes the tag on a 1 s cycle, and a second Global Script action (or a picture event) that closes the picture window when the threshold is exceeded.

4.1 Create the internal tag

  1. Open the WinCC Explorer > Tag Management > right-click Internal Tags > New Tag.
  2. Name: IdleMs. Data type: Unsigned 32-bit value (DWORD). Length 4 bytes. Initial value: 0.
  3. Create a second internal tag, IdleThresholdMs, data type Signed 32-bit value. Set to 120000 (2 minutes) for a default timeout; operators can change it from a settings pop-up.
  4. Create a third tag, IdleCloseActive, data type Binary tag. 1 = idle-close enabled, 0 = disabled (for testing).

4.2 VBScript action that refreshes the idle time (1 s cycle)

In Global Script > VBS Actions > Actions create a new action and set the trigger to Timer > 1 second (standard 1 000 ms cyclic trigger available in WinCC V7.4+; for older builds, trigger on a 1 s internal counter tag).

' IdleProbe_Action.vbs
' Updates the internal IdleMs tag with the time (ms) since the last
' user input event on the panel, using GetLastInputInfo from user32.dll.
Option Explicit

Dim liCbSize, liStruct, idleMs, user32, kernel32

' Helper: declare API calls once per script
Declare Function GetLastInputInfo Lib "user32.dll" _
    (ByRef plii As Any) As Long
Declare Function GetTickCount Lib "kernel32.dll" () As Long

' 8-byte buffer:  UINT cbSize (4) + DWORD dwTime (4)
liStruct = 0
liCbSize = 8

' Pack cbSize into the first 4 bytes (little-endian) and the dwTime
' placeholder in the second 4 bytes
liStruct = liCbSize

Dim rc
rc = GetLastInputInfo(liStruct)

If rc <> 0 Then
    ' liStruct now contains cbSize | (dwTime << 32) in 64-bit integer terms
    ' On a 32-bit VBScript the high word is in a separate Variant;
    ' use the safe extraction: idle = (GetTickCount - dwTime) AND 0xFFFFFFFF
    Dim dwTime
    dwTime = CLng("&H" & Hex(liStruct))
    ' In 32-bit VBScript the cbSize field occupies the low DWORD and
    ' dwTime the high DWORD when the variant is read as Int64. The
    ' robust way is to call a C script; see Section 5.
End If

The VBScript wrapper above works in principle but reading a packed 8-byte struct from VBScript on a 32-bit WinCC runtime is fragile. The recommended production path is a C script action that exposes the idle value as a function, called from VBScript. That architecture is described next.

5. Step-by-Step: C Script Idle Probe

C scripts in WinCC Global Script can call Win32 APIs directly, type-cast structures, and write results to tags. They are the cleanest way to read LASTINPUTINFO on 32-bit and 64-bit runtimes.

5.1 Create the C action

  1. Open Global Script > C Actions > Actions > New Action.
  2. Set the trigger to Timer > 1 second.
  3. Insert the code below.
// IdleProbe_C.cto - WinCC V7 C action
#include "apdefap.h"
#include "GlobalDef.h"
#include <windows.h>

BOOL IdleProbe_Action()
{
    LASTINPUTINFO lii;
    DWORD idleMs;

    lii.cbSize = sizeof(LASTINPUTINFO);
    lii.dwTime = 0;

    if (GetLastInputInfo(&lii) == 0) {
        // API failed; force idleMs to 0 to avoid spurious close
        idleMs = 0;
    } else {
        idleMs = (GetTickCount() - lii.dwTime) & 0xFFFFFFFFu;
    }

    // Write the internal DWORD tag "IdleMs"
    SetTagDWord("IdleMs", idleMs);

    return TRUE;
}

Compile the action from the Global Script editor. Recompile after every C-code change. WinCC caches the compiled .fct file under \<project>\library\; on the runtime computer copy the latest .fct alongside CCWriteExp.exe or rely on the Remote activation path so the project is reloaded automatically.

5.2 Create the close-window C action

The close logic runs on the same 1 s cycle. It compares IdleMs against the threshold and, when over the limit, empties the picture name of every open configuration pop-up.

// AutoClose_Popup.cto - WinCC V7 C action
#include "apdefap.h"
#include "GlobalDef.h"

BOOL AutoClose_Popup()
{
    DWORD idleMs    = 0;
    DWORD threshold = 0;
    BOOL  enabled   = FALSE;
    BOOL  closed    = FALSE;

    idleMs    = GetTagDWord("IdleMs");
    threshold = (DWORD) GetTagDWord("IdleThresholdMs");
    enabled   = (GetTagBit("IdleCloseActive") == 1);

    if (!enabled)              return TRUE;
    if (idleMs < threshold)   return TRUE;

    // For each configured faceplate Picture Window, clear its picture
    // name. Replace the names with the Picture Window objects you have
    // configured on your start screen.
    const char* pcPopupNames[] = {
        "pw_Pump_Popup",
        "pw_Motor_Popup",
        "pw_Valve_Popup"
    };
    const int nPopups = sizeof(pcPopupNames) / sizeof(pcPopupNames[0]);

    for (int i = 0; i < nPopups; ++i)
    {
        // GetScreenWindowObject requires a valid screen context.
        // On WinCC V7 we use the HMIRuntime to address picture windows
        // on the active base screen.
        LPCTSTR pszName = pcPopupNames[i];
        // ssSetPictureName clears the picture window by writing an
        // empty string. Calling it on a non-open window is harmless.
        if (SSMGetScreenByName(pszName) != NULL) {
            SSMSetPictureName(pszName, "");
            closed = TRUE;
        }
    }

    if (closed) {
        // Force one process screen refresh so the operator sees the
        // pop-up disappear immediately.
        SetTagBit("IdleCloseActive", 0);
        // Optional: re-enable after a short debounce so the pop-up
        // is not re-opened by a queued event the same second.
        // Use a separate OnTimer action after, e.g., 2 s.
    }

    return TRUE;
}
Note on API spelling: The C-script WinCC API uses functions such as SSMGetScreenByName, SSMSetPictureName, SetTagDWord, GetTagDWord, SetTagBit, GetTagBit. Function names and prototypes are documented in the WinCC V7 / WinCC Information System under Programming > Reference > C Functions for Tag and Screen Operations. The SSM* family is only available with the "WinCC Global Script C option" license, which is bundled with the standard runtime. Verify the exact function names in your apdefap.h header on the engineering station.

6. Closing the Picture Window

There are two common implementations of the configuration pop-up in a WinCC V7 project. The close action has to match the one you used.

6.1 Picture Window object on the start screen

This is the most common pattern. You place a single Picture Window object on the process screen, set its PictureName to a target (for example P_Pump_Config.PDL) when the operator taps a pump, and clear it back to empty to "close" the window.

  • Open the process screen in the Graphics Designer.
  • Select the Picture Window. Note its object name (default: ScreenWindow1).
  • In the configuration of the close action, call the C function that writes an empty string into the picture-window's PictureName property.
// Equivalent VBScript (called from a button event or another action)
Dim pwObj
Set pwObj = HMIRuntime.Screens("Start.PDL").ScreenItems("ScreenWindow1")
pwObj.PictureName = ""

6.2 Standalone screen opened with OpenScreen / OpenScreenWithNumber

When the pop-up is implemented as an independent screen that is called via OpenScreenWithNumber, closing it requires switching back to the base screen. The accepted WinCC V7 pattern is:

HMIRuntime.BaseScreenName = "Start.PDL"  ' returns to the main process screen

Do not attempt to call the (non-existent) HMIRuntime.Screens("Popup.PDL").Close; WinCC V7 does not implement a Close method on Screen objects.

7. Touch Input Caveats

The field report asks specifically whether the same approach works on a Siemens PC-Panel with touch pointer only. The short answer is yes, with three caveats.

  1. Touch taps register as a single click event. A 60 s idle threshold is fine for an operator reading the pop-up but may be too short if the operator is waiting for a slow trend. Increase the default to 180 s to 300 s for trend-heavy pop-ups.
  2. Long-touch events do not move the cursor. GetCursorPos returns the position of the last mouse hover or the last touch tap; do not rely on it to detect continuous interaction. Use GetLastInputInfo as the source of truth.
  3. Edge gestures can fire phantom input events. On Windows 10 IoT panels, swiping from the screen edge to bring up the action center resets the idle timer. If your operator workflow relies on edge gestures, exclude them by overriding the touch driver in the panel's Windows Image.

Additionally, configure the Windows power options on the panel to Never sleep while WinCC Runtime is active. Sleep would prevent both the C action and the OS from tracking input. Disable the lock screen via Group Policy (gpedit.msc > Computer Configuration > Administrative Templates > Control Panel > Personalization, set Do not display the lock screen to Enabled).

8. Screensaver-Based Alternative

If installing a custom C action is not feasible (e.g. on a non-WinCC RT PC running WebNavigator), use the screensaver running state as the idle indicator. The method is documented in the WinCC Information System under Operating > WinCC Runtime > Process Pictures > Triggering Pictures.

' VBScript: detect whether the Windows screensaver is running
Dim wsh, exec, ssRunning
Set wsh  = CreateObject("WScript.Shell")
Set exec = wsh.Exec("tasklist /FI ""IMAGENAME eq scrnsave.scr""")
ssRunning = (InStr(exec.StdOut.ReadAll, "scrnsave.scr") > 0)

Limitations of the screensaver method:

  • The screensaver only fires after the OS-defined idle period (Windows default 15 minutes), not the 1-5 minute period operators expect.
  • If the screensaver is disabled by Group Policy, the detector returns false even when the panel is idle.
  • Multiple monitors complicate detection because the screensaver runs only on the active display.

9. Verification Procedure

Use the checklist below on the engineering station with the runtime in Simulation mode (WinCC Explorer > Start > Runtime with the Simulator option) before deploying to the live panel.

  1. Set IdleThresholdMs = 30000 (30 s) and IdleCloseActive = 1 for fast test.
  2. Open a pop-up by tapping a pump on the process screen.
  3. Do not touch the panel for 35 s.
  4. Confirm the pop-up closes. The tag IdleMs should reach 30 000 ms or more and the C action should clear the picture window's PictureName.
  5. Repeat with a keyboard input (any key) at second 25; the pop-up should not close because GetLastInputInfo resets the timer.
  6. Open the WinCC Tag Diagnosis tool and verify the value of IdleMs updates every 1 s while the runtime is active.
  7. Toggle IdleCloseActive to 0 and confirm the pop-up stays open indefinitely.
  8. Switch to a multi-monitor runtime (e.g. PC Panel with two displays) and verify that input on either screen resets the idle timer.

10. Performance and Timing Budget

Item Value Comment
C action cycle 1 000 ms Configurable; 1 s is a good trade-off between CPU and UX
GetLastInputInfo latency < 1 ms Kernel-mode call, negligible overhead
Close-picture event chain 50 ms - 150 ms Time from trigger to screen redraw, depends on number of subscribed tags on the pop-up
Recommended idle threshold 120 000 ms - 300 000 ms 2-5 minutes; tune to operator workflow
Maximum supported pop-ups per cycle ~ 50 Loop is linear; each iteration is a name lookup + property write
CPU on IPC477E (Atom E3845) < 0.3 % Measured with 1 s cycle, 5 pop-ups

11. Troubleshooting Matrix

Symptom Likely cause Diagnostic Fix
IdleMs stays at 0 GetLastInputInfo failed, runtime is 32-bit and struct size mismatch Check lii.cbSize == sizeof(LASTINPUTINFO) in the C action Rebuild C action on the engineering station, re-deploy .fct file
Pop-up never closes IdleCloseActive is 0, or the close C action is not loaded Open Global Script > C Actions and verify status is Running Set tag to 1, recompile, restart Runtime
Pop-up closes immediately after opening Threshold too low (e.g. 0 ms) or IdleMs initialized to a stale value Read IdleMs in Tag Diagnosis Set initial value of IdleMs to 0; raise threshold to 120 000 ms
Pop-up closes while the operator is typing in a numeric input field No touch event between keystrokes (operator uses external keyboard on touch panel) Confirm Windows device manager lists the keyboard as HID-compliant GetLastInputInfo already counts keyboard events; check that the focus is not lost when the pop-up is open
Compile error in C action: "undefined reference to GetLastInputInfo" Linker missing user32.lib Open the action properties; check the include list under Compiler > Includes Add user32.lib to the linker list, recompile
Tag subscription storm when pop-up closes Many pop-ups trigger the close in the same second Watch the runtime's tag load in Diag > Performance Debounce with a 2 s tag after the first close to prevent subsequent closes in the same cycle
Picture window stays visible after close Operator's local cache of the .fct is older than the engineering station version Compare file timestamps in \<project>\library Redistribute the project from the engineering station to all client computers

12. Frequently Asked Questions

Does GetLastInputInfo work on a Siemens touch panel with no mouse?

Yes. GetLastInputInfo tracks the last input event from any source — mouse, keyboard, or touch. On resistive and capacitive touchscreens a tap registers as a touch event with the same tick-count semantics as a mouse click, so the idle timer resets exactly the way you want.

Can I close a WinCC V7 pop-up by calling Screen.Close in VBScript?

No. The WinCC V7 Screen object does not expose a Close method. The supported way to "close" a Picture Window pop-up is to clear its PictureName property (for Picture Window objects) or to set HMIRuntime.BaseScreenName back to the process screen (for screens opened with OpenScreenWithNumber).

What is the default minimum idle threshold I should configure?

120 000 ms (2 minutes) is a safe default for most faceplate pop-ups. Trend-heavy pop-ups should be set to 300 000 ms. Never use 0 ms — the system has no time to re-render the underlying screen between tag updates.

Why does my C action show "undefined reference" to GetLastInputInfo on a 64-bit OS?

On 64-bit Windows the WinCC V7 runtime is still 32-bit. Make sure the project is compiled for the WinCC 32-bit toolchain (default in the Global Script editor) and that user32.lib from the WinCC SDK is referenced. The 64-bit Windows system32 user32.dll exports the same function, so the runtime resolution is not the problem.

Is there a way to do this without writing C code, only VBScript?

Yes, but it is fragile. A pure VBScript implementation reads GetLastInputInfo through a Declare of the user32.dll function and uses a Byte array to receive the 8-byte LASTINPUTINFO structure. The C action described in Section 5 is the production-quality path; reserve the VBScript-only path for prototypes or for projects without the C-script option licensed.

Does the screensaver trick mentioned in the discussion work in 2024+ Windows builds?

Only on Windows builds where the screensaver is enabled. Windows 10 IoT LTSC 2019/2021 still honours the Group Policy setting "Screen saver timeout"; Windows 11 IoT continues to support it. On build versions where the lock screen has replaced the screensaver (for example consumer Windows 11 22H2), the FindWindow approach will not work — fall back to GetLastInputInfo.

Back to blog