Как установить CommandTarget для MenuItem внутри ContextMenu?
-
03-07-2019 - |
Вопрос
(Этот вопрос относится к еще один , но достаточно отличающийся, так что я думаю, что это требует размещения здесь.)
Вот (в большой степени) Окно
:
<Window x:Class="Gmd.TimeTracker2.TimeTrackerMainForm"
xmlns:local="clr-namespace:Gmd.TimeTracker2"
xmlns:localcommands="clr-namespace:Gmd.TimeTracker2.Commands"
x:Name="This"
DataContext="{Binding ElementName=This}">
<Window.CommandBindings>
<CommandBinding Command="localcommands:TaskCommands.ViewTaskProperties"
Executed="HandleViewTaskProperties"
CanExecute="CanViewTaskPropertiesExecute" />
</Window.CommandBindings>
<DockPanel>
<!-- snip stuff -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- snip more stuff -->
<Button Content="_Create a new task" Grid.Row="1" x:Name="btnAddTask" Click="HandleNewTaskClick" />
</Grid>
</DockPanel>
</Window>
и вот (в большой степени оторванный) UserControl
:
<UserControl x:Class="Gmd.TimeTracker2.TaskStopwatchControl"
xmlns:local="clr-namespace:Gmd.TimeTracker2"
xmlns:localcommands="clr-namespace:Gmd.TimeTracker2.Commands"
x:Name="This"
DataContext="{Binding ElementName=This}">
<UserControl.ContextMenu>
<ContextMenu>
<MenuItem x:Name="mnuProperties" Header="_Properties" Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="What goes here?" />
</ContextMenu>
</UserControl.ContextMenu>
<StackPanel>
<TextBlock MaxWidth="100" Text="{Binding Task.TaskName, Mode=TwoWay}" TextWrapping="WrapWithOverflow" TextAlignment="Center" />
<TextBlock Text="{Binding Path=ElapsedTime}" TextAlignment="Center" />
<Button Content="{Binding Path=IsRunning, Converter={StaticResource boolToString}, ConverterParameter='Stop Start'}" Click="HandleStartStopClicked" />
</StackPanel>
</UserControl>
С помощью различных методов UserControl
можно динамически добавлять в Window
. Возможно через кнопку в окне. Возможно, более проблематично из постоянного резервного хранилища при запуске приложения. Р>
Как видно из xaml, я решил, что для меня имеет смысл попытаться использовать команды как способ обработки различных операций, которые пользователь может выполнять с Task
s. Я делаю это с конечной целью преобразования всей командной логики в более формально определенный уровень контроллера, но я пытаюсь проводить рефакторинг по одному шагу за раз.
Проблема, с которой я сталкиваюсь, связана с взаимодействием между командой в ContextMenu
UserControl
и командой CanExecute
команды, определяется в окне. Когда приложение запускается впервые и сохраненные задачи восстанавливаются в TaskStopwatches в окне, фактические элементы пользовательского интерфейса не выбираются. Если я сразу же щелкну правой кнопкой мыши UserControl
в Окно
в попытке выполнить команду ViewTaskProperties
, CanExecute
Обработчик никогда не запускается и пункт меню остается отключенным. Если я затем щелкаю некоторый элемент пользовательского интерфейса (например, кнопку) просто для того, чтобы сосредоточить внимание, то обработчики CanExecute
запускаются со свойством Source CanExecuteRoutedEventArgs
, установленным на элемент пользовательского интерфейса это имеет фокус.
В некотором отношении это поведение, похоже, известно - я узнал, что меню будут направлять событие через элемент, который был последним и имел фокус, чтобы всегда не отправлять событие из пункта меню. Однако я думаю, что мне хотелось бы, чтобы источником события был сам элемент управления или Задача, в которую оборачивается элемент управления (но Task
не является Элементом, поэтому я не думаю, что это может быть источником).
Я подумал, что, возможно, мне не хватает свойства CommandTarget
в MenuItem
в UserControl
, и я сначала подумал, что мне нужно Команда приходит из UserControl, поэтому, естественно, я сначала попытался:
<MenuItem x:Name="mnuProperties"
Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding ElementName=This}" />
Это не удалось как недопустимая привязка. Я не уверен почему. Затем я подумал: «Хм, я смотрю вверх по дереву, поэтому, возможно, мне нужен RelativeSource». и я попробовал это:
<MenuItem x:Name="mnuProperties"
Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TaskStopwatchControl}}}" />
Это также не удалось, но когда я снова посмотрел на свой xaml, я понял, что ContextMenu
находится в свойстве UserControl, это не дочерний элемент. Поэтому я догадался (и на данный момент это было предположение):
<MenuItem x:Name="mnuProperties"
Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding RelativeSource={x:Static RelativeSource.Self}}" />
И это тоже не удалось.
Одной неудачной попытки угадать и проверить, как это, достаточно, чтобы заставить меня отступить и понять, что я здесь упускаю какую-то фундаментальную концепцию. Так что мне делать?
<Ол> CommandTarget
в том, что она предоставляет механизм для изменения источника команды? MenuItem
в UserControl.ContextMenu
с UserControl
? Или я что-то не так делаю просто потому, что чувствую необходимость? Правильно ли мое желание иметь контекст команды, установленной элементом, по которому щелкнули для создания контекстного меню, в отличие от элемента, который имел фокус перед контекстным меню? Возможно, мне нужно написать собственную команду вместо использования RoutedUICommand
:
private static RoutedUICommand viewTaskPropertiesCommand = new RoutedUICommand("View a task's details.", "ViewTaskProperties", typeof(TaskCommands));
public static RoutedUICommand ViewTaskProperties
{
get { return viewTaskPropertiesCommand; }
}
Есть ли в моем дизайне какой-то более глубокий недостаток? Это мой первый значительный проект WPF, и я делаю это в свое свободное время.
Решение
1: Да, CommandTarget контролирует, откуда RoutedCommand начинает маршрутизацию.
2: ContextMenu имеет PlacementTarget свойство, которое позволит получить доступ к вашему UserControl:
<MenuItem x:Name="mnuProperties" Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding PlacementTarget,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}}"/>
Чтобы не повторять это в каждом элементе меню, вы можете использовать стиль.
3 & amp; 4: я сказал бы, что Ваше желание разумно. Поскольку обработчик Execute находится в окне, это сейчас не имеет значения, но если бы у вас были разные области приложения, каждый со своим собственным обработчиком Execute для одной и той же команды, было бы важно, где находится фокус.
Другие советы
Аналогичное решение, которое я нашел, использовало свойство Tag родительского объекта для получения текста данных:
<Grid Tag="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<Grid.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem
Header="{Binding Path=ToolbarDelete, Mode=Default, Source={StaticResource Resx}}"
Command="{Binding RemoveCommand}"
CommandParameter="{Binding DataContext.Id, RelativeSource={RelativeSource TemplatedParent}}"/>
</ContextMenu>
</Grid.ContextMenu>
<TextBlock Text="{Binding Name}" Padding="2" />
</Grid>