I have a situation where i need to be able to programatically generate a control element and place it into a grid.
if i premake the control in the xaml it is pretty straight forward:
<Grid ShowGridLines="True" Name='NewGrid'>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" Name="Row1" />
<RowDefinition Height="Auto" Name="Row2"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" Name="col1"/>
<ColumnDefinition Width="Auto" Name="col2"/>
</Grid.ColumnDefinitions>
<Label
Name='Buttonlabel'
HorizontalAlignment='Left'
Content="ButtonLabel"
VerticalAlignment='Top'
FontWeight="Bold"
Grid.Row="0"
Grid.Column="1"/>
</Grid>
This will place my Label into Row '0' and column '1'. But if i have a function in Powershell that is generating the label, i have no way of placing the label in Grid.Row='0', Grid.Column='1'. since location properties do not naturally exist when i create the label like this:
$label = New-Object System.Windows.Controls.Label
$label.Name = 'ButtonLabel'
$label.Content = 'ButtonLabel'
$label.HorizontalAlignment ='Left'
$label.VerticalAlignment ='top'
$label.FontWeight ='Bold'
$label.Grid.Row = '0' (error property not found)
$label.Grid.Column = '1' (error property not found)
Any ideas on how to place an item in to a grid like this?
UPDATE WITH SOLUTION
Here is the complete (working) function where i am calling this in case anyone needs a sample:
Function CreateRow{
$grd = New-Object system.windows.controls.grid
$grd.Name = "InnerGrid"
$grd.ShowGridLines = 'True'
$row1 = new-object system.windows.controls.rowdefinition
$row1.height = "Auto"
$row2 = new-object system.windows.controls.rowdefinition
$row2.height = "Auto"
$col1 = new-object system.windows.controls.columndefinition
$col1.width = "Auto"
$col2 = new-object system.windows.controls.columndefinition
$col2.width = "Auto"
$grd.RowDefinitions.add($row1)
$grd.RowDefinitions.add($row2)
$grd.ColumnDefinitions.add($col1)
$grd.ColumnDefinitions.add($col2)
$NewButton = New-Object System.Windows.Controls.Label
$NewButton.Content = "Label " + $counter
$NewButton.Name = "FirstLabel"
[System.Windows.Controls.Grid]::SetRow($NewButton,0)
[System.Windows.Controls.Grid]::SetColumn($NewButton,1)
$grd.AddChild($NewButton)
$NewButton2 = New-Object System.Windows.Controls.Label
$newButton2.Name = "SecondLabel"
$NewButton2.Content = "Label " + ($counter + 2 )
[System.Windows.Controls.Grid]::SetRow($NewButton2,1)
[System.Windows.Controls.Grid]::SetColumn($NewButton2,0)
$newButton2.HorizontalAlignment = 'Right'
$grd.AddChild($NewButton2)
return $grd
}
To set properties like this, use the static method on the "parent" class:
[System.Windows.Controls.Grid]::SetRow($label,0)
[System.Windows.Controls.Grid]::SetColumn($label,1)
Related
Not sure what the best way to word the question is, but how can I dynamically generate all these elements and assign an add_click on and to the generated elements?
I am using a foreach loop in powershell to build a tabbed WPF window based of the contents of a powershell object.
Everything works up until adding the control to the tabbutton to allow it to display the tabitem. I assume it is because in the foreach I am using the same variable name for each element, and I am not sure how to specify which tabitem is assigned to each tabbutton.
Additionally, I am looking for a powershell only solution.
I thought I might be able to use New-Variable/Get-Variable to build the variables with the counter included in the variable name but the add_click still could not call the tabitem.
Added 20 Jan 2023
Add-Type -AssemblyName PresentationCore, PresentationFramework, System.Windows.Forms, System.Drawing
#Region XAML for base gui
$Xaml = #"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="1000"
Height="700"
Margin="0,0,0,0"
Background="#35333a"
BorderBrush="#666374"
Foreground="#514e5d"
WindowStartupLocation="CenterScreen"
WindowStyle="None"
AllowsTransparency="True"
ResizeMode="CanResize">
<Grid Background="#241b2f" Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="3*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="6*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="6" VerticalAlignment="Center" HorizontalAlignment="Center" Height="20" Width="20" Background="white" BorderThickness="0,0,0,0" Name="CloseButton" />
<ScrollViewer VerticalScrollBarVisibility="Hidden" Grid.Row="1" Grid.RowSpan="4">
<StackPanel Name="QuestionTabsPanel">
</StackPanel>
</ScrollViewer>
<TabControl Background="#241b2f" BorderThickness="0,0,0,0" Padding="-1" Grid.Row="1" Grid.Column="2" Name="TabControlPanel" SelectedIndex="0">
</TabControl>
</Grid>
</Window>
"#
#EndRegion
# get questions from json and convert them to objects
$Json = get-content 'C:\Users\d.eckhout.mil\Desktop\Files\questions.json' | ConvertFrom-Json
$Questions = $Json | foreach{
[pscustomobject]#{
QuestionText = $_.questiontext
Image = $_.image
Answers = $_.answers
CorrectAnswer = $_.correctanswer
Explaination = $_.explaination
}
}
# counting variables
$QuestionsCorrect = 0
$QuestionNumber = 1
# randomize questions
$Questions = $Questions | Sort-Object {Get-Random}
# build window with base xaml
$Window = [Windows.Markup.XamlReader]::Parse($Xaml)
[xml]$xml = $Xaml
$xml.SelectNodes("//*[#Name]") | ForEach-Object { Set-Variable -Name $_.Name -Value $Window.FindName($_.Name) }
# custom close button event
$CloseButton.Add_Click({$Window.Close()})
# Make window moveable
$Window.Add_MouseDown({
if ($_.ChangedButton -eq 'Left'){
$this.DragMove()
}
})
# loop through our question objects and build a tab for each
foreach ($Question in $Questions){
#Region Add a tab button for the question to sidebar
$TabButton = New-Object System.Windows.Controls.Button
$TabButton.Name = "TabButton$QuestionNumber"
$TabButton.content = "$QuestionNumber"
$TabButton.Height="40"
$TabButton.Background="#241b2f"
$TabButton.BorderThickness="0,0,0,0"
$TabButton.Foreground="#ffffff"
$QuestionTabsPanel.Children.Add($TabButton)
#EndRegion
#Region Create tab content panel for the question
$TabContent = New-Object System.Windows.Controls.TabItem
#$TabContent.Visibility="Collapsed"
$TabContent.Name="TabContent$QuestionNumber"
$TabControlPanel.Items.Add($TabContent)
#EndRegion
$TabButton.Add_Click({
$sauceButton=$_ #the button this click event was triggered by
$sauceButtonContent=[Int]$sauceButton.Content #its content casted as Int
$tabIndexToSelect=$sauceButtonContent-1 # the first tabItem has index 0
$TabControlPanel.SelectedIndex = $tabIndexToSelect
})
#Region Add grid to tab content area...grid lets us better control placement of children
$TabContentStack = New-Object System.Windows.Controls.StackPanel
$TabContentStack.Name="TabStack$QuestionNumber"
$TabContent.Content = ($TabContentStack)
#EndRegion
#Region Add question text to grid
$TabContentQuestionText = New-Object System.Windows.Controls.TextBlock
$TabContentQuestionText.HorizontalAlignment="Center"
$TabContentQuestionText.VerticalAlignment="Top"
$TabContentQuestionText.TextWrapping="Wrap"
$TabContentQuestionText.Text=$Question.QuestionText
$TabContentQuestionText.FontSize="14"
$TabContentQuestionText.Height="21"
$TabContentQuestionText.Foreground="#ffffff"
$TabContentStack.Children.Add($TabContentQuestionText)
#EndRegion
$QuestionNumber++
}
$Window.ShowDialog()
[
{
"questiontext": "lorem ipsum?",
"answers": [
"first ipsum",
"second ipsum",
"third ipsum",
"fourth ipsum"
],
"correctanswer": "lorem ipsum",
"explaination": "lorem ipsum"
},
{
"questiontext": "lorem ipsum?",
"answers": [
"first ipsum",
"second ipsum",
"third ipsum",
"fourth ipsum"
],
"correctanswer": "lorem ipsum",
"explaination": "lorem ipsum"
}
]
picture of gui running
You can use selecedIndex with the content of the button your click event originates from:
$TabButton.Add_Click({
$sauceButton=$_ #the button this click event was triggered by
$sauceButtonContent=[Int]$sauceButton.Content #its content casted as Int
$tabIndexToSelect=$sauceButtonContent-1 # the first tabItem has index 0
$TabControlPanel.SelectedIndex = $tabIndexToSelect
})
However I recommend to create a datacontext, add ps-objects to that context for each tabitem and bind it to your tabcontrol, and set the property itemsource to "binging" simular to this example: Powershell XAML - Use XAML datatemplate to create new tabitems
I have some powershell code where i am leveraging a xaml template to generate a grid of servers. Since the number of servers and names will change (fed from a spreadsheet) i am not able to hardcode the names of the XAML elements.
I am able to generate the grid (server items grouped by types of servers) successfully without any issues. my issue now is how to reference the XAML items generated 'on-the-fly' later. I feel like this is a relatively easy question based on the complexity of what i am already doing, but i am new to working with XAML, especially generating a XAML form on the fly.
Here is the XAML template and the two functions that generate the xaml items on the fly.
[xml]$xaml = #'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title='MainWindow' Height='Auto' Width='Auto' FontSize='9'>
<Window.Resources>
<Style x:Key="GroupBoxStyle1" TargetType="{x:Type GroupBox}">
<Setter Property="BorderBrush" Value="Blue"/>
<Setter Property="BorderThickness" Value="5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupBox">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Grid.Row="0"
Grid.ColumnSpan="2"
BorderThickness="1"
BorderBrush="#25A0DA"
Background="#25A0DA">
<Label Foreground="White">
<ContentPresenter Margin="4"
ContentSource="Header"
RecognizesAccessKey="True" />
</Label>
</Border>
<Border Grid.Row="1"
BorderThickness="1,0,1,1"
BorderBrush="#25A0DA">
<ContentPresenter Margin="4" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<ScrollViewer ScrollViewer.VerticalScrollBarVisibility ='Auto' ScrollViewer.HorizontalScrollBarVisibility ='Auto'>
<Grid ShowGridLines="False" Name='StartingGrid'>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button
Name='Refresh_Btn'
Content='Refresh'
Width='70'
Height='30'
FontSize="9"
FontWeight="Bold"
Grid.Column="0"
Grid.Row="0"/>
<Button
Name='Reboot_Btn'
Content='Reboot'
Width='70'
Height='30'
FontSize="9"
FontWeight="Bold"
Grid.Column="1"
Grid.Row="0"/>
</Grid>
</ScrollViewer>
</Window>
'#
Function CreateServerGrid{
$srv = $Srv.name
$grd = New-Object system.windows.controls.grid
$grd.Name = "Server" + ($counter)
#$grd.ShowGridLines = 'True'
$row1 = new-object system.windows.controls.rowdefinition
$row1.height = "Auto"
$row2 = new-object system.windows.controls.rowdefinition
$row2.height = "Auto"
$row3 = new-object system.windows.controls.rowdefinition
$row3.height = "Auto"
$col1 = new-object system.windows.controls.columndefinition
$col1.width = "Auto"
$col2 = new-object system.windows.controls.columndefinition
$col2.width = "Auto"
$col3 = new-object system.windows.controls.columndefinition
$col3.width = "Auto"
$grd.RowDefinitions.add($row1)
$grd.RowDefinitions.add($row2)
$grd.RowDefinitions.add($row3)
$grd.ColumnDefinitions.add($col1)
$grd.ColumnDefinitions.add($col2)
$grd.ColumnDefinitions.add($col3)
$ServerLabel = New-Object System.Windows.Controls.Label
$ServerLabel.Name = "$srv"
$ServerLabel.FontWeight = "UltraBold"
$ServerLabel.Content = $srv
[System.Windows.Controls.Grid]::SetRow($ServerLabel,0)
[System.Windows.Controls.Grid]::SetColumn($ServerLabel,0)
$ServerLabel.HorizontalAlignment = 'Right'
#RegisterName($srv.name,$ServerLabel)
#$GUIhash.Add($_.TargetType,$_)
$ItemLabel0 = New-Object System.Windows.Controls.Label
$ItemLabel0.Name = "Label_LastDeployment_$srv"
#$ItemLabel1.Height = "Auto"
$ItemLabel0.Content = "Last Deployment: "
[System.Windows.Controls.Grid]::SetRow($ItemLabel0,1)
[System.Windows.Controls.Grid]::SetColumn($ItemLabel0,1)
$ItemText0 = New-Object System.Windows.Controls.textBox
$ItemText0.Name = "LD_$srv"
#$ItemLabel1.Height = "Auto"
$ItemText0.text = "OtherData1 " + ($counter + 2 )
[System.Windows.Controls.Grid]::SetRow($ItemText0,1)
[System.Windows.Controls.Grid]::SetColumn($ItemText0,2)
$ItemLabel1 = New-Object System.Windows.Controls.Label
$ItemLabel1.Name = "Label_LastReboot_$srv"
#$ItemLabel1.Height = "Auto"
$ItemLabel1.Content = "Last Reboot: "
[System.Windows.Controls.Grid]::SetRow($ItemLabel1,2)
[System.Windows.Controls.Grid]::SetColumn($ItemLabel1,1)
$ItemText1 = New-Object System.Windows.Controls.textBox
$ItemText1.Name = "LR_$srv"
#$ItemLabel1.Height = "Auto"
$ItemText1.text = "OtherData1 " + ($counter + 2 )
[System.Windows.Controls.Grid]::SetRow($ItemText1,2)
[System.Windows.Controls.Grid]::SetColumn($ItemText1,2)
$grd.AddChild($ServerLabel)
$grd.AddChild($ItemLabel0)
$grd.AddChild($ItemText0)
$grd.AddChild($ItemLabel1)
$grd.AddChild($ItemText1)
return $grd
}
Function StartTheFun {
$script:counter = 1
foreach ($grp in $grouping){
$WPFStartingGrid.RowDefinitions.add((new-object System.Windows.Controls.RowDefinition -property #{ Height = 'Auto'}))
If($Counter -eq 3){
$script:cnt ++
$WPFStartingGrid.ColumnDefinitions.add((new-object System.Windows.Controls.ColumnDefinition -property #{ Width = 'Auto'}))
$script:counter = 1
}
$grid =#()
$newStkPnl = new-object System.Windows.Controls.Grid
$NewGB = New-Object System.Windows.Controls.GroupBox
$NewGB.Header = $grp
$NewGB.Margin = "0"
$init = 0
$ct = 0
$srvCnt = ($list | ?{$_.'Computer_Description' -match $grp}).count
#Write-Host $grp
#Write-Host "ServerCount: " $srvCnt
$CntCheck = $false
if ($srvCnt -gt 4){$CntCheck = $true}
#Write-Host "CntCheck: " $CntCheck
foreach ($srv in $list | ?{$_.'Computer_Description' -match $grp}) {
Write-Host 'Server: ' $srv.name
Write-Host 'Init: ' $init
$newStkPnl.RowDefinitions.add((new-object System.Windows.Controls.RowDefinition -property #{ Height = 'Auto'}))
#$ControlObj.Add($srv.Name,$_)
$grid = CreateServerGrid $srv
[System.Windows.Controls.Grid]::SetRow($grid,$init)
[System.Windows.Controls.Grid]::SetColumn($grid,$ct)
$newStkPnl.AddChild($grid)
$init ++
$srvCnt --
if($srvCnt -gt 0){
If ($init -gt 3){
$newStkPnl.ColumnDefinitions.add((new-object System.Windows.Controls.ColumnDefinition -property #{ Width = 'Auto'}))
$ct ++
$init = 0
}
If (($srvCnt -gt 0) -and ($init -eq 0)){
$newStkPnl.ColumnDefinitions.add((new-object System.Windows.Controls.ColumnDefinition -property #{ Width = 'Auto'}))
$ct ++
$init = 0
}
}
Write-Host 'NewSrvCnt: ' $srvCnt
}
$NewGB.Content = $newStkPnl
$NewGB.Name = 'Group' + ($counter)
$NewGB.Style = $GuiHash.Window.FindResource("GroupBoxStyle1")
[System.Windows.Controls.Grid]::SetRow($NewGB,$counter)
[System.Windows.Controls.Grid]::SetColumn($NewGB,$cnt)
$Script:counter ++
$WPFStartingGrid.AddChild($NewGB)
}
}
Any ideas on how to best reference these elements? I am providing an image so people can see what it looks like when it runs.
I used ISE-Sterioids template to create a simple 3 field WPF form that asks for three things:
- ID
- Email
- Reference
I have it sucessfully working with these fields when using the OK and Cancel buttons, but I would like to capter "Enter" to submit the form and "Escape" to cancel the form, but I am having difficulty adding the events.
I have tried similar code to the technet article here; but as this isn't using WPF I think I am missing something
$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
{$x=$objTextBox.Text;$objForm.Close()}})
My code here:
#region XAML window definition
# Right-click XAML and choose WPF/Edit... to edit WPF Design
# in your favorite WPF editing tool
# Default Form Values
$123 = 'ID'
$toEmail = 'email address'
$ref = "ref"
$xaml = #'
<Window
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" mc:Ignorable="d"
MinWidth="200"
Width ="400"
SizeToContent="Height"
Title="Proofing script"
Topmost="True">
<Grid Margin="10,2,10,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- <TextBlock Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Margin="5">Please enter your details:</TextBlock> -->
<TextBlock Grid.Column="0" Grid.Row="1" Margin="5"><Run Text="Number:"/></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="2" Margin="5"><Run Text="To Email :"/></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="3" Margin="5"><Run Text="Salesforce Ref:"/></TextBlock>
<TextBox x:Name="TxtName" Grid.Column="1" Grid.Row="1" Margin="5"/>
<TextBox x:Name="TxtEmail" Grid.Column="1" Grid.Row="2" Margin="5"/>
<TextBox x:Name="ref" Grid.Column="1" Grid.Row="3" Margin="5"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,5,0,0" Grid.Row="4" Grid.ColumnSpan="2">
<Button x:Name="ButOk" MinWidth="80" Height="22" Margin="5" Content="OK" />
<Button x:Name="ButCancel" MinWidth="80" Height="22" Margin="5" Content="Cancel" IsCancel="True" />
</StackPanel>
</Grid>
</Window>
'#
#endregion
#region Code Behind
function Convert-XAMLtoWindow
{
param
(
[Parameter(Mandatory)]
[string]
$XAML,
[string[]]
$NamedElement=$null,
[switch]
$PassThru
)
Add-Type -AssemblyName PresentationFramework
$reader = [XML.XMLReader]::Create([IO.StringReader]$XAML)
$result = [Windows.Markup.XAMLReader]::Load($reader)
foreach($Name in $NamedElement)
{
$result | Add-Member NoteProperty -Name $Name -Value $result.FindName($Name) -Force
}
if ($PassThru)
{
$result
}
else
{
$null = $window.Dispatcher.InvokeAsync{
$result = $window.ShowDialog()
Set-Variable -Name result -Value $result -Scope 1
}.Wait()
$result
}
}
function Show-WPFWindow
{
param
(
[Parameter(Mandatory)]
[Windows.Window]
$Window
)
$result = $null
$null = $window.Dispatcher.InvokeAsync{
$result = $window.ShowDialog()
Set-Variable -Name result -Value $result -Scope 1
}.Wait()
$result
}
#endregion Code Behind
#region Convert XAML to Window
$window = Convert-XAMLtoWindow -XAML $xaml -NamedElement 'ButCancel', 'ButOk', 'ref', 'TxtEmail', 'TxtName' -PassThru
#endregion
#region Define Event Handlers
# Right-Click XAML Text and choose WPF/Attach Events to
# add more handlers
$window.ButCancel.add_Click(
{
$window.DialogResult = $false
}
)
$window.ButOk.add_Click(
{
$window.DialogResult = $true
}
)
#endregion Event Handlers
#region Manipulate Window Content
#$window.TxtName.Text = $env:username
$window.ref.Text = $ref
$window.TxtName.Text = $123
$window.TxtEmail.Text = $toEmail
$null = $window.TxtName.Focus()
#endregion
# Show Window
$result = Show-WPFWindow -Window $window
If i use ISE Steroids to add an event I get similar to this
$window.ButOk.add_KeyDown{
# remove param() block if access to event information is not required
param
(
[Parameter(Mandatory)][Object]$sender,
[Parameter(Mandatory)][Windows.Input.KeyEventArgs]$e
)
# add event code here
}
Working with key down events can be a bit tricky since these are depending on the current UIFocus, therefor your button does not act on any keydown event since it is not in the current focus scope when you´re just editing the Textboxes. Since you only plan on using this simple form, I would suggest adding an event handler to your window that handles this. So your code behind section should look something like this:
$window.add_KeyDown{
param
(
[Parameter(Mandatory)][Object]$sender,
[Parameter(Mandatory)][Windows.Input.KeyEventArgs]$e
)
if($e.Key == $Key.Return)
{
$window.DialogResult = $true
}
if($e.Key -eq $Key.Escape)
{
$window.DialogResult = $false
}
}
The accepted answer didn't work for me yet but it helped me a lot so I'm answering here.
I made a simple form with https://poshgui.com/Editor adding the code in the answer and got the error message:
System.Management.Automation.RuntimeException: Unable to find type [Windows.Input.KeyEventArgs].
My TextBox element has got the focus, so I created the event for it:
TextBoxSearchInput.Add_KeyDown{
param (
[Parameter(Mandatory)][Object]$sender,
[Parameter(Mandatory)][System.Windows.Forms.KeyEventArgs]$e
)
if($e.Key -eq $Key.Return){
$FormOkButton.PerformClick()
}
if($e.Key -eq $Key.Escape){
$Form.close()
}
}
There may be a better way than "PerformClick" but for now it works.
I know this thread here is old but I was also sucessfull with this:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# Build Form
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "Any Name"
$Form.Size = New-Object System.Drawing.Size(700,150) #use any size
$Form.StartPosition = "CenterScreen" # I prefer this
$Form.Topmost = $true
$form.KeyPreview = $true #This is the important part
$form.Add_KeyDown{
param (
[Parameter(Mandatory)][Object]$sender,
[Parameter(Mandatory)][System.Windows.Forms.KeyEventArgs]$e
)
if($_.KeyCode -eq "Escape"){
$Form.close()
}
}
I have working complex grid in wpf written in XAML.
My purpose is to convert the XAML code to c#, so I am going to be able to generate this grid programmatically.
This is the working XAML code
<Grid>
<!--<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ElementName=datagrid1, Path=RowHeaderWidth}" />
<ColumnDefinition Width="{Binding ElementName=Column1, Path=ActualWidth}" />
<ColumnDefinition Width="{Binding ElementName=Column2, Path=ActualWidth}" />
<ColumnDefinition Width="{Binding ElementName=Column3, Path=ActualWidth}" />
<ColumnDefinition Width="{Binding ElementName=Column4, Path=ActualWidth}" />
</Grid.ColumnDefinitions>
<Border Grid.Column="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" BorderThickness="2">
<Label HorizontalAlignment="Center">Super Header!!</Label>
</Border>
</Grid>
<DataGrid AutoGenerateColumns="False"
Name="datagrid1" Grid.Row="1" Width="10">
<DataGrid.Columns>
<DataGridTextColumn Header="column 1" Width="100" x:Name="Column1" />
<DataGridTextColumn Header="column 2" Width="80" x:Name="Column2"/>
<DataGridTextColumn Header="column 3" Width="80" x:Name="Column3"/>
<DataGridTextColumn Header="column 4" Width="*" x:Name="Column4"/>
</DataGrid.Columns>
</DataGrid>-->
When I compile it shows the desire result which looks like
this: http://i.imgur.com/BK8GDtd.png?1
What I have tried is this :
Grid outernGrid = new Grid();
outernGrid.ShowGridLines = true;
RowDefinition row1 = new RowDefinition();
row1.Height = GridLength.Auto;
RowDefinition row2 = new RowDefinition();
row2.Height = new GridLength(2, GridUnitType.Star);
outernGrid.RowDefinitions.Add(row1);
outernGrid.RowDefinitions.Add(row2);
//Second grid
Grid innerGrid = new Grid();
ColumnDefinition col0 = new ColumnDefinition();
ColumnDefinition col1 = new ColumnDefinition();
ColumnDefinition col2 = new ColumnDefinition();
ColumnDefinition col3 = new ColumnDefinition();
ColumnDefinition col4 = new ColumnDefinition();
//Binding Initialize
Binding b0 = new Binding { ElementName = "dataGrid2", Path = new PropertyPath("RowHeaderWidth") };
Binding b1 = new Binding { ElementName = "Column1", Path = new PropertyPath("ActualWidth") };
Binding b2 = new Binding { ElementName = "Column2", Path = new PropertyPath("ActualWidth") };
Binding b3 = new Binding { ElementName = "Column3", Path = new PropertyPath("ActualWidth") };
Binding b4 = new Binding { ElementName = "Column4", Path = new PropertyPath("ActualWidth") };
//Set binding
BindingOperations.SetBinding(col1, DataGrid.ColumnWidthProperty, b1);
BindingOperations.SetBinding(col2, DataGrid.ColumnWidthProperty, b2);
BindingOperations.SetBinding(col3, DataGrid.ColumnWidthProperty, b3);
BindingOperations.SetBinding(col4, DataGrid.ColumnWidthProperty, b4);
BindingOperations.SetBinding(col0, DataGrid.ColumnWidthProperty, b0);
//Adding columns
innerGrid.ColumnDefinitions.Add(col0);
innerGrid.ColumnDefinitions.Add(col1);
innerGrid.ColumnDefinitions.Add(col2);
innerGrid.ColumnDefinitions.Add(col3);
innerGrid.ColumnDefinitions.Add(col4);
Label header = new Label();
header.Content = "This is the superheader";
//Datagrid
DataGrid dataGrid = new DataGrid { Name = "dataGrid2" };
dataGrid.AutoGenerateColumns = false;
dataGrid.RowHeaderWidth = 10;
//datagrid columns
DataGridTextColumn datacol1 = new DataGridTextColumn();
DataGridTextColumn datacol2 = new DataGridTextColumn();
DataGridTextColumn datacol3 = new DataGridTextColumn();
DataGridTextColumn datacol4 = new DataGridTextColumn();
datacol1.Header = "Column1";
datacol2.Header = "Column2";
datacol3.Header = "Column3";
datacol4.Header = "Column4";
datacol1.Width = 100;
datacol1.Width = 80;
datacol1.Width = 80;
datacol1.Width = DataGridLength.Auto;
Grid.SetColumn(header,1);
header.SetValue(Grid.ColumnSpanProperty,4);
Grid.SetRow(innerGrid, 0);
Grid.SetColumn(dataGrid, 1);
outernGrid.Children.Add(innerGrid);
outernGrid.Children.Add(dataGrid);
RootWindow.Content = outernGrid;
}
And the output is this
Can someone help me solving this issue.
I have two observations for you while I attempt to work on this.
First in your code sample you have this:
datacol1.Width = 100;
datacol1.Width = 80;
datacol1.Width = 80;
datacol1.Width = DataGridLength.Auto;
I imagine you wanted this:
datacol1.Width = 100;
datacol2.Width = 80;
datacol3.Width = 80;
datacol4.Width = DataGridLength.Auto;
Second, you do not name your columns in the Datagrid programmatically.
You'll have to do something like this for each column:
datacol1.ColumnName = "Column1";
datacol2.ColumnName = "Column2";
datacol3.ColumnName = "Column3";
datacol4.ColumnName = "Column4";
I have a problem regarding GridSplitter visiblity.
In this, whatever I am hosting a Winform DataGridView. The GridSplitter, when dragged is properly visible on other controls. But not on this grid. In fact, whatever I host instead of Datagridview, becomes the topmost control, which makes the GridSplitter hide behind it.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Name="rowForButton"/>
<RowDefinition Name="rowForGridSplitter" Height="Auto" MinHeight="81" />
</Grid.RowDefinitions>
<Button Grid.Row="0" Height="50" Width="110" Content="Button in First Row"/>
<my:WindowsFormsHost Panel.ZIndex="0" Grid.Row="1" Margin="30,11,138,0" x:Name="winHost" Height="58" VerticalAlignment="Top" OpacityMask="Transparent">
<win:DataGridView x:Name="dataGridView"></win:DataGridView>
</my:WindowsFormsHost>
<GridSplitter BorderThickness="1" Panel.ZIndex="1" Grid.Row="1" HorizontalAlignment="Stretch" Height="5" ShowsPreview="True" VerticalAlignment="Top">
</GridSplitter>
</Grid>
Usually you should either put a GridSplitter into its own grid cell or ensure via margins that no control can overlap it. But I don't know whether that exactly applies to you here. See also here.
I encountered this problem also, there is my solution:
var splitter = new GridSplitter()
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
FocusVisualStyle = null,
ShowsPreview = true,
Background = new SolidColorBrush(new Color() { R = 1, G = 1, B = 1, A = 1 }),
};
// non-style / essential window which will display over your WinForm control
var PopupWindowForSplitter = new PopupWindow()
{
Background = new SolidColorBrush(new Color() { R = 1, G = 1, B = 1, A = 1 }),
Visibility = Visibility.Collapsed
};
PopupWindowForSplitter.Show();
...
Point _ptForSplitterDrag = new Point(0,0);
splitter.DragStarted += (o, e) =>
{
var pt = splitter.PointToScreen(new Point());
_ptForSplitterDrag = splitter.PointToScreen(Mouse.GetPosition(splitter));
PopupWindowForSplitter.Left = pt.X;
PopupWindowForSplitter.Top = pt.Y;
PopupWindowForSplitter.Height = splitter.ActualHeight;
PopupWindowForSplitter.Width = splitter.ActualWidth;
PopupWindowForSplitter.Activate();
PopupWindowForSplitter.Visibility = Visibility.Visible;
};
splitter.DragDelta += (o, e) =>
{
var pt = splitter.PointToScreen(Mouse.GetPosition(splitter)) - _ptForSplitterDrag
+ splitter.PointToScreen(new Point());
if (splitter.ResizeDirection == GridResizeDirection.Rows)
{
PopupWindowForSplitter.Top = pt.Y;
}
else
{
PopupWindowForSplitter.Left = pt.X;
}
};
splitter.DragCompleted += (o, e) =>
{
var initializeData = typeof(GridSplitter).GetMethod("InitializeData", BindingFlags.NonPublic | BindingFlags.Instance);
var moveSplitter = typeof(GridSplitter).GetMethod("MoveSplitter", BindingFlags.NonPublic | BindingFlags.Instance);
if (moveSplitter != null && initializeData != null)
{
initializeData.Invoke(splitter, new object[] { true });
var pt = splitter.PointToScreen(Mouse.GetPosition(splitter)) - _ptForSplitterDrag;
if (splitter.ResizeDirection == GridResizeDirection.Rows)
{
moveSplitter.Invoke(splitter, new object[] { 0, pt.Y });
}
else
{
moveSplitter.Invoke(splitter, new object[] { pt.X, 0 });
}
}
PopupWindowForSplitter.Visibility = Visibility.Collapsed;
};
Maybe there are some issues in my description because of my poor english, but I think the code is enough to explain it.
Windows Forms controls are always rendered seperately from your WPF controls, and as a result will always appear over your WPF application.
See Hosting a Microsoft Win32 Window in WPF (subheading Notable Differences in Output Behavior) for more info.
Try using a WPF-native DataGrid control. There are a couple of commercial third-party controls you can buy, or you could take a look at one provided by Microsoft (currently still in CTP):
Xceed DataGrid
Telerik RadGridView
Microsoft DataGrid CTP
In your situation, the quickest fix would be to move the GirdSplitter to the Row with the Button:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Name="rowForButton"/>
<RowDefinition Name="rowForGridSplitter" Height="Auto" MinHeight="81" />
</Grid.RowDefinitions>
<Button Grid.Row="0" Height="50" Width="110" Content="Button in First Row"/>
<my:WindowsFormsHost Panel.ZIndex="0" Grid.Row="1" Margin="30,11,138,0" x:Name="winHost" Height="58" VerticalAlignment="Top" OpacityMask="Transparent">
<win:DataGridView x:Name="dataGridView"></win:DataGridView>
</my:WindowsFormsHost>
<GridSplitter BorderThickness="1" Panel.ZIndex="1" Grid.Row="0" HorizontalAlignment="Stretch" Height="5" ShowsPreview="True" VerticalAlignment="Bottom">
</GridSplitter>
</Grid>
Now just adjust the margins to make sure there is some space between the button and the grid splitter.
The solution would be to add a 'Windows Form' label inside the grid splitter, and to do so programmatically after the addition of the DataGridView so it appears on top of it, as follows:
void AddLabelToSplitter()
{
string template =
#" <ControlTemplate
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:mn='clr-namespace:MyNameSpace;assembly=MyAssembly'
xmlns:wf='clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' TargetType='{x:Type GridSplitter}'>
<mn:ExtendedWindowsFormsHost x:Name='Grid_Splitter_WindowsFormsHost' HorizontalAlignment='Stretch' VerticalAlignment='Stretch'>
<wf:Label Dock='Fill' BackColor='DarkGray'></wf:Label>
</mn:ExtendedWindowsFormsHost>
</ControlTemplate>";
Grid_Splitter.Template = (ControlTemplate)XamlReader.Parse(template);
}
Using a regular windows form host would not work, as it wouldn't pass down the mouse events to the splitter, so use the ExtendedWindowsFormsHost instead from below link:
Keep Mouse Events bubbling from WindowsFormsHost on