(Этот вопрос относится к еще одному , но достаточно отличается, так что я думаю, что он заслуживает размещения здесь.)
Вот (в большой степени) Window
:
<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. Я делаю это с возможной целью преобразования всей командной логики в более формально определенный уровень контроллера, но я пытаюсь проводить рефакторинг по одному шагу за раз.
Проблема, с которой я сталкиваюсь, связана с взаимодействием между командой в UserControl
ContextMenu
и командой CanExecute
, определенной в окне. Когда приложение запускается впервые и сохраненные задачи восстанавливаются в TaskStopwatches в окне, фактические элементы пользовательского интерфейса не выбираются. Если я сразу же щелкаю правой кнопкой мыши UserControl
в Window
в попытке выполнить команду 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, и я делаю его в свое свободное время в качестве обучающего опыта, поэтому я определенно не против изучения превосходной архитектуры решения.