modernWPFUI + powershell = System.Windows.Markup.StaticResourceHolder - wpf

I want to use the ModernWPFUI Theme on Powershell.
Generally, i use only XAML in powershell for a better GUI, so i really don't know too much
Now, this is my test code:
XAML(test.xaml):
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="http://schemas.modernwpf.com/2019"
x:Name="Window"
Title="Test" Height="68.293" Width="462.195">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/ModernWpf;component/ControlsResources.xaml" />
<ResourceDictionary Source="pack://application:,,,/ModernWpf;component/ThemeResources/Light.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
</Window>
PS1(run.ps1):
[System.Reflection.Assembly]::LoadWithPartialName('presentationframework') | out-null
[System.Reflection.Assembly]::LoadFrom('.\assembly\ModernWpf.dll') | out-null
[System.Reflection.Assembly]::LoadFrom('.\assembly\ModernWpf.Controls.dll') | out-null
[System.Reflection.Assembly]::LoadFrom('.\assembly\System.ValueTuple.dll') | out-null
function LoadXml ($global:filename)
{
$XamlLoader=(New-Object System.Xml.XmlDocument)
$XamlLoader.Load($filename)
return $XamlLoader
}
$XamlMainWindow=LoadXml("test.xaml")
$Reader=(New-Object System.Xml.XmlNodeReader $XamlMainWindow)
$Form=[Windows.Markup.XamlReader]::Load($Reader)
$Global:Current_Folder =(get-location).path
$Form.ShowDialog()
result:
MethodInvocationException: (path)\run.ps1: 15: 1
Line |
15 | $ Form = [Windows.Markup.XamlReader] :: Load ($ Reader)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Exception calling "Load" with arguments "1": "An exception was thrown when specifying a value on 'System.Windows.Markup.StaticResourceHolder'."
Invalid operation: (path)\run.ps1: 22: 1
Line |
22 | $ Form.ShowDialog ()
| ~~~~~~~~~~~~~~~~~~
| A method cannot be called on a null-valued expression.
I tried to edit in or add it "clr-namespace:ModernWpf;assembly=ModernWpf" with the same result.

Related

WPF DataGrid Cell Selection

I'm trying to search a WPF DataGrid via PowerShell, select and highlight the cells, each one in turn, that contains the search keyword. The goal is to 1) search the DataGrid, 2) select and highlight the cell that contains search keyword while moving the focus/scrolling to the row that contains column/cell and 3) repeat the same for any other columns/cells that may contain the same keyword.
I put together a sample script (see below) to demonstrate what I have done so far. In the sample script shown below, I was able to search and find the row contains the keyword and scroll to that row. However, I couldn't figure out how to highlight the cell that contains the keyword on that row.
The sample data shown below has pre-defined columns for demonstration purpose. However, the actual data returned from backend is dynamic such that the number of columns, title of the columns would vary and any column may contain the keyword. How do we select the cell that contains the keyword? Is there any better approach to achieve this overall goal? Thanks in advance for the help.
[xml]$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"
xmlns:local="clr-namespace:Test"
Title="MainWindow" Height="175" Width="550">
<Grid>
<TextBox x:Name="tb_Search" HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="149"/>
<Button x:Name="bt_Search" Content="Search" HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" IsDefault="True" Height="22" Margin="165,10,0,0" />
<DataGrid x:Name="dg" Margin="10,45,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="100" Height="Auto" Width="Auto" ColumnWidth="Auto" AlternationCount="1" IsReadOnly="True" SelectionMode="Extended" SelectionUnit="Cell" Background="White" />
</Grid>
</Window>
"#
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load($reader)
#Turn XAML into PowerShell objects
$xaml.SelectNodes("//*[#*[contains(translate(name(.),'n','N'),'x:Name')]]") | ForEach-Object{
Set-Variable -Name ($_.Name) -Value $Window.FindName($_.Name)
}
#sample data
$DataSet = New-Object System.Data.DataSet
$Table = $DataSet.Tables.Add("Table")
$Properties = #("Country","Capital","Population")
$Properties | foreach {
$Column = New-Object System.Data.DataColumn($_)
$Table.Columns.Add($Column)
}
$Null=$Table.Rows.Add("China PR","Beijing","20,693,000")
$Null=$Table.Rows.Add("India","New Delhi","16,787,949")
$Null=$Table.Rows.Add("Japan","Tokyo","13,189,000")
$Null=$Table.Rows.Add("Philippines","Manila","12,877,253")
$Null=$Table.Rows.Add("Russia","Moscow","11,541,000")
$Null=$Table.Rows.Add("Egypt","Cairo","10,230,350")
$Null=$Table.Rows.Add("USA","Washington, D.C","658,893")
$Null=$Table.Rows.Add("China PR","Beijing","20,693,000")
$Null=$Table.Rows.Add("India","New Delhi","16,787,949")
$Null=$Table.Rows.Add("Japan","Tokyo","13,189,000")
$Null=$Table.Rows.Add("Philippines","Manila","12,877,253")
$Null=$Table.Rows.Add("Russia","Moscow","11,541,000")
$Null=$Table.Rows.Add("Egypt","Cairo","10,230,350")
$Null=$Table.Rows.Add("USA","Washington, D.C","658,893")
#populate datagrid
$DataView = New-Object System.Data.DataView($Table)
$array = New-Object System.Collections.ArrayList
[void] $array.AddRange($DataView)
$dg.clear()
$dg.ItemsSource = $array
$dg.IsReadOnly = $true
$bt_Search.Add_Click({
$SearchValue = $tb_Search.text
for ($i = 0; $i -lt $dg.Items.Count; $i++)
{
if ($dg.Items[$i].Row[$dg.Columns.DisplayIndex] -eq "$SearchValue")
{
[System.Windows.Forms.MessageBox]::Show("Keyword Found")
$dg.ScrollIntoView($dg.Items[$i]) #scroll to the row that contains the keyword searched
}
}
})
#Display Form
$Window.ShowDialog() | Out-Null
Using a very similar method to what you're doing I was able to get it to highlight all cells that contain the search phrase (since I misunderstood the question at first). What I had to do was iterate through columns instead of searching all columns at once for each row. That way we know what column the desired info is in, and then we can select the cell in that column after we scroll the correct row into view.
$bt_Search.Add_Click({
$SearchValue = $tb_Search.text
$dg.SelectedCells.Clear()
for ($i = 0; $i -lt $dg.Items.Count; $i++)
{
0..($dg.Columns.Count-1)|?{$dg.Items[$i].Row[$_] -eq "$SearchValue"}|%{
$dg.ScrollIntoView($dg.Items[$i],$dg.Columns[$_])
$DGCell = $dg.Columns[$_].GetCellContent($dg.Items[$i]).Parent
$DGCellInfo = New-Object System.Windows.Controls.DataGridCellInfo($DGCell)
$dg.SelectedCells.add($DGCellInfo)
}
}
})
This at least gets you the groundwork to select a cell, you'll just need to figure out how to track the current cell to be able to move on to the next one since it sounds like you want to be able to move cell to cell by clicking the Search button over and over.
Edit: In regards to setting it up to do a Find/Find Next, you could setup a global variable, set $i to that, and then set that global variable inside your loop each time. Then anytime you get a match run a break to kill the loop, and it should just pick up where it left off. Might also want to add a line to reset the global variable before the loop so that it starts over if it hits the end. Something like this should do it:
$global:SearchIndex = 0
$bt_Search.Add_Click({
$SearchValue = $tb_Search.text
$dg.SelectedCells.Clear()
for ($i = $global:SearchIndex; $i -lt $dg.Items.Count; $i++)
{
0..($dg.Columns.Count-1)|?{$dg.Items[$i].Row[$_] -eq "$SearchValue"}|%{
$dg.ScrollIntoView($dg.Items[$i],$dg.Columns[$_])
$DGCell = $dg.Columns[$_].GetCellContent($dg.Items[$i]).Parent
$DGCellInfo = New-Object System.Windows.Controls.DataGridCellInfo($DGCell)
$dg.SelectedCells.add($DGCellInfo)
$global:SearchIndex = $i
break
}
}
#check if we hit the end of the table. If we did display a notice and reset the search index.
If($global:SearchIndex -ge $dg.Items.Count){
[System.Windows.Forms.MessageBox]::Show("No more matches found, resetting search to begining of table.")
$global:SearchIndex=0
}
})
Edit2: Ok, you're right, the break stops increments, so it will just find the same result each time. In order to fix that we need to also track the column. If we change the internal loop to a For loop as well we can just increment the internal loop each time we track. We also need to track breaks, so if the inner loop breaks the outer loop will too. So, something like this should do that:
$global:SearchIndex = 0
$global:SearchIndex2 = 0
$bt_Search.Add_Click({
$SearchValue = $tb_Search.text
$dg.SelectedCells.Clear()
for ($i = $global:SearchIndex; $i -lt $dg.Items.Count; $i++)
{
$global:BreakPoint = $false
For($j=$global:SearchIndex2;$j -le 2;$j++){
If($dg.Items[$i].Row[$j] -eq "$SearchValue"){
$dg.ScrollIntoView($dg.Items[$i],$dg.Columns[$j])
$DGCell = $dg.Columns[$j].GetCellContent($dg.Items[$i]).Parent
$DGCellInfo = New-Object System.Windows.Controls.DataGridCellInfo($DGCell)
$dg.SelectedCells.add($DGCellInfo)
$global:SearchIndex = $i
$global:SearchIndex2 = $j+1
$global:BreakPoint = $true
break
}
}
If($global:BreakPoint){break}
$global:SearchIndex2=0
}
#check if we hit the end of the table. If we did display a notice and reset the search index.
If($global:SearchIndex -ge $dg.Items.Count){
[System.Windows.Forms.MessageBox]::Show("No more matches found, resetting search to begining of table.")
$global:SearchIndex=0
$global:SearchIndex2=0
}
})

WPF runspace crashes using powershell version 2

I'm writing a powershell script, which installes various drivers.
My script is working fine, so I want to add a gui using WPF.
First I created a gui using WPF, nothing spectacular, just a window with a label.
I would like to update this label from my install script. So I create two runspaces, one that creates and shows the WPF gui and the other one executes my install script.
That works fine as long as I'm using powershell version 3 or higher. With powershell 2, which I had to use with a new Windows 7 installation, the wpf runspace crashes down.
I hope there is a way to get this working with powershell version 2.
Here is a sample script, demonstating what I'm doing.
#########################################################################################
#
# W P F - R U N S P A C E
#
#########################################################################################
$syncHashWpfLuaNotification = [hashtable]::Synchronized(#{})
$runspaceWpfLuaNotification =[runspacefactory]::CreateRunspace()
$runspaceWpfLuaNotification.ApartmentState = "STA"
$runspaceWpfLuaNotification.ThreadOptions = "ReuseThread"
$runspaceWpfLuaNotification.Open()
$runspaceWpfLuaNotification.SessionStateProxy.SetVariable("syncHashWpfLuaNotification",$syncHashWpfLuaNotification)
$psCmd = [PowerShell]::Create().AddScript({
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$xaml = #"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TreiberInstaller" Height="431" Width="626" Background="Black"
WindowStyle="None" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Label Name="lblProgress" Content="Progress" HorizontalAlignment="Center" Grid.Column="1" Grid.Row="2" VerticalAlignment="Top" Foreground="White" FontFamily="Calibri" FontSize="18" HorizontalContentAlignment="Center"/>
</Grid>
</Window>
"#
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$syncHashWpfLuaNotification.Window = [Windows.Markup.XamlReader]::Load( $reader )
$syncHashWpfLuaNotification.lblProgress = $syncHashWpfLuaNotification.window.FindName("lblProgress")
$syncHashWpfLuaNotification.Window.ShowDialog() | Out-Null
$syncHashWpfLuaNotification.Error = $Error
})
$psCmd.Runspace = $runspaceWpfLuaNotification
$data = $psCmd.BeginInvoke()
Sleep -Milliseconds 450 # Wait a moment that the gui is ready
#########################################################################################
#
# W O R K E R - R U N S P A C E
#
#########################################################################################
$ScriptBlock = {
#----------------------------------------------------------------------
# SetLabelText: Sets the lable-text
#----------------------------------------------------------------------
function SetLabelText
{
param(
[Parameter(Position=0, Mandatory = $true, ValueFromPipeline = $false)]
[ValidateNotNullOrEmpty()]
[string]$Text
)
if(-not $syncHashWpfLuaNotification) {return}
try
{
$syncHashWpfLuaNotification.Window.Dispatcher.invoke(
[action]{$syncHashWpfLuaNotification.lblProgress.Content = $Text},
"Normal"
)
}
catch {}
}
#----------------------------------------------------------------------
# CloseProgressWindow: Closes the window
#----------------------------------------------------------------------
function CloseProgressWindow()
{
if(-not $syncHashWpfLuaNotification) {return}
try
{
$syncHashWpfLuaNotification.Window.Dispatcher.invoke(
[action]{$syncHashWpfLuaNotification.Window.Close()},
"Normal"
)
}
catch{}
}
#Starting here
SetLabelText -Text "Starting installation..."
Sleep 2
for($i=1;$i -le 19; $i++)
{
SetLabelText -Text ("Progress Step " + $i)
}
for($i=20;$i -le 24; $i++)
{
SetLabelText -Text ("Progress Step " + $i)
Sleep 1
}
CloseProgressWindow
} #End of $ScriptBlock
$syncHash1 = [hashtable]::Synchronized(#{})
$workerRunspace =[runspacefactory]::CreateRunspace()
$workerRunspace.ApartmentState = "STA"
$workerRunspace.ThreadOptions = "ReuseThread"
$workerRunspace.Open()
$workerRunspace.SessionStateProxy.SetVariable("syncHash1",$syncHash1)
$workerRunspace.SessionStateProxy.SetVariable("syncHashWpfLuaNotification",$syncHashWpfLuaNotification)
$psCmd1 = [PowerShell]::Create().AddScript($ScriptBlock)
$psCmd1.Runspace = $workerRunspace
$data = $psCmd1.BeginInvoke()
#########################################################################################
#
# S C R I P T E N D
#
#########################################################################################
#Wait for end of both runspaces
while(($runspaceWpfLuaNotification.RunspaceAvailability -eq "Busy") -or ($workerRunspace.RunspaceAvailability -eq "Busy"))
{
if($runspaceWpfLuaNotification.RunspaceAvailability -eq "Busy") { Write-Host "Window is open" }
if($workerRunspace.RunspaceAvailability -eq "Busy") { Write-Host "Worker is running" }
Sleep 1
}
Write-Host "Script ended"
There are some problems with how your pass delegates to Dispatcher.Invoke call.
ScriptBlock literals are bounded to current session state. If you pass bounded ScriptBlock to different Runspace, then it can cause some undesired effects, like code not invoked in target Runspace or deadlocks. See my other answer about this. So, first thing you need to do is to create new not bounded ScriptBlock. You can do that with [ScriptBlock]::Create method.
Since ScriptBlock is no more bounded to current session state, you can not refer variables from it, like you do with $Text in your code. You should pass them as additional parameters to Dispatcher.Invoke.
In .NET Framework 3.5, which will be used with PowerShell v2, there is no Invoke(Action, DispatcherPriority) overload in Dispatcher class, so that, Invoke([Action]{...}, "Normal") will be resolved to this Invoke(Delegate, Object[]) method instead. You should use different overload: Invoke(DispatcherPriority, Delegate), which exists in .NET Framework 3.5.
Try downloading WPFRunspace, which should work with PS V2. It provides a background worker for WPF and Forms based scripts.

xml to sql server issue

I have one xml code which i would use for insert data to sql server table. I want to parse this xml and insert to sql server table.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="red_50">#FFEBEE</color>
<color name="red_100">#FFCDD2</color>
<color name="red_200">#EF9A9A</color>
<color name="red_300">#E57373</color>
<color name="red_400">#EF5350</color>
<color name="red_500">#F44336</color>
<color name="red_600">#E53935</color>
<color name="red_700">#D32F2F</color>
<color name="red_800">#C62828</color>
<color name="red_900">#B71C1C</color>
<color name="red_A100">#FF8A80</color>
<color name="red_A200">#FF5252</color>
<color name="red_A400">#FF1744</color>
<color name="red_A700">#D50000</color>
</resources>
Example:
For
<color name="orange_50">#FFF3E0</color>
The main problem is to get :
name:orange_50, hue:orange, value:50, hexcolor:#FFF3E0 and save all data to table.
This is what I have tried :
DECLARE #XML XML = '<resources>
<color name="red_600">#E53935</color>
<color name="red_700">#D32F2F</color>
<color name="red_800">#C62828</color>
<color name="red_900">#B71C1C</color>
<color name="red_A100">#FF8A80</color>
<color name="red_A200">#FF5252</color>
<color name="red_A400">#FF1744</color>
<color name="red_A700">#D50000</color>
</resources>
'
SELECT NAME = Events.value('#name', 'varchar(25)')
FROM #XML.nodes('ressources/color') AS XTbl(Events)
i try to get color name and work fine but for get hexadecimal i don't know how do. And i want to extract in red_800 for example, red only for hue column and 800 for value column.
Thanks
This is one possible way :
SELECT
NAME = Events.value('#name', 'varchar(25)')
,HUE = SUBSTRING(Events.value('#name', 'varchar(25)'), 1, CHARINDEX('_', Events.value('#name', 'varchar(25)'))-1)
,VALUE = SUBSTRING(Events.value('#name', 'varchar(25)'), CHARINDEX('_', Events.value('#name', 'varchar(25)')) + 1, LEN(Events.value('#name', 'varchar(25)')))
,HEXCOLOR = Events.value('.', 'varchar(25)')
FROM #XML.nodes('/resources/color') AS XTbl(Events)
Sqlfiddle Demo
output :
| NAME | HUE | VALUE | HEXCOLOR |
|----------|-----|-------|----------|
| red_600 | red | 600 | #E53935 |
| red_700 | red | 700 | #D32F2F |
| red_800 | red | 800 | #C62828 |
| red_900 | red | 900 | #B71C1C |
| red_A100 | red | A100 | #FF8A80 |
| red_A200 | red | A200 | #FF5252 |
| red_A400 | red | A400 | #FF1744 |
| red_A700 | red | A700 | #D50000 |
Related discussion : T-SQL get substring after specific character?*
*) The same trick used here to get part of the name attribute value that is located after _ for VALUE, and before _ for HUE columns

Selecting XML node using powershell with Where clause

I have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<video publishState="Published" xmlns="urn:schemas-microsoft-com:msnvideo:catalog">
<uuid>ed255a56e807</uuid>
<title>ABCD</title>
<description>ABCD</description>
<startDate>2014-06-12T06:30:00Z</startDate>
<activeEndDate>2017-06-01</activeEndDate>
<searchableEndDate>2017-06-01</searchableEndDate>
<archiveEndDate>2019-05-22</archiveEndDate>
<videoFiles>
<videoFile sourceFileHash="ebc2f23aba9f0ff8d0146a052089a34f" bitrate="400" width="320" height="180" fileSize="9654368" msnFileId="ED0ADD1E-7C36-472E-8186-5C43DDCCD58C" formatCode="101">
<uri>http://e2/ds/977d13e5-285f-4fe3-b49c-a6b604e89c22.mp4</uri>
</videoFile>
<videoFile sourceFileHash="ebc2f23aba9f0ff8d0146a052089a34f" bitrate="600" width="640" height="360" fileSize="14911095" msnFileId="59AB7AE5-15F3-4EA8-ABD9-92E1A1CE0424" formatCode="102">
<uri>http://e2/ds/0aaad23d-bcb1-48e4-85c7-788b58ab8ae6/ds/3f4b77a3-3157-48a4-88b5-df854025cfe5.mp4</uri>
</videoFile>
<videoFile msnFileId="EDAA3798-B16B-435F-A9CA-6B1D07729D44" formatCode="1001" fileHash="ebc2f23aba9f0ff8d0146a052089a34f">
<uri>ftp://handler_20140611_highlight_b_222747.mp4</uri>
</videoFile>
<videoFile sourceFileHash="ebc2f23aba9f0ff8d0146a052089a34f" bitrate="3000" width="1280" height="720" fileSize="70668740" msnFileId="9505AEF2-7742-4BB1-9427-F07185D6FC9B" formatCode="104">
<uri>http://e2/ds/a2e1563f-c9a3-41f7-97fe-e024742c6bbf.mp4</uri>
</videoFiles>
</video>
I am trying to get URI value where formatCode="1001". However since its an array, I am unable to do so. On getting to formatCode =1001 all values of array $uri are printed on console. I have tried piping Where clause e.g | Where {$xml.video.videoFiles.videoFile.formatCode -eq 1001 } but that too didn't help.
I am using the following code:
Get-ChildItem D:\video*.xml | ForEach-Object
{
$xml = [xml] (Get-Content $_.fullname)
# $xml.video.videoFiles.videoFile | Where-Object { $_.formatCode -eq 1001 } | Format-Table msnFileId, uri
$out = New-Object PSObject
$out | Add-Member NoteProperty UUID -Value $xml.video.uuid.innertext
$out | Add-Member NoteProperty EndDate -Value $xml.video.activeEndDate
Foreach ($uri in $xml.video.videoFiles.videoFile.uri)
{
if ($xml.video.videoFiles.videoFile.formatCode -eq 1001)
{
$temp1 = [string]$uri
}
}
$out | Add-Member NoteProperty URI -Value $temp1
Write-Output $out
}
XPath is a powerfull tool to select specific part of an XML. I know XPath but not powershell, I'm basing my answer from this post (your XML has similarity to the one I linked, both has default namespace), some modifications to the powershell script maybe required to make it fit your needs :
.....
$ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI)
$xml.SelectNodes("/ns:video/ns:videoFiles/ns:videoFile[#formatCode='1001']/ns:uri", $ns)
Or if you're sure formatCode is unique, you can use SelectSingleNode instead of SelectNodes.

How can I set Style properties on a column in an XAML datagrid using Powershell?

I am building a WPF UI using Powershell and XAML. The particular feature I am having problems with is setting the style on the columns. I am able to set the style within the XAML, but there are 9 columns (3 columns each in 3 data grids) so I feel it would be neater to set the properties in the code rather than repeat the same block 9 times.
This is how I set the style on a single column through XAML:
<DataGridTextColumn Header="Attribute" Binding="{Binding Attribute}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="Background" Value="LightGreen"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
This sets blank cells to light green. However, my preferred method would be to set this in Powershell.
So far I have figured out how to manually create the columns in Powershell if they are not explicitly added in XAML. I do this like so (after binding to the controls):
$arrAllDataGrids = #($dataGridUser, $dataGridHome, $dataGridWWID)
ForEach ($objDataGrid in $arrAllDataGrids)
{
$objDataGrid.AutoGenerateColumns = $false
$colAttribute = New-Object System.Windows.Controls.DataGridTextColumn -Property #{"Header" = "Attribute"; "Binding" = New-Object System.Windows.Data.Binding "Attribute"}
$colValue = New-Object System.Windows.Controls.DataGridTextColumn -Property #{"Header" = "Value"; "Binding" = New-Object System.Windows.Data.Binding "Value"}
$colDate = New-Object System.Windows.Controls.DataGridTextColumn -Property #{"Header" = "Date"; "Binding" = New-Object System.Windows.Data.Binding "Date"}
$arrColumns = #($colAttribute, $colValue, $colDate)
ForEach ($objColumn in $arrColumns) {$objDataGrid.Columns.Add($objColumn)}
}
However, I if I try and add a style through Powershell I come up against Dependency Properties. I've tried to examine them by creating the style in XAML and then trying to reverse engineer it in Powershell, this is what I've worked out so far:
$dataGridUser.Columns[0].ElementStyle is Type System.Windows.Style
$dataGridUser.Columns[0].ElementStyle.Triggers is Type System.Windows.Trigger
$dataGridUser.Columns[0].ElementStyle.Triggers.Property is Type Selected.System.Windows.Trigger
$dataGridUser.Columns[0].ElementStyle.Triggers.Property.Property is Type System.Windows.DependencyProperty
I'm unable to create an object of type System.Windows.DependencyProperty which should hold a Name property of Text. The best I have managed so far is to create one column through XAML and then extract the Dependency property object like this:
$objDepend = $($dataGridUser.Columns[0].ElementStyle.Triggers | Select Property).Property
Using this object, I can then create a new column object with the correct trigger property (I appreciate this is still missing the 'Setter' property but I think the same principles will apply) like this:
$objSelect = $(New-Object System.Windows.Trigger | Select Property)
$objSelect.Property = $objDepend
$objTrigger = New-Object System.Windows.Trigger
$objTrigger.Value = ""
$objTrigger.Property = $objSelect.Property
$objStyle = New-Object System.Windows.Style
$objStyle.Triggers.Add($objTrigger)
$colAttribute = New-Object System.Windows.Controls.DataGridTextColumn -Property #{"Header" = "Attribute"; "Binding" = New-Object System.Windows.Data.Binding "Attribute"; "ElementStyle" = $objStyle}
This is clearly not the right way to go about this though, I am obviously missing something, presumably around Dependency properties which I suspect of being derived rather than simply set.
I hope that all makes sense, can anyone help?
I hope it is acceptable to answer my own (and only) question but I thought I would share the solution I developed in case anyone else is in a similar position.
Simply write a function that creates the Windows.Style.Object and passes it back. I've started with two default strings like so:
$strXAMLStart = #"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<DataGrid x:Name="dataGridTrigger">
<DataGrid.Columns>
<DataGridTextColumn>
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>`n
"#
$strXAMLEnd = #"
</Style.Triggers>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
"#
So the function briefly creates and loads an XAML object using the style parameters passed to it and then extracts the style object and passes it back as the result of the function. This can then be applied to the column in the UI.
The function is as follows:
Function Set-ColumnStyle ($hashStyle)
{
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName PresentationCore
Add-Type -AssemblyName WindowsBase
Add-Type -AssemblyName System.Windows.Forms
$strXAMLTrigger = $null
ForEach ($key in $hashStyle.Keys)
{
$strValue = $hashStyle[$key]
If ($key -eq "Blank") {$strTrigger = ""}
Else {$strTrigger = $key}
$strXAMLTrigger += " <Trigger Property=`"Text`" Value=`"$strTrigger`">`n"
$strXAMLTrigger += " <Setter Property=`"Background`" Value=`"$strValue`"/>`n"
$strXAMLTrigger += " </Trigger>`n"
}
[xml]$xamlContent = $strXAMLStart + $strXAMLTrigger + $strXAMLEnd
$xamlReader = (New-Object System.Xml.XmlNodeReader $xamlContent)
$xamlWindow = [Windows.Markup.XamlReader]::Load($xamlReader)
$dataGridTrigger = $xamlWindow.FindName("dataGridTrigger")
$objStyle = $dataGridTrigger.Columns[0].ElementStyle
Return $objStyle
}
I've used the string "blank" to represent actual blank cells, so this is replaced by "" if detected.
You can now call this function and pass it a hashtable of column contents to desired colours and use the returned object to set the style on the column:
$hashStyle = #{"Blank" = "#FFFF5050"; "0" = "#FF50FF50"; "267009" = "#FF50FF50"}
$objStyle = Set-ColumnStyle $hashStyle
$colDay = New-Object System.Windows.Controls.DataGridTextColumn -Property #{"Header" = "Day"; "Binding" = New-Object System.Windows.Data.Binding "Day"; "ElementStyle" = $objStyle}
$dataGridScripts.Columns.Add($colDay)
Hope that all makes sense!

Resources