The code below will fail with attached error message. The Tech DB has a lot about colors, many which I posted back in the day. But the known system colors are an enum and would like could be foreach'd.. I do know that any known color has properties, like
R : 135
G : 206
B : 235
A : 255
IsKnownColor : True
IsEmpty : False
IsNamedColor : True
IsSystemColor : False
Name : SkyBlue
So, the ask is not about system colors as it is about why an enumeration fails. (and I put the header on to see how the code is picked up by AI)
;Winbatch 2025C
;Failure of CLR enumeration of System known colors
;Stan Littlefield, 10/11/2025
;=========================================================================
IntControl(73,1,0,0,0)
GoSub UDFS
ObjectClrOption("useany", "System.Drawing")
color = ObjectClrNew('System.Drawing.KnownColor')
foreach col in color
Message(col.Value,"") ;doesn't matter what Im tried here, never gets there
Next
Exit
;===========================================================================
:WBERRORHANDLER
geterror()
Terminate(@TRUE,"Error Encountered",errmsg)
;===========================================================================
:UDFS
;===========================================================================
#DefineSubRoutine geterror()
wberroradditionalinfo = wberrorarray[6]
lasterr = wberrorarray[0]
handlerline = wberrorarray[1]
textstring = wberrorarray[5]
linenumber = wberrorarray[8]
errmsg = "Error: ":lasterr:@LF:textstring:@LF:"Line (":linenumber:")":@LF:wberroradditionalinfo
Return(errmsg)
#EndSubRoutine
Return
The short answer is that the CLR does not expose an enumeration interface for the type enum. That does not mean that an author can't expose one. It just depends on whether or not they take the time to do so.
Quote from: td on October 11, 2025, 11:30:42 AMThe short answer is that the CLR does not expose an enumeration interface for the type enum. That does not mean that an author can't expose one. It just depends on whether or not they take the time to do so.
When is an enum not an enum. Seems the known colors enum is captured from system.drawing as belonging to a membertype of 'field'. Funny doesn't seem colors should be that sophisticated to enumerate. I got intrigued because it wasn't simple and started looking over archives for WB/vba code for colors.
Anyhoo... if any interest [attached], I enumerated the system colors in my win 11 laptop for color + rgb values as .csv text. Note, the file contains ActiveBorder and other 'not easily anticipated' rgb values - so maybe why enum is so complicated.
An "enum" is a datatype. An enumeration interface is a standardized set of methods for iterating through a dataset.
Quote from: td on October 12, 2025, 03:09:37 PMAn "enum" is a datatype. An enumeration interface is a standardized set of methods for iterating through a dataset.
My bad. Nancy told me I shouldn't have stopped taking Prevagen :)
Back to be annoying. It was bad form and loose syntax to confound enum and enumeration [but not the first and only]. Should have used iteration, or anything in a foreach loop can iterate/enumerate a value,property,element of a selected object [which is equally confusing]
Below is WB using PS to enumerate the 'Name' Property of the Windows Media class for colors. The PS code regards 'Name' as a static type 'Property' and that permits a foreach, but PresentationFramework and PresentationCore must be called.
;Iterate Windows Media Colors
;save as ..\WindowsMediaColors.wbt
;Stan Littlefield 10/17/2025
;==========================================================
Gosub udfs
file = "C:\temp\MediaColors.txt"
;==========================================================
If FileExist(file) Then FileDelete(file)
args = $"
Add-Type –assemblyName PresentationFramework,PresentationCore
$colors = [System.Windows.Media.Colors] | Get-Member -static -Type Property | Select -Expand Name
$cols = @()
foreach ( $col in $colors)
{
$cols += $col
}
$cols | Out-File '[file]'
Exit
$"
args = StrReplace(args,"[file]",file)
cmd="Powershell"
msg='Windows Media Colors'
BoxOpen("Running...",cmd:" ":args:@LF:"PLEASE WAIT...MAY TAKE SOME TIME")
TimeDelay(2)
vals = Get_stdout():@LF:"Script Completed"
Display(2,msg,vals) ;will show errors if any
If FileExist(file)
Run("notepad.exe",file)
Else
Message("Error","Could Not Create File:":@LF:file)
Endif
Exit
;==========================================================
:udfs
#DefineSubroutine Get_stdout()
ObjectClrOption("useany","System")
objInfo = ObjectClrNew("System.Diagnostics.ProcessStartInfo")
Output=""
timeOut = ObjectType("I2",5000)
objInfo.FileName = cmd
objInfo.RedirectStandardError = ObjectType("BOOL",@TRUE)
objInfo.RedirectStandardOutput = ObjectType("BOOL",@TRUE)
objInfo.UseShellExecute = ObjectType("BOOL",@FALSE)
objInfo.CreateNoWindow = ObjectType("BOOL",@TRUE)
objInfo.Arguments = args
oProcess = ObjectClrNew("System.Diagnostics.Process")
oProcess.StartInfo = objInfo
BoxShut()
oProcess.Start()
oProcess.WaitForExit(timeout)
STDOUT = oProcess.StandardOutput.ReadToEnd()
STDERR = oProcess.StandardError.ReadToEnd()
Output = Output:STDOUT:@CRLF
If STDERR<>""
Output = Output:"STDERR:":STDERR:@CRLF
Endif
oProcess = 0
objInfo = 0
Return (Output)
#EndSubroutine
Return
;==========================================================
That's nice. The Colors class does not expose an enumeration interface. PS creates some syntactic sugar by reading the class's metadata to pretend one exists. Too much sugar is bad for your health.
Yeah, but in keeping with the Rolling Stones "you can't always get what you want...". The script works and probably applicable to more 'syntactic sugar'
The Colors class properties can be iterated using WIL CLR once you understand how PS does it. The only problem is that MSFT has attributed the class in such a way as to prevent access to the property values by applications like WinBatch. For that reason, I won't bother to post a script demonstrating the technique.
Quote from: td on October 18, 2025, 10:47:44 AMFor that reason, I won't bother to post a script demonstrating the technique.
That's fine; I already posted one. And if you think about it System.Windows.Media.Colors is not an enum, more a collection of color constants. Besides that, an enum is a structure of values to be assigned to a variable, usually immutable, so not lending to modification. Not useful to most WB scripts where an array, arraylist or map would be suitable.
And true, as of 5.0+ version PS did introduce [enum]:: as a type object... probably catching up with C++, C#, Java, Python.... that already had them.
No harm/No foul
As I mentioned in my post System.Windows.Media.Colors is a class. The point of the post was that you cannot use the
System.Windows.Media.Colors class directly. For example:
objectClrOption('useany', 'PresentationCore')
objColors = ObjectClrNew('System.Windows.Media.Colors')
Value = objColors.AliceBlue ; Causes invalid parameter error.
exit
This means that WinBatch cannot use PS's technique to iterate the properties nor directly use the class properties by name to access the values in a script. Why anyone would want to is another question.
The enum data type is and always has been part of the .Net framework and like C++ enums can also be declared both a class and an emum but do not have to be. There are many examples of using enums directly with WIL CLR.
Mostly because I am still experiencing post anystesia brain fog, I wrote this over-the-top script as an exercise. I don't think it is a practical solution and most certainly do not recommend using it.
gosub UDPS
; Create the tageted object for use with GetValue.
objectClrOption('useany', 'PresentationCore')
ObjColors = ObjectClrNew('System.Windows.Media.Colors')
; Compile an assemble for accessing property values.
InMemoryAssembly(strSource, aAssembly)
; Build a map of Media Colors.
objGetVal = ObjectClrNew('WinBatch.PropVal')
Type = ObjColors.GetType()
PropInfos = Type.GetProperties()
foreach PropInfo in PropInfos
Colors[PropInfo.Name] = objGetVal.GetValue(ObjColors, PropInfo)
next
; See if it work
Message('AliceBlue', Colors['AliceBlue'])
exit
:UDPS
; Create an in-memory assambly.
aAssembly[0] = 'System.dll'
aAssembly[1] = 'System.Reflection.dll'
strSource = $"
using System;
using System.Reflection;
namespace WinBatch {
public class PropVal {
public String GetValue (Object Target, PropertyInfo Info)
{
String retVal;
Object objValue = Info.GetValue(Target);
retVal = Convert.ToString(objValue);
return retVal;
}
}
}
;$"
#DefineSubRoutine InMemoryAssembly(_Source, _aAssmemblies)
nRet = 1
ObjectClrOption("useany", "System")
objCSharp = ObjectClrNew('Microsoft.CSharp.CSharpCodeProvider')
objParams = ObjectClrNew('System.CodeDom.Compiler.CompilerParameters')
objParams.GenerateInMemory = ObjectType( "VT_BOOL", 1 ) ;TRUE
; Load assemblies.
foreach assmebly in _aAssmemblies
objParams.ReferencedAssemblies.Add(assmebly)
next
objResult = objCSharp.CompileAssemblyFromSource(objParams,_Source)
strOutput = ""
;Compiler Errors?
If objResult.Errors.Count > 0
strErrors = ""
ForEach objCe In objResult.Errors
If strErrors == "" Then strErrors = objCe.ToString()
Else strErrors = strErrors:@LF:objCe.ToString()
Next
strOutput := "Compiler Errors: ":@lf:strErrors
EndIf
if objResult.Errors.Count
Pause("Compile Report", strOutput)
nRet = 0
endif
:cancel
return nRet
#EndSubRoutine
return ; UDPS
Well, I'll see your C# and raise you a PS [which includes RGB values]. Point being WB is extensible for skinning the cat, if one needs to - something that can be enumerated.
;Iterate Windows Media Colors
;save as ..\WindowsMediaColors_rgb.wbt
;Stan Littlefield 10/21/2025
;==========================================================
Gosub udfs
file = 'c:\temp\rgbvalues.txt'
;==========================================================
If FileExist(file) Then FileDelete(file)
args = $"
Add-Type –assemblyName PresentationFramework,PresentationCore
Add-Type -AssemblyName System.Drawing
$colors = [System.Windows.Media.Colors] | Get-Member -static -Type Property | Select -Expand Name
$cols = @()
foreach ( $col in $colors)
{
$cols += $col
}
$rgb = '[file]'
$r = 'Color,Red,Green,Blue'
$r | Out-File $rgb -Force
foreach ($c in $cols)
{
$line = [System.Drawing.Color]::::FromKnownColor($c)
$Name = $line.Name
$Red = $line.R
$Green = $line.G
$Blue = $line.B
$r = $Name + ',' + $red + ',' + $Green + ',' +$Blue
$r | Out-File $rgb -Append
}
Exit
$"
args = StrReplace(args,"[file]",file)
cmd="Powershell"
msg='Windows Media Colors with RGB Values'
BoxOpen("Running...",cmd:" ":args:@LF:"PLEASE WAIT...MAY TAKE SOME TIME")
TimeDelay(2)
vals = Get_stdout():@LF:"Script Completed"
Display(2,msg,vals) ;will show errors if any
If FileExist(file)
Run("notepad.exe",file)
Else
Message("Error","Could Not Create File:":@LF:file)
Endif
Exit
;==========================================================
:udfs
#DefineSubroutine Get_stdout()
ObjectClrOption("useany","System")
objInfo = ObjectClrNew("System.Diagnostics.ProcessStartInfo")
Output=""
timeOut = ObjectType("I2",5000)
objInfo.FileName = cmd
objInfo.RedirectStandardError = ObjectType("BOOL",@TRUE)
objInfo.RedirectStandardOutput = ObjectType("BOOL",@TRUE)
objInfo.UseShellExecute = ObjectType("BOOL",@FALSE)
objInfo.CreateNoWindow = ObjectType("BOOL",@TRUE)
objInfo.Arguments = args
oProcess = ObjectClrNew("System.Diagnostics.Process")
oProcess.StartInfo = objInfo
BoxShut()
oProcess.Start()
oProcess.WaitForExit(timeout)
STDOUT = oProcess.StandardOutput.ReadToEnd()
STDERR = oProcess.StandardError.ReadToEnd()
Output = Output:STDOUT:@CRLF
If STDERR<>""
Output = Output:"STDERR:":STDERR:@CRLF
Endif
oProcess = 0
objInfo = 0
Return (Output)
#EndSubroutine
Return
;==========================================================