WPF uses a concept called “Attached Properties” to handle certain things, like when you put controls into a DockPanel. Basically, anything you put inside a DockPanel has a property “Dock” which you can set … but because the property is actually defined by the DockPanel, it doesn’t show up in PowerBoots, so you can’t just say -Dock "Bottom" when you create the child items … at least, not yet.
I’m still thinking about the best way for me to expose this in PowerBoots, because the problem is that attached properties can get attached several ways, and not just on direct children of an element. So I wanted to show you a way that you can do it for now so you can at least use the DockPanel effectively:
DockPanel -LastChildFill $true {
DockPanel -LastChildFill $true {
Button "OK" -Padding "2,0,2,0" |
ForEach-Object { $_.SetValue(
[System.Windows.Controls.DockPanel]::DockProperty,
[System.Windows.Controls.Dock]::Right)
$_
}
TextBox -HorizontalAlignment Stretch
} | ForEach-Object { $_.SetValue(
[System.Windows.Controls.DockPanel]::DockProperty,
[System.Windows.Controls.Dock]::Bottom)
$_
}
TextBox -AcceptsReturn $true -minWidth 300 -minHeight 200
}
}
A better way …
Here’s what I’m thinking about for the next release of PowerBoots (you can take this and use it now, if you like it, but I’m really looking for, uhm … better ideas). Basically, I have written a function: Set-AttachedProperty, which takes an attached property and a value, and passes through the element on the pipeline. You may want to use this in conjunction with the module I published awhile back for creating type accelerators, because it lets you run a line like this:
Which will let you substitute [DockPanel] for [System.Windows.Controls.DockPanel] … and of course, you can use it for all the types you want to use attached properties from, and it’s a real lifesaver if you need to use a bunch of them. Of course, in this particular example, we really only need to use a single attached property, so it’s enough to define that property ahead of time:
Then you can use the Set-AttachedProperty function through an alias sap, and rewrite that huge block above like this:
DockPanel -LastChildFill $true {
DockPanel -LastChildFill $true {
Button "OK" -Padding "2,0,2,0" | sap $DockProperty Right
TextBox -HorizontalAlignment Stretch
} | sap $DockProperty Bottom
TextBox -AcceptsReturn $true -minWidth 300 -minHeight 200
}
}
Pretty slick, right? And it will even print out the list of values in the error message if you invoke it with an invalid value against an enum property like dock: sap $DockProperty "" … The problem I have with it is that you have to predefine your $DockProperty variable, and you can’t just define it against the root class. So I’m trying to find a way to tweak the dynamic property generation to make it so that the pipe into | sap $DockProperty Bottom can just be a parameter to the original element: -Dock Bottom … if I can’t find that, in the worst case scenario, I’ll just add an -AttachedProperties parameter with a hashtable of $DockProperty,“Bottom” or something … what do you think?
In any case, here’s the sap function, for now, and remember to use it as part of the pipeline:
function Set-AttachedProperty {
[CmdletBinding()]
PARAM(
[Parameter(Position=0,Mandatory=$true)
[System.Windows.DependencyProperty]
$Property
,
[Parameter(Mandatory=$true,ValueFromPipeline=$true)
$Element
)
DYNAMICPARAM {
$paramDictionary = new-object System.Management.Automation.RuntimeDefinedParameterDictionary
$Param1 = new-object System.Management.Automation.RuntimeDefinedParameter
$Param1.Name = "Value"
# $Param1.Attributes.Add( (New-ParameterAttribute -Position 1) )
$Param1.Attributes.Add( (New-Object System.Management.Automation.ParameterAttribute -Property @{ Position = 1 }) )
$Param1.ParameterType = $Property.PropertyType
$paramDictionary.Add("Value", $Param1)
return $paramDictionary
}
PROCESS {
$Element.SetValue($Property, $Param1.Value)
$Element
}
}
New-Alias sap Set-AttachedProperty