Введение
Как известно, в 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 установлен на подавляющем большинстве рабочих компьютеров, и предоставляет богатые возможности про обработке и визуализации данных, данную возможность можно и нужно использовать при решении различных задач на практике.
Автор: Свиткин В.Г.
Комментариев нет:
Отправить комментарий