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: |