尝试运行第二个实例时激活隐藏的wpf应用程序

时间:2014-01-16 06:24:22

标签: c# wpf

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

    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.我认为这就是为什么我的应用程序在处于隐藏状态时不会打开,但不知道如何修复它。

告诉我是否需要发布更多代码或澄清任何内容。提前谢谢。

3 个答案:

答案 0 :(得分:8)

  

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

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

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

<强>更新

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

更新2

当使用Hide()方法隐藏WPF的窗口时,它会在内部调用UpdateVisibilityProperty(Visibility.Hidden),然后将UIElement的内部可见性标志设置为false。当我们调用Show()的{​​{1}}方法时,会调用WPF Window,并切换UpdateVisibilityProperty(Visibility.Visible)的内部可见性标记。

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

通过查看UpdateVisibilityProperty()内部实现,在不调用WPF WindowShow()方法的情况下切换内部visiblity标志的唯一方法是发送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;
}

答案 1 :(得分:-1)

感谢 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(handle);

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

答案 2 :(得分:-1)

从我早期的帖子开始,并引用 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();
        }
    }

}