WinCC Picture Slideshow for Trends: C-Script Implementation Guide

David Krause19 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

1. Overview

Many WinCC V7 monitoring stations are required to display several trend PDLs in a continuous loop without operator intervention. The classic use case is a power and energy consumption dashboard where 6 to 12 trend.pdl screens must be shown in sequence, each holding the focus for 1 to 2 minutes before the next picture is opened. This guide consolidates the working pattern recommended by Siemens and proven in field installations: an internal integer counter is incremented by a global C action on a cyclic trigger, an I/O field bound to that tag fires an OnObjectChanged event, and a C script in the event handler calls OpenPicture() with a switch/case block that selects the right PDL for the current counter value.

The pattern is stable across WinCC V7.0 through V7.5 SP2 and uses only the standard WinCC Graphics Designer and Global Script editor. No additional option packages, no WinCC/Audit, no Connectivity Pack, and no external DLL are required. The same logic maps cleanly to WinCC Unified V17+ through V20, but the runtime objects, trigger model, and C# / VB scripting differ; the differences are summarized in the closing section of this article.

Scope of this guide: the slideshow is a single-process, single-runtime loop running on a WinCC Station or WinCC Server. It assumes a local RT (Runtime) project, the WinCC Graphics Runtime is active, and the trend PDLs already exist in the GraCS directory of the project. If you are designing a redundant pair, trigger the same script on the preferred partner and accept brief flicker during failover.

2. Prerequisites and Runtime Environment

Before adding any C code, confirm the following:

  • WinCC V7.2 SP2 or later (V7.3, V7.4, V7.5 SP2 are all compatible). WinCC V7.0 SP3 works but requires the legacy ANSI C compiler; later versions ship with the Microsoft Visual C++ runtime that compiled actions expect.
  • Microsoft Windows 10 LTSC 2019, Windows Server 2016, or Windows Server 2019. WinCC V7.2 is not certified on Windows 11; if you must run on Windows 11, use WinCC V7.5 SP2 with the corresponding SIMATIC PC-Image update.
  • WinCC Explorer open as administrator, the project is in Runtime-ready state, and the Graphics Runtime is registered as autostart service.
  • All trend PDLs (e.g. trend1.pdl ... trend6.pdl) exist in the project picture tree and pass a manual OpenPicture() test from the WinCC Explorer toolbar.
  • The user account that opens the WinCC project in the editor has the right to create internal tags and global actions. This is the default for local administrators; on a domain-joined engineering workstation the local SIMATIC HMI group membership is sufficient.

The minimum tag and script inventory needed before the first keystroke of C code:

Object Type Name Initial Value Purpose
Internal tag Signed 32-bit (DWORD) slideshow_index 0 Holds the current picture index 0..N-1. Range must cover 0..5 for 6 pictures, or 0..11 for 12 pictures; the upper bound defines the modulo in the global action.
Internal tag Binary slideshow_run 1 (true) Start/Stop flag. 1 = slideshow advances, 0 = slideshow paused on the current picture.
Global C action Cyclic trigger 1 min Slideshow_GlobalAction Increments slideshow_index modulo N and writes back the wrapped value.
Picture PDL slideshow_host.pdl Container picture with the I/O field, start/stop button, and a Picture Window that loads the active trend PDL.
Picture Window Smart object PicWin_Slides Picture name bound to a string tag Hosts the currently selected trend picture so that the host PDL does not unload during a switch.

3. Architecture of the Slideshow Loop

The slideshow is driven by three decoupled layers. Layer 1 is a global C action that fires on a cyclic trigger and increments an internal integer counter. Layer 2 is the I/O field bound to that counter; whenever the counter value changes, the Graphics Runtime raises the OnObjectChanged event and the Layer 3 C script executes, calling OpenPicture() with the matching PDL name. The Picture Window approach is preferred for the host because the host PDL never closes, so the I/O field, button, and any alarm line remain on screen while only the embedded picture changes.

The data flow is unidirectional:

Cyclic Trigger 1 minute (configurable) Global C Action index = (index + 1) % N Internal Tag slideshow_index 0..N-1, written by Layer 1 I/O Field OnObjectChanged Layer 3 C script in PDL event OpenPicture() trendX.pdl via switch/case Trend PDL on screen Visible 1..2 minutes

Decoupling the timer from the picture change is the key engineering choice. It keeps the picture-change logic deterministic (the script always runs because the value of the tag has changed) and allows the operator to pause the slideshow simply by locking the counter, without touching the trigger or the script. The counter tag also doubles as a state indicator visible in WinCC Online Trend Control, so the runtime engineer can see exactly which picture is on screen from the diagnostic window.

4. Step 1: Create the Internal Counter Tag

Open the WinCC Explorer, expand Tag Management, right-click Internal Tags and choose New Tag. Use the parameters from the table below. Internal tags are volatile by default; they are reset to their start value on every Runtime start, which is the correct behavior for a slideshow that must always begin at picture 0.

Property slideshow_index slideshow_run
Name slideshow_index slideshow_run
Data type Signed 32-bit value (DWORD) Binary Tag
PLC / Driver Internal Internal
Start value 0 1 (true)
Update 250 ms (default) 500 ms (default)
Limits 0..11 (low/high) for 12 pictures, or 0..5 for 6 0..1
Substitute value 0 1

Why Signed 32-bit and not Unsigned 8-bit? WinCC global actions are compiled as 32-bit; the modulo operation runs faster on the native int, and you avoid an integer-promotion warning in the C compiler output that ships with WinCC. For 6 pictures the visible range is 0..5; for 12 pictures 0..11; if you later add a 13th picture, only the limit and the modulo constant change.

Save the tag, then verify in Tag Management > Simulation that the tag accepts a write of 0..5 and rejects values outside the configured range when the limits dialog is set to Substitute value on violation.

5. Step 2: Build the Global C Action with a Cyclic Trigger

Open the Global Script editor from the WinCC Explorer navigator. Right-click Actions and choose New > C-Action. Name it g_slideshow_tick for clarity. In the trigger dialog, set the trigger type to Cyclic trigger and the interval to 00:01:00 (1 minute) for the canonical case; use 00:02:00 for the 2-minute rotation. Anything below 30 s starts to fight with picture load times on slower panels.

The C body is intentionally minimal:

// ----------------------------------------------------------------
// g_slideshow_tick: increments slideshow_index with wrap-around.
// Trigger: cyclic, 1 minute (change to 2 minutes in the trigger dialog).
// ----------------------------------------------------------------
#include "apdefap.h"

#define INDEX_TAG  "slideshow_index"
#define RUN_TAG    "slideshow_run"
#define PICTURE_COUNT 6   // change to 12 if you have 12 trend PDLs

int gscAction(void)
{
    DWORD index = 0;
    DWORD run   = 0;

    // Read current state
    index = GetTagDWord(INDEX_TAG);
    run   = GetTagByte(RUN_TAG);

    // Stop flag held low (0) pauses the slideshow.
    if (run == 0) {
        return 0;
    }

    // Increment with modulo wrap-around
    index = (index + 1) % PICTURE_COUNT;

    // Write back
    SetTagDWord(INDEX_TAG, index);
    return 0;
}

Compile with File > Compile (or Ctrl+F7). The WinCC C compiler should report zero warnings and zero errors. If a warning warning C4244: '=' : conversion from 'unsigned long' to 'DWORD' appears, the tag type is wrong; reset it to Signed 32-bit as described in Step 1. Save and place the action in the Project Functions > Actions tree under a folder named Slideshow for organization.

Why a global action and not a local C action in the PDL? Local C actions attached to a timer are tied to the lifetime of the picture. The moment the slideshow calls OpenPicture() on a different PDL, the timer disappears and the counter never advances again. A global action lives in the runtime memory space of the WinCC project and survives every picture change, which is exactly the behavior we need.

6. Step 3: Define Picture Names and Tag Names

WinCC searches and replaces tag names and picture names in C scripts based on the // WINCC:TAGNAME_SECTION_START / // WINCC:TAGNAME_SECTION_END and // WINCC:PICNAME_SECTION_START / // WINCC:PICNAME_SECTION_END comment fences. Place every #define of a tag name inside the tag-name fences, and every #define of a picture name inside the picture-name fences. This is not cosmetic: if you skip the fences, the WinCC C editor and the project rename tool will silently miss the symbol and a picture rename will leave broken references in your script.

// WINCC:PICNAME_SECTION_START
#define PIC_0  "trend1.pdl"
#define PIC_1  "trend2.pdl"
#define PIC_2  "trend3.pdl"
#define PIC_3  "trend4.pdl"
#define PIC_4  "trend5.pdl"
#define PIC_5  "trend6.pdl"
// WINCC:PICNAME_SECTION_END

// WINCC:TAGNAME_SECTION_START
#define INDEX_TAG  "slideshow_index"
#define RUN_TAG    "slideshow_run"
// WINCC:TAGNAME_SECTION_END

Storing these #defines in a project function (e.g. slideshow_defines.h) and including the file in every script that needs the symbols keeps the rename tool happy and removes the risk of a typo creeping into multiple files. The C preprocessor concatenates the symbols at compile time, so the runtime cost is zero.

7. Step 4: Configure the I/O Field and OnObjectChanged Event

Open the host picture slideshow_host.pdl. Drag an I/O Field from the smart objects palette and configure it as an output field (read-only) bound to slideshow_index. In the I/O field properties set:

  • Configuration > Data format: Decimal
  • Configuration > Field type: Output
  • Configuration > Output / Input: Output
  • Limits > Low / High value: 0 / 5 (or 0 / 11)
  • Appearance > Visible: unchecked (operator does not need to see the counter, but leaving it visible is a useful commissioning aid)

Right-click the I/O field and choose Properties > Events > Output Value Changed. The dialog opens the C editor with the OnObjectChanged signature pre-populated. Confirm the function signature matches the WinCC convention:

void OnObjectChanged(char* lpszPictureName, char* lpszObjectName)

The argument names vary by WinCC version; the order is always picture name first, object name second. If the editor pre-fills the arguments as ipsPictureName and ipsObjectName, leave them as-is; both names are accepted by the C compiler.

8. Step 5: Implement the Switch/Case Picture Change Script

Inside the OnObjectChanged event, add the body that maps the counter value to a trend picture. A switch/case block is preferred over a chain of if statements because the Visual C compiler builds a jump table when the case values are consecutive integers (0, 1, 2, ...), and the resulting code is faster and smaller for slideshows with many pictures. The same code also reads more naturally when the project is reviewed a year later.

// ----------------------------------------------------------------
// Slideshow picture change: fires when slideshow_index is written.
// ----------------------------------------------------------------
#include "apdefap.h"

// WINCC:PICNAME_SECTION_START
#define PIC_0  "trend1.pdl"
#define PIC_1  "trend2.pdl"
#define PIC_2  "trend3.pdl"
#define PIC_3  "trend4.pdl"
#define PIC_4  "trend5.pdl"
#define PIC_5  "trend6.pdl"
// WINCC:PICNAME_SECTION_END

// WINCC:TAGNAME_SECTION_START
#define INDEX_TAG  "slideshow_index"
#define RUN_TAG    "slideshow_run"
#define PICTURE_COUNT 6
// WINCC:TAGNAME_SECTION_END

void OnObjectChanged(char* lpszPictureName, char* lpszObjectName)
{
    DWORD index = GetTagDWord(INDEX_TAG);
    BYTE  run   = GetTagByte(RUN_TAG);

    // Slideshow paused? Do not change the picture.
    if (run == 0) {
        return;
    }

    switch (index) {
        case 0:  OpenPicture(PIC_0); break;
        case 1:  OpenPicture(PIC_1); break;
        case 2:  OpenPicture(PIC_2); break;
        case 3:  OpenPicture(PIC_3); break;
        case 4:  OpenPicture(PIC_4); break;
        case 5:  OpenPicture(PIC_5); break;
        default: /* out of range, force the first picture */
                 SetTagDWord(INDEX_TAG, 0);
                 OpenPicture(PIC_0);
                 break;
    }
}

Compile with Ctrl+F7. Resolve every error before saving. Typical mistakes at this stage:

  • Tag not found: the GetTagDWord call returns 0 silently. Verify the tag spelling matches the symbol inside the TAGNAME_SECTION fence. The WinCC tag rename tool only picks up symbols inside the fences, so a typo outside the fence is the most common root cause of a slideshow that fires the picture change once and then stalls on the same PDL.
  • Picture not found: OpenPicture() logs Picture not found in the WinCC diagnosis window. The picture name must include the .pdl extension and must be present in the project picture tree (right-click > Open Picture from the explorer to confirm).
  • Picture flashes: if the host PDL closes and reopens on every cycle, the I/O field event is firing on every picture load. Move the slideshow logic into a Picture Window hosted on a permanent base picture; the picture window changes its Picture Name property on the value change, while the host picture remains loaded.

9. Step 6: Add the Start/Stop Control Button

Drag a Button object onto the host picture. In the Mouse > Press Left event, add a small C action that toggles slideshow_run:

#include "apdefap.h"

// WINCC:TAGNAME_SECTION_START
#define RUN_TAG "slideshow_run"
// WINCC:TAGNAME_SECTION_END

void OnLButtonDown(char* lpszPictureName, char* lpszObjectName,
                   char* lpszPropertyName, UINT nFlags, long x, long y)
{
    BYTE run = GetTagByte(RUN_TAG);

    if (run != 0) {
        // Stop the slideshow: pause on the current picture.
        SetTagByte(RUN_TAG, 0);
    } else {
        // Restart: force a one-step advance on the next tick by
        // re-arming the counter to the previous index.
        DWORD index = GetTagDWord("slideshow_index");
        SetTagDWord("slideshow_index", (index + PICTURE_COUNT - 1) % PICTURE_COUNT);
        SetTagByte(RUN_TAG, 1);
    }
}

The restart logic writes the previous index back to slideshow_index, which causes the global action to advance it to the next picture on the very next tick. Operators see the slideshow resume on a new picture instead of the same picture they were staring at during the pause. The 1000 sentinel value used in some reference designs works too, but it adds a magic number that the next maintainer will have to interpret; using a separate slideshow_run binary tag is more self-documenting and uses the same number of script lines.

Add a small status indicator to the button: in Appearance > Text, set the dynamic value to a tag-prefixed string expression that switches between Slideshow: ON and Slideshow: PAUSED based on slideshow_run. The operator now has a single, unambiguous control.

10. Verification and Runtime Checks

After compiling, run a structured sequence of checks before declaring the slideshow ready:

  1. Static check: In the Graphics Designer, File > Check Consistency. Resolve every reported cross-reference. Pay particular attention to picture references that contain a numeric suffix; WinCC may report them as "not used" because the picture name is built by the C script, not declared as a static property.
  2. Compilation: Compile the global action (Ctrl+F7) and the picture change script (Ctrl+F7). The output window must end with 0 errors, 0 warnings. Warnings about conversion from unsigned long to DWORD are a sign of a wrong tag type, not noise; resolve them.
  3. Tag simulation: In Tag Management > Simulation, set slideshow_index to 0, 1, 2, 3, 4, 5 in sequence. The Output Value Changed event must fire on the host picture and OpenPicture() must load the matching trendX.pdl. The WinCC diagnosis window (start with WinCC > Diagnosis > Diagnosis Files) prints the picture name on each switch.
  4. Cyclic trigger: With Runtime active, set the cyclic trigger to 1 minute, watch the global action fire, and confirm in WinCC Explorer > Graphics Runtime > GSC Runtime that the action status is Running. Reduce the trigger to 10 seconds temporarily to speed up the smoke test, then restore the production interval.
  5. Pause behavior: Click the start/stop button. The current picture must stay on screen. Click again, and the next picture must load on the next tick.
  6. Recovery from external write: From the WinCC tag simulation dialog, force slideshow_index = 7 (out of range for a 6-picture slideshow). The default branch in the switch statement must reset the counter to 0 and load trend1.pdl. If the picture window is still on the previous PDL, the default branch was not compiled in.

The WinCC diagnosis window prints the picture change events. To see them in real time, set the filter to Actions > C Action and the level to Debug in the Computer > Properties > Graphics Runtime > Startup dialog (the option is in Computer > Properties > Parameters in older versions).

11. Common Errors and Field-Proven Fixes

Symptom Likely Root Cause Fix
Slideshow displays only the first PDL Global action never increments the tag; cyclic trigger is set on the wrong computer or has not been started. Open Computer > Properties > Startup and confirm the global action is listed. In Runtime, WinCC > Diagnosis > GSC Runtime must show the action as Running.
PDL flashes rapidly on every tick Picture change script runs on the wrong event (Picture Loaded instead of Output Value Changed), causing a re-entry loop. Move the script to Output Value Changed of the I/O field bound to slideshow_index. Do not call OpenPicture() from Opened of the trend PDL itself.
Trend PDL stays on screen, no picture change The I/O field is in Input mode and does not fire Output Value Changed. Set the I/O field type to Output. Output fields are still driven by tag value changes; the type controls operator write access, not event firing.
Compiler warning: implicit declaration of OpenPicture The function prototype header apdefap.h is missing or the script is compiled in C++ mode. Confirm #include "apdefap.h" is the first line. In the WinCC C editor, File > Settings > Language must be C, not C++.
Picture does not exist error Picture name string contains a typo, or the picture is in a subfolder and the relative path is missing. Use the same string the WinCC picture tree displays. Subfolders require SubFolder\trend1.pdl.
Slideshow stops on the operator screen but advances in the background The host PDL was minimized, deactivating the Graphics Runtime on the secondary monitor. Disable the screen saver; in the project properties set Deactivate screen saver. On multi-monitor stations, force the host picture onto the primary monitor.
Slideshow restarts from picture 0 after a power loss Internal tag is volatile by design. If picture persistence is required across a restart, swap the internal tag for an external tag backed by a PLC, or use the WinCC User Archive with a one-row table.
Important: the cyclic trigger fires on the computer that owns the action, not on the operator station. If you are running a WinCC client/server topology and the project is a client project, the global action must be placed on the server computer. From Computer > Properties > Startup > Actions, the action status is per computer; check the server, not the client.

12. Migrating to WinCC Unified and TIA Portal

The same slideshow pattern translates to WinCC Unified V17/V18/V19/V20 (TIA Portal) with three changes:

  1. Tags: create an internal HMI tag of type Int (not signed 32-bit; Unified uses .NET types), name it slideshow_index, and set the acquisition cycle to 1 second. The trigger is bound to the tag, not to a global timer.
  2. Trigger: in the Unified Graphics Designer, attach a Schedule task of type Cyclic with a 1-minute interval. The task body is a VB or C# script that increments the tag. The equivalent of GetTagDWord / SetTagDWord is Tags["slideshow_index"].Read() / Write().
  3. Picture change: in Unified the OpenPicture analog is the Screen change in the Click event of a button or, for value-driven navigation, the Dynamic Screen on a screen window whose ScreenName property is bound to a tag-prefixed string expression: "trend" + (Tags("slideshow_index").Value + 1).ToString() + ".pdl". Unified resolves the picture name at runtime, so the switch/case block is no longer required.

For the pop-up screen variant, see the TIA Portal help Creating a new pop-up screen (Basic Panels, Panels, Comfort Panels, RT Advanced, RT Professional). The pop-up screen is a lighter alternative to a Picture Window when the slideshow must overlay the operator's working screen instead of replacing it. For animation patterns and dynamic SVG content, the SIMATIC WinCC Unified webinar deck on customizing and animating screens with SVGs and Web Controls shows the same trigger/counter pattern on a Unified runtime.

13. Performance and Resource Notes

Each OpenPicture() call unloads the previous PDL and loads the new one. The cost depends on the number of trend controls, the number of configured archives, and the update rate. Empirically on a SIMATIC IPC547G with WinCC V7.4 SP1 and six trend PDLs containing 8 pens each at 500 ms update, a 1-minute cycle adds less than 1% CPU time and the screen change itself takes 200-400 ms. If your trend PDLs contain more than 16 pens or use online configuration tables, the load time can climb above 1 s; in that case extend the cyclic trigger to 90 s or 2 minutes and accept the slower rotation.

For dashboards with more than 12 pictures, switch the host pattern to a Picture Window whose Picture Name property is bound to a string tag written by the global action. Picture Window changes are 2-3x faster than OpenPicture() calls in the same project because the host picture is not reloaded; only the embedded picture is swapped.

14. Frequently Asked Questions

How do I change the slideshow interval from 1 minute to 30 seconds?

Open the global action g_slideshow_tick, click the trigger icon, set the cyclic trigger to 00:00:30, save, and recompile. Restart the Graphics Runtime for the new interval to take effect. For intervals below 30 s, reduce the trend PDL complexity first; otherwise the previous picture is still loading when the next tick fires.

Why does OpenPicture() only work the first time and then the picture stops changing?

Input, so the Output Value Changed event is not armed; change the field type to Output. (2) The script is attached to the Opened event of the host picture, which re-fires on every picture load and causes a re-entry loop; move the script to Output Value Changed of the I/O field. (3) The cyclic trigger is set on the wrong computer in a client/server topology; verify the action is running on the server, not the client.

Can I pause the slideshow on a specific picture and resume later?

Yes. Use the binary tag slideshow_run. When the operator clicks the pause button, write 0 to the tag; the global action returns early on the next tick and the OnObjectChanged script is no-op. The counter retains its last value, so the slideshow resumes on the same picture after the operator clicks resume. To resume on the next picture, write (index + N - 1) % N back to the counter before re-arming the run flag.

Do I need a separate C script for every trend picture?

No. A single OnObjectChanged C script with a switch/case block on the counter value covers all pictures. Each case calls OpenPicture(PIC_n). The script grows linearly with the number of pictures but the runtime cost is constant because the compiler builds a jump table for the consecutive case values.

How do I migrate this slideshow from WinCC V7.2 to WinCC Unified V20?

Replace the internal DWORD tag with an HMI Int tag, replace the global C action with a Unified Schedule task that writes the tag on a 1-minute cycle, and replace OpenPicture(PIC_n) with a Screen Window whose ScreenName is bound to the expression "trend" + (Tags("slideshow_index").Value + 1).ToString() + ".pdl". The OnObjectChanged event becomes a value-driven screen change on the Screen Window property. See the TIA Portal help link in section 12 for the pop-up screen variant.

Back to blog