Довольно часто в разработке приложений для работы с документами возникает необходимость отобразить эти самые документы не переключаясь из приложения. В подавляющем большинстве этими документами являются текстовые документы MS Word и электронные таблицы MS Excel. К сожалению мне не удалось найти каких-либо достойных контролов для их отображения в свободном доступе, так что было решено встроить сам офис в приложение, благо он установлен практически на каждом компьютере. В этой и следующей статье посмотрим как это можно сделать.
Начнём с создания юзерконтрола, отображающего документы Word.
Задача следующая: открывать документ внутри нашего приложения только для чтения без возможности отредактировать или пересохранить документ, меню и панели ворда спрятать.
Структура контрола будет такая: WPF-UserControl => WindowsFormsHost => WindowsForms-UserControl => Panel => Word app
Для работы нам понадобится подключить две библиотеки:
Microsoft.Office.Interop.Word Office
WindowsForms UserControl
Для работы понадобится несколько WinApi функций:
#region Изменить родителя окна
[DllImport("user32.dll")]
private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
#endregion
#region Перемещение окна
[DllImport("user32.dll", SetLastError = true)]
public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
#endregion
#region Развернуть окно на весь экран
private const int SW_SHOWMAXIMIZED = 3;
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
#endregion
#region Спрятать заголовок окна
const int WS_BORDER = 8388608;
const int WS_DLGFRAME = 4194304;
const int WS_CAPTION = WS_BORDER | WS_DLGFRAME;
const int WS_SYSMENU = 524288;
const int WS_THICKFRAME = 262144;
const int WS_MINIMIZE = 536870912;
const int WS_MAXIMIZEBOX = 65536;
const int GWL_STYLE = -16;
const int GWL_EXSTYLE = -20;
const int WS_EX_DLGMODALFRAME = 0x00000001;
const int SWP_NOMOVE = 0x2;
const int SWP_NOSIZE = 0x1;
const int SWP_FRAMECHANGED = 0x20;
const uint MF_BYPOSITION = 0x400;
const uint MF_REMOVE = 0x1000;
[DllImport("user32.dll")]
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);
public void MakeExternalWindowBorderless(IntPtr MainWindowHandle)
{
int Style = 0;
Style = GetWindowLong(MainWindowHandle, GWL_STYLE);
Style = Style & Not(WS_CAPTION);
Style = Style & Not(WS_SYSMENU);
Style = Style & Not(WS_THICKFRAME);
Style = Style & Not(WS_MINIMIZE);
Style = Style & Not(WS_MAXIMIZEBOX);
SetWindowLong(MainWindowHandle, GWL_STYLE, Style);
Style = GetWindowLong(MainWindowHandle, GWL_EXSTYLE);
SetWindowLong(MainWindowHandle, GWL_EXSTYLE, Style | WS_EX_DLGMODALFRAME);
//SetWindowPos(MainWindowHandle, new IntPtr(0), 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
}
#endregion
#region Спрятать меню окна
[DllImport("user32.dll")]
private static extern IntPtr GetMenu(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int GetMenuItemCount(IntPtr hMenu);
[DllImport("user32.dll")]
private static extern bool DrawMenuBar(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool RemoveMenu(IntPtr hMenu, uint uPosition, uint uFlags);
public void WindowsReStyle(IntPtr MainWindowHandle)
{
int style = GetWindowLong(MainWindowHandle, GWL_STYLE);
IntPtr HMENU = GetMenu(MainWindowHandle);
int count = GetMenuItemCount(HMENU);
if (count > 0)
{
for (int i = count; i >= 0; i += -1)
{
uint menuToRemove = Convert.ToUInt32(i);
RemoveMenu(HMENU, menuToRemove, (MF_BYPOSITION | MF_REMOVE));
}
DrawMenuBar(MainWindowHandle);
}
}
#endregion
Переменные контрола
private Process process;
private Word.Application word;
private Word.Document document;
private List<string> hiddenBars;
private int originalWordWindowWidth;
private int originalWordWindowHeight;
private int originalWordWindowLeft;
private int originalWordWindowTop;
Методы открытия/закрытия Word
/// <summary>
/// Запустить Word
/// </summary>
private void StartWord()
{
this.word = new Word.Application();
this.word.Application.Caption = "WORD VIEWER";
this.originalWordWindowWidth = this.word.Width;
this.originalWordWindowHeight = this.word.Height;
this.originalWordWindowLeft = this.word.Left;
this.originalWordWindowTop = this.word.Top;
this.word.Width = 0;
this.word.Height = 0;
this.word.Left = -100;
this.word.Top = -100;
this.word.Visible = true;
this.word.CommandBars.ActiveMenuBar.Enabled = false;
foreach (Process proc in Process.GetProcessesByName("winword"))
if (proc.MainWindowTitle == "WORD VIEWER")
this.process = proc;
SetParent(this.process.MainWindowHandle, this.wordContainer.Handle);
ShowWindow(this.process.MainWindowHandle, SW_SHOWMAXIMIZED);
MakeExternalWindowBorderless(this.process.MainWindowHandle);
WindowsReStyle(this.process.MainWindowHandle);
MoveWindow(this.process.MainWindowHandle, -3, -3, this.wordContainer.Width + 6, this.wordContainer.Height + 6, true);
}
/// <summary>
/// Закрыть Word
/// </summary>
private void CloseWord()
{
if ((this.word != null))
{
if (this.word.Documents.Count > 0)
this.document.Close(SaveChanges: false);
this.word.CommandBars.ActiveMenuBar.Enabled = true;
foreach (string cb in this.hiddenBars)
this.word.CommandBars[cb].Visible = true;
this.word.Width = this.originalWordWindowWidth;
this.word.Height = this.originalWordWindowHeight;
this.word.Left = this.originalWordWindowLeft;
this.word.Top = this.originalWordWindowTop;
this.word.Quit(SaveChanges: false);
GC.Collect();
GC.WaitForPendingFinalizers();
Marshal.ReleaseComObject(this.document);
Marshal.ReleaseComObject(this.word);
}
}
Методы открытия/закрытия документа
/// <summary>
/// Открыть документ
/// </summary>
/// <param name="path">Путь к документу</param>
public void OpenWordDocument(string path)
{
if (this.word == null)
this.StartWord();
foreach (Microsoft.Office.Core.CommandBar cb in this.word.CommandBars)
{
if (cb.Visible)
{
try
{
if (!this.hiddenBars.Contains(cb.Name))
this.hiddenBars.Add(cb.Name);
cb.Visible = false;
}
catch { }
}
}
if (this.word.Documents.Count > 0)
this.document.Close(SaveChanges: false);
this.document = this.word.Documents.Open(FileName: path, ReadOnly: true);
}
/// <summary>
/// Закрывает текущую таблицу
/// </summary>
public void CloseWordDocument()
{
if (this.word != null && this.word.Documents.Count > 0)
this.document.Close(SaveChanges: false);
}
Подцепимся к нескольким событиям для изменения размера word'а / корректного закрытия приложения
/// <summary>
/// При изменении размеров родительского контейнера
/// </summary>
void OnContainerResize(object sender, EventArgs e)
{
Panel container = sender as Panel;
if (container == null)
return;
if ((this.process != null))
{
MakeExternalWindowBorderless(this.process.MainWindowHandle);
WindowsReStyle(this.process.MainWindowHandle);
MoveWindow(this.process.MainWindowHandle, -3, -3, container.Width + 6, container.Height + 6, true);
}
}
/// <summary>
/// При закрытии приложения
/// </summary>
void OnMainWindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
this.CloseWord();
}
/// <summary>
/// При краше приложения
/// </summary>
void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
this.CloseWord();
}
WPF UserControl
XAML контрола будет содержать лишь одну строку:
<WindowsFormsHost x:Name="wordHost" />
При загрузке WPF-контрола добавляем в WindowFormsHost WindowsForms-контрол
private WordWindowsForms control;
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
this.control = new WordWindowsForms();
this.wordHost.Child = this.control;
}
Dependency Property чтоб можно было биндить путь к файлу
/// <summary>
/// Путь к файлу
/// </summary>
public string File
{
get { return (string)this.GetValue(FileProperty); }
set { this.SetValue(FileProperty, value); }
}
public static readonly DependencyProperty FileProperty = DependencyProperty.Register("File", typeof(string), typeof(Word), new PropertyMetadata(null, OnFilePropertyChanged));
public static void OnFilePropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Word ech = sender as Word;
if (ech == null || ech.control == null)
return;
if (string.IsNullOrWhiteSpace((string)e.NewValue))
ech.control.CloseWordDocument();
else
ech.control.OpenWordDocument((string)e.NewValue);
}