Which method allows convert ARGB to string as 'int, int, int'? Example. The $Main background color is set as a string '0, 100, 200'. Return $Main.BackColor gives ARGB but not string '0, 100, 200':
$Main = [System.Windows.Forms.Form] #{
BackColor = '0, 100, 200'
}
$Main.BackColor
--------------
R : 0
G : 100
B : 200
A : 255
IsKnownColor : False
IsEmpty : False
IsNamedColor : False
IsSystemColor : False
Name : ff0064c8
The To.String() method returns the result Color [A=255, R=0, G=100, B=200]
. This is not what is expected.
At the moment I am doing this:
('R', 'G', 'B').ForEach({ $Main.BackColor.$_ }) -join ', '
--------------
0, 100, 200
However, I hope that there are special methods for converting as ARGB to string, as string to ARGB. What are these methods? Thanks
However, I hope that there are special methods for converting as argb to string, as string to argb. What are these methods?
You'd think so, but to my knowledge there isn't a builtin two-way convertion for this, unfortunately.
Here are a couple of things you can do instead:
Create your own ConvertFrom-Color function:
function ConvertFrom-Color
{
param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[System.Drawing.Color[]]$InputColor
)
process {
foreach($color in $InputColor){
$color.R,$color.G,$color.B -join ', '
}
}
}
And use when needed:
PS C:\> $colorString = $Main.BackColor |ConvertFrom-Color
PS C:\> $colorString
0, 100, 200
Use an [int]
An ARGB color consists of 4 1-byte channels, which fits perfectly into a 32-bit integer. If you need to communicate a distinct color using a single argument, this is already supported:
PS C:\> $color = [System.Drawing.Color]'0,100,100'
PS C:\> $color.ToArgb()
-16751516
PS C:\> $argbValue = $color.ToArgb()
PS C:\> [System.Drawing.Color]::FromArgb($argbValue)
R : 0
G : 100
B : 100
A : 255
IsKnownColor : False
IsEmpty : False
IsNamedColor : False
IsSystemColor : False
Name : ff006464
Just like with your initial string representation, it's worth highlighting that converting from the [int] representation works both with explicit casts and implicit conversions when assigning to typed properties:
$argbValue = -16751516
# This works just fine
[System.Drawing.Color]$argbValue
# As does this
[System.Windows.Forms.Form]#{ BackColor = $argbValue }
Override Color.ToString()
Since you're interested in changing the behavior of the default string representation of System.Drawing.Color, you might as well override the ToString() method implementation:
Update-TypeData -TypeName System.Drawing.Color -MemberType ScriptMethod -MemberName ToString -Value {
$this.psobject.Properties['R','G','B'].Value -join ', '
} -Force
Now all instances of [System.Drawing.Color] will use your custom ToString() method when converted to a string:
PS C:\> "$($Main.BackColor)"
0, 100, 200
There are some nice and initiative answers for this question, but it looks like all have overlooked the correct way, using type converters. There is a ColorConverter responsible for ConvertTo and ConvertFrom String/Color:
$Color = [System.Drawing.Color]'0, 100, 200'
[System.Drawing.ColorConverter]::new().ConvertToString($Color)
# Result: 0, 100, 200
This is the same class which is used by PropertyGrid to convert the string to Color or Color and vice versa.
It will take care of A element of the color as well, for example if you create the color like 100, 0, 100, 200. This solution automatically include or ignore A portion whenever is required:
$Color = [System.Drawing.Color]'100, 0, 100, 200'
[System.Drawing.ColorConverter]::new().ConvertToString($Color)
# Result: 100, 0, 100, 200
Reza Aghaei's helpful answer shows the proper solution and Mathias R. Jessen's excellent answer shows robust alternatives.
Let me complement them with a pragmatic hack; while deriving serializable representations from for-display strings (.ToString()) is generally ill-advised, the format at hand is unlikely to change in a way that will break this:
PS> $Main.BackColor -replace '[^\d,]' # remove all chars. except digits and commas
255,0,100,200
Note: The above includes the alpha value, unlike the original input string; however, casting back to [System.Drawing.Color] works equally well.
As far as I know there is no custom string format in System.Drawing.Color. You need custom conversion. You can use your method or any other. I would use custom expression and string interpolation:
$c = [System.Drawing.Color]'0, 100, 200'
#simulate array
$c,$c | select #{N="Color";E={"$($_.R), $($_.G), $($_.B)"}}
Result:
Color
-----
0, 100, 200
0, 100, 200
Related
I'm using Powershell desired state configuration and have a separate definition document (psd1) I load into memory via
$ConfigData = Import-PowerShellDataFile "$Path"
where I'd like to update the node names based on an environment variable. The overall psd1 file shows it's type as a hashtable (name and basetype are below), then allnodes shows up as an array, then nodename as another array.
Hashtable System.Object
Object[] System.Array
Object[] System.Array
Replace doesn't persist (like below). If I try to assign it back to itself or a copy, 'The property 'nodename' cannot be found on this object. Verify that the property exists yadda'
$ConfigData.AllNodes.NodeName -replace 'vms','vmp'
or
$ConfigDataHolder.AllNodes.NodeName = $ConfigData.AllNodes.NodeName -replace 'vms','vmp'
Direct reference/assigment doesn't persist, where below's output is still the servername previously, even in a clone scenario.
$ConfigData.AllNodes.NodeName[2] = "something"
Since you're using property access on arrays in order to access values of its elements, you're taking advantage of member-access enumeration.
However, member-access enumeration - by design - only works for getting values, not for setting (updating) them.
The behavior when updating is attempted is obscure, unfortunately, as you've experienced:
When you try to assign to a member-access-enumerated property as a whole, the error message is obscure: it tells you that no such property exists, because on setting it only looks at the array object itself, even though it does find it on getting, when it looks at the arrays elements; a minimal example:
$data = #{ arr = #(#{ subarr = 1, 2 }, #{ subarr = 3, 4 }) }
# !! ERROR " property 'subarr' cannot be found on this object"
$data.arr.subarr = #(42, 43)
If you try a specific index (e.g. [2]), but the array that the index is applied to was itself obtained via member-access enumeration, the assignment in effect operates on a temporary array, and is therefore effectively discarded, quietly; a minimal example:
$data = #{ arr = #(#{ subarr = 1, 2 }, #{ subarr = 3, 4 }) }
# !! IGNORED, because .arr.subarr is the result of
# !! member-access enumeration.
$data.arr.subarr[0] = 42
The solution is to target the array elements individually for updating, either with a single index (e.g., [2]) or in a loop, one by one.
A simplified example:
$configData = #{
AllNodes = #(
#{
NodeName = #(
'Node1a',
'Node1b'
)
},
#{
NodeName = #(
'Node2a',
'Node2b'
)
}
)
}
# OK: a specific element of *both* arrays involved - .AllNodes and .NodeName -
# is targeted and can therefore be assigned to.
$configData.AllNodes[0].NodeName[1] = 'NewNode1b'
I having a trouble with a code in MAYA 2020.
I want to create a new expression into a default value of a Arnold aiUserDataInt
I need the default value of a aiUserDataInt = attribute from a geometry that I crate name "ID"
so, my code looks like this:
string $Selected[] = `ls -selection`;
for ($node in $Selected)
aiUserDataInt1.default = $Selected.id;
but I have these error:
// Error: Line 2.37: Cannot cast data of type string[] to float. //
So, I suppose default value do not accept arrays
my question would be: is there a way to convert array into float?
or what am I doing wrong?
Thanks in advance
Unfortunately that's not how mel works. You can do:
string $Selected[] = `ls -selection`;
for ($node in $Selected)
{
int $id = getAttr($node + ".id");
setAttr("aiUserDataInt1.default", $id);
}
Didn't test it, but it should work like this. You get and set attributes with getAttr() and setAttr().
Using powershell to set the HA datastores for my VMware environment. I just don't know how to replace a certain value.
$Cluster = Get-Cluster $ClusterName | Get-View
$HAInfo = $Cluster.Configuration.DasConfig
Result of $HAinfo is this:
Enabled : True
VmMonitoring : vmMonitoringDisabled
HostMonitoring : enabled
VmComponentProtecting : disabled
FailoverLevel : 1
AdmissionControlPolicy : VMware.Vim.ClusterFailoverResourcesAdmissionControlPolicy
AdmissionControlEnabled : True
DefaultVmSettings : VMware.Vim.ClusterDasVmSettings
Option : {das.ignoreRedundantNetWarning}
HeartbeatDatastore : {Datastore-datastore-2367254, Datastore-datastore-1586741}
HBDatastoreCandidatePolicy : userSelectedDs
LinkedView :
Now I'm interested in the HeartbeatDatastore, which now contains:
Type Value
---- -----
Datastore datastore-2367254
Datastore datastore-1586741
I need to replace the Value with new values. I could easily do this by just writing:
$Hainfo.HeartbeatDatastore[1].value = "newvalue"
But I can't be sure whether it contains 0, 1 or 2 values. My problem is that when for example it only contains 1 row (datastore, datastore-2367254), I don't know how I should add a new row with new values.
Not sure if this extra info is needed:
$Hainfo.HeartbeatDatastore | get-member
TypeName: VMware.Vim.ManagedObjectReference
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Type Property string Type {get;set;}
Value Property string Value {get;set;}
Please assist.
Solved it like this:
$HAInfo.HeartbeatDatastore.Clear()
$dsObj1 = New-Object VMWare.Vim.ManagedObjectReference
$dsObj1 = here I fill it with the values I converted
$HAInfo.HeartbeatDatastore += $dsObj1
$dsObj2 = New-Object VMWare.Vim.ManagedObjectReference
$dsObj2 = here I fill it with the values I converted
$HAInfo.HeartbeatDatastore += $dsObj2
Lets say, that I have this function in Javascript which can generate string based on proper configuration:
function func(config) {
// ...
}
also, let's assume, that the config variable has structure as below (all of these can be not given to function call):
{
"color": string, // can be: "blue", "red", "green"
"number": int, // can be: any number
"other": string, // can be: "x", "y"
}
How to create proper binding for this? I'm stuck with:
[#bs.deriving abstract]
type options = {
[#bs.optional]
color: [#bs.string] [ | `blue | `red | `green ]
[#bs.optional]
number: int,
[#bs.optional]
other: [#bs.string] [ | `x | `y ]
}
[#bs.module]
external func: options => string = "func";
But it does not work when trying to use like this:
let config = MyModule.config(
~color=`blue,
~number=123,
~other=`x
);
let value = MyModule.func(config);
The color and other values are integers, not strings.
This is a case of a JavaScript idiom for named parameters (objects with optional fields), needing to be adapted to the OCaml/ReasonML idiom (functions with actual labelled parameters). You would do this in three steps. Step 1, as Glenn showed, define an external for the config:
type config;
[#bs.obj] external config: (
~color:[#bs.string] [`blue | `red | `green]=?,
~number:int=?,
~other:[#bs.string] [`x | `y]=?,
unit,
) => config = "";
Step 2, bind to the JavaScript function using the JavaScript style of the config object:
[#bs.val] external func: config => string = "";
Step 3, wrap the JavaScript function binding in an OCaml-idiomatic function with labelled parameters:
let func(~color=?, ~number=?, ~other=?, ()) = ()
|> config(~color?, ~number?, ~other?)
|> func;
You can use it like this:
let result = func(~color=`blue, ());
The #bs attributes are often poorly thought out hacks that you shouldn't expect to work well with other attributes, or really with anything other than exactly what the documentation explains or shows examples of. However, if an attribute is used where it is not intended you'll usually at least get a warning about the attribute being unused, which your code does.
#bs.string in particular only works on types at the outermost level of externals, i.e. on types whose values will be passed directly to the external function. There is also a way to create JavaScript objects using external functions which also happens to use less magic and give you much more control over the API. As far as I'm aware, the only downside compared to #bs.deriving is that you can't override field names using something like #bs.as. They have to be valid OCaml identifiers.
Here's your example implemented using an external function annotated with #bs.obj:
type options;
[#bs.obj] external options : (
~color:[#bs.string] [`blue | `red | `green]=?,
~number:int=?,
~other:[#bs.string] [`x | `y]=?,
unit
) => options = "";
To use it you call it exactly as with #bs.deriving:
let config = options(~color=`blue,~number=123, ~other=`x, ());
But even with this I've encountered edge cases where integer values are passed in instead of strings. For this reason I tend to avoid the polymorphic variant attributes altogether and instead use ordinary variants along with conversion functions. This has the added benefit of being more idiomatic, blending in better and being more interoperable with non-BuckleScript code.
Here's what your example might look like using this approach:
type color = Blue | Red | Green;
let colorToString = fun
| Blue => "blue"
| Red => "red"
| Green => "green";
type other = X | Y;
let otherToString = fun
| X => "x"
| Y => "y";
[#bs.obj] external options : (
~color:string=?,
~number:int=?,
~other:string=?,
unit
) => options = "";
[#bs.module] external func: options => string = "func";
let func = (~color=?, ~number=?, ~other=?, ()) =>
func(options(
~color = ?Belt.Option.map(color, colorToString),
~number?,
~other = ?Belt.Option.map(other, otherToString),
()));
let config = func(~color=Blue,~number=123, ~other=X, ());
This is because in reality, those values are variants, instead of trying to make it exactly like JavaScript, I would rather try something more idiomatic to Reason:
type color = Blue | Green | Red;
type coords = X | Y;
type config = {
color,
coords,
number: int
};
let func = (config: config) => "something"
And then inside your function actually return strings (if that is what you really need) by pattern matching on the correct values provided to config.
See the working code here.
Hope it helps!
I'm having trouble with adding hashtables to a multidimensional array. I coded the following:
$Data = #{BIBs = #(
#{$BIB = #{BIBName=$BIBName},
#{Standort = $Standort},
#{Bücher = #(
#{BuchName = $BuchName;
Autor = $Autor
})
}}
)}
This code is functioning and creates an output, which I store in a JSON:
{
"BIBs": [
{
"BIB1": [
{
"BIBName": "123"
},
{
"Standort": "123"
},
{
"Bücher": [
{
"Autor": "123",
"BuchName": "123"
}
]
}
]
},
{
"BIB2": [
{
"BIBname": "345"
},
{
"Standort": "345"
},
{
"Bücher": [
{
"Autor": "345",
"Buchname": "345"
}
]
}
]
}
]
}
I have extra code which adds another hashtable to array "BIBs" as you can see.
$jsonfile = "C:\Skripte\bibV2-1000.json"
$Data = Get-Content $jsonfile | ConvertFrom-Json
$Data.BIBs += New-Object -TypeName PSObject -Property #{
$BIB = #{BIBname=$BIBName}, #{Standort=$Standort},
#{Bücher = #(#{Buchname=$BuchName;Autor=$Autor})}
}
When the output is like above, I'm not able to add another hashtable to "Bücher". I checked the type of "Bücher" with
$data.BIBs.BIB1.Bücher.GetType()
and it's actually an array:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
I tried
$Data.BIBs.BIB1.Bücher += #{Person="Max";Alter="35"}
to add a new hashtable, like I did with "BIB2", but I am getting the error:
The property 'Bücher' cannot be found on this object. Verify that the property
exists and can be set.
At line:5 char:1
+ $data.BIBs.BIB1.Bücher += #{Motor="asdf";pers="345"}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyAssignmentException
Do you know how I add #{Person="Max";Alter="35"} to "Bücher"?
tl;dr
Setting a key / property value via member-access enumeration is not supported (see below).
Instead, you must obtain the specific object whose .Bücher property you want to modify explicitly:
($Data.BIBs.BIB1 | ? Bücher).Bücher += #{ BuchName='neues Buch'; Autor='Johann Doe' }
Note: This assumes that:
only one element of array $Data.BIBs.BIB1 has a .Bücher property (key)
that, if the property / key does exist, it is nonempty and therefore is "truthy" in a Boolean context, such as the expression passed to ? (Where-Object); like member-access enumeration, this simplified Where-Object syntax - ? Bücher instead of ? { $_.Bücher } - is a PSv3+ feature called comparison statement.
Mathias R. Jessen has provided the crucial pointer in comments on the question:
PowerShell has an intentional asymmetry with respect to dot notation across collection-valued properties for getting values vs. setting values.
On getting, PSv3+ applies member-access enumeration, which, in a nutshell, allows you to access a property on a collection and implicitly get that property's value from each element IN that collection, with the results getting collected in an array.
On setting, member-access enumeration is not applied; the rationale is that the risk of unintentional modification of data is too high - see GitHub issue #5271 and in particular this comment by a core member of the PS team.
The unfortunate aspect is that the current error message doesn't tell you that.
It stems from the fact that when attempting to set a property at the collection level, the property is looked for only directly on the collection (instead of on its elements), where it (usually) doesn't exist.
Let's take a look at a simplified example:
$data = #{ # a hashtable
a = ( # array of hashtables
#{ b1 = 'b1' },
#{ b2 = 'b2' },
#{ b3 =
#{ b31 = 'b31' }, #{ b32 = 'b32' } # array of hashtables
}
)
}
On getting, everything works fine:
PS> $data.a.b3
Name Value
---- -----
b31 b31
b32 b32
Even though $data.a is an [object[]] array, an object (hashtable) with property .b3 was found among its elements, and that object's .b3 value is output.
This is member-access enumeration in action (although the more typical uses is case for the property to exist on all elements of the array and for the individual values to be collected in an [object[]] array).
On setting, PowerShell forgoes member-access enumeration and therefore unsuccessfully looks for a .b3 property only directly on the [object[]] instance that is $data.a and, of course, arrays have no .b3 property:
PS> $data.a.b3 += #{ b33 = 'b33' } # Try to add an element; !! FAILS
The property 'b3' cannot be found on this object.
Verify that the property exists and can be set.
...