Question Gui-configuration dynamique de boutons

Plus d'informations
il y a 16 ans 6 mois #5227 par crystof
Bonjour à tous et merci pour ce forum.

Je voudrais me construire une interface-gui avec des boutons qui puissent êtres configurés dynamiquement via un xml.
Mais Voila mon soucis, j'arrive parfaitement à créer des boutons, ce n'est pas compliqué, mais je ne parviens pas à leurs associer une action - handler.
j'obtiens une erreur
\"Cast non valide de 'System.String' en 'System.EventHandler'\"

Si vous avez une idée ou quelques pistes ... Merci d'avance.

voici mon code
1- le xml (simplissime :-))
[code:1]
<TheGui>
<Button name=\"QUIT\" action=\"$TheEnd\"/>
</TheGui>
[/code:1]

1- le ps (pareil :-))
[code:1]
Param ([switch]$debug)
if ($debug) {$debugPreference=\"Continue\"}

Write-Debug \"Starting script in debug mode\"
Set-PSDebug -strict

[reflection.assembly]::loadwithpartialname(\"System.Windows.Forms\"«») | Out-Null
[reflection.assembly]::loadwithpartialname(\"System.Drawing\"«») | Out-Null

$global:Buttons=@{} #Hashtable, Button name/action association

#=== Func : Read xml config file
function ReadConfig {
$txtPath=\".\Gui.xml\"
if( ($txtPath -ne \"\"«») -and ($txtPath -notlike \"[ ]*\"«») ){ #-and (Test-Path $txtPath) ){
$global:MObj = [xml](Get-Content $txtPath)

# read xml
$global:MObj.TheGui.Button | ForEach-Object {
$global:Buttons[$_.name]+=@($_.action)
}
}else{
write-debug \"file not found\"
}
}

#=== HANDLERS
$TheEnd=
{
$form1.close()
}

#=== FORMS
$form1 = New-Object System.Windows.Forms.Form
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 121
$System_Drawing_Size.Height = 306
$form1.ClientSize = $System_Drawing_Size

$panel = New-Object System.Windows.Forms.Panel
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 93
$System_Drawing_Size.Height = 262
$panel.Size = $System_Drawing_Size
$panel.TabIndex = 23
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 12
$System_Drawing_Point.Y = 12
$panel.Location = $System_Drawing_Point
$form1.Controls.Add($panel)

#=== Process xml config
ReadConfig
$btns = new-object System.Collections.ArrayList
if ($buttons -ne $null) {
$btnOffset = 0
foreach ($action in $buttons.GetEnumerator()) {
$btn = New-Object windows.forms.Button
$btn.Text = $action.key
$btn.top = $btnOffset
$btn.Width = 60
$exprString = '$action.value'
$scriptBlock = Invoke-Expression $exprString
$btn.add_Click($($scriptBlock))
$panel.Controls.Add($btn)
$btnOffset += 30
$btns += $btn
Write-Debug \"=Add btn $($btn.Text);$($exprString);\"
}
}

#=== DISPLAY
$form1.ShowDialog()| Out-Null
[/code:1]<br><br>Message édité par: crysto444, à: 31/08/09 16:38

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 16 ans 6 mois #5230 par Laurent Dardenne
Salut,
tu dois créer un scriptblock :
[code:1]
$code=\&quot;get-process\&quot;
$sbBouton=$ExecutionContext.InvokeCommand.NewScriptBlock($code)
[/code:1]
ou en plus explicite :
[code:1]
#On cast un scriptblock en un délégué EventHandler(sender,args)
[System.EventHandler] $EventHandlerBtn = $ExecutionContext.InvokeCommand.NewScriptBlock($code)
[/code:1]
Invoke-expression retourne un PSobjet, je crois.

Si dans ton XML tu veux une indirection, utiliser le contenu du champs comme un nom de variable:
[code:1]
$FormName=\&quot;FrmMain\&quot;
$code='$FormName.Bouton'
$sb= $ExecutionContext.InvokeCommand.NewScriptBlock(
$ExecutionContext.InvokeCommand.ExpandString($Code)
)
$sb
[/code:1]
Enfin sache que la ligne suivante
[code:1]
$global:Buttons[$_.name]+=@($_.action)
[/code:1]
crée un nouveau tableau à chaque itération, si tu veux qq chose d'un peu plus rapide utilise une arraylist. Enfin c'est un détail :)<br><br>Message édité par: Laurent Dardenne, à: 31/08/09 17:27

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 16 ans 6 mois #5232 par crystof
je vais tester ça

Merci beaucoup.:cheer:

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 16 ans 6 mois #5241 par crystof
Bonjour

Ci dessous le code modifié (si ça peut aider...)merci à Laurent pour ses conseils.
[code:1]
Param ([switch]$debug)
if ($debug) {$debugPreference=\&quot;Continue\&quot;}
Write-Debug \&quot;Starting script in debug mode\&quot;
Set-PSDebug -strict

[reflection.assembly]::loadwithpartialname(\&quot;System.Windows.Forms\&quot;«») | Out-Null
[reflection.assembly]::loadwithpartialname(\&quot;System.Drawing\&quot;«») | Out-Null

$global:Buttons=@{} #Hashtable, Button name/action association

#=== Func : Read xml config file
function ReadConfig {
$txtPath=\&quot;.\Gui.xml\&quot;
if( ($txtPath -ne \&quot;\&quot;«») -and ($txtPath -notlike \&quot;[ ]*\&quot;«») ){ #-and (Test-Path $txtPath) ){
$global:MObj = [xml](Get-Content $txtPath)

# read xml
$global:MObj.TheGui.Button | ForEach-Object {
$global:Buttons.($_.name)+=@($_.action)
}
}else{
write-debug \&quot;file not found\&quot;
}
}

#=== Func :
function OnClickHandler{
$Name=($This -as [string]).split(\&quot;:\&quot;«») -replace \&quot; \&quot;,\&quot;\&quot;
$Action=$Buttons.($Name[1])
Write-host \&quot;OnClickHandler: Clic on [$($Name[1])] button, call:$($Action);\&quot;
&amp; ( $ExecutionContext.InvokeCommand.NewScriptBlock($ExecutionContext.InvokeCommand.ExpandString($Action)) )
Write-debug \&quot;OnClickHandler: End Call:$($Action);\&quot;
}

#=== HANDLERS
$TheEnd=
{
Write-debug \&quot;CLOSING\&quot;
$form1.close()
}

#=== FORMS
$form1 = New-Object System.Windows.Forms.Form
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 121
$System_Drawing_Size.Height = 306
$form1.ClientSize = $System_Drawing_Size

$panel = New-Object System.Windows.Forms.Panel
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 93
$System_Drawing_Size.Height = 262
$panel.Size = $System_Drawing_Size
$panel.TabIndex = 23
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 12
$System_Drawing_Point.Y = 12
$panel.Location = $System_Drawing_Point
$form1.Controls.Add($panel)

#=== Process xml config
ReadConfig
if ($buttons -ne $null) {
$btnOffset = 0
foreach ($action in $buttons.GetEnumerator()) {
$btn = New-Object windows.forms.Button
$btn.Text = $action.key
$btn.top = $btnOffset
$btn.Width = 60
$btn.Add_Click( $ExecutionContext.InvokeCommand.NewScriptBlock( \&quot;OnClickHandler\&quot; ) )
Write-Debug \&quot;=Add btn $($btn.Text);$($action.value);\&quot;
$panel.Controls.Add($btn)
$btnOffset += 30
}
}

#=== DISPLAY
$form1.ShowDialog()| Out-Null
[/code:1]
et le xml (Gui.xml) :
[code:1]
&lt;TheGui&gt;
&lt;Button name=\&quot;QUIT\&quot; action=\&quot;$TheEnd\&quot;/&gt;
&lt;Button name=\&quot;testcall\&quot; action=\&quot;.\testcall.ps1\&quot;/&gt;
&lt;/TheGui&gt;
[/code:1]
La fonction OnClickHandler - ligne 1 ne me satifait pas vraiment, mais ça marche...

A+

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 16 ans 6 mois #5243 par crystof
Bonjour

Ci dessous le code modifié (si ça peut aider...)merci à Laurent pour ses conseils.
[code:1]
Param ([switch]$debug)
if ($debug) {$debugPreference=\&quot;Continue\&quot;}
Write-Debug \&quot;Starting script in debug mode\&quot;
Set-PSDebug -strict

[reflection.assembly]::loadwithpartialname(\&quot;System.Windows.Forms\&quot;«») | Out-Null
[reflection.assembly]::loadwithpartialname(\&quot;System.Drawing\&quot;«») | Out-Null

$global:Buttons=@{} #Hashtable, Button name/action association

#=== Func : Read xml config file
function ReadConfig {
$txtPath=\&quot;.\Gui.xml\&quot;
if( ($txtPath -ne \&quot;\&quot;«») -and ($txtPath -notlike \&quot;[ ]*\&quot;«») ){ #-and (Test-Path $txtPath) ){
$global:MObj = [xml](Get-Content $txtPath)

# read xml
$global:MObj.TheGui.Button | ForEach-Object {
$global:Buttons.($_.name)+=@($_.action)
}
}else{
write-debug \&quot;file not found\&quot;
}
}

#=== Func :
function OnClickHandler{
$Name=($This -as [string]).split(\&quot;:\&quot;«») -replace \&quot; \&quot;,\&quot;\&quot;
$Action=$Buttons.($Name[1])
Write-host \&quot;OnClickHandler: Clic on [$($Name[1])] button, call:$($Action);\&quot;
&amp; ( $ExecutionContext.InvokeCommand.NewScriptBlock($ExecutionContext.InvokeCommand.ExpandString($Action)) )
Write-debug \&quot;OnClickHandler: End Call:$($Action);\&quot;
}

#=== HANDLERS
$TheEnd=
{
Write-debug \&quot;CLOSING\&quot;
$form1.close()
}

#=== FORMS
$form1 = New-Object System.Windows.Forms.Form
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 121
$System_Drawing_Size.Height = 306
$form1.ClientSize = $System_Drawing_Size

$panel = New-Object System.Windows.Forms.Panel
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 93
$System_Drawing_Size.Height = 262
$panel.Size = $System_Drawing_Size
$panel.TabIndex = 23
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 12
$System_Drawing_Point.Y = 12
$panel.Location = $System_Drawing_Point
$form1.Controls.Add($panel)

#=== Process xml config
ReadConfig
if ($buttons -ne $null) {
$btnOffset = 0
foreach ($action in $buttons.GetEnumerator()) {
$btn = New-Object windows.forms.Button
$btn.Text = $action.key
$btn.top = $btnOffset
$btn.Width = 60
$btn.Add_Click( $ExecutionContext.InvokeCommand.NewScriptBlock( \&quot;OnClickHandler\&quot; ) )
Write-Debug \&quot;=Add btn $($btn.Text);$($action.value);\&quot;
$panel.Controls.Add($btn)
$btnOffset += 30
}
}

#=== DISPLAY
$form1.ShowDialog()| Out-Null
[/code:1]
et le xml (Gui.xml) :
[code:1]
&lt;TheGui&gt;
&lt;Button name=\&quot;QUIT\&quot; action=\&quot;$TheEnd\&quot;/&gt;
&lt;Button name=\&quot;testcall\&quot; action=\&quot;.\testcall.ps1\&quot;/&gt;
&lt;/TheGui&gt;
[/code:1]
La fonction OnClickHandler - ligne 1 ne me satifait pas vraiment, mais ça marche...

A+

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 16 ans 6 mois #5244 par Laurent Dardenne
Salut,
crysto444 écrit:

si ça peut aider...

Oui, il est trés bien ton script.
Il manque tout de même de commentaires, pour que chacun puisse le comprendre.

De mon coté je pense que le mieux serait d'ajouter, un nom au composant, comme ceci :
[code:1]
$BtnNumber=0
foreach ($action in $buttons.GetEnumerator()) {
$btn = New-Object windows.forms.Button
$btn.Text = $action.key
$btn.top = $btnOffset
$btn.Width = 60
$btn.Name=\&quot;Bouton$($BtnNumber++;$btnnumber)\&quot;
[/code:1]
Du coup, je suppose que pour toi l'idée est que le nom du bouton est égal au texte affiché.
Si c'est le cas on ne peux plus modifier le texte, car ce texte est référencé dans le tableau $Buttons, faut le savoir.

Peut-être manque-t-il un champ XML pour le nom du bouton ?
crysto444 écrit:

La fonction OnClickHandler - ligne 1 ne me satifait pas vraiment, mais ça marche...

A première vue je n'ai pas compris ce que c'était sensé faire :blink:
Tu peux simplifier comme ceci :
[code:1]
function OnClickHandler{
#$this = sender
$Name=$this.Text
$Action=$Buttons.($Name)
Write-host \&quot;OnClickHandler: Clic on [$Name] button, call:$($Action[0]);\&quot;
&amp; ( $ExecutionContext.InvokeCommand.NewScriptBlock($ExecutionContext.InvokeCommand.ExpandString($Action[0])) )
Write-debug \&quot;OnClickHandler: End Call:$($Action[0]);\&quot;
}
[/code:1]
Une chose aussi à comprendre est qu'il n'y a qu'un seul gestionnaire d'événement pour tous les boutons.
Dans ce cas tu peux simplifier l'appel, c'est tjr le même :
[code:1]
$btn.Add_Click( {OnClickHandler} )
[/code:1]
A mon avis, il n'est pas nécessaire d'utiliser le dynamisme ici (pour le moment ?).

Que tu permettes plusieurs occurences d'un nom de bouton dans le XML, fait que le tableau d'action est bien expansé mais cela ne peut être qu'une seule instruction.
C'est à dire que si le tableau contient + instructions indépendante, tu dois les séparer par un point virgule ou un retour chariot.

Ensuite si le contenu de ton action est toujours un nom de scriptblock déclaré dans le script de la form, il existe donc forcément au travers d'une variable, celle que tu indiques dans le XML, tu peux faire ceci :
[code:1]
#récupère le nom d'une variable puis la variable et enfin son contenu : le code
(gv $name.Remove(0,1)).value
#Ou son exécution
&amp;((gv $name.Remove(0,1)).value)
[/code:1]
Mais si on procéde ainsi le reste ne fonctionne plus, on doit savoir quand le faire ou ne pas le faire.

Sinon, comme tu dois le savoir, ceci permet de ne pas expanser la chaîne :
[code:1]action=\&quot;Dir `$pwd\&quot;[/code:1]
sans cela on écrit en dur le nom du répertoire
[code:1]'Dir c:\'[/code:1]

Il manque peut être un peu plus de paramètrage dans le XML, par exemple on ne peut pas insérer + lignes dans un attribut.
Une autre approche :
[code:1]
&lt;TheGui&gt;
&lt;Button&gt;
&lt;name&gt;QUIT&lt;/name&gt;
&lt;action&gt;$TheEnd&lt;/action&gt;
&lt;/Button&gt;
&lt;Button&gt;
&lt;name&gt;testcall&lt;/name&gt;
&lt;action&gt;.\testcall.ps1&lt;/action&gt;
&lt;/Button&gt;
&lt;Button&gt;
&lt;name&gt;Arrête la surveillance des process notepad&lt;/name&gt;
&lt;action&gt;&lt;![CDATA[# Event WMi via .NET
$Event=New-object WMIEvent.PoshStopWatchingEvent(\&quot;Process\&quot;,
$pid,
([System.Management.Automation.Runspaces.Runspace]::«»DefaultRunSpace).InstanceID,
[WMIEvent.PoshTransmissionActor]::«»PowerShell)
#déclenche l'événement WMI
$Event.Fire()
]]&gt;&lt;/action&gt;
&lt;/Button&gt;
&lt;/TheGui&gt;
&lt;!-- lecture cdata :$XML.TheGui.Button[2].action.get_innerxml()--&gt;
[/code:1]
Mais ici ce n'est peut être pas ton besoin, mais le mien ;)
A ce sujet, quel était l'objectif initial ?

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Temps de génération de la page : 0.068 secondes
Propulsé par Kunena