Blog » Jak użyć klasy SPGridView ???

Jak użyć klasy SPGridView ???

 
Jak użyć klasy SPGridView ???
Poruszony temat jest szeroko opisywany na różnych innych blogach i moim zdaniem brakowało miejsca, w którym to wszystko by było zagregowane. Tematem głównym jest klasa SPGridView, dostępna w API od SharePointa. Jest to rozwinięcie standardowej .NET-wej klasy GridView więc poruszone kwestie przydadzą się również przy pisaniu zwykłej aplikacji ASP.NET. W tym poście pokarze jak z poziomu kodu C# podpiąć źródło, którym jest obiekt klasy ObjectDataSource, stworzyć własny szablon kolumny, włączyć stronicowanie i filtrowanie oraz rozwijane menu przy elemencie.

W celu prezentacji sposobu użycia klasy SPGridView utworzony zostanie składnik WebPart, który wyświetli dane za pomocą wzmiankowanej klasy. Dane będą pobierane z prostej listy zawierającej parę kolumn, które zostały opisane poniżej.
Pracę zaczynamy od utworzenia projektu typu ClassLibrary. Ze względu, na to że jest to projekt tworzony na SharePointa należy dodać referencje do biblioteki Microsoft.SharePoint System.Web. Należy też zmienić nazwę utworzonej automatycznie klasy na bardziej intuicyjną oraz ustawić by dziedziczyła z klasy System.Web.UI.WebControls.WebParts.WebPart. W celu instalacji całego rozwiązania należy przygotować odpowiednie pliki XML. Sposób instalacji jest standardowy więc nie będę go tu opisywał. Jak by pojawiły się jakieś problemy to proszę piszcie w komentarzach. Cały projekt na tę chwilę powinien wyglądać następująco:

W tym momencie mamy już prostego webparta zainstalowanego i wrzuconego na stronę w SharePoint. Pozostaje jedynie utworzenie źródła danych np.: w postaci listy. Dlatego też należy utworzyć zwykłą listę z następującymi kolumnami:

 - Count (typu Liczba),
 - Link (typu Pojedynczy wiersz tekstu),
 - Lookup (typu Odnośnik - do jakieś inne listy),
 - Hide (typu Pojedynczy wiersz tekstu),
 - Disable (typu Pojedynczy wiersz tekstu).

Gdy już mamy listę należy do niej dodać parę elementów. Wartości w kolumnach będą wykorzystywane w kolejnych zagadnieniach więc warto nadać takie same jak prezentowane poniżej:

Aby było łatwiej operować na danych należy stworzyć klasę z czterema właściwościami. Klasa będzie służyć jako obiekt danych podpinany do SPGridView. Przykład klasy wygląda następująco:
[Serializable]
public class ItemSource
{
   public string Title { get; set; }
   public string Count { get; set; }
   public string Link { get; set; }
   public string Lookup { get; set; }
   public string Hide { get; set; }
   public string Disable { get; set; }
}


Uwaga !!! Klasa musi być publiczna i mieć dodany parametr Serializable !!! 
Pierwszą kontrolką która jest do umieszczenia w WebParcie jak można się domyślić jest SPGridView. Po jej dodaniu należy wybrać jakie kolumny mają być wyświetlane.

Poniżej prezentowane są dwie metody utworzenia dedykowanego szablonu kolumny.

Najłatwiejszy sposób utworzenia kolumny zakłada wykorzystanie klasy SPBoundField (w zwykłym GridView jest klasa BoundField). W tym celu należy dodać nowy plik typu *.cs do projektu i ustawić dziedziczenie po wzmiankowanej klasie. Dzięki temu nasza klasa nabierze dodatkowych właściwości. Przykład kolumny wykonanej w ten sposób przedstawiony został poniżej:

namespace Nikolajuk.CustomGridView
{
    public class CustomSPBoundField : SPBoundField
    {
        protected override void ChildControlDataBinding(Control childControl, object dataItem, MemberDescriptor dataFieldPropertyDescriptor)
        {
            PlaceHolder placeHolder = (PlaceHolder)childControl;
            string value = this.GetPropertyValue(dataItem, dataFieldPropertyDescriptor.Name);
            
            if (string.IsNullOrEmpty(value))
            {
                return;
            }

            SPFieldLookupValue lookup = new SPFieldLookupValue(value);
            placeHolder.Controls.Add(new Literal() { Text = lookup.LookupValue });
        }

        protected override Control GetChildControlInstance()
        {
            return new PlaceHolder();
        }
    }
}


Do tej kolumny będziemy podpinać dane w formacie odnośnika (odnośnik jest zapisywany jako tekst w formacie np.: 34;#Odnośnik działa !!!). Kolumna sparsuje podane wyrażenie i wyświetli jedynie tekst czyli w podanym przykładzie: Odnośnik działa !!!. 

Innym sposobem zrobienia dedykowanej kolumny jest dziedziczenie z interfejsu ITemplate. Tym razem szablon zawierać będzie pole tekstowe i guzik. Zostanie zrobione to tak, że po wciśnięciu guzika użytkownik zostanie przekierowany na stronę zdefiniowaną w źródle danych, a w parametrze pid dołączonym do adresu URL będzie podawany tekst wpisany do pola tekstowego znajdującego się nad guzikiem. Aby stworzyć szablon kolumny należy dodać kolejny nowy plik klasy *.cs do projektu, a następnie dziedziczyć po interfejsie ITemplate (standardowy interfejs z .NETa):
 

namespace Nikolajuk.CustomGridView
{
    public class CustomTemplateGridView : ITemplate
    {
        public void InstantiateIn(Control container)
        {
            throw new NotImplementedException();
        }
    }

}


Metoda InstantiateIn służy do sterowania w jaki sposób ma być wyświetlana kolumna. 

Tak jak wspomniałem wcześniej dodajmy tutaj jedno pole tekstowe, a pod nim kontrolkę typu guzik. Całość można sformatować wykorzystując klasy odpowiedzialne za renderowanie HTMLa. Dodatkowo trzeba dodać pole typu HiddenField, które posłuży do przekazania linka do zdarzenia obsługi kliknięcia guzika. Oprócz sposobu wyświetlenia kontrolek w kolumnie należy zapewnić możliwość synchronizacji danych z kontrolką SPGridView (bindowanie danych). Do tego celu dodaje się zdarzenie do obiektu o nazwie container:
 

container.DataBinding += new EventHandler(container_DataBinding);

W ciele zdarzenia należy zdefiniować sposób podpinania danych:

 
DataControlFieldCell div = (DataControlFieldCell)sender;
SPGridViewRow gridViewRow = (SPGridViewRow)div.NamingContainer;
DataRowView text = (DataRowView)gridViewRow.DataItem;
tbCount.Text = text["Count"].ToString();
hf.Value = text["Link"].ToString();


Zadziwiające może być w tym miejscu użycie klasy DataRowView - przecież wcześniej zdefiniowaliśmy klasę do danych. Spowodowane jest to jest tym, że zasadniczo dane będą podpinane w postaci kolekcji DataTable. Wybranie tej, a nie innej klasy jest spowodowane tym, że z automatu wpiera sortowanie, filtrowanie itp.. Podobnie zresztą jak DataSet oraz DataView. Odpowiadając na pytanie: To po co nam klasa ItemSource ? Jest ona wygodniejsza do budowania obiektu z danymi i operowana na nich.

Po zdefiniowaniu sposobu podpięcia danych nie należy zapomnieć o obsłudze zdarzenia kliknięcia guzika:
 

Button imageButton = (Button)sender;
SPGridViewRow item = (SPGridViewRow)imageButton.NamingContainer;
HiddenField link = item.FindControl("hf") as HiddenField;
TextBox count = item.FindControl("tbCount") as TextBox;
string url = string.Format("{0}?pid={1}", link.Value, count.Text);
SPUtility.Redirect(url, SPRedirectFlags.DoNotEncodeUrl, HttpContext.Current);

Cała klasa po tych modyfikacjach wygląda następująco:
 
namespace Nikolajuk.CustomGridView
{
    public class CustomTemplateGridView : ITemplate
    {
        private TextBox tbCount;
        private Button btnClick;
        private HiddenField hf;

        public void InstantiateIn(Control container)
        {
            HtmlGenericControl divText = new HtmlGenericControl("div");
            tbCount = new TextBox();
            tbCount.ID = "tbCount";
            tbCount.Width = 100;
            divText.Controls.Add(tbCount);
            container.Controls.Add(divText);

            HtmlGenericControl divButton = new HtmlGenericControl("div");
            btnClick = new Button();
            btnClick.ID = "btnClick";
            btnClick.Text = "Zmień stronę";
            btnClick.Click += new EventHandler(btnClick_Click);
            divButton.Controls.Add(btnClick);
            container.Controls.Add(divButton);
            container.DataBinding += new EventHandler(container_DataBinding);

            hf = new HiddenField();
            hf.ID = "hf";
            container.Controls.Add(hf);
        }

        void container_DataBinding(object sender, EventArgs e)
        {
            DataControlFieldCell div = (DataControlFieldCell)sender;
            SPGridViewRow gridViewRow = (SPGridViewRow)div.NamingContainer;
            DataRowView text = (DataRowView)gridViewRow.DataItem;
            tbCount.Text = text["Count"].ToString();
            hf.Value = text["Link"].ToString();
        }

        void btnClick_Click(object sender, EventArgs e)
        {
            Button imageButton = (Button)sender;
            SPGridViewRow item = (SPGridViewRow)imageButton.NamingContainer;
            HiddenField link = item.FindControl("hf") as HiddenField;
            TextBox count = item.FindControl("tbCount") as TextBox;
            string url = string.Format("{0}?pid={1}", link.Value, count.Text);
            SPUtility.Redirect(url, SPRedirectFlags.DoNotEncodeUrl, HttpContext.Current);
        }
    }
}

Mając zdefiniowane szablony kolumn należy podpiąć dane do obiektu SPGridView. W tym celu należy przeciążyć metodę CreateChildControls() w klasie bazowej webparta. W pierwszej kolejności należy utworzyć obiekt który będzie odpowiadał za dostarczanie danych - w naszym przypadku jest to ObjectDataSource (referencja do niego jest globalna):
 
protected override void CreateChildControls()
{
    objectDataSource = new ObjectDataSource();
    objectDataSource.ID = "EventsObjectDataSource";
    objectDataSource.SelectMethod = "SelectData";
    objectDataSource.TypeName = this.GetType().AssemblyQualifiedName;
    objectDataSource.ObjectCreating += new ObjectDataSourceObjectEventHandler(objectDataSource_ObjectCreating);
    this.Controls.Add(objectDataSource);
}

void objectDataSource_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
{
      e.ObjectInstance = this;
}

Jak widać w kodzie zamieszczonym powyżej obiekt ObjectDataSource wymaga zdefiniowana metody która dostarczy dane. Przykład metody pobierającej dane z listy, którą utworzono wcześniej zamieszczony został poniżej
 
public DataTable SelectData()
{
            SPList listData = SPContext.Current.Web.Lists["Dane"];
            DataTable dataSource = new DataTable();
            dataSource.Columns.Add("Title");
            dataSource.Columns.Add("Count");
            dataSource.Columns.Add("Link");
            dataSource.Columns.Add("Lookup");
            //Kolumna odpowiedzialna z ukrywanie elementów menu
            dataSource.Columns.Add("Hide");
            //Kolumna odpowiedzialna z wyłączanie elementów menu
            dataSource.Columns.Add("Disable");
            foreach (SPListItem spListItem in listData.Items)
            {
                string title = spListItem["Title"] != null ? spListItem["Title"].ToString() : string.Empty;
                string count = spListItem["Count"] != null ? spListItem["Count"].ToString() : string.Empty;
                string link = spListItem["Link"] != null ? spListItem["Link"].ToString() : string.Empty;
                string lookup = spListItem["Lookup"] != null ? spListItem["Lookup"].ToString() : string.Empty;
                string hide = spListItem["Hide"] != null ? spListItem["Hide"].ToString() : string.Empty;
                string disable = spListItem["Disable"] != null ? spListItem["Disable"].ToString() : string.Empty;
                dataSource.Rows.Add(title, count, link, lookup, hide, disable);
            }
            return dataSource;
}


Jako ścieżkę podałem ścieżkę do pustej strony znajdującej się w bibliotece dokumentów.

Gdybyśmy podpinali obiekt klasy innej niż DataTable przy jednoczesnym włączeniu sortowania pojawiłby się błąd typu: 

The data source 'EventsObjectDataSource' does not support sorting with IEnumerable data. Automatic sorting is only supported with DataView, DataTable, and DataSet. 

Kolejnym krokiem jest zdefiniowanie właściwości obiektu SPGridView w metodzie CreateChildControls():
 

gridView = new SPGridView();
gridView.ID = "EventsGridView";
gridView.DataSourceID = objectDataSource.ID;
gridView.AutoGenerateColumns = false;
gridView.TemplateControl = null;

//Kolumna standardowa
BoundField firstField = new BoundField();
firstField.DataField = "Title";
firstField.HeaderText = "Nazwa";
firstField.SortExpression = "Title";
gridView.Columns.Add(firstField);

//Kolumna stworzona przez implemntację interfejsu ITemplate
TemplateField secondColumn = new TemplateField();
secondColumn.HeaderText = "Dedykowana kolumna";
CustomTemplateGridView customTemplateGridView = new CustomTemplateGridView();
secondColumn.ItemTemplate = customTemplateGridView;
secondColumn.SortExpression = "Count";
gridView.Columns.Add(secondColumn);

//Kolumna stworzona przez dziedziczenie po klasie SPBoundField
CustomSPBoundField thirdField = new CustomSPBoundField();
thirdField.DataField = "Lookup";
thirdField.HeaderText = "Odnośnik";
thirdField.SortExpression = "Lookup";
gridView.Columns.Add(thirdField);

this.Controls.Add(gridView);

Wszystkie kolumny pobierają dane z tego samego źródła, które zostało zbudowane na podstawie klasy ItemSource. Podsumowując, cała klasa webparta wygląda następująco:
namespace Nikolajuk.CustomGridView
{
    public class GridViewWebPart : System.Web.UI.WebControls.WebParts.WebPart
    {
        protected SPGridView gridView;
        protected ObjectDataSource objectDataSource;

        protected override void CreateChildControls()
        {
            objectDataSource = new ObjectDataSource();
            objectDataSource.ID = "EventsObjectDataSource";
            objectDataSource.SelectMethod = "SelectData";
            objectDataSource.TypeName = this.GetType().AssemblyQualifiedName;
            objectDataSource.ObjectCreating += new ObjectDataSourceObjectEventHandler(objectDataSource_ObjectCreating);
            this.Controls.Add(objectDataSource);

            gridView = new SPGridView();
            gridView.ID = "EventsGridView";
            gridView.DataSourceID = objectDataSource.ID;
            gridView.AutoGenerateColumns = false;
            gridView.TemplateControl = null;

            //Kolumna standardowa
            BoundField firstField = new BoundField();
            firstField.DataField = "Title";
            firstField.HeaderText = "Nazwa";
            firstField.SortExpression = "Title";
            gridView.Columns.Add(firstField);

            //Kolumna stworzona przez implemntację interfejsu ITemplate
            TemplateField secondColumn = new TemplateField();
            secondColumn.HeaderText = "Dedykowana kolumna";
            CustomTemplateGridView customTemplateGridView = new CustomTemplateGridView();
            secondColumn.ItemTemplate = customTemplateGridView;
            secondColumn.SortExpression = "Count";
            gridView.Columns.Add(secondColumn);

            //Kolumna stworzona przez dziedziczenie po klasie SPBoundField
            CustomSPBoundField thirdField = new CustomSPBoundField();
            thirdField.DataField = "Lookup";
            thirdField.HeaderText = "Odnośnik";
            thirdField.SortExpression = "Lookup";
            gridView.Columns.Add(thirdField);

            this.Controls.Add(gridView);
        }

        void objectDataSource_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
        {
            e.ObjectInstance = this;
        }

        public DataTable SelectData()
        {
            SPList listData = SPContext.Current.Web.Lists["Dane"];
            DataTable dataSource = new DataTable();
            dataSource.Columns.Add("Title");
            dataSource.Columns.Add("Count");
            dataSource.Columns.Add("Link");
            dataSource.Columns.Add("Lookup");
            //Kolumna odpowiedzialna z ukrywanie elementów menu
            dataSource.Columns.Add("Hide");
            //Kolumna odpowiedzialna z wyłączanie elementów menu
            dataSource.Columns.Add("Disable");
            foreach (SPListItem spListItem in listData.Items)
            {
                string title = spListItem["Title"] != null ? spListItem["Title"].ToString() : string.Empty;
                string count = spListItem["Count"] != null ? spListItem["Count"].ToString() : string.Empty;
                string link = spListItem["Link"] != null ? spListItem["Link"].ToString() : string.Empty;
                string lookup = spListItem["Lookup"] != null ? spListItem["Lookup"].ToString() : string.Empty;
                string hide = spListItem["Hide"] != null ? spListItem["Hide"].ToString() : string.Empty;
                string disable = spListItem["Disable"] != null ? spListItem["Disable"].ToString() : string.Empty;
                dataSource.Rows.Add(title, count, link, lookup, hide, disable);
            }
            return dataSource;
        }
    }
}

W tym miejscu mamy już w pełni funkcjonalne SPGridView, za pomocą którego można wyświetlać dane wykorzystując dedykowane kolumny. Cała kontrolka SPGridView prezentuje się następująco:
Tak przygotowana klasa jest fundamentem do jej dalszej rozbudowy. Pierwszy problem jaki się pojawia to taki, że często wyników które miałyby zostać wyświetlone jest bardzo dużo. Przydaje się wtedy oczywiście funkcja stronicowania. Można ją włączyć ustawiając następujące parametry:
 
//Stronicowanie
gridView.PagerTemplate = null;
gridView.AllowPaging = true; //kluczowa właściwość !!!
gridView.PageSize = 2; //Lista elementów na jednej stronie

Poniżej dodania obiektu SPGridView do kolekcji kontrolek na stronie należy dodać do tej samej kolekcji obiekt klasy SPGridViewPager (nie mogłem znaleźć klasy o podobnej funkcjonalności w GridView).
Po tych zmianach metoda CreateChildControls() wygląda następująco:
 
protected override void CreateChildControls()
{
    objectDataSource = new ObjectDataSource();
    objectDataSource.ID = "EventsObjectDataSource";
    objectDataSource.SelectMethod = "SelectData";
    objectDataSource.TypeName = this.GetType().AssemblyQualifiedName;
    objectDataSource.ObjectCreating += new ObjectDataSourceObjectEventHandler(objectDataSource_ObjectCreating);
    this.Controls.Add(objectDataSource);

    gridView = new SPGridView();
    gridView.ID = "EventsGridView";
    gridView.DataSourceID = objectDataSource.ID;
    gridView.AutoGenerateColumns = false;
    gridView.TemplateControl = null;
    //Stronicowanie
    gridView.PagerTemplate = null;
    gridView.AllowPaging = true; //kluczowa właściwość !!!
    gridView.PageSize = 2; //Lista elementów na jednej stronie

    //Kolumna standardowa
    BoundField firstField = new BoundField();
    firstField.DataField = "Title";
    firstField.HeaderText = "Nazwa";
    firstField.SortExpression = "Title";
    gridView.Columns.Add(firstField);

    //Kolumna stworzona przez implemntację interfejsu ITemplate
    TemplateField secondColumn = new TemplateField();
    secondColumn.HeaderText = "Dedykowana kolumna";
    CustomTemplateGridView customTemplateGridView = new CustomTemplateGridView();
    secondColumn.ItemTemplate = customTemplateGridView;
    secondColumn.SortExpression = "Count";
    gridView.Columns.Add(secondColumn);

    //Kolumna stworzona przez dziedziczenie po klasie SPBoundField
    CustomSPBoundField thirdField = new CustomSPBoundField();
    thirdField.DataField = "Lookup";
    thirdField.HeaderText = "Odnośnik";
    thirdField.SortExpression = "Lookup";
    gridView.Columns.Add(thirdField);

    this.Controls.Add(gridView);

    SPGridViewPager pager = new SPGridViewPager();
    pager.GridViewId = gridView.ID;
    this.Controls.Add(pager);
}

Otrzymany efekt prezentowany jest poniżej:
Oczywiście to na początek wystarczy ale szybko pojawia się dużo pomysłów na temat jak zmienić stronicowanie, to znaczy zmienić sposób wyświetlania liczby stron itp. W pierwszej kolejności można podpiąć klasy CSS ale to nie zawsze wystarcza. Innym wyjściem jest napisanie własnej klasy odpowiadającej za stronicowanie. Wtedy mamy pełną możliwość wpłynięcia na sposób wyświetlania stron w naszym SPGridView. Aby to zrobić należy dodać do projektu kolejny plik *.cs dla klasy. Klasa ta musi dziedziczyć po SPGridViewPager:
 
namespace Nikolajuk.CustomGridView
{
    public class CustomSPGridViewPager : SPGridViewPager
    {

    }
}

Aby móc wpłynąć na sposób wyświetlenia stronicowania trzeba przeciążyć metodę Render w tej klasie. Dodatkowo aby sterować prawidłowym przechodzeniem ze strony do strony to potrzebujemy właściwości: PageIndex, PageCount. W przykładzie podmieniłem standardowe strzałeczki na inne załączone do projektu. Cała klasa wygląda następująco:
namespace Nikolajuk.CustomGridView
{
    public class CustomSPGridViewPager : SPGridViewPager
    {
        protected override void Render(System.Web.UI.HtmlTextWriter output)
        {
            if ((this.GridViewControl != null) && (this.PreviousPageLinkIsEnabled || this.NextPageLinkIsEnabled))
            {
                this.RenderImage(output, this.PreviousPageLinkIsEnabled, "previouspage", "/_layouts/images/Nikolajuk.CustomGridView/prev.png", "/_layouts/images/blank.gif", "Poprzednia strona");
                output.Write(string.Format("Strona {0} z {1}", this.GridViewControl.PageIndex.ToString(), this.GridViewControl.PageCount.ToString()));
                this.RenderImage(output, this.NextPageLinkIsEnabled, "nextpage", "/_layouts/images/Nikolajuk.CustomGridView/next.png", "/_layouts/images/blank.gif", "Następna strona");
            }
        }

        private bool PreviousPageLinkIsEnabled
        {
            get
            {
                if (this.GridViewControl != null)
                {
                    if (this.GridViewControl.PageIndex > 0)
                    {
                        return true;
                    }
                }
                return false;
            }
        }

        private bool NextPageLinkIsEnabled
        {
            get
            {
                if (this.GridViewControl != null)
                {
                    if (this.GridViewControl.PageIndex < (this.GridViewControl.PageCount - 1))
                    {
                        return true;
                    }
                }
                return false;
            }
        }

        private void RenderImage(HtmlTextWriter output, bool isEnabled, string postbackEventArgument, string enabledImageUrl, string disabledImageUrl, string toolTip)
        {
            string str = disabledImageUrl;
            if (isEnabled)
            {
                output.Write("");
str = enabledImageUrl;
}
output.Write("\"");");
if (isEnabled)
{
output.Write("
"); } } } }

Oczywiście, żeby właśnie ta klasa została użyta do wyświetlenia stronicowania należy zamienić linię w metodzie CreateChildControls():
SPGridViewPager pager = new SPGridViewPager();

na
CustomSPGridViewPager pager = new CustomSPGridViewPager();

Należy szczególną uwagę zwrócić na ścieżki do plików z obrazami. Otóż ścieżki podawane są w następujący sposób: 
    - "/_layouts/images/Nikolajuk.CustomGridView/prev.png" - strzałka skierowana w lewo,  
    - "/_layouts/images/Nikolajuk.CustomGridView/next.png" - strzałka skierowana w prawo. 

A jeśli chodzi o strukturę w katalogu 12 to przedstawia się następująco:  
Po uruchomieniu tego kodu efekt przedstawia się następująco:
Mając w pełni opanowane stronicowanie, kolejne wyzwanie jakie przed nami stoi to sortowanie z filtrowaniem. Aby to zrobić w SPGridView należy ustawić właściwości (sortowanie i filtrowanie w prezentowane poniżej dostępne jest tylko w SPGridView):
 
//Filtrowanie i sortowanie
gridView.AllowFiltering = true;
gridView.AllowSorting = true;
gridView.FilterDataFields = "Title,Count,Lookup";
gridView.FilteredDataSourcePropertyName = "FilterExpression";
gridView.FilteredDataSourcePropertyFormat = "{1} = '{0}'";

W tej chwili klikając na nagłówek kolumny rozwinie się menu z opcjami sortowania i filtrowania. Dzięki temu, że użyliśmy DataTable nie musimy pisać obsługi zdarzeń sortowania i filtrowania:
Niestety SPGridView same nie dodaje informacji o tym na jakiej kolumnie włączone jest filtrowanie. W standardowych widokach SharePointowych pojawia się jednak wtedy ikonka przedstawiająca "lejek". Można to również zrobić w naszym SPGridView. W tym celu należy posłużyć się zdarzeniem GridViewRowEventHandler:
 
gridView.RowDataBound += new GridViewRowEventHandler(gridView_RowDataBound);

W ciele metody odpowiedzialnej za zdarzenie należy w odpowiednim momencie dodać ikonkę do nagłówka kolumny. W SharePoint taka ikonka jak wspomniałem wcześniej już istnieje więc ją wykorzystamy. Sposób implementacji metody wygląda następująco:
void gridView_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if ((sender != null) && (e.Row.RowType == DataControlRowType.Header))
    {
        string filteredColumn = ((SPGridView)sender).FilterFieldName;
        this.SetGridViewFilterIcon(filteredColumn, e.Row);
    }
}

private void SetGridViewFilterIcon(string filteredColumn, GridViewRow gridViewRow)
{
    if (string.IsNullOrEmpty(filteredColumn) || gridViewRow == null)
    {
        return;
    }

   for (int i = 0; i < this.gridView.Columns.Count; i++)
   {
       DataControlField field = this.gridView.Columns[i];

       if (field.SortExpression != filteredColumn)
       {
           continue;
       }

       Image filterIcon = new Image();
       filterIcon.ImageUrl = "~/_layouts/images/filter.gif";
       filterIcon.ImageAlign = ImageAlign.Left;
       filterIcon.Style[HtmlTextWriterStyle.MarginTop] = "2px";
       filterIcon.ID = "filterIcon";

       Panel panel = new Panel();
       panel.Controls.Add(filterIcon);

       gridViewRow.Cells[i].Controls.Add(panel);

       break;
   }
}

Warto zwrócić uwagę na ten fragment:
if (field.SortExpression != filteredColumn)

filteredColumn - jest parametrem metody i zwraca nazwę wewnętrzną kolumny !!! . Czyli w naszym przypadku: Title,Count,Lookup. Oprócz właściwości SortExpression nie ma innego pojemnika na wewnętrzną nazwę kolumny. W reszcie właściwości zapamiętana jest nazwa wyświetlana, czyli dla naszego przypadku odpowiednio: Nazwa, Dedykowana kolumna, Odnośnik. 
Dla porządku wspomnę tylko w jaki sposób można pogrupować dane, otóż robi się to przez ustawienie następujących właściwości:
//Grupowanie
gridView.AllowGrouping = true;
gridView.AllowGroupCollapse = true;
gridView.GroupField = "Title";
gridView.GroupDescriptionField = "Title";
gridView.GroupFieldDisplayName = "Nazwa:";

Właściwości są oczywiste więc nie będę ich opisywał. Idąc dalej dochodzimy do wniosku, że do pełni szczęścia brakuje nam już tylko menu rozwijanego przy elemencie (ta funkcja również dostępna jest tylko w SPGridView). Robi się je równie prosto jak poprzednie funkcje. Przyjmijmy, że menu rozwijane chcemy ustawić na kolumnie o nazwie Title dlatego należy zmodyfikować deklaracje tej kolumny w następujący sposób: Należy zamienić kod:
//Kolumna standardowa
BoundField firstField = new BoundField();
firstField.DataField = "Title";
firstField.HeaderText = "Nazwa";
firstField.SortExpression = "Title";
gridView.Columns.Add(firstField);

na
MenuTemplate presenterListMenu = new MenuTemplate();
presenterListMenu.ID = "TitleListMenu";
MenuItemTemplate biogMenu = new MenuItemTemplate("Idź do strony testowej", "/_layouts/images/ADDCOL.GIF");
biogMenu.ClientOnClickNavigateUrl = "http://omega/sites/PGS/Dokumenty%20i%20strony/Test.aspx?this=%EDIT%&that=%NAME%";
presenterListMenu.Controls.Add(biogMenu);
this.Controls.Add(presenterListMenu);

SPMenuField firstField = new SPMenuField();
firstField.TextFields = "Title";
firstField.HeaderText = "Nazwa";
firstField.MenuTemplateId = "TitleListMenu";
firstField.NavigateUrlFields = "Count,Title, Lookup";
firstField.NavigateUrlFormat = "http://omega/sites/PGS/Dokumenty%20i%20strony/Test.aspx?p={0}&q={1}";
firstField.TokenNameAndValueFields = "EDIT=Count,NAME=Title";
firstField.SortExpression = "Title";
gridView.Columns.Add(firstField);

W efekcie uzyskujemy obraz przedstawiony poniżej:
Proszę też zwrócić uwagę w jaki sposób przekazuje się parametry do strony na którą użytkownik zostanie przekierowany po wybraniu pozycji w menu.
Istnieje też możliwość zrobienia dynamicznego menu tzn, takiego które liczbę pozycji w menu uzależnia od wartości w którejś kolumnie listy. Aby to zrobić należy ponownie zmodyfikować kod:
 
MenuTemplate presenterListMenu = new MenuTemplate();
presenterListMenu.ID = "TitleListMenu";
MenuItemTemplate biogMenu = new MenuItemTemplate("Idź do strony testowej", "/_layouts/images/ADDCOL.GIF");
biogMenu.ClientOnClickNavigateUrl = "http://omega/sites/PGS/Dokumenty%20i%20strony/Test.aspx?this=%EDIT%&that=%NAME%";
presenterListMenu.Controls.Add(biogMenu);
this.Controls.Add(presenterListMenu);

SPMenuField firstField = new SPMenuField();
firstField.TextFields = "Title";
firstField.HeaderText = "Nazwa";
firstField.MenuTemplateId = "TitleListMenu";
firstField.NavigateUrlFields = "Count,Title, Lookup";
firstField.NavigateUrlFormat = "http://omega/sites/PGS/Dokumenty%20i%20strony/Test.aspx?p={0}&q={1}";
firstField.TokenNameAndValueFields = "EDIT=Count,NAME=Title";
firstField.SortExpression = "Title";
gridView.Columns.Add(firstField);

na
MenuTemplate presenterListMenu = new MenuTemplate();
presenterListMenu.ID = "TitleListMenu";
MenuItemTemplate biogMenu = new MenuItemTemplate("Idź do strony testowej", "/_layouts/images/ADDCOL.GIF");
biogMenu.ID = "biogMenu";
biogMenu.ClientOnClickNavigateUrl = "http://omega/sites/PGS/Dokumenty%20i%20strony/Test.aspx?this=%EDIT%&that=%NAME%";
presenterListMenu.Controls.Add(biogMenu);

MenuItemTemplate hideMenu = new MenuItemTemplate("To będzie czasem ukryte", "/_layouts/images/ADDCOL.GIF");
hideMenu.ID = "hideMenu";
hideMenu.ClientOnClickNavigateUrl = "http://omega/sites/PGS/Dokumenty%20i%20strony/Test.aspx?this=%EDIT%&that=%NAME%";
presenterListMenu.Controls.Add(hideMenu);

MenuItemTemplate disableMenu = new MenuItemTemplate("To będzie czasem wyłączone", "/_layouts/images/ADDCOL.GIF");
disableMenu.ID = "disableMenu";
disableMenu.ClientOnClickNavigateUrl = "http://omega/sites/PGS/Dokumenty%20i%20strony/Test.aspx?this=%EDIT%&that=%NAME%";
presenterListMenu.Controls.Add(disableMenu);

this.Controls.Add(presenterListMenu);

SPMenuField firstField = new SPMenuField();
firstField.TextFields = "Title";
firstField.HeaderText = "Nazwa";
firstField.MenuTemplateId = "TitleListMenu";
firstField.NavigateUrlFields = "Count,Title, Lookup";
firstField.NavigateUrlFormat = "http://omega/sites/PGS/Dokumenty%20i%20strony/Test.aspx?p={0}&q={1}";
firstField.TokenNameAndValueFields = "EDIT=Count,NAME=Title";
firstField.SortExpression = "Title";
//Ustawienie kolumny odpowiedzialnej za ukrywanie elementów menu
firstField.HiddenMenuItemIdsFields = "Hide";
//Ustawienie kolumny odpowiedzialnej za wyłączanie elementów menu
firstField.DisabledMenuItemIdsFields = "Disable";
gridView.Columns.Add(firstField);

Tak skonfigurowana kolumna wyświetla pozycję w rozwijanym menu w następujący sposób
Zrzut Wartość kolumny Hide Wartość kolumny Disabled
   
hideMenu  
  hideMenu,disableMenu
 

Gdy mamy wiele pozycji w menu rozwijanym czasami zachodzi potrzeba zrobienia linii oddzielającej niektóre pozycje od reszty. Taką linię robi się w następujący sposób:
 

MenuSeparatorTemplate sepMenu = new MenuSeparatorTemplate();
presenterListMenu.Controls.Add(sepMenu);

To już koniec tego najdłuższego posta jaki kiedykolwiek napisałem. 

Dziękuję, że dotrwaliście do końca. Źródła i materiały podlinkowane są poniżej.  

1. Dobry opis najważniejszych właściwości SPGridView + opis ograniczeń jakie niesie z sobą użycie tej kontrolki
 
http://blog.gutek.pl/post/2008/09/24/SPGridView-sposob-wykorzystania-oraz-ograniczenia.aspx

2. Sposób na szybkie podpięcie danych z listy SharePoint 

http://sharethispoint.com/archive/2006/09/05/UsingtheSharePointv3DataGridControlSPGridView.aspx

3. Strona źródłowa pokazująca w jaki sposób szybko zrobić własną kontrolkę do stronicowania 

http://ketulpatel.wordpress.com/2008/06/06/custom-paging-in-spgridview-extending-spgridviewpager/ 

4. Dokładny opis jak włączyć filtrowanie 

http://kitmenke.com/blog/2009/09/05/sharepoints-spgridview-filtering-and-apostrophes/ 

5. Opis jak zrobić menu rozwijane 

http://blogs.msdn.com/powlo/archive/2007/02/25/displaying-custom-data-through-sharepoint-lists-using-spgridview-and-spmenufield.aspx

6. Ciekawy pomysł zaimplementowany w jQuery jak zrobić filtrowanie elementów widoku 

http://instantlistfilter.codeplex.com/


UWAGA !!! - Kod źródłowy 

 
 

Dodany: 2010-03-25 23:37:33 przez Michał Nikołajuk | Wypowiedzi: 4
Dodaj do Yahoo Bookmarks Dodaj do Facebook Dodaj do Twitter Dodaj do Live Dodaj do Yahoo MyWeb Dodaj do MySpace Dodaj do Google Bookmarks
Komentarze
El_Topo
Witam,
Kod poszedł na Twojego maila na ms-groups.
2010-04-07 22:37:07
Michał Nikołajuk
Hej,
Coś mi z tym nie idzie. Proszę jednak podeślij mi swój kod.
Pozdrawiam
2010-04-06 23:17:18
Michał Nikołajuk
Hej El Topo,
dzięki za zwrócenie uwagi, oczywiście dodam to do tego posta. Co do kodów źródłowych to powinienem sobie poradzić :)
2010-04-01 14:17:06
El Topo
Witam, Bardzo pomocny post.
A skoro agregujemy rzeczy dotyczące SPGridview, proponuję dodanie informacji ukrywaniu/wyłączaniu elementów z SPMenuField. Świetny post w sprawie tego tematu jest tutaj: http://blog.ksenthil.net/archive/2009/07/27/a-control-with-the-specified-id-could-not-be-found-again.aspx
Daj znać to podeślę twój kod zmodyfikowany tak, żeby dobrze zaprezentować sprawę.
2010-04-01 02:33:23
Zostaw komentarz Subskrybuj



 Security code