пятница, 27 января 2012 г.

ASP.NET: Пара сценариев отображения данных в GridView

Здравствуйте. Сегодня поговорим о веб-разработке. Несмотря на то, что популярность MVC растет очень быстро, Web Forms ещё никто не отменял. К тому же много приложений написано с использованием Web Forms, да и уже написанные приложения требуют поддержки. И если простейшие контролы (например, TextBox) у новичков вопросов, как правило, не вызывают, то сориентироваться в чем-то более сложном уже проблема.
В данном посте я расскажу в общих чертах, как использовать контрол GridView для работы со списком или специализированным источником данных. Предупреждаю сразу, тема не новая и статья ориентирована на начинающих разработчиков.
Поехали.


Начнем с теории. Класс GridView
Отображает значения источника данных в таблице, где каждый столбец представляет поле, а каждая строка — запись. Элемент управления GridView позволяет выбирать, сортировать и изменять эти записи.
Его можно связывать с элементами-источниками данных (например, наследниками DataSourceControl), а можно просто отображать перечисляемые списки, указав значение свойства DataSource. Начнем с последнего.

Итак, задача: Имеется страница, на которой расположен GridView, и список, который нужно на этой странице формировать и иметь возможность его изменять (то есть добавлять/удалять элементы). Список хранится только на странице.

Порядок действий:
  • Создаём веб-приложение
  • Удаляем с домашней страницы всё лишнее (необязательно)
  • Создаём страницу GridViewList.aspx, указываем для неё мастер страницу
  • Добавляем в меню пункт со ссылкой на GridViewList.aspx
Отлично, полигон для действий готов. Класс-объект, с которым мы будем работать, я определил так:

  1. [Serializable]
  2. public class People
  3. {
  4.     [XmlAttribute]
  5.     public int Id { get; set; }
  6.     [XmlAttribute]
  7.     public string FirstName { get; set; }
  8.     [XmlAttribute]
  9.     public string LastName { get; set; }
  10. }

Теперь определимся со списком. Во-первых, так как список формируется и изменяется только в рамках этой страницы, я решил хранить его во ViewState. Я просто определил свойство на странице, которое автоматом себя сохраняет.

  1. public List<People> Peoples
  2. {
  3.     get
  4.     {
  5.         // Получение ключа для поиска во ViewState.
  6.         // hfPeoplesViewState - HiddenField, которое хранит в себе этот ключ
  7.         // Это всё сделано только для того, чтобы обеспечить уникальность ключа на странице.
  8.         // По идее тут можно было просто написать var str = "Peoples_Key" и всё бы работало
  9.         var str = hfPeoplesViewState.Value;
  10.         if (string.IsNullOrEmpty(str))
  11.         {
  12.             // Если ещё ничего не хранит, то создать ключ
  13.             hfPeoplesViewState.Value = string.Format("Peoples_{0}", Guid.NewGuid());
  14.             str = hfPeoplesViewState.Value;
  15.         }
  16.  
  17.         // Проверяю, если во ViewState ещё нет того списка, что мне нужен - создаю его
  18.         if (ViewState[str] == null || !(ViewState[str] is List<People>))
  19.         {
  20.             var peoples = new List<People>
  21.                               {
  22.                                   new People {Id = 1, LastName = "Мурадов", FirstName = "Артем"},
  23.                                 new People {Id = 2, LastName = "Пупкин", FirstName = "Василий"},
  24.                                 new People {Id = 3, LastName = "Елопанов", FirstName = "Инокентий"}
  25.                               };
  26.             ViewState[str] = peoples;
  27.             return peoples;
  28.         }
  29.         return ViewState[str] as List<People>;
  30.     }
  31.     set
  32.     {
  33.         // Аналогично методу get
  34.         var str = hfPeoplesViewState.Value;
  35.         if (string.IsNullOrEmpty(str))
  36.         {
  37.             hfPeoplesViewState.Value = string.Format("Peoples_{0}", Guid.NewGuid());
  38.             str = hfPeoplesViewState.Value;
  39.         }
  40.  
  41.         ViewState[str] = value;
  42.     }
  43. }

Добавим GridView в разметку. По пути также добавим пару текстовых полей и кнопку, для возможности добавления элемента в коллекцию.

  1. <asp:TextBox runat="server" ID="tbFirstName">
  2. </asp:TextBox>
  3. <asp:TextBox runat="server" ID="tbLastName">
  4. </asp:TextBox>
  5. <asp:Button runat="server" OnClick="BtAddPeople" Text="+" />
  6. <hr />
  7. <asp:GridView runat="server" ID="gv" AutoGenerateColumns="True" Width="60%">
  8.     <EmptyDataTemplate>
  9.         Записей нет</EmptyDataTemplate>
  10. </asp:GridView>

В обработчике событий

  1. protected void Page_Load(object sender, EventArgs e)
  2. {
  3.     if (!IsPostBack)
  4.     {
  5.         gv.DataSource = Peoples;
  6.         gv.DataBind();
  7.     }
  8. }
  9.  
  10. protected void BtAddPeople(object sender, EventArgs e)
  11. {
  12.     if (!string.IsNullOrEmpty(tbFirstName.Text) && !string.IsNullOrEmpty(tbLastName.Text))
  13.     {
  14.         var id = Peoples.Count > 0 ? Peoples.Max(x => x.Id) + 1 : 1;
  15.         var p = new People
  16.         {
  17.             Id = id,
  18.             FirstName = tbFirstName.Text,
  19.             LastName = tbLastName.Text
  20.         };
  21.         Peoples.Add(p);
  22.         UpdateGrid();
  23.     }
  24. }
  25.  
  26. private void UpdateGrid()
  27. {
  28.     gv.DataSource = Peoples;
  29.     gv.DataBind();
  30. }

Окей, теперь при первой загрузке страницы, у нас будет заполняться GridView. А при добавлении элемента в коллекцию, GridView будет обновляться. Вот как это выглядит:

Добавление работает, но у нас нет ни сортировки, ни редактирования, ни удаления, да ещё и включена автогенерация полей. Вот с последнего и начнем. Отключаем автогенерацию, указываем нужные нам поля, разрешаем сортировку (список возможных полей):

  1. <asp:GridView runat="server" ID="gv" AutoGenerateColumns="False" Width="60%" AllowSorting="True">
  2.     <Columns>
  3.         <asp:BoundField HeaderText="ИД" DataField="Id" SortExpression="Id" ReadOnly="True">
  4.             <ItemStyle Width="5%"></ItemStyle>
  5.         </asp:BoundField>
  6.         <asp:BoundField HeaderText="Имя" DataField="FirstName" SortExpression="FirstName">
  7.             <ItemStyle Width="40%"></ItemStyle>
  8.         </asp:BoundField>
  9.         <asp:BoundField HeaderText="Фамилия" DataField="LastName" SortExpression="LastName">
  10.             <ItemStyle Width="40%"></ItemStyle>
  11.         </asp:BoundField>
  12.         <asp:CommandField ShowEditButton="True" ShowDeleteButton="True" ShowCancelButton="True">
  13.             <ItemStyle Width="15%"></ItemStyle>
  14.         </asp:CommandField>
  15.     </Columns>
  16.     <EmptyDataTemplate>
  17.         Записей нет</EmptyDataTemplate>
  18. </asp:GridView>
Теперь у нас есть кнопки, для того, чтобы сортировать, редактировать или удалять. Но только этого функционала пока нет. GridView не может самостоятельно править нашу коллекцию. Но это не беда, нам достаточно подписаться на нужные нам события и реализовать всё самим. Для редактирования нужны следующие события: OnRowEditing="GvEditing" OnRowUpdating="GvUpdating" OnRowCancelingEdit="GvCancelingEdit", для сортировки OnSorting="GvSorting", для удаления OnRowDeleting="GvDeleting".

  1. <asp:GridView runat="server" ID="gv" AllowSorting="True" OnSorting="GvSorting" OnRowEditing="GvEditing"
  2.     OnRowUpdating="GvUpdating" OnRowCancelingEdit="GvCancelingEdit" OnRowDeleting="GvDeleting"
  3.     DataKeyNames="Id" AutoGenerateColumns="False" Width="60%" >
  4.     <Columns>
  5.         <asp:BoundField HeaderText="ИД" DataField="Id" SortExpression="Id" ReadOnly="True">
  6.             <ItemStyle Width="5%"></ItemStyle>
  7.         </asp:BoundField>
  8.         <asp:BoundField HeaderText="Имя" DataField="FirstName" SortExpression="FirstName">
  9.             <ItemStyle Width="40%"></ItemStyle>
  10.         </asp:BoundField>
  11.         <asp:BoundField HeaderText="Фамилия" DataField="LastName" SortExpression="LastName">
  12.             <ItemStyle Width="40%"></ItemStyle>
  13.         </asp:BoundField>
  14.         <asp:CommandField ShowEditButton="True" ShowDeleteButton="True" ShowCancelButton="True">
  15.             <ItemStyle Width="15%"></ItemStyle>
  16.         </asp:CommandField>
  17.     </Columns>
  18.     <EmptyDataTemplate>
  19.         Записей нет</EmptyDataTemplate>
  20. </asp:GridView>

Вот код для сортировки

  1. /// <summary>
  2. /// Возникает при сортировке в гриде.
  3. /// </summary>
  4. protected void GvSorting(object sender, GridViewSortEventArgs e)
  5. {
  6.     var p = Peoples;
  7.  
  8.     // Необходимо определить, по какому именно полю сортировать
  9.     if (e.SortExpression == "LastName")
  10.     {
  11.         // используем вспомогательный метод Sort
  12.         p = Sort("LastName", list => list.OrderBy(x => x.LastName).ToList(),
  13.                  list => list.OrderByDescending(x => x.LastName).ToList(), p);
  14.     }
  15.     if (e.SortExpression == "FirstName")
  16.     {
  17.         p = Sort("FirstName", list => list.OrderBy(x => x.FirstName).ToList(),
  18.                  list => list.OrderByDescending(x => x.FirstName).ToList(), p);
  19.     }
  20.     if (e.SortExpression == "Id")
  21.     {
  22.         p = Sort("Id", list => list.OrderBy(x => x.Id).ToList(),
  23.                  list => list.OrderByDescending(x => x.Id).ToList(), p);
  24.     }
  25.  
  26.     Peoples = p;
  27.     // обновление грида
  28.     UpdateGrid();
  29. }
  30.  
  31. /// <summary>
  32. /// Вспомогательный метод для сортировки
  33. /// </summary>
  34. private List<People> Sort(string column, Func<List<People>, List<People>> ascFunc, Func<List<People>, List<People>> descFunc, List<People> data)
  35. {
  36.     List<People> result;
  37.     // Если в прошлвй раз была сортировка по этому же полю
  38.     if (hfLastSortFieldState.Value == column)
  39.     {
  40.         // смотрим, в каком направлении была сортировка в прошлвй раз и сортируем в другом
  41.         result = hfLastSortDirectionState.Value != "ASC" ? ascFunc(data) : descFunc(data);
  42.         // сохраняем колонку и направление сортировки
  43.         hfLastSortDirectionState.Value = hfLastSortDirectionState.Value != "ASC" ? "ASC" : "DESC";
  44.         hfLastSortFieldState.Value = column;
  45.     }
  46.     else
  47.     {
  48.         // Если это поле ещё не сортировано, сортируем по возрастанию
  49.         result = ascFunc(data);
  50.         // сохраняем колонку и направление сортировки
  51.         hfLastSortDirectionState.Value = "ASC";
  52.         hfLastSortFieldState.Value = column;
  53.     }
  54.  
  55.     return result;
  56. }

Код для редактирования и удаления

  1. /// <summary>
  2. /// Событие удаления элемента. Поле Id попадает в набор ключей,
  3. /// так как в гриде указано DataKeyNames="Id"
  4. /// Нам осталось только определить ключ и убрать элемент с этим ключем из коллекции
  5. /// </summary>
  6. protected void GvDeleting(object sender, GridViewDeleteEventArgs e)
  7. {
  8.     int id;
  9.     // Определяем идентификатор
  10.     if (int.TryParse(string.Format("{0}", e.Keys["Id"]), out id))
  11.     {
  12.         // Убираем из списка
  13.         Peoples = Peoples.Where(x => x.Id != id).ToList();
  14.         // обновляем грид
  15.         UpdateGrid();
  16.     }
  17. }
  18.  
  19. /// <summary>
  20. /// Событие возникает, когда пользователь начинает редактировать строку.
  21. /// Необходимо просто указать гриду индекс редактируемой строки и обновить грид
  22. /// </summary>
  23. protected void GvEditing(object sender, GridViewEditEventArgs e)
  24. {
  25.     gv.EditIndex = e.NewEditIndex;
  26.     UpdateGrid();
  27. }
  28.  
  29. /// <summary>
  30. /// Событие возникает, когда пользователь обновляет строку.
  31. /// Необходимо определить Id строки, получить элемент по этому Id, обновить поля и обновить грид
  32. /// </summary>
  33. protected void GvUpdating(object sender, GridViewUpdateEventArgs e)
  34. {
  35.     int id;
  36.     if (int.TryParse(string.Format("{0}", e.Keys["Id"]), out id))
  37.     {
  38.         Peoples
  39.             .Where(x => x.Id == id)
  40.             .ToList()
  41.             .ForEach(x =>
  42.                          {
  43.                             // я использую string.Format, так как
  44.                             // e.NewValues["FirstName"] имеет тип object
  45.                             // и может быть равен null
  46.                              x.FirstName = string.Format("{0}", e.NewValues["FirstName"]);
  47.                              x.LastName = string.Format("{0}", e.NewValues["LastName"]);
  48.                          });
  49.         gv.EditIndex = -1;
  50.         UpdateGrid();
  51.     }
  52. }
  53.  
  54. /// <summary>
  55. /// Событие возникает, когда пользователь отменяет обновление строки
  56. /// Нужно установить gv.EditIndex = -1; и обновить грид
  57. /// </summary>
  58. protected void GvCancelingEdit(object sender, GridViewCancelEditEventArgs e)
  59. {
  60.     gv.EditIndex = -1;
  61.     UpdateGrid();
  62. }

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

Это всё хорошо, но что делать, если мы работаем с базой данных? В таких случаях можно определять источники данных (например, наследники DataSourceControl) и связывать их с гридом. Я приведу пример с использованием ObjectDataSource. Я выбрал ObjectDataSource, так как он зависит только от вашего кода и ему всё равно, откуда берутся данные - из базы данных, сервиса, файла, да чего угодно.

Формализуем задачу. Необходимо реализовать такой же функционал, как и в предыдущем примере, но с использованием ObjectDataSource. Только на этот раз данные я буду хранить не на странице, а в статической переменной - чтобы эти данные были доступны в рамках всего приложения (ведь так и происходит, когда вы работаете, например, с базой данных).

Вот класс автомобиля, с которым будем работать

  1. public class Car
  2. {
  3.     public int Id { get; set; }
  4.     public string Manufacturer { get; set; }
  5.     public string Model { get; set; }
  6. }

Начнем с класса-репозитория. Именно его методы будет вызывать ObjectDataSource для получения данных и манипуляции с ними. Хочу заметить, что я подобрал сигнатуру и набор методов репозитория специально для использования в ObjectDataSource. Вот сам класс:

  1. public class CarRepository
  2. {
  3.     /// <summary>
  4.     /// Статическая переменная. В ней будем хранить наши данные
  5.     /// </summary>
  6.     private static List<Car> _store;
  7.  
  8.     /// <summary>
  9.     /// Статический конструктор. Внутри инициализируем наши данные
  10.     /// </summary>
  11.     static CarRepository()
  12.     {
  13.         _store = new List<Car>();
  14.  
  15.         for (var i = 0; i < 1000; i++)
  16.         {
  17.             _store.Add(new Car
  18.             {
  19.                 Id = i,
  20.                 Manufacturer = "Manufacturer" + i,
  21.                 Model = "Model" + i
  22.             });
  23.         }
  24.     }
  25.  
  26.     /// <summary>
  27.     /// Получение данных. Принимает три параметра. Назначение первых двух очевидно,
  28.     /// а вот в третьем приходит информация  для сортировки. Например, у нас сортировка по полю
  29.     /// "Id". При сортировке по возрастанию переметр sort будет равен "Id".
  30.     /// При сортировке по убыванию - "Id DESC"
  31.     /// </summary>
  32.     public IEnumerable<Car> GetCars(int maximumRows, int startRowIndex, string sort)
  33.     {
  34.         IEnumerable<Car> temp = _store;
  35.  
  36.         // Сперва определяем направление сортировки, затем поле, по которому сортируем
  37.         if (sort.Contains("DESC"))
  38.         {
  39.             if (sort.Contains("Id")) temp = _store.OrderByDescending(x => x.Id);
  40.             if (sort.Contains("Manufacturer")) temp = _store.OrderByDescending(x => x.Manufacturer);
  41.             if (sort.Contains("Model")) temp = _store.OrderByDescending(x => x.Model);
  42.         }
  43.         else
  44.         {
  45.             if (sort.Contains("Id")) temp = _store.OrderBy(x => x.Id);
  46.             if (sort.Contains("Manufacturer")) temp = _store.OrderBy(x => x.Manufacturer);
  47.             if (sort.Contains("Model")) temp = _store.OrderBy(x => x.Model);
  48.         }
  49.  
  50.         return temp.Skip(startRowIndex).Take(maximumRows);
  51.     }
  52.  
  53.     /// <summary>
  54.     /// Необходим для подсчёта общего количества элеменов.
  55.     /// Используется для постраничного вывода данных
  56.     /// </summary>
  57.     public int Count()
  58.     {
  59.         return _store.Count;
  60.     }
  61.  
  62.     /// <summary>
  63.     /// Для обновления в метод приходит экземпляр Car с
  64.     /// идентификатором обновляемой машины и новыми значениями полей
  65.     /// </summary>
  66.     public void Update(Car car)
  67.     {
  68.         _store
  69.             .Where(x => x.Id == car.Id)
  70.             .ToList()
  71.             .ForEach(x =>
  72.                          {
  73.                              x.Manufacturer = car.Manufacturer;
  74.                              x.Model = car.Model;
  75.                          });
  76.     }
  77.  
  78.     /// <summary>
  79.     /// Удаление. Код и так понятен.
  80.     /// </summary>
  81.     public void Delete(Car car)
  82.     {
  83.         _store = _store.Where(x => x.Id != car.Id).ToList();
  84.     }
  85.  
  86.     /// <summary>
  87.     /// Добавление
  88.     /// </summary>
  89.     public void Insert(Car car)
  90.     {
  91.         car.Id = _store.Count > 0 ? _store.Max(x => x.Id) + 1 : 1;
  92.         _store.Add(car);
  93.     }
  94. }

Отлично. Теперь, как и в предыдущем примере, добавляем страницу в проект и кидаем на эту страницу 2 текстбокса и кнопку (для возможности добавления)

  1. <asp:TextBox runat="server" ID="tbManufacturer">
  2. </asp:TextBox>
  3. <asp:TextBox runat="server" ID="tbModel">
  4. </asp:TextBox>
  5. <asp:Button runat="server" OnClick="BtAddCar" Text="+" />
  6. <hr />

  1. protected void BtAddCar(object sender, EventArgs e)
  2. {
  3.     if (!string.IsNullOrEmpty(tbManufacturer.Text) && !string.IsNullOrEmpty(tbModel.Text))
  4.     {
  5.         var carRepository = new CarRepository();
  6.         carRepository.Insert(new Car {Manufacturer = tbManufacturer.Text, Model = tbModel.Text});
  7.         gv.DataBind();
  8.     }
  9. }


Теперь поработаем над самим контролом ObjectDataSource.

  1. <asp:ObjectDataSource ID="ods" runat="server" TypeName="GridViewTest.CarRepository"
  2.     DataObjectTypeName="GridViewTest.Car" EnablePaging="True" MaximumRowsParameterName="maximumRows"
  3.     StartRowIndexParameterName="startRowIndex" SortParameterName="sort" InsertMethod="Insert"
  4.     SelectCountMethod="Count" SelectMethod="GetCars" UpdateMethod="Update" DeleteMethod="Delete">
  5. </asp:ObjectDataSource>

Поясню по каждому полю.
  • TypeName="GridViewTest.CarRepository" - указывает класс-источник данных
  • DataObjectTypeName="GridViewTest.Car" - указывает тип объекта, которым будем оперировать. Благодаря этому мы можем в репозитории указывать методы, принимающие в качестве параметра экземпляр типа Car. Этот тип должен иметь конструктор без параметров и поля, доступные для записи.
  • EnablePaging="True" указываем, что используется постраничный вывод
  • MaximumRowsParameterName="maximumRows" StartRowIndexParameterName="startRowIndex" SortParameterName="sort" имена параметров в методах
  • InsertMethod="Insert" SelectCountMethod="Count" SelectMethod="GetCars" UpdateMethod="Update" DeleteMethod="Delete" имена методов в репозитории

Теперь у нас всё готово для того, чтобы добавить на страницу грид. Приведу весь его код.

  1. <asp:GridView runat="server" ID="gv" AllowSorting="True" DataKeyNames="Id" AutoGenerateColumns="False"
  2.     DataSourceID="ods" Width="60%" AllowPaging="True" PageSize="20">
  3.     <Columns>
  4.         <%--Тут, как и в предыдущем примере, я использую BoundField--%>
  5.         <asp:BoundField HeaderText="ИД" DataField="Id" ReadOnly="True" SortExpression="Id">
  6.             <ItemStyle Width="5%"></ItemStyle>
  7.         </asp:BoundField>
  8.         <%--Здесь я тоже мог бы использовать BoundField, но я хотел показать, как использовать
  9.         TemplateField с односторонней (Eval) и двухсторонней (Bind) привязкой данных--%>
  10.         <asp:TemplateField HeaderText="Производитель" SortExpression="Manufacturer">
  11.             <ItemStyle Width="35%"></ItemStyle>
  12.             <ItemTemplate>
  13.                 <%#Eval("Manufacturer")%>
  14.             </ItemTemplate>
  15.             <EditItemTemplate>
  16.                 <asp:TextBox runat="server" ID="tbMan" Text='<%#Bind("Manufacturer") %>'></asp:TextBox>
  17.             </EditItemTemplate>
  18.         </asp:TemplateField>
  19.         <%--Тут всё аналогично предыдущему столбцу--%>
  20.         <asp:TemplateField HeaderText="Модель" SortExpression="Model">
  21.             <ItemStyle Width="35%"></ItemStyle>
  22.             <ItemTemplate>
  23.                 <%#Eval("Model")%>
  24.             </ItemTemplate>
  25.             <EditItemTemplate>
  26.                 <asp:TextBox runat="server" ID="tbModel" Text='<%#Bind("Model") %>'></asp:TextBox>
  27.             </EditItemTemplate>
  28.         </asp:TemplateField>
  29.         <%--Я хочу показать, что для того, чтобы использовать кнопки редактирования/удаления,
  30.         вам не обязательно пользоваться CommandField. Достаточно указать нужное значение в поле
  31.         CommandName любого кнопочного контрола--%>
  32.         <asp:TemplateField>
  33.             <ItemStyle Width="25%"></ItemStyle>
  34.             <ItemTemplate>
  35.                 <asp:LinkButton runat="server" CommandName="Edit" Text="Изменить"></asp:LinkButton>
  36.                 <%--А раз это простые кнопки, то можно им добавлять нужное поведение. Например,
  37.                 OnClientClick отработает на клиенте, спросит у пользователя разрешение на действие,
  38.                 и постбек произойдет только в том случае, если пользователь ответит утвердительно --%>
  39.                 <asp:LinkButton runat="server" CommandName="Delete" Text="Удалить"
  40.                 OnClientClick="return confirm('Вы действительно хотите удалить запись?');"></asp:LinkButton>
  41.             </ItemTemplate>
  42.             <EditItemTemplate>
  43.                 <asp:LinkButton runat="server" CommandName="Update" Text="Обновить"></asp:LinkButton>
  44.                 <asp:LinkButton runat="server" CommandName="Cancel" Text="Отменить"></asp:LinkButton>
  45.             </EditItemTemplate>
  46.         </asp:TemplateField>
  47.     </Columns>
  48.     <%--Контент, который будет показан, если записей не будет вовсе--%>
  49.     <EmptyDataTemplate>
  50.         Записей нет</EmptyDataTemplate>
  51. </asp:GridView>

Собрав всё вместе, получаем результат:



Вот и всё. Данные можно изменять, сортировать, удалять.

Таким образом, используя GridView и различные источники данных, можно легко манипулировать данными и представлять их в удобном для вас виде.

Исходный код солюшена

P.S. Решил добавить немного информации о фильтрации наборов данных. Хотя это и выходит за рамки статьи, но это частый сценарий при работе с источниками данных.

Итак, давайте в наш последний пример добавим немного фильтров. Я просто хочу, чтобы можно было фильтровать список по Id используя параметры URL запроса, а также я добавлю 2 текстовых поля для фильтрации по производителю и модели.
Итак, наши текстовые поля:

  1. <asp:TextBox runat="server" ID="tbManufacturerFilter">
  2. </asp:TextBox>
  3. <asp:TextBox runat="server" ID="tbModelFilter">
  4. </asp:TextBox>
  5. <asp:Button runat="server" Text="Искать" />

Для чего там кнопка и почему она ничего не делает я поясню позже.

Далее необходимо указать источнику данных, что параметры для получения информации необходимо брать из текстовых полей и URL. Это можно сделать с помощью коллекции SelectParameters.

  1. <asp:ObjectDataSource ID="ods" runat="server" TypeName="GridViewTest.CarRepository"
  2.     DataObjectTypeName="GridViewTest.Car" EnablePaging="True" MaximumRowsParameterName="maximumRows"
  3.     StartRowIndexParameterName="startRowIndex" SortParameterName="sort" InsertMethod="Insert"
  4.     SelectCountMethod="Count" SelectMethod="GetCars" UpdateMethod="Update" DeleteMethod="Delete">
  5.     <SelectParameters>
  6.         <asp:QueryStringParameter DefaultValue="-1" QueryStringField="id" Name="id" Type="Int32" />
  7.         <asp:ControlParameter ControlID="tbManufacturerFilter" DefaultValue="" Name="manFilter" ConvertEmptyStringToNull="false"
  8.             PropertyName="Text" Type="String" />
  9.         <asp:ControlParameter ControlID="tbModelFilter" DefaultValue="" Name="modelFilter" ConvertEmptyStringToNull="false"
  10.             PropertyName="Text" Type="String" />            
  11.     </SelectParameters>
  12. </asp:ObjectDataSource>

Собственно, названия QueryStringParameter и ControlParameter говорят сами за себя (Список типов параметров и как их использовать). Однако, так как мы имеем дело с ObjectDataSource, наш источник данных должен поддерживать эти параметры. Вот код всех обновленных функций класса-источника данных.

  1. /// <summary>
  2. /// Получение данных. Принимает шесть параметров. Первые три предназначены для фильтрации.
  3. /// Назначение следующих двух очевидно.
  4. /// А вот в шестом приходит информация  для сортировки. Например, у нас сортировка по полю
  5. /// "Id". При сортировке по возрастанию переметр sort будет равен "Id".
  6. /// При сортировке по убыванию - "Id DESC"
  7. /// </summary>
  8. public IEnumerable<Car> GetCars(int id, string manFilter, string modelFilter, int maximumRows, int startRowIndex, string sort)
  9. {
  10.     IEnumerable<Car> temp = _store.Where(x => CheckCar(x, id, manFilter, modelFilter));
  11.  
  12.     // Сперва определяем направление сортировки, затем поле, по которому сортируем
  13.     if (sort.Contains("DESC"))
  14.     {
  15.         if (sort.Contains("Id")) temp = temp.OrderByDescending(x => x.Id);
  16.         if (sort.Contains("Manufacturer")) temp = temp.OrderByDescending(x => x.Manufacturer);
  17.         if (sort.Contains("Model")) temp = temp.OrderByDescending(x => x.Model);
  18.     }
  19.     else
  20.     {
  21.         if (sort.Contains("Id")) temp = temp.OrderBy(x => x.Id);
  22.         if (sort.Contains("Manufacturer")) temp = temp.OrderBy(x => x.Manufacturer);
  23.         if (sort.Contains("Model")) temp = temp.OrderBy(x => x.Model);
  24.     }
  25.  
  26.     return temp.Skip(startRowIndex).Take(maximumRows);
  27. }
  28.  
  29. /// <summary>
  30. /// Необходим для подсчёта общего количества элеменов.
  31. /// Используется для постраничного вывода данных
  32. /// </summary>
  33. public int Count(int id, string manFilter, string modelFilter)
  34. {
  35.     return _store.Where(x => CheckCar(x, id, manFilter, modelFilter)).Count();
  36. }
  37.  
  38. /// <summary>
  39. /// Проверка соответствия конкретного экземпляра Car входящим параметрам
  40. /// </summary>
  41. private static bool CheckCar(Car car, int id, string manFilter, string modelFilter)
  42. {
  43.     return (id < 0 || car.Id == id) &&
  44.         (string.IsNullOrEmpty(manFilter) || car.Manufacturer.Contains(manFilter)) &&
  45.            (string.IsNullOrEmpty(modelFilter) || car.Model.Contains(modelFilter));
  46. }

Теперь как это работает. При биндинге, GridView будет спрашивать данные у источника данных, который, в свою очередь, будет извлекать параметры и вызывать методы класса-источника данных, отправляя в него значения этих параметров. То есть это будет происходить само собой, при любом постбеке. Это означает, что если мы установим свойство у текстовых полей AutoPostBack=True или просто поставим кнопку (можно даже без обработчика), которая будет делать постбек, то этого достаточно, чтобы данные в гриде обновились с учетом заданных параметров. Как это выглядит:




Конечно, все вкусные плюшки в виде сортировки и добавления/изменения элементов остаются.

Новый исходный код солюшена


Всем спасибо.

Полезные ссылки:

86 комментариев:

  1. Спасибо, сейчас буду проходить туториал)

    ОтветитьУдалить
  2. Спасибо! Наконец эта магия с GridView раскрыта ;-)

    ОтветитьУдалить
  3. Столкнулся с такой проблемой: Есть GridView связанный с таблицей в DataSet. Для того чтобы массово редактировать и обновлять строки в гриде определены ItemTemplate с TextBoxми привязаными через Bind(). Гриду разрешен постраничный просмотр. И все сохраняется и редактируется пока страница одна. Если к примеру изменил значение в строке находящейся на 3-ей странице, переключился на 1-ую и потом нажал на сохранение то цикл по строкам грида foreach (GridViewRow r in GV_fact0.Rows) проходит только по активной странице. И как следствие получить новые значения с 3-ей страницы нет никакой возможности. Подскажите пожалуста как добраться до всех страниц и строк грида? Или есть какой то обходной маневр? Буду очень признателен.

    ОтветитьУдалить
    Ответы
    1. Давайте разберемся:
      1. У вас есть GridView, у которого в ItemTemplate указаны текстбоксы. То есть грид в процессе редактирования не участвует, а где то на форме есть кнопка, которая при нажатии проходит все строки грида и обновляет данные.
      2. У грида настроен пейджинг. Что это означает. Что грид не хранит другие страницы, которые не отображает. То есть в гриде есть только те строки, что он показывает. Следовательно, когда пользователь переходит на другую страницу, все его изменения теряются.

      Я тут вижу 4 варианта:
      1. Обновлять данные как только их изменяют. (То есть пользователь изменил значение текстбокса и сразу обновить это значение в базе)
      2. Сделать свой пейджинг, который применяет изменения при переходе на другую страницу (спрашивая предварительно разрешения у пользователя)
      3. Хранить где то все изменения пользователя (то есть яваскриптом определять что и где пользователь меняет, куда то это сохранять и применять изменения если нажмется кнопка Сохранить)
      4. Использовать какой то другой контрол или написать свой.

      Удалить
    2. Анонимный2 марта 2012 г., 9:22

      Спасибо за оперативный ответ.
      По поводу первой части ответа: да так и есть, есть кнопка которая все сохраняет и грид получается не сохраняет никаких строк кроме строк на текущей странице(Я что то такое предполагал).
      Придется до события GV_fact0_PageIndexChanging вешать клиентский confirm с вопросом типа «Сохранить изменения?». Или в самом GV_fact0_PageIndexChanging на стороне сервера пробежать по гриду и изменения сохранить в исходном DataSetе. А уж потом после нажатия кнопки сохранения заливать все скопом в базу.
      В любом случае спасибо за подсказку.

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

    ОтветитьУдалить
    Ответы
    1. Пожалуйста. Я для того и писал статью, чтобы "косить деревья" :)

      Удалить
    2. Это не вы случаем удалили мой комент? или я случайно нажал. Перепишу:
      Спасибо, полезная статья , очень помогла))

      Удалить
    3. Комментарий был удален автором, а автор комментария - Вы. Я Ваши комментарии не трогал :)

      Удалить
    4. Извиняюсь за столь просто вопрос, но как Вы добавили файлы *.designer.cs в вебформы? И еще в двух словах, если не трудно, зачем нужны hidden поля в рассматриваемом примере? Я начинающий .net-чик, поэтому многое для меня пока что непонятно)

      Удалить
    5. 1. Когда вы добавляете веб форму в проект, то автоматически добавляются 3 файла:
      Webform.aspx - разметка формы
      Webform.aspx.cs - обработчики событий и логика
      Webform.aspx.designer.cs - содержимое автоматически генерируется, в зависимости от того, что написано в разметке

      2. Поскольку веб приложения не имеют состояния, я не мог хранить нужные мне данные в полях классов. Поэтому я использовал скрытые поля. Можно также использовать куки, ViewState, сессию, приложение, профиль или базу данных для того, чтобы хранить какую-то информацию дольше одного постбека. Подробнее тут.

      Удалить
    6. Таки допилил) сейчас через ado.net пытаюсь вытащить данные с базы. Еще раз спасибо, блог закину в закладки, вдруг еще что интересно будет :)

      Удалить
  5. Здравствуйте!
    Может вы мне ответите.
    У меня в форме есть GridView id="GridView1", его DataSource установлен вручную, им служит некая таблица, кот. является результатом запроса. Есть также кнопка Button1, ее действие - постановка фильтра на эту таблицу (формирование предложения WHERE запроса). НО!-кнопка отрабатывает после того, как форма перезагрузится (у кнопки AutoPostBack="true")(я смотрел отладчиком), таким образом, таблица и GridView не фильтруются, т.е. последовательность событий такова Page_Load, Button1_Click(). Вопрос - как заставить кнопку отработать до загрузки GridView

    ОтветитьУдалить
    Ответы
    1. Вам, насколько я понял, и не нужно заставлять отрабатывать кнопку раньше. В качестве быстрого, но не самого оптимального, решения, вы можете в обработчике кнопки Button1_Click() получить новые данные, заново установить DataSource у грида и вызвать DataBind().

      Удалить
    2. Здравствуйте! Спасибо за быстрый ответ.
      >установить DataSource у грида
      Я должен сформировать и отработать новый запрос (на основании данных, полученных через кнопку)?

      Удалить
    3. Собственно, да. Насколько я понял, раз вы сами устанавливаете датасурс у грида, то даные, что он отображает, не изменятся, пока вы сами их не измените или не привяжете грид к другому набору данных (в вашем случае, к фильтрованному). То есть как я вижу логику:
      1. Вы закачиваете какие то данные в грид
      2. Пользователь на странице указывает параметры фильтрации и жмет кнопку
      3. На сервере в обработчике кнопки вы снова формируете набор данных, но уже фильтрованный, и привязываете к гриду

      Удалить
    4. >На сервере в обработчике кнопки вы снова формируете набор данных, но уже фильтрованный, и привязываете к гриду
      Я-то думал, для того и сделано событие перезагрузки страницы (в процедуре page_load), чтобы страницу отображать с новыми условиями и в этой процедуре менять DataSource.

      Удалить
    5. На самом деле, вы вольны сами выбирать, как и где обновлять данные. Page_Load нужна только для отработки логики при загрузке страницы на сервере, есть ещё много других событий. Почитайте про жизненный цикл страницы asp.net

      Удалить
    6. Здравствуйте! Извините, что пристаю к вам с "простыми" вопросами.
      Дело в том, что в GridView, созданном при помощи мастера, есть возможности edit (update), insert и delete записей (записи берутся из таблицы в базе данных). Так вот delete - делается, а edit (update)и insert -нет! Я уже голову сломал, может вы подскажете? Я смотрел текст, UpdateCommand и там есть, но, видимо, чего-то не хватает.Я бы предоставил текст, но не знаю, как это сделать.

      Удалить
    7. Ну я гадать не умею, покажите код страницы и разметки :).

      Удалить
    8. >покажите код страницы и разметки :).
      А как это сделать?

      Удалить
    9. Ну, грид у вас находится на какой то странице, верно? кроме дизайнера, там есть ещё разметка, а также файл для кода. Вот это и нужно показать (любым способом, хоть скриншот, хоть ссылку на файл, хоть оба файла сюда в комментарий поместите)

      Удалить
    10. Просто вставить код в комментарий?

      Удалить
    11. Почистил комментарии. Можете просто выслать мне на почту солюшен, tym32167@gmail.com. Ещё попробуйте в команде UPDATE указать просто UPDATE [$street] SET [ZIP] = @ZIP, [streetNAME] = @streetNAME, [streetSOCR] = @streetSOCR, [streetfull] = @streetfull WHERE [streetId] = @original_streetId. Если не заработает, будем дальше разбираться.

      Удалить
    12. Сделал, как вы сказали - действительно помогло! Но почему? Я все-таки вышлю вам исходники.Как заставить работать надпись New ("Вставить").
      Вам спасибо!
      Скажите, а отменить операцию "Delete" можно только на стороне клиента, при помощи Javascript, или можно на это сделать на сервере (ASP.NET)?

      Удалить
    13. там в гриде вроде есть события RowDeleted и RowDeleting (я точно не помню, как называются). Тот, который *ing, в этом событии можно отменить удаление на сервере.

      Удалить
  6. А как отменить? Сделать свойство IsPostBack=false?

    ОтветитьУдалить
    Ответы
    1. у меня ж в статье есть событие GvDeleting. Вот внутри обработчика этого события можно отменить удаление.

      Удалить
  7. Здравствуйте!
    А как сделать скроллинг в GridView вместо разбиения на страницы и как можно выбрать строку в качестве текущей без AutoGenerateSelectButton

    ОтветитьУдалить
    Ответы
    1. Здравствуйте.
      1. Про скролинг. Если вам надо вывалить все данные сразу на клиента - просто не используйте пейджинг. Если вы имеете ввиду динамическую подгрузку данных во время скроллинга - это называется "бесконечный скролинг" и легко гуглится (например, вот).
      2. Использовать свойство кнопки ButtonField.CommandName

      Удалить
  8. Не подскажите, как задать стиль Confirm окну, у меня выскакивает серое, яваскриптовское.

    ОтветитьУдалить
    Ответы
    1. Я в посте и использую яваскриптовское окно. Если хотите свое окно, то нужно менять обработчик нажатия кнопки удаления OnClientClick

      Удалить
    2. Вот меня и смущает что в коде обычный confirm, а окошко на скрине без шапки, рамок и цвета явно другие. Вот над этой строкой "Вот и всё. Данные можно изменять, сортировать, удалять." скрин.

      Удалить
    3. Просто на скринах я использовал браузер Firefox, там такие конфирмы :)

      Удалить
  9. Здравствуйте. Зарание прошу извининение за глупый вопрос.
    1. У меня есть клас подключенный к ObjectDataSource, GridView к ObjectDataSource. В класе я обращаюсь к таблице SELECT(который работает, таблица SQL) и DELETE. Так вот, DELETE не работает, хотя DataKeyNames задан, и в процидуру заходит(когда поставить точку останова), и правильно опредиляет праметр(тоесть если usid=3 то передает 3 и т.д.), и запрос на DELETE выполняется без ошибок, а строка не удаляется
    (OldValuesParameterFormatString ставлю как называется параметр, если удаляю то
    ObjectDataSource 'ObjectDataSource1' не может найти не групповой метод 'DeleteQuery', который имеет параметры: p,
    если ставлю {0}
    ObjectDataSource 'ObjectDataSource1' не может найти не групповой метод 'DeleteQuery', который имеет параметры: p, usid..)?
    2. У меня есть DataSet подключенный к ObjectDataSource, GridView к ObjectDataSource.
    В DataSet грамотно составляю запрос на DELETE,OldValuesParameterFormatString ставлю как называется параметр, выдает ошибку Invalid columne name "имя параметра", тоесть параметр не передается хотя DELETE вызывается и DataKeyNames задан. Запрос в TableAdapter простой
    DELETE FROM Table1
    WHERE (usid = p)
    параметр прописываю в Parametrs.
    Пожалуйста помогите.
    Зарание спасибо.

    ОтветитьУдалить
    Ответы
    1. Если в метод, где происходит удаление, код заходит и параметры передаются правильные, то проблема не в гриде, а в коде этого метода. Возможно, где то не вызвали SaveChanges (если используете какую-либо ORM). Попробуйте этот метод вызвать, к примеру, в обработчике нажатия кнопки, подставив идентификатор существующей записи. Если удаления не будет - то проблема в вашем коде. Также возможно ваш метод содержит конструкцию WHERE, которая не возвращает записей. В любом случае, без взгляда на код я вряд ли смогу помочь. Можете прислать мне те фрагменты кода, что не работают, мой ящик tym32167[@]gmail.com. Правда, оперативность гарантировать не могу, постараюсь поглядеть на неделе.

      Удалить
  10. Хотел еще сказать БОЛЬШОЕ СПАСАБО за статю!!!

    ОтветитьУдалить
  11. Артём Мурадов18 ноября 2012 г., 10:45

    Большое спасибо что удилили мне внимание. Я выслал своих 2 проекта из адреса vasya-shara@ukr.net.
    Буду очень рад если ответите.

    ОтветитьУдалить
  12. Большое спасибо за ответ! Я поставил, так как вы сказали - "@" в запросе возле параметра и все нормально работает.

    ОтветитьУдалить
  13. Здравствуйте! Очень полезная статья! Спасибо.
    Хотелось узнать как можно выполнять поиск по одному из столбцов списка, например, "Имя" ?

    ОтветитьУдалить
    Ответы
    1. Здравствуйте. Спасибо. GridView только отображает данные. Чтобы сделать поиск, нужно работать с источником данных. Например, в SqlDataSource это можно сделать с помощью QueryParameter или ControlParameter.

      Удалить
    2. Спасибо за оперативный ответ)

      Удалить
    3. Возник еще один вопрос: пытаюсь вставить поиск в вашем примере с автомобилями и не знаю к какому элементу применить QueryParameter или ControlParameter. Подскажите, пожалуйста?

      Удалить
    4. Обновил статью. Добавил информацию по фильтрации данных. Надеюсь, вам это поможет.

      Удалить
    5. Спасибо! Вы мне очень помогли.

      Удалить
  14. Здравствуйте! У меня есть тоже вопрос по GridView!
    1. GridView заполняется данными из БД с помощью SqlDataSource, DataKeyNames установлен значением Id
    2. При первом заполнении GridView строки редактируются и удаляются без ошибок
    3. На странице есть DropDownList с AutoPostBack который фильтрует записи по дате в зависимости от выбранного значения DropDownList (в обработчике DropDownList_OnSelectedIndexChanged я присваиваю SqlDataSource.SelectCommand новое значение и выполняю привязку)
    4. После фильтрации данные отображаются правильно, но записи не редактируются и не удаляются. После дебага оказалось, что коллекция DataKeyNames пустая.
    Если не сложно, подскажите что можно с этим сделать?

    ОтветитьУдалить
    Ответы
    1. Возможно, когда Вы присваиваете новое значение для SqlDataSource.SelectCommand, в этой команде не делаете селект на поле Id и это поле просто не приходит? Попробуйте внутри грида поставить скрытое поле (asp:hiddenfield) и привязать к нему Value = <%#Eval("Id")%>

      Удалить
  15. Спасибо за быстрый ответ!!! И за помощь большое спасибо!!!
    В GridView:


    />



    В обработчике SqlDataSource_Updating:
    int ridx = GridView1.EditIndex;
    HiddenField h = (HiddenField)GridView1.Rows[ridx].FindControl("HiddenField1");
    Int32 id = Convert.ToInt32(h.Value);
    e.Command.Parameters["@IdAdditional"].Value = id;

    Может у кого-то будет такая же проблема!!!

    Еще раз большое спасибо))))


    ОтветитьУдалить
  16. Не хочет печатать кусок кода, ну ладно(((

    ОтветитьУдалить
  17. Здравствуйте!
    Тут описано, как сделать поиск в базе. А мне надо не отфильтровать запись (записи), а перейти к нужной записи (я знаю ее уникальный ключ) и отобразить ее, например, в GridView, так что соседние записи тоже были бы видны (если я поставлю фильтр по этому ключу, то соседние записи отсекаются и не видны ). В WinForms это делала функция Find объекта BindingSource, привязанного к нужной таблице. А здесь?

    ОтветитьУдалить
    Ответы
    1. Здравствуйте. Тут вам необходимо сделать:
      1. Запрос к источнику данных такой, чтобы он возвращал и ваш элемент, и ещё элементы. Это сделать нетрудно, достаточно правильно сортировать и фильтровать.
      2. Для того, чтобы отметить в гриде строку как выбранную, необходимо указать индекс выбранной строки

      Удалить
    2. Спасибо за ответ. Но сортировка в базе данных означает нарушение "естественного" порядка записей, а мне надо его сохранить.
      По ходу дела у меня еще возник другой вопрос: есть источник данных для GridView, назовем его ds, он возвращает результаты некоего запроса, предложение SELECT этого запроса я могу узнать из свойства ds.SelectCommand, предложение WHERE из свойства ds.FilterExpression, а из какого свойства я узнаю каков порядок и поле сортировки, т.е. предложение ORDER BY?

      Удалить
    3. Понятия "естественного порядка" в базах данных нет, данные всегда приходят в каком то прядке (например, по дате добавления или идентификатору). И я не совсем понимаю, зачем вам узнавать параметры запроса, если вы должны их задавать сами? По поводу параметров - у меня пока нет возможности подебажить и поглядеть, попробуйте это сделать самостоятельно. Ну, или есть же документация для этого контрола.

      Удалить
  18. Здравствуйте Артём!Подскажите, как решить такую задачу для ASP.net
    Внутри GridView должны быть кнопки с надписями. При нажатии на кнопку внутри GridView я должен получать некое уникальное значение, позволяющее идентифицировать нажатую кнопку.
    Эти кнопки - не управляющие кнопки типа "выбрать" или "редактировать" строку, а суть сами данные.
    GridView формируется динамически, то есть в дизайне я ничего не создаю.
    Внутри создаются ButtonField, потом присваиваю DataSet.
    Пока мне удалось получить только номер строки, в которой было нажатие элемента.
    заранее спасибо.

    ОтветитьУдалить
    Ответы
    1. Здравствуйте. Вообще по номеру строки Вы можете узнать все, что Вам нужно, достаточно просто в какой нибудь невидимый столбец записать данные и потом, зная строку, поиском по контролам эти данные найти. Если нужно динамически прикрутить TemplateField, то это тоже можно (гуглим). Ну, и я накатал готовый примерчик, что можно кинуть на страничку и погонять (пример надуманный, просто как вариант)

      Удалить
    2. Спасибо за ответ, примеры посмотрю.
      Только не понял, как "зная строку, поиском по контролам эти данные найти."
      Возможно, ответ в примере.

      Удалить
  19. Добрый день, Артём
    По примерам получилось следующее:
    1. не вызывается обработка события bt_Click у меня почему-то.
    2. Использую поэтому обработку события в GridView:
    GridView1_RowCommand(object sender, GridViewCommandEventArgs e)

    Но оно возникает, если нажата в GridView кнопка типа ButtonField, а если использовать TemplateField то не срабатывает.

    ОтветитьУдалить
    Ответы
    1. Ещё момент, который я не указал.
      Динамически строятся также и столбцы. Их имя и количество заранее неизвестно.
      Их имена формируются динамически, например Name0, Name1 и т.д.
      Причём, при нажатии кнопки внутри GridView формируется новый набор данных, со своим количеством строк и столбцов.

      Удалить
    2. Прошу прощения, что задержал с ответом. Если ещё актуально, просто пришлите мне на мыло код, который не работает. Мыло есть в контактах.

      Удалить
  20. Артем, добрый день! Подскажите, пожалуйста как сделать сортировку GridView по определенным полям, если данные получены с помощью DataSet?

    ОтветитьУдалить
  21. Сортировка должна происходить при нажатии на заголовки GridView.

    ОтветитьУдалить
    Ответы
    1. Как то так:
      protected void gv_OnSorting(object sender, GridViewSortEventArgs e)
      {
      var ta = new Data.DataSet1TableAdapters.TestDataTableAdapter();
      var dataTable = ta.GetData();

      var prevSortExpression = ViewState["prevExpr"] as String;
      var prevSortDirection = ViewState["prevDir"] as String;

      var direction = string.Compare(prevSortExpression, e.SortExpression) == 0
      ? (string.Compare(prevSortDirection, "ASC") == 0 ? "DeSC" : "ASC")
      : "ASC";

      var dataview = new DataView(dataTable);
      dataview.Sort = e.SortExpression + " " + direction;

      ViewState["prevExpr"] = e.SortExpression;
      ViewState["prevDir"] = direction;

      gv.DataSource = dataview;
      gv.DataBind();
      }

      Удалить
  22. Добрый день! Артем, у меня данные получены с помощью DataSet, а не через Adapter.

    ОтветитьУдалить
    Ответы
    1. Ну через DataSet вы получаете DataTable? Если так, то логика то та ж самая. В противном случае, код в студию, или мне на мыло.

      Удалить
    2. Вот ещё вариант. Без типизированных датасетов
      protected void gv_OnSorting(object sender, GridViewSortEventArgs e)
      {
      using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["DbTestConnectionString"].ConnectionString))
      {
      var command = "SELECT [ID],[Name] FROM [DbTest].[dbo].[TestData]";

      var prevSortExpression = ViewState["prevExpr"] as String;
      var prevSortDirection = ViewState["prevDir"] as String;

      var direction = string.Compare(prevSortExpression, e.SortExpression) == 0
      ? (string.Compare(prevSortDirection, "ASC") == 0 ? "DeSC" : "ASC")
      : "ASC";

      var sortExpr = e.SortExpression + " " + direction;

      ViewState["prevExpr"] = e.SortExpression;
      ViewState["prevDir"] = direction;

      if (!string.IsNullOrEmpty(direction)) command += " ORDER BY " + sortExpr;

      var sqlCommand = new SqlCommand(command, conn);
      var dataAdapter = new SqlDataAdapter(sqlCommand);

      var ds = new DataSet();
      dataAdapter.Fill(ds);

      var table = ds.Tables[0];
      gv.DataSource = table;
      gv.DataBind();
      }

      Естественно, код надо будет допилить (например, избегать sql инъекций). Но суть должна быть ясна.

      Удалить
  23. Заработало вот в каком виде:

    private const string ASCENDING = " ASC";
    private const string DESCENDING = " DESC";

    public SortDirection GridViewSortDirection
    {
    get
    {
    if (ViewState["sortDirection"] == null)
    ViewState["sortDirection"] = SortDirection.Ascending;
    return (SortDirection)ViewState["sortDirection"];
    }
    set { ViewState["sortDirection"] = value; }
    }

    protected void gvMain_Sorting1(object sender, GridViewSortEventArgs e)
    {
    string sortExpression = e.SortExpression;
    if (GridViewSortDirection == SortDirection.Ascending)
    {
    GridViewSortDirection = SortDirection.Descending;
    SortGridView(sortExpression, DESCENDING);
    }
    else
    {
    GridViewSortDirection = SortDirection.Ascending;
    SortGridView(sortExpression, ASCENDING);
    }
    }


    private void SortGridView(string sortExpression, string direction)
    {
    DataSet MyData = GetMainTable();
    DataTable dt = MyData.Tables["Problems"];
    DataView dv = new DataView(dt);
    dv.Sort = sortExpression + direction;
    gvMain.DataSource = dv;
    gvMain.DataBind();

    }

    ОтветитьУдалить
    Ответы
    1. Будьте бдительны. Насколько я понял, в данном случае сортировка будет проходить уже после получения данных с сервера.

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

    ОтветитьУдалить
    Ответы
    1. Ну можно сделать фиксированную высоту контейнеру грида, или я не понял в чем вопрос, собственно.

      Удалить
  25. Добрый день! Добавил к таблице удалении строки. Первый раз удаление происходит без проблем, определяет ключ, но в последующие разы упорно ключи не определяет. Просто пишет пустую строку и все. Ну и собственно удаление не происходит. В чем тут проблема может быть?

    ОтветитьУдалить
    Ответы
    1. Все, разобрался. Глюк связан с скрытым поле ключей. Его нужно "проявлять" перед каждой процедурой удаления и тогда все норм)

      Удалить
    2. Здорово, что так быстро удалось разобраться :)

      Удалить
  26. Добрый день! Подскажите как правильно построить работу с GridView для:
    1. Перехода от страницы с DetailView с выбранными значения полей из GridView обратно на страницу с GridView.
    2. При переходе обратно GridView должен отобразить туже страницу что и до первоначального перехода на страницу с DetailView.
    Это может быть при просмотре детального содержимого со 2... N страницы GridView или при отображение GridView результатов поиска
    С уважением Юрий Косенко

    ОтветитьУдалить
    Ответы
    1. Так грид и DetailView можно же разместить на одной странице, и при выборе элемента в гриде отображать детализацию в Detail

      Удалить
    2. вопрос решен при помощи Session
      вот код
      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Web;
      using System.Web.UI;
      using System.Web.UI.WebControls;
      using DSTableAdapters;
      using System.IO;
      using Udove;
      using System.Data;

      public partial class NEWS_Session : System.Web.UI.Page
      {
      UsersPlanZakupTA upzTA = new UsersPlanZakupTA();
      protected void Page_Load(object sender, EventArgs e)
      {
      if (!IsPostBack)
      {
      gvPlanBind();
      }
      }
      private void gvPlanBind()
      {
      if (Session["sPlan"] != null)
      {
      gvPlan.DataSource = upzTA.GetData();
      gvPlan.PageIndex = Convert.ToInt32(Session["sPlan"]);
      }
      else
      {
      gvPlan.DataSource = upzTA.GetData();
      }
      gvPlan.DataBind();
      }

      protected void gvPlan_PageIndexChanging(object sender, GridViewPageEventArgs e)
      {
      gvPlan.PageIndex = e.NewPageIndex;
      Session["sPlan"] = e.NewPageIndex;
      gvPlanBind();
      }
      }

      Удалить
    3. Мое мнение - для передачи целого числа между страницами, можно было бы обойтись параметром запроса, типа http://.../News.aspx?page=1 . Сессия - это уже тяжелая артиллерия, и копить там лишние параметры не очень хорошо. Но если уж делаете через сессию, то
      1. Давайте ключам осмысленные значения, типа News_page_index
      2. Сохраняйте эти значения ключей в конфигах или статических константах, чтобы можно было такими значениями централизованно управлять.

      Удалить
  27. Артём спасибо за Ваше замечание - обязательно его учу при дальнейшей разработке.

    ОтветитьУдалить
  28. Добрый день!
    Подскажите, пожалуйста, у GridView есть возможность указать ShowInsertButton="true". После чего он отображает кнопку "вставить". Но не появляется никаких пустых колонок, для заполнения. Я для добавления использовал DetailsView у Вас в примере - текстовые поля. Грид не поддерживает вставку ? Для чего тогда кнопка ? Я нигде не могу найти информацию по этому поводу, к сожалению. Буду очень благодарен за разъяснение. Спасибо!

    ОтветитьУдалить
    Ответы
    1. Я уже некоторое время не использую GridView (и вообще Web Forms), потому Вам придется слегка погуглить, благо информации об этом вроде достаточно

      Удалить
    2. Спасибо, не правильно гуглил. ShowInsertButton свойство CommandField (не GridView как такового), но, и GridView и DetailsView используют CommandFields - но GridView не поддерживает вставку.

      Удалить
    3. Спасибо, что отписали. Думаю, другим читателям это тоже будет полезно знать.

      Удалить
  29. Этот комментарий был удален администратором блога.

    ОтветитьУдалить