ListBoxItem MouseBinding в ControlTemplate InputBinding не работает MVVM

Я надеюсь, что для кого-то это очевидная проблема. Я хочу, чтобы команда Edit запускалась, когда пользователь дважды щелкает элемент ListBoxItem внутри ListBox. Я делал это раньше в пользовательских элементах управления, но хочу сделать это непосредственно в VIEW, так как это достаточно простой ListBox. Но это не будет подключаться.

Вот список:

<ListBox SelectedItem="{Binding DataQuerySortSelected}"
         ItemContainerStyle="{StaticResource SortListItemStyle}"
         ItemsSource="{Binding DataQueryHolder.DataQuerySorts}">

    <ListBox.InputBindings>
        <KeyBinding Key="Delete" Command="{Binding DataQuerySortDelete}" />
    </ListBox.InputBindings>

    <ListBox.Resources>
        <Style TargetType="{x:Type ListBoxItem}">
            <Setter Property="AllowDrop" Value="True" />
            <EventSetter Event="PreviewMouseMove" Handler="DragDropListBoxItem_PreviewMouseMoveEvent" />
            <EventSetter Event="Drop" Handler="DragDropListBoxItem_Drop" />
        </Style>

    </ListBox.Resources>
</ListBox>

Обратите внимание, что привязка Delete Key на верхнем уровне работает просто отлично. Вот ссылочный стиль (введенный как отдельный ResourceDictionary, но вставка стиля в строку не имеет значения):

<Style x:Key="SortListItemStyle" TargetType="ListBoxItem">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListBoxItem">
                <Border Name="MainBorder">
                    <ContentPresenter>
                        <ContentPresenter.InputBindings>
                            <MouseBinding Gesture="LeftDoubleClick" Command="{Binding DataQuerySortEdit}" />
                        </ContentPresenter.InputBindings>
                    </ContentPresenter>

                    <Border.InputBindings>
                        <MouseBinding Gesture="LeftDoubleClick" Command="{Binding DataQuerySortEdit}" />
                    </Border.InputBindings>
                </Border>

                <ControlTemplate.Triggers>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter TargetName="MainBorder" Value="Yellow" Property="Background" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Я поместил мышиную привязку в двух местах, просто чтобы увидеть, если это имеет значение, но это не так. Там просто нет никакой проводки там происходит. Все остальное работает как положено. Если я создаю простую кнопку в представлении и указываю ее на команду DataQuerySortEdit, она работает как положено.

Я что-то пропустил? Спасибо за любую помощь.


РЕДАКТИРОВАТЬ: Просто добавив немного дополнительной информации в ответ на ответ J. Я присвоил Border of ControlTemplate привязку относительно ближайшего списка и дал списку имя, чтобы выходные данные подтвердили, что найдут его. Это было окно вывода:

System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''DataQuerySort' (HashCode=7641038)'. BindingExpression:Path=DataQuerySortEdit; DataItem='DataQuerySort' (HashCode=7641038); target element is 'MouseBinding' (HashCode=65119131); target property is 'Command' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''DataQuerySort' (HashCode=50439840)'. BindingExpression:Path=DataQuerySortEdit; DataItem='DataQuerySort' (HashCode=50439840); target element is 'MouseBinding' (HashCode=3649016); target property is 'Command' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''DataQuerySort' (HashCode=65588106)'. BindingExpression:Path=DataQuerySortEdit; DataItem='DataQuerySort' (HashCode=65588106); target element is 'MouseBinding' (HashCode=35717517); target property is 'Command' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''DataQuerySort' (HashCode=32836053)'. BindingExpression:Path=DataQuerySortEdit; DataItem='DataQuerySort' (HashCode=32836053); target element is 'MouseBinding' (HashCode=66172851); target property is 'Command' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''ListBox' (Name='SortListBox')'. BindingExpression:Path=DataQuerySortEdit; DataItem='ListBox' (Name='SortListBox'); target element is 'MouseBinding' (HashCode=28263486); target property is 'Command' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''ListBox' (Name='SortListBox')'. BindingExpression:Path=DataQuerySortEdit; DataItem='ListBox' (Name='SortListBox'); target element is 'MouseBinding' (HashCode=27134857); target property is 'Command' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''ListBox' (Name='SortListBox')'. BindingExpression:Path=DataQuerySortEdit; DataItem='ListBox' (Name='SortListBox'); target element is 'MouseBinding' (HashCode=7437765); target property is 'Command' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'DataQuerySortEdit' property not found on 'object' ''ListBox' (Name='SortListBox')'. BindingExpression:Path=DataQuerySortEdit; DataItem='ListBox' (Name='SortListBox'); target element is 'MouseBinding' (HashCode=58400697); target property is 'Command' (type 'ICommand')

Таким образом, вторая попытка связывания (я предполагаю, что попытка в Border InputBinding) прекрасно подходит для соответствующего списка, но все еще не может найти ICommand. Я попытался сделать относительную находку в окне, в Gird, содержащем список и т. Д., И все еще не могу передать его по проводам. Я также пытался, как упоминал J, поместить относительный поиск непосредственно в MouseBindings, и они приводят к тем же ошибкам.


EDIT2: вот команда и свойства в ViewModel, используя MVVMLight

public DataQuerySort DataQuerySortSelected
{
    get { return _DataQuerySortSelected; }
    set { NotifySetProperty(ref _DataQuerySortSelected, value, () => DataQuerySortSelected); }
}
private DataQuerySort _DataQuerySortSelected;


public RelayCommand DataQuerySortEdit
{
    get { return new RelayCommand(_DataQuerySortEdit, CanDataQuerySortEdit); }
}
private void _DataQuerySortEdit()
{
    DataQuerySortHolder = DataQuerySortSelected.Copy();
    DataQuerySortEditMode = EditMode.Edit;
}    
private bool CanDataQuerySortEdit() 
{ 
    return DataQuerySortSelected != null; 
}

Извлечение CanDataQuerySortEdit не имеет значения. Я знаю, что все работает, потому что, если я создаю кнопку и указываю на нее, она работает. Если я также создаю привязку ввода в ListBox для мыши, например, клавишу Delete, которая работает - до тех пор, пока я, конечно, щелкаю за пределами ListBoxItems.

EDIT3: вот часть самого представления, включая класс, текстовый текст и ресурсы. Я попытался сделать относительные привязки, чтобы быть "{x:Type Window}" и "{x:Type l:ToolkitWindowBase}". ToolkitWindowBase расширяет окно напрямую. FrmDataBrowserViewModel расширяет класс ToolkitViewModelBase, который расширяет ViewModelBase из MVVMLight:

<l:ToolkitWindowBase x:Class="GISToolkit.frmDataBrowser"  x:Name="mainWindow" Icon="Images/favicon.ico" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d"
    xmlns:l="clr-namespace:GISToolkit;assembly="
    xmlns:lc="clr-namespace:GISToolkit.Controls;assembly="
    xmlns:ls="clr-namespace:GISToolkit.Settings;assembly="
    xmlns:system="clr-namespace:System;assembly=mscorlib"
    xmlns:xctk="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
    xmlns:xctkp="clr-namespace:Xceed.Wpf.Toolkit.Primitives;assembly=Xceed.Wpf.Toolkit"
    Title="Solutions GIS Toolkit - Setup"  
    ResizeMode="CanResizeWithGrip" Foreground="White"
    l:ControlBox.HasMaximizeButton="False" l:ControlBox.HasMinimizeButton="False" l:ControlBox.HasCloseButton="False"
    Height="{Binding RunTimeHeight, Mode=TwoWay}" 
    Width="{Binding RunTimeWidth, Mode=TwoWay}" IsSettingsDirty="{Binding IsCurrentSettingsDirty}" IsEnabled="True">

    <Window.DataContext>
        <l:frmDataBrowserViewModel />
    </Window.DataContext>

    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Themes/DataBrowser.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

    ..................
<l:ToolkitWindowBase />

РЕДАКТИРОВАТЬ 4: На всякий случай, если кто-то еще продолжает листинг, сделайте мне одолжение, создайте новый проект WPF с именем "WpfMvvmApplication1" с одним окном с именем "BindingTestWindow" и моделью представления с именем "MainWindowViewModel". Тогда для окна, вставленного (должно быть просто вырезать / вставить, если вы не использовали разные имена для файлов / проекта):

<Window x:Class="WpfMvvmApplication1.BindingTestWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:WpfMvvmApplication1"
        Title="BindingTestWindow" Height="300" Width="300">

    <Window.DataContext>
        <l:BindingTestViewModel />
    </Window.DataContext>

    <Grid>
        <GroupBox Header="Sorting:" >
            <Grid>
                <ListBox Background="White" Name="SortListBox" ItemsSource="{Binding TestCollection}">

                    <ListBox.InputBindings>
                        <KeyBinding Key="Delete" Command="{Binding TestCommand}" />
                    </ListBox.InputBindings>

                    <ListBox.Resources>
                        <Style TargetType="{x:Type ListBoxItem}">
                            <Setter Property="AllowDrop" Value="True" />
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="ListBoxItem">
                                        <Border Name="MainBorder" Padding="0" Margin="0">
                                            <ContentPresenter />

                                            <Border.InputBindings>
                                                <MouseBinding Gesture="LeftDoubleClick" Command="{Binding TestCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
                                            </Border.InputBindings>
                                        </Border>

                                        <ControlTemplate.Triggers>
                                            <Trigger Property="IsSelected" Value="True">
                                                <Setter TargetName="MainBorder" Value="Yellow" Property="Background" />
                                            </Trigger>
                                        </ControlTemplate.Triggers>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </ListBox.Resources>
                </ListBox>
            </Grid>
        </GroupBox>
    </Grid>
</Window>

и для VIEWMODEL:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Windows.Input;

namespace WpfMvvmApplication1
{
    public class BindingTestViewModel : NotificationObject
    {
        public BindingTestViewModel()
        {
            TestCollection = new ObservableCollection<string>();
            for (int i = 0; i < 10; i++ )
                TestCollection.Add("test" + i);
        }

        public ICommand TestCommand { get { return new DelegateCommand(_TestCommand); } }

        private void _TestCommand() { System.Diagnostics.Debugger.Break(); }

        public ObservableCollection<string> TestCollection
        {
            get { return _TestCollection; }
            set 
            {
                _TestCollection = value;
                RaisePropertyChanged(() => TestCollection);
            }
        }
        private ObservableCollection<string> _TestCollection;
    }

    public class DelegateCommand : ICommand
    {
        private readonly Action _command;
        private readonly Func<bool> _canExecute;
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public DelegateCommand(Action command, Func<bool> canExecute = null)
        {
            if (command == null)
                throw new ArgumentNullException();
            _canExecute = canExecute;
            _command = command;
        }

        public void Execute(object parameter)
        {
            _command();
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute();
        }

    }

    public class NotificationObject : INotifyPropertyChanged
    {
        protected void RaisePropertyChanged<T>(Expression<Func<T>> action)
        {
            var propertyName = GetPropertyName(action);
            RaisePropertyChanged(propertyName);
        }

        private static string GetPropertyName<T>(Expression<Func<T>> action)
        {
            var expression = (MemberExpression)action.Body;
            var propertyName = expression.Member.Name;
            return propertyName;
        }

        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

В этом нет ничего другого. Это дает мне ошибку привязки, когда inputbinding находится внутри list boxtiem, а не когда, скажем, в самом list box. Похоже, это должно работать, поскольку в выводе говорится, что он находит окно в FindAncestor.

3 ответа

Решение

Большое спасибо J King за то, что там висел. Но похоже, что работа в XAML не работает. В итоге я сделал это в коде VIEW (надеюсь, он кому-нибудь поможет):

public BindingTestWindow()
{
    InitializeComponent();

    SortListBox.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
}

private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
    if (SortListBox.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
    {
        BindingTestViewModel vm = (BindingTestViewModel)this.DataContext;

        for(int i = 0; i < SortListBox.Items.Count; i++)
        {
            ListBoxItem lbi = (ListBoxItem)SortListBox.ItemContainerGenerator.ContainerFromIndex(i);
            lbi.InputBindings.Clear();
            lbi.InputBindings.Add(new InputBinding(vm.TestCommand, new MouseGesture(MouseAction.LeftDoubleClick)));
        }
    }
}  

Чтобы манипулировать фактическими элементами списка (а не их содержимым через ListBox.Items), мы должны использовать ItemContainerGenerator. Но для того, чтобы сделать это, вы должны ждать, пока он будет полностью сгенерирован, и, следовательно, необходим обработчик событий. Это не красиво, но это работает.

Эрни

Попробуйте изменить привязку команды к следующему коду. Элемент списка - это визуальный дочерний элемент списка, и вам необходимо правильно подключить ваш текст данных. Я думаю. Обратите внимание на тип окна. Измените его на тот, где ваш верхний уровень datacontext, где команда объявлена ​​и список существует. Т.е.: usercontrol, window и т.д...

<MouseBinding Gesture="LeftDoubleClick" Command="{Binding DataQuerySortEdit, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}}" /> 

Возможно, вы сможете отладить это, глядя на ваше окно вывода, когда вы запустите свое приложение, оно покажет вам ошибки связывания, подобные этой.

РЕДАКТИРОВАТЬ 1: Mvvm Light Event, чтобы командовать

Хорошо, я полностью упустил из виду тот факт, что вы подаете в суд на Mvvm Light. MVVM-Light имеет встроенную функцию для привязки событий к командам в вашей модели представления.

Добавьте следующее в ваши пространства имен Windows:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="http://www.galasoft.ch/mvvmlight"

Теперь измените вашу рамку внутри элемента списка, чтобы включить следующее:

<Border ...>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="LeftDoubleClick">
            <cmd:EventToCommand Command="{Binding DataContext.DataQuerySortEdit, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Border>

Mvvm light построил это для того, чтобы облегчить именно то, что вы ищете. Это позволяет вам принять любое событие и привязать к нему команду. Я использую это для проверки формы через события lostfocus элементов управления и т. Д.

Убедитесь, что в вашем проекте есть следующие ссылки:

  • GalaSoft.MvvmLight.WPF45
  • GalaSoft.MvvmLight.Extras.WPF45
  • Microsoft.Expression.Interactions

Надеюсь это поможет

РЕДАКТИРОВАТЬ 3:

Просто последнее усилие, что делать, если вы используете следующее:

Он добавляет Datacontext к имени команды.

Я потратил довольно много времени, возясь с этой проблемой. Удалось смешать несколько ответов вместе с результатом, который работает. Привязки ввода в ListBox ведут себя странно. Некоторые работают над самим ListBox, например MiddleClick, другие должны быть реализованы на элементах. J Kings Привязка к DataContext сделала свое дело в ContentPresenter LisBoxItemStyle.

 <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ListBoxItem}">

                    <Border x:Name="Bd" 
                            BorderBrush="{TemplateBinding BorderBrush}" 
                            BorderThickness="{TemplateBinding BorderThickness}" 
                            Background="{TemplateBinding Background}" 
                            Padding="{TemplateBinding Padding}" 
                            SnapsToDevicePixels="true">

                        <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                          SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                                          VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                            <ContentPresenter.InputBindings>
                                <MouseBinding Gesture="Ctrl+MiddleClick" 
                                              Command="{Binding  DataContext.MiddleClickCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
                                <MouseBinding Gesture="Ctrl+RightClick" 
                                              Command="{Binding DataContext.CtrlRightClickCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
                                <MouseBinding MouseAction="RightClick" 
                                              Command="{Binding DataContext.RightClickCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
                            </ContentPresenter.InputBindings>
                        </ContentPresenter>
                    </Border>
                    <ControlTemplate.Triggers>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsMouseOver" Value="True"/>
                            </MultiTrigger.Conditions>
                            <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.MouseOver.Background}"/>
                            <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.MouseOver.Border}"/>
                        </MultiTrigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="Selector.IsSelectionActive" Value="False"/>
                                <Condition Property="IsSelected" Value="True"/>
                            </MultiTrigger.Conditions>
                            <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Background}"/>
                            <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Border}"/>
                        </MultiTrigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="Selector.IsSelectionActive" Value="True"/>
                                <Condition Property="IsSelected" Value="True"/>
                            </MultiTrigger.Conditions>
                            <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Background}"/>
                            <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Border}"/>
                        </MultiTrigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter Property="TextElement.Foreground" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>

                </ControlTemplate>
            </Setter.Value>
        </Setter>  
Другие вопросы по тегам