=== Введение в 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 «красит» окна. А может окна сами красят себя? :-)