WinCC C-Action OnPropertyChanged Boolean Logic Explained

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

Overview

Siemens WinCC exposes a per-property C-Action that fires every time a configured object property changes value. The script in this reference is a canonical OnPropertyChanged handler that reads three user-defined configuration properties from a custom WinCC object, evaluates a boolean expression, and writes the result to the two visibility properties @On_Vis and @Off_Vis. The exact line that confuses engineers is the nested equality comparison:

Result = ((Neg == NegRes) == Signal);

The expression is valid ISO C, has no precedence trickery, and reduces to a single boolean that can be rendered in a truth table. This article decodes that line, explains why GetPropBOOL is used in this specific handler instead of GetTagBit, and provides a refactored version that reads an entire PLC error word at once instead of polling single bits. The target audience is the WinCC V7.x and WinCC TIA (Comfort/Professional) engineer who has just inherited a custom object library and needs to understand, modify, or troubleshoot visibility logic on HMI screens.

Prerequisites

  • WinCC V7.4 SP3 / V7.5 SP2 / V7.5 SP4 or TIA Portal WinCC Professional V16-V18 with the C-Script option installed.
  • Graphics Designer license with the C-Editor add-in enabled (Options > Settings > C-Script).
  • A configured custom WinCC object exposing the user-defined properties Signal, Sig_neg, and NegRes as type BOOL.
  • Knowledge of the C-Action trigger model: scripts fire on property change events, not on tag polling unless a trigger tag is explicitly configured.
  • Knowledge of the WinCC tag namespace: internal tags, process tags, and structure tags (see the WinCC Information System under Configuration > Tags).
Note: The header apdefap.h declares the WinCC API prototypes (GetPropBOOL, SetPropBOOL, GetTagBit, GetTagWord, SetTagBit, and so on). If the script is recompiled outside Graphics Designer, that header must be on the include path or the build will fail with unresolved externals.

The C-Action Source Under Review

#include "apdefap.h"
void OnPropertyChanged(char* lpszPictureName,
                       char* lpszObjectName,
                       char* lpszPropertyName,
                       BOOL  value)
{
    BOOL Signal, Neg, NegRes, Result;

    Signal  = GetPropBOOL(lpszPictureName, lpszObjectName, "Signal");
    Neg     = GetPropBOOL(lpszPictureName, lpszObjectName, "Sig_neg");
    NegRes  = GetPropBOOL(lpszPictureName, lpszObjectName, "NegRes");

    Result  = ((Neg == NegRes) == Signal);

    SetPropBOOL(lpszPictureName, lpszObjectName, "@On_Vis",  Result);
    SetPropBOOL(lpszPictureName, lpszObjectName, "@Off_Vis", !Result);
}

Three custom properties (Signal, Sig_neg, NegRes) act as configuration inputs. The script computes Result from those inputs and assigns it to the built-in visibility properties @On_Vis and @Off_Vis. The @ prefix is the WinCC convention for system properties; the suffix _Vis marks the visibility attribute of the picture's ON and OFF states.

C Operator Semantics: == vs =

The first source of confusion is operator choice. C has two completely different operators that look almost identical in proportional fonts:

Operator Name Result Typical Use
= Assignment Value of the right-hand side Write a value into a variable
== Equality comparison 1 (TRUE) or 0 (FALSE) Test whether two values match
!= Inequality 1 (TRUE) or 0 (FALSE) Test whether two values differ

In the line Result = ((Neg == NegRes) == Signal);, the inner and outer == are both comparisons that yield 0 or 1. The single = in the outermost position is the assignment that stores that 0 or 1 into Result. There is no precedence trick: == has left-to-right associativity, so the expression is parsed as ((Neg == NegRes) == Signal) exactly as written, with no implicit grouping surprise.

Warning: A common bug in WinCC C-Actions is the accidental if (Signal = TRUE) pattern. In C, = is a valid expression that returns the assigned value, so the branch always fires. WinCC V7.x with the strict compiler option turned on (Project Properties > C-Editor > Compiler Options > Warning Level 3) will warn about this; the TIA Portal C-Editor by default does not.

Decoding the Boolean Expression

The expression ((Neg == NegRes) == Signal) can be expanded into a truth table. The intermediate == produces a 1 when Neg and NegRes match, and a 0 when they differ. The outer == then tests whether that 1-or-0 matches the value of Signal. The two inputs Sig_neg and NegRes together behave like a logical XOR enable: they are typically wired to two separate "invert" configuration flags.

Neg (Sig_neg) NegRes Signal Inner (Neg == NegRes) Result
0 0 0 1 0
0 0 1 1 1
0 1 0 0 1
0 1 1 0 0
1 0 0 0 1
1 0 1 0 0
1 1 0 1 0
1 1 1 1 1

Reading the right-hand column: Result is TRUE only when Signal agrees with the agreement of Neg and NegRes. The pattern is the standard implementation of a configurable inverted logic gate. The four boolean inputs (Signal, Sig_neg, NegRes, Result) cover all 16 possible states of three inputs, of which 8 yield Result = TRUE and 8 yield Result = FALSE. The implementation is a multiplexer that picks between Signal and !Signal based on the XOR of Sig_neg and NegRes.

Sig_neg (Neg) NegRes Signal (Neg == NegRes) == Signal Result (BOOL) @On_Vis / !@Off_Vis

GetPropBOOL vs GetTagBit vs GetTagWord

The source uses GetPropBOOL, which is the correct choice for configuration data bound to a custom object's user-defined properties. GetTagBit is the right call when the script needs a runtime process value from a tag, and GetTagWord is the right call when many bits live inside a single WORD and the script wants to avoid a per-bit round trip to the data manager.

Function Reads From Use When Typical Cost
GetPropBOOL Object's user-defined property Reading static config such as invert flags Local, no datamanager
GetTagBit Single BOOL tag in datamanager Reading one live process bit 1 datamanager lookup
GetTagByte BYTE tag Reading 8 packed bits at once 1 datamanager lookup
GetTagWord WORD tag Reading 16 packed bits at once 1 datamanager lookup
GetTagDWord DWORD tag Reading 32 packed bits at once 1 datamanager lookup

Every call to GetTagBit enters the WinCC datamanager, acquires the data lock, copies the value, and releases the lock. In a script that runs every 250 ms and reads eight error bits, that is eight locks and copies per cycle. Reading the underlying WORD once and then masking the bits with C bitwise operators (&, |, <<, >>) collapses that into one lookup. The refactored block from the source is reproduced and expanded below.

The Property System Behind the Script

Three custom properties drive the logic, and two built-in system properties carry the output.

Property Name Direction Type Source Purpose
Signal Input BOOL User-defined (config) The live or configuration signal value
Sig_neg Input BOOL User-defined (config) First inversion flag
NegRes Input BOOL User-defined (config) Second inversion flag
@On_Vis Output BOOL WinCC system Visibility of the ON-state graphics
@Off_Vis Output BOOL WinCC system Visibility of the OFF-state graphics

User-defined properties are added in the Graphics Designer by opening the object's properties dialog and selecting New under the Properties tab. WinCC stores them in the picture's .pdl file as a serialized structure; GetPropBOOL is the only public C API to read them at runtime. The system properties @On_Vis and @Off_Vis are evaluated by the WinCC Picture Renderer after every cycle; assigning to them in a C-Action takes effect on the next screen update.

Best Practice Refactor: Read the Error Word Once

Where the source mentions the common case of "different error bits of the PLC occupying the same byte, word, or double word," the following pattern should be used. The script reads the entire error WORD from the PLC and tests individual bits with a mask.

#include "apdefap.h"

#define ERR_OVERVOLTAGE   0x0001u   /* bit 0 */
#define ERR_OVERTEMP      0x0002u   /* bit 1 */
#define ERR_OVERCURRENT   0x0004u   /* bit 2 */
#define ERR_COMM_FAULT    0x0008u   /* bit 3 */
#define ERR_SAFETY        0x0010u   /* bit 4 */

BOOL bAnyError = FALSE;
WORD wError    = 0;

/* One datamanager call instead of five GetTagBit() calls */
wError = GetTagWord("PLC_DB100_DBW0_ERRORS");

if (wError & ERR_OVERVOLTAGE)  { /* overvoltage handling  */ }
if (wError & ERR_OVERTEMP)     { /* overtemperature        */ }
if (wError & ERR_OVERCURRENT)  { /* overcurrent            */ }
if (wError & ERR_COMM_FAULT)   { /* comm fault             */ }
if (wError & ERR_SAFETY)       { /* safety chain tripped   */ }

bAnyError = (wError != 0);
SetPropBOOL(lpszPictureName, lpszObjectName, "@On_Vis",  bAnyError);
SetPropBOOL(lpszPictureName, lpszObjectName, "@Off_Vis", !bAnyError);

For a 32-bit status word, use GetTagDWord and DWORD masks with the 0x00000001u notation. For a 64-bit status doubleword (WinCC V7.4 and later), use GetTagDWord on the low and high halves separately and combine. Avoid the deprecated GetTagBitWait / SetTagBitWait family for cyclic scripts, because the Wait variants block the Graphics Designer thread for up to the configured timeout and freeze the HMI when the PLC is offline.

Step-by-Step: Implementing the C-Action on a Custom Object

  1. Open the object in Graphics Designer. Right-click the custom object and choose Configuration > Properties. Add three BOOL properties named exactly Signal, Sig_neg, and NegRes (case-sensitive; WinCC matches property names literally).
  2. Wire the properties to source tags. For each new property, click the small lightbulb icon, select Tag, and pick the source. For Signal wire a live process tag such as PLC_DB100.DBX0.0. For Sig_neg and NegRes either wire to internal tags _InvertSignal and _InvertResult or set them to constant values in the object default dialog.
  3. Open the C-Editor on the @On_Vis property. Right-click @On_Vis in the property list, choose C-Action. The editor opens with the signature void OnPropertyChanged(char*, char*, char*, BOOL) prefilled.
  4. Paste the script. Copy the source body, replacing the function signature with the prefilled one if needed. Save and recompile (F7 in V7.x, the hammer icon in TIA Portal).
  5. Set the trigger. Under Trigger in the C-Editor, add the tag(s) that should cause this script to re-fire. For visibility logic, trigger on the source Signal tag with cycle 250 ms or on a 1-second standard cycle.
  6. Compile and assign. Confirm there are no errors in the output pane. WinCC V7.x will report C1010 "compiler aborted" if apdefap.h is missing; the resolution is to add the WinCC include path under Options > Settings > C-Editor > Include Paths.
  7. Runtime check. Start Runtime (RT) and toggle the source Signal tag. The ON-state graphics should appear and the OFF-state should hide, with both transitions completing within one RT cycle (typically 250 ms).

Verification and Debugging

When the C-Action does not behave as expected, work through the following matrix before assuming a logic bug.

Symptom Likely Root Cause Diagnostic Step Fix
Visibility never changes at runtime Trigger tag missing or set to a tag that does not update Open Tag Diagnosis in RT, confirm trigger tag is refreshing Add the source tag to the trigger list or switch to a 1 s standard cycle
Visibility flickers Script reads process value directly and trigger cycle is too fast Check RT logging for re-fire rate Add a debounce internal tag or use GetTagBit with a 500 ms cycle
Compile error C2065: GetPropBOOL undeclared Missing apdefap.h include path Check include paths in C-Editor settings Add %ProgramFiles(x86)%\Siemens\Automation\WinCC\aplib\ to include paths
Compile warning "function should return a value" Missing return at end of script when not using void signature Inspect function signature Match the signature: OnPropertyChanged is void and must not return a value
WinCC freezes when PLC is offline Script uses GetTagBitWait Grep the script for Wait Replace with GetTagBit or wrap the call in a quality check
Result always TRUE Property name typo, e.g. signal vs Signal Watch the property name in Graphics Designer Make the property name identical to the string in GetPropBOOL
Object does not show up in library Custom object saved without .pdl extension Check file type in library directory Save the picture as a custom object via File > Save As Custom Object

For deep debugging, add a temporary printf redirection through the WinCC logging system. WinCC V7.x routes printf output to the WinCC Diagnosis window when the project is started with the Diagnostic option enabled. In TIA Portal, use LCIDebugTrace() if available, or write a temporary log line into an internal text tag and display it on a diagnostics screen.

Edge Cases and Field-Proven Caveats

  • Boolean width. C's BOOL in WinCC is a 32-bit integer typedef. Truth tables above used 0/1 notation, but the actual value in memory is 0 or 1, not a single bit. Use the value in integer comparisons if you ever cast to int.
  • Property change events are level-triggered, not edge-triggered. If a downstream consumer of Result watches for a transition, set @On_Vis directly from a tag trigger instead of from this C-Action, because assigning the same value twice in a row does not produce a change event.
  • Multi-user editing. When two engineers open the same picture in Graphics Designer, the second saver wins and the C-Action they did not modify is overwritten verbatim. The C-Action is stored as a separate .pdl_action record, not as part of the picture body, so diffs are not shown in the standard merge tool. Use WinCC's Project Differencer to compare C-Actions across project branches.
  • Trigger cycle vs RT cycle. The script fires on the configured trigger (default 1 s). The picture is redrawn on the RT cycle (default 250 ms). The two are decoupled: it is possible to set @On_Vis faster than the picture redraws, but the user will not see the change. The default values are already chosen to keep CPU load below 20 % on a TIA Runtime PC with a 4-core CPU and 8 GB of RAM.
  • Unicode property names. If the project is migrated to WinCC V7.5 with Unicode project names enabled, property names are stored in UTF-16 and the char* pointer in OnPropertyChanged is the ANSI representation. Custom property names should remain ASCII for the lifetime of the project to avoid migration risk.
  • Return semantics in void OnPropertyChanged. Some script templates show a return at the end; this is legal in C but unnecessary for a void function. The WinCC C compiler emits warning C4715 "not all control paths return a value" only for non-void functions.
Safety: Never wire a C-Action to a safety-rated tag (SIL 2/3 or PL d/e). Visibility is a cosmetic property; the safety chain must be implemented in the PLC and read by the HMI with a separate, hardwired display tag. C-Actions can be disabled by the operator in the WinCC RT diagnostics dialog.

Frequently Asked Questions

What does ((Neg == NegRes) == Signal) actually mean in plain English?

The expression is TRUE only when Signal matches the agreement of the two configuration flags Sig_neg and NegRes. If both flags are equal (both 0 or both 1) the inner == returns 1, and that 1 must equal Signal for the result to be TRUE. If the two flags differ, the inner == returns 0, and Signal must also be 0 for the result to be TRUE.

Why use GetPropBOOL instead of GetTagBit for configuration flags?

GetPropBOOL reads user-defined object properties that are stored inside the picture, not inside the WinCC datamanager. This avoids creating three internal tags purely to act as invert flags, and it makes the custom object portable: it can be dropped into any picture with the same three configuration properties and will work without tag configuration.

Can I read multiple error bits with a single call instead of multiple GetTagBit calls?

Yes. Define the error bits as a WORD or DWORD tag in the PLC, read it once with GetTagWord or GetTagDWord, and then test the individual bits with bitwise masks such as if (wError & 0x0001u). This reduces five datamanager lookups to one and improves RT performance measurably on large projects.

The script gives a compile error that apdefap.h is missing. Where is it?

On a default WinCC V7.5 SP4 install the header is at C:\Program Files (x86)\Siemens\Automation\WinCC\aplib\apdefap.h. In TIA Portal WinCC Professional the same header ships with the installation; add the path under Project > C-Editor > Include Paths. After the include path is set, do a full rebuild of the picture with F7 in V7.x or the hammer icon in TIA.

Is it safe to call SetPropBOOL("@On_Vis", TRUE) from a C-Action on a safety-related tag?

No. @On_Vis only affects graphics rendering, not the underlying safety chain. Use a non-safety display tag and read it from the C-Action; the safety PLC must enforce the actual safety function independently. The C-Action can be disabled at runtime by the operator, so any safety state displayed by it is purely informational.

Back to blog