wpf 无法从任务栏操作无模式对话框(无SC_MINIMIZE消息)

8fsztsew  于 2023-06-24  发布在  其他
关注(0)|答案(1)|浏览(116)

如果我通过$WPFdialog.Show()从Powershell脚本中打开一个非模态对话框,窗口会出现,但不会对单击任务栏图标做出React。我希望它能够最小化并从那里恢复。如果我通过ShowDialog()打开它,就没有问题。
我不想使用模态对话框,因为我需要GUI线程来检查后台任务活动。我的方法是在后台活动处理旁边调用DoEvents()。这在调整窗口大小或关闭窗口时工作正常,但对话框不会对任务栏上的单击做出React。事实上,它似乎根本没有从任务栏接收WM_SYSCOMMAND SC_MINIMIZE消息,所以我不知道在哪里添加适当的事件处理程序。
感谢帮助。Windows 7 Powershell 5.1

示例脚本:

param( $ARG1, $ARG2, $ARG3, $ARG4 )
Add-Type -AssemblyName System.Windows.Forms
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')

################### XAML-Code ###################
$inputXML = @"
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="Test App" Height="200" Width="300" ResizeMode="CanResize" WindowStartupLocation="CenterScreen">
<Grid>
<Label HorizontalAlignment="Center" VerticalAlignment="Center">Welcome</Label>
<Button x:Name="Close" Content="Close" HorizontalAlignment="Center" Margin="0,101,0,0" VerticalAlignment="Top"/>
</Grid>
</Window>
"@

############### Parse and use XAML ###############
$inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window'
[xml]$xaml = $inputXML
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$WPFdialog=[Windows.Markup.XamlReader]::Load( $reader )
$xaml.SelectNodes("//*[@Name]") | %{Set-Variable -Name "WPF$($_.Name)" -Value $WPFdialog.FindName($_.Name)}

$WPFclose.Add_Click({
    $WPFdialog.Close()
})

#$res = $WPFdialog.ShowDialog()
$res = $WPFdialog.Show()
do
{
    [System.Windows.Forms.Application]::DoEvents()
    # planned: handle some background progress here
    Start-Sleep -Milliseconds 20
    $hwnd = [Windows.PresentationSource]::FromVisual($WPFdialog)
}
while ($hwnd)
6kkfgxo0

6kkfgxo01#

你自己找到了一个务实的解决方案

  • 即使您正在创建一个 * WPF * 窗口,您也在循环中使用 * WinForms * 相关的[System.Windows.Forms.Application]::DoEvents()方法,以便保持窗口响应,因为WPF没有 * 等效的方法。
  • 然而,为了让事件处理完全使用这种方法-特别是通过任务栏重新激活窗口-您必须使用[System.Threading.Thread]::Sleep()而不是Start-Sleep**。
  • 原因不明; GitHub issue #19796讨论意外行为。
# Load both the WinForms and the WPF assemblies.
Add-Type -AssemblyName System.Windows.Forms, PresentationFramework

# ...

# Show the window non-modally and activate it.
$null = $WPFdialog.Show(); $null = $WPFDialog.Activate()

# Process GUI events periodically in a loop, with other operations in between.
while ($WPFdialog.IsVisible) {
    # Process GUI events.
    #  Even though this is a *WinForms* method, it 
    #  also works with *WPF* applications.
    [System.Windows.Forms.Application]::DoEvents()

    # Planned: handle some background progress here
    # Note:
    #  Any operations here must finish quickly,
    #  otherwise GUI event processing will be blocked.

    # Sleep a little to so as not to create a tight loop.
    # IMPORTANT: For handling of *all* events, notably
    #            reactivation of a minimized window via the taskbar, use
    #            [System.Threading.Thread]::Sleep() instead 
    #            of Start-Sleep
    [System.Threading.Thread]::Sleep(20)
}

'Done.'

以上所述的代价是不得不加载WinForms程序集-尽管这在实践中可能不是问题。
请继续阅读,以获得仅WPF的解决方案。
一个纯WPF解决方案是可能的,即通过一个WPF模拟WinForms的DoEvents()自定义实现 *,如DispatcherFrame类的帮助主题所示,并在下面实现。
同样,也必须使用[System.Threading.Thread]::Sleep()而不是Start-Sleep

# Only WPF is needed now.
Add-Type -AssemblyName PresentationFramework

# ...

# Define a custom DoEvents()-like function that processes GUI WPF events and can be 
# called in a custom event loop in the foreground thread, as shown below.
# Adapted from: https://learn.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatcherframe
function DoWpfEvents {
  [Windows.Threading.DispatcherFrame] $frame = [Windows.Threading.DispatcherFrame]::new($True)
  $null = [Windows.Threading.Dispatcher]::CurrentDispatcher.BeginInvoke(
    'Background', 
    [Windows.Threading.DispatcherOperationCallback] {
      param([object] $f)
      ($f -as [Windows.Threading.DispatcherFrame]).Continue = $false
      return $null
    }, 
    $frame)
  [Windows.Threading.Dispatcher]::PushFrame($frame)
}

$null = $WPFdialog.Show(); $null = $WPFDialog.Activate()
while ($WPFdialog.IsVisible) {

    # Call the custom WPF DoEvents function
    DoWpfEvents

    # Planned: handle some background progress here

    # IMPORTANT: Use [System.Threading.Thread]::Sleep() instead  of Start-Sleep
    [Threading.Thread]::Sleep(20)
}
    • 注意**:您在Win 7 SP1上报告重绘问题Windows PowerShell *(. NET 4.8.03761);请看下面动画屏幕截图中的再现步骤。

相比之下,我在我的Windows 11 22H2(ARM)虚拟机上看不到这个,无论是在Windows PowerShell www.example.com(. NET Framework 4.8.9166.0)还是在PowerShell Core v7.4.0-preview.3(. NET 8.0.0-preview.3.23174.8)中:v5.1.22621.1778 (.NET Framework 4.8.9166.0) nor in PowerShell Core v7.4.0-preview.3 (.NET 8.0.0-preview.3.23174.8):
DoWpfEvents无法调度WM_PAINT消息,如果窗口是不活动的,并在前台窗口,以便当前台窗口得到关闭(仍然不活动!)对话框需要重绘。DoEvents似乎处理得很好。
(我的附录,Yacc)这就是在Win 7上使用DoWpfEvents调度的方式。DoEvents工作正常。唯一的区别是WM_PAINT(消息代码15)丢失。

相关问题