=== Введение в WPF. Часть 2. Анатомия Window ===
[[dotnet:wpf_introduction|Часть 1]]
Пришла очередь препарировать окна. Вспомним минимальный код, из прошлой статьи, который позволил нам с Вами, отобразить программно окно с использованием WPF:
using System;
using System.Windows;
namespace Juice.HelloWpf
{
class Program
{
[STAThread]
static void Main(string[] args)
{
Application app = new Application();
Window windows = new Window();
windows.Show();
app.Run();
}
}
}
Что происходит в момент создания окна? Начнем с конструктора. Конструируя окно WPF, поступает примерно следующим образом. Перво-наперво устанавливает ряду полей значения по умолчанию. Во вторых проверяет, существование Application. Зачем? Ну просто для того, что бы не заставлять нас с Вами, выполнять лишние телодвижения. Если мы посмотрим еще раз на код приведенный Выше и вспомним о том, что приложение имеет главное окно, то можем задать вопрос, где мы его установили? Попробуем провести эксперимент. Перепишем код так:
using System;
using System.Windows;
namespace Juice.HelloWpf
{
class Program
{
[STAThread]
static void Main(string[] args)
{
Application app = new Application();
Window windows = new Window();
app.MainWindow.Show();
app.Run();
}
}
}
Код также отлично отработал. Следствие этого не сложного эксперимента, следующие: окно само сделало себя главным и добавило в коллекцию Windows класса Application. А теперь попробуем поменять местами две строчки кода:
using System;
using System.Windows;
namespace Juice.HelloWpf
{
class Program
{
[STAThread]
static void Main(string[] args)
{
Window windows = new Window();
Application app = new Application();
app.MainWindow.Show();
app.Run();
}
}
}
И убедимся, что от перестановки слагаемых, в нашем случае, сумма изменяется.
На момент создание окна, не существовало экземпляра Application, окно выяснив это не «смогло» себя добавить в соответствующее свойство. А объект Application сможет установить корректные значения только в методе Run(). Именно по этому app.MainWindow вырнуло null в программу и попытка вызвать метод Show() привела к исключению.
Каждый экземпляр окна имеет внутреннее свойство App, которое позволяет ему получить ссылку на экземпляр приложения. Окно пользуется им в двух ситуациях. Первую мы уже рассмотрели, вторая ситуация возникает когда окно закрывается, оно удаляет себя из коллекции Windows экземпляра приложения и обнуляет MainWindow, только в том случае если окно было главным окном приложения. Именно поэтому коллекция Windows у Application всегда поддерживается в актуальном состоянии.
Разберемся с позиционированием окном. В отличие от WindowsForms где у Form было свойство Location, Window такого свойства не имеет. Зато присутствуют известные нам по WindwosForm свойства Left и Top.
Left - отступ от левого, края рабочего стола.
Top - отступ от верхнего, края рабочего стола.
Используя эти свойства мы можем позиционировать окно.
Первое, что вызывает удивление это использование типа double для хранения этих значений. Но удивление быстро проходит. Оказывается в WPF, размеры исчисляются не в обычных пикселах, а в логических пикселах. Которые, на самом деле, не зависят от разрешения монитора. В англоязычной литературе часто встречается сокращение DIP (Device Independent Pixel). 96 DIP = 1 дюйму = 2.54 сантиметра. А так как разрешающая способность для большинства современных мониторов это 96 DPI, то в подавляющем большинстве случаев 1 DIP = 1 физическому пикселу.
По этой [[http://www.codeplex.com/perspective|ссылке]] находится один интересный проект с открытым исходным кодом и в нем есть класс DipHelper который позволяет легко оперировать DIP в метрической и дюймовой системах счисления.
Основное преимущество от использования DIP это сохранение исходных размеров при использовании других устройств вывода или устройств с другими разрешающими характеристиками, например принтеров.
Для управления размером окон мы можем использовать свойства Width и Height. Тем не менее их поведение отличается от привычного нам по модели WindowsForms. Поставим простой эксперимент.
using System;
using System.Windows;
namespace HelloWPFNew
{
public class HeloWPF
{
[STAThread]
public static void Main()
{
Application app = new Application();
Window window = new Window();
window.Title = String.Format("Width: {0}; Height: {1};", window.Width, window.Height);
app.Run(window);
}
}
}
Результатом работы программы есть вывод в заголовок окна, значений его ширины и высоты. Для задания заголовка используется свойство Title. Результат мягко говоря удивительный. Но оказывается Width и Height не инициализируются окном при создании, вернее инициализируются значением NaN. Размерами окна по умолчанию заведует операционная система, а мы с помощью этих свойств лишь можем попросить ее сделать окно нужного нам размера.
Например так:
using System;
using System.Windows;
namespace HelloWPFNew
{
public class HeloWPF
{
[STAThread]
public static void Main()
{
Application app = new Application();
Window window = new Window();
window.Width = 320;
window.Height = 240;
window.Show();
app.Run();
}
}
}
Почему попросить? Да потому, что в WPF используется новая модель для управления размерами отображаемых элементов, на основе динамической разметки. Окна и другие визуальные элементы могут динамически изменять собственные размеры. Но об этом как нибудь в следующий раз.
Для того, что бы узнать действительные размеры окна мы можем воспользоваться свойствами ActualWidth и ActualHeight, которые мы можем использовать после того как окно уже было отображено.
Чтоб посмотреть на эти свойства в действии посмотрите на следующую, не сложную, программу:
using System;
using System.Windows;
namespace HelloWPFNew
{
public class HeloWPF
{
[STAThread]
public static void Main()
{
Application app = new Application();
Window window = new Window();
Console.WriteLine("ActualWidth: {0}; ActualHeight: {1};", window.ActualWidth, window.ActualHeight);
window.Show();
Console.WriteLine("ActualWidth: {0}; ActualHeight: {1};", window.ActualWidth, window.ActualHeight);
app.Run();
}
}
}
Результат работы:
ActualWidth: 0; ActualHeight: 0;
ActualWidth: 960; ActualHeight: 559;
Еще одним способом позиционирования окон является свойство WindowsStartupLocation, класса Window, которое может принимать значения:
WindowStartupLocation.Manual (по умолчанию) – выбирается операционной системой, или переопределяется программистом.
WindowStartupLocation.CenterScreen – выравнивание по центру рабочей области
WindowStartupLocation.CenterOwner – используется при выравнивании относительно родительского окна.
Для того, что бы закрепить сегодняшний материал, напишем простую программу, когда созданное нами окно, будет по нажатию соответствующих клавиш, «прикреплять» к краям рабочей области. При этом окно будет занимать ровно половину рабочей области. Отлавливать мы будем нажатия следующих клавиш T (Top), B(Bottom), L(Left), R(Right).
using System;
using System.Windows;
using System.Windows.Input;
namespace HelloWPFNew
{
public class HeloWPF
{
[STAThread]
public static void Main()
{
Application app = new Application();
// создаем окно
Window window = new Window();
window.Width = 320;
window.Height = 240;
// позиционируем по центру рабочей области
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
// подписываемся на уведомление о нажатии клавиш
window.KeyUp += new KeyEventHandler(window_KeyUp);
// отображаем окно
window.Show();
app.Run();
}
static void window_KeyUp(object sender, KeyEventArgs e)
{
// выясняем текущий размер рабочей области
Rect workArea = SystemParameters.WorkArea;
// получаем ссылку на окно
Window window = sender as Window;
// позиционируем окно взависимости от пользовательского ввода
switch (e.Key)
{
case Key.T:
{
window.Left = 0;
window.Top = 0;
window.Width = workArea.Width;
window.Height = workArea.Height / 2;
break;
}
case Key.B:
{
window.Width = workArea.Width;
window.Height = workArea.Height / 2;
window.Left = 0;
window.Top = workArea.Height - window.Height;
break;
}
case Key.L:
{
window.Left = 0;
window.Top = 0;
window.Width = workArea.Width / 2;
window.Height = workArea.Height;
break;
}
case Key.R:
{
window.Width = workArea.Width / 2;
window.Height = workArea.Height;
window.Left = workArea.Width - window.Width;
window.Top = 0;
break;
}
}
}
}
}
В следующий раз, попробуем разобраться с тем, как WPF «красит» окна. А может окна сами красят себя? :-)