
Auto-Syncing Checkbox States Using Dependent Waves in Igor Pro

guoqilin
Question
I have a panel with four checkboxes: `p` (master), `px`, `py`, and `pz`. The `p` checkbox acts as a **master checkbox**, which controls the others as follows:
- Master control: Checking/unchecking `p` should check/uncheck all dependent checkboxes (`px`, `py`, `pz`).
- Auto-update master:
- If any dependent checkbox is unchecked, `p` should uncheck.
- If all dependent checkboxes are checked, `p` should check.
My current implementation achieves this logic but uses a wave (`pDependentCheckBoxeValues`) to track the dependent checkbox states. However, this wave requires manual updates when the dependent checkboxes change.
How can I make `pDependentCheckBoxeValues` a _dependent wave_ that automatically updates when its source variables (`pxCheckBoxValue`, `pyCheckBoxValue`, `pzCheckBoxValue`) change, without explicitly rewriting it each time?
Current Code:
#pragma TextEncoding = "UTF-8" #pragma rtGlobals=3 // Use modern global access method and strict wave access #pragma DefaultTab={3,20,4} // Set default tab width in Igor Pro 9 and later Function Main() Initialize() Execute "OrbitalSelectionPanel()" End Function Initialize() Variable/G pCheckBoxValue = 0 Variable/G pxCheckBoxValue = 0 Variable/G pyCheckBoxValue = 0 Variable/G pzCheckBoxValue = 0 Make/O pDependentCheckBoxeValues = {pxCheckBoxValue, pyCheckBoxValue, pzCheckBoxValue} End Function pCheckBoxProc(cba) : CheckBoxControl STRUCT WMCheckboxAction &cba switch( cba.eventCode ) case 2: // mouse up Variable checked = cba.checked NVAR pxCheckBoxValue = root:pxCheckBoxValue NVAR pyCheckBoxValue = root:pyCheckBoxValue NVAR pzCheckBoxValue = root:pzCheckBoxValue WAVE pDependentCheckBoxeValues = pDependentCheckBoxeValues pxCheckBoxValue = checked pyCheckBoxValue = checked pzCheckBoxValue = checked pDependentCheckBoxeValues = {pxCheckBoxValue, pyCheckBoxValue, pzCheckBoxValue} break case -1: // control being killed break endswitch return 0 End Function pCheckBoxSync(cba) : CheckBoxControl STRUCT WMCheckboxAction &cba switch( cba.eventCode ) case 2: // mouse up Variable checked = cba.checked NVAR pxCheckBoxValue = pxCheckBoxValue NVAR pyCheckBoxValue = pyCheckBoxValue NVAR pzCheckBoxValue = pzCheckBoxValue NVAR pCheckBoxValue = pCheckBoxValue WAVE pDependentCheckBoxeValues = pDependentCheckBoxeValues pDependentCheckBoxeValues = {pxCheckBoxValue, pyCheckBoxValue, pzCheckBoxValue} pCheckBoxValue = sum(pDependentCheckBoxeValues) == numpnts(pDependentCheckBoxeValues) break case -1: // control being killed break endswitch return 0 End Window OrbitalSelectionPanel() : Panel PauseUpdate; Silent 1 // building window... Variable CheckBoxwidth = 40.00 Variable CheckBoxheight = 12.00 Variable VerticalDistance = 20.00 Variable HorizontalDistance = 40.00; Variable XPosition = 20.00 Variable YPosition = 20.00 NewPanel /W=(500.0,300.0,800.0,400.0) SetDrawLayer UserBack CheckBox pCheckBox, pos={XPosition+0*HorizontalDistance,YPosition+1*VerticalDistance},size={CheckBoxwidth,CheckBoxheight},proc=pCheckBoxProc,title="p", variable=pCheckBoxValue,help={"Select all p orbitals"} CheckBox pxCheckBox,pos={XPosition+1*HorizontalDistance,YPosition+1*VerticalDistance},size={CheckBoxwidth,CheckBoxheight},proc=pCheckBoxSync,title="px",variable=pxCheckBoxValue CheckBox pyCheckBox,pos={XPosition+2*HorizontalDistance,YPosition+1*VerticalDistance},size={CheckBoxwidth,CheckBoxheight},proc=pCheckBoxSync,title="py",variable=pyCheckBoxValue CheckBox pzCheckBox,pos={XPosition+3*HorizontalDistance,YPosition+1*VerticalDistance},size={CheckBoxwidth,CheckBoxheight},proc=pCheckBoxSync,title="pz",variable=pzCheckBoxValue EndMacro
First, I would avoid using global variables and especially waves (which are definitely visible to users) to store control variables. This is not necessary unless you explicitly use the variables / waves for some other task unrelated to the panel. Use
ControlInfo/W=PanelName CtrlName
to inquire the state of each checkbox instead. Call
CheckBox CrtlName ,win=PanelName ,value=setTheValue
to set the value in the panel.
Otherwise, I find no problem with your approach. pCheckBoxSync() is exactly the way to go about this and to implement your conditions. Just set the value of each Checkbox within the mouse-up even while doing (p was pressed => check/uncheck all px,py,pz) and (all px,py,pz active / inactive => adjust p). I think doing it 'automatically' (whatever that means) will just lead to much pain down the road. To discern which of the check-boxes was pressed you can...
June 17, 2025 at 03:57 am - Permalink
Hi chozo,
Thank you for taking the time to review my implementation and for your insightful suggestions! Your advice on avoiding global variables/waves and directly querying/setting control states via
ControlInfo
andCheckBox
commands was exactly what I needed to streamline the code. I’ve refactored the solution to:Eliminate global variables and use panel control states directly.
Simplify logic by unifying checkbox handlers and leveraging
WhichListItem()
for dynamic checks.Improve robustness by enforcing panel-name constants and consistent loops.
Your point about avoiding "automatic" dependencies (like wave-based tracking) was particularly valuable—it led me to a cleaner, more maintainable approach. The final version now handles all sync logic within
pCheckBoxSync()
, triggered by a singleCheckBoxControl
function.I’ve shared the refined code below in case it’s useful for others. Thanks again for your guidance—it’s greatly appreciated!
June 17, 2025 at 08:03 am - Permalink
Great that it worked for you! :-) Maybe one more thing you want to change, especially if you are sharing your code with other users: Avoid giving generic names to functions and global constants such as "panelName" and "Main". As soon as another function / constant / wave with this name exists anywhere in your current Igor session you will get an error. You may use the 'static' prefix for functions / constants which are never exposed to users to prevent this. Here is your code boiled down to the essentials:
Or if you have Igor 9:
June 17, 2025 at 08:20 pm - Permalink
Hi chozo,
Thank you for the follow-up and for sharing these excellent refinements! Your points about avoiding generic names and leveraging Igor’s newer features (like
for (str : list)
in Igor 9) are incredibly valuable—I especially appreciate how you’ve:Eliminated naming conflicts by using descriptive prefixes (
OrbitalSelect_*
) and avoiding globals.Streamlined the panel setup with
ModifyControlList
, making it more maintainable.Highlighted Igor 9 optimizations like the modern loop syntax, which I’ll adopt moving forward.
Your "boiled-down" version is a masterclass in clean Igor Pro coding! I’ve already incorporated these changes into my project and will definitely use this approach for future panels.
Thanks again for taking the time to share your expertise—it’s made a real difference in the quality of my code.
June 18, 2025 at 07:52 am - Permalink
Programming is an intellectual labor that brings physical and mental pleasure to people.
June 18, 2025 at 08:22 am - Permalink