Замена иконки окна из внешней DLL

161
02 сентября 2021, 16:30

Есть консольное приложение, которое вызывает метод в сторонней DLL. Метод открывает новое окно с дефолтной иконкой Windows.Forms.

[DllImport("external.dll", EntryPoint = "create_driver", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int CreateDriver([Out] out IntPtr nativeDriverHandle);
[DllImport("external.dll", EntryPoint = "show_properties", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int ShowProperties(IntPtr nativeDriverHandle);
[STAThread]
internal static void Main(string[] args)
{
     var driverHandle = IntPtr.Zero;
     CreateDriver(out driverHandle);
     int result = ShowProperties(driverHandle);
}

Пробовал с помощью SetWindowsHookEx (в момент создания окна по сообщению WM_CREATE) и FindWindowEx (по имени окна "Свойства") как-то выцепить handle окна, ничего не получилось.

Потом наткнулся на статью на support.microsoft, где говорится, что хуки глобальные не получится использовать. И на статью на CodeProject, где для этого пишется своя DLL на ++.

Edit: выяснил, что dll создает окно не WinForms, а Qt.

Вопрос: реально ли изменить иконку Qt-окна из ConsoleApp на c#?

Answer 1

1 вариант (плохой, но может кому-то понадобится):

[DllImport("user32.dll")]
private static extern IntPtr FindWindow(string className, string windowTitle);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hwnd, int message, int wParam, IntPtr lParam);
private static const string WINDOW_CLASS_NAME = "needed window class name"; //в моем случае Qt5QWindowIcon
private const int WM_SETICON = 0x80;
private const int WM_GETICON = 0x007F;
private const int ICON_SMALL = 0;
private const int ICON_BIG = 1;
internal static void Main(string[] args)
{
      var driverHandle = IntPtr.Zero;
      CreateDriver(out driverHandle);
      bool endChangeProperty = false;
      var t = new Task(() =>
                       {
                            var icon = new Icon(@"ICON_PATH\Icon.ico");
                            var iconHndl = icon.Handle;
                            while (!endChangeProperty)
                            {
                                 var windowHandle = FindWindow(WINDOW_CLASS_NAME, null);
                                 if (windowHandle != IntPtr.Zero)
                                 {
                                      var windowIcon = SendMessage(windowHandle, WM_GETICON, ICON_SMALL, IntPtr.Zero);
                                      if (!iconHndl.Equals(windowIcon))
                                      {
                                           SendMessage(windowHandle, WM_SETICON, ICON_BIG, iconHndl);
                                           SendMessage(windowHandle, WM_SETICON, ICON_SMALL, iconHndl);
                                       }
                                  }
                                  Thread.Sleep(100);
                             }
                        });
      t.Start();
      var result = ShowProperties(driverHandle);
      endChangeProperty = true;
      t.Wait();
      t.Dispose();
}

Плохой, потому что:

  • выполняется в Task
  • если есть еще какие-то приложения, которые создают окна искомого класса, то на время выполнения Task иконка будет меняться и в этих приложениях. Как получить адекватно поток создаваемого окна, чтобы сравнить его с текущим своим потоком, я не докопал

2 вариант:

[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hwnd, int message, int wParam, IntPtr lParam);
private static const string WINDOW_CLASS_NAME = "needed window class name"; //в моем случае Qt5QWindowIcon
private const int WM_SETICON = 0x80;
private const int ICON_SMALL = 0;
private const int ICON_BIG = 1;
internal static void Main(string[] args)
{
      var driverHandle = IntPtr.Zero;
      CreateDriver(out driverHandle);
      Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, TreeScope.Subtree, OnQtFormOpened);
      var res = ShowProperties(driverHandle);
      Automation.RemoveAllEventHandlers();
}

private static void OnQtFormOpened(object sender, AutomationEventArgs e)
{
    var processId = Process.GetCurrentProcess().Id;
    if (sender is AutomationElement ae && ae.Current.ClassName == WINDOW_CLASS_NAM && processId == ae.Current.ProcessId)
    {
        var iconHndl = new Icon(@"ICON_PATH\Icon.ico").Handle;
        SendMessage(new IntPtr(ae.Current.NativeWindowHandle), WM_SETICON, ICON_BIG, iconHndl);
        SendMessage(new IntPtr(ae.Current.NativeWindowHandle), WM_SETICON, ICON_SMALL, iconHndl);
    }
}

Примечания:

  • AutomationElement.RootElement - элемент рабочего стола
  • TreeScope.Subtree - чтобы просматривать все дерево элементов. Есть еще TreeScope.Children, но в этом случае изменится иконка только у окна первого уровня. Если в этом окне открыть еще одно окно, то событие не сработает
  • Automation.RemoveAllEventHandlers - удаляет все обработчики с AutomationElement. Есть еще Automation.RemoveEventHandler по конкретному обработчику, но у меня он пытался удалить один обработчик, который добавляю, секунды 3. RemoveAll отрабатывает сразу же
READ ALSO
Nullable- это больше, чем структура?

Nullable- это больше, чем структура?

В общем, интереса ради залез в исходники Nullable<T> и увидел вот такое:

92
Асинхронное заполнение ObservableCollection

Асинхронное заполнение ObservableCollection

Стоит задача : при инициализации выполняется заполнение ObservableCollection данными, где имеется трудоемкий метод

220
Наследование + event broadcasting

Наследование + event broadcasting

Заранее прошу прощения за невнятный заголовок, не придумал как лучше объяснить

87
The application exited with code: 255

The application exited with code: 255

При попытке запуска какой-либо программы через VS выдает такую ошибкуРаньше этого не было, но после обновления ОС на Mac'е началось

115