WPF DataGrid Добавить, обновить и удалить с помощью MVVM

Я ищу пример кода/статьи, который продемонстрировал бы WPF DataGrid в действии с шаблоном MVVM для добавления, обновления и удаления записи из базы данных.

У меня есть специальное требование, позволяющее пользователю вставлять новую запись, используя DataGrid, а не новую дочернюю форму.

Если кто-то может рекомендовать хороший ресурс или предоставить образец для этой конкретной задачи, это будет большой помощью для меня.

Ответ 2

Я не знаю никаких хороших статей по этому вопросу, но я не вижу проблемы; до тех пор, пока вы привязываетесь к ObservableCollection или ListCollectionView, содержащему объекты, у которых класс имеет конструктор по умолчанию (я не думаю, что существуют другие ограничения), DataGrid будет обрабатывать вещи довольно хорошо. В коллекции, к которой вы привязаны, должен быть способ добавления новых элементов, поэтому вам необходимо привязать к ICollection или IEditableCollectionView - последнее предпочтительнее, поскольку оно имеет определенные опции для управления созданием элементов - см. AddNew, CanAddNew и т.д., с которыми DataGrid хорошо работает.

Ответ 3

Изменить. Вставьте деталь, соответствующую вашему вопросу. Полная статья: http://www.codeproject.com/Articles/30905/WPF-DataGrid-Practical-Examples

В этом примере показано, как использовать DataGrid для выполнения CRUD-операций посредством привязки, где интеграция базы данных развязана через уровень доступа к данным (DAL).

Архитектура

Этот пример представляет собой простое приложение CRUD, которое позволяет пользователю редактировать элементы в таблице Customers базы данных Northwind. В этом примере есть уровень доступа к данным, который предоставляет методы поиска/удаления/обновления, которые работают с простыми объектами данных, и слой презентации, который адаптирует эти объекты таким образом, что они могут быть эффективно связаны с WPF Framework. Поскольку мы выполняем только функции CRUD, я не добавил Business Logic Layer (BLL); если вы пурист, вы можете добавить пропущенный BLL; однако я чувствую, что это мало добавит к этому примеру.

Классы ключей в этой архитектуре показаны ниже:

Уровень доступа к данным предоставляет интерфейс для управления жизненным циклом объектов данных клиента. Класс, который реализует этот интерфейс, использует типизированный DataSet в качестве уровня интеграции базы данных; однако это скрыто от клиентов DAL. Наличие этого уровня означает, что мы не связаны напрямую с схемой базы данных или сгенерированной схемой набора данных, то есть мы можем изменить нашу схему, но при этом предоставить нижеприведенный интерфейс нашим клиентам:

public interface ICustomerDataAccessLayer
{
    /// Return all the persistent customers
    List<CustomerDataObject> GetCustomers();

    /// Updates or adds the given customer
    void UpdateCustomer(CustomerDataObject customer);

    /// Delete the given customer
    void DeleteCustomer(CustomerDataObject customer);
}
public class CustomerDataObject
{
    public string ID { get; set; }

    public string CompanyName { get; set; }

    public string ContactName { get; set; }
}

Как вы можете видеть, интерфейсы или классы интерфейса (например ObservableCollection), определенные DAL, не имеют определенных интерфейсов. Проблема заключается в том, как связать клиентов, возвращаемых ICustomerDataAccess.GetCustomers, с нашим DataGrid и убедиться, что изменения синхронизированы с базой данных.

Мы можем привязать DataGrid непосредственно к нашей коллекции клиентов, List; однако нам необходимо убедиться, что методы UpdateCustomer и DeleteCustomer на нашем интерфейсе DAL вызываются в соответствующие моменты времени. Один из подходов, который мы можем предпринять, - это обработать события/команды, открытые DataGrid, чтобы определить, какое действие он только что выполнил или намеревается выполнить в связанной коллекции клиентов. Однако при этом мы будем писать код интеграции, специфичный для DataGrid. Что делать, если мы хотим изменить пользовательский интерфейс, чтобы представить ListView и несколько текстовых полей (просмотр деталей)? Нам пришлось бы переписать эту логику. Кроме того, ни одно из событий DataGrid не соответствует тому, что мы хотим. Есть события "Окончание", но нет событий "Завершено"; поэтому данные, видимые для обработчиков событий, не находятся в состоянии фиксации. Лучшим подходом было бы, если бы мы могли адаптировать нашу коллекцию объектов Customer таким образом, чтобы они могли быть привязаны к любому подходящему элементу управления WPF, с операциями добавления/редактирования/удаления, синхронизированными с базой данных через наш DAL. Обработка операций удаления

Класс ObservableCollection является хорошим кандидатом для наших потребностей в привязке данных. Он предоставляет событие CollectionChanged, которое запускается, когда элементы добавляются или удаляются из коллекции. Если мы скопируем данные наших клиентов в ObservableCollection и привяжем их к DataGrid, мы можем обработать событие CollectionChanged и выполнить требуемую операцию в DAL. В следующем фрагменте кода показано, как CustomerObjectDataProvider (который определяется как ObjectDataProvider в XAML) создает ObservableCollection для объектов CustomerUIO. Эти объекты пользовательского интерфейса просто обертывают свои копии объектов данных, чтобы выявлять те же свойства.

public CustomerObjectDataProvider()
{
    dataAccessLayer = new CustomerDataAccessLayer();
}

public CustomerUIObjects GetCustomers()
{
    // populate our list of customers from the data access layer
    CustomerUIObjects customers = new CustomerUIObjects();

    List<CustomerDataObject> customerDataObjects = dataAccessLayer.GetCustomers();
    foreach (CustomerDataObject customerDataObject in customerDataObjects)
    {
        // create a business object from each data object
        customers.Add(new CustomerUIObject(customerDataObject));
    }

    customers.CollectionChanged += new
      NotifyCollectionChangedEventHandler(CustomersCollectionChanged);

    return customers;
}

void CustomersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.Action == NotifyCollectionChangedAction.Remove)
    {
        foreach (object item in e.OldItems)
        {
            CustomerUIObject customerObject = item as CustomerUIObject;

            // use the data access layer to delete the wrapped data object
            dataAccessLayer.DeleteCustomer(customerObject.GetDataObject());
        }
    }
}

Когда пользователь удаляет строку с помощью элемента управления DataGrid, событие CollectionChanged запускается в связанной коллекции. В обработчике событий мы вызываем метод DAL DeleteCustomer с обернутым объектом данных, переданным в качестве параметра.

Обработка операций удаления относительно проста, но как насчет обновлений или вставок? Вы можете подумать, что тот же подход можно использовать, свойство NotifyCollectionChangedEventArgs.Action включает операции добавления; однако это событие не запускается, когда элементы в коллекции обновляются. Кроме того, когда пользователь добавляет новый элемент в DataGrid, объект первоначально добавляется к связанной коллекции в неинициализированном состоянии, поэтому мы будем видеть только объект с его значениями по умолчанию. Нам действительно нужно определить, когда пользователь завершит редактирование элемента в сетке. Обработка обновлений/вложений

Чтобы определить, когда пользователь заканчивает редактирование связанного элемента, нам нужно немного углубиться в механизм привязки. DataGrid способен выполнять атомную фиксацию строки, которая в настоящее время редактируется; это становится возможным, если связанные элементы реализуют интерфейс IEditableObject, который предоставляет методы BeginEdit, EndEdit и CancelEdit. Как правило, объект, реализующий этот интерфейс, возвращается к своему состоянию в момент, когда метод BeginEdit был вызван как ответ на вызываемый метод CancelEdit. Однако в этом случае мы не очень обеспокоены возможностью отмены изменений; все, что нам действительно нужно знать, - это когда пользователь закончил редактирование строки. Это предъявляется обвинение, когда DataGrid вызывает EndEdit в нашем связанном элементе.

Чтобы уведомить CustomerDataObjectProvider, что EndEdit был вызван на одном из объектов в связанной коллекции, CustomerUIObject реализует IEditableObject следующим образом:

public delegate void ItemEndEditEventHandler(IEditableObject sender);

public event ItemEndEditEventHandler ItemEndEdit;

#region IEditableObject Members

public void BeginEdit() {}

public void CancelEdit() {}

public void EndEdit()
{
    if (ItemEndEdit != null)
    {
        ItemEndEdit(this);
    }
}

#endregion

Когда элементы добавляются в коллекцию CustomerUIObjects, это событие обрабатывается для всех элементов коллекции, при этом обработчик просто перенаправляет событие:

public class CustomerUIObjects : ObservableCollection<CustomerDataObject>
{
    protected override void InsertItem(int index, CustomerUIObject item)
    {
        base.InsertItem(index, item);

        // handle any EndEdit events relating to this item
        item.ItemEndEdit += new ItemEndEditEventHandler(ItemEndEditHandler);
    }

    void ItemEndEditHandler(IEditableObject sender)
    {
        // simply forward any EndEdit events
        if (ItemEndEdit != null)
        {
            ItemEndEdit(sender);
        }
    }

    public event ItemEndEditEventHandler ItemEndEdit;
}

Теперь ClientObjectDataProvider может обрабатывать это событие, чтобы получать уведомление о том, что CommitEdit вызывается на любом из связанных элементов. Затем он может вызвать методы DAL для синхронизации состояния базы данных:

public CustomerUIObjects GetCustomers()
{
    // populate our list of customers from the data access layer
    CustomerUIObjects customers = new CustomerUIObjects();

    List<CustomerDataObject> customerDataObjects = dataAccessLayer.GetCustomers();
    foreach (CustomerDataObject customerDataObject in customerDataObjects)
    {
        // create a business object from each data object
        customers.Add(new CustomerUIObject(customerDataObject));
    }

    customers.ItemEndEdit += new ItemEndEditEventHandler(CustomersItemEndEdit);
    customers.CollectionChanged += new
      NotifyCollectionChangedEventHandler(CustomersCollectionChanged);

    return customers;
}

void CustomersItemEndEdit(IEditableObject sender)
{
    CustomerUIObject customerObject = sender as CustomerUIObject;

    // use the data access layer to update the wrapped data object
    dataAccessLayer.UpdateCustomer(customerObject.GetDataObject());
}

Вышеприведенный код будет обрабатывать операции вставки и обновления.

В заключение этот метод адаптирует элементы данных и коллекции, предоставляемые DAL в элементы пользовательского интерфейса и коллекции, которые более подходят для привязки данных в рамках WPF Framework. Вся логика синхронизации базы данных выполняется путем обработки события из этой связанной коллекции; поэтому нет специального кода WPF DataGrid.