среда, 26 сентября 2012 г.

Редактирование в ячейках таблицы и удаление строк

Сегодня, убегая с работы, в спешке перепутал DataGrid и GridView, поэтому не смог показать, как задать шаблон для редактирования ячейки. В общем, исправляюсь и показываю. Ну и чтобы жизнь медом не казалась, пусть еще будет в каждой строке кнопка, которая будет удалять текущую строку.
Итак, задача номер раз. Сделать в таблице возможность редактирования ячейки.
Для демонстрации возьму вот такую структуру данных:

class Item
{
    public string Name { get; set; }
    public double Percent { get; set; }
    public static ObservableCollection<Item> GetItems()
    {
        ObservableCollection<Item> result = new ObservableCollection<Item>();
        result.Add(new Item() { Name = "Параметр 1", Percent = 10 });
        result.Add(new Item() { Name = "Параметр 2", Percent = 23 });
        result.Add(new Item() { Name = "Параметр 3", Percent = 15.5 });
        result.Add(new Item() { Name = "Параметр 4", Percent = 0 });
        result.Add(new Item() { Name = "Параметр 5", Percent = 56 });
        result.Add(new Item() { Name = "Параметр 6", Percent = 78 });
        return result;
    }
}
Для показа такого списка, можно воспользоваться вот таким DataGrid:

<DataGrid x:Name="dgMain" AutoGenerateColumns="False" IsReadOnly="True">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Имя" Binding="{Binding Name}" Width="1*" />
        <DataGridTextColumn Header="Процент" Width="100" Binding="{Binding Percent,StringFormat={}{0}%}" />
    </DataGrid.Columns>
</DataGrid>

Ну и можем инициализировать его данными:
 
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Loaded += MainWindow_Loaded;
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        dgMain.ItemsSource = Item.GetItems();
    }
}
Запускаем:
Все. Показ работает. Теперь необходимо сделать поддержку редактирования процентов. Для этого заменим TextColumn на TemplateColumn и определим для него не только шаблон показа, но и шаблон редактирования:
<DataGridTemplateColumn Header="Процент" Width="100">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Percent,StringFormat={}{0}%}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="1*" />
                    <ColumnDefinition Width="auto" />
                </Grid.ColumnDefinitions>
                <TextBox Text="{Binding Percent,Mode=TwoWay}" />
                <TextBlock Grid.Column="1" Text="%" />
            </Grid>
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

Теперь, запустив приложение и дважды кликнув на ячейке мы получим возможность редактирования:
Единственная проблема, которая здесь раздражает, это необходимость двойного клика, чтобы перейти к редактированию. Давайте изменим эту прискорбную ситуацию.
Для этого, установим значение двух свойств и подпишемся на изменение выбранной ячейки у нашего DataGrid:

<DataGrid x:Name="dgMain" AutoGenerateColumns="False" CanUserAddRows="False" SelectionMode="Extended" SelectionUnit="Cell" SelectedCellsChanged="dgMain_SelectedCellsChanged_1">

Обработчик будет иметь вид:

private void dgMain_SelectedCellsChanged_1(object sender, SelectedCellsChangedEventArgs e)
{
    if (e.AddedCells.Count == 0) return;
    var currentCell = e.AddedCells[0];
    if (currentCell.Column ==
        dgMain.Columns[1])
    {
        dgMain.BeginEdit();
    }
}

Все, теперь редактирование будет начинаться по одиночному клику.

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

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Items = Item.GetItems();
        DeleteCommand = new MyCommand() { Collection = Items };
    }

    class MyCommand : ICommand
    {
        public ObservableCollection<Item> Collection { get; set; }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            Collection.Remove(parameter as Item);
        }
    }

    public ObservableCollection<Item> Items
    {
        get { return (ObservableCollection<Item>)GetValue(ItemsProperty); }
        set { SetValue(ItemsProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Items.  This enables animation, styling, binding, etc...

    public static readonly DependencyProperty ItemsProperty =
        DependencyProperty.Register("Items", typeof(ObservableCollection<Item>), typeof(MainWindow), new PropertyMetadata(null));
 
    public ICommand DeleteCommand
    {
        get { return (ICommand)GetValue(DeleteCommandProperty); }
        set { SetValue(DeleteCommandProperty, value); }
    }
 
    // Using a DependencyProperty as the backing store for DeleteCommand.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DeleteCommandProperty =
        DependencyProperty.Register("DeleteCommand", typeof(ICommand), typeof(MainWindow), new PropertyMetadata(null));

    private void dgMain_SelectedCellsChanged_1(object sender, SelectedCellsChangedEventArgs e)
    {
        if (e.AddedCells.Count == 0) return;
        var currentCell = e.AddedCells[0];
        if (currentCell.Column ==
            dgMain.Columns[1])
        {
            dgMain.BeginEdit();
        }
    }
}

Добавляем новый столбец, в который помещаем кнопки привязанную к команде:
 
<DataGridTemplateColumn Width="30">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Button Content="Х" Command="{Binding Path=DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" CommandParameter="{Binding}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Единственной проблемой этого решения, условно можно считать наличие заголовка столбца над кнопками:
Но ведь это же WPF, давайте зададим стиль заголовка таким, чтобы его не было видно на белом фоне:
<DataGridTemplateColumn.HeaderStyle>
    <Style TargetType="{x:Type DataGridColumnHeader}" >
        <Setter Property="Background" Value="White"></Setter>
        <Setter Property="Foreground" Value="White"></Setter>
        <Setter Property="BorderBrush" Value="White"></Setter>
        <Setter Property="BorderThickness" Value="1"></Setter>
    </Style>
</DataGridTemplateColumn.HeaderStyle>

Теперь DataGrid выглядит так:
Мелочь конечно, но приятно.

На сегодня все, единственно, я приведу полную разметку DataGrid, вдруг выше чего забыл:


<DataGrid x:Name="dgMain" AutoGenerateColumns="False" CanUserAddRows="False" Margin="5"
            SelectionMode="Extended" SelectionUnit="Cell" SelectedCellsChanged="dgMain_SelectedCellsChanged_1"
            ItemsSource="{Binding Path=Items, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Имя" Binding="{Binding Name}" Width="1*" />
        <DataGridTemplateColumn Header="Процент" Width="100">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Percent,StringFormat={}{0}%}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="1*" />
                            <ColumnDefinition Width="auto" />
                        </Grid.ColumnDefinitions>
                        <TextBox Text="{Binding Percent,Mode=TwoWay}" />
                        <TextBlock Grid.Column="1" Text="%" />
                    </Grid>
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Width="30">
            <DataGridTemplateColumn.HeaderStyle>
                <Style TargetType="{x:Type DataGridColumnHeader}" >
                    <Setter Property="Background" Value="White"></Setter>
                    <Setter Property="Foreground" Value="White"></Setter>
                    <Setter Property="BorderBrush" Value="White"></Setter>
                    <Setter Property="BorderThickness" Value="1"></Setter>
                </Style>
            </DataGridTemplateColumn.HeaderStyle>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <Button Content="Х" Command="{Binding Path=DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" CommandParameter="{Binding}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

22 комментария:

  1. Здравствуйте , раньше работал с таблицами в winforms загружал данные из MySql в dataGridViev где редактировал нужные параметры и сохранял обратно в MySQL . Всё легко и просто ...

    В WPF загружаю данные в dataGrid после чего редактирую нужную ячейку а затем отправляю в MySQL в итоге всё проходит успешно кроме того что в базу приходят старые параметры которые загрузил при получении таблицы

    то есть если была строка

    параметр 1 - номер1 - 25%

    я изменил на

    параметр 1 - номер1 - 30%

    то в базу данных придёт

    параметр 1 - номер1 - 25%

    Немного не пойму почему не получается так же как winforms (

    ОтветитьУдалить
    Ответы
    1. Добрый день.
      Для доступа к базе данных какую технологию используете? ADO?

      Удалить
  2. Добрый день . Прошу прощения , разобрался с проблемой причиной которой было позднее время и моя невнимательность .
    По глупости пытался сохранить изменения в Sql при завершении редактирования ячейки (CellEditEnding) .... после чего утром на свежую голову понял что данные в таблицы сохраняются уже после завершения редактирования ячейки . И куда вернее пойти немного другим путём ..

    Прошу ещё раз прощение . И спасибо что всё же решили откликнутся на помощь .

    ОтветитьУдалить
  3. Здравствуйте. Полезна была и эта статья и О INotifyPropertyChanged и DependencyProperty. Попытался их соединить : если класс к которому привязываю DataGrid обычный , то всё работает (столбцы нужны 2 string,4 intи главное один CheckBox с bool). А когда создаю класс потомком DependencyObject выскакивает ошибка , как я понял - значение по умолчанию у свойств класса должно быть string. Где и как это можно изменить ? Спасибо.

    ОтветитьУдалить
    Ответы
    1. DependencyProperty состоит из двух частей. Динамической и статической. В статической необходимо значение по умолчанию прописывать правильно. В вашем случае, вот так:
      new UIPropertyMetadata("")

      Удалить
    2. Так и писал
      public bool li
      {
      get { return (bool)GetValue(li_svojstvo); }
      // set { SetValue(li_svojstvo, value); }
      }
      public static readonly DependencyProperty li_svojstvo = DependencyProperty.Register("li",
      typeof(bool), typeof(cl_столбцы_для_DataGrid), new UIPropertyMetadata(""));
      С string всё работает как должно, а bool и int выдаёт ошибку,

      Удалить
    3. А, значит я вас не понял... У вас свойство не типа строка, а типа bool. Значит и значение по умолчанию должно быть bool. Напишите вместо пустой строки - false.

      Удалить
    4. Спасибо. я сам был день без инета и смог спокойно подумать и до такого додумался.
      Искренне понравились Ваши заметки. Какой Вы можете посоветовать материал для начинающего по WPF и XAML ?

      Удалить
    5. Я в свое время в одной из толстых книжек по C# было две главы по WPF. Собственно книг про WPF больше не читал. В основном, если возникает проблема, то читаю MSDN, ищу поисковиками...

      Удалить
    6. Понятно как и я.
      Спасибо за помощь

      Удалить
    7. Пожалуйста. Будут вопросы, задавайте здесь, или на форумах MSDN. Там компания отвечающих хорошая подобралась ;)

      Удалить
  4. Не работает поддержка редактирования пока не выключишь IsReadOnly. А если его выключить, то при добавление в последнюю строку записи вы падает следующая строка для записи. Крайне не удобно. как можно это решать?

    ОтветитьУдалить
    Ответы
    1. Добрый день.
      Редактировать записи в отдельном окне (в очень большом количестве случаев это оправдано) или поставьте CanUserAddRows в false.

      Удалить
    2. спасибо большое. А вот у меня небольшой вопрос по DataGrid. Можно ли в таблице менять header столбцов программно? к примеру: я добавляю программно столбцы, в которых должна быть забита информация по различным датам. А дату логичнее всего поставить в заголовок. То есть, чтобы в шапке был datepicker или вроде этого?

      Удалить
    3. Этот комментарий был удален автором.

      Удалить
    4. Можно. Что-то XAML в комментарии не вставляется, поэтому вот, картинкой: https://yadi.sk/i/iUxNoM6ccNvj8

      Удалить
  5. Можете написать код для редактирования колонки типа CheckBox? Я запутался

    ОтветитьУдалить
    Ответы
    1. Да там то же самое. Свойство (обязательно свойство) должно быть типа bool ну и вот здесь пример на столбец такого типа http://msdn.microsoft.com/ru-ru/library/system.windows.controls.datagridcheckboxcolumn(v=vs.110).aspx (в конце)

      Удалить
  6. Добрый день! Сделал все по примеру в новом проекте - работает!
    При попытке перенести на мой другой проект - не работает, не добавляются позиции в datagrid... при дебаге, компилятор не заходит в class MyCommand : ICommand - может в этом проблема?? Помогите ПЛЗ

    ОтветитьУдалить
    Ответы
    1. Добрый день. Скорее всего проблема в Binding. Убедитесь, что у вас свойство, оно с модификатором public и правильно оно прописано в Binding.

      Удалить
    2. Уже все возможные варианты с модификаторами доступа перебрал) В XAML ничего не изменял, при этом в маинвиндов добавление работает, но когда я переношу датагрид в другие окна - перестает работать..

      Удалить
    3. Скорее всего у вас в DataContext этих окон лежит (если лежит) другой ViewModel у которого нет соответствующей команды...

      Удалить