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();
Related
What I want to achieve: I want to have a DataTable with rows of different types with different properties. One example is a sum row at the end of the DataTable (there will be more once this works).
Inspired by the two answers to this question describing extended DataRow classes (not the accepted one!) I have implemented the following:
public class ProjectEffortTable : DataTable
{
public ProjectEffortTable() : base() { }
public ProjectEffortTable(String TableName) : base(TableName) { }
protected ProjectEffortTable(System.Runtime.Serialization.SerializationInfo Info, System.Runtime.Serialization.StreamingContext Context) : base (Info, Context) { }
public ProjectEffortTable(String TableName, String TableNamespace) : base(TableName, TableNamespace) { }
protected override Type GetRowType()
{
return typeof(ProjectEffortRow);
}
protected override DataRow NewRowFromBuilder(DataRowBuilder Builder)
{
return new ProjectEffortRow(Builder);
}
}
public class ProjectEffortRow : DataRow
{
public ProjectEffortRow (DataRowBuilder Builder) : base (Builder)
{
}
public Boolean IsSum { get; set; }
}
With the following code I can include a new sum row:
var SumRow = ProjectEfforts.NewRow() as ProjectEffortRow;
SumRow.IsSum = true;
// calculate sums for all month columns
foreach (DataColumn Column in ProjectEfforts.Columns)
{
Decimal Sum = 0;
foreach (DataRow CurrentRow in ProjectEfforts.Rows)
{
if (CurrentRow[Column] is Double)
{
Sum += Convert.ToDecimal(CurrentRow[Column]);
}
}
SumRow[Column] = Decimal.Truncate(Sum);
}
ProjectEfforts.Rows.Add(SumRow);
The problem: The DataTable object can be manipulated by the user (using a DataGridView) and I need to save these changes to a data base in my data model (without saving the sum row).
To check for changes if have the following function:
Boolean CheckForChanges()
{
Boolean Changed = false;
var ProjectChanges = DataTableObject.GetChanges();
if (ProjectChanges != null)
{
for (var i = 0; i < ProjectChanges.Rows.Count; i++)
{
if (!(ProjectChanges.Rows[i] as ProjectEffortRow).IsSum)
{
Changed = true;
}
}
}
return Changed;
}
Unfortunately that method always returns true because it seems that GetChanges() creates a new DataTable where the information of the property is lost.
What I don't want to do: I don't want to add columns to the DataTable for each of the properties because this would tightly couple my view with the data model. If I created new columns for each property I would do that in the model and would need to hide all these columns in the view - which I deem quite ugly.
The question: Is it possible to somehow create a DataTable containing custom types of DataRows that maintain custom properties?
Thanks in advance for any help
After thinking some more I found a solution that works fine so far. I am not sure yet how well it scales but for the sum row I am quite satisfied. The key was to also implement GetChanges with custom code as the information about sum rows is known in that function.
Here's my current implementation:
public class ProjectEffortTable : DataTable
{
public ProjectEffortTable() : base() { }
public ProjectEffortTable(String TableName) : base(TableName) { }
protected ProjectEffortTable(System.Runtime.Serialization.SerializationInfo Info, System.Runtime.Serialization.StreamingContext Context) : base (Info, Context) { }
public ProjectEffortTable(String TableName, String TableNamespace) : base(TableName, TableNamespace) { }
protected override Type GetRowType()
{
return typeof(ProjectEffortRow);
}
protected override DataRow NewRowFromBuilder(DataRowBuilder Builder)
{
return new ProjectEffortRow(Builder);
}
public new ProjectEffortTable GetChanges()
{
var Changes = Clone() as ProjectEffortTable;
foreach (ProjectEffortRow CurrentRow in Rows)
{
if ((CurrentRow.RowState != DataRowState.Unchanged) && (!CurrentRow.IsSum))
{
Changes.ImportRow(CurrentRow);
}
}
if (Changes.Rows.Count == 0)
{
Changes = null;
}
return Changes;
}
public new ProjectEffortTable GetChanges(DataRowState RowStates)
{
var Changes = Clone() as ProjectEffortTable;
foreach (ProjectEffortRow CurrentRow in Rows)
{
if ((CurrentRow.RowState == RowStates) && (!CurrentRow.IsSum))
{
Changes.ImportRow(CurrentRow);
}
}
if (Changes.Rows.Count == 0)
{
Changes = null;
}
return Changes;
}
public void AddSumRow()
{
// add line with sum for each month column
var SumRow = NewRow() as ProjectEffortRow;
SumRow.IsSum = true;
Rows.Add(SumRow);
RecalculateSums();
}
public Boolean HasSumRow()
{
var SumRowFound = false;
if ((Rows[Rows.Count - 1] as ProjectEffortRow).IsSum)
{
SumRowFound = true;
}
return SumRowFound;
}
public void RemoveSumRow()
{
if (HasSumRow())
{
Rows[Rows.Count - 1].Delete();
}
}
private void RecalculateSums()
{
if (!HasSumRow())
{
throw new ApplicationException("Recalculation of sum triggered without sum row being present");
}
foreach (DataColumn Column in Columns)
{
Decimal Sum = 0;
foreach (ProjectEffortRow CurrentRow in Rows)
{
if ((CurrentRow[Column] is Double) && (!CurrentRow.IsSum))
{
Sum += Convert.ToDecimal(CurrentRow[Column]);
}
}
Rows[Rows.Count - 1][Column] = Decimal.Truncate(Sum);
}
}
}
I am creating varied number of MvxSpinners programmatically. The number of the MvxSpinners generated cannot be predetermined. It is determined by the user input.
I have a List<Beneficiary>. Each MvxSpinner is meant to update each Beneficiary in the collection.
Since I cannot determine the number of MvxSpinner (which corresponds to the count of the Beneficiary in the collection) to be generated, I am forced to have one ICommand to handle all the HandleSelectedItem event of the MvxSpinners.
The Challenge
I am having difficulty determining the index of the List<Beneficiary> to update depending on the MvxSpinner the user clicked.
An Example
let
var BeneficiaryList=new List<Beneficiary>()
If there are 5 Beneficiary object in the collection, 5 MvxSpinner will be generated.
If the user selects a MVXSpinner which is meant to update index 2 of the collection, how do i determine the index of Beneficary to update?
What I have tried
private IList<Beneficiary> _beneficiaryList;
public IList<Beneficiary> BeneficiaryList
{
get { return _beneficiaryList; }
set { _beneficiaryList= value; RaisePropertyChanged(() => BeneficiaryList); }
}
public ICommand UpdateBeneficiary=> new MvxCommand<Beneficiary>(item =>
{
//item is of type Beneficiary
//But I do not know which index of BeneficiaryList to update
});
Your help will be deeply appreciated.
You probably need a List of ICommands too, one for each spinner. Something like this in your view model...
private IList<ICommand> _commands;
public IList<ICommand> Commands {
get {
if (_commands == null) {
_commands = BeneficiaryList.Select(x => new MvxCommand<Beneficiary>(item => {
...
}));
}
return _commands;
}
}
And set up your bindings like this (assuming you've got a list of spinners)
for (int i = 0; i < spinners.Count; i++) {
var spinner = spinners[i];
set.Bind (spinner).To(vm => vm.Commands[i]);
}
Well, it is interesting to answer my own question.
What I did was to give each Spinner a unique ID that corresponds to the index of the collection.
I created a custom Spinner called MvxSpinnerIndexer extending MvxSpinner (I really do not think it matters. You can just extend Spinner). MvxSpinnerIndexer retrieved the Id and the SelectedItem and then placed the two into a Dictionary
Here is the source for MvxSpinnerIndexer
public class MvxSpinnerIndexer : Spinner
{
public MvxSpinnerIndexer(Context context, IAttributeSet attrs)
: this(
context, attrs,
new MvxAdapter(context)
{
SimpleViewLayoutId = global::Android.Resource.Layout.SimpleDropDownItem1Line
})
{ }
public MvxSpinnerIndexer(Context context, IAttributeSet attrs, IMvxAdapter adapter)
: base(context, attrs)
{
var itemTemplateId = MvxAttributeHelpers.ReadListItemTemplateId(context, attrs);
var dropDownItemTemplateId = MvxAttributeHelpers.ReadDropDownListItemTemplateId(context, attrs);
adapter.ItemTemplateId = itemTemplateId;
adapter.DropDownItemTemplateId = dropDownItemTemplateId;
Adapter = adapter;
SetupHandleItemSelected();
}
public new IMvxAdapter Adapter
{
get { return base.Adapter as IMvxAdapter; }
set
{
var existing = Adapter;
if (existing == value)
return;
if (existing != null && value != null)
{
value.ItemsSource = existing.ItemsSource;
value.ItemTemplateId = existing.ItemTemplateId;
}
base.Adapter = value;
}
}
[MvxSetToNullAfterBinding]
public IEnumerable ItemsSource
{
get { return Adapter.ItemsSource; }
set { Adapter.ItemsSource = value; }
}
public int ItemTemplateId
{
get { return Adapter.ItemTemplateId; }
set { Adapter.ItemTemplateId = value; }
}
public int DropDownItemTemplateId
{
get { return Adapter.DropDownItemTemplateId; }
set { Adapter.DropDownItemTemplateId = value; }
}
public ICommand HandleItemSelected { get; set; }
public int ViewId { get; set; }
private void SetupHandleItemSelected()
{
ItemSelected += (sender, args) =>
{
//sender.
var control = (MvxSpinnerIndexer)sender;
var controlId = control.Id;
var position = args.Position;
HandleSelected(position, controlId);
};
}
protected virtual void HandleSelected(int position, int? controlId)
{
var item = Adapter.GetRawItem(position);
var content = new Dictionary<string, object> {{"Index", controlId}, {"SelectedItem", item}};
//var param = new ListItemWithIndexModel { Index = controlId, SelectedItem = item };
if (HandleItemSelected == null
|| item == null
|| !HandleItemSelected.CanExecute(content))
return;
HandleItemSelected.Execute(content);
}
}
In your ViewModel
public ICommand SpinnerSelected => new MvxCommand<Dictionary<string, object>>(item =>
{
var selectedItem = item["SelectedItem"] as ListItemModel;//Cast to the actual model
var index = item["Index"] as int?;//The index of the collection to update
});
I believe this will be useful to the community.
I have an asp.net page, wherein i am using enums (with Properties defined in class file in app_code)
Now my problem is whenever page gets postbacks the value of the enum in property gets resetted to the first one
I even tried setting the property as static, but still that didn't helped. below is my enum and property declaration:
private static UrlType _type;
public static UrlType UrlPattern
{
get
{
HttpContext.Current.Response.Write("GET: " +_type + "<br>");
return _type;
}
set
{
_type = value;
HttpContext.Current.Response.Write("SET : " +_type + "<br>");
}
}
public int VanityId { get; set; }
public enum UrlType
{
ArticleOnly,
ArticleCategoryCombination,
Normal,
TechForum
}
and this is how i calls:
public void BindRewrite()
{
GrdRewrite.DataSource = objVanity.GetAllRewriteVanities(Vanity.UrlPattern);
GrdRewrite.DataBind();
if (Vanity.UrlPattern == Vanity.UrlType.ArticleCategoryCombination)
{
GrdRewrite.Columns[2].Visible = false;
GrdRewrite.Columns[3].Visible = GrdRewrite.Columns[5].Visible = GrdRewrite.Columns[6].Visible = true;
}
else if (Vanity.UrlPattern == Vanity.UrlType.ArticleOnly)
{
GrdRewrite.Columns[5].Visible = true;
GrdRewrite.Columns[2].Visible = GrdRewrite.Columns[3].Visible = GrdRewrite.Columns[6].Visible = false;
}
else if (Vanity.UrlPattern == Vanity.UrlType.Normal)
{
GrdRewrite.Columns[2].Visible = true;
GrdRewrite.Columns[3].Visible = GrdRewrite.Columns[5].Visible = GrdRewrite.Columns[6].Visible = false;
}
}
protected void Page_Load(object sender, EventArgs e)
{
pnlAdmin.Visible = (objVanity.UserName == "host");
if (objVanity.UserName == "host")
Enable();
else
FieldsOpenForEditors(objVanity.SiteSupportUrlFormat);
if (!IsPostBack)
{
Vanity.GenerateListFromEnums(drpAdminUrlType);
if (objVanity.UserName == "host")
Vanity.UrlPattern = Vanity.UrlType.ArticleOnly;
else
Vanity.UrlPattern = objVanity.SiteSupportUrlFormat;
BindRewrite();
}
}
can anyone tell me how to retain the value of the enum across postbacks
i think viewstate could be option, but don't have any clue about how to store the enum value and restore the string value casted in enum.
If you want to persist a value between post back, you need to store it in Session, Cache or ViewState.
In your case, ViewState could be a prefer choice.
public UrlType UrlPattern
{
get
{
if (ViewState["UrlPattern"] != null)
return (UrlType)Enum.Parse(typeof(UrlType), ViewState["UrlPattern"].ToString());
return UrlType.Normal; // Default value
}
set
{
ViewState["UrlPattern"] = value;
}
}
I have two property in my class: MyCountry & MyCity. I set this class to sourceobject of a property grid. I want load cities i combo when select a country. for example I have 2 Country data:
Country1
Country2
And For Country1, I have (city data)
City11
City12
City13
And For Country2, I have (city data)
city21
City22
City23
When I change select country item in propertygrid, I want load cities of it in city item. this mean, when select Country1, display City11,City12,City13 in City item and when select Country2 Display City21,Cityy22,City23 in City Item.
How can I It ?
my class is :
public class KeywordProperties
{
[TypeConverter(typeof(CountryLocationConvertor))]
public string MyCountry { get; set; }
[TypeConverter(typeof(CityLocationConvertor))]
public string MyCity { get; set; }
}
and I use below class for load countries data for display in combo :
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
HumanRoles Db = new HumanRoles();
List<LocationsFieldSet> Items = new List<LocationsFieldSet>();
Items = Db.LoadLocations(0);
string[] LocationItems = new string[Items.Count];
int count = 0;
foreach (LocationsFieldSet Item in Items)
{
LocationItems[count] = Item.Title;
count++;
}
return new StandardValuesCollection(LocationItems);
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return true;
}
}
The ITypeDescriptorContext interface provides a property called Instance
which lets you access the object to which the type descriptor request is connected.
You can use this property to determine the current value of the MyCountry property
the user selected. Depending on the value you can load the cities for this country.
Furthermore, in the setter of the MyCountry property I check whether or not the
new value is different from the old one and if this is the case I reset the MyCity property
(to not get an invalid combination of country and city).
Here is a small code sample. For the sake of simplicity I only use one type converter
for both properties.
public class KeywordProperties
{
public KeywordProperties()
{
MyCountry = "Country1";
}
private string myCountry;
[TypeConverter(typeof(ObjectNameConverter))]
public string MyCountry
{
get { return myCountry; }
set
{
if (value != myCountry)
MyCity = "";
myCountry = value;
}
}
private string myCity;
[TypeConverter(typeof(ObjectNameConverter))]
public string MyCity
{
get { return myCity; }
set { myCity = value; }
}
}
public class ObjectNameConverter : StringConverter
{
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
KeywordProperties myKeywordProps = context.Instance as KeywordProperties;
if (context.PropertyDescriptor.Name == "MyCountry")
{
List<string> listOfCountries = new List<string>();
listOfCountries.Add("Country1");
listOfCountries.Add("Country2");
return new StandardValuesCollection(listOfCountries);
}
List<string> listOfCities = new List<string>();
if (myKeywordProps.MyCountry == "Country1")
{
listOfCities.Add("City11");
listOfCities.Add("City12");
listOfCities.Add("City13");
}
else
{
listOfCities.Add("City21");
listOfCities.Add("City22");
listOfCities.Add("City23");
}
return new StandardValuesCollection(listOfCities);
}
}
In the example above there is one side effect I do not like.
Setting the MyCountry property leads to settting also the MyCity property.
To workaround this side effect you could also use the PropertyValueChanged event
of the PropertyGrid to handle invalid country/city selections.
private void propertyGrid1_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
{
if (e.ChangedItem.Label == "MyCountry")
{
if(e.ChangedItem.Value != e.OldValue)
m.MyCity = "";
}
}
If you use this event, just repalce the code in the setter of the MyCountry property with:
myCountry = value;
I want to store a generic list in the viewstate in an ASP.NET user control, so I have the following code:
protected List<MoodleCourse> MoodleCoursesCreated
{
get
{
if (ViewState["MoodleCoursesCreated"] == null)
{
return new List<MoodleCourse>();
}
else
{
return (List<MoodleCourse>)ViewState["MoodleCoursesCreated"];
}
}
set
{
ViewState["MoodleCoursesCreated"] = value;
}
}
So to add an item to the list, I call MoodleCoursesCreated.Add(new MoodleCourse);
However it seems then I must do MoodleCourseCreated = MoodleCourseCreated to activate the setter so the list actually gets stored in the viewstate. I suspect there is a more elegant way to achieve this, does anyone have any suggestions? Cheers
Calling .Add() you never trigger the setter. I guess, you don't need setter at all. Instead, override Add() method or create your own AddMoodleCourse(MoodleCourse moodleCourse):
protected void AddMoodleCourse(MoodleCourse moodleCourse)
{
var courses = ViewState["MoodleCoursesCreated"] as List<MoodleCourse>;
if (courses == null)
{
courses = new List<MoodleCourse>();
ViewState["MoodleCoursesCreated"] = courses;
}
courses.Add(moodleCourse);
}
and now call MoodleCoursesCreated.AddMoodleCourse(new MoodleCourse);
protected List<MoodleCourse> MoodleCoursesCreated
{
get
{
if (ViewState["MoodleCoursesCreated"] == null)
{
ViewState["MoodleCoursesCreated"] = new List<MoodleCourse>();
}
return (List<MoodleCourse>)ViewState["MoodleCoursesCreated"];
}
set
{
ViewState["MoodleCoursesCreated"] = value;
}
}
Don't use a property if you want it to have side-effects in the getter. Instead just use a method like this:
protected List<MoodleCourse> GetMoodleCourses()
{
List<MoodleCourse> list = (List<MoodleCourse>)ViewState["MoodleCoursesCreated"];
if (list == null)
{
list = new List<MoodleCourse>();
ViewState["MoodleCoursesCreated"] = list;
}
return list;
}
you should hold the same reference in the getter:
protected List<MoodleCourse> MoodleCoursesCreated
{
get
{
if (ViewState["MoodleCoursesCreated"] == null)
ViewState["MoodleCoursesCreated"] = new List<MoodleCourse>();
return ViewState["MoodleCoursesCreated"] as List<MoodleCourse>;
}
set
{
ViewState["MoodleCoursesCreated"] = value;
}
}