OT: CLR and System Colors

Started by spl, October 11, 2025, 09:08:41 AM

Previous topic - Next topic

spl

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
Stan - formerly stanl [ex-Pundit]

td

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.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

spl

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.
Stan - formerly stanl [ex-Pundit]

td

An "enum" is a datatype. An enumeration interface is a standardized set of methods for iterating through a dataset.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

spl

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 :)
Stan - formerly stanl [ex-Pundit]

spl

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
;==========================================================
Stan - formerly stanl [ex-Pundit]

td

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.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

spl

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' 
Stan - formerly stanl [ex-Pundit]

td

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. 
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

spl

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
Stan - formerly stanl [ex-Pundit]

td

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.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

td

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

"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

spl

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
;==========================================================
Stan - formerly stanl [ex-Pundit]

SMF spam blocked by CleanTalk