Search code examples
wpfpowershelldatagrid

Link WPF XAML to DataGrid in PowerShell


I was following along with the guide located here trying to make a GUI with PowerShell, and it all goes well except I have a DataGrid that needs to be populated in my GUI.

In my XML grid I have:

[xml]$form=@"
<Window
    [...]
    <Grid>
        [...]
        <DataGrid Name="CSVGrid" HorizontalAlignment="Left" Height="340" Margin="10,60,0,0" VerticalAlignment="Top" Width="765"/>
        [...]
    </Grid>
</Window>
"@

Now in the tutorial to create the form it uses the following:

$XMLReader = (New-Object System.Xml.XmlNodeReader $Form)
$XMLForm = [Windows.Markup.XamlReader]::Load($XMLReader)

But to get my DataGrid to work, I believe I need to define my "CSVGrid" DataGrid as "system.Windows.Forms.DataGridView" somewhere, but I'm not sure how to tie that together. Running it without defining this will spit out errors if I try to invoke any DataGrid properties, like setting column amounts or column names.

Any ideas?

The way POSHGUI implements their forms actually works perfectly for my purpose, but I prefer editing the WPF forms in Visual Studio. If need be, I can rebuild the form in POSHGUI but hopefully there's a way to tie this together here so I can continue to use the VS GUI for editing the form's GUI.

Edit: It should be noted there's not just the data grid on the form, in case that wasn't clear.

Edit 2: As an extra bit of info, the way POSHGUI formats the controls is like so:

#the form itself
$Form                            = New-Object system.Windows.Forms.Form
$Form.ClientSize                 = '400,400'
$Form.text                       = "Form"
$Form.TopMost                    = $false
#a datagrid
$DataGridView1                   = New-Object system.Windows.Forms.DataGridView
$DataGridView1.width             = 382
$DataGridView1.height            = 335
$DataGridView1.location          = New-Object System.Drawing.Point(8,55)
#a button
$Button1                         = New-Object system.Windows.Forms.Button
$Button1.text                    = "My button"
$Button1.width                   = 126
$Button1.height                  = 30
$Button1.location                = New-Object System.Drawing.Point(156,13)
$Button1.Font                    = 'Microsoft Sans Serif,10'

It then ties them together with:

$Form.controls.AddRange(@($DataGridView1,$Button1))

So that happily defines the DataGrid variable as a "system.Windows.Forms.DataGridView", &c, whereas the method of putting the entire XML into a $variable and passing that into a "System.Xml.XmlNodeReader" doesn't make the distinction I don't believe, which is why I'm unable to call any DataGrid properties.

But again, I'd rather create the GUI in Visual Studio if I can...

Edit 3: If it helps at all, checking the intellisense dropdown, various DataGrid properties are there, but not ColumnCount for example:

enter image description here

So maybe it's working as intended? But at the same time, if my DataGrid variable is defined explicitly as a system.Windows.Forms.DataGridView, like in the POSHGUI example, then setting ColumnCount works flawlessly...


Solution

  • Before we get too far into it, you can't use a DataGridView in WPF (which is what I'll be showing you here). You can, however, use a DataGrid instead which is pretty much the same thing (and wayyyy shorter and easier in PowerShell than Windows Forms, which is what POSHGUI uses (awesome tool though it is).

    If you're still interested though, here's a super basic walkthrough of how you could do this. First, to define the XAML

    $inputXML = @"
    <Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp2"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <DataGrid Name="Datagrid" AutoGenerateColumns="True" >
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="180" />
                <DataGridTextColumn Header="Type" Binding="{Binding Type}" Width="233"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
    </Window>
    "@ 
    

    Note that we are defining which columns from our input object we wanna display. Haven't found a great way to automatically create them yet.

    Next, load the form (using the method from my walkthrough on the topic here)

    $inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window'
    [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
    [xml]$XAML = $inputXML
    #Read XAML
    
        $reader=(New-Object System.Xml.XmlNodeReader $xaml) 
    try{$Form=[Windows.Markup.XamlReader]::Load( $reader )}
    catch [System.Management.Automation.MethodInvocationException] {
        Write-Warning "We ran into a problem with the XAML code.  Check the syntax for this control..."
        write-host $error[0].Exception.Message -ForegroundColor Red
        if ($error[0].Exception.Message -like "*button*"){
            write-warning "Ensure your &lt;button in the `$inputXML does NOT have a Click=ButtonClick property.  PS can't handle this`n`n`n`n"}
    }
    catch{#if it broke some other way <span class="wp-smiley wp-emoji wp-emoji-bigsmile" title=":D">:D</span>
        Write-Host "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed."
            }
    
    #===========================================================================
    # Store Form Objects In PowerShell
    #===========================================================================
    
    $xaml.SelectNodes("//*[@Name]") | %{Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name)}
    

    Now, to add some items to this DataGridView. The above code also gives us a variable titled $WPFGridView in our session. We can add child items to our DataGridView by calling the .AddChild() method of that variable and feeding it items that have at least the same properties as we defined in our DataGrid earlier.

    $WPFDatagrid.AddChild([pscustomobject]@{Name='Stephen';Type=123})
    $WPFDatagrid.AddChild([pscustomobject]@{Name='Geralt';Type=234})
    

    Then, to display our GUI, we call the $Form's ShowDialog() method like so, and then the bottom should greet us.

    $Form.ShowDialog()
    

    The image depicts a Windows 10 operating system displaying a Windows Presentation Foundation User Interface which contains a datagrid with two columns that contain the items added in the code snippet above

    Full code sample is available here. If you want to learn more about using WPF GUI elements like this, I have a full walkthrough available here.