0

I want to create a Powershell GUI with Text over a Progressbar, for example I use to count files in a directory, but the Text is flickering. The idea I got from here: PowerShell - Add text to ProgressBar (GUI). The updated label works, but since it doesn't have a true transparent background I cannot lay it over the bar. Setting the Doublebuffer is not helping, any tips on how to improve?

This is the example code:

$code = @"
    using System;
    using System.Windows.Forms;

    public class DoubleBufferedForm : System.Windows.Forms.Form
    {
        public DoubleBufferedForm()
        {
            this.DoubleBuffered = true;
        }
    }
"@

$Type = [DoubleBufferedForm]
if(!$Type)
{
    Add-Type -TypeDefinition $code -ReferencedAssemblies System.Windows.Forms, System.Drawing, System.Data, System.ComponentModel
}
Add-Type -AssemblyName System.Windows.Forms

[System.Windows.Forms.Application]::EnableVisualStyles()
[void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void][Reflection.Assembly]::LoadWithPartialName("System.Drawing")

$Path = "C:\Windows"
$Title = "Directory Usage Analysis: $Path"

$ObjForm = New-Object DoubleBufferedForm
$ObjForm.Text = $title
$ObjForm.Height = 200
$ObjForm.Width = 400
$ObjForm.BackColor = "White"
$ObjForm.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedSingle
$ObjForm.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen

$ProgressBar = New-Object System.Windows.Forms.ProgressBar
$ProgressBar.Style = 'Continuous'
$ProgressBar.Width = $ObjForm.Width - 100
$ProgressBar.Height = 20
$ProgressBar.Left = ($ObjForm.Width - $ProgressBar.Width) / 2
$ProgressBar.Top = 40
$ObjForm.Controls.Add($ProgressBar)

$x = ($Progressbar.Width / 2)
$y = ($Progressbar.Height / 100)

$ObjLabel = New-Object system.Windows.Forms.Label
$ObjLabel.AutoSize = $true
$ObjLabel.Left = ($ObjForm.Width - $Label.Width) / 2
$ObjLabel.Top = 70
$ObjLabel.Location = New-Object System.Drawing.Size($x, $y)

$ObjForm.controls.add($ObjLabel)

$SyncHash = [hashtable]::Synchronized(@{ Form = $ObjForm; ProgressBar = $ProgressBar; Label = $ObjLabel ; path = $path})
$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.ThreadOptions = "ReuseThread"
$Runspace.Open()
$Runspace.SessionStateProxy.SetVariable("SyncHash", $SyncHash)
$Worker = [PowerShell]::Create().AddScript({
    $files = @([System.IO.Directory]::GetFileSystemEntries($SyncHash.path,'*'))
    for ($i = 0; $i -le $files.count; $i++) {
            start-sleep -Milliseconds 100
            [int]$pct = ($i/$files.count)*100
            $text = "$pct %"
            
            $Font = New-Object System.Drawing.Font('Arial',10,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))
            $brush1 = New-Object Drawing.SolidBrush([System.Drawing.Color]::Black)
            
            $PBCG = $SyncHash.ProgressBar.CreateGraphics()
            $x = ($($SyncHash.ProgressBar.Width) / 2) - ([int]$PBCG.MeasureString($text, $Font).Width / 2)
            $y = ($($SyncHash.ProgressBar.Height) / 2) - ([int]$PBCG.MeasureString($text, $Font).Height / 2)
            $PointF = [System.Drawing.PointF]::new($x, $y)    
    
            $SyncHash.ProgressBar.Value = $pct
            $SyncHash.label.text = "Completed: $pct %"
            
            $SyncHash.Form.Refresh()
            $PBCG.DrawString($text, $Font, $brush1, $PointF)
        }
        $SyncHash.Form.Refresh()
        start-sleep 5
        $SyncHash.Form.Close()
    })
$Worker.Runspace = $Runspace
$Worker.BeginInvoke() | out-null
$ObjForm.ShowDialog()


I already tried it with different approaches of the Runspace and Doublebuffer but I'm not understanding anything from the .NET stuff I could find.

This also didn't help: Anyway to remove text flickering in this custom ProgressBar class?

Using WPF would be too complicated for me, since I already have a complete Form, where I just want to implement this bar.

JudgeFudge
  • 63
  • 2
  • 9

1 Answers1

0

You can handle WM_PAINT and then use TextRenderer and draw desired text on the ProgressBar.

Example - PowerShell Show Text on ProgressBar - Flicker free

using assembly System.Windows.Forms
using namespace System.Windows.Forms
using namespace System.Drawing
$assemblies = "System.Windows.Forms", "System.Drawing"
$code = @"
using System.Drawing;
using System.Windows.Forms;
public class MyProgressBar : ProgressBar
{
    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);
        if (m.Msg == 0xF /*WM_PAINT*/)
        {
            using (var g = CreateGraphics())
                TextRenderer.DrawText(g, Text,
                   Font, ClientRectangle, ForeColor);
        }
    }
}
"@
Add-Type -ReferencedAssemblies $assemblies -TypeDefinition $code -Language CSharp 
$form = [Form] @{
    ClientSize = [Point]::new(400,100);
    Text = "ProgressBar Text Sample";
}    
$form.Controls.AddRange(@(
    ($myProgressBar1 = [MyProgressBar] @{Location = [Point]::new(10,40);
        ForeColor = [Color]::Black;
        Width = 300; 
        Height = 50;})
))
$timer = [Timer] @{Interval=100; Enabled=$false}
$timer.add_Tick({param($sender,$e)
    if($myProgressBar1.Value -lt 100){
        $myProgressBar1.Value+=1
        $myProgressBar1.Text = $myProgressBar1.Value.ToString();
    }
})
$form.add_Load({param($sender,$e) $timer.Start()});
$null = $form.ShowDialog()
$timer.Dispose()
$form.Dispose()
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398