Как установить CommandTarget для MenuItem внутри ContextMenu?

StackOverflow https://stackoverflow.com/questions/616206

  •  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>
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top