Использование WINAPI функции SetParent в приложениях Windows Forms

 

Введение

Как известно, в Windows приложения представляются как набор окон, связываемых родительскими отношениями. В приложениях Windows Forms все немного по-другому: есть формы и элементы управления, и доступ ко всем аспектам управления отношениями окон не возможен. Однако имеется возможность получения дескрипторов окон и вызова соответствующих функций через объекты System.Runtime.InteropServises. В данной статье рассматривается использование функции SetParent для изменения владельца окна в приложении C# Windows Forms.

 

Отображение окна без визуальных стилей

Недавно (2016 г.) на форумах MSDN возник вопрос, как в Windows Forms отобразить окно без визуальных стилей (так, как оно отображается в конструкторе среды разработки). На это было найдено следующее решение. Windows не использует визуальные стили для окон, которые являются дочерними для другого окна (именно поэтому в конструкторе не отображаются стили: окном-владельцем является среда разработки). Соответственно, можно создать невидимое окно и сделать наше окно дочерним для этого окна. Код выглядит так:

 

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Windows.Forms;

using System.Runtime.InteropServices;

 

namespace WindowsForms_test

{

    public partial class Form1 : Form

    {

        public static Form TopLevelForm;

        public static Form MainForm;

 

        [DllImport("user32.dll")]

        public static extern int SetParent(IntPtr hWnd, IntPtr NewParent);

 

        public Form1()

        {

            InitializeComponent();

            TopLevelForm = this;

 

            //создаем невидимое окно

            this.WindowState = FormWindowState.Maximized;//окно на весь экран

            this.TransparencyKey = this.BackColor;//окно все прозрачное

            this.FormBorderStyle = FormBorderStyle.None;//окно не имеет рамки

 

            FormMain f = new FormMain();//создаем настоящее окно

            f.BackColor = SystemColors.ControlLight;//чтобы не совпадало с ключем прозрачности

            IntPtr hwnd = f.Handle;

            SetParent(hwnd, this.Handle);//засовываем настоящее окно в невидимое окно   

           

            f.Show();

            MainForm = f;

        }

       

 

        private void Form1_Activated(object sender, EventArgs e)

        {

            MainForm.WindowState = FormWindowState.Normal;

            MainForm.Activate();

        }

    }

}

Форма Form1 максимизируется и ее ключ прозрачности устанавливается одинаковым с цветом фона. Форма FormMain (реальная главная форма приложения), запихивается в форму Form1. Чтобы сворачивание окна правильно обрабатывалось, в FormMain размещаем следующий обработчик события Resize:

 

private void FormMain_Resize(object sender, EventArgs e)

        {

            if (this.WindowState != FormWindowState.Minimized) return;

            Form1.TopLevelForm.WindowState = FormWindowState.Minimized;  

           

        }

 

Вид окна с включенными стилями в Windows 7 приведен на рисунке 1.

 

Рисунок 1 – Вид окна с включенными стилями

 

Так вид изменяется, если окно является дочерним (рисунок 2). Отличие проявляется в отсутствие прозрачности окна.

 

Рисунок 2 – Окно с отключенными стилями

 

Встраивание внешнего приложения в форму Windows Forms

Оказывается, функцию SetParent можно вызвать и для окна из другого процесса. В большинстве случаев, приложение будет продолжать корректно работать. В документации MSDN ранее было примечание, что делать это не рекомендуется, но впоследствии оно было выпилено.

Конечно, от самого факта встраивания приложения в свое окно толку 0, но некоторые приложения (такие как MS Office) предоставляют программный интерфейс для взаимодействия с ним. Можно встроить в свою форму Excel в качестве суперпродвинутой DataGridView!

 

Создадим проект Windows Forms, и добавим в него библиотеки Office Interop (Interop.Excel.Dll и Interop.Microsoft.Office.Core.Dll). Создадим пользовательский элемент управления, инкапсулирующий окно Excel. Добавим в него определения необходимых функций Windows API.

 

[DllImport("user32.dll")]

public static extern int SetParent(IntPtr hWnd, IntPtr NewParent);

 

[DllImport("user32.dll")]

public static extern uint SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);

 

[DllImport("user32.dll")]

public static extern uint GetWindowLong(IntPtr hWnd, int nIndex);

 

[DllImport("user32.dll")]

public static extern int MoveWindow(IntPtr hWnd, int x, int y, int w, int h, int repaint);

 

Объявим необходимые константы и переменные:

 

        const uint WS_CHILD = 0x40000000;

        const uint WS_POPUP = 0x80000000;

        const uint WS_BORDER = 0x00800000;

        const uint WS_CAPTION = 0x00C00000;  /* WS_BORDER | WS_DLGFRAME  */

        const uint WS_THICKFRAME = 0x00040000;

        const uint WS_SIZEBOX = WS_THICKFRAME;

 

        const int GWL_STYLE = (-16);//смещение стиля в структуре окна

 

        public Excel.Application _Xl=null;//приложение Excel

 

        protected bool _Initialized = false;//указывает, что Excel загружен

 

Добавим метод инициализации Excel:

public void InitializeExcel()//загрузка Excel

        {

            _Xl = new Excel.Application();//запуск приложения

            _Xl.WindowState = Excel.XlWindowState.xlMaximized;

 

            var book = _Xl.Workbooks.Add(Type.Missing);//создание новой пустой книги

            Marshal.ReleaseComObject(book);

 

            _Xl.Visible = true;//окно видимо

            _Xl.DisplayExcel4Menus = false;//выключить меню

            _Xl.DisplayFormulaBar = false;//выключить строку формул

            _Xl.ShowWindowsInTaskbar = false;//не показывать в панели задач

            _Xl.DisplayAlerts = false;//не показывать сообщения

            _Xl.DisplayStatusBar = false;//не показывать строку состояния           

 

            IntPtr wnd = (IntPtr)_Xl.Hwnd;//дескриптор окна Excel

            SetParent(wnd, this.Handle);//изменение владельца окна Excel на этот элемент управления

 

            uint style1 = GetWindowLong(wnd, GWL_STYLE);//получим стиль окна

            uint style2 = style1 & (~WS_CAPTION);//уберем заголовок

            style2 = style2 & (~WS_SIZEBOX);//уберем возможность изменения размера

            SetWindowLong(wnd, GWL_STYLE, (style2));//установка нового стиля

            MoveWindow((IntPtr)(_Xl.Hwnd), 0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height, 1);//установка размеров окна

 

            this._Initialized = true;//Excel загружен!

        }

 

Добавим другие методы, обеспечивающие функционирование элемента:

 

public void Destroy()//освобождение ресурсов

        {

            if (_Xl != null)

            {

                SetParent((IntPtr)_Xl.Hwnd, (IntPtr)0);//убираем владельца окна

                Excel.Workbook wb = _Xl.ActiveWorkbook;

                wb.Close(false, Type.Missing, Type.Missing);//закрытие книги

                Marshal.ReleaseComObject(wb);

                _Xl.Quit();//выход из приложения

                Marshal.ReleaseComObject(_Xl);

                _Xl = null;

            }

            _Initialized = false;

        }

 

        ~AdvancedDataGrid()

        {

            this.Destroy();

        }

 

        private void AdvancedDataGrid_Resize(object sender, EventArgs e)//подгонка размеров окна при изменении размера элемента

        {

            if (!_Initialized) return;

            if (_Xl == null) return;

            MoveWindow((IntPtr)(_Xl.Hwnd), 0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height, 1);           

        }

 

        public void SetCellContent(int sheet, int row, int col, object val)//установка значения ячейки

        {

            if (!_Initialized) return;

            if (sheet < 0) return;

            if (row < 0) return;

            if (col < 0) return;

           

            Excel.Workbook wb = _Xl.ActiveWorkbook;

 

            Excel.Worksheet sh = (Excel.Worksheet)wb.Sheets[sheet];//получение листа

            //Excel.Worksheet sh =  wb.ActiveSheet;           

            Marshal.ReleaseComObject(wb);

 

            //sh.Protect(Contents: false);

          

            sh.Cells[row, col] = val;//установка значения

 

            //sh.Protect(Contents: true);

                       

            Marshal.ReleaseComObject(sh);

           

        }

 

        void IDisposable.Dispose()

        {

            this.Destroy();

        }

 

Использование данного элемента производится так:

    public partial class Form1 : Form

    {

       

        public Form1()

        {

            InitializeComponent();

            advancedDataGrid1.InitializeExcel();//загрузка Excel

            advancedDataGrid1.SetCellContent(1, 1, 2, "s");//установка значения ячейки

 

        }

 

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)

        {

            advancedDataGrid1.Destroy();//освобождение ресурса           

        }

 

        private void Form1_Resize(object sender, EventArgs e)

        {

           

        }

    }

 

Результат представлен на рисунке 3.

 

Рисунок 3 – Встраивание Excel в приложение

 

Обратите внимание, что для корректного освобождения ресурсов при закрытии окна должен быть вызван метод Destroy, который не только уничтожает используемые COM объекты (Marshal.ReleaseComObject), но и отменяет родительские отношения между окнами вызовом SetParent с нулевым дескриптором. Если этого не сделать, Excel вылетит с ошибкой при закрытии приложения.

 

Полный проект (Visual Studio 2010) можно загрузить по ссылке: https://yadi.sk/d/iR2Y7Mg73PCgZh

 

Заключение

Таким образом, с помощью функции SetParent можно влиять на внешний вид окон Windows Forms, и даже встроить другое приложение в форму.

Поскольку Excel установлен на подавляющем большинстве рабочих компьютеров, и предоставляет богатые возможности про обработке и визуализации данных, данную возможность можно и нужно использовать при решении различных задач на практике.

 

Автор: Свиткин В.Г.


Комментариев нет:

Отправить комментарий