【问题标题】:Activate a hidden wpf application when trying to run a second instance尝试运行第二个实例时激活隐藏的 wpf 应用程序
【发布时间】:2014-01-16 06:24:22
【问题描述】:

我正在开发一个 wpf 应用程序,而不是在用户关闭按钮时退出应用程序,而是将其最小化到托盘(类似于 google talk)。

    void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        e.Cancel = true;

        this.Hide();
    }

我需要的是,如果用户忘记了应用程序的一个实例并尝试打开一个新实例,我必须关闭第二个实例并将我的应用程序设置为前台应用程序。如果应用程序处于最小化状态(未隐藏),我可以做到这一点。我正在使用以下代码

      protected override void OnStartup(StartupEventArgs e)
           {

            Process currentProcess = Process.GetCurrentProcess();


            var runningProcess = (from process in Process.GetProcesses()
                              where
                              process.Id != currentProcess.Id &&
                              process.ProcessName.Equals(
                              currentProcess.ProcessName,
                              StringComparison.Ordinal)
                              select process).FirstOrDefault();
            if (runningProcess != null)
                {
                    Application.Current.Shutdown();

                    ShowWindow(runningProcess.MainWindowHandle, 5);

                    ShowWindow(runningProcess.MainWindowHandle, 3);
                }

           }

      [DllImport("user32.dll")]
      private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);

当应用程序被最小化时,它对 MainWindowHandle 有一些独特的价值。当我隐藏应用程序时,runningProcess 的 MainWindowHandle 显示为 0。我认为这就是为什么我的应用程序在处于隐藏状态时无法打开,但不知道如何修复它的原因。

如果我需要发布更多代码或澄清任何事情,请告诉我。提前谢谢你。

【问题讨论】:

  • 与其操纵其他应用程序,不如使用IPC在它们之间提供清晰的通信。使用名为Mutex 标记应用程序已启动。当第二个应用程序启动时,它可以确定,然后使用 IPC 说 请激活 并退出。然后,第一个应用程序将完全控制如何激活。

标签: c# wpf


【解决方案1】:

当我隐藏应用程序时,runningProcess 的 MainWindowHandle 显示为 0

你是对的。如果进程没有与之关联的图形界面(隐藏/最小化),则 MainWindowHandle 值为零。

作为解决方法,您可以尝试通过使用 EnumDesktopWindows 函数枚举所有打开的窗口并将其进程 ID 与隐藏/最小化窗口的进程 ID 进行比较来获取隐藏窗口的 HANDLE

更新

WPF 的 WIN32 窗口与标准 WIN32 窗口的行为略有不同。它的类名由单词 HwndWrapper、创建它的 AppDomain 的名称和唯一的随机 Guid(每次启动时更改)组成,例如 HwndWrapper[WpfApp.exe;;4d426cdc -31cf-4e4c-88c7-ede846ab6d44]

更新 2

当使用 Hide() 方法隐藏 WPF 的窗口时,它会在内部调用 UpdateVisibilityProperty(Visibility.Hidden),这反过来将 UIElement 的内部可见性标志设置为 false。当我们调用WPF WindowShow()方法时,UpdateVisibilityProperty(Visibility.Visible)被调用,UIElement的内部可见性标志被切换。

当我们使用ShowWindow() 显示 WPF 窗口时,UpdateVisibilityProperty() 方法不会被触发,因此内部可见性标志不会被反转(这会导致窗口以黑色背景显示)。

通过查看WPF Window 内部实现,在不调用Show()Hide() 方法的情况下切换内部可见性标志的唯一方法是发送WM_SHOWWINDOW 消息。

const int GWL_EXSTYLE = (-20);
const uint WS_EX_APPWINDOW = 0x40000;

const uint WM_SHOWWINDOW = 0x0018;
const int SW_PARENTOPENING = 3;

[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

[DllImport("user32.dll")]
private static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumWindowsProc ewp, int lParam);

[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);

[DllImport("user32.dll")]
private static extern uint GetWindowTextLength(IntPtr hWnd);

[DllImport("user32.dll")]
private static extern uint GetWindowText(IntPtr hWnd, StringBuilder lpString, uint nMaxCount);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern bool GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);

delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);

static bool IsApplicationWindow(IntPtr hWnd) {
  return (GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_APPWINDOW) != 0;
}

static IntPtr GetWindowHandle(int pid, string title) {
  var result = IntPtr.Zero;

  EnumWindowsProc enumerateHandle = delegate(IntPtr hWnd, int lParam)
  {
    int id;
    GetWindowThreadProcessId(hWnd, out id);        

    if (pid == id) {
      var clsName = new StringBuilder(256);
      var hasClass = GetClassName(hWnd, clsName, 256);
      if (hasClass) {

        var maxLength = (int)GetWindowTextLength(hWnd);
        var builder = new StringBuilder(maxLength + 1);
        GetWindowText(hWnd, builder, (uint)builder.Capacity);

        var text = builder.ToString(); 
        var className = clsName.ToString();

        // There could be multiple handle associated with our pid, 
        // so we return the first handle that satisfy:
        // 1) the handle title/ caption matches our window title,
        // 2) the window class name starts with HwndWrapper (WPF specific)
        // 3) the window has WS_EX_APPWINDOW style

        if (title == text && className.StartsWith("HwndWrapper") && IsApplicationWindow(hWnd))
        {
          result = hWnd;
          return false;
        }
      }
    }
    return true;
  };

  EnumDesktopWindows(IntPtr.Zero, enumerateHandle, 0);

  return result;
}

使用示例

...
if (runningProcess.MainWindowHandle == IntPtr.Zero) {
  var handle = GetWindowHandle(runningProcess.Id, runningProcess.MainWindowTitle);
  if (handle != IntPtr.Zero) {
    // show window
    ShowWindow(handle, 5);
    // send WM_SHOWWINDOW message to toggle the visibility flag
    SendMessage(handle, WM_SHOWWINDOW, IntPtr.Zero, new IntPtr(SW_PARENTOPENING));
  }
}
...

【讨论】:

  • 此方法返回的句柄与First实例的句柄不同。但是,我有一种方法可以从我的第二个实例的代码中了解我的第一个实例的主窗口句柄。我可以用它做点什么吗?现在,当我尝试使用 ShowWindow() 使用此句柄打开时,但窗口只是一个黑屏(视图和视图模型未激活)。
  • @Raj123 我很抱歉。我使用控制台应用程序针对简单的 WinForms 应用程序测试了前面的代码示例。原来 WPF 窗口有点不同。我已更新代码示例以使用 WPF 窗口。不过,我只使用简单的 WPF 应用程序测试了代码。让我知道它是否适合您...
  • 在我的情况下,指针返回为 0。无论如何,正如我所说,我找到了一种方法来获取第一个实例的主窗口句柄。我们不能用这个句柄来激活应用程序吗?我的意思是当我试图这样做时,窗口正在打开。但是窗口只是一个黑屏。
  • 使用SendMessage() 的黑窗修复对我不起作用(不知道为什么)。但是,我能够通过侦听窗口的Activated 事件(或覆盖OnActivated)来修复它(当窗口以ShowWindow 显示时调用),如果不是,则将其设置为可见在那里。
  • 在我的测试中,runningProcess.MainWindowTitle 在主窗口隐藏时总是返回empty。我必须手动将主窗口的标题传递给 GetWindowHandle 方法,它工作得很好。
【解决方案2】:

感谢 IronGeek,这太棒了。我只是在学习 c# 并且挣扎了一段时间试图让它工作。我也不能在这里“添加评论”,因为这里的声誉不足,所以这篇文章。我正在使用 WPF .Net 5.0。 我四处搜寻以实现此功能,因此对于其他新手,他们还需要在程序中使用类似以下内容来接收消息(抱歉,不确定我是从哪个页面复制的,其中很多(个人需要自己制作) Mainwindow_Loaded 事件处理程序)。

    private void Mainwindow_Loaded_Event(object sender, RoutedEventArgs e)
    {
        hwndSource = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
        hwndSource.AddHook(new HwndSourceHook(WndProc));
    }

    private  IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == WM_SHOWWINDOW)
        {
            MessageBox.Show("I recieved WM_SHOWWINDOW");
            handled = true;
        }
        return IntPtr.Zero;
    }

在我的情况下也需要您提到的“放在前面”提示,这是需要的: (来自Bring Word to Front) 把它放在声明部分:

[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);

并将以下内容放在“SendMessage(handle, WM_SHOWWINDOW, IntPtr.Zero, new IntPtr(SW_PARENTOPENING));'之后声明:

SetForegroundWindow(句柄);

没有这个,激活的窗口只是隐藏在其他窗口后面,必须通过在任务栏中手动查找来找到它。 所以现在我终于得到了一个非隐藏窗口,但现在需要看看隐藏窗口需要什么,因为这是我的真正目标。

【讨论】:

    【解决方案3】:

    从我早期的帖子开始,并引用 IronGeek 的早期评论,问题是“如果进程没有与之关联的图形界面(隐藏/最小化),则 MainWindowHandle 值为零”。因此,任何试图传递隐藏窗口句柄的尝试都是注定的,因为它不存在。

    所以我找到了一种解决方法,尽管它需要目标进程定期检查是否存在新消息。因此这仍然不理想,但它在 2021 年对我有用(WPF,.Net 5.0)并且不需要导入 user32.dll。相反,它使用 MainWindowTitle 作为容器来执行临时类型的进程间通信 (IPC),以被动地发送消息。 MainWindowTitle 可以在运行时设置,并且可以从其他进程中查看,因此它可以像进程间变量一样使用。这是我下面的整个解决方案,注意它需要发布到本地文件夹才能看到它是如何运行的,因为关键是运行多个实例。

    <Window x:Class="TitleComsTest.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:TitleComsTest"
        mc:Ignorable="d"
        Name="TitleComsTest" Title="TitleComsTest" Height="400" Width="600" 
        WindowStartupLocation = "CenterScreen" Closing="TitleComsTest_Closing" Visibility="Hidden">
    <Grid>
        <TextBox Name ="TextBox1" HorizontalAlignment="Center" VerticalAlignment="Center" Height="230" Width="460"/>
        <Button Name="QuitButton" Content=" Really Quit " HorizontalAlignment="Center" Margin="0,0,0,30" VerticalAlignment="Bottom" Click="QuitButton_Click"/>
    </Grid>
    

    背后的代码:

    using System;
    using System.Diagnostics;
    using System.Windows;
    using System.Windows.Threading;
    
    namespace TitleComsTest
    {
        public partial class MainWindow : Window
        {
            //string for MainWindowTitle when first instance and visible:
            const string my_Visible_exe_Name = "TitleComstest";
            //string for MainWindowTitle when first instance and Hidden:
            const string my_Hidden_exe_Name = "TitleComstest...";
            //string for MainWindowTitle when 2nd instance :
            const string my_exe_Name_Flag = "TitleComstest, Please-Wait";   
    
            bool reallyCloseThisProgram = false;
            private DispatcherTimer timer1, timer2;
            
            public MainWindow()
            {
                InitializeComponent();
    
                //Get an array of processes with the chosen name
                Process[] TitleComstests = Process.GetProcessesByName(my_Visible_exe_Name);
                if (TitleComstests.Length > 1)
                {   //Then this is not the first instance
                    for (int i = 0; i < TitleComstests.Length; i++)
                    {
                        if (TitleComstests[i].MainWindowTitle == my_Visible_exe_Name)
                        {   //The first instance is visible as the MainWindowTitle has been set to the visible name
                            Close(); //Quit - nothing to do but close the new instance
                        }
                    }
                    //The first instance is hidden, so set MainWindowTitle so the first instance can see it and react
                    this.Title = my_exe_Name_Flag;
                    this.WindowState = WindowState.Minimized; //Minimize the window to avoid having two windows shown at once
                    this.Visibility = Visibility.Visible;     //The second instance needs to be visible (minimized is enough) to be seen
                    StartTimerQuit(4000); //arbitrary time, needs to be longer than 2000ms which is the checking period - see StartTimerLook(2000);
                }
                else
                {
                    TextBox1.Text = "This is Multi-instance demo using the 'MainWindowTitle' to send messages\r\nto the first (hidden) instance to wake it up.";
                    TextBox1.Text += "\r\n\r\nThis demo requires the program be published to a local folder and \r\nnot run in the debugger.";
                    TextBox1.Text += "\r\n\r\nYou can type here to mark this instance: _____________ \r\n\r\nand then hide me by clicking top right close window 'X'";
                    TextBox1.Text += "\r\n\r\nOnce closed then start the program again to see the 1st instance pop up.";
                    TextBox1.Text += "\r\n\r\nFinally use the 'Really Quit' button to end this demo.";
                    this.Visibility = Visibility.Visible;
                }
            }
    
            private void StartTimerQuit(Int32 interval) //Timer to Quit setup and start
            {
                timer1 = new DispatcherTimer();   timer1.Tick += timerQuit_Tick;
                timer1.Interval = new TimeSpan(0, 0, 0, 0, interval);   timer1.Start();
            }
            private void timerQuit_Tick(object sender, EventArgs e)
            {
                reallyCloseThisProgram = true;   Close(); 
            }
    
            private void TitleComsTest_Closing(object sender, System.ComponentModel.CancelEventArgs e)
            {
                if (!reallyCloseThisProgram)
                {
                    e.Cancel = true;
                    this.Title = my_Hidden_exe_Name; //Set the Title text to flag a hidden state
                    this.Visibility = Visibility.Hidden;
                    //Start checking every 2 secs at the process names - could be faster but this is a constant background process
                    StartTimerLook(2000);
                }
            }
    
            private void StartTimerLook(Int32 interval) //Timer to look for new instances setup and start
            {
                timer2 = new DispatcherTimer();  timer2.Tick += timerLook_Tick;
                timer2.Interval = new TimeSpan(0, 0, 0, 0, interval);   timer2.Start();
            }
    
            private void timerLook_Tick(object sender, EventArgs e)
            {   //Every timer interval check to see if a process is present with the Ttile name flag 
                Process[] myNameFlagProcesses = Process.GetProcessesByName(my_Visible_exe_Name);
    
                for (int i = 0; i < myNameFlagProcesses.Length; i++)
                {
                    if (myNameFlagProcesses[i].MainWindowTitle == my_exe_Name_Flag) //If name flag is seen ...
                    {   //... then wake up
                        TextBox1.Text += "\r\n Saw the other window";
                        this.Visibility = Visibility.Visible;
                        this.Title = my_Visible_exe_Name; //Set the Title text to flag a visible state
                        this.Show();
                        this.Activate();
                        timer2.Stop();
                    }
                }
    
            }
    
            private void QuitButton_Click(object sender, RoutedEventArgs e)
            {
                reallyCloseThisProgram = true;   Close();
            }
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-07
      • 1970-01-01
      • 2013-02-04
      • 1970-01-01
      相关资源
      最近更新 更多