1

I need a listview that can support a Column header with two lines of text. I have spent a lot of time trying to use add_DrawColumnHeader to create that 2nd line. I have no issues creating it in c# but powershell seems to be elusive.

when loading the e.Graphics.Drawstring with the Rectangle I get this error: Cannot convert argument "point", with value: "{X=60,Y=8,Width=60,Height=8}", for "DrawString" to type "System.Drawing.PointF"

When I check to see if the Overload is available, I see this, so it should work:

OverloadDefinitions
-------------------
void DrawString(string s, System.Drawing.Font font, System.Drawing.Brush brush, float x, float y)
void DrawString(string s, System.Drawing.Font font, System.Drawing.Brush brush, System.Drawing.RectangleF layoutRectangle)
void DrawString(string s, System.Drawing.Font font, System.Drawing.Brush brush, System.Drawing.RectangleF layoutRectangle, System.Drawing.StringFormat format)
void DrawString(string s, System.Drawing.Font font, System.Drawing.Brush brush, System.Drawing.PointF point)
void DrawString(string s, System.Drawing.Font font, System.Drawing.Brush brush, float x, float y, System.Drawing.StringFormat format)
void DrawString(string s, System.Drawing.Font font, System.Drawing.Brush brush, System.Drawing.PointF point, System.Drawing.StringFormat format)

If I use the Visual Studio Powershell Form I don't get a error, but I don't get columns either.. LOL

This is a simplified example of my code:

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

$form = New-Object System.Windows.Forms.Form
$form.Text = "ListView with Custom Column Headers"
$form.Size = New-Object System.Drawing.Size(400, 300)

$listView1 = New-Object System.Windows.Forms.ListView
$listView1.Location = New-Object System.Drawing.Point(10, 10)
$listView1.Size = New-Object System.Drawing.Size(380, 200)
$listView1.View = [System.Windows.Forms.View]::Details
$listView1.OwnerDraw = $true

# Add columns
$columnHeader1 = New-Object System.Windows.Forms.ColumnHeader
$columnHeader1.Tag = "First Line`nSecond Line"

$columnHeader2 = New-Object System.Windows.Forms.ColumnHeader
$columnHeader2.Tag = "Third Line`nFourth Line"

$listView1.Columns.AddRange(@($columnHeader1, $columnHeader2))

# Add items for demonstration
$listView1.Items.Add("Item 1") | Out-Null
$listView1.Items[0].SubItems.Add("Subitem 1") | Out-Null

$listView1.Items.Add("Item 2") | Out-Null
$listView1.Items[1].SubItems.Add("Subitem 2") | Out-Null

$listView1.Items.Add("Item 3") | Out-Null
$listView1.Items[2].SubItems.Add("Subitem 3") | Out-Null

# Subscribe to the DrawColumnHeader event
$listView1.add_DrawColumnHeader({
    param($sender, $e)
    $e.DrawDefault = $false
    $Global:ee = $e

    # Draw two rows of text
    $sf = New-Object System.Drawing.StringFormat
    $sf.LineAlignment = [System.Drawing.StringAlignment]::Center
    $sf.Alignment = [System.Drawing.StringAlignment]::Center
    $HalfHeight = $e.Bounds.Height / 2
    $TopHeightHalf = $e.Bounds.Top + $e.Bounds.Height / 2
$firstLineRect = New-Object System.Drawing.Rectangle ($e.Bounds.Left, $e.Bounds.Top, $e.Bounds.Width, $e.Bounds.Height / 2)
$secondLineRect = New-Object System.Drawing.Rectangle ($e.Bounds.Left,$e.Bounds.Top + $e.Bounds.Height / 2 , $e.Bounds.Width, $HalfHeight)

    $e.Graphics.DrawString("First Line", $listView1.Font, [System.Drawing.Brushes]::Black, $firstLineRect, $sf)
    $e.Graphics.DrawString("Second Line", $listView1.Font, [System.Drawing.Brushes]::Black, $secondLineRect, $sf)
})

# Add ListView control to the form
$form.Controls.Add($listView1)

# Show the form
$form.ShowDialog() | Out-Null
1
  • 1
    Might be a typo in your MRE, but $firstLineRect = New-Object System.Drawing.Rectangle ($e.Bounds.Left, $e.Bounds.Top, $e.Bounds.Width, $e.Bounds.Height / 2) needs to be $firstLineRect = New-Object System.Drawing.Rectangle ($e.Bounds.Left, $e.Bounds.Top, $e.Bounds.Width, ($e.Bounds.Height / 2) ) otherwise you get "Method invocation failed because [System.Object[]] does not contain a method named 'op_Division'." - powershell is treating it as ( (left, top, height, width) / 2 ) rather than ( left, top, height, (width/2) ) (similar issue for secondLineRect = too... Commented Mar 21, 2024 at 22:40

1 Answer 1

1

Short Answer

You're missing an F - change:

$firstLineRect = New-Object System.Drawing.Rectangle ($e.Bounds.Left, $e.Bounds.Top, $e.Bounds.Width, ($e.Bounds.Height / 2))

to

$firstLineRect = New-Object System.Drawing.RectangleF ($e.Bounds.Left, $e.Bounds.Top, $e.Bounds.Width, ($e.Bounds.Height / 2))
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>    ^ RectangleF

Long Answer

This appears to be caused by a difference in the way PowerShell does type coercion versus C# - your code is passing parameters to DrawString with the following types:

$e.Graphics.DrawString(
    "First Line",                    # System.String
    $listView1.Font,                 # System.Drawing.Font
    [System.Drawing.Brushes]::Black, # System.Drawing.SolidBrush
    $firstLineRect,                  # System.Drawing.Rectangle
    $sf                              # System.Drawing.StringFormat
)

There's no exact match for these parameters though - the overloads of DrawString are:

write-host ($e.Graphics.DrawString | out-string)

OverloadDefinitions
-------------------
void DrawString(string s, System.Drawing.Font font, System.Drawing.Brush brush, float x, float y)
void DrawString(System.ReadOnlySpan[char] s, System.Drawing.Font font, System.Drawing.Brush brush, float x, float y)
void DrawString(string s, System.Drawing.Font font, System.Drawing.Brush brush, System.Drawing.PointF point)
void DrawString(System.ReadOnlySpan[char] s, System.Drawing.Font font, System.Drawing.Brush brush, System.Drawing.PointF point)
void DrawString(string s, System.Drawing.Font font, System.Drawing.Brush brush, float x, float y, System.Drawing.StringFormat format)
void DrawString(System.ReadOnlySpan[char] s, System.Drawing.Font font, System.Drawing.Brush brush, float x, float y, System.Drawing.StringFormat format)
void DrawString(string s, System.Drawing.Font font, System.Drawing.Brush brush, System.Drawing.PointF point, System.Drawing.StringFormat format)
void DrawString(System.ReadOnlySpan[char] s, System.Drawing.Font font, System.Drawing.Brush brush, System.Drawing.PointF point, System.Drawing.StringFormat format)
void DrawString(string s, System.Drawing.Font font, System.Drawing.Brush brush, System.Drawing.RectangleF layoutRectangle)
void DrawString(System.ReadOnlySpan[char] s, System.Drawing.Font font, System.Drawing.Brush brush, System.Drawing.RectangleF layoutRectangle)
void DrawString(string s, System.Drawing.Font font, System.Drawing.Brush brush, System.Drawing.RectangleF layoutRectangle, System.Drawing.StringFormat format)
void DrawString(System.ReadOnlySpan[char] s, System.Drawing.Font font, System.Drawing.Brush brush, System.Drawing.RectangleF layoutRectangle, System.Drawing.StringFormat format)

The C# compiler realises System.Drawing.SolidBrush inherits from System.Drawing.Brush, and it also finds the implicit conversion operator for RectangleF.Implicit(Rectangle to RectangleF) and it uses this to invoke the following overload:

void DrawString(
    string s,
    System.Drawing.Font font,
    System.Drawing.Brush brush,
    System.Drawing.RectangleF layoutRectangle,
    System.Drawing.StringFormat format
)

PowerShell, on the other hand, seems to be trying to bind to this overload of DrawString for... reasons (basically it's not 100% identical to C#):

void DrawString(
    string s,
    System.Drawing.Font font,
    System.Drawing.Brush brush,
    System.Drawing.PointF point,        # <--- PointF
    System.Drawing.StringFormat format
)

and the error is saying it can't convert $firstLineRect (a System.Drawing.Rectangle) to a System.Drawing.PointF for the point parameter.

If you explicitly assign a System.Drawing.RectangleF to $firstLineRect PowerShell will select the same overload as C# and you're golden...

... but don't forget to specifically assign a System.Drawing.RectangleF (with the F again) to $secondLineRect as well otherwise you're back to square one...


Update

You could also explicitly cast to System.Drawing.Rectangle and PowerShell will invoke the appropriate conversion operator fine:

$r = new-object System.Drawing.Rectangle(10, 10, 10, 10)
[System.Drawing.RectangleF] $r

Location : {X=10, Y=10}
Size     : {Width=10, Height=10}
X        : 10
Y        : 10
Width    : 10
Height   : 10
Left     : 10
Top      : 10
Right    : 20
Bottom   : 20
IsEmpty  : False

but it seems it doesn't consider the implicit conversion operator during overload selection :-(

To use this in your code:

$e.Graphics.DrawString(
    "First Line",
    $listView1.Font,
    [System.Drawing.Brushes]::Black,
    [System.Drawing.RectangleF] $firstLineRect, # <-- explicitly convert to RectangleF
    $sf
)
Sign up to request clarification or add additional context in comments.

4 Comments

thank you so much, I never know you could force an overload like that,..
This leads into another small problem, that I will start researching, The Two Line are crammed into the header space. How does one increase the Header Height to accommodate to lines of test? My Thought was maybe a large fond for the header, then 1/2 size fonts for the test....
The Dual font tricked work.
[System.Drawing.RectangleF] $firstLineRect only works because the RectangleF type has a predefined conversion operator for Rectangle -> RectangleF (see the link). It’s not really “forcing an overload” as such - it’s just passing a slightly different set of typed parameters so powershell’s heuristics select a different overload (it won’t work for arbitrary types in general - Rectangle to PointF, for example).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.