Окно, которое ведет себя как модально, так и немодально - PullRequest
6 голосов
/ 14 мая 2010

Я хочу создать окно WPF, которое ведет себя как модальное диалоговое окно, в то же время облегчая отдельные операции в некоторых других окнах того же приложения. Пример такого поведения можно увидеть в Adobe Photoshop, который предлагает несколько диалогов, которые позволяют пользователю использовать инструмент «Пипетка» для выбора объектов на изображении, отключая практически все другие функции приложения.

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

Ответы [ 4 ]

2 голосов
/ 14 мая 2010

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

Я объясню четыре специфичных для WPF способа сделать это:

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

  • Вы можете добавить MergedDictionary к ResourceDictionary вашего окна, который заставляет все TextBoxes стать TextBlocks, все кнопки становятся отключенными и т. Д., За исключением случаев, когда они явно переопределяются с помощью пользовательских вложенных свойств. Поэтому вместо того, чтобы циклически переключаться между всеми пользовательскими интерфейсами, выборочно включающими / отключающими, вы просто добавляете или удаляете объект из коллекции MergedDictionaries.

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

  • Использование привязки данных и стилей для включения / отключения отдельных элементов управления, а не итерация по ним

Сведения о замене окна изображением окна

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

<Window ...>
  <Grid>
    <ContentPresenter x:Name="OriginalContent" />
    <Rectangle>
      <Rectangle.Fill>
        <VisualBrush Visual="{Binding ElementName=OriginalContent}" />
      </Rectangle.Fill>
    </Rectangle>
  </Grid>
</Window>

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

<Window ...>
  <my:SelectiveDisabler>
    <Grid x:Name="LayoutRoot"> ... </Grid>  <!-- Original content -->
  </my:SelectiveDisabler>
</Window>

Добавив обработчики событий мыши в Rectangle и вызвав VisualTreeHelper.HitTest в ContentPresenter, чтобы определить, какой объект был нажат в исходном контенте. С этого момента вы можете проигнорировать событие мыши, перенаправить его на исходное содержимое для обработки или, в случае элемента управления пипеткой или функции выбора объекта, просто извлечь нужные объекты / информацию.

Подробности подхода MergedDictionary

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

Наивный способ сделать это состоит в том, чтобы просто создать неявные стили в объединенном ResourceDictionary, чтобы все TextBoxes отображались как TextBlocks, все кнопки выглядели как Borders и т. Д. Это не очень хорошо работает, потому что любой TextBox со своим собственным стилем или ControlTemplate явно установлен может пропустить обновления. Кроме того, вы не можете получить все объекты по желанию, и нет никакого способа легко удалить события Commands или Click из кнопок, потому что они явно указаны, и стиль не переопределит это.

Лучшим способом для этого является использование стилей в объединенном ResourceDictionary для установки присоединенного свойства, а затем использование выделенного кода в PropertyChangedCallback для обновления свойств, которые вы действительно хотите изменить. Прикрепленное свойство «ModalMode», если установлено значение «истина», сохранит все локальные значения и привязки для ряда свойств (Template, Command, Click, IsEnabled и т. Д.) В частном объекте DependencyProperty объекта, а затем перезапишет их стандартными значениями. , Например, свойство Command кнопки будет временно установлено равным нулю. Когда присоединенное свойство «ModalMode» становится ложным, все исходные локальные значения и привязки копируются обратно из временного хранилища, и временное хранилище очищается.

Этот метод предоставляет удобный способ выборочно включать / отключать части вашего пользовательского интерфейса, просто добавляя другое присоединенное свойство «IgnoreModalMode». Вы можете вручную установить это значение в True для любых элементов UIE, к которым вы не хотите применять изменения ModalMode. Затем ваш ModalMode PropertyChangedCallback проверяет это и, если оно истинно, ничего не делает.

Подробная информация о подходе InputManager

Если вы захватите мышь, вы можете получить координаты мыши, независимо от того, куда она перемещена. Переведите их в экранные координаты, используя CompositionTarget.TransformToDevice (), затем используйте CompositionTarget.TransformFromDevice () в каждом окне-кандидате. Если координаты мыши находятся в границах, протестируйте отключенное окно (это все еще можно сделать, даже если окно отключено), и если вам нравится объект, по которому щелкнул пользователь, используйте InputManager.ProcesInput, чтобы вызвать событие мыши обрабатывается в другом окне точно так же, как если бы оно не было отключено.

Подробности использования привязки данных

Вы можете использовать стили для привязки свойства IsEnabled кнопок, MenuItems и т. Д. К статическому значению, например:

<Setter Property="IsEnabled" Value="{Binding NonModal, Source={x:Static local:ModalModeTracker.Instance}}" />

Теперь по умолчанию все элементы с этими стилями автоматически отключаются, когда свойство NonModal становится ложным. Однако любой отдельный элемент управления может быть отменен с помощью IsEnabled="true", чтобы оставаться включенным даже в вашем модальном режиме. Более сложные привязки могут быть выполнены с помощью MultiBinding и EDF ExpressionBinding, чтобы установить любые правила, которые вы хотите.


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

В любом случае, WPF делает это намного проще, чем это было во времена WinForms. Разве ты не любишь силу WPF?

0 голосов
/ 17 мая 2010

Я считаю, что лучший подход для решения этой проблемы - это использование подхода InputManager, упомянутого ранее. Этот шаблон проектирования позволяет вам подключать команды к кнопкам панели инструментов / пунктам меню и т. Д., И каждый из них будет вызывать обработчик CanExecute, который вы укажете для своей команды. В этом обработчике вы должны установить команду не включенной, если ваше немодальное окно всегда было сверху.

http://msdn.microsoft.com/en-us/library/ms752308.aspx

0 голосов
/ 14 мая 2010

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

0 голосов
/ 14 мая 2010

То, что вы ищете, похоже на Многодокументный интерфейс . Это не доступно по умолчанию в WPF, но есть некоторые попытки поддержать это, и бесплатно и реклама .

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

...