Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.
Вся двумерная графика WPF реализуется классами, производными от абстрактных
классов Drawing и Shape. Классы, наследующие Drawing, являются более простыми
конструкциями, чем классы, произведенные от Shape, и поэтому требуют меньших
системных ресурсов.
Абстрактный класс Drawing находится в пространстве имен System.Windows.Media.Drawing и содержит общее описание того, что должно быть нарисовано: фигура, точечный
рисунок, строка текста или видео. Производные от него классы описывают конкретные
типы содержимого:
GeometryDrawing - готовит внутри себя данные фигуры для рисованияImageDrawing - готовит внутри себя данные изображения (точечный
рисунок)GlyphRunDrawing - готовит внутри себя текстовые данныеVideoDrawing - готовит внутри себя аудио- или
видеофайлDrawingGroup - послойно накапливает коллекцию данных предыдущих
четырех объектов Drawing как один составной объект рисования для последующего
вывода через класс отображения ( DrawingBrush, DrawingImage
или Visual )Все эти производные классы являются запечатанными ( sealed ) и не могут продолжать
цепочку наследования.

Нужно понимать, что перечисленные объекты содержат только описание того, что
нужно нарисовать. Но чтобы воспроизвести содержимое любого из них, нужно передать
этот объект в конструктор экземпляра класса-контейнера DrawingImage, а сам
контейнер присоединить к свойству Source объекта-представления Image.
Объект GeometryDrawing позволяет создать фигуру с заливкой и контуром путем
совместного использования объектов Geometry, Pen и Brush, адресуемых его одноименными
свойствами-ссылками, где
Geometry описывает структуру самой геометрии фигурыPen описывает контур фигурыBrush описывает
заливку фигурыСвойство-ссылка типа Geometry адресует экземпляр одного из классов,
производных от абстрактного класса Geometry. Таковыми
являются следующие sealed ) классы:

Класс GeometryGroup может накапливать описание геометрических примитивов своих
родственников за счет свойства-коллекции Children типа GeometryCollection,
а затем передавать их как единый составной объект свойству-ссылке Geometry объекта GeometryDrawing. В дополнение к такому описанию геометрии объект GeometryDrawing добавляет свое описание свойств Pen и Brush. Затем можно передать все это содержимое
в коллекцию DrawingGroup как очередной готовый слой для последующего отображения.
Такой механизм определяет векторную (в отличие от точечной) графику, которую
можно произвольно масштабировать без потери качества рисунка на устройстве
отображения с любым разрешением.
Продемонстрируем применение описанного на примере построения графиков (см. по
ссылке http://vitiy.info/?p=5).
Построим два графика: sin() и cos() для одного периода.
Создайте новое решение WpfGraphics с новым проектом WpfApp1 типа WPF Application
Заполните декларативную
часть Window1.xaml проекта следующей разметкой<Window x:Class="WpfApp1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid Margin="20">
<Image Name="image1" />
</Grid>
</Window>
Реализуйте бизнес-логику окна в застраничном файле Window1.xaml.cs с помощью следующего кодаusing System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Globalization;
namespace WpfApp1
{
public partial class Window1 : Window
{
//
// Поля
//
const int countDot = 30;// Количество отрезков
// Список для хранения данных
List<double[]> dataList = new List<double[]>();
// Или можно DoubleCollection data = new DoubleCollection();
// Контейнер слоев рисунков
DrawingGroup drawingGroup = new DrawingGroup();
public Window1()
{
InitializeComponent();
DataFill();// Заполнение списка данными
Execute(); // Заполнение слоев
// Отображение на экране
image1.Source = new DrawingImage(drawingGroup);
}
// Генерация точек графиков
void DataFill()
{
double[] sin = new double[countDot + 1];
double[] cos = new double[countDot + 1];
for (int i = 0; i < sin.Length; i++)
{
double angle = Math.PI * 2 / countDot * i;
sin[i] = Math.Sin(angle);
cos[i] = Math.Cos(angle);
}
dataList.Add(sin);
dataList.Add(cos);
}
// Послойное формирование рисунка в Z-последовательности
void Execute()
{
BackgroundFun(); // Фон
GridFun(); // Мелкая сетка
SinFun(); // Строим синус линией
CosFun(); // Строим косинус точками
MarkerFun(); // Надписи
}
// Фон
private void BackgroundFun()
{
// Создаем объект для описания геометрической фигуры
GeometryDrawing geometryDrawing = new GeometryDrawing();
// Описываем и сохраняем геометрию квадрата
RectangleGeometry rectGeometry = new RectangleGeometry();
rectGeometry.Rect = new Rect(0, 0, 1, 1);
geometryDrawing.Geometry = rectGeometry;
// Настраиваем перо и кисть
geometryDrawing.Pen = new Pen(Brushes.Red, 0.005);// Перо рамки
geometryDrawing.Brush = Brushes.Beige;// Кисть закраски
// Добавляем готовый слой в контейнер отображения
drawingGroup.Children.Add(geometryDrawing);
}
// Горизонтальная сетка
private void GridFun()
{
// Создаем коллекцию для описания геометрических фигур
GeometryGroup geometryGroup = new GeometryGroup();
// Создаем и добавляем в коллекцию десять параллельных линий
for (int i = 1; i < 10; i++)
{
LineGeometry line = new LineGeometry(new Point(1.0, i * 0.1),
new Point(-0.1, i * 0.1));
geometryGroup.Children.Add(line);
}
// Сохраняем описание геометрии
GeometryDrawing geometryDrawing = new GeometryDrawing();
geometryDrawing.Geometry = geometryGroup;
// Настраиваем перо
geometryDrawing.Pen = new Pen(Brushes.Gray, 0.003);
double[] dashes = { 1, 1, 1, 1, 1 };// Образец штриха
geometryDrawing.Pen.DashStyle = new DashStyle(dashes, -.1);
// Настраиваем кисть
geometryDrawing.Brush = Brushes.Beige;
// Добавляем готовый слой в контейнер отображения
drawingGroup.Children.Add(geometryDrawing);
}
// Строим синус линией
private void SinFun()
{
// Строим описание синусоиды
GeometryGroup geometryGroup = new GeometryGroup();
for (int i = 0; i < dataList[0].Length - 1; i++)
{
LineGeometry line = new LineGeometry(
new Point((double)i / (double)countDot,
.5 - (dataList[0][i] / 2.0)),
new Point((double)(i + 1) / (double)countDot,
.5 - (dataList[0][i + 1] / 2.0)));
geometryGroup.Children.Add(line);
}
// Сохраняем описание геометрии
GeometryDrawing geometryDrawing = new GeometryDrawing();
geometryDrawing.Geometry = geometryGroup;
// Настраиваем перо
geometryDrawing.Pen = new Pen(Brushes.Blue, 0.005);
// Добавляем готовый слой в контейнер отображения
drawingGroup.Children.Add(geometryDrawing);
}
// Строим косинус точками
private void CosFun()
{
// Строим описание косинусоиды
GeometryGroup geometryGroup = new GeometryGroup();
for (int i = 0; i < dataList[1].Length; i++)
{
EllipseGeometry ellips = new EllipseGeometry(
new Point((double)i / (double)countDot,
.5 - (dataList[1][i] / 2.0)), 0.01, 0.01);
geometryGroup.Children.Add(ellips);
}
// Сохраняем описание геометрии
GeometryDrawing geometryDrawing = new GeometryDrawing();
geometryDrawing.Geometry = geometryGroup;
// Настраиваем перо
geometryDrawing.Pen = new Pen(Brushes.Green, 0.005);
// Добавляем готовый слой в контейнер отображения
drawingGroup.Children.Add(geometryDrawing);
}
// Надписи
private void MarkerFun()
{
GeometryGroup geometryGroup = new GeometryGroup();
for (int i = 0; i <= 10; i++)
{
FormattedText formattedText = new FormattedText(
String.Format("{0,7:F}", 1 - i * 0.2),
CultureInfo.InvariantCulture,
FlowDirection.LeftToRight,
new Typeface("Verdana"),
0.05,
Brushes.Black);
formattedText.SetFontWeight(FontWeights.Bold);
Geometry geometry = formattedText.BuildGeometry(new Point(-0.2, i * 0.1 - 0.03));
geometryGroup.Children.Add(geometry);
}
GeometryDrawing geometryDrawing = new GeometryDrawing();
geometryDrawing.Geometry = geometryGroup;
geometryDrawing.Brush = Brushes.LightGray;
geometryDrawing.Pen = new Pen(Brushes.Gray, 0.003);
drawingGroup.Children.Add(geometryDrawing);
}
}
}
Разберитесь с кодом
и запустите приложение - результат будет таким
Поизменяйте размер
окна - рисунок автоматически масштабируется с сохранением качества отображенияИногда может потребоваться перевести монитор в полноэкранный режим, чтобы
получить стиль игровых программ или старого доброго DOS. Обычно в таком случае
отключают и курсор, а все управление программой перекладывают на клавиатуру.
Из существующих вариантов переключения в полноэкранный режим наиболее простой,
это настроить окно window так:
window.WindowStyle = WindowStyle.None; window.ResizeMode = ResizeMode.NoResize;
Но такой способ не убирает панель задач и не блокирует появление меню Пуск, которое также включает панель задач.
Есть более сложный способ, приведенный в "http://www.swart.ws/2009/03/kiosk-full-screen-wpf-applications.html", но он имеет те же недостатки.
Для достижения полной иллюзии режима FullScreen следует отключить системные
клавиши, вызывающие панель задач и меню Пуск, и самый эффективный (на мой
взгляд) способ приведен в "http://www.sql.ru/Forum/actualthread.aspx?tid=632552",
которым мы и воспользуемся в данном упражнении.
Из приведенного в статье http://support.microsoft.com/kb/126449/ru сочетания клавиш мы воспользуемся только следующими вариантами:
Этого будет достаточно, чтобы поддерживать полноэкранный режим и одновременно не блокировать остальную функциональность клавиатуры.
Добавьте к решению
командой File/Add/New Project новый проект с именем FullScreen и
назначьте его стартовым
В панели Solution Explorer добавьте к корню проекта FullScreen новую папку Images командой контекстного
меню Add/New Folder
В панели Solution
Explorer командой Add/Existing Item контекстного меню для папки Images скопируйте
в нее файл flower2.jpg из прилагаемого каталога Source
Определите
в интерфейсной части окна следующий код<Window x:Class="FullScreen.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
Background="Green"
Loaded="Window_Loaded"
KeyDown="Window_KeyDown"
KeyUp="Window_KeyDown"
>
<Window.ContextMenu>
<ContextMenu>
<MenuItem Name="changeScreen" Click="MenuItem_Click" />
</ContextMenu>
</Window.ContextMenu>
<DockPanel>
<Menu Name="menu" DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="Open" />
<MenuItem Header="Save" />
<MenuItem Header="SaveAs" />
<MenuItem Header="Exit" />
</MenuItem>
<MenuItem Header="Edit">
<MenuItem Header="Cut" />
<MenuItem Header="Copy" />
<MenuItem Header="Paste" />
</MenuItem>
</Menu>
<Image Source="Images/flower2.jpg" Stretch="Uniform" />
</DockPanel>
</Window>
Для настройки окна и его элементов мы применили синтаксис атрибута,
а для создания каркаса контекстного меню - синтаксис тега
свойства ( свойства
зависимости, присоединенные свойства ). Атрибут Stretch элемента Image может
принимать следующие значения:
None - изображение масштабируется до естественного размера и в области
просмотра справа внизу видно столько, сколько поместилосьFill - изображение масштабируется по всей области просмотра без соблюдения
пропорцийUniform - изображение масштабируется с соблюдением
пропорций, чтобы полностью поместиться в область
просмотраUniformToFill - изображение масштабируется с соблюдением пропорций так,
чтобы в область просмотра полностью вместился хотя бы один размер, а для
другой стороны видно столько, сколько поместилосьТеперь необходимо создать обработчики для событий, выделеных в листинке,
в файле присоединенного кода Window1.xaml.cs. Для этого:
Найдите в разметке
файла Window1.xaml атрибуты событийLoaded="Window_Loaded" KeyDown="Window_KeyDown" KeyUp="Window_KeyDown" Click="MenuItem_Click"
В любом месте каждого
из этих атрибутов щелкните правой кнопкой мыши и выполните команду Navigate to Event Handler.
Оболочка создаст обработчики с указанными именами, а если обработчик уже существует, то перейдет к нему.
Заполните
файл поддержки разметки Window1.xaml.cs следующим кодомusing System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace FullScreen
{
public partial class Window1 : Window
{
// Объявляем как поля для видимости в функциях
bool fullScreen = false;// Состояние экрана
WindowStyle windowStyle;
WindowState windowState;
ResizeMode resizeMode;
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Запоминаем начальные параметры окна
windowState = this.WindowState;
windowStyle = this.WindowStyle;
resizeMode = System.Windows.ResizeMode.CanResizeWithGrip;
this.ResizeMode = resizeMode;
changeScreen.Header = "FullScreen";
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
FullScreen();
}
private void FullScreen()
{
if (!fullScreen)// Переходим в полноэкранный режим
{
if (this.WindowState == WindowState.Maximized)
{
// Чтобы скрыть панель задач и не было мерцания
this.Hide();
this.WindowState = WindowState.Normal;
}
App.Current.MainWindow.WindowStyle = WindowStyle.None;// Без заголовка
App.Current.MainWindow.Topmost = true; // На передний план
App.Current.MainWindow.WindowState = WindowState.Maximized;// Развернуть
App.Current.MainWindow.ResizeMode = ResizeMode.NoResize;// Неизменяемое
menu.Visibility = Visibility.Collapsed;// Скрываем меню
HookSystemKeys.FunHook();// Запрещаем системные клавиши
this.Visibility = Visibility.Visible;
fullScreen = true;
changeScreen.Header = "WindowScreen";
}
else // Восстанавливаем оконный режим
{
this.WindowStyle = windowStyle;
this.Topmost = false;
this.WindowState = windowState;
this.ResizeMode = resizeMode;
menu.Visibility = Visibility.Visible;// Показываем меню
HookSystemKeys.FunUnHook();// Освобождаем системные клавиши
fullScreen = false;
changeScreen.Header = "FullScreen";
}
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
this.Close();
}
}
}
В приведенном коде используется класс HookSystemKeys,
который нам нужно подключить к приложению и который взят из "http://www.sql.ru/Forum/actualthread.aspx?tid=632552" (там
же и описан).
Добавьте к текущему
проекту FullScreen командой Project/Add New Item новый файл с именем HookSystemKeys.cs
Заполните файл HookSystemKeys.cs такusing System;
using System.Diagnostics;
using System.Runtime.InteropServices;
// Для перечисления Keys
using System.Windows.Forms;
namespace FullScreen
{
// Отключение системной клавиши
// http://www.sql.ru/Forum/actualthread.aspx?tid=632552
class HookSystemKeys
{
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYUP = 257;// Отпускание любой клавиши
private static LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
// Поддержка флагов состояния системных клавиш
static bool CtrlKey, AltKey, WinKey;
private static void StateKey(int vkCode, IntPtr wParam)
{
switch ((Keys)vkCode)
{
case Keys.LControlKey:
case Keys.RControlKey:
if (wParam == (IntPtr)WM_KEYUP)
CtrlKey = false;
else
CtrlKey = true;
break;
case Keys.LMenu:
case Keys.RMenu:
if (wParam == (IntPtr)WM_KEYUP)
AltKey = false;
else
AltKey = true;
break;
case Keys.LWin:
case Keys.RWin:
if (wParam == (IntPtr)WM_KEYUP)
WinKey = false;
else
WinKey = true;
break;
}
}
// Общедоступная упаковка оригинала
public static void FunHook()
{
_hookID = SetHook(_proc);
}
public static void FunUnHook()
{
UnhookWindowsHookEx(_hookID);
}
private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}
private delegate IntPtr LowLevelKeyboardProc(int nCode,
IntPtr wParam, IntPtr lParam);
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
int vkCode = Marshal.ReadInt32(lParam);
StateKey(vkCode, wParam);
if (WinKey // Системная
|| CtrlKey (Keys)vkCode == Keys.Escape // Ctrl+Esc
|| AltKey (Keys)vkCode == Keys.Escape // Alt+Esc
|| AltKey (Keys)vkCode == Keys.Tab) // Alt+Tab
{
return (IntPtr)1;
}
else
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}
}
Командой Project/Add Reference добавьте к проекту ссылку на библиотеку System.Windows.Forms.dll, в которой
находится перечисление Keys кодов клавиш
Запустите проект FullScreen и
испытайте построенную нами имитацию полноэкранного режимаОконный и полноэкранный (уменьшенный масштаб) режимы будут выглядеть так


Главное меню мы создали для примера, чтобы показать, что его можно отключать в полноэкранном режиме. Переключение режимов выполняется вызовом контекстного меню по щелчку правой кнопки мыши на окне приложения. В полноэкранном режиме отключаются нежелательные для нас системные клавиши, которые могли бы привести к сбою режима:
В оконном режиме функциональность системных клавиш восстанавливается.
Для полного сходства можно было бы отключить мышь, но тогда все управление окном нужно будет возложить на клавиатуру. В нашем приложении мы для закрытия окна использовали клавишу Esc. Закрыть окно можно и системной комбинацией Alt+F4, которую мы не отключали. Осталась активной комбинация Alt+Space, вызывающая системное меню приложения.
Мы использовали в классе HookSystemKeys перечисление System.Windows.Forms.Keys библиотечной сборки System.Windows.Forms, а в обработчике Window_KeyDown - перечисление System.Windows.Input.Key библиотечной сборки WindowsBase.dll. Это разные перечисления и их нельзя путать.
Попробуйте разобраться
с кодомОбъект ImageDrawing принимает графические данные в виде битовой карты (точечного
рисунка). Для создания изображения необходимо создать экземпляр ImageDrawing и установить значения его свойств ImageDrawing.ImageSource и ImageDrawing.Rect.
Свойство ImageDrawing.ImageSource задает изображение для рисования, а свойство ImageDrawing.Rect задает положение и размер каждого изображения.
Если нужно отобразить один рисунок, то его сразу можно присоединить к объекту
отображения Image, как мы это делали в предыдущем упражнении. Если же требуется
нарисовать несколько рисунков сразу, то необходимо организовать конвейер формирования
рисунков. Вначале создаются отдельные слои с помощью ImageDrawing, затем они
передаются в накопитель DrawingGroup,
где располагаются в Z -последовательности в порядке их добавления. В таком порядке
каждый новый слой будет перекрывать все предыдущие и располагаться ближе к
пользователю. Затем этот слоеный объект передается в рисовальщик DrawingImage,
который и присоединяется, в конечном итоге, к элементу отображения Image.
Объект DrawingGroup как составной объект рисования, может принимать
не только точечные рисунки. В первом упражнении мы использовали его для накопления
векторных рисунков геометрии, порожденных объектом GeometryDrawing.
Он также способен принимать текстовые данные от GlyphRunDrawing, или
медийные данные звука и видео от объекта VideoDrawing. Объект DrawingGroup является единственным типом базового объекта Drawing, который
позволяет определять свою собственную область отсечения. Но об этом чуть позже,
а сейчас приступим к рассмотрению объекта ImageDrawing.
Класс ImageDrawing легко спутать с важным классом DrawingImage, названия которых
так похожи. Но это нас не должно путать, если мы представим изготовление готового
рисунка как последовательность операций на конвейере. Это будет выглядеть примерно
так:
GeometryDrawing |
-> | DrawingGroup |
-> | DrawingImage |
-> | Image |
ImageDrawing |
||||||
GlyphRunDrawing |
||||||
VideoDrawing |
Добавьте к решению
проект типа WPF Application с именем WpfApp3 и
назначьте его стартовым
Добавьте к проекту
командой Project/New Folder папку с именем Images
В панели Solution
Explorer вызовите контекстное меню для папки Images и скопируйте в нее командой Add/Existing Item из прилагаемого каталога Source пять файлов с рисунками (не забудьте изменить
фильтр диалогового окна Add Existing Item на All Files ):market 031.jpgmarket 032.jpgmarket 034.jpgmarket 039.jpgmarket 040.jpg
В панели Solution
Explorer выделите все пять рисунков одновременно и в панели Properties установите
для них свойстваВ этом упражнении мы рассмотрим два способа использования объекта ImageDrawing - с помощью разметки на XAML и с помощью кода C#. Оба способа работают совершенно
одинаково и для того, чтобы это подчеркнуть, мы с помощью них реализуем одну
и ту же задачу. Попутно рассмотрим решение некоторых мелких вопросов, таких,
как предотвращение повторного запуска созданного окна приложения, назначение
главного окна и формирование всплывающей подсказки.
Вначале создадим главное
окно приложения Window1, в котором примененим объект ImageDrawing в кодовой
части Window1.xaml.cs. Затем построим дочернее окно Window2, в котором используем
объект ImageDrawing в дискрипторной части Window2.xaml.
Заполните файл разметки Window1.xaml следующим дескрипторным кодом<Window x:Class="WpfApp3.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Главное окно Window1: Работа объекта ImageDrawing через процедурный код"
Loaded="Window_Loaded"
MouseDoubleClick="Show_Window2"
SizeToContent="WidthAndHeight"
ResizeMode="NoResize">
<Window.ContextMenu>
<ContextMenu>
<MenuItem Header="Создать дочернее окно" Click="Create_Window2" />
</ContextMenu>
</Window.ContextMenu>
</Window>
Эта разметка настраивает окно и создает контекстное меню с одним пунктом.
Вызов дочернего окна, которое будет иллюстрировать разметочный способ применения
объекта ImageDrawing, предусмотрим в обработчиках событий MouseDoubleClick окна
и Click контекстного меню. Такое дублирование одной и той же задачи создания
дочернего окна выбрано потому, чтобы испытать разные способы предотвращения
повторного запуска уже существующего окна.
Событие Loaded срабатывает после загрузки окна в оперативную память, поэтому
в его обработчик удобно поместить код создания объектов содержимого окна. Атрибуты SizeToContent и ResizeMode дескриптора <Window> делают окно подстраиваемым
под содержимое (при первом появлении) и неизменяемое в размерах пользователем,
соответственно. Контекстное меню прикрепляется к объекту окна и будет вызываться
в любой его точке, кроме заголовка.
Щелкните правой кнопкой
мыши на каждом из событий Loaded, MouseDoubleClick, Click и выполните команду Navigate to Event Handler,
чтобы создать в кодовой части обработчики с уже заготовленными именами
В файле Window1.xaml.cs заполните обработчик события Loaded следующим кодомprivate void Window_Loaded(object sender, RoutedEventArgs e)
{
// Размеры всех рисунков одинаковы
const int WIDTH = 348, HEIGHT = 232;
// Создаем накопитель рисунков DrawingGroup
DrawingGroup drawingGroup = new DrawingGroup();
// Левый верхний
ImageDrawing pict1 = new ImageDrawing();
pict1.Rect = new Rect(0, 0, WIDTH, HEIGHT);
pict1.ImageSource = new BitmapImage(
new Uri(@"Images\market 040.jpg", UriKind.Relative));
drawingGroup.Children.Add(pict1);
// Правый верхний
ImageDrawing pict2 = new ImageDrawing();
pict2.Rect = new Rect(350, 0, WIDTH, HEIGHT);
pict2.ImageSource = new BitmapImage(
new Uri(@"Images\market 039.jpg", UriKind.Relative));
drawingGroup.Children.Add(pict2);
// Левый нижний
ImageDrawing pict3 = new ImageDrawing();
pict3.Rect = new Rect(0, 234, WIDTH, HEIGHT);
pict3.ImageSource = new BitmapImage(
new Uri(@"Images\market 034.jpg", UriKind.Relative));
drawingGroup.Children.Add(pict3);
// Правый нижний
ImageDrawing pict4 = new ImageDrawing();
pict4.Rect = new Rect(350, 234, WIDTH, HEIGHT);
pict4.ImageSource = new BitmapImage(
new Uri(@"Images\market 032.jpg", UriKind.Relative));
drawingGroup.Children.Add(pict4);
// Передать рисовальщику
DrawingImage drawingImageSource = new DrawingImage(drawingGroup);
// Заморозить DrawingImage для лучшей производительности
drawingImageSource.Freeze();
// Передать элементу отображения
Image image = new Image();
image.Stretch = Stretch.None;
image.Source = drawingImageSource;
// Контейнер Border для присоединения к содержимому окна
Border border = new Border();
border.Background = Brushes.White;
border.BorderBrush = Brushes.White;
border.BorderThickness = new Thickness(2); // Толщина рамки
border.Margin = new Thickness(10); // Внешний отступ-поле
border.Child = image; // Отдать родителю
this.Background = Brushes.Blue;
this.Content = border;
}
Дополните конструктор
окна Window1 следующим кодомpublic Window1()
{
// Инициализация разметочной части
InitializeComponent();
// Корректировка заголовка окна
this.Title += "=\"Так голодают буржуины!\"";
// Всплывающая подсказка
this.ToolTip = "Вызывайте дочернее окно\n"
+ "двойным щелчком мыши\n"
+ "или контекстным меню...";
}
Здесь применено экранирование двойных кавычек в строковой константе. В определении всплывающей подсказки использован управляющий символ новой строки.
Запустите приложение
- первое окно будет выглядеть так
Обратите внимание, что окно не имеет системных кнопок минимизации и максимизации,
а только кнопку закрытия. Это поведение обеспечивается настройкой в разметке
окна с помощью атрибута ResizeMode="NoResize".
При наведении курсора на окно работает всплывающая подсказка, напоминающая способы создания дочернего окна. Но пока обработчики, предназначенные для этой функциональности, у нас пустые, да и само окно еще нужно заготовить.
Добавьте к
текущему (выделенному в панели Solution Explorer ) проекту WpfApp3 командой Project/Add Window новое окно WPF с
именем Window2.xaml
Наполните файл Window2.xaml следующей
разметкой<Window x:Class="WpfApp3.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Дочернее окно Window2: Работа объекта ImageDrawing через разметку"
SizeToContent="WidthAndHeight"
ResizeMode="NoResize"
Background="Green"
>
<Border BorderBrush="White" BorderThickness="2"
Margin="10"
Background="White"
>
<Image Stretch="None">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<!-- Рис.1 - левый верхний (x, y, width, height) -->
<ImageDrawing Rect="0,0,348,232" ImageSource="Images\market 031.jpg"/>
<!-- Рис.2 - правый верхний (x, y, width, height) -->
<ImageDrawing Rect="350,0,348,232" ImageSource="Images\market 034.jpg"/>
<!-- Рис.3 - левый нижний (x, y, width, height) -->
<ImageDrawing Rect="0,234,348,232" ImageSource="Images\market 039.jpg"/>
<!-- Рис.4 - правый нижний (x, y, width, height) -->
<ImageDrawing Rect="350,234,348,232" ImageSource="Images\market 040.jpg"/>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Border>
</Window>
Дополните конструктор
класса Window2 следующим кодомpublic Window2()
{
// Инициализация разметочной части
InitializeComponent();
// Корректировка заголовка окна
this.Title += "=\"Так голодают буржуины!\"";
// Дочернее окно не отображать в панели задач ОС
this.ShowInTaskbar = false;
}
Заполните обработчики
щелчка и контекстного меню
в классе Window1 следующим кодомWindow wnd2;
private void Show_Window2(object sender, MouseButtonEventArgs e)
{
wnd2 = new Window2();
wnd2.Show();
}
private void Create_Window2(object sender, RoutedEventArgs e)
{
wnd2 = new Window2();
wnd2.Show();
}
Запустите приложение
и вызовите окно Window2 через окно Window1, как напоминает всплывающая подсказка
Получится следующий
результат
Здесь есть несколько существенных недостатков, которые следует устранить:
this.ShowInTaskbar
= false; )Во первых, зачем пользователю много экземпляров одного и того же окна. Во вторых, хоть окна не имеют системных кнопок минимизации, все равно их можно минимизировать все сразу через операционную систему командой "Показать рабочий стол". А в этом случае вновь сделать видимыми вторичные окна не удасться и придется их закрывать через Диспетчер задач (либо через оболочку Visual Studio останавливать процесс - работу приложения).
Для устранения указанных недостатков нужно выполнить следующее:
Для назначения
окна Window1 главным добавьте в его конструктор следующий кодpublic Window1()
{
// Инициализация разметочной части
InitializeComponent();
// Корректировка заголовка окна
this.Title += "=\"Так голодают буржуины!\"";
// Всплывающая подсказка
this.ToolTip = "Вызывайте дочернее окно\n"
+ "двойным щелчком мыши\n"
+ "или контекстным меню...";
// Сделать главным окном приложения
Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
Application.Current.MainWindow = this;
}
Запустите
приложение и убедитесь, что теперь все дочерние окна при закрытии первичного
окна тоже закрываются вместе с приложениемДля блокирования повторного создания дочерних окон применим два способа:
Window1 ссылку wnd2 на дочернее окно в
свойство Wnd2 и будем ее обнулять в обработчике Closed окна Window2. В первом
окне перед созданием дочернего будем эту ссылку проверять - если ненулевая,
значит окно существует и новое создавать нельзяApplication.Current.Windows типа WindowCollection приложения и в первом окне перед созданием дочернего проверять,
отсутствует ли в коллекции существующих окон приложения интересующее нас
окно. Любое созданное окно приложения сразу заносится в эту коллекцию, а
при закрытии между событиями Closing и Closed немедленно удаляется из нее.
Единственное, что потребуется определить в дочернем окне, так это свойство Name, по которому мы будем искать это окно в коллекции приложенияИтак, реализуем сказанное...
В классе Window1 найдите
объявление поля wnd2 (строка: Window wnd2; ), щелкните на поле wnd2 правой
кнопкой мыши и выполните команду контекстного меню Refactor/Encapsulate FieldПоявится окно Encapsulate Field, предлагающее обернуть поле в общедоступное
свойство с именем Wnd2

Скажите OK и оболочка
создаст следующий код свойстваpublic static Window Wnd2
{
get { return Window1.wnd2; }
set { Window1.wnd2 = value; }
}
Аксессор get нам не нужен, поскольку в клиенте Window2 при закрытии окна мы
будем поле wnd2 только обнулять через свойство Wnd2, поэтому get следует удалить.
Внесите в класс Window1 следующий окончательный код реализации первого способа// Предотвращение повторного открытия окна: Способ 1
static Window wnd2;
public static Window Wnd2
{
set { Window1.wnd2 = value; }
}
// Обработчик двойного щелчка
private void Show_Window2(object sender, MouseButtonEventArgs e)
{
if (wnd2 == null)
{
wnd2 = new Window2();
wnd2.Show();
}
else
wnd2.Activate();// Сдвинуть на передний план
}
Модифицируйте класс Window2 следующим образомpublic partial class Window2 : Window
{
public Window2()
{
// Инициализация разметочной части
InitializeComponent();
// Корректировка заголовка окна
this.Title += "=\"Так голодают буржуины!\"";
// Дочернее окно не отображать в панели задач ОС
this.ShowInTaskbar = false;
// Регистрация обработчика
this.Closed += new EventHandler(Window2_Closed);
}
private void Window2_Closed(object sender, EventArgs e)
{
Window1.Wnd2 = null; // Для предотвращения повторного запуска
GC.WaitForFullGCComplete(); // Ждать завершения сборки мусора
GC.Collect(); // Начать сборку мусора
}
}
Здесь мы немного перестарались и ввели еще принудительный вызов сборщика мусора. Это необязательно, так как запуск механизма освобождения брошенных ссылок активизирует сама среда исполнения и в том случае, когда заканчивается выделенная домену приложения оперативная память.
Запустите приложение
и убедитесь, что обработчик создания дочернего окна по двойному щелчку теперь
работает правильно
В разметку файла Window2.xaml добавьте определение свойства Name окна Window2 для его идентификации в коллекции
приложения<Window x:Class="WpfApp3.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Window_2"
Title="Дочернее окно Window2: Работа объекта ImageDrawing через разметку"
SizeToContent="WidthAndHeight"
ResizeMode="NoResize"
Background="Green"
>
...................................
</Window>
Модифицируйте обработчик
контекстного меню в файле Window1.xaml.cs следующим образом// Предотвращение повторного открытия окна: Способ 2
// Обработчик контекстного меню
private void Create_Window2(object sender, RoutedEventArgs e)
{
bool windowExists = false;
foreach (Window window in Application.Current.Windows)
{
if (window.Name == "Window_2")
{
windowExists = true;
window.Activate();// Сдвинуть на передний план
break;
}
}
if (!windowExists)
{
wnd2 = new Window2();
wnd2.Show();
}
}
Запустите приложение
и убедитесь, что и этот обработчик блокирования повторного запуска
теперь тоже работает правильно
Разберитесь с кодомВ будущих упражнениях мы продолжим знакомство с графическими возможностями WPF.

Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.
Вся двумерная графика WPF реализуется классами, производными от абстрактных
классов Drawing и Shape. Классы, наследующие Drawing, являются более простыми
конструкциями, чем классы, произведенные от Shape, и поэтому требуют меньших
системных ресурсов.
Абстрактный класс Drawing находится в пространстве имен System.Windows.Media.Drawing и содержит общее описание того, что должно быть нарисовано: фигура, точечный
рисунок, строка текста или видео. Производные от него классы описывают конкретные
типы содержимого:
GeometryDrawing - готовит внутри себя данные фигуры для рисованияImageDrawing - готовит внутри себя данные изображения (точечный
рисунок)GlyphRunDrawing - готовит внутри себя текстовые данныеVideoDrawing - готовит внутри себя аудио- или
видеофайлDrawingGroup - послойно накапливает коллекцию данных предыдущих
четырех объектов Drawing как один составной объект рисования для последующего
вывода через класс отображения ( DrawingBrush, DrawingImage
или Visual )Все эти производные классы являются запечатанными ( sealed ) и не могут продолжать
цепочку наследования.

Нужно понимать, что перечисленные объекты содержат только описание того, что
нужно нарисовать. Но чтобы воспроизвести содержимое любого из них, нужно передать
этот объект в конструктор экземпляра класса-контейнера DrawingImage, а сам
контейнер присоединить к свойству Source объекта-представления Image.
Объект GeometryDrawing позволяет создать фигуру с заливкой и контуром путем
совместного использования объектов Geometry, Pen и Brush, адресуемых его одноименными
свойствами-ссылками, где
Geometry описывает структуру самой геометрии фигурыPen описывает контур фигурыBrush описывает
заливку фигурыСвойство-ссылка типа Geometry адресует экземпляр одного из классов,
производных от абстрактного класса Geometry. Таковыми
являются следующие sealed ) классы:

Класс GeometryGroup может накапливать описание геометрических примитивов своих
родственников за счет свойства-коллекции Children типа GeometryCollection,
а затем передавать их как единый составной объект свойству-ссылке Geometry объекта GeometryDrawing. В дополнение к такому описанию геометрии объект GeometryDrawing добавляет свое описание свойств Pen и Brush. Затем можно передать все это содержимое
в коллекцию DrawingGroup как очередной готовый слой для последующего отображения.
Такой механизм определяет векторную (в отличие от точечной) графику, которую
можно произвольно масштабировать без потери качества рисунка на устройстве
отображения с любым разрешением.
Продемонстрируем применение описанного на примере построения графиков (см. по
ссылке http://vitiy.info/?p=5).
Построим два графика: sin() и cos() для одного периода.
Создайте новое решение WpfGraphics с новым проектом WpfApp1 типа WPF Application
Заполните декларативную
часть Window1.xaml проекта следующей разметкой<Window x:Class="WpfApp1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid Margin="20">
<Image Name="image1" />
</Grid>
</Window>
Реализуйте бизнес-логику окна в застраничном файле Window1.xaml.cs с помощью следующего кодаusing System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Globalization;
namespace WpfApp1
{
public partial class Window1 : Window
{
//
// Поля
//
const int countDot = 30;// Количество отрезков
// Список для хранения данных
List<double[]> dataList = new List<double[]>();
// Или можно DoubleCollection data = new DoubleCollection();
// Контейнер слоев рисунков
DrawingGroup drawingGroup = new DrawingGroup();
public Window1()
{
InitializeComponent();
DataFill();// Заполнение списка данными
Execute(); // Заполнение слоев
// Отображение на экране
image1.Source = new DrawingImage(drawingGroup);
}
// Генерация точек графиков
void DataFill()
{
double[] sin = new double[countDot + 1];
double[] cos = new double[countDot + 1];
for (int i = 0; i < sin.Length; i++)
{
double angle = Math.PI * 2 / countDot * i;
sin[i] = Math.Sin(angle);
cos[i] = Math.Cos(angle);
}
dataList.Add(sin);
dataList.Add(cos);
}
// Послойное формирование рисунка в Z-последовательности
void Execute()
{
BackgroundFun(); // Фон
GridFun(); // Мелкая сетка
SinFun(); // Строим синус линией
CosFun(); // Строим косинус точками
MarkerFun(); // Надписи
}
// Фон
private void BackgroundFun()
{
// Создаем объект для описания геометрической фигуры
GeometryDrawing geometryDrawing = new GeometryDrawing();
// Описываем и сохраняем геометрию квадрата
RectangleGeometry rectGeometry = new RectangleGeometry();
rectGeometry.Rect = new Rect(0, 0, 1, 1);
geometryDrawing.Geometry = rectGeometry;
// Настраиваем перо и кисть
geometryDrawing.Pen = new Pen(Brushes.Red, 0.005);// Перо рамки
geometryDrawing.Brush = Brushes.Beige;// Кисть закраски
// Добавляем готовый слой в контейнер отображения
drawingGroup.Children.Add(geometryDrawing);
}
// Горизонтальная сетка
private void GridFun()
{
// Создаем коллекцию для описания геометрических фигур
GeometryGroup geometryGroup = new GeometryGroup();
// Создаем и добавляем в коллекцию десять параллельных линий
for (int i = 1; i < 10; i++)
{
LineGeometry line = new LineGeometry(new Point(1.0, i * 0.1),
new Point(-0.1, i * 0.1));
geometryGroup.Children.Add(line);
}
// Сохраняем описание геометрии
GeometryDrawing geometryDrawing = new GeometryDrawing();
geometryDrawing.Geometry = geometryGroup;
// Настраиваем перо
geometryDrawing.Pen = new Pen(Brushes.Gray, 0.003);
double[] dashes = { 1, 1, 1, 1, 1 };// Образец штриха
geometryDrawing.Pen.DashStyle = new DashStyle(dashes, -.1);
// Настраиваем кисть
geometryDrawing.Brush = Brushes.Beige;
// Добавляем готовый слой в контейнер отображения
drawingGroup.Children.Add(geometryDrawing);
}
// Строим синус линией
private void SinFun()
{
// Строим описание синусоиды
GeometryGroup geometryGroup = new GeometryGroup();
for (int i = 0; i < dataList[0].Length - 1; i++)
{
LineGeometry line = new LineGeometry(
new Point((double)i / (double)countDot,
.5 - (dataList[0][i] / 2.0)),
new Point((double)(i + 1) / (double)countDot,
.5 - (dataList[0][i + 1] / 2.0)));
geometryGroup.Children.Add(line);
}
// Сохраняем описание геометрии
GeometryDrawing geometryDrawing = new GeometryDrawing();
geometryDrawing.Geometry = geometryGroup;
// Настраиваем перо
geometryDrawing.Pen = new Pen(Brushes.Blue, 0.005);
// Добавляем готовый слой в контейнер отображения
drawingGroup.Children.Add(geometryDrawing);
}
// Строим косинус точками
private void CosFun()
{
// Строим описание косинусоиды
GeometryGroup geometryGroup = new GeometryGroup();
for (int i = 0; i < dataList[1].Length; i++)
{
EllipseGeometry ellips = new EllipseGeometry(
new Point((double)i / (double)countDot,
.5 - (dataList[1][i] / 2.0)), 0.01, 0.01);
geometryGroup.Children.Add(ellips);
}
// Сохраняем описание геометрии
GeometryDrawing geometryDrawing = new GeometryDrawing();
geometryDrawing.Geometry = geometryGroup;
// Настраиваем перо
geometryDrawing.Pen = new Pen(Brushes.Green, 0.005);
// Добавляем готовый слой в контейнер отображения
drawingGroup.Children.Add(geometryDrawing);
}
// Надписи
private void MarkerFun()
{
GeometryGroup geometryGroup = new GeometryGroup();
for (int i = 0; i <= 10; i++)
{
FormattedText formattedText = new FormattedText(
String.Format("{0,7:F}", 1 - i * 0.2),
CultureInfo.InvariantCulture,
FlowDirection.LeftToRight,
new Typeface("Verdana"),
0.05,
Brushes.Black);
formattedText.SetFontWeight(FontWeights.Bold);
Geometry geometry = formattedText.BuildGeometry(new Point(-0.2, i * 0.1 - 0.03));
geometryGroup.Children.Add(geometry);
}
GeometryDrawing geometryDrawing = new GeometryDrawing();
geometryDrawing.Geometry = geometryGroup;
geometryDrawing.Brush = Brushes.LightGray;
geometryDrawing.Pen = new Pen(Brushes.Gray, 0.003);
drawingGroup.Children.Add(geometryDrawing);
}
}
}
Разберитесь с кодом
и запустите приложение - результат будет таким
Поизменяйте размер
окна - рисунок автоматически масштабируется с сохранением качества отображенияИногда может потребоваться перевести монитор в полноэкранный режим, чтобы
получить стиль игровых программ или старого доброго DOS. Обычно в таком случае
отключают и курсор, а все управление программой перекладывают на клавиатуру.
Из существующих вариантов переключения в полноэкранный режим наиболее простой,
это настроить окно window так:
window.WindowStyle = WindowStyle.None; window.ResizeMode = ResizeMode.NoResize;
Но такой способ не убирает панель задач и не блокирует появление меню Пуск, которое также включает панель задач.
Есть более сложный способ, приведенный в "http://www.swart.ws/2009/03/kiosk-full-screen-wpf-applications.html", но он имеет те же недостатки.
Для достижения полной иллюзии режима FullScreen следует отключить системные
клавиши, вызывающие панель задач и меню Пуск, и самый эффективный (на мой
взгляд) способ приведен в "http://www.sql.ru/Forum/actualthread.aspx?tid=632552",
которым мы и воспользуемся в данном упражнении.
Из приведенного в статье http://support.microsoft.com/kb/126449/ru сочетания клавиш мы воспользуемся только следующими вариантами:
Этого будет достаточно, чтобы поддерживать полноэкранный режим и одновременно не блокировать остальную функциональность клавиатуры.
Добавьте к решению
командой File/Add/New Project новый проект с именем FullScreen и
назначьте его стартовым
В панели Solution Explorer добавьте к корню проекта FullScreen новую папку Images командой контекстного
меню Add/New Folder
В панели Solution
Explorer командой Add/Existing Item контекстного меню для папки Images скопируйте
в нее файл flower2.jpg из прилагаемого каталога Source
Определите
в интерфейсной части окна следующий код<Window x:Class="FullScreen.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
Background="Green"
Loaded="Window_Loaded"
KeyDown="Window_KeyDown"
KeyUp="Window_KeyDown"
>
<Window.ContextMenu>
<ContextMenu>
<MenuItem Name="changeScreen" Click="MenuItem_Click" />
</ContextMenu>
</Window.ContextMenu>
<DockPanel>
<Menu Name="menu" DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="Open" />
<MenuItem Header="Save" />
<MenuItem Header="SaveAs" />
<MenuItem Header="Exit" />
</MenuItem>
<MenuItem Header="Edit">
<MenuItem Header="Cut" />
<MenuItem Header="Copy" />
<MenuItem Header="Paste" />
</MenuItem>
</Menu>
<Image Source="Images/flower2.jpg" Stretch="Uniform" />
</DockPanel>
</Window>
Для настройки окна и его элементов мы применили синтаксис атрибута,
а для создания каркаса контекстного меню - синтаксис тега
свойства ( свойства
зависимости, присоединенные свойства ). Атрибут Stretch элемента Image может
принимать следующие значения:
None - изображение масштабируется до естественного размера и в области
просмотра справа внизу видно столько, сколько поместилосьFill - изображение масштабируется по всей области просмотра без соблюдения
пропорцийUniform - изображение масштабируется с соблюдением
пропорций, чтобы полностью поместиться в область
просмотраUniformToFill - изображение масштабируется с соблюдением пропорций так,
чтобы в область просмотра полностью вместился хотя бы один размер, а для
другой стороны видно столько, сколько поместилосьТеперь необходимо создать обработчики для событий, выделеных в листинке,
в файле присоединенного кода Window1.xaml.cs. Для этого:
Найдите в разметке
файла Window1.xaml атрибуты событийLoaded="Window_Loaded" KeyDown="Window_KeyDown" KeyUp="Window_KeyDown" Click="MenuItem_Click"
В любом месте каждого
из этих атрибутов щелкните правой кнопкой мыши и выполните команду Navigate to Event Handler.
Оболочка создаст обработчики с указанными именами, а если обработчик уже существует, то перейдет к нему.
Заполните
файл поддержки разметки Window1.xaml.cs следующим кодомusing System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace FullScreen
{
public partial class Window1 : Window
{
// Объявляем как поля для видимости в функциях
bool fullScreen = false;// Состояние экрана
WindowStyle windowStyle;
WindowState windowState;
ResizeMode resizeMode;
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Запоминаем начальные параметры окна
windowState = this.WindowState;
windowStyle = this.WindowStyle;
resizeMode = System.Windows.ResizeMode.CanResizeWithGrip;
this.ResizeMode = resizeMode;
changeScreen.Header = "FullScreen";
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
FullScreen();
}
private void FullScreen()
{
if (!fullScreen)// Переходим в полноэкранный режим
{
if (this.WindowState == WindowState.Maximized)
{
// Чтобы скрыть панель задач и не было мерцания
this.Hide();
this.WindowState = WindowState.Normal;
}
App.Current.MainWindow.WindowStyle = WindowStyle.None;// Без заголовка
App.Current.MainWindow.Topmost = true; // На передний план
App.Current.MainWindow.WindowState = WindowState.Maximized;// Развернуть
App.Current.MainWindow.ResizeMode = ResizeMode.NoResize;// Неизменяемое
menu.Visibility = Visibility.Collapsed;// Скрываем меню
HookSystemKeys.FunHook();// Запрещаем системные клавиши
this.Visibility = Visibility.Visible;
fullScreen = true;
changeScreen.Header = "WindowScreen";
}
else // Восстанавливаем оконный режим
{
this.WindowStyle = windowStyle;
this.Topmost = false;
this.WindowState = windowState;
this.ResizeMode = resizeMode;
menu.Visibility = Visibility.Visible;// Показываем меню
HookSystemKeys.FunUnHook();// Освобождаем системные клавиши
fullScreen = false;
changeScreen.Header = "FullScreen";
}
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
this.Close();
}
}
}
В приведенном коде используется класс HookSystemKeys,
который нам нужно подключить к приложению и который взят из "http://www.sql.ru/Forum/actualthread.aspx?tid=632552" (там
же и описан).
Добавьте к текущему
проекту FullScreen командой Project/Add New Item новый файл с именем HookSystemKeys.cs
Заполните файл HookSystemKeys.cs такusing System;
using System.Diagnostics;
using System.Runtime.InteropServices;
// Для перечисления Keys
using System.Windows.Forms;
namespace FullScreen
{
// Отключение системной клавиши
// http://www.sql.ru/Forum/actualthread.aspx?tid=632552
class HookSystemKeys
{
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYUP = 257;// Отпускание любой клавиши
private static LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
// Поддержка флагов состояния системных клавиш
static bool CtrlKey, AltKey, WinKey;
private static void StateKey(int vkCode, IntPtr wParam)
{
switch ((Keys)vkCode)
{
case Keys.LControlKey:
case Keys.RControlKey:
if (wParam == (IntPtr)WM_KEYUP)
CtrlKey = false;
else
CtrlKey = true;
break;
case Keys.LMenu:
case Keys.RMenu:
if (wParam == (IntPtr)WM_KEYUP)
AltKey = false;
else
AltKey = true;
break;
case Keys.LWin:
case Keys.RWin:
if (wParam == (IntPtr)WM_KEYUP)
WinKey = false;
else
WinKey = true;
break;
}
}
// Общедоступная упаковка оригинала
public static void FunHook()
{
_hookID = SetHook(_proc);
}
public static void FunUnHook()
{
UnhookWindowsHookEx(_hookID);
}
private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}
private delegate IntPtr LowLevelKeyboardProc(int nCode,
IntPtr wParam, IntPtr lParam);
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
int vkCode = Marshal.ReadInt32(lParam);
StateKey(vkCode, wParam);
if (WinKey // Системная
|| CtrlKey (Keys)vkCode == Keys.Escape // Ctrl+Esc
|| AltKey (Keys)vkCode == Keys.Escape // Alt+Esc
|| AltKey (Keys)vkCode == Keys.Tab) // Alt+Tab
{
return (IntPtr)1;
}
else
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}
}
Командой Project/Add Reference добавьте к проекту ссылку на библиотеку System.Windows.Forms.dll, в которой
находится перечисление Keys кодов клавиш
Запустите проект FullScreen и
испытайте построенную нами имитацию полноэкранного режимаОконный и полноэкранный (уменьшенный масштаб) режимы будут выглядеть так


Главное меню мы создали для примера, чтобы показать, что его можно отключать в полноэкранном режиме. Переключение режимов выполняется вызовом контекстного меню по щелчку правой кнопки мыши на окне приложения. В полноэкранном режиме отключаются нежелательные для нас системные клавиши, которые могли бы привести к сбою режима:
В оконном режиме функциональность системных клавиш восстанавливается.
Для полного сходства можно было бы отключить мышь, но тогда все управление окном нужно будет возложить на клавиатуру. В нашем приложении мы для закрытия окна использовали клавишу Esc. Закрыть окно можно и системной комбинацией Alt+F4, которую мы не отключали. Осталась активной комбинация Alt+Space, вызывающая системное меню приложения.
Мы использовали в классе HookSystemKeys перечисление System.Windows.Forms.Keys библиотечной сборки System.Windows.Forms, а в обработчике Window_KeyDown - перечисление System.Windows.Input.Key библиотечной сборки WindowsBase.dll. Это разные перечисления и их нельзя путать.
Попробуйте разобраться
с кодомОбъект ImageDrawing принимает графические данные в виде битовой карты (точечного
рисунка). Для создания изображения необходимо создать экземпляр ImageDrawing и установить значения его свойств ImageDrawing.ImageSource и ImageDrawing.Rect.
Свойство ImageDrawing.ImageSource задает изображение для рисования, а свойство ImageDrawing.Rect задает положение и размер каждого изображения.
Если нужно отобразить один рисунок, то его сразу можно присоединить к объекту
отображения Image, как мы это делали в предыдущем упражнении. Если же требуется
нарисовать несколько рисунков сразу, то необходимо организовать конвейер формирования
рисунков. Вначале создаются отдельные слои с помощью ImageDrawing, затем они
передаются в накопитель DrawingGroup,
где располагаются в Z -последовательности в порядке их добавления. В таком порядке
каждый новый слой будет перекрывать все предыдущие и располагаться ближе к
пользователю. Затем этот слоеный объект передается в рисовальщик DrawingImage,
который и присоединяется, в конечном итоге, к элементу отображения Image.
Объект DrawingGroup как составной объект рисования, может принимать
не только точечные рисунки. В первом упражнении мы использовали его для накопления
векторных рисунков геометрии, порожденных объектом GeometryDrawing.
Он также способен принимать текстовые данные от GlyphRunDrawing, или
медийные данные звука и видео от объекта VideoDrawing. Объект DrawingGroup является единственным типом базового объекта Drawing, который
позволяет определять свою собственную область отсечения. Но об этом чуть позже,
а сейчас приступим к рассмотрению объекта ImageDrawing.
Класс ImageDrawing легко спутать с важным классом DrawingImage, названия которых
так похожи. Но это нас не должно путать, если мы представим изготовление готового
рисунка как последовательность операций на конвейере. Это будет выглядеть примерно
так:
GeometryDrawing |
-> | DrawingGroup |
-> | DrawingImage |
-> | Image |
ImageDrawing |
||||||
GlyphRunDrawing |
||||||
VideoDrawing |
Добавьте к решению
проект типа WPF Application с именем WpfApp3 и
назначьте его стартовым
Добавьте к проекту
командой Project/New Folder папку с именем Images
В панели Solution
Explorer вызовите контекстное меню для папки Images и скопируйте в нее командой Add/Existing Item из прилагаемого каталога Source пять файлов с рисунками (не забудьте изменить
фильтр диалогового окна Add Existing Item на All Files ):market 031.jpgmarket 032.jpgmarket 034.jpgmarket 039.jpgmarket 040.jpg
В панели Solution
Explorer выделите все пять рисунков одновременно и в панели Properties установите
для них свойстваВ этом упражнении мы рассмотрим два способа использования объекта ImageDrawing - с помощью разметки на XAML и с помощью кода C#. Оба способа работают совершенно
одинаково и для того, чтобы это подчеркнуть, мы с помощью них реализуем одну
и ту же задачу. Попутно рассмотрим решение некоторых мелких вопросов, таких,
как предотвращение повторного запуска созданного окна приложения, назначение
главного окна и формирование всплывающей подсказки.
Вначале создадим главное
окно приложения Window1, в котором примененим объект ImageDrawing в кодовой
части Window1.xaml.cs. Затем построим дочернее окно Window2, в котором используем
объект ImageDrawing в дискрипторной части Window2.xaml.
Заполните файл разметки Window1.xaml следующим дескрипторным кодом<Window x:Class="WpfApp3.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Главное окно Window1: Работа объекта ImageDrawing через процедурный код"
Loaded="Window_Loaded"
MouseDoubleClick="Show_Window2"
SizeToContent="WidthAndHeight"
ResizeMode="NoResize">
<Window.ContextMenu>
<ContextMenu>
<MenuItem Header="Создать дочернее окно" Click="Create_Window2" />
</ContextMenu>
</Window.ContextMenu>
</Window>
Эта разметка настраивает окно и создает контекстное меню с одним пунктом.
Вызов дочернего окна, которое будет иллюстрировать разметочный способ применения
объекта ImageDrawing, предусмотрим в обработчиках событий MouseDoubleClick окна
и Click контекстного меню. Такое дублирование одной и той же задачи создания
дочернего окна выбрано потому, чтобы испытать разные способы предотвращения
повторного запуска уже существующего окна.
Событие Loaded срабатывает после загрузки окна в оперативную память, поэтому
в его обработчик удобно поместить код создания объектов содержимого окна. Атрибуты SizeToContent и ResizeMode дескриптора <Window> делают окно подстраиваемым
под содержимое (при первом появлении) и неизменяемое в размерах пользователем,
соответственно. Контекстное меню прикрепляется к объекту окна и будет вызываться
в любой его точке, кроме заголовка.
Щелкните правой кнопкой
мыши на каждом из событий Loaded, MouseDoubleClick, Click и выполните команду Navigate to Event Handler,
чтобы создать в кодовой части обработчики с уже заготовленными именами
В файле Window1.xaml.cs заполните обработчик события Loaded следующим кодомprivate void Window_Loaded(object sender, RoutedEventArgs e)
{
// Размеры всех рисунков одинаковы
const int WIDTH = 348, HEIGHT = 232;
// Создаем накопитель рисунков DrawingGroup
DrawingGroup drawingGroup = new DrawingGroup();
// Левый верхний
ImageDrawing pict1 = new ImageDrawing();
pict1.Rect = new Rect(0, 0, WIDTH, HEIGHT);
pict1.ImageSource = new BitmapImage(
new Uri(@"Images\market 040.jpg", UriKind.Relative));
drawingGroup.Children.Add(pict1);
// Правый верхний
ImageDrawing pict2 = new ImageDrawing();
pict2.Rect = new Rect(350, 0, WIDTH, HEIGHT);
pict2.ImageSource = new BitmapImage(
new Uri(@"Images\market 039.jpg", UriKind.Relative));
drawingGroup.Children.Add(pict2);
// Левый нижний
ImageDrawing pict3 = new ImageDrawing();
pict3.Rect = new Rect(0, 234, WIDTH, HEIGHT);
pict3.ImageSource = new BitmapImage(
new Uri(@"Images\market 034.jpg", UriKind.Relative));
drawingGroup.Children.Add(pict3);
// Правый нижний
ImageDrawing pict4 = new ImageDrawing();
pict4.Rect = new Rect(350, 234, WIDTH, HEIGHT);
pict4.ImageSource = new BitmapImage(
new Uri(@"Images\market 032.jpg", UriKind.Relative));
drawingGroup.Children.Add(pict4);
// Передать рисовальщику
DrawingImage drawingImageSource = new DrawingImage(drawingGroup);
// Заморозить DrawingImage для лучшей производительности
drawingImageSource.Freeze();
// Передать элементу отображения
Image image = new Image();
image.Stretch = Stretch.None;
image.Source = drawingImageSource;
// Контейнер Border для присоединения к содержимому окна
Border border = new Border();
border.Background = Brushes.White;
border.BorderBrush = Brushes.White;
border.BorderThickness = new Thickness(2); // Толщина рамки
border.Margin = new Thickness(10); // Внешний отступ-поле
border.Child = image; // Отдать родителю
this.Background = Brushes.Blue;
this.Content = border;
}
Дополните конструктор
окна Window1 следующим кодомpublic Window1()
{
// Инициализация разметочной части
InitializeComponent();
// Корректировка заголовка окна
this.Title += "=\"Так голодают буржуины!\"";
// Всплывающая подсказка
this.ToolTip = "Вызывайте дочернее окно\n"
+ "двойным щелчком мыши\n"
+ "или контекстным меню...";
}
Здесь применено экранирование двойных кавычек в строковой константе. В определении всплывающей подсказки использован управляющий символ новой строки.
Запустите приложение
- первое окно будет выглядеть так
Обратите внимание, что окно не имеет системных кнопок минимизации и максимизации,
а только кнопку закрытия. Это поведение обеспечивается настройкой в разметке
окна с помощью атрибута ResizeMode="NoResize".
При наведении курсора на окно работает всплывающая подсказка, напоминающая способы создания дочернего окна. Но пока обработчики, предназначенные для этой функциональности, у нас пустые, да и само окно еще нужно заготовить.
Добавьте к
текущему (выделенному в панели Solution Explorer ) проекту WpfApp3 командой Project/Add Window новое окно WPF с
именем Window2.xaml
Наполните файл Window2.xaml следующей
разметкой<Window x:Class="WpfApp3.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Дочернее окно Window2: Работа объекта ImageDrawing через разметку"
SizeToContent="WidthAndHeight"
ResizeMode="NoResize"
Background="Green"
>
<Border BorderBrush="White" BorderThickness="2"
Margin="10"
Background="White"
>
<Image Stretch="None">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<!-- Рис.1 - левый верхний (x, y, width, height) -->
<ImageDrawing Rect="0,0,348,232" ImageSource="Images\market 031.jpg"/>
<!-- Рис.2 - правый верхний (x, y, width, height) -->
<ImageDrawing Rect="350,0,348,232" ImageSource="Images\market 034.jpg"/>
<!-- Рис.3 - левый нижний (x, y, width, height) -->
<ImageDrawing Rect="0,234,348,232" ImageSource="Images\market 039.jpg"/>
<!-- Рис.4 - правый нижний (x, y, width, height) -->
<ImageDrawing Rect="350,234,348,232" ImageSource="Images\market 040.jpg"/>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Border>
</Window>
Дополните конструктор
класса Window2 следующим кодомpublic Window2()
{
// Инициализация разметочной части
InitializeComponent();
// Корректировка заголовка окна
this.Title += "=\"Так голодают буржуины!\"";
// Дочернее окно не отображать в панели задач ОС
this.ShowInTaskbar = false;
}
Заполните обработчики
щелчка и контекстного меню
в классе Window1 следующим кодомWindow wnd2;
private void Show_Window2(object sender, MouseButtonEventArgs e)
{
wnd2 = new Window2();
wnd2.Show();
}
private void Create_Window2(object sender, RoutedEventArgs e)
{
wnd2 = new Window2();
wnd2.Show();
}
Запустите приложение
и вызовите окно Window2 через окно Window1, как напоминает всплывающая подсказка
Получится следующий
результат
Здесь есть несколько существенных недостатков, которые следует устранить:
this.ShowInTaskbar
= false; )Во первых, зачем пользователю много экземпляров одного и того же окна. Во вторых, хоть окна не имеют системных кнопок минимизации, все равно их можно минимизировать все сразу через операционную систему командой "Показать рабочий стол". А в этом случае вновь сделать видимыми вторичные окна не удасться и придется их закрывать через Диспетчер задач (либо через оболочку Visual Studio останавливать процесс - работу приложения).
Для устранения указанных недостатков нужно выполнить следующее:
Для назначения
окна Window1 главным добавьте в его конструктор следующий кодpublic Window1()
{
// Инициализация разметочной части
InitializeComponent();
// Корректировка заголовка окна
this.Title += "=\"Так голодают буржуины!\"";
// Всплывающая подсказка
this.ToolTip = "Вызывайте дочернее окно\n"
+ "двойным щелчком мыши\n"
+ "или контекстным меню...";
// Сделать главным окном приложения
Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
Application.Current.MainWindow = this;
}
Запустите
приложение и убедитесь, что теперь все дочерние окна при закрытии первичного
окна тоже закрываются вместе с приложениемДля блокирования повторного создания дочерних окон применим два способа:
Window1 ссылку wnd2 на дочернее окно в
свойство Wnd2 и будем ее обнулять в обработчике Closed окна Window2. В первом
окне перед созданием дочернего будем эту ссылку проверять - если ненулевая,
значит окно существует и новое создавать нельзяApplication.Current.Windows типа WindowCollection приложения и в первом окне перед созданием дочернего проверять,
отсутствует ли в коллекции существующих окон приложения интересующее нас
окно. Любое созданное окно приложения сразу заносится в эту коллекцию, а
при закрытии между событиями Closing и Closed немедленно удаляется из нее.
Единственное, что потребуется определить в дочернем окне, так это свойство Name, по которому мы будем искать это окно в коллекции приложенияИтак, реализуем сказанное...
В классе Window1 найдите
объявление поля wnd2 (строка: Window wnd2; ), щелкните на поле wnd2 правой
кнопкой мыши и выполните команду контекстного меню Refactor/Encapsulate FieldПоявится окно Encapsulate Field, предлагающее обернуть поле в общедоступное
свойство с именем Wnd2

Скажите OK и оболочка
создаст следующий код свойстваpublic static Window Wnd2
{
get { return Window1.wnd2; }
set { Window1.wnd2 = value; }
}
Аксессор get нам не нужен, поскольку в клиенте Window2 при закрытии окна мы
будем поле wnd2 только обнулять через свойство Wnd2, поэтому get следует удалить.
Внесите в класс Window1 следующий окончательный код реализации первого способа// Предотвращение повторного открытия окна: Способ 1
static Window wnd2;
public static Window Wnd2
{
set { Window1.wnd2 = value; }
}
// Обработчик двойного щелчка
private void Show_Window2(object sender, MouseButtonEventArgs e)
{
if (wnd2 == null)
{
wnd2 = new Window2();
wnd2.Show();
}
else
wnd2.Activate();// Сдвинуть на передний план
}
Модифицируйте класс Window2 следующим образомpublic partial class Window2 : Window
{
public Window2()
{
// Инициализация разметочной части
InitializeComponent();
// Корректировка заголовка окна
this.Title += "=\"Так голодают буржуины!\"";
// Дочернее окно не отображать в панели задач ОС
this.ShowInTaskbar = false;
// Регистрация обработчика
this.Closed += new EventHandler(Window2_Closed);
}
private void Window2_Closed(object sender, EventArgs e)
{
Window1.Wnd2 = null; // Для предотвращения повторного запуска
GC.WaitForFullGCComplete(); // Ждать завершения сборки мусора
GC.Collect(); // Начать сборку мусора
}
}
Здесь мы немного перестарались и ввели еще принудительный вызов сборщика мусора. Это необязательно, так как запуск механизма освобождения брошенных ссылок активизирует сама среда исполнения и в том случае, когда заканчивается выделенная домену приложения оперативная память.
Запустите приложение
и убедитесь, что обработчик создания дочернего окна по двойному щелчку теперь
работает правильно
В разметку файла Window2.xaml добавьте определение свойства Name окна Window2 для его идентификации в коллекции
приложения<Window x:Class="WpfApp3.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Window_2"
Title="Дочернее окно Window2: Работа объекта ImageDrawing через разметку"
SizeToContent="WidthAndHeight"
ResizeMode="NoResize"
Background="Green"
>
...................................
</Window>
Модифицируйте обработчик
контекстного меню в файле Window1.xaml.cs следующим образом// Предотвращение повторного открытия окна: Способ 2
// Обработчик контекстного меню
private void Create_Window2(object sender, RoutedEventArgs e)
{
bool windowExists = false;
foreach (Window window in Application.Current.Windows)
{
if (window.Name == "Window_2")
{
windowExists = true;
window.Activate();// Сдвинуть на передний план
break;
}
}
if (!windowExists)
{
wnd2 = new Window2();
wnd2.Show();
}
}
Запустите приложение
и убедитесь, что и этот обработчик блокирования повторного запуска
теперь тоже работает правильно
Разберитесь с кодомВ будущих упражнениях мы продолжим знакомство с графическими возможностями WPF.

Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.