Como definir CommandTarget para MenuItem dentro de um ContextMenu?
-
03-07-2019 - |
Pergunta
(Esta questão está relacionada com a outro , mas o suficiente diferente que eu acho que ele merece colocação aqui.)
Aqui está uma (fortemente cortado) 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>
e aqui está uma (fortemente cortado) 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>
Através de várias técnicas, um UserControl
podem ser adicionados dinamicamente ao Window
. Talvez através do botão na janela. Talvez, ainda mais problemática, a partir de um armazenamento de backup persistente quando a aplicação é iniciada.
Como pode ser visto a partir do XAML, eu decidi que não faz sentido para mim para tentar usar comandos como uma maneira de lidar com várias operações que o usuário pode executar com Task
s. Estou fazendo isso com o objetivo eventual de factoring toda a lógica de comando em uma camada de controlador mais formalmente definida, mas eu estou tentando refatorar um passo de cada vez.
O problema que eu estou encontrando está relacionada com a interação entre o comando em UserControl
do ContextMenu
e CanExecute
do comando, definido na janela. Quando o aplicativo primeiro começa e as tarefas salvas são restaurados em TaskStopwatches na janela, há elementos de UI reais são selecionados. Se eu logo em seguida r clique num UserControl
na Window
em uma tentativa de executar o comando ViewTaskProperties
, o manipulador CanExecute
nunca se eo item de menu permanece desativado. Se eu clique em algum elemento da interface do usuário (por exemplo, o botão) apenas para dar algo de foco, os manipuladores CanExecute
são executados com Fonte conjunto de propriedades da CanExecuteRoutedEventArgs
ao elemento UI que tem o foco.
Em alguns aspectos, este comportamento parece ser known-- Aprendi que menus irá encaminhar o evento através do elemento que o último foco tinha de evitar sempre que envia o evento a partir do item de menu. O que eu acho que eu gostaria, no entanto, é para a origem do evento a ser o próprio controle, ou a tarefa que o controle está envolvendo-se em torno (mas Task
não é um elemento, então eu não acho que ele pode ser uma fonte).
Eu pensei que talvez eu estava perdendo a propriedade CommandTarget
na MenuItem
na UserControl
, e meu primeiro pensamento foi que eu queria o comando vir do UserControl, então naturalmente eu tentei primeiro:
<MenuItem x:Name="mnuProperties"
Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding ElementName=This}" />
Esta falha como uma ligação inválida. Eu não sei por que. Então eu pensei, "Hmmm, eu estou olhando para a árvore, então talvez o que eu preciso é um RelativeSource" e eu tentei este:
<MenuItem x:Name="mnuProperties"
Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TaskStopwatchControl}}}" />
Isso também não, mas quando eu olhei para o meu xaml novamente, eu percebi que o ContextMenu
está em uma propriedade do UserControl, não é um elemento filho. Então eu imaginei (e neste ponto ele era uma suposição):
<MenuItem x:Name="mnuProperties"
Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding RelativeSource={x:Static RelativeSource.Self}}" />
E, que também falhou.
Um palpite-e-check falhou como este é suficiente para me fazer recuar e perceber que eu estou sentindo falta de algum tipo de conceito fundamental aqui, no entanto. Então, o que eu faço?
- É a minha re compreensão: o papel da
CommandTarget
correto em que este fornece um mecanismo para modificar a fonte de um comando ?
- Como faço para ligar a partir de um
MenuItem
emUserControl.ContextMenu
aoUserControl
possuir? Ou estou fazendo algo errado, simplesmente porque percebo a necessidade de? -
É o meu desejo de ter no contexto de um conjunto de comandos pelo elemento que foi clicado para gerar o menu de contexto, em oposição ao elemento que tinha o foco antes do menu de contexto, incorreta? Talvez eu preciso escrever meu próprio comando em vez de usar o
RoutedUICommand
:private static RoutedUICommand viewTaskPropertiesCommand = new RoutedUICommand("View a task's details.", "ViewTaskProperties", typeof(TaskCommands)); public static RoutedUICommand ViewTaskProperties { get { return viewTaskPropertiesCommand; } }
-
Existe alguma falha mais profunda fundamental no meu projeto? Este é meu primeiro projeto significativo WPF, e eu estou fazendo isso no meu próprio tempo como uma experiência de aprendizagem, então eu definitivamente não sou opposed para a aprendizagem de uma arquitetura de solução superior.
Solução
1: Sim, CommandTarget controles onde o RoutedCommand começa roteamento de.
2: ContextMenu tem um PlacementTarget propriedade que permitirá o acesso a seu UserControl:
<MenuItem x:Name="mnuProperties" Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding PlacementTarget,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}}"/>
Para evitar repetir isso em cada MenuItem você poderia usar um estilo.
3 & 4: Eu diria que o seu desejo é razoável. Desde o manipulador Executar está na Janela não importa agora, mas se você tivesse diferentes regiões do aplicativo, cada um com seu próprio Executar manipulador para o mesmo comando, seria importa onde o foco foi.
Outras dicas
uma solução semelhante que encontrei foi usando a propriedade Tag do pai para pegar o datacontext:
<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>