Хорошо ... это было "весело", как в "Программистах". Реальная боль в кайстере, чтобы понять, но с милой огромной улыбкой на лице, которую я сделал. (Пора взять IcyHot для моего плеча, учитывая, что я сам так сильно похлопал по нему!: P)
В любом случае, это многошаговая вещь, но удивительно простая, когда вы все выясните. Короткая версия - вам нужно использовать оба LostFocus
и LostKeyboardFocus
, а не одно или другое.
LostFocus
легко. Всякий раз, когда вы получаете это событие, установите IsEditing
в false. Сделано и сделано.
Контекстные меню и фокус потерянной клавиатуры
LostKeyboardFocus
немного сложнее, поскольку контекстное меню для вашего элемента управления может запускать его на самом элементе управления (т. Е. Когда открывается контекстное меню для вашего элемента управления, элемент управления все еще имеет фокус, но он теряет фокус клавиатуры и, таким образом, LostKeyboardFocus
пожаров.)
Чтобы обработать это поведение, вы переопределяете ContextMenuOpening
(или обрабатываете событие) и устанавливаете флаг уровня класса, указывающий, что меню открывается. (Я использую bool _ContextMenuIsOpening
.) Затем в переопределении (или событии) LostKeyboardFocus
вы проверяете этот флаг и, если он установлен, вы просто очищаете его и больше ничего не делаете. Однако, если он не установлен, это означает, что что-то кроме открытия контекстного меню приводит к тому, что элемент управления теряет фокус клавиатуры, поэтому в этом случае вы хотите установить для IsEditing
значение false.
Уже открытые контекстные меню
Теперь существует странное поведение: если контекстное меню для элемента управления открыто, и, таким образом, элемент управления уже потерял фокус клавиатуры, как описано выше, если вы щелкнете в другом месте приложения, прежде чем новый элемент управления получит фокус, ваш элемент управления получит Сначала фокус клавиатуры, но только на долю секунды, а затем мгновенно возвращает его новому элементу управления.
Это на самом деле работает в наших интересах, так как это означает, что мы также получим еще одно событие LostKeyboardFocus
, но на этот раз флаг _ContextMenuOpening будет установлен в значение false, и, как описано выше, наш обработчик LostKeyboardFocus
затем установит IsEditing
в ложь, что именно то, что мы хотим. Я люблю счастливое приключение!
Теперь просто переместил фокус на элемент управления, на который вы щелкнули, без предварительной настройки фокуса обратно на элемент управления, владеющий контекстным меню, а затем нам нужно было бы сделать что-то вроде перехвата события ContextMenuClosing
и проверки того, какой элемент управления будет если мы сфокусируемся на следующем, тогда мы установим IsEditing
на false, только если элемент управления, который скоро будет сфокусирован, не был тем, который породил контекстное меню, поэтому мы в основном избежали пули.
Предупреждение: контекстные меню по умолчанию
Теперь есть также предостережение о том, что если вы используете что-то вроде текстового поля и явно не устанавливаете в нем свое собственное контекстное меню, то вы не получаете событие ContextMenuOpening
, что удивило мне. Однако это легко исправить, просто создав новое контекстное меню с теми же стандартными командами, что и контекстное меню по умолчанию (например, вырезать, скопировать, вставить и т. Д.) И назначив его текстовому полю. Он выглядит точно так же, но теперь вы получаете событие, необходимое для установки флага.
Однако даже в этом случае у вас есть проблема, как будто вы создаете сторонний многократно используемый элемент управления, и пользователь этого элемента управления хочет иметь свое собственное контекстное меню, вы можете случайно установить для своего более высокий приоритет, и вы ' переопределим их!
Обходной путь был, так как текстовое поле на самом деле является элементом в шаблоне IsEditing
для моего элемента управления, я просто добавил новый DP на внешний элемент управления под названием IsEditingContextMenu
, который затем связал с текстовым полем через внутренний * Стиль 1057 *, затем я добавил DataTrigger
в этом стиле, который проверяет значение IsEditingContextMenu
на внешнем элементе управления, и если оно пустое, я устанавливаю меню по умолчанию, которое я только что создал выше и которое хранится в ресурсе.
Вот внутренний стиль для текстового поля (элемент с именем 'Root' представляет внешний элемент управления, который пользователь фактически вставляет в свой XAML) ...
<Style x:Key="InlineTextbox" TargetType="TextBox">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="ContextMenu" Value="{Binding IsEditingContextMenu, ElementName=Root}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBoxBase}">
<Border Background="White" BorderBrush="LightGray" BorderThickness="1" CornerRadius="1">
<ScrollViewer x:Name="PART_ContentHost" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEditingContextMenu, RelativeSource={RelativeSource AncestorType=local:EditableTextBlock}}" Value="{x:Null}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Cut" />
<MenuItem Command="ApplicationCommands.Copy" />
<MenuItem Command="ApplicationCommands.Paste" />
</ContextMenu>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
Обратите внимание, что вы должны установить начальную привязку контекстного меню в стиле, а не непосредственно в текстовом поле, иначе DataTrigger стиля будет заменен непосредственно установленным значением, что сделает триггер бесполезным, и вы сразу вернетесь в квадрат, есличеловек использует 'null' для контекстного меню.(Если вы ХОТИТЕ скрыть меню, вы все равно не будете использовать «ноль». Вы установите пустое меню, так как ноль означает «Использовать по умолчанию»)
Так что теперь пользователь может использоватьобычное свойство ContextMenu
, когда IsEditing
равно false ... они могут использовать IsEditingContextMenu
, когда IsEditing имеет значение true, и если они не указали IsEditingContextMenu
, для текстового поля используется определенное нами внутреннее значение по умолчанию.Поскольку контекстное меню текстового поля на самом деле никогда не может быть нулевым, его ContextMenuOpening
всегда срабатывает, и поэтому логика для поддержки этого поведения работает.
Как я уже сказал ... НАСТОЯЩАЯ боль в банке, вычисляющей все это,но, черт побери, если у меня нет действительно крутого чувства выполненного долга здесь.
Я надеюсь, что это поможет другим здесь с такой же проблемой.Не стесняйтесь отвечать здесь или пишите мне с вопросами.
Mark