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 Tasks. 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?

  1. É a minha re compreensão: o papel da CommandTarget correto em que este fornece um mecanismo para modificar a fonte de um comando
  2. ?
  3. Como faço para ligar a partir de um MenuItem em UserControl.ContextMenu ao UserControl possuir? Ou estou fazendo algo errado, simplesmente porque percebo a necessidade de?
  4. É 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; }
    }
    
  5. 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.

Foi útil?

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>
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top