ASP.NET [Horizontal scroll fro GridView]
shows how to add horizontal scrolling to my grid
the problem is that it also scrolls around the autogenerated pager.
I CAN make external pager but surely theres a better solution?
the question is: is there an easy way to make only my gridview horizontaly scrollable without making the pager scrollable.
Another solution much simpler:
Create an empty table and give it an id. In my case customPager.
Give your default GridView pager a css style. You just need a name no css coding.
Include jquery in your aspx (head section).
You see the 2 lines of script below... Those move the default paging outside where you put the customPager
This is the simplest that I can produce... hopefully it helps...
<table id="customPager">
</table>
<hr />
<div id="dvGridView" style="height: 200px;overflow:scroll;">
<asp:GridView ID="GridView1" runat="server" AllowPaging="True" PageSize="5">
<PagerSettings Position="Top" />
<PagerStyle CssClass="pagerStyle" />
</asp:GridView>
</div>
<script>
$('#customPager').html($('.pagerStyle').html());
$('.pagerStyle').html('');
</script>
erich007 gave a very intresting answer and thats probably the way to go but I didn;t have time to test it.
the following method worked for me:
quote:
http://blogs.visoftinc.com/2008/03/19/extending-the-gridview-to-work-with-the-new-datapager-control/
Extending the GridView to Work With the New DataPager Control
By Dave Marini | Published: March 19, 2008
Technorati Tags: ASP.NET,C#
Recently I read an article by Scott Mitchell about the new ListView and DataPager controls that are included in the .NET Framework version 3.5. This got me curious as to how to adapt other well known databound controls like the GridView so that they can work together with the DataPager control. After some light reading and rather heavy reflecting over the guts of the GridView control, I had a gameplan and was ready to go. Extending a databound control to allow communication with the DataPager control isn't too difficult to do, but it does assume that you have an understanding of how the data is bound to the control. But why bother to do this when the GridView has its own paging control built in? Well, for one thing, being able to decouple the pager from the grid allows us to do some pretty cool things. One thing I can think of is having the GridView in the left bar and the paging control someplace in the main window. Also, we can have a grid with two pagers. Each keeps track of the current page consistently, regardless of which one we use to page through our data. Finally, the templating controls of the pager are very powerful compared to those of the GridView by itself.
Let's start with how a databound control can communicate with the DataPager. The DataPager control can be hooked up to any control that implements the IPageableItemContainer interface located in the System.Web.Extensions assembly. Here's a quick shot of what the interface looks like courtesy of .NET Reflector:
public interface IPageableItemContainer
{
//Events
event EventHandler<PageEventArgs> TotalRowCountAvailable;
// Methods
void SetPageProperties(int startRowIndex, int maximumRows, bool databind);
// Properties
int MaximumRows { get; }
int StartRowIndex { get; }
}
The MaximumRows and StartRowIndex properties of the interface are simply there to create a way for your databound control to determine the data window to display. This effectively defines the basis for what you will consider to be a "Page" of data. The SetPageProperties method is important in the interaction with the DataPager because this is the method in your control that the DataPager will call whenever you click on any of the DataPagerField controls (The Next or Previous Buttons, for example). Finally, the interface defines an event called TotalRowCountAvailable. This event tells the DataPager how many records are in the data being bound to your control. This is obviously important as the DataPager uses the total row count to determine how man page buttons to render or whether to disable the next or previous buttons so that you don't go to a nonexistent page index.
So let's begin extending the GridView with the hooks for the DataPager. Thinking about the IPageableItemContainer interface in terms of the GridView, we realize that MaximumRows is equivalent to the existing PageSize property, and StartRowIndex can be calculated from the existing PageSize and PageIndex properties. We also prepare the event by declaring it and creating a corresponding event invoker. Since I want my new Paging capability to be the default for this grid, I force the Pager to hide itself every page load. You can add a toggle to this behavior should you so desire. Finally, we stub out the SetPageProperties method, but we'll leave it blank for now as we'll revisit it shortly. So far our new GridView looks like this:
public class PageableGridView : GridView, IPageableItemContainer
{
public PageableGridView() : base()
{
PagerSettings.Visible = false;
}
public event EventHandler<PageEventArgs> TotalRowCountAvailable;
public int MaximumRows
{
get{ return this.PageSize; }
}
public int StartRowIndex
{
get{ return (this.PageSize * this.PageIndex); }
}
protected virtual void OnTotalRowCountAvailable(PageEventArgs e)
{
if (TotalRowCountAvailable != null)
TotalRowCountAvailable(this, e);
}
protected virtual void SetPageProperties(int startRowIndex, int maximumRows, bool dataBind) { }
}
The good news is that we're more than half way there. Now is where things get a bit more complex. We still need a way to set the control with the pagesize and starting row values based on what the DataPager says we need to display once the user clicks on one of its buttons. This is where the SetPageProperties method comes into play. Here is a basic implementation that gets the job done:
protected virtual void SetPageProperties(int startRowIndex, int maximumRows, bool dataBind)
{
if (databind)
{
PageSize = maximumRows;
int newPageIndex = (startRowIndex / PageSize);
if (PageIndex != newPageIndex)
{
OnPageIndexChanging(new GridViewPageEventArgs(newPageIndex));
PageIndex = newPageIndex;
OnPageIndexChanged(EventArgs.Empty);
}
}
RequiresDataBinding = databind;
}
When the DataPager sends the grid its paging information, the grid needs to set any appropriate params on itself to prepare to bind to the right window of data. The 2 properties that the grid is already equipped to do this with are the PageSize and PageIndex properties. These properties can be calculated from the information sent into the method, so we set them. Of course, if the Page is being changed, we should probably fire the OnPageIndexChanging event. This means if you aren't binding to a datasource, you'll still need to make sure to handle this event. Finally, we instruct the grid to rebind itself should the DataPager be in the process of binding to data. This is a basic implementation so you'll want to do any data integrity checks here as well, for example checking to ensure that the new PageIndex and StartRowIndex values are in the valid range.
There's only one thing left to do to complete our integration of the GridView to the DataPager. For the DataPager to render the appropriate number of page buttons, or for it to know when to disable the next or previous page buttons on the pager, it needs to know how many total rows there are. This, coupled with the Page Size, which is specified declaratively on the DataPager itself, help it determine the number of pages that the data source contains. The problem is, this information isn't known to the pager. It is known to the GridView however, and we need to get it from there and pass it on to the DataPager. But when can we be sure that the GridView has this data? The GridView control inherits from CompositeDataboundControl. This type contains a special variant of the CreateChildControls method that also takes in a property indicating whether the control is being bound to data or simply re-rendered. A little reflecting shows that the GridView uses this method to bind to its data source. Knowing this, it appears this is the place we will want to inject our trigger for the TotalRowCountAvailable event to be raised. This gets a little complicated because we need to handle the case where we're binding to a datasource manually or using a DataSource Control like ObjectDataSource or SqlDataSource, which have the total row count specified within them. A couple of helper methods are required to ensure we get the right value:
//Gets row count from SqlDataSource and the like...
private int _GetTotalRowsFromDataSourceObject(IEnumerable dataSource)
{
DataSourceView view = this.GetData(); if (AllowPaging && view.CanPage && view.CanRetrieveTotalRowCount)
return base.SelectArguments.TotalRowCount;
else
return (PageIndex * PageSize) + _GetSourceCount(dataSource);
}
//Gets the row count from a manually bound source or from a source in viewstate
private int _GetSourceCount(IEnumerable dataSource)
{
ICollection source = dataSource as ICollection;
return source != null ?
source.Count :
(from x in dataSource.OfType<object>() select 1).Sum();
}
The _GetTotalRowsFromDataSourceObject method retrieves the total number of records from the DataSource object, if it's available. This depends on a couple of things, such as whether the EnablePaging property was set on the DataSource Control and whether the Object has completed the query operation. Worst case, we return a single page of data and be done with it. The _GetSourceCount method is used for two particular occasions. First, it's how you get the row count in the event that you bind the grid's DataSource property manually and then call DataBind(). Secondly, this method will also prove useful when the grid is rebinding after a postback to data that is stored in the viewstate. In both cases, we use a little linq to extract the total number of resulting data items (or rows in the case of viewstate) in the datasource. Now let's see how we use these methods to tie it all together:
protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
{
int baseResult = base.CreateChildControls(dataSource, dataBinding);
if (dataSource != null)
{
int dataSourceCount = (IsBoundUsingDataSourceID && dataBinding) ?
_GetTotalRowsFromDataSource(dataSource) :
_GetSourceCount(dataSource);
OnTotalRowCountAvailable(new PageEventArgs(StartRowIndex, MaximumRows, dataSourceCount));
}
return baseResult;
}
The base control's CreateChildControls method is called first, since the grid uses it to actually do the databinding. if there isn't any data to bind to, there will not be a reason to notify the pager, so we check to make sure we have data, then determine the number of rows in the source through the above mentioned process. Finally, we fire off the event with the derived data and then we return the original result so as not to affect any other operations that may rely on it. We now have a GridView that can be coupled with the new DataPager control. Here's a sample of markup that we would use to use these controls together on a page:
<asp:DataPager ID="DataPager1" runat="server" PageSize="2" PagedControlID="grid2">
<Fields>
<asp:NextPreviousPagerField />
</Fields>
</asp:DataPager>
<custom:pageablegridview id="grid2" runat="server" autogeneratecolumns="true" allowpaging="true"
onpageindexchanging="grid2_PageIndexChanging" />
Notice that I don't have to specify the PageSize property of the GridView because the PageSize property on the DataPager will be in control of this value. To ensure this, we could shadow the PageSize property on the grid, but this will suffice for now. So that's really all there is to it. For newly created server controls that are bound to data, making these inroads to allow your control to work with the DataPager is pretty simple. For the existing controls, like the GridView, it's just a matter of knowing how the binding takes place and then injecting the functionality someplace where you know the needed values will exist.
Related
I have a GridView that has multiple columns. Let's say I have a business modell that looks like this:
public class MyObject
{
public String Title {get;set;}
public DateTime Date {get;set;}
public String NavigateUrl {get;set;}
}
My grid has two visible columns (Title and Date).
What I want to do, is to make each row clickable and to act like a link, that is going to the specified NavigateUrl.
No problem so far.
I could use the OnRowDataBound-Event to assign a Javascript that tells the Grid that I performed a Select and in the OnRowEvent, I could perform a Response.Redirect.
But that is not what I want to do, because it is not SEO friendly.
How can I make the whole "grid row as link" idea SEO friendly?
Edit: Adding an extra, visible column is no option
Provide a link as a column in the grid, and make the tr's onclick event fire off the link.
What you want is to make the GridView's cell contents LinkButtons, with the NavigateUrl specified. They will then show up as ordinary links on the page. You can define this behavior in the ASPX/ASCX markup and then DataBind, or dynamically build your GridView by adding rows manually in a loop through your data objects, setting the properties manually. Either one will work; I daresay databinding may be a bit faster but it can also be fiddly.
i want to ask a question about how to do paging in ASP.net coding with C#.
the thing i try to do is that, i want to display some content, have multi column and multi rows.
in the actually coding, i have a list of object, let say Object A
class A {
integer id;
string name;
string desc;
}
and want to display this onto the page with pagable functionality.
i do google search, ASP.net offer gridview and listview, but they all require a datasource, which mean that table directly bind to the database.
that is not what i want, coz my list of object A come from some where else, not from my database (e.g it is a composite set of data, which generate in runtime)
so it there anyway i still can use those benifit component, or i have to do it all on my own for the paging???
thx
My advice is to use a GridView, with which you can use an ObjectDataSource, which can take its underlying data from a class method that you specify. So the class method might be (following your code example):
public static List<A> GetAllAs()
{
return myAs;
}
and your aspx page would contain
<asp:ObjectDataSource ID="MyODS" runat="server" TypeName="Namespace.Classname" SelectMethod="GetAllAs" />
<asp:GridView ID="grdMyGridView" runat="server" DataSourceID="MyODS" AllowPaging="True" ... >
The TypeName and SelectMethod attributes of the ObjectDataSource tell it which method, and where, to use to fill the ODS with data. The AllowPaging="True" gives you paging on the GridView automatically.
A DataSource property will also accept a List<> or BindingList<> .
To use this in code:
protected void Page_Load(object sender, EventArgs e)
{
var data = new List<Sample>();
data.Add (...);
GridView1.DataSource = data;
GridView1.DataBind();
}
And maybe some IsPostback logic etc.
I have a datalist inside a usercontrol that gets loaded into a page where users can customize a report based on some checkboxes.
One of the checkboxes, however, is "Hide Worklog" which should hide the worklog column from the result set because it can be quite long and interfere with the report.
If I do:
datatable1.Columns.Remove("WorkLog");
the code throws an exception because:
<asp:Label ID="WorkLog" runat="server" Text='<%# Bind("WorkLog") %>'></asp:Label></td>
doesn't exist.
Am I going about the usercontrol all wrong? This usercontrol should always be able to show the worklog, so I don't think it's bad to bind it in there, but at the same time I want to be able to hide it if the user wants.
Try removing the label control from your DataList instead of removing the column from the data source (i.e. the DataTable)
DataList1.Controls.Remove(DataList1.FindControl("WorkLog"));
You shouldn't get an error if the data source has more columns than you're displaying on the page, however, you will get an error, as you've discovered, if you're trying to display a column that doesn't exist in the data source.
bind it in code behind after checking some condition. like
if (visible) {
//bind
}
while removing control
visible = false;
you might need to change visible to session var :)
I have AspxGridView on my page. I want users to be able to set up some data on the web form, and after they press one button, the data from screen is being read, validated and bussines object is being created. This object has a GetData() function, and returns an array of objects representing rows in a grid.
I want ASPXGrid not to populate, until user clicks the button. I know I can set DataSourceId in design time to null – but then I lost availability of synchronizing grid columns with object properties (I cannot add or edit some column properties for new columns). I know I can intercept ObjectCreating event and provide grid with an fake object returning empty data sets. But are there any more elegant solutions?
When are you doing the DataBind() call? Couldn't you just place that inside an if block to make sure it only happens on postback?
if(Page.IsPostBack) {
DoDataBindingStuff();
}
If I bind GridView to SqlDataSource, then the first time page will be created, GridView will display columns retrieved from data source. But if on postback I set GridView.DataSourceID to null or to an empty string
protected void Page_Load(object sender, EventArgs e)
{
...
if (IsPostBack)
GridView1.DataSourceID = "";
...
}
, then GridView won’t display any rows at all. But why is that?
Assuming GridView has EnableViewstate set to true, then it should be able to display rows it retrieved from data source when page was first created!
I realize one could argue that Framework notices that DataSourceId has changed and assumes GridView doesn't need data from previous data source, but I’d assume Framework would realize that empty string or null reference doesn’t point to any of data source and thus wouldn’t remove any data GridView retrieved from previous data source?!
thank you
EDIT:
Hello,
Basically what is going on is once you have set the DataSourceID to null or an empty string the control takes this as an indication from the consumer of the control that they do not wish to bind any data at all (even ViewState data). The control checks the DataSourceID prior to binding and if it is an empty string then it does not DataBind in its EnsureDataBound method.
So if you set DataSourceID to null inside Page_Load(), but GridView only checks DataSourceId moments before binding ( which happens much after Page_Load ), then until DataSourceId is checked, ViewState containing data from previous data source should still be available inside Page_Load() ( and still available,for example,inside an event handler subscribed to an event that caused a postback)?!
Your control will not rebind to the ViewState data if you set the DataSourceID to null or an empty string. The article I linked below has an excellent explanation as to why.
Basically what is going on is once you have set the DataSourceID to null or an empty string the control takes this as an indication from the consumer of the control that they do not wish to bind any data at all (even ViewState data). The control checks the DataSourceID prior to binding and if it is an empty string then it does not DataBind in its EnsureDataBound method.
System.What?: DataSource VS. DataSourceID (Internals)
I’d assume Framework would realize
that empty string or null reference
doesn’t point to any of data source
and thus wouldn’t remove any data
GridView retrieved from previous data
source?
Why do you think that way, you reset the value, of course, the girdview has to be bind the new value you reset.