Why WPF app takes a few minutes to complete closing? - wpf

Here's a quite simple code but I found a weird behaviour when closing the app from task bar (right-click icon -> Close window). Could somebody explain why it behaves like the following?
a) if window is minimized the app takes a few minutes to complete closing.
b) if window is not minimized app closes immediately.
c) if window is minimized but the 2 commented lines are uncommented app closes immediately.
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[xml]$xaml = #'
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
</Window>
'#
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
$window = [Windows.Markup.XamlReader]::Load($reader)
$window.add_Closing({
# $window.WindowState = "Normal"
# $window.Hide()
Write-Host "Here will everytime be passed immediately."
})
$window.ShowDialog() > $null
Write-Host "Here takes time to be passed when minimized."
Environment
Windows 10 Home (10.0.18363)
PowerShell 5.1.18362.752
Updated 08/06/2020
Added two Write-Host statement. Both will be passed immediately in case of b) and c) but won't in case of a).

Related

POSHGUI How to access SelectedItem/Value of a WPF DataGrid

I am using a SaaS called Poshgui that allows you to create WPF GUIs for the frontend and connect controls with Powershell functions as your code-behind utilizing two-way data binding. Currently, I have a button control that when clicked fires off a powershell function, which executes a SQL query against the database to return all actively running jobs and binds that data to my DataGrid, which is also displayed in the frontend GUI.
What I would like to do is click/highlight the row in the datagrid and be able to click a separate button that will execute a powershell function using the value of the column/row the user currently has selected. I have tried using SelectedItem/SelectedValue, but to no avail.
Here is an example of something I have tried, but unfortunately can not get the selected value back in my testing: $test = ($JobStatusDataGrid.SelectedItem).JobName
<DataGrid HorizontalAlignment="Left" VerticalAlignment="Top" Width="332" Height="334" Margin="31,152,0,0" ItemsSource="{Binding JobStatusData}" Name="JobStatusDataGrid"/>
function StartJob {
$results = New-Object 'System.Collections.ObjectModel.ObservableCollection[System.Object]'
foreach ($object in $JobStatusDataGrid.SelectedItems)
{
$results.Add([PSCustomObject]#{
"JobName" = $object.JobName
"CurrentStatus" = $object.CurrentStatus
}
)
write-host $object.JobName
}
}

Error trying to implement IValueConverter in PowerShell - XAML GUI script

What am I missing? I was trying to implement converters to my XAML-based PowerShell script but with no luck.
I've picked up pieces of information from sites like StackOverflow. but couldn't find one successful implementation of a converter in powershell XAML-based GUI script.
in the code I am testing the converter, and it works (you can see 2 examples for conversion) so that means that powershell itself accepted the new converter type, buy this converter cannot be implemented in my xaml code.
$src = #'
using System;
using System.Windows;
using System.Windows.Data;
using System.Globalization;
namespace MyProject
{
public class DemoConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return "kuku";
}
else
{
return "bobo";
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
}
'#
Add-Type -AssemblyName PresentationFramework
Add-Type -TypeDefinition $src -ReferencedAssemblies PresentationFramework
#Checking that the new type works and convert is done...
$c = new-object MyProject.DemoConverter
$c.Convert("gg", $null, $null, $null)
$c.Convert(55, $null, $null, $null)
#Now declaring and loading the xaml
[xml]$XAML = #'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cnv="clr-namespace:MyProject" >
<Window.Resources>
<cnv:DemoConverter x:Key="TestConverter" />
</Window.Resources>
<Grid>
<TextBox x:Name="txtTestValue" Text="I'm here to show that xaml loading works!" />
</Grid>
</Window>
'#
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )
$Window.ShowDialog() | out-null
I keep getting this error:
Exception calling "Load" with "1" argument(s): "Cannot create unknown type '{clr-namespace:MyProject}DemoConverter'."
If I remove the line: <cnv:DemoConverter x:Key="TestConverter" />
It will not give the above error and the window will show (but of course, convertion in xaml will not be available), so I guess I'm doing something wrong with namespace and/or assembly deceleration that XAML doesn't like.
Note that on my xaml I'm not yet using the converter. I just want to overcome the above error before trying to use the converter.
Thank you so much in advance!
I signed up for Stackoverflow just so I could participate in this question.
I searched for HOURS for a solution to this before I ran across this question here, and then my hopes were dashed to see it's been sitting out here almost a year with no answer.
I had found this related question earlier, and it gives the basic solution we're trying to implement with this question, but doesn't give any details on making it actually work.
How to use IValueConverter from powershell?
Luckily, after several more hours, and piecing together a lot of other info, I finally solved it!
There are 3 pieces to it.
The converter type being added via the C# code is not part of the same namespace of the form, even if you specify the form's namespace in the C# class code. So an extra xmlns: line is needed to include the converter type's namespace in the form. Then when defining the converter in the form resources, you reference that namespace instead of the form's own namespace (usually "local", but I see your example you used "cnv"). Also though, for the reason below, you want your namespace in your C# code to be different from your form's own namespace. (I don't know for sure, but the system might get confused having 2 different namespaces that are really physically separate.)
BUT, even with the added xmlns: line to include the converter's namespace, the form still can't find it. This is becuase, I discovered, the added converter type is also added to a different Assembly. Apparently when adding custom types with "Add-Type -TypeDefinition", PowerShell creates its own in-memory Assembly and adds the type to that. So, in the added xmlns: line to include the converter's namespace, we also have to specify the assembly for the custom converter type.
BUT, BUT... What's worse is that this "made-up" Assembly has a randomly generated name, AND that Assembly name is different every time you run the script. (I could not find this fact documented anywhere. I discovered it entirely by accident.) So how can you include an assembly name if you don't know the name? Basically do what you did, create an instance of your new converter object in a variable, then check the properties of that new object variable to determine the Assembly name. Then use string Replace to update your XAML string with the Assembly name.
SO, see the updated code I posted which has the following changes. As the original poster indicated, this example doesn't show the converter in action, but the script will at least run and display the form without error, indicating the the form itself accepted the converter definition and usage in the TextBox.
Changed namespace in the C# code to MyConverter so it's different
from the form's MyProject namespace.
In the line that creates the $c instance of the converter object, I changed the namespace to MyConverter instead of MyProject, to match the new namespace as above.
Added $AssemblyName variable to pull out the random assembly name. (I also commented out the calls to Convert and ConvertBack.)
Changed the Here-String for the XAML code to a string variable, $inputXML so we can use Replace on it. Will convert to xml document later.
xlmns:Converter line added to include MyConverter namespace. Note the ";assembly=myassembly" piece. The "myassembly" name is simply aplaceholder for the string Replace to easily find later.
In the declaration of the converter Window.Resources, changed the "cnv:" to "Converter:" to match the Converter type's namespace declaration.
Added another TextBox to show including the Converter and that the form build accepts this.
Added [xml]$XAML = $inputXML.... line to convert to xml and replace with the actual assembly name.
Code
$src = #'
using System;
using System.Windows;
using System.Windows.Data;
using System.Globalization;
namespace MyConverter
{
public class DemoConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return "kuku";
}
else
{
return "bobo";
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
}
'#
Add-Type -AssemblyName PresentationFramework
Add-Type -TypeDefinition $src -ReferencedAssemblies PresentationFramework
#Checking that the new type works and convert is done...
$c = new-object MyConverter.DemoConverter
$AssemblyName = $c.gettype().Assembly.FullName.Split(',')[0]
#$c.Convert("gg", $null, $null, $null)
#$c.Convert(55, $null, $null, $null)
#Now declaring and loading the xaml
$inputXML = #'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Converter="clr-namespace:MyConverter;assembly=myassembly"
xmlns:cnv="clr-namespace:MyProject" >
<Window.Resources>
<Converter:DemoConverter x:Key="TestConverter" />
</Window.Resources>
<Grid>
<TextBox x:Name="txtTestValue" Text="I'm here to show that xaml loading works!" />
<TextBox x:Name="txtTestValue2" Text="{Binding Path=Whatever, Converter={StaticResource TestConverter}}" />
</Grid>
</Window>
'#
[xml]$XAML = $inputXML -replace 'myassembly', $AssemblyName
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )
$Window.ShowDialog() | out-null

Error when using PowerShell to add DataSource to ComboBox

My goal is to have a viewable text that can say what I want and have the value of that text be used for the onchange event. However, I can't seem to get the datasource to attach to the combobox.
<ComboBox x:Name="WPFDomainUsersBox" HorizontalAlignment="Left" Margin="288,10,0,0" VerticalAlignment="Top" Width="215" Height="23" Text="Domain Users"/>
...
...
...
#create a datatable to bind to our combobox
$datatable = New-Object system.Data.DataTable
#Define Columns
$ColValue = New-Object system.Data.DataColumn "Value",([string])
$ColText = New-Object system.Data.DataColumn "Text",([string])
#add columns to datatable
$datatable.columns.add($ColValue)
$datatable.columns.add($ColText)
#List option.
$DomainUsers = Get-ADUser -Filter *
ForEach($DUsers in $DomainUsers) {
#$WPFDomainUsersBox.Items.Add($DUsers.SamAccountName)
$datarow = $datatable.NewRow()
#Enter data in the row
$datarow.Value = $DUsers.SamAccountName
$datarow.Text = $DUsers.SamAccountName
#Add the row to the datatable
$datatable.Rows.Add($datarow)
}
$WPFDomainUsersBox.Datasource = $datatable
I keep getting the following:
The property 'Datasource' cannot be found on this object. Verify that
the property exists and can be set.
A WPF ComboBox has no property named "Datasource". It has an ItemsSource property that you can set to any IEnumerable like for example the DataView of a DataTable:
$WPFDomainUsersBox.ItemsSource = $datatable.DefaultView

Basic databinding example with PowerShell and ShowUI - can't make it work at all

I'm using ShowUI to explore PowerShell GUIs, it's based on WPF behind the scenes, and I'm trying to get databinding to work. Ideally I'd like a hashtable of my data and textboxes bound to the properties, so that typing in the textboxes updates the hashtable and updating the hashtable updates the textboxes. Or something approximating that.
I don't know my way around WPF databinding, I'm trying things like this:
ipmo showui
StackPanel {
label -name "a" -Content { binding -ElementBinding b -path Text }
Textbox -name "b"
} -Show
and what I get is a UI showing up, but typing doesn't change anything. I've tried quite a few trial-and-error variations on this - setting the binding on the TextBox instead using Textbox -Text { binding... }, or binding on both, using syntax like binding -Source a instead of ElementBinding, using binding -Source $a with the control's variable name, using -DataContexts on the textbox or on the parent stackpanel, trying with and without default values in various places for the commands. I've tried using a button with an event handler that updates a hashtable and trying to bind the hashtable, or with a PSCustomObject; binding a textbox to a slider value - a lot of trial and error, but no result.
There is an example of databinding in ShowUI here which pulls command help into a list and steps through it, that appears to work fine. And the first example here also works fine - as long as you have pictures in the folder. These make me think ShowUI can handle databinding - and without scaffolding or initialization code.
Following this C# tutorial and trying to port it almost literally to ShowUI, I get this:
ipmo showui
$s = [pscustomobject]#{fname="Mahak"; lname="Garg";}
Grid -Name "StuInfo" -rows 3 {
TextBox -Text { Binding fname } -row 0
Textbox -Text { Binding lname } -row 1
Button -name "button1" -Content "Next" -row 2 -On_Click {
$s2 = [pscustomobject]#{fname="Jupi"; lname="Gupta";}
$stuinfo.DataContext = $s2
Write-Host "."
}
} -On_Loaded {
$stuinfo.DataContext = $s
} -show
And the UI appears, and the data does not. Clicking the button writes a . to the console, but does not update the textboxes.
I'm using PowerShell 4 so I can't directly use classes, and the latest ShowUI 1.5 (I think) dev branch from Github. [Edit: this was at least part of my problem, actually using an old version on one computer and a new version on another]
What am I missing or misunderstanding?
I've never used ShowUI before (didn't know it existed until I saw this question). So, I downloaded it and tried your script above. I got the same results as you did. I found that the $s variable in the On_Loaded method was null. Changing the two instances of $s to $global:s made it work:
ipmo showui
$global:s = [pscustomobject]#{fname="Mahak"; lname="Garg";}
Grid -Name "StuInfo" -rows 3 {
TextBox -Text { Binding fname } -row 0
Textbox -Text { Binding lname } -row 1
Button -name "button1" -Content "Next" -row 2 -On_Click {
$s2 = [pscustomobject]#{fname="Jupi"; lname="Gupta";}
$stuinfo.DataContext = $s2
Write-Host "."
}
} -On_Loaded {
$stuinfo.DataContext = $global:s
} -show
The first script you had, I couldn't get binding to an element working. I tried using a DataContext to link them that way and it worked... sort of. It seems like whatever binds to the property first is the only item that can update it. So, if the textbox is first then it worked but if the label was first it didn't work.
Works:
ipmo showui
$dc = [pscustomobject]#{ myText="my text" }
StackPanel {
Textbox -Text {Binding -Path myText -UpdateSourceTrigger PropertyChanged}
Label -Content {Binding -Path myText}
} -Show -DataContext #($dc)
$dc
Didn't work:
ipmo showui
$dc = [pscustomobject]#{ myText="my text" }
StackPanel {
Label -Content {Binding -Path myText}
Textbox -Text {Binding -Path myText -UpdateSourceTrigger PropertyChanged}
} -Show -DataContext #($dc)
$dc
I hope this helps you some... it seems like ShowUI would be a nice tool but IDK, seems buggy.

Open new window after first

How can this be done
Login window appears first and if every thing is fine just close login window and open second Main window.
in win forms we modify program.cs but in wpf there is no program.cs.
Any solutions.?
Actully i did most of the work in the window that is created By default and now want to make it secondary(mean it should appear and then close when wanted giving control to new window)
<Application x:Class="DevnMark_V1._0.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="Application_Startup">
<Application.Resources>
</Application.Resources>
</Application>
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
var login = new MainWindow();
login.ShowDialog();
if (myAppSett.Default.validated == true)
{
var mainWindow = new DevNMarkMainWindow();
mainWindow.ShowDialog();
}
}
Login Window start XML
<Window x:Class="DevnMark_V1._0.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
xmlns:local="clr-namespace:Progress"
Title="MainWindow" Height="292" Width="563" WindowStyle="None" BorderBrush="#FF0A6277" AllowsTransparency="True" WindowStartupLocation="CenterScreen" Topmost="True">
Exception occurs when i close Login window and occurs at point InitializeComponent();of second window when it is viewed when it is going to be initilized
I solved this problem in this way:
I removed from App.xaml the StartupUri="MainWinodw.xaml", leaving only Startup="Application_Startup".
In Application_Startup, I IMMEDIATELY referenced both login and main windows:
loginwindow Login = new loginwindow();
mainwindow Main = new mainwindow();
I verified my Login, then closed the login window and opened the main window with a simple .Show():
Login.ShowDialog();
if (!Login.DialogResult.HasValue || !Login.DialogResult.Value)
{
Application.Current.Shutdown();
}
main.Show();
No changes in ShutdownMode.
There may be no program.cs, but there is an App.xaml.cs in the default WPF program template and you can do the same thing there.
What you want to do is remove StartupUri="LoginWindow.xaml" from App.xaml and then modify App.xaml.cs's constructor to invoke your login window and your main window, like this:
public App() : base() {
bool authenticated = false;
LoginWindow login;
while (!authenticated)
{
login = new LoginWindow();
login.ShowDialog();
authenticated = ValidUser(login.username, login.password);
}
MainWindow main = new MainWindow(login.username);
main.ShowDialog();
}
The above example assumes you've added username and password as public properties to LoginWindow, and that you've modified MainWindow's constructor to take a parameter.
The proposed OnExplicitShutdown method works and you can avoid explicitly shutting the app down in the second window simply by opening it with ShowDialog followed by this.Shutdown(), all in App.xaml thus not interfering with the rest of the application.

Resources