Вопрос

Хорошо, у меня есть контроль, у которого есть свойство Isediting, которое для аргумента имеет шаблон по умолчанию, который обычно является текстовым блоком, но когда Isediting True, он заменяет текстовое поле для редактирования на месте. Теперь, когда управление теряет фокус, если он все еще редактирует, он должен бросить режим редактирования и переключаться в шаблоне TextBlock. Довольно прямо, верно?

Подумайте о поведении переименования файла в Windows Explorer или на вашем рабочем столе (что то же самое, что я знаю ...) Это поведение, которое мы хотим.

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

Если вы вместо этого используете LostKeyboardFocus, хотя это решает проблему «другого FocusManager», теперь у вас есть новая: когда вы редактируете, и вы щелкните правой кнопкой мыши на текстовом поле, чтобы показать контекстное меню, потому что в контекстном меню есть клавиатура Фокус, ваш контроль теряет фокусировку на клавиатуре, выпадает из режима редактирования и закрывает контекстное меню, запутав пользователя!

Теперь я пытался установить флаг, чтобы игнорировать The LostKeyBoardFocus незадолго до того, как меню открывается, а затем использовал этот FIAG в событии LostKeyBoardFocus, чтобы определить, чтобы выгнать его из режима редактирования или нет, но если меню открыто, и я нажимаю в другом месте в Приложение, поскольку в самом контроле больше не было фокусировки клавиатуры (меню было), контроль никогда не получает другого события LostKeyBoardFocus, поэтому оно остается в режиме редактирования. (Возможно, мне придется добавить чек, когда меню закрывается, чтобы увидеть, что сфокусировано, затем вручную выгнать его из EditMode, если это не контроль. Это кажется многообещающим.)

Итак ... кто -нибудь имеет представление о том, как я могу успешно кодировать это поведение?

Отметка

Это было полезно?

Решение

ОК ... это было «весело», как в программистом. Настоящая боль в Кистере, чтобы выяснить, но с хорошей огромной улыбкой на лице, которую я сделал. (Пора получить какую -нибудь Icyhot для моего плеча, учитывая, что я сам так сильно похлопал!: P)

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

LostFocus это легко. Всякий раз, когда вы получаете это событие, установите IsEditing к ложку. Сделано и сделано.

Контекстные меню и утерянный фокус клавиатуры

LostKeyboardFocus немного сложнее, так как контекстное меню для вашего контроля может уволить, что на самом управлении (т.е. Когда открывается контекстное меню для вашего управления, контроль все еще имеет фокус, но он теряет фокус клавиатуры и, следовательно,, LostKeyboardFocus Пожары.)

Чтобы справиться с этим поведением, вы переворачиваете ContextMenuOpening (или обработайте событие) и установите флаг на уровне класса, указывающий, что меню открывается. (Я использую bool _ContextMenuIsOpening.) Тогда в LostKeyboardFocus Переопределить (или событие), вы проверяете этот флаг, и если он установлен, вы просто очищаете его и ничего не делаете. Если это не установлено, то это означает, что что -то, кроме контекстного меню IsEditing к ложку.

Уже открытые контекстные меню

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

Это на самом деле работает в наших интересах, так как это означает, что мы также получим другой LostKeyboardFocus Событие, но на этот раз флаг _contextmenuopening будет установлен на false, и точно так же, как описано выше, наш LostKeyboardFocus обработчик затем установит IsEditing Ложно, что именно то, что мы хотим. Я люблю случайность!

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

Предостережение: контекстные меню по умолчанию

Теперь есть также предостережение, что, если вы используете что -то вроде текстового поля и явно не установил свое собственное контекстное меню, то вы не получить ContextMenuOpening событие, которое удивило меня. Это легко исправить, однако, просто создав новое контекстное меню с теми же стандартными командами, что и контекстное меню по умолчанию (например, вырезать, копировать, вставить и т. Д.) И присвоить его текстовому поле. Это выглядит точно так же, но теперь вы получите событие, вам нужно установить флаг.

Тем не менее, даже там у вас есть проблема, как будто вы создаете контрольный контроль от третьей стороны, и пользователь этого элемента управления хочет иметь свое собственное контекстное меню, вы можете случайно установить свое на более высокий приоритет, и вы переопределите их !

Путь, так как текстовый поток на самом деле является элементом в IsEditing Шаблон для моего контроля, я просто добавил новый DP на внешний контроль, называемый IsEditingContextMenu который я затем привязан с текстовым полем с помощью внутреннего TextBox стиль, затем я добавил 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>

Обратите внимание, что вам нужно установить начальную связующую контекстные меню в стиле, а не непосредственно на текстовом поле, или иначе дататриг стиля заменяется непосредственно установленным значением, что делает триггер бесполезным, и вы возвращаетесь к квадрату, если человек использует «Нуль» для контекстного меню. (Если вы хотите подавить меню, вы все равно не будете использовать «null». Вы бы установили его в пустое меню, поскольку NULL означает «Использовать по умолчанию»)

Итак, теперь пользователь может использовать обычный ContextMenu собственность, когда IsEditing ложь ... они могут использовать IsEditingContextMenu Когда IsEditing верно, и если они не указали IsEditingContextMenu, Внутреннее значение, которое мы определили, используется для текстового поля. Поскольку контекстное меню текстового поля никогда не может быть нулевым, его ContextMenuOpening Всегда стреляет, и, следовательно, логика для поддержки этого поведения работает.

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

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

Отметка

Другие советы

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

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

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

Итак, у вас есть три варианта:

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

Разве не было бы просто проще:

    void txtBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        TextBox txtBox = (sender as TextBox);

        if (e.NewFocus is ContextMenu && (e.NewFocus as ContextMenu).PlacementTarget == txtBox)
        {
            return;
        }

        // Rest of code for existing edit mode here...
    }

Я не уверен в проблеме контекстного меню, но я пытался сделать что -то подобное и обнаружил, что использование захвата мыши дает вам (практически) поведение, которое вы хотите:

Смотрите ответ здесь: Как контроль может обрабатывать на щелчок мыши за пределами этого элемента управления?

Не уверен, но это может быть полезно. У меня была аналогичная проблема с редактируемой комбо -коробкой. Моя проблема заключалась в том, что я использовал метод переопределения OnLostFocus, который не вызывался. Исправлено, что я приложил обратный вызов к событию LostFocus, и все сработало все хорошо.

Я прошел здесь в поисках решения аналогичной проблемы: у меня есть ListBox который теряет фокус, когда ContextMenu Открывается, и я не хочу, чтобы это произошло.

Моим простым решением было настроить Focusable к False, оба для ContextMenu и это MenuItemS:

<ContextMenu x:Key="QueryResultsMenu" Focusable="False">
    <ContextMenu.Resources>
        <Style TargetType="MenuItem">
            <Setter Property="Focusable" Value="False"/>
        </Style>
    </ContextMenu.Resources>
    <MenuItem ... />
</ContextMenu>

Надеюсь, это поможет будущим искателям ...

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top