Как сделать окно неактивным wpf

Обновлено: 18.05.2024

Подводные камни WPF

Зачастую разработчики явно включают необходимые словари ресурсов прямо в XAML разметке пользовательских элементов управления вот таким образом:


На первый взгляд, в таком подходе нет никакой проблемы — просто для элемента управления указываем минимально необходимый набор стилей. Предположим, в нашем приложении SomeControl существует в 10 экземплярах на одном из окон. Проблема заключается в том, что при создании каждого из этих экземпляров, указанный словарь будет заново вычитываться, обрабатываться и храниться отдельной копией в памяти. Чем больше подключаемые словари, чем больше экземпляров — тем больше уходит времени на инициализацию содержащего их представления и тем больше памяти расходуется впустую. Мне на практике приходилось иметь дело с приложением, в котором перерасход памяти из-за лишних ResourceDictionary был порядка 200 мегабайт.

Утечки памяти

Утечки на событиях

Например, в приложении есть список объектов, свойства которых можно изменять в окне редактирования. Для реализации этого окна понадобилось устанавливать IsModified в true внутри его модели представления при изменении любого свойства редактируемого объекта.

Предположим, модель представления для редактирования реализована так:


Здесь конструктор устанавливает «сильную» ссылку между бизнес-сущностью и моделью представления редактора. Если создавать экземпляр EntityEditorViewModel при каждом показе окна, то такие объекты будут накапливаться в памяти и удалятся только в том случае, если ссылающаяся на них бизнес-сущность станет «мусором».

Один из вариантов решения проблемы — предусмотреть удаление обработчика. Например, реализовать IDisposable и в методе Dispose() «отписываться» от события. Но тут сразу стоит сказать, что обработчики, заданные лямбда-выражениями как в примере, не могут быть удалены простым способом, т.е. вот такое не сработает:

Но подход с явным удалением не гарантирует отсутствие утечек памяти — можно банально забыть позвать Dispose(). Помимо этого, может быть очень проблематично определить тот момент, когда нужно его вызвать. В качестве альтернативы можно рассмотреть более громоздкий, но действенный подход — Weak Events. Общая идея их реализации в том, что между источником события и подписчиком устанавливается «слабая» ссылка, и подписчик может быть автоматически удален, когда на него больше не станет «сильных» ссылок.

Утечки при байндинге

Помимо потенциальной проблемы, описанной выше, в WPF есть как минимум два типа утечек, которые специфичны именно для этой технологии.

Предположим, у нас есть простой объект:


И мы привязываемся к этому свойству из какого-либо элемента управления:

Другая возможная проблема при установке байндингов — привязка к коллекциям, которые не реализуют интерфейс INotifyCollectionChanged. Механизм возникновения утечек в этом случае очень похож на предыдущий. Способ борьбы очевиден — нужно либо явно указывать OneTime режим привязки, либо использовать коллекции, реализующие INotifyCollectionChanged — например, ObservableCollection.

Наследование визуальных компонентов и стили

Иногда возникает надобность в наследовании стандартных элементов управления для расширения их функциональности, изменения поведения. На первый взгляд, это элементарно:


Но если в приложении используются стили элементов, отличные от стилей по умолчанию, проблема при использовании такого наследника будет заметна сразу. Следующий фрагмент скриншота показывает разницу в отображении базового элемента управления и производного при включенной теме PresentationFramework.Aero.


Но получается, что при использовании производного элемента управления нужно всегда помнить и о добавлении стиля в ресурсы. Или же сделать файл с этим производным стилем и подключать его каждый раз, когда нужно использовать новый элемент.

Есть один способ обойтись без изменений в XAML — в конструкторе производного элемента явно устанавливать ему стиль, взятый с базового:


Таким образом, если не нужно добавлять никаких изменений в базовый стиль, этот способ будет наиболее оптимальным. В противном случае лучше воспользоваться предыдущим вариантом.

Ошибки байндинга


И затем активировать его при старте приложения:

Стандартные средства валидации

В WPF существует несколько способов валидации данных.

ValidationRule — наследуя этот класс можно создавать специализированные правила валидации, которые затем привязываются к полям в XAML разметке. Из «условных» плюсов — не требуются изменения классов модели для выполнения валидации, хотя в некоторых случаях это может быть не самым оптимальным вариантом. Но при этом есть значительный недостаток — ValidationRule не наследует DependencyObject, соответственно в наследниках нет возможности создавать свойства, на которые в последствии можно будет байндиться. Это означает, что нет простого очевидного способа производить валидацию свойств в связке друг с другом — например, если значение одного не может быть больше значения другого. Валидационное правило, реализованное таким способом, может иметь дело только с текущим значением поля и фиксированными значениями свойств, которые были указаны при создании экземпляра этого правила.

IDataErrorInfo, INotifyDataErrorInfo — реализуя эти интерфейсы в классах моделей представления можно легко осуществлять валидацию как отдельных свойств, так и нескольких свойств в связке друг с другом. Обычно для уменьшения количества кода один из этих интерфейсов реализуют в базовом классе моделей и предоставляют средства лаконичного описания правил в наследниках. Например, через регистрацию правил в статическом конструкторе для каждого из типов:


Или через атрибуты:

Но и подход с использованием DataErrorInfo интерфейсов не покрывает все валидационные задачи — в случаях, когда для проверки правила нужно иметь доступ к объектам за пределами валидируемой сущности, начинают возникать проблемы. Например, проверка на уникальность требует доступа к полной коллекции объектов, а это означает что каждый элемент такой коллекции должен иметь ссылку на нее, что сильно усложняет работу с объектом.

Такой подход на самом деле тоже можно обобщить. Воспользуемся упомянутой выше идеей с регистрацией валидаторов в статическом конструкторе. Вот пример базового класса:


А также пример использования:


Таким образом, все валидационные правила находятся внутри самой сущности. Те из них, которые не требуют «внешних» объектов, используются в реализации IDataErrorInfo из базового класса. Для проверки остальных достаточно позвать функцию Validate в нужном месте и использовать результат для принятия решений о дальнейших действиях.

Неправильное использование события PropertyChanged

Мне довольно часто приходилось встречать код подобного вида в WPF проектах:


Причем во многих случаях это был обработчик собственных событий, т.е. «слушал» изменения свойств того же класса, где и был объявлен.

В таком подходе есть несколько серьезных недостатков, которые по итогу приводят к очень тяжелому в плане поддержки и расширения коду. Некоторые из них очевидны: например, при переименовании свойств можно забыть изменить константы в условиях, но это мелкий и легко решаемый недостаток. Гораздо более серьезная проблема в том, что при таком подходе бывает практически невозможно отследить все сценарии, при которых выполняется тот или иной кусок логики.

Можно сформулировать следующий критерий для самопроверки, правильно ли используется обработчик события PropertyChanged: если алгоритм внутри обработчика не зависит от конкретных названий свойств, то все в порядке. В противном случае нужно искать более удачное решение. Примером правильного применения может быть, например, установка свойства IsModified в true при изменении какого-либо свойства модели представления.

Избыточное использование Dispatcher

Неоднократно встречал в WPF проектах принудительное выполнение операций на UI потоке даже в тех случаях, когда это не нужно. Для того, чтобы описать масштаб проблемы, приведу пару цифр, полученных с помощью простых тестов на ноутбуке c процессором Core i7-3630QM 2.4GHz:

  • Время, затраченное Dispatcher.Invoke сверх «полезной» нагрузки при вызове из того же потока, к которому принадлежит Dispatcher — 0.2 мкс на один вызов.
  • Тот же показатель, но при вызове из другого потока — 26 мкс на вызов.

Чтобы уменьшить вред для производительности, достаточно придерживаться простых правил:

  • Диспетчеризировать только то, что действительно нельзя выполнить на фоновом потоке. Например, есть кусок кода, который что-то читает из WEB-сервиса, потом делает расчет по какому-то алгоритму, потом устанавливает пару свойств на модели представления. В этом случае только установка свойств должна диспетчеризироваться (т.к. в свою очередь вызывает обработчики PropertyChanged, среди которых есть код, работающий с UI).
  • Избегать циклов, внутри которых есть обращение к Dispatcher. Например, нужно прочитать список с сервера, и по данным каждого элемента сделать обновление UI. В этом случае лучше сначала просчитать на фоновом потоке все, что нужно будет обновлять на UI, и только потом одним вызовом Dispatcher.Invoke сделать обновление. Вызов Invoke после обработки каждого элемента списка будет крайне неоптимальным решением.

Модальные диалоги

  • Отдельное модальное окно (Window.ShowDialog), стилизованное нужным образом.
  • «Эмуляция» модального окна через добавление панели в визуальное дерево основного окна, которая находится «над» всем остальным содержимым, тем самым перекрывая его.


и ожидать что на основном потоке больше ничего не может произойти, пока пользователь не ответит на вопрос.

Рассмотрим одну из наиболее простых реализаций «эмулированного» диалога.

В первую очередь объявим интерфейс менеджера диалогового окна, через который модель представления будет показывать диалоги. Для начала не будем учитывать возможность получать «ответ» от окна — просто покажем диалог с кнопкой «Закрыть».


Далее реализуем элемент управления, который будет «привязываться» к менеджеру и показывать окно поверх остальных элементов, когда это необходимо:


Опять же, для упрощения, этот контрол рассчитан на то, что в модели представления есть экземпляр реализации IModalDialogHelper в свойстве DialogHelper. В более универсальном решении должна быть возможность подставлять любое свойство.

Я не буду здесь приводить пример простейшей реализации IModalDialogHelper, так как она очевидна: методы Show() и Close() устанавливают соответствующим образом IsVisible, команда CloseCommand просто вызывает метод Close(). Show() еще устанавливает свойство Text.

Для решения этой проблемы немного изменим прототип метода Show:


Возможность ожидать завершения этого метода через await дает стразу несколько преимуществ:

И следует сказать еще пару слов об использовании «новых» диалогов в обработчиках событий типа CancelEventHandler. Подтверждение действий в таких событиях тоже нужно будет реализовывать немного не так как раньше.


Проблема в том, что e.Cancel всегда будет true для кода, вызвавшего Window_Closing, так как await не останавливает выполнение потока, а создает возможность «вернуться» в нужное место в методе после завершения асинхронной задачи. Для вызвавшего кода, Windows_Closing завершится сразу после установки e.Cancel в true.

Правильное решение заключается в том, что тело условия должно оперировать уже не e.Cancel, а явно вызывать «отмененное» действие таким образом, чтобы оно гарантированно выполнилось без дополнительных запросов, минуя повторный вызов этого обработчика. В случае закрытия главного окна программы, например, это может быть явный вызов завершения всего приложения.

Анализ производительности отображения

Многие разработчики знают, что такое «профайлер» и знают, какие есть средства для анализа производительности приложения и анализа потребления памяти. Но в WPF приложениях часть нагрузки на процессор исходит, например, из механизма обработки XAML разметки – парсинг, разметка, рисование. «Стандартными» профайлерами непросто определить, на какую именно активность, связанную с XAML, тратятся ресурсы.

Не буду подробно останавливаться на возможностях существующих инструментальных средств, просто перечислю ссылки на информацию о них. Разобраться с тем, как ими пользоваться, не составит труда любому разработчику.

И еще немного о INotifyPropertyChanged

Одна из самых популярных тем споров в рамках технологии WPF — как наиболее рационально реализовывать INotifyPropertyChanged. Самый лаконичный вариант — использовать АОП, как я уже описывал в одном из примеров в статье об Aspect Injector. Но не всем этот подход нравится, и в качестве альтернативы можно использовать сниппеты. Но тут возникает вопрос о наиболее оптимальном содержимом сниппета. Сперва приведу примеры не самых удачных вариантов.


В данном случае имя свойства указано константой, и не важно — будет оно в именованной константе или, как в примере, «захардкоджено» прямо в вызове метода оповещения — проблема остается той же: при переименовании самого свойства существует вероятность оставить старое значение константы. Эту проблему многие решают следующим изменением метода NotifyPropertyChanged:


В этом случае вместо названия можно указать лямбда-выражение, возвращающее нужное свойство:


К сожалению, этот вариант тоже имеет недостатки — вызов такого метода всегда связан с Reflection, что по итогу в сотни раз медленнее вызова предыдущего варианта NotifyPropertyChanged. В случае приложений для мобильных устройств это может быть критичным.


Если параметр, помеченный этим атрибутом, не указан явно, компилятор подставит в него имя члена класса, вызывающего метод. Таким образом вызов NotifyPropertyChanged() из примера выше равнозначен NotifyPropertyChanged(“Name”). Но что делать, если нужно сообщить об изменении какого-либо свойства «снаружи», не из его сеттера?

Например, у нас есть «калькулируемое» свойство:


При добавлении, удалении или изменении элементов коллекции items нам нужно сообщать об изменении TotalPrice, чтобы интерфейс пользователя всегда отображал его актуальное значение. Учитывая недостатки первых двух решений, приведенных выше, можно сделать следующий ход — все-таки использовать Reflection для получения имени свойства из лямбда-выражения, но сохранять его в статической переменной. Таким образом для каждого отдельно взятого свойства «тяжелая» операция будет выполняться только один раз.


Саму статическую функию GetPropertyName можно положить и в базовый класс для всех «notifiable» сущностей — это не принципиально. Проверка на UnaryExpression нужна, чтобы функция нормально обрабатывала свойства значимых типов, т.к. компилятор добавляет операцию боксинга, чтобы привести указанное свойство к object.

В качестве итога можно сказать, что если использование АОП для INotifyPropertyChanged по каким-либо причинам не устраивает, то можно воспользоваться сниппетами следующего содержания:

Вместо послесловия

WPF — неплохая технология, которую Microsoft по-прежнему позиционирует как основной фреймворк для разработки «настольных» приложений. К сожалению, при написании программ сложнее «калькулятора» обнаруживается ряд проблем, не заметных с первого взгляда, но все они решаемы. Согласно недавним заявлениям Microsoft, они инвестируют в развитие технологии, и в новой версии уже есть много улучшений. В первую очередь они относятся к инструментальным средствам и производительности. Хочется надеяться, что в будущем новые возможности будут добавлены не только в инструментальные средства, но и в сам фреймворк, облегчая работу программиста и избавляя от «хаков» и «велосипедов», которые приходится делать сейчас.

UPD2: изменил пример реализации хелпера для модального диалога на более лаконичный с использованием TaskCompletionSource.

Сделать неактивной или вообще убрать кнопку "Закрыть"

Решение

Народ, кто знает, как можно сделать неактивной или убрать кнопку "Закрыть"? Искал-искал, все вроде свойства просмотрел, так и не нашёл ничего.
Так настроено окно сейчас:

Добавлено через 15 часов 22 минуты
Сам нашёл:

Лучшие ответы ( 1 ) 94731 / 64177 / 26122 Ответы с готовыми решениями:

Убрать из заголовка окна кнопки "свернуть", "развернуть", "закрыть"
У класса Window есть кнопки сворачивание, раскрытие во весь экран и закрытие, так вот мне надо.

Как убрать кнопку "разворачивания" у окна?
Здравствуйте. Вопрос собственно в заголовке) Как убрать у окна кнопку его разворачивания.

Сделать неактивной кнопку "Закрыть" в правом верхнем углу
Мне нужно отключить кнопку закрытия диалогового окна; диалоговые окна: errordlg, warndlg, MsgBox.

Необходимо сделать на форме кнопку "Развернуть" неактивной
необходимо сделать развернуть на форме неактивным (имею ввиду, что при нажатии на него не должно.

Почетный модератор 8694 / 3646 / 404 Записей в блоге: 9 Ну тогда предложу и свой вариант Думал в WPF без API можно ) + мой немного усовершенствован, но для WinForms. Переделать не сложно. Программист 407 / 407 / 7 На этой странице все написано, посмотри стиль окна burning1ife 1447 / 1269 / 293 Записей в блоге: 9 как вариант можешь перехватывать событие Closing и там

поисал по памяти.

Выставляешь WindowStyle="None" и AllowsTransparency="True" у окна.
Затем добавляешь 3 кнопки в разметку и реализацию в код. Кнопку "Закрыть" делаешь неактивной. Перетаскивание тож в три строчки.
Если непонятно пиши. Да, и ещё событие OnClosing отменяешь.

4341 / 2003 / 387 Записей в блоге: 1 Я уже выложил же код, как можно сделать эту кнопку неактивной. Самому делать кнопки напряжно =). Тем более, если у проги стандартный стиль, то и этому окну его менять не в тему. 87844 / 49110 / 22898

Помощь в написании контрольных, курсовых и дипломных работ здесь.

Сделать кнопку "Завершить тест" неактивной, пока не отвечены все вопросы теста
Здравствуйте! помогите решить проблему. Я создаю тест в экселе, и нужно записать макрос, чтобы.


Убрать кнопку "Закрыть" в форме
Добрый вечер. Поскажите можно ли как-нибудь убрать кнопку закрыть из угла формы и сотавить только.


Сделать невидимой кнопку "закрыть"
Как сделать невидимой кнопку "закрыть" у форму, но чтобы иконка осталась

Как сделать кнопку "закрыть"
Доброго времени суток. Пытаюсь освоить GUI программирование. Есть кнопка "закрыть".При "кликанье".

Затемнение родительского окна (wpf)

Те кто работал с delphi должны понять о чем речь, при открытии в модальном окне дочернего окна, нужно сделать затемнение родительского, кто знает как это сделать в wpf?

А то у нас окон много, и при открытии они немного сливаются по цветам. вообщем нужно затемнить прошлое окно, есть идеи?

задан 13 июл '12 в 7:30 johniek_comp johniek_comp 2,509 3 3 золотых знака 23 23 серебряных знака 44 44 бронзовых знака 30 ноя '17 в 11:31

Если не ошибаюсь, вам надо вот это

и, собственно, применение:

ответ дан 13 июл '12 в 7:54 1,756 1 1 золотой знак 22 22 серебряных знака 50 50 бронзовых знаков именно это! спс 13 июл '12 в 8:33 Мне понравилось! Добавил себе такую фичу. Спасибо 17 ноя '16 в 11:10

Для начала полезные ссылки:

Создаёшь библиотеку типа WPF User Control Library , называешь её Controls . Удаляешь из вновь созданного проекта UserControl .

Создаёшь папку Shaders . Добавляешь класс:

TintShaderEffect.cs

Добавляешь в проект в папку Shaders файл TintShader.fx , который содержит:

TintShader.fx

Устанавливаешь Shazzam Shader Editor. Выбираешь в программе File -> New_Shader File . Выбираешь место сохранения и называешь файл TintShader.fx . Далее вставляешь в редакторе код шейдера, который показан выше. Выбираешь в меню Tools -> Compile Shader . Далее выбираешь Tools -> Explore Compiled Shaders . Ищешь и открываешь папку TintShaderEffect (название зависит от того, как ты назвал файл шейдера). Копируешь в проект в папку Shaders файл TintShaderEffect.ps , у файла выставляешь Действие при построении (Build Action) в Resource .

Добавляешь класс в корень проекта Controls

AssemblyHelper.cs

Далее добавляешь в проект Controls класс

CustomWindow.cs

Создаёшь основной проект, например, назвав его Wpf_Shaders. Проект содержит в себе следующий код:

MainWindow.xaml

MainWindow.xaml.cs

SomeWindow.xaml

SomeWindow.xaml.cs

Не забудь добавить ссылку на проект Controls для проекта Wpf_Shaders . Всё, запускаешь программу, открываешь окно нажав на кнопку "Новое окно". Появившееся окно автоматом затеняет родительское окно. Закрываешь, с родительского окна снимается шейдерный эффект. Этот эффект можно применять к любому наследнику от UIElement .

Следует помнить, что это пример того, как не писать много кода для применения эффекта. Можно прописать всё намного гибче в CustomWindow и, к примеру, если какое-то свойство выставлено в True, то не применять эффект и т.д. Так же не забываем, что цвет можно прописать любой. Всё это дело работает очень быстро, так как это шейдеры.

Отображение окна

Чтобы отобразить окно, необходимо создать экземпляр класса Window и вызвать метод Show() или ShowDialog().

Метод ShowDialog() отображает модальное окно. Модальные окна не позволяют пользователю получать доступ к родительскому окну, блокируя возможность использования в нем мыши и возможность ввода в нем каких-то данных до тех пор, пока модальное окно не будет закрыто. Вдобавок метод ShowDialog() не осуществляет возврат до тех пор, пока модальное окно не будет закрыто, так что выполнение любого находящегося после него кода на время откладывается. (Однако это не означает, что в данное время не может выполняться никакой другой код — например, при наличии запущенного таймера обработчик его событий все равно будет работать.) Наиболее часто применяемая в коде схема выглядит так: отображение модального окна, ожидание его закрытия и последующее выполнение над его данными какой-нибудь операции.

Ниже показан пример использования метода ShowDialog():

Метод Show() отображает немодальное окно, которое не блокирует доступ пользователя ни к каким другим окнам. Более того, метод Show() производит возврат управления сразу после отображения окна, благодаря чему следующие за ним в коде операторы выполняются незамедлительно. Можно создавать и показывать сразу несколько немодальных окон, и пользователь может взаимодействовать со всеми ними одновременно.

В случае применения немодальных окон иногда требуется код синхронизации, гарантирующий обновление информации во втором окне при внесении каких-то изменений в первом и тем самым исключающий вероятность работы пользователя с недействительными данными.

Ниже показан пример использования метода Show():

Модальные окна идеально подходят для предоставления пользователю приглашения сделать выбор, прежде чем выполнение операции сможет быть продолжено. Например, возьмем приложение Microsoft Word. Это приложение всегда отображает окна Options (Параметры) и Print (Печать) в модальном режиме, вынуждая пользователя принимать решение перед продолжением. С другой стороны, окна, предназначенные для поиска по тексту или проверки наличия в документе орфографических ошибок, Microsoft Word отображает в немодальном режиме, позволяя пользователю редактировать текст в основном окне документа, пока идет выполнение задачи.

Закрывается окно точно так же просто, с помощью метода Close(). Альтернативным вариантом является сокрытие окна из вида путем использования метода Hide() или установки для свойства Visibility значения Hidden. И в том и в другом случае окно остается открытым и доступным для кода. Как правило, скрывать имеет смысл только немодальные окна. Дело в том, что при сокрытии модального окна код остается в "замороженном" состоянии до тех пор, пока окно не будет закрыто, а закрыть невидимое окно пользователь никак не сможет.

Позиционирование окна

Обычно размещать окно в каком-нибудь точно определенном месте на экране не требуется. В таких случаях можно просто указать для свойства WindowStartupLocation значение CenterOwner и ни о чем не беспокоится. В других случаях, которые хоть встречаются реже, но все-таки бывают, должна задаваться точная позиция окна, что подразумевает использование значения Manual для свойства WindowStartupLocation и указание точных координат в свойствах Left и Top.

Иногда выбору подходящего месторасположения и размера для окна нужно уделять немного больше внимания. Для примера рассмотрим следующую ситуацию: вы случайно создали окно с размером, который является слишком большим для отображения на дисплее с низким разрешением. Если речь идет о приложении с единственным окном, тогда наилучшим решением будет создать окно с возможностью изменения размеров. Если же речь идет о приложении с несколькими плавающими окнами, то дело усложняется.

Можно попробовать просто ограничить позиции окна теми, которые поддерживаются даже на самых маленьких мониторах, но это, скорее всего, будет раздражать пользователей мониторов более новых моделей (которые приобрели мониторы с большей разрешающей способностью специально для того, чтобы иметь возможность умещать на экране больше информации). Поэтому вероятнее всего придется принимать решение о наилучшем размещении окна во время выполнения. А для этого потребуется извлечь кое-какую базовую информацию о доступном оборудовании для отображения с помощью класса System.Windows.SystemParameters.

Класс SystemParameters поддерживает огромный список статических свойств, которые возвращают информацию о различных параметрах системы. Например, его можно использовать для определения, включил ли пользователь помимо всего прочего функцию "горячего" отслеживания (hot tracking) и возможность перетаскивания целых окон. В случае окон класс SystemParameters особенно полезен, потому что предоставляет два свойства, которые возвращают информацию о размерах текущего экрана: FullPrimaryScreenHeight и FullPrimaryScreenWidth. Оба они довольно просты, что иллюстрируется в показанном ниже коде (центрирующем окно во время выполнения):

Хотя этот код эквивалентен применению свойства WindowStartupLocation со значением CenterScreen, он предоставляет гибкость, позволяя реализовать различную логику позиционирования и выполнять ее в подходящее время.

Еще лучший вариант предусматривает применение прямоугольника SystemParameters.WorkArea для размещения окна в свободной области экрана. При вычислении рабочей области область, где пристыкована панель задач (и любые другие "полосы", стыкованные с рабочим столом), не учитывается.

Оба примера с позиционированием окна характеризуются одним небольшим недостатком. Когда свойство Тор устанавливается для окна, которое уже является видимым, это окно незамедлительно перемещается и обновляется. То же самое происходит и при установке свойства Left в следующей строке кода. В результате пользователям с хорошим зрением может быть заметно, что окно перемещается дважды. К сожалению, класс Window не предоставляет метода, который бы позволял устанавливать оба этих свойства одновременно. Поэтому единственным решением является позиционирование окна после его создания, но перед его отображением с помощью метода Show() или ShowDialog().

Сохранение и восстановление информации о местоположении окна

К числу типичных требований для окна относится и запоминание его последнего месторасположения. Эта информация может сохраняться как в конфигурационном файле пользователя, так и в системном реестре Windows.

Если нужно сделать так, чтобы информация о расположении какого-то важного окна хранилась в конфигурационном файле конкретного пользователя, дважды щелкните на узле Properties (Свойства) в окне Solution Explorer и выберите раздел Settings (Параметры). После этого добавьте действующий только на уровне данного пользователя параметр с типом данных System.Windows.Rect, как показано на рисунке:

Свойство для сохранения информации о расположении и размерах окна

При наличии такого параметра далее можно легко создать код, который будет автоматически сохранять информацию о размерах и расположении окна:

Обратите внимание, что в приведенном коде используется свойство RestoreBounds, которое предоставляет корректные размеры (т.е. последний размер окна в обычном состоянии — не свернутом и не развернутом), даже если в текущий момент окно развернуто или свернуто. (Эта удобная функция не была напрямую доступной в Windows Forms и требовала вызова неуправляемой API-функции GetWindowPlacement().)

Извлечь эту информацию, когда она необходима, тоже легко:

Единственным ограничением при таком подходе является необходимость создавать отдельное свойство для каждого окна, у которого должна сохраняться информация о расположении и размерах. Если требуется, чтобы информация о расположении сохранялась у множества различных окон, тогда, возможно, лучше будет разработать более гибкую систему.

Класс Window

Класс Window унаследован от класса ContentControl. Это означает, что он может содержать только одного потомка (каковым обычно является контейнер компоновки наподобие элемента управления Grid) и что его фон можно закрашивать с помощью кисти путем установки свойства Background.

Можно еще также использовать и свойства BorderBrush и BorderThickness для добавления вокруг окна границы, но эта граница добавляется внутри оконной рамки (то есть по краю клиентской области). Оконную рамку можно вообще удалять путем установки для свойства WindowStyle значения None, что позволяет создавать полностью настраиваемое (т.е. имеющее специальную форму) окно.

— это область внутри окна. Именно в ней размещается содержимое. К не клиентской области относится граница и строка заголовка в верхней части окна. За управление этой областью отвечает операционная система.

Помимо этого, класс Window имеет небольшой набор членов, знакомых любому программисту для Windows. Наиболее очевидными из них являются свойства, которые касаются внешнего вида и позволяют изменять способ отображения не клиентской части окна. Основные члены класса Window перечислены ниже:

AllowsTransparency

Если установлено в true, класс Window позволяет другим окнам "проглядывать" через данное при условии, что для фона был установлен прозрачный цвет. В случае установки в false (поведение по умолчанию), находящееся позади данного окна содержимое не "просматривается", и прозрачный цвет фона визуализируется как черный. В случае использования в комбинации со свойством WindowsStyle, установленным в None, это свойство позволяет создавать окна, имеющие необычную форму.

Icon

Объект imageSource, идентифицирующий значок, который должен использоваться для данного окна. Значки отображаются в левом верхнем углу окна (если в нем применяется один из стандартных стилей границ), в панели задач (если свойство ShowInTaskBar установлено в true) и в окне выбора, которое появляется, когда пользователь нажимает комбинацию клавиш <Alt+Tab> для перехода между работающими приложениями. Поскольку эти значки имеют разные размеры, в используемом для них файле .ico должны содержаться изображения с размерами как минимум 16x16 и 32x32 пикселя.

В последних версиях ОС Windows (Windows Vista и Windows 7) добавлен новый стандарт для значков 48x48 и 256x256 пикселей, размер которых можно изменять. Если свойство Icon установлено в null, окно получает тот же значок, что и приложение (значок для которого можно указать в Visual Studio, дважды щелкнув на узле Properties (Свойства) в окне Solution Explorer и перейдя на вкладку Application (Приложение)). Если свойство вообще опущено, WPF для изображения окна будет использовать стандартный, но непримечательный значок

Тор и Left

Определяют расстояние между левым верхним углом окна и левыми верхними краями экрана (в независимых от устройства единицах). При изменении любого из них генерируется событие LocationChanged. Если свойство WindowStartupPosition установлено в Manual, значения этих свойств можно указывать до появления окна для определения, задавая его начальную позицию. Какое бы значение не использовалось для WindowStartupPosition, эти свойства можно устанавливать в любой момент после отображения окна, изменяя его текущую позицию

ResizeMode

Принимает значение перечисления ResizeMode, которое определяет, может ли пользователь изменять размеры окна. Также влияет на видимость кнопок, отвечающих за разворачивание и сворачивание окна. Чтобы полностью заблокировать размеры окна, используйте значение NoResize. Чтобы пользователь мог только сворачивать окно, применяйте значение CanMinimize. Чтобы пользователь мог изменять размер окна всеми возможными способами, указываете значение CanResize. Чтобы в правом нижнем углу окна отображалась еще и визуальная подсказка, указывающая, что размеры окна разрешено изменять, задавайте значение CanResizeWithGrip.

RestoreBounds

Предоставляет границы окна. Если окно в текущий момент развернуто или свернуто, это свойство отражает границы, которые использовались последними перед развертыванием или свертыванием. Это чрезвычайно удобно, когда необходимо сохранить информацию о позиции и размерах окна.

ShowInTaskBar

Если установлено в true, окно отображается в панели задач и списке, появляющемся после нажатия комбинации клавиш <Alt+Tab>. Обычно это свойство устанавливается в true только для главного окна приложения.

SizeToContent

Позволяет создать окно, которое автоматически увеличивается в соответствии с размерами содержимого. Это свойство принимает значение перечисления SizeToContent. Чтобы отключить автоматическое изменение размеров окна, используйте значение Manual. Чтобы окно могло увеличиваться в различных направлениях в соответствии с размерами динамического содержимого, применяйте, соответственно, значение Height, Width или WidthAndHeight. При установке значения SizeToContent окно может увеличиваться в размерах в соответствии с содержимым так, что будет выходить за пределы экрана.

Title

Заголовок, который отображается в строке заголовка окна (и в панели задач)

Topmost

Если установлено в true, окно всегда отображается поверх остальных окон в приложении (если только у них это свойство также не равно true). Такая настройка очень удобна для палитр, которые обычно должны "плавать" над другими окнами

WindowStartupLocation

Принимает значение перечисления WindowStartupLocation. Для размещения окна в конкретной позиции с помощью свойств Left и Тор используйте значение Manual. Для размещения окна по центру экрана применяйте значение CenterScreen. Для размещения окна с учетом позиции того окна, которое его запустило, указывайте значение CenterOwner. При отображении немодального окна с использованием значения CenterOwner удостоверьтесь, что свойство Owner нового окна установлено перед тем, как показывать его.

WindowState

Принимает значение перечисления WindowState. Информирует о том, в каком состоянии находится окно: развернутом, свернутом или обычном (и позволяет изменить его). При изменении значения этого свойства генерируется событие StateChanged

WindowStyle

Принимает значение перечисления WindowStyle, которое определяет внешний вид границы окна. Возможные значения: SingleBorderWindow (по умолчанию), ThreeDBorderWindow (граница визуализируется несколько иным образом в Windows ХР), ToolWindow (отображается тонкая граница, удобная для "плавающих" окон с инструментами без кнопок сворачивания и разворачивания) и None (визуализируется очень тонкая приподнятая граница без области для строки заголовка). Увидеть разницу можно на рисунке ниже:

Настройка нестандартных окон

В большинстве случаев фиксированная графика в WPF для создания окон необычной формы не используется. Вместо этого в таких окнах просто применяется совершенно прозрачный фон, на котором затем размещается уже имеющее нужную форму содержимое. (Примером этого является показанная ниже кнопка, которая "нависает" над совершенно прозрачной областью.)

Преимущество такого подхода заключается в том, что он является модульным. Окно может состоять из множества отдельных компонентов, представляющих собой первоклассные элементы WPF. Но даже еще более важно то, что такой подход позволяет пользоваться другими функциональными возможностями WPF и создавать по-настоящему динамические пользовательские интерфейсы. Например, он позволяет создавать содержимое необычной формы с возможностью изменения его размеров или применять анимацию для обеспечения непрерывно выполняющихся эффектов прямо внутри окна. Сделать такое очень не просто, если графика находится в одном статическом файле.

Ниже показан пример. Здесь окно содержит элемент Grid с единственной ячейкой. Эту ячейку совместно используют два элемента. Первый — элемент Path, который прорисовывает границу окна нестандартной формы и заливает ее градиентным узором, а второй — контейнер макета, в котором находится предназначенное для окна содержимое, перекрывающее элемент Path.

В данном случае в качестве контейнера макета служит элемент StackPanel, но в принципе это может быть и какой-то другой элемент (например, еще один Grid или Canvas для абсолютного позиционирования на основе координат). В этом элементе StackPanel находится кнопка закрытия (со знакомым значком X) и текст:

Окно нестандартной формы использующее элемент Path

Ключевым компонентом в данном примере является элемент Path, который создает фон. Он представляет собой простую векторную фигуру, которая состоит из ряда линий и дуг.

В текущий момент элемент Path имеет фиксированный размер (так же, как и окно), однако его размер несложно сделать изменяемым, поместив элемент в контейнер Viewbox. Еще улучшить этот пример можно, придав кнопке для закрытия окна более убедительный внешний вид — например, с помощью векторного значка X, прорисовываемого на красной поверхности. Хотя для представления кнопки и обработки связанных с ней событий мыши можно было бы воспользоваться и отдельным элементом Path, лучше поступить следующим образом: изменить стандартный элемент управления Button путем применения шаблона и затем сделать элемент Path, прорисовывающий значок X, частью этой измененной кнопки.

Перемещение окон нестандартной формы

В WPF эта задача решается гораздо легче. Здесь в любое время можно инициировать режим перетаскивания окна путем вызова метода Window.DragMove().

Итак, для того чтобы позволить пользователю перетаскивать окно необычной формы, которое было показано в предыдущем примере, необходимо просто добавить и обработать для окна (или того элемента в этом окне, который затем будет выполнять ту же роль, что и строка заголовка) событие MouseLeftButtonDown:

Теперь окно будет следовать за курсором мыши по экрану до тех пор, пока пользователь не отпустит кнопку мыши.

Изменение размеров окон нестандартной формы

Изменение размеров окна нестандартной формы — задача не из простых. Если форма окна хотя бы отчасти напоминает прямоугольник, наиболее простым подходом будет добавить в правом нижнем углу элемент захвата и изменения размера путем установки для свойства ResizeMode значения CanResizeWithGrip. Однако при размещении такого элемента предполагается, что окно имеет прямоугольную форму.

Например, в случае создания окна с эффектом скругленных краев за счет использования объекта Border, такой прием может и сработать. Элемент захвата и изменения размера появится в правом нижнем углу и, в зависимости от того, насколько скругленным был сделан этот угол, разместится в пределах поверхности окна, которому принадлежит. Но в случае создания окна более экзотической формы с применением, например, элемента Path, такой подход точно не сработает — элемент захвата и изменения размера "зависнет" в пустой области рядом с окном.

Прежде чем воспользоваться любым из этих подходов, нужно придумать способ для определения момента наведения пользователем курсора мыши на край окна. В WPF это легче всего сделать, разместив вдоль каждого края окна специальный элемент. Быть видимым этому элементу вовсе необязательно — на самом деле он даже может быть полностью прозрачным и позволять окну проглядывать сквозь него. Его единственная задачей будет перехват событий мыши.

Лучше всего на эту роль подходит элемент Rectangle толщиной в 5 единиц. Ниже приведен пример размещения этого элемента так, чтобы он позволял изменять размер с правой стороны окна со скругленными углами:

В данном случае элемент Rectangle размещается в верхней строке, но для свойства RowSpan получает значение 3. Благодаря этому он растягивается на все три строки и занимает всю правую сторону окна. В свойстве Cursor указывается тот курсор мыши, который должен появляться при наведении мыши на этот элемент. В данном случае это курсор изменения размера "запад-восток" — он имеет знакомую всем форму двухконечной стрелки, которая указывает вправо и влево.

Обработчики событий элемента Rectangle переключают окно в режим изменения размера, когда пользователь щелкает на краю. Здесь необходимо захватить мышь для обеспечения уверенности в том, что события будут продолжать поступать даже в случае перемещения мыши за счет перетаскивания с поверхности прямоугольника в какую-нибудь сторону. Захват мыши снимается, когда пользователь отпускает левую кнопку мыши:

Как работать с окнами в WPF/MVVM?

Как правильно реализовать работу окон в WPF/MVVM? Например, открывается главная форма ввода логина/пароли, после ввода данных открывается другая форма, в зависимости, какой пользователь и какая у него роль. Гуглил, подходящих примеров не нашел. Везде рассматриваются модальные окна. Покидайте примеры, пожалуйста. Которые выполнены строго в соответствии MVVM-паттерна. Благодарю.

Решения вопроса 0 Ответы на вопрос 4

Добрый день.
Вот старенькая статья с тремя разными вариантами реализации показа дочерних окон. Сейчас правда уже другой подход использую. Когда все дочерние View создаются как контролы. Дочернее окно одно и содержит ContentPresenter. При вызове метода показа дочернего окна в его DataContext помещается ViewModel. А как сопоставить View и ViewModel храниться в ресурсах:

При показе окна ContentPresenter сам, по типу объекта лежащего в DataContext создает View. Если не очень понятно, то могу скинуть развернутый пример.

Читайте также: