UIfiedWPFBase.ps1
using namespace System.Collections.Generic using namespace System.Windows using namespace System.Windows.Controls using namespace System.Windows.Input class WPFElement : UIElement { WPFElement() { $this.WrapProperty("Enable", "IsEnabled") Add-Member -InputObject $this -Name Visible -MemberType ScriptProperty -Value { $this.NativeUI.Visibility -eq [Visibility]::Visible } -SecondValue { if ($args[0]) { $this.NativeUI.Visibility = [Visibility]::Visible } else { $this.NativeUI.Visibility = [Visibility]::Collapsed } } $this.AddNativeUIChild = { param ( [WPFElement] $element ) $this.NativeUI.AddChild($element.NativeUI) | Out-Null } $this.RemoveNativeUIChild = { param ( [WPFElement] $element ) $this.NativeUI.Children.Remove($element.NativeUI) | Out-Null } $this.ShowError = { param ( [Object] $errorObject ) [MessageBox]::Show($errorObject) } } } class WPFHost : UIHost { [HashTable] $SyncHash $UIRunspace [void] ShowFrame([ScriptBlock] $frameScriptBlock) { $this.SyncHash = [HashTable]::Synchronized(@{}) $this.SyncHash.Errors = @() $this.UIRunspace =[RunspaceFactory]::CreateRunspace() $this.UIRunspace.Name = "UIRunspace" $this.UIRunspace.ApartmentState = "STA" $this.UIRunspace.ThreadOptions = "ReuseThread" $this.UIRunspace.Open() $this.UIRunspace.SessionStateProxy.SetVariable("SyncHash", $this.SyncHash) $referenceScript = " Import-Module ""$PSScriptRoot\..\UIfied"" Set-UIWpf " $ps = [PowerShell]::Create() $ps = $ps.AddScript($referenceScript) $script = [ScriptBlock]::Create("`$SyncHash.Window = " + $frameScriptBlock.ToString() + "; `$SyncHash.Window.ShowDialog()") $ps = $ps.AddScript($script) $ps.Runspace = $this.UIRunspace $ps.BeginInvoke() | Out-Null } } class WPFWindow : WindowBase { WPFWindow() { $windowNativeUI = [Window]::new() $windowNativeUI.SizeToContent = 'WidthAndHeight' $windowNativeUI.Margin = 10 $this.SetNativeUI($windowNativeUI) $this.WrapProperty("Caption", "Title") $this.AddScriptBlockProperty("Loaded") $this.AddNativeUIChild = { param ( [WPFElement] $element ) $this.NativeUI.Content = $element.NativeUI } } [void] ShowDialog() { $this.NativeUI.ShowDialog() } [void] OnLoaded() { Invoke-Command -ScriptBlock $this._Loaded -ArgumentList $this } } class WPFStackPanel : WPFElement { WPFStackPanel() { $this.SetNativeUI([StackPanel]::new()) $this.WrapProperty("Orientation", "Orientation") } } class WPFLabel : WPFElement { WPFLabel() { $this.SetNativeUI([Label]::new()) $this.WrapProperty("Caption", "Content") } } class WPFButton : WPFElement { WPFButton() { $this.SetNativeUI([Button]::new()) $this.WrapProperty("Caption", "Content") $this.AddScriptBlockProperty("Action") $this.NativeUI.Add_Click({ $this.Control.OnAction() }) } [void] OnAction() { $this.InvokeTrappableCommand($this._Action, $this) } } class WPFTextBox : WPFElement { WPFTextBox() { $this.SetNativeUI([TextBox]::new()) $this.WrapProperty("Text", "Text") $this.AddScriptBlockProperty("Change") $this.NativeUI.Add_TextChanged({ $this.Control.OnChange() }) } [void] OnChange() { $this.InvokeTrappableCommand($this._Change, $this) } } class WPFCheckBox : WPFElement { WPFCheckBox() { $this.SetNativeUI([CheckBox]::new()) $this.WrapProperty("Caption", "Content") $this.WrapProperty("IsChecked", "IsChecked") $this.AddScriptBlockProperty("Click") $this.NativeUI.Add_Click({ $this.Control.OnClick() }) } [void] OnClick() { $this.InvokeTrappableCommand($this._Click, $this) } } class WPFRadioButton : WPFElement { WPFRadioButton() { $this.SetNativeUI([RadioButton]::new()) $this.WrapProperty("Caption", "Content") $this.WrapProperty("IsChecked", "IsChecked") $this.AddScriptBlockProperty("Click") $this.NativeUI.Add_Click({ $this.Control.OnClick() }) } [void] OnClick() { $this.InvokeTrappableCommand($this._Click, $this) } } class WPFRadioGroup : WPFElement { hidden $StackPanel WPFRadioGroup() { $this.SetNativeUI([GroupBox]::new()) $this.StackPanel = [StackPanel]::new() $this.NativeUI.Content = $this.StackPanel $this.AddNativeUIChild = { param ( [WPFElement] $element ) $this.StackPanel.AddChild($element.NativeUI) } } } class WPFList : WPFStackPanel { [List[ListItem]] $Items = [List[ListItem]]::new() WPFList() { $this.Orientation = [Orientation]::Horizontal } [void] AddColumn([WPFListColumn] $listColumn) { $column = [WPFStackPanel]::new() $column.Orientation = [Orientation]::Vertical $title = [WPFLabel]::new() $title.Caption = $listColumn.Title $column.AddChild($title) $this.AddChild($column) } [void] AddItem([ListItem] $listItem) { $this.Items.Add($listItem) $columnIndex = 0 $this.Children | ForEach-Object { $column = $_ $cell = $listItem.Children.Item($columnIndex) $cell.NativeUI.Height = 25 $column.AddChild($cell) $columnIndex++ } } [void] RemoveItem([ListItem] $listItem) { $this.Items.Remove($listItem) $columnIndex = 0 $this.Children | ForEach-Object { $column = $_ $cell = $listItem.Children.Item($columnIndex) $column.RemoveChild($cell) $columnIndex++ } } [void] Clear() { $this.Items.ToArray() | ForEach-Object { $this.RemoveItem($_) } } } class WPFListColumn { [String] $Name [String] $Title } class WPFTabItem : WPFElement { hidden $StackPanelNativeUI WPFTabItem() { $this.SetNativeUI([TabItem]::new()) $this.StackPanelNativeUI = [StackPanel]::new() $this.NativeUI.Content = $this.StackPanelNativeUI $this.WrapProperty("Caption", "Header") $this.AddNativeUIChild = { param ( [WPFElement] $element ) $this.StackPanelNativeUI.AddChild($element.NativeUI) | Out-Null } } } class WPFTabControl : WPFElement { WPFTabControl() { $this.SetNativeUI([TabControl]::new()) } } class WPFModal : WPFElement { WPFModal() { $windowNativeUI = [Window]::new() $windowNativeUI.WindowStyle = [WindowStyle]::None $windowNativeUI.SizeToContent = 'WidthAndHeight' $windowNativeUI.Margin = 10 $this.SetNativeUI($windowNativeUI) #$this.WrapProperty("Caption", "Title") $this.AddNativeUIChild = { param ( [WPFElement] $element ) $this.NativeUI.Content = $element.NativeUI } } [void] Show() { $this.NativeUI.WindowStartupLocation = "CenterOwner" $this.NativeUI.ShowDialog() } [void] Hide() { $this.NativeUI.Hide() } } class WPFTimer : WPFElement { [System.Windows.Threading.DispatcherTimer] $Timer [Double] $Interval = 1000 WPFTimer () { $label = [Label]::new() $label.Visibility = [Visibility]::Collapsed $this.SetNativeUI($label) $this.AddScriptBlockProperty("Elapsed") $this.Timer = New-Object System.Windows.Threading.DispatcherTimer Add-Member -InputObject $this.Timer -MemberType NoteProperty -Name Control -Value $this $this.Timer.Add_Tick({ $this.Control.OnElapsed() #[System.Windows.Input.CommandManager]::InvalidateRequerySuggested() }) } [void] OnElapsed() { Invoke-Command -ScriptBlock $this._Elapsed -ArgumentList $this } [void] Start() { $this.Timer.Interval = [TimeSpan]::FromSeconds($this.Interval / 1000) $this.Timer.Start() } [void] Stop() { $this.Timer.Stop() } } class WPFDatePicker : WPFElement { WPFDatePicker() { $this.SetNativeUI([DatePicker]::new()) $this.WrapProperty("Value", "SelectedDate") $this.AddScriptBlockProperty("Change") $this.NativeUI.Add_SelectedDateChanged({ $this.Control.OnChange() }) } [void] OnChange() { $this.InvokeTrappableCommand($this._Change, $this) } } class WPFTimePicker : WPFElement { WPFTimePicker() { $textBox = [TextBox]::new() $textBox.MaxLength = 5 $this.SetNativeUI($textBox) Add-Member -InputObject $this -Name Value -MemberType ScriptProperty -Value { $this.GetTextTime() } -SecondValue { $this.NativeUI.Text = $args[0] } $this.AddScriptBlockProperty("Change") $this.NativeUI.Add_TextChanged({ $this.Control.OnChange() }) $this.AddScriptBlockProperty("LostFocus") $this.NativeUI.Add_LostFocus({ $this.Control.OnLostFocus() }) } [void] OnChange() { $this.InvokeTrappableCommand($this._Change, $this) } [void] OnLostFocus() { $this.Value = $this.GetTextTime() $this.InvokeTrappableCommand($this._LostFocus, $this) } hidden [String] GetTextTime() { [DateTime] $dateTime = [DateTime]::Today if (-not [DateTime]::TryParse("2000-01-01 " + $this.NativeUI.Text, [ref] $dateTime)) { return "00:00" } else { return $dateTime.ToShortTimeString() } } } class WPFBrowser : WPFStackPanel { [HashTable[]] $Data = [HashTable[]] @() [int] $PageRows = 10 [int] $CurrentPage = 0 [Boolean] $IsEditable = $true [HashTable] $CurrentRow #region Components Declaration hidden [WPFListColumn[]] $Columns = [WPFListColumn[]] @() hidden [WPFListColumn] $EditionColumn hidden [WPFList] $List = [WPFList]::new() hidden [WPFStackPanel] $ButtonPanel = [WPFStackPanel]::new() hidden [WPFButton] $FirstButton = [WPFButton]::new() hidden [WPFButton] $PreviousButton = [WPFButton]::new() hidden [WPFButton] $NextButton = [WPFButton]::new() hidden [WPFButton] $LastButton = [WPFButton]::new() hidden [WPFButton] $AddNewButton = [WPFButton]::new() #endregion WPFBrowser() { $this.AddScriptBlockProperty("AddNew") $this.AddScriptBlockProperty("Edit") $this.AddScriptBlockProperty("Delete") $this.AddChild($this.List) $this.AddButtons() } #region Components Creation hidden [void] AddButtons() { $this.ButtonPanel = [WPFStackPanel]::new() $this.ButtonPanel.Orientation = "Horizontal" $this.FirstButton.Action = { $this.Parent.Parent.OnMoveFirst() } $this.PreviousButton.Action = { $this.Parent.Parent.OnMovePrevious() } $this.NextButton.Action = { $this.Parent.Parent.OnMoveNext() } $this.LastButton.Action = { $this.Parent.Parent.OnMoveLast() } $this.AddNewButton.Action = { $this.Parent.Parent.OnAddNew() } $this.ButtonPanel.AddChild($this.FirstButton) $this.ButtonPanel.AddChild($this.PreviousButton) $this.ButtonPanel.AddChild($this.NextButton) $this.ButtonPanel.AddChild($this.LastButton) $this.ButtonPanel.AddChild($this.AddNewButton) $this.StyleComponents() $this.AddChild($this.ButtonPanel) } [void] AddColumn([WPFListColumn] $listColumn) { $this.Columns += $listColumn $this.List.AddColumn($listColumn) } hidden [void] CreateEditionColumn() { $this.EditionColumn = New-Object WPFListColumn -Property @{Name = "_Edition"; Title = " "} $this.AddColumn($this.EditionColumn) } hidden [void] AddEditionButtons([HashTable] $hash, [ListItem] $listItem, [int] $rowIndex) { $editionPanel = [WPFStackPanel]::new() $editionPanel.Orientation = "Horizontal" $listItem.AddChild($editionPanel) $editButton = [WPFButton]::new() Add-Member -InputObject $editButton -MemberType NoteProperty -Name CurrentRow -Value $hash $editButton.Action = { $this.Parent.Parent.Parent.Parent.CurrentRow = $this.CurrentRow $this.Parent.Parent.Parent.Parent.OnEdit() } $editionPanel.AddChild($editButton) $deleteButton = [WPFButton]::new() Add-Member -InputObject $deleteButton -MemberType NoteProperty -Name CurrentRow -Value $hash $deleteButton.Action = { $this.Parent.Parent.Parent.Parent.CurrentRow = $this.CurrentRow $this.Parent.Parent.Parent.Parent.OnDelete() } $editionPanel.AddChild($deleteButton) $this.StyleEditionButtons($editButton, $deleteButton, $rowIndex) } #endregion #region Data show [void] Refresh() { $this.RefreshEditable() $rowIndex = 0 $this.List.Clear() $this.GetSelectedData() | ForEach-Object { $hash = $_ $listItem = $this.GetDataListItem($hash, $rowIndex) $this.List.AddItem($listItem) $rowIndex++ } } hidden [void] RefreshEditable() { if ($this.EditionColumn -eq $null -and $this.IsEditable) { $this.CreateEditionColumn() } $this.AddNewButton.Visible = $this.IsEditable } hidden [HashTable[]] GetSelectedData() { return $this.Data | Select-Object -Skip ($this.CurrentPage * $this.PageRows) -First $this.PageRows } hidden [int] GetLastPage() { $lastPage = [Math]::Truncate($this.Data.Count / $this.PageRows) if (($this.Data.Count % $this.PageRows) -eq 0) { $lastPage-- } return $lastPage } hidden [ListItem] GetDataListItem([HashTable] $hash, [int] $rowIndex) { $listItem = [ListItem]::new() $this.Columns | ForEach-Object { $column = $_ if ($column -eq $this.EditionColumn -and $this.IsEditable) { $this.AddEditionButtons($hash, $listItem, $rowIndex) } else { $this.AddCell($hash, $column.Name, $listItem, $rowIndex) } } return $listItem } [void] AddCell([HashTable] $hash, [string] $columnName, [ListItem] $listItem, [int] $rowIndex) { $itemLabel = [WPFLabel]::new() $itemLabel.Caption = $hash."$columnName" $this.StyleCell($itemLabel, $rowIndex) $listItem.AddChild($itemLabel) } #endregion #region Style [Media.Brush] $ShadowBrush = ([Media.Brush] "#FFF3F3F3") [Media.Brush] GetRowBackground([int] $rowIndex) { if (($rowIndex % 2) -eq 0) { return $this.ShadowBrush } else { return [Media.Brushes]::Transparent } } [void] StyleComponents() { $this.ButtonPanel.NativeUI.HorizontalAlignment = "Right" $this.FirstButton.Caption = "|<" $this.PreviousButton.Caption = "<<" $this.NextButton.Caption = ">>" $this.LastButton.Caption = ">|" $this.AddNewButton.Caption = "+" $this.FirstButton.NativeUI.Margin = 10 $this.PreviousButton.NativeUI.Margin = 10 $this.NextButton.NativeUI.Margin = 10 $this.LastButton.NativeUI.Margin = 10 $this.AddNewButton.NativeUI.Margin = 10 } [void] StyleCell($cell, [int] $rowIndex) { $cell.NativeUI.Background = $this.GetRowBackground($rowIndex) } [void] StyleEditionButtons([WPFButton] $editButton, [WPFButton] $deleteButton, [int] $rowIndex) { $editButton.Caption = "/" $deleteButton.Caption = "X" $editButton.Parent.NativeUI.Background = $this.GetRowBackground($rowIndex) } #endregion #region Move Events hidden [void] OnMoveFirst() { $this.CurrentPage = 0 $this.Refresh() } hidden [void] OnMovePrevious() { if ($this.CurrentPage -gt 0) { $this.CurrentPage-- } $this.Refresh() } hidden [void] OnMoveNext() { if ($this.CurrentPage -lt $this.GetLastPage()) { $this.CurrentPage++ } $this.Refresh() } hidden [void] OnMoveLast() { $this.CurrentPage = $this.GetLastPage() $this.Refresh() } #endregion #region CRUD Events hidden [void] OnAddNew() { $this.InvokeTrappableCommand($this._AddNew, $this) } hidden [void] OnEdit() { $this.InvokeTrappableCommand($this._Edit, $this) } hidden [void] OnDelete() { $this.InvokeTrappableCommand($this._Delete, $this) } #endregion } class WPFMenuItem : WPFElement { WPFMenuItem() { $this.SetNativeUI([MenuItem]::new()) $this.WrapProperty("Caption", "Header") $this.AddScriptBlockProperty("Action") $this.NativeUI.Add_Click({ $this.Control.OnAction() }) } [void] OnAction() { $this.InvokeTrappableCommand($this._Action, $this) } } class WPFDropDownMenu : WPFButton { WPFDropDownMenu() { $this.NativeUI.ContextMenu = [ContextMenu]::new() $this.AddNativeUIChild = { param ( [WPFElement] $element ) $this.NativeUI.ContextMenu.Items.Add($element.NativeUI) } $this.Action = { param($this) $this.NativeUI.ContextMenu.IsOpen = -not $this.NativeUI.ContextMenu.IsOpen } } } class WPFAutoComplete : WPFTextBox { WPFAutoComplete() { $this.NativeUI.ContextMenu = [ContextMenu]::new() $this.NativeUI.ContextMenu.PlacementTarget = $this.NativeUI $this.NativeUI.ContextMenu.Placement = [System.Windows.Controls.Primitives.PlacementMode]::Bottom $this.NativeUI.Add_KeyUp({ if ($_.Key -eq [Key]::Down) { $this.Control.AddItems() $this.Control.NativeUI.ContextMenu.IsOpen = $true $this.Control.NativeUI.ContextMenu.Focus() } }) $this.AddScriptBlockProperty("ItemsRequested") } [void] AddItems() { $this.OnItemsRequested() } [void] OnItemsRequested() { [AutoCompleteItem[]] $items = Invoke-Command -ScriptBlock $this._ItemsRequested -ArgumentList $this | Select-Object -First 20 $this.NativeUI.ContextMenu.Items.Clear() 0..($items.Count - 1) | ForEach-Object { $menuItem = [WPFMenuItem]::new() $menuItem.Caption = $items[$_].Text Add-Member -InputObject $menuItem -MemberType NoteProperty -Name AutoCompleteTextBox -Value $this Add-Member -InputObject $menuItem -MemberType NoteProperty -Name AutoCompleteId -Value $items[$_].Id $menuItem.Action = { param ($this) $this.AutoCompleteTextBox.Text = $this.AutoCompleteId $this.AutoCompleteTextBox.NativeUI.ContextMenu.IsOpen = $false } $this.NativeUI.ContextMenu.Items.Add($menuItem.NativeUI) } #$this.NativeUI.ContextMenu.MinWidth = $this.NativeUI.Width } } |