I have created a CompositeDataBoundControl that i can databind perfectly well.
Now i want to do the same thing, but not for a list of objects, but for a single object.
Reasons is that i want my colleagues to have the ability to simply use the <%# Eval("X") %> in their front end code.
The problem is that the CompositeDataBoundControl has a method that i have to override, which only accepts a collection as a datasource
CreateChildControls(System.Collections.IEnumerable dataSource, bool dataBinding)
Is there a way to do the same thing for a single object?
You need to override the DataSource property
public class SingleObjectView : CompositeDataBoundControl
{
private object dataSource;
public override object DataSource
{
get { return new List<object> { dataSource }; }
set { dataSource = value; }
}
protected override int CreateChildControls(System.Collections.IEnumerable dataSource, bool dataBinding)
{
SingleItem singleItem = null;
if (dataBinding)
{
var it = dataSource.GetEnumerator();
it.MoveNext();
singleItem = new SingleItem(it.Current);
}
else
{
singleItem = new SingleItem(null);
}
ItemTemplate.InstantiateIn(singleItem);
Controls.Add(singleItem);
if (dataBinding)
singleItem.DataBind();
return 1;
}
[PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(SingleItem))]
public ITemplate ItemTemplate { get; set; }
}
public class SingleItem : Control, IDataItemContainer
{
public SingleItem(object dataItem)
{
DataItem = dataItem;
}
public object DataItem { get; set; }
public int DataItemIndex
{
get { return 0; }
}
public int DisplayIndex
{
get { return 0; }
}
}
edit to show the usage
<ctrl:SingleObjectView ID="productView" runat="server">
<ItemTemplate>
<%# Eval("ProductName") %>
<br />
<%# Eval("Price","{0:c}") %>
</ItemTemplate>
</ctrl:SingleObjectView>
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
productView.DataSource = new { ProductName="My Product", Price="10"};
productView.DataBind();
}
}
So the DataSource receives an object which doesn't implement IEnumerable, but overriding the DataSource property of the SingleObjectView control, ensures that CreateChildControls is called.
What about create a list of control, add one single object and then call the method?
I use FormView to allow an instance of a single object to be templated:
protected void fvProduct_Init(object sender, EventArgs e)
{
// Load product template
fvProduct.ItemTemplate = LoadTemplate("Templates/ProductTemplate.ascx");
// Bind product to data source
fvProduct.DataSource = new[] { Product };
fvProduct.DataBind();
}
Then in ProductTemplate.ascx you can do stuff like this:
<h1><%# Eval("ProductName") %></h1>
<p><%# Eval("Description") %></p>
<h4><%# Eval("Price", "{0:C}")%></h4>
In your scenario you mentioned you were using a custom control but the idea is basically the same as the answers above. You just create an enumerable with one item and bind it. The reason for using a FormView is because it is designed to only display exactly one item at a time.
Related
I need to populate a set of repeater drop down list items from a custom class but cannot seem to figure it out. It works perfectly when I use just a standard ListItem collection, but for some reason my custom class only returns a bunch of empty drop down lists. I'm new to repeaters and am trying to understand them the best I can, but this one kind of has me beat. Code is shown below. I have a base class that contains a child class, the child class is used to populate the drop down lists. When ListItemOptions is set to just a List<ListItem>', the actual type ofListItem', it works perfect. Why doesn't my repeater like my class?
// My base class. FYI: there are about 20 other properties in this class. I'm not just aimlessly jumping to a child class for fun!
public class ConfiguratorOption
{
private List<ListItemOptions> _listItemOptions = new List<ListItemOptions>();
public ConfiguratorOption() { }
public List<ListItemOptions> ListItemOptions { get { return _listItemOptions; } set { _listItemOptions = value; } }
public void AddListItemOption(string nodeId, string value, string displayName)
{
ListItemOptions lioNew = new ListItemOptions();
NodeID = nodeId;
Value = value;
DisplayName = displayName;
ListItemOptions.Add(lioNew);
}
}
// The child class.
public class ListItemOptions : IEnumerable
{
private string _nodeID = string.Empty;
private string _displayName = string.Empty;
private string _value = string.Empty;
public ListItemOptions() { }
public string NodeID { get { return _nodeID; } set { _nodeID = value; } }
public string Value { get { return _value; } set { _value = value; } }
public string DisplayName { get { return _displayName; } set { _displayName = value; } }
public IEnumerator GetEnumerator()
{
return (IEnumerator)this;
}
}
// The infamous repeater...
<asp:Repeater runat="server" ID="rptConfigurationOptions">
<ItemTemplate>
<div class="ConfigurationBlock">
<asp:DropDownList ID="ddnOption" runat="server" CssClass="DropDownStandard" style="width: 500px;" DataTextField="DisplayName" DataValueField="Value" AutoPostBack="true"
DataSource='<%# Eval("ListItemOptions") %>' OnSelectedIndexChanged="ddnOption_SelectedIndexChanged" ></asp:DropDownList>
</div>
</ItemTemplate>
</asp:Repeater>
// And the repeater code..
private void BindRepeater<T>(Repeater rpt, List<T> cOp)
{
rpt.Controls.Clear();
rpt.DataSource = cOp;
rpt.DataBind();
}
You can't use Eval this way. Eval will essentially evaluate the property and return a string. In your case that could render out looking like:
<asp:dropdown DataSource='List<ListItemOptions>'/>
Don't set the datasource via Eval. Handle the repeater's ItemDataBound event and set the DropDownList's datasource manually to the actual property.
I have created a custom control to implement a table we use all over the place. It has been working fine for a while and now I was asked to add a new column type that would allow any valid asp.net markup to be put in it and that markup will be rendred within that table column.
I created the following class:
[PersistChildren(false), ParseChildren(true)]
public class UserDefinedMarkupColumn : GridColumnBase
{
private Collection<WebControl> _innerMarkup = new Collection<WebControl>();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[PersistenceMode(PersistenceMode.InnerProperty)]
public virtual Collection<WebControl> InnerMarkup { get { return _innerMarkup; } }
public override System.Web.UI.WebControls.TableCell CreateCell(object dataSourceObject, GridColumnBase col, out CustomGridRow extraRow, Action<object, System.Web.UI.WebControls.CommandEventArgs> handler, bool isMobileLayout = false)
{
TableCell newCell = new TableCell();
foreach (var ctrl in _innerMarkup)
{
newCell.Controls.Add(ctrl);
}
extraRow = null;
return newCell;
}
}
and this markup for the column:
<CustomGrid:UserDefinedMarkupColumn ID="markup" HeaderText="Some Markup">
<InnerMarkup>
<asp:TextBox runat="server" />
</InnerMarkup>
</CustomGrid:UserDefinedMarkupColumn>
The problem is that when I hit CreateCell the _innerMarkup collection is always empty. I saw several samples using a ControlsCollection but the issue is that GridColumnBase does not inherit from Control, it creates a TableCell which does.
EDIT: I also tried adding a PlaceHolder control and using it's ControlsCollection, but it is empty as well.
private PlaceHolder _innerControl = new PlaceHolder();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public virtual ControlCollection InnerMarkup { get { return _innerControl.Controls; } }
I'm creating a custom gridview control for my ASP.NET APP and one of the things I want it to create a new custom type of column in order to do the following:
(This is how it looks now to create a column)
<asp:TemplateField>
<HeaderTemplate>
<asp:LinkButton Text="R. Name" ToolTip="Resource Name" CommandName="Sort" CommandArgument="ResourceName"
runat="server" />
<uc:GridViewFilter ID="ucGridViewFilterResourceName" ColumnName="ResourceName" AssociatedControlType="TextBoxString"
OnFilterApplied="ucGridViewFilter_FilterApplied" runat="server" />
</HeaderTemplate>
<ItemTemplate>
<%# Eval("ResourceName") %>
</ItemTemplate>
I would like to have something like this:
<asp:GridViewExColumn HeaderTitle="R. Name" HeaderToolTip="Resource Name" ColumnName="ResourceName" SearchType="TextBoxString" OnFilterApplied="ucGridViewFilter_FilterApplied" Text='<%# Eval("ResourceName") %>' />
Any one can show some light at the end of the tunnel? I'm totally lost, I could successfully create my custom gridview but don't know of where to start with this custom column type. Thanks
I finally found the way to do what I was looking for, and I'll provide you with my founds and maybe you can even make it better.
I created a new .cs extending the control DataControlField like this:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Web.UI;
using System.Web.UI.WebControls;
using VMPortal.Tools;
public class GridViewExColumn : DataControlField
{
public string HeaderToolTip { get; set; }
public string GridViewID { get; set; }
public string SearchType { get; set; }
public string DataField
{
get
{
object value = ViewState["DataField"];
if (value != null)
return value.ToString();
return string.Empty;
}
set
{
ViewState["DataField"] = value;
OnFieldChanged();
}
}
protected override DataControlField CreateField()
{
return new BoundField();
}
public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
{
base.InitializeCell(cell, cellType, rowState, rowIndex);
if (cellType == DataControlCellType.Header)
{
var lb = new LinkButton
{
Text = HeaderText,
ToolTip = String.IsNullOrWhiteSpace(HeaderToolTip) ? HeaderText : HeaderToolTip,
CommandName = "Sort",
CommandArgument = DataField
};
var tt = cell.Controls;
cell.Controls.Add(lb);
if (!String.IsNullOrWhiteSpace(SearchType))
{
// Add filter control
var ctrlFilter = Control.Page.LoadControl("~/Controls/GridViewFilter.ascx") as GridViewFilter;
cell.Controls.Add(ctrlFilter);
ctrlFilter.ID = GridViewID + DataField;
ctrlFilter.ColumnName = DataField;
ctrlFilter.AssociatedControlType = SearchType;
ctrlFilter.FilterApplied += ctrlFilter_FilterApplied;
}
}
else if (cellType == DataControlCellType.DataCell)
cell.DataBinding += new EventHandler(cell_DataBinding);
}
protected void cell_DataBinding(object sender, EventArgs e)
{
var cell = (TableCell)sender;
var dataItem = DataBinder.GetDataItem(cell.NamingContainer);
var dataValue = DataBinder.GetPropertyValue(dataItem, DataField);
string value = dataValue != null ? dataValue.ToString() : "";
cell.Text = value;
}
protected void ctrlFilter_FilterApplied(object sender, EventArgs e)
{
var filterExpression = sender as FilterExpression;
if (filterExpression != null)
{
if (FilterApplied != null)
FilterApplied(null, EventArgs.Empty);
}
}
Now I can add a new column like the follow
<uc:GridViewExColumn HeaderText="R. Name" HeaderToolTip="Resource Name" DataField="ResourceName" SearchType="TextBoxString" GridViewID="ucGridViewEx" OnFilterApplied="ucGridViewFilter_FilterApplied" />
Hope it helps someone else.
I'm not sure if this is the best way to go about this but I'm trying to show some search results using a repeater control. The query is written in the code behind page and i cant figure out how to bind the results to the control. So far I've created a list of saleItem objects, the saleItem object contains the string i want to show in the repeater.
Search.aspx.cs
List<SaleItem> resultsList = new List<SaleItem>();
SqlDataReader reader = doMainQuery.ExecuteReader();
while (reader.Read())
{
SaleItem newItem = new SaleItem((string)reader["saleTitle"]);
resultsList.Add(newItem);
}
showResults.DataSource = resultsList;
showResults.DataBind();
SaleItem.cs
public class SaleItem
{
private String connectionString;
public string saleTitle;
public SaleItem(string s)
{
saleTitle = s;
}
public string getTitle()
{
return saleTitle;
}
}
Search.aspx
id like to be able to show the title in a similar way to this, any ideas?
<asp:repeater
id="showResults"
Runat="server" >
<ItemTemplate>
<%# Eval("saleTitle")%></ItemTemplate> // resultsList.SaleItem.getTitle()?
</asp:repeater>
You can refactor your code as follow
public class SaleItem
{
public string saleTitle {get;set;}
}
public static class Extension
{
public static IEnumerable<T> Select<T>(this SqlDataReader reader, Func<SqlDataReader, T> projection)
{
while (reader.Read())
{
yield return projection(reader);
}
}
}
List<SaleItem> resultsList = new List<SaleItem>();
SqlDataReader reader = doMainQuery.ExecuteReader();
var resultsList = reader.Select(x => new SaleItem { saleTitle = x["saleTitle"].ToString() }).Distinct().ToList();
showResults.DataSource = resultsList;
showResults.DataBind();
<asp:repeater id="showResults" Runat="server" >
<ItemTemplate>
<%# Eval("saleTitle")%>
</ItemTemplate>
</asp:repeater>
I am trying to do efficient paging with a gridview without using a datasource control. By efficient, I mean I only retrieve the records that I intend to show.
I am trying to use the PagerTemplate to build my pager functionality.
In short, the problem is that if I bind only the records that I intend to show on the current page, the gridview doesn't render its pager template, so I don't get the paging controls.
It's almost as if I MUST bind more records than I intend to show on a given page, which is not something I want to do.
You need to create a custom gridview control that inherits from GridView. Without the DataSourceControl, the gridview does not have knowledge of the total number of records that could potentially be bound to the control. If you bind 10 out of 100 records and you set the PageSize property to 10, the gridview only knows that there are 10 records which will be less than or equal to the PageSize and the pager control will not display. In order for your gridview to show the pager, it has to know the total number of records that could potentially be retrieved. By inheriting the gridview and overriding the InitializePager method, we can intercept the pagedDataSource and modify the AllowCustomPaging and VirtualCount methods.
This is the one I created
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI.WebControls;
using System.ComponentModel;
namespace cly.Web.CustomControls
{
public class clyGridView : GridView
{
private const string _virtualCountItem = "bg_vitemCount";
private const string _sortColumn = "bg_sortColumn";
private const string _sortDirection = "bg_sortDirection";
private const string _currentPageIndex = "bg_pageIndex";
public clyGridView ()
: base()
{
}
#region Custom Properties
[Browsable(true), Category("NewDynamic")]
[Description("Set the virtual item count for this grid")]
public int VirtualItemCount
{
get
{
if (ViewState[_virtualCountItem] == null)
ViewState[_virtualCountItem] = -1;
return Convert.ToInt32(ViewState[_virtualCountItem]);
}
set
{
ViewState[_virtualCountItem] = value;
}
}
public string GridViewSortColumn
{
get
{
if (ViewState[_sortColumn] == null)
ViewState[_sortColumn] = string.Empty;
return ViewState[_sortColumn].ToString();
}
set
{
if (ViewState[_sortColumn] == null || !ViewState[_sortColumn].Equals(value))
GridViewSortDirection = SortDirection.Ascending;
ViewState[_sortColumn] = value;
}
}
public SortDirection GridViewSortDirection
{
get
{
if (ViewState[_sortDirection] == null)
ViewState[_sortDirection] = SortDirection.Ascending;
return (SortDirection)ViewState[_sortDirection];
}
set
{
ViewState[_sortDirection] = value;
}
}
private int CurrentPageIndex
{
get
{
if (ViewState[_currentPageIndex] == null)
ViewState[_currentPageIndex] = 0;
return Convert.ToInt32(ViewState[_currentPageIndex]);
}
set
{
ViewState[_currentPageIndex] = value;
}
}
private bool CustomPaging
{
get { return (VirtualItemCount != -1); }
}
#endregion
#region Overriding the parent methods
public override object DataSource
{
get
{
return base.DataSource;
}
set
{
base.DataSource = value;
// store the page index so we don't lose it in the databind event
CurrentPageIndex = PageIndex;
}
}
protected override void OnSorting(GridViewSortEventArgs e)
{
//Store the direction to find out if next sort should be asc or desc
SortDirection direction = SortDirection.Ascending;
if (ViewState[_sortColumn] != null && (SortDirection)ViewState[_sortDirection] == SortDirection.Ascending)
{
direction = SortDirection.Descending;
}
GridViewSortDirection = direction;
GridViewSortColumn = e.SortExpression;
base.OnSorting(e);
}
protected override void InitializePager(GridViewRow row, int columnSpan, PagedDataSource pagedDataSource)
{
// This method is called to initialise the pager on the grid. We intercepted this and override
// the values of pagedDataSource to achieve the custom paging using the default pager supplied
if (CustomPaging)
{
pagedDataSource.VirtualCount = VirtualItemCount;
pagedDataSource.CurrentPageIndex = CurrentPageIndex;
}
base.InitializePager(row, columnSpan, pagedDataSource);
}
protected override object SaveViewState()
{
//object[] state = new object[3];
//state[0] = base.SaveViewState();
//state[1] = this.dirtyRows;
//state[2] = this.newRows;
//return state;
return base.SaveViewState();
}
protected override void LoadViewState(object savedState)
{
//object[] state = null;
//if (savedState != null)
//{
// state = (object[])savedState;
// base.LoadViewState(state[0]);
// this.dirtyRows = (List<int>)state[1];
// this.newRows = (List<int>)state[2];
//}
base.LoadViewState(savedState);
}
#endregion
public override string[] DataKeyNames
{
get
{
return base.DataKeyNames;
}
set
{
base.DataKeyNames = value;
}
}
public override DataKeyArray DataKeys
{
get
{
return base.DataKeys;
}
}
public override DataKey SelectedDataKey
{
get
{
return base.SelectedDataKey;
}
}
}
}
Then when you are binding the data:
gv.DataSource = yourListOrWhatever
gv.VirtualItemCount = numOfTotalRecords;
gv.DataBind();