2

Как можно обработать событие в MV
DelegateCommand.cs

 class DelegateCommand : ICommand
    {
        Action<object> execute;
        Func<object, bool> canExecute;
    public event EventHandler CanExecuteChanged;


    public DelegateCommand(Action&lt;object&gt; execute, Func&lt;object, bool&gt; canExecute)
    {
        this.execute = execute;
        this.canExecute = canExecute;
    }

    public DelegateCommand(Action&lt;object&gt; execute)
    {
        this.execute = execute;
        this.canExecute = this.AlwaysCanExecute;
    }

    public DelegateCommand()
    {

    }

    public void Execute(object param)
    {
        execute(param);
    }

    public bool CanExecute(object param)
    {
        return canExecute(param);
    }


    public void RaiseCanExecuteChanged()
    {
        if (CanExecuteChanged != null)
            CanExecuteChanged(this, EventArgs.Empty);
    }


    private bool AlwaysCanExecute(object param)
    {
        return true;
    }

}

MainWindow.xaml

                <ListView Width="1031" ItemsSource="{Binding GetTopItem}" 
                           ScrollViewer.VerticalScrollBarVisibility="Disabled" HorizontalAlignment="Left" ScrollViewer.HorizontalScrollBarVisibility="Hidden" 
                           Background="{x:Null}" BorderBrush="{x:Null}" Foreground="#DD000000">
                    <ListView.DataContext>
                        <vm:BookMainVM/>
                    </ListView.DataContext>
                    <ListView.ItemsPanel>
                        <ItemsPanelTemplate>
                            <StackPanel Orientation="Horizontal" HorizontalAlignment="Left"></StackPanel>
                        </ItemsPanelTemplate>
                    </ListView.ItemsPanel>
                &lt;ListView.ItemTemplate&gt;
                    &lt;DataTemplate&gt;

                        &lt;Border HorizontalAlignment=&quot;Left&quot; CornerRadius=&quot;25,25,25,25&quot;  BorderThickness=&quot;1&quot; BorderBrush=&quot;#FF474747&quot; Height=&quot;288&quot; Margin=&quot;0,0,0,0&quot; &gt;
                            &lt;Grid   Grid.Column=&quot;0&quot; Height=&quot;Auto&quot; Width=&quot;216&quot; Margin=&quot;-1&quot;&gt;
                                &lt;Grid.RowDefinitions&gt;
                                    &lt;RowDefinition Height=&quot;49*&quot;/&gt;
                                    &lt;RowDefinition Height=&quot;5*&quot;/&gt;
                                    &lt;RowDefinition Height=&quot;18*&quot;/&gt;
                                &lt;/Grid.RowDefinitions&gt;
                                &lt;Border CornerRadius=&quot;25,25,0,0&quot; RenderTransformOrigin=&quot;0.5,0.5&quot;&gt;
                                    &lt;Border.Background&gt;
                                        &lt;ImageBrush ImageSource=&quot;{Binding Img_src}&quot; Stretch=&quot;Fill&quot; /&gt;
                                    &lt;/Border.Background&gt;
                                &lt;/Border&gt;
                                &lt;Label Content=&quot;{Binding Rate}&quot;  Background=&quot;#FFBFBFBF&quot; RenderTransformOrigin=&quot;0.5,0.5&quot; Grid.Row=&quot;1&quot; FontSize=&quot;11&quot; FontFamily=&quot;Meiryo UI&quot; HorizontalContentAlignment=&quot;Center&quot;/&gt;
                                &lt;Border CornerRadius=&quot;0,0,25,25&quot;  Grid.Row=&quot;2&quot; Background=&quot;#FF8F8F8F&quot;&gt;
                                    &lt;TextBlock  Margin=&quot;6,0,0,0&quot; Text=&quot;{Binding Name}&quot; HorizontalAlignment=&quot;Left&quot; TextWrapping=&quot;Wrap&quot;  VerticalAlignment=&quot;Top&quot; Grid.Row=&quot;2&quot; Height=&quot;61&quot; Width=&quot;190&quot;/&gt;
                                &lt;/Border&gt;
                            &lt;/Grid&gt;
                        &lt;/Border&gt;
                    &lt;/DataTemplate&gt;
                &lt;/ListView.ItemTemplate&gt;
            &lt;/ListView&gt;

Как при нажатии на элемент коллекции нужно как-то обработать это в MV и там получить данные Text="{Binding Name}. Я уже перепробовал много способов и в итоге решил спросить

  • InputBindings пробовали? https://ru.stackoverflow.com/a/735522/218063 – Андрей NOP Jul 24 '21 at 14:21
  • это не подходит так как если я напишу это в ListView.ItemTemplate будет другой DataContext а именно нашего Элемента коллекции, а не VM – zelberman Jul 24 '21 at 14:27

1 Answers1

3

Например, так:

  1. В вашу VM для элемента списка положите свойство Activate типа ICommand, и пропишите в него экземпляр DelegateCommand

  2. В вашем XAML укажите InputBindings:

    <ListView.ItemTemplate>
        <DataTemplate>
            <Border ... >
                <Border.InputBindings>
                    <MouseBinding Gesture="LeftClick" Command="{Binding Activate}"/>
                </Border.InputBindings>
    

Вроде бы всё.


Пример структуры VM:

class MainVM
{
    public ObservableCollection<ItemVM> GetTopItem { get; }
public MainVM()
{
    var itemVMs = new List&lt;ItemVM&gt;();
    foreach (Item item in Model.GetItems())
        itemVMs.Add(new ItemVM(item));
    GetTopItem = new ObservableCollection&lt;ItemVM&gt;(itemVMs);
}

}

class ItemVM { Item item; public ItemVM(Item item) { this.item = item; Activate = new DelegateCommand(o => MessageBox.Show(item.Id.ToString())); }

public ICommand Activate { get; }

}


Если совсем не хочется иметь дело с несколькими VM, можно сделать так. Вместо ObservableCollection<ItemVM> выставить ObservableCollection<Item>. Activate положить в MainVM:

class MainVM
{
    public ObservableCollection<Item> GetTopItem { get; }
public MainVM()
{
    GetTopItem = new ObservableCollection&lt;Item&gt;(Model.GetItems());
    Activate = new DelegateCommand(o =&gt;
    {
        var item = (Item)o;
        MessageBox.Show(item.Id.ToString());
    });
}
public ICommand Activate { get; }

}

Подправить XAML согласно рекомендации @aepot:

<MouseBinding Gesture="LeftClick"
              Command="{Binding DataContext.Activate, RelativeSource={RelativeSource AncestorType=ListView}}"
              CommandParameter="{Binding}"/>

У меня такое работает, проверил.

VladD
  • 206,799
  • <DataTemplate>... ``` DataContext будет класса Item но не VM – zelberman Jul 24 '21 at 14:35
  • @yey_OK_cey: Я специально попробовал, нет, DataContext правильный, всё работает. – VladD Jul 24 '21 at 14:44
  • Минутку мы в ListView биндим ObservableCollection. Внутри <ListView.ItemTemplate> будет DataContext ObjectCollection то есть Item. А у меня должно обрабатывать MainVM – zelberman Jul 24 '21 at 14:50
  • @yey_OK_cey: Ну, я ж пишу «В вашу VM для элемента списка положите свойство Activate». – VladD Jul 24 '21 at 15:00
  • 1
    @yey_OK_cey не вижу проблемы прибиндиться и к главной вьюмодели Command="{Binding DataContext.Activate, RelativeSource={RelativeSource AncestorType=ListView}}" – aepot Jul 24 '21 at 15:02
  • @aepot: Так тоже будет работать, но будет сложность с тем, чтобы понять, по какому элементу был клик. – VladD Jul 24 '21 at 15:03
  • @VladD его можно передать в команду через CommandParameter="{Binding}" – aepot Jul 24 '21 at 15:07
  • @yey_OK_cey https://ru.stackoverflow.com/a/1122753/373567 – aepot Jul 24 '21 at 15:10
  • 1
    @aepot: Технически — да, это решаемая проблема. Но если нам уж приходится решать такую проблему, то возникает вопрос, правильно ли мы всё делаем. – VladD Jul 24 '21 at 15:10
  • Зависит от того, насколько код внутри команды завязан на старшую вьюмодель. Если зависимостей много, проще добраться до нее биндами, чем тащить все зависимости в элемент. – aepot Jul 24 '21 at 15:13
  • @aepot: Ну это вопрос вкуса и архитектуры. Обрабатывать активацию элемента по идее должен сам элемент, а не его контейнер, но YMMV. – VladD Jul 24 '21 at 18:27
  • @VladD - Ну, я ж пишу «В вашу VM для элемента списка положите свойство Activate». У меня Item это контейнер там только поля Name, Img_src и тд... Есть Моя VM где я получаю коллекцию. И именно в ней нужно обрабатывать. – zelberman Jul 25 '21 at 09:49
  • @yey_OK_cey: Мне кажется, логичнее было бы сделать Item более «активным» объектом, чтобы он сам себя обрабатывал. Но если очень не хочется, привязка, как предложил @aepot, сработает.` – VladD Jul 25 '21 at 09:52
  • @VladD надо при нажатии на Item как в браузере когда нажимаете по гиперссылке, открывалось новое окно, так что это как я думаю стоит отнести к VM. Она получит некоторые данные элемента, по этим данным будет запрос к бд, что бы сформировать страницу для выгрузки. – zelberman Jul 25 '21 at 17:43
  • @yey_OK_cey: Ну да, это операция для VM, но по идее не обязательно для VM всегда списка. Вы можете точно так же сделать Item отдельной VM. Или можете не делать, и так и так будет работать. – VladD Jul 25 '21 at 18:19
  • @VladD И все таки как обработать нажатие если не получается внутри DataTemplate – zelberman Jul 25 '21 at 19:23
  • @yey_OK_cey: Ну делаете привязку как в первом комментарии @aepot, кладёте команду в VM коллекции.` – VladD Jul 25 '21 at 20:50
  • @VladD что значит VM коллекции. Есть VM и Item. Можете написать пример, что бы было понятно – zelberman Jul 30 '21 at 08:14
  • @yey_OK_cey: Написал пример. А почему только одна VM? Для каждого Item-а своя ItemVM. – VladD Jul 30 '21 at 21:20
  • @VladD А если у меня только Item и MainVM, где есть метод возврата observablecollection. Так что у меня нету ItemVM. да и надобности не вижу. Как можно ли это сделать в MainVM. Item этот как обычная ссылка на которую я хочу нажать, получить некоторые данные и открыть в новой страничке уже полную информацию – zelberman Jul 31 '21 at 13:35
  • @yey_OK_cey: Надобность есть: раз пишете по MVVM, то логика Item'а должна по идее быть в его VM (то есть в ItemVM). По MVVM выставлять модельные объекты для View не правильно. Но если хочется как есть, а команду положить в MainVM, то привязывайтесь к команде как Command="{Binding DataContext.Activate, RelativeSource={RelativeSource AncestorType=ListView}}" CommandParameter="{Binding}", как советует @aepot.` – VladD Jul 31 '21 at 14:04
  • @VladD можете тогда показать пример с 1 VM и с моим или с вашим DelgateCommand. Я пытаюсь сделать, но не выходит – zelberman Jul 31 '21 at 18:21
  • @yey_OK_cey: Ну написал. Но я не очень рекомендую этот путь. – VladD Aug 01 '21 at 20:38
  • @VladD у меня вообще ничего не происходит когда я нажимаю. Можете скинуть весь xaml и DelegateCommand(если у вас не такой же как у меня) – zelberman Aug 03 '21 at 08:48
  • @yey_OK_cey: Ну это не раньше чем вечером. А вы не забыли в вашу MainVM положить команду? И поставьте в DelegateCommand.Execute breakpoint, чтобы посмотреть, приходит туда управление или нет. – VladD Aug 03 '21 at 09:07
  • @VladD я поменял немного код. Я понять только не могу почему (Item)o не работает и пишет что нельзя преобразовать в статический тип Visual Style Element.ListViewItem. Ну это в принципе не важно. Мне хватает что оно передает в object. Спасибо! – zelberman Aug 04 '21 at 11:20
  • @yey_OK_cey: Странно, а в каком смысле пишет? При компиляции или во время выполнения? Дайте точное сообщение об ошибке. И да, какой тип вашего GetTopItem? – VladD Aug 04 '21 at 11:29
  • @VladD Тип observablecollection. Item это просто класс который я написал. Xaml в вопросе. Пока что нет доступа к коду – zelberman Aug 04 '21 at 17:20
  • @VladD при компиляции и версия у меня .net 4.7.2 – zelberman Aug 04 '21 at 18:42
  • @yey_OK_cey: А можно точное сообщение об ошибке? – VladD Aug 04 '21 at 20:09
  • @VladD
    State Error CS0723 Cannot declare a variable of static type 'VisualStyleElement.ListView.Item' State Error CS0716 Cannot convert to static type 'VisualStyleElement.ListView.Item'
    – zelberman Aug 05 '21 at 09:37
  • Аааа! Хм. Ну укажите не (Item)o, а полное имя с namespace, что-то типа (YourApp.Model.Item)o. – VladD Aug 05 '21 at 09:42