WPF — привязка событий к методам класса Item в ItemControl

Я немного новичок в WPF/XAML (хотя я изучил С#) и был бы очень признателен за любую помощь в моем вопросе. Я некоторое время просматривал другие сообщения и гуглил, но я не могу найти удовлетворительного или подробного ответа, который заставил бы меня продолжить мой проект. Подробности см. ниже. Заранее спасибо!

Задача

У меня есть класс Tile, который состоит из нескольких свойств и обработчика событий. У меня также есть ItemControl с кнопкой (как в DataTemplate) и ItemSource которого представляет собой набор плиток.

Теперь я хочу связать событие «Click» кнопки, чтобы вызвать метод обработчика событий, определенный в классе Tile.

Другими словами, когда я нажимаю кнопку любого элемента в ItemControl, должен вызываться обработчик метода соответствующего экземпляра Tile (из коллекции). Как бы я решил эту проблему?

Ниже приведен весь код, упрощенный, чтобы не отвлекать:

XAML

<Window x:Class="SampleWPF.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="300" Width="300">

    <!-- Make a ItemControl for "Tile"s. -->
    <ItemsControl x:Name="TileList">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <!-- Wire the click event of this Button 
                 to event handler in the Tile class. -->
                <Button Content="Show"></Button>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

</Window>

КОДЕКС

namespace SampleWPF
{
    public partial class MainWindow : Window
    {
        ObservableCollection<Tile> tiles;

        public MainWindow()
        {
            InitializeComponent();

            // Adding some sample data for testing.
            tiles = new ObservableCollection<Tile>();
            tiles.Add(new Tile("Item 1"));
            tiles.Add(new Tile("Item 2"));

            TileList.ItemsSource = tiles;
        }
    }

    public class Tile : INotifyPropertyChanged
    {
        public string Data
        { /* Accessors and PropertyNotifiers */ }

        public Tile(string data)
        { /* Initializing and assigning "Data" */ }

        // INotifyPropertyChanged implementation...
        // { ... }

        // This event handler should be bound to the Button's "Click" event
        // in the DataTemplate of the Item.
        public void ShowButton_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Viewing item from: " + this.Data);
        }
    }
}

Следовательно, если я нажму первую кнопку «Показать», вывод должен быть «Просмотр элемента из: Элемент 1», а если я нажму вторую кнопку «Показать», вывод должен быть «Просмотр элемента из: Элемент 2».

Итак, каков рекомендуемый/эффективный способ сделать это? Мой код не соответствует этому требованию?


person crazyGamer    schedule 11.12.2014    source источник
comment
AnyAnswer.SatisfyYou() ? пометить как ответ : попросить о помощи;   -  person MajkeloDev    schedule 12.12.2014


Ответы (3)


Позвольте мне начать с некоторых основ: Не назначайте источник элемента в codeBehind — используйте Binding следующим образом:

<Controll ItemSource="{Binding MyObservableCollection}"/>

Есть много способов добиться этого. Я думаю, что использование this.Data не лучшее решение для этого. Например, если у вашего хвоста есть идентификатор или что-то в этом роде, вы можете назначить этот идентификатор кнопке CommandParameter, как показано ниже.

<Button CommanParameter="{Binding Path=ID}" Click="ShowButton_Click"/>

И затем в событии Your button_click вы можете «поймать» это следующим образом:

public void ShowButton_Click(object sender, EventArgs e)
{
  int ID = int.Parse(((Button)sender).CommandParameter.ToString());
}

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

Чтобы использовать эту привязку, вам необходимо установить DataContext. Вы можете сделать это в ctor следующим образом:

 public MainWindow()
    {
        InitializeComponent();

        // Adding some sample data for testing.
        tiles = new ObservableCollection<Tile>();
        tiles.Add(new Tile("Item 1"));
        tiles.Add(new Tile("Item 2"));
        // below You are setting a datacontext of a MainWindow to itself
        this.DataContext = this;
    }

ДРУГОЕ РЕДАКТИРОВАНИЕ

Предположим, что у вашего хвостового класса есть свойство с именем ID. Если вы привязали этот идентификатор к Button.CommandParameter, вы можете позже получить плитку с помощью linq следующим образом:

public void ShowButton_click(object sender, EventArgs e)
{
   int MyId = int.Parse(((Button)sender).CommandParameter.ToString());
   Tile TileIWasSearchingFor = (from t in tiles where t.ID == MyId select t).First();
// do something with tile You found
}
person MajkeloDev    schedule 11.12.2014
comment
Хм, хорошо, тогда мне нужно найти в коллекции экземпляр Tile, который соответствует идентификатору, а затем, наконец, получить к нему доступ? И почему мое окно отображается пустым, когда я устанавливаю ‹ItemControl ItemSource={Привязка плиток}/› .... ‹/ItemControl›? - person crazyGamer; 11.12.2014
comment
Я отвечу, обновив свой ответ - person MajkeloDev; 11.12.2014
comment
Хорошо, круто, это сработало. И правильно ли я понимаю, что такое ID согласно моему первому комментарию? - person crazyGamer; 11.12.2014
comment
Этот оператор LinQ может не работать (вам может потребоваться применить его к плитке). Если вам понадобится дополнительная помощь, просто спросите. - person MajkeloDev; 11.12.2014
comment
Хорошо следил за вашими строками, но я думаю, что LINQ можно было бы избежать ..? Проверьте мой ответ ниже. - person crazyGamer; 13.12.2014
comment
Вы правы :) Ваш ответ очень подходит для этого вопроса, он включает в себя все, что нужно ОП :) - person MajkeloDev; 13.12.2014

Обработчики событий - неправильный подход - используйте Commands и, что более важно, MVVM.

Поскольку я вижу, что вы новичок (и, вероятно, из WinForms или ASP.NET), вам следует прочитать этот блог, чтобы понять, как должно измениться ваше мышление — это самая важная часть, которую нужно понять, прежде чем заняться WPF: http://rachel53461.wordpress.com/2012/10/12/switching-from-winforms-to-wpfmvvm/

Вам также следует прочитать блог Кента Бугарта о том, как работает MVVM на основе базовых принципов: http://kentb.blogspot.co.uk/2009/03/view-models-pocos-versus.html

person toadflakz    schedule 11.12.2014
comment
Вы совершенно правы, но MVVM довольно сложен для начинающих. Обычно я предлагаю получить некоторые основы, а затем использовать MVVM. - person MajkeloDev; 11.12.2014
comment
Предполагается, что этот человек уже знаком с другой технологией пользовательского интерфейса, поскольку он сразу же спрашивает об обработчиках событий (что является стандартной парадигмой взаимодействия для ASP.NET/WinForms). Для тех, у кого вообще нет опыта работы с пользовательским интерфейсом, я бы также предложил другой подход. - person toadflakz; 11.12.2014
comment
Ну, ваше мышление имеет «более широкий диапазон», я вижу. С этим не поспоришь. Голосую за ваш ответ, так как он может заинтересовать его и помочь ему в будущем. - person MajkeloDev; 11.12.2014
comment
Привет, да, посты действительно полезны... Например, WinForms для WPF: D Огромное спасибо! Проголосовал. Что ж, теперь я ищу другие сообщения о командах в WPF, и я снова прокомментирую, если мне понадобится дополнительная помощь? - person crazyGamer; 11.12.2014
comment
Конечно. Блог Кента на Commands в значительной степени охватывает большую часть того, что вам нужно знать, особенно DelegateCommand. И как только вы освоитесь с базовыми концепциями, посмотрите на PRISM — это золотой стандарт разработки WPF MVVM. Также не забывайте иногда отмечать правильный ответ. ;) - person toadflakz; 11.12.2014

Что ж, поскольку мое требование было довольно «простым», мне удалось обойти это, избегая команд. Благодаря ответу MajkeloDev здесь: https://stackoverflow.com/a/27419974/3998255 за руководство.

Это последний обработчик события:

public void ShowButton_Click(object sender, EventArgs e)
    {
        Tile requestingTile = (sender as Button).DataContext as Tile;
        if(requestingTile != null)
            MessageBox.Show("Viewing item from: " + this.Data); 
            // Or whatever else you want to do with the object...
    }

Кроме того, добавление ItemSource в качестве атрибута XAML:

<ItemsControl x:Name="TileList" ItemsSource="{Binding tiles}">

И установка DataContext в конструкторе MainWindow:

public MainWindow()
{ 
    this.DataContext = this;
    // Whatever else you want to do...
}

Ну работает как надо.

person crazyGamer    schedule 13.12.2014
comment
Все в порядке, но мне это не нравится. Часть данных. Представьте себе класс с очень большим количеством кода, будет ли он читабельным? - person MajkeloDev; 13.12.2014
comment
Э-э, не хорошо, как я уже говорил, я просто написал это, чтобы не усложнять. На самом деле у меня другие требования, мне нужен был способ получить доступ к нужному экземпляру для необходимых операций. Однако да, вы правы, если данные большие, это не имеет смысла (я на самом деле не делаю этого в своей программе). - person crazyGamer; 15.12.2014