Pregunta

(Esta pregunta está relacionada con otro , pero lo suficientemente diferente como para que creo que justifica la colocación aquí.)

Aquí hay una ventana (código) :

<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>

y aquí hay un UserControl (muy copiado):

<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>

A través de varias técnicas, un UserControl se puede agregar dinámicamente a la Ventana . Tal vez a través del botón en la ventana. Quizás, más problemáticamente, de un almacén de respaldo persistente cuando se inicia la aplicación.

Como se puede ver en xaml, he decidido que tiene sentido para mí intentar usar los Comandos como una forma de manejar varias operaciones que el usuario puede realizar con las Tareas . Estoy haciendo esto con el objetivo final de tener en cuenta toda la lógica de comando en una capa de Controlador definida más formalmente, pero estoy tratando de refactorizar un paso a la vez.

El problema que estoy encontrando está relacionado con la interacción entre el comando en el UserControl del ContextMenu y el comando CanExecute , definido en la ventana. Cuando la aplicación se inicia por primera vez y las tareas guardadas se restauran en TaskStopwatches en la ventana, no se seleccionan elementos de la IU real. Si luego vuelvo a hacer clic en un UserControl en la Window en un intento de ejecutar el comando ViewTaskProperties , el CanExecute el controlador nunca se ejecuta y el elemento de menú permanece deshabilitado. Si luego hago clic en algún elemento de la interfaz de usuario (por ejemplo, el botón) solo para dar un enfoque, los controladores de CanExecute se ejecutan con la propiedad Source de CanExecuteRoutedEventArgs establecida en el elemento UI que tiene el foco.

En cierto sentido, este comportamiento parece ser conocido: he aprendido que los menús enrutarán el evento a través del elemento que se enfocó por última vez para evitar enviar siempre el evento desde el elemento del menú. Lo que creo que me gustaría, sin embargo, es que la fuente del evento sea el control en sí mismo, o la tarea en la que el control se está envolviendo (pero Task no es un elemento, por lo que no pienses que puede ser una fuente).

Pensé que tal vez me estaba perdiendo la propiedad CommandTarget en el MenuItem en el UserControl , y mi primer pensamiento fue que quería el comando para provenir del UserControl, así que naturalmente intenté primero:

<MenuItem x:Name="mnuProperties" 
          Header="_Properties" 
          Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}" 
          CommandTarget="{Binding ElementName=This}" />

Esto falló como un enlace inválido. No estoy seguro de por qué. Entonces pensé, "Hmmm, estoy mirando el árbol, así que tal vez lo que necesito es una Fuente Relativa" Y probé esto:

<MenuItem x:Name="mnuProperties" 
          Header="_Properties" 
          Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}" 
          CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TaskStopwatchControl}}}" />

Eso también falló, pero cuando volví a mirar mi xaml, me di cuenta de que ContextMenu está en una propiedad del UserControl, no es un elemento secundario. Así que lo adiviné (y en este punto era una conjetura):

<MenuItem x:Name="mnuProperties" 
          Header="_Properties" 
          Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}" 
          CommandTarget="{Binding RelativeSource={x:Static RelativeSource.Self}}" />

Y eso también falló.

Un error de adivinar y verificar como este es suficiente para que me aleje y me dé cuenta de que me estoy perdiendo algún tipo de concepto fundamental, sin embargo. Entonces, ¿qué hago?

  1. ¿Mi entendimiento es correcto: la función de CommandTarget es correcta ya que proporciona un mecanismo para modificar la fuente de un comando?
  2. ¿Cómo puedo enlazar desde un MenuItem en UserControl.ContextMenu al propietario UserControl ? ¿O estoy haciendo algo mal simplemente porque percibo la necesidad de hacerlo?
  3. ¿Mi deseo de tener el contexto de un comando establecido por el elemento en el que se hizo clic para generar el menú contextual, a diferencia del elemento que tenía el foco antes del menú contextual, es incorrecto? Tal vez necesito escribir mi propio comando en lugar de usar el RoutedUICommand :

    private static RoutedUICommand viewTaskPropertiesCommand = new RoutedUICommand("View a task's details.", "ViewTaskProperties", typeof(TaskCommands));
    public static RoutedUICommand ViewTaskProperties
    {
        get { return viewTaskPropertiesCommand; }
    }
    
  4. ¿Hay algún defecto fundamental más profundo en mi diseño? Este es mi primer proyecto WPF significativo, y lo estoy haciendo en mi tiempo como experiencia de aprendizaje, por lo que definitivamente no me opongo a aprender una arquitectura de solución superior.

¿Fue útil?

Solución

1: Sí, CommandTarget controla desde dónde comienza RoutingCommand desde.

2: ContextMenu tiene un La propiedad PlacementTarget que permitirá el acceso a su 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 esto en todos los elementos de menú, puedes usar un estilo.

3 & amp; 4: Yo diría que tu deseo es razonable. Ya que el controlador de ejecución está en la ventana, no importa en este momento, pero si tuviera diferentes regiones de la aplicación, cada una con su propio controlador de ejecución para el mismo comando, importaría dónde estaba el foco.

Otros consejos

Una solución similar que encontré estaba usando la propiedad Tag del padre para capturar el 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 bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top