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);
}
}
}
Related
I have some data in a list that shows for example the Item Number, Line Number, Quantity, and Pack Size of an order. The problem is that some lines on the order will have the same item number but different quantities, pack sizes or both. For example :
Order# Line# Item# Qty Pack Size
100 1 12345 640 (10#64)
100 1 12345 128 (1#128)
100 2 23124 48 (1#48)
100 3 53425 80 (1#80)
Shown above for item 12345 they have ordered a total of 768 pieces but for 640 of those pieces they would like to receive 10 packs containing 64 pieces each and they would like 128 of the pieces in just one pack. When the ListView is created naturally it will create 4 rows, one row for each of the rows in the list. I would like it to only show 3 rows one for each Item# and if there are duplicate items it should add the quantities and combine the pack sizes into one row. I have tried doing something like this trying to force it to do what i wanted but it obviously does not work that well. I am fairly new to Android and ListViews so I am hoping that there is a better way to accomplish this. Can anyone point me in the right direction?
public class LoadInfoViewAdapter : BaseAdapter<Items>
{
private List<Items> lItems;
private Context gContext;
private string gLoadnStop;
private string gPacks;
private decimal gtotal;
int total;
public LoadInfoViewAdapter(Context context, List<Items> loadinfo, string LoadnStop)
{
lItems = loadinfo;
gContext = context;
gLoadnStop = LoadnStop;
}
public override int Count
{
get { return lItems.Count; }
}
public override long GetItemId(int position)
{
return position;
}
public override Items this[int position]
{
get { return lItems[position]; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View row = convertView;
if (row == null)
{
row = LayoutInflater.From(gContext).Inflate(Resource.Layout.LoadInfoItems_Row, null);
}
//If the item at the current position is the same as the item in the next position add the qty and combine the packs
if (lItems[position].ItemNo == lItems[position + 1].ItemNo)
{
string itemno = lItems[position].ItemNo;
int count = 0;
row.FindViewById<TextView>(Resource.Id.txtItemNo).Text = lItems[position].ItemNo.Trim();
row.FindViewById<TextView>(Resource.Id.txtLineNo).Text = lItems[position].LineNum.Trim();
foreach (var item in lItems.Where(r => r.ItemNo == itemno))
{
if (count == 0)
{
gPacks = lItems[position].Pack.ToString().Trim();
gtotal = lItems[position].Qty;
}
else
{
gPacks = lItems[position + 1].Pack.ToString().Trim() + ", " + gPacks;
gtotal = lItems[position + 1].Qty + gtotal;
}
count = +1;
}
row.FindViewById<TextView>(Resource.Id.txtQuantity).Text = gtotal + " (" + gPacks + ") ";
gtotal = 0;
}
//check to see if the current item is was the same as the previous item added. If so I dont want to create a row for that item.
else if (lItems[position].ItemNo == lItems[position - 1].ItemNo)
{
//Not too sure what would be the best code to go here
}
//if it has no duplicate create the row like normal
else
{
row.FindViewById<TextView>(Resource.Id.txtItemNo).Text = lItems[position].ItemNo.Trim();
row.FindViewById<TextView>(Resource.Id.txtLineNo).Text = lItems[position].LineNum.Trim();
row.FindViewById<TextView>(Resource.Id.txtQuantity).Text = lItems[position].Qty.ToString().Trim() + lItems[position].Pack.ToString().Trim();
}
return row;
}
I would like it to return something like this :
12345 1 768((10#64),(1#128))
23124 2 48(1#48)
53425 3 80(1#80)
For whatever reason it will do what i was hoping for the duplicate item numbers but it sometimes duplicates a different item in the list like:
12345 1 768((10#64),(1#128))
23124 2 48(1#48)
53425 3 80(1#80)
23124 2 48(1#48)
Anyone got any suggestions?
Your fundamental issue is that the Items indexer is stating that you have 4 items available, but your desired result is 3 items.
This isn't really a task that you should be solving in your BaseAdapter. You should be processing the list in advance and reducing to your target 3 items BEFORE passing it into your base adapter.
Also, you should try and avoid using FindViewFromId in GetView, unless the convertView is null. In order to do this, you can pack the previously identified Views into the Tag property: that's what it's for, see https://blog.xamarin.com/creating-highly-performant-smooth-scrolling-android-listviews/
If you really want to do it all in your base adapter, then convert the items in the constructor, something like this:
public class LoadInfoViewAdapter : BaseAdapter<Items>
{
private List<CondensedItems> cItems;
private Context gContext;
public LoadInfoViewAdapter(Context context, List<Items> loadinfo, string LoadnStop)
{
gContext = context;
gLoadnStop = LoadnStop;
// Get all of the unique item numbers.
var uniqueItemNos = loadInfo.Select(x => x.ItemNo).Distinct();
cItems = new List<CondensedItem>();
foreach(string uniqueItem in uniqueItemNos)
{
// Gets all of the items with the target itemNo. If you want to avoid merging them all, you need to do some work checking for sequential indexes or something similar.
var matchingItems = loadInfo.Select(x => x.ItemNo == uniqueItem).ToList();
var lineNo = matchingItems[0].LineNo;
var qty = matchingItems.Sum(x => x.Qty).ToString();
StringBuilder packsSB = new StringBuilder();
foreach(var item in matchingItems)
{
packs.Append(item.Pack + ",");
}
string packs = packsSB.ToString().Trim(',');
cItems.Add(
new CondensedItem
{
ItemNo = uniqueItem,
LineNo = lineNo,
Packs = $"{qty}({packs})"
});
}
}
public override int Count
{
get { return cItems.Count; }
}
public override long GetItemId(int position)
{
return position;
}
public override Items this[int position]
{
get { return cItems[position]; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View row = convertView;
CondensedViewHolder viewHolder;
if (row == null)
{
row = LayoutInflater.From(gContext).Inflate(Resource.Layout.LoadInfoItems_Row, null);
viewHolder = new CondensedViewHolder
{
ItemView = row.FindViewById<TextView>(Resource.Id.txtItemNo),
LineView = row.FindViewById<TextView>(Resource.Id.txtLineNo),
PacksView = row.FindViewById<TextView>(Resource.Id.txtQuantity)
}
row.Tag = viewHolder;
}
else
{
viewHolder = row.Tag as CondensedViewHolder;
}
viewHolder.ItemView.Text = cItems[position].ItemNo;
viewHolder.LineView.Text = cItems[position].LineNo;
viewHolder.PacksView.Text = cItems[position].Packs;
return row;
}
private class CondensedItem
{
string ItemNo { get; set; }
string LineNo { get; set; }
string Packs { get; set; }
}
private class CondensedViewHolder : Java.Lang.Object
{
public TextView ItemView { get; set; }
public TextView LineView { get; set; }
public TextView PacksView { get; set; }
}
}
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 a table in SQL Server that is populated from a Web Service. I want it to be refreshed on a scheduled basis. I would like to have something similar to an SQL Merge operation.
That is i define my source (Web Service) and my target (SQL Table) and i define how to handle missing from source missing from target and matches.
Lets consider a scenario where i have only two fields in the table Description and Deleted and the Web Service provides only the Description.
If a description is present in both the table and the web service then i just updated (or not).
If a description is present in the web service but not in the table i want it to be inserted
If a description is no longer present in the web server i want it marked as Deleted = true
What i currently have is:
public class WebServiceResults: AbstractOperation
{
public WebServiceResults()
{
var WebService = new WebService();
WSResults = WebService.GetResults();
}
IEnumerable<WSResult> WSResults { get; set; }
public override IEnumerable<Row> Execute(IEnumerable<Row> rows)
{
foreach(var obj in WSResults)
yield return Row.FromObject(obj);
}
}
class SQLTableResults : AbstractOperation
{
public SQLTableResults()
{
SQLResults = data.MyTable.Select(x=>new {x.Description,x.Deletet});
}
Data data = new Data();
IEnumerable<SQLResult> SQLResults { get; set; }
public override IEnumerable<Row> Execute(IEnumerable<Row> rows)
{
foreach (var obj in SQLResults)
yield return Row.FromObject(obj);
}
}
public override void Dispose()
{
data.Dispose();
base.Dispose();
}
}
class JoinTables : JoinOperation
{
protected override Row MergeRows(Row leftRow, Row rightRow)
{
Row row = leftRow.Clone();
row["Description2"] = rightRow["Description"];
return row;
}
protected override void SetupJoinConditions()
{
FullOuterJoin
.Left("Description")
.Right("Description");
}
}
class MergeTables : AbstractOperation
{
Data data = new Data();
public MergeTables()
{ }
public override IEnumerable<Row> Execute(IEnumerable<Row> rows)
{
foreach (var obj in rows)
{
if (String.IsNullOrEmpty((string)obj["Description2"]))
{
//Code for not matched at target
yield return Row.FromObject(obj);
}
if (String.IsNullOrEmpty((string)obj["Description"]))
{
//Code for not matched at source
yield return Row.FromObject(obj);
}
{
//Code for matched
yield return Row.FromObject(obj);
}
}
}
public override void Dispose()
{
data.Dispose();
base.Dispose();
}
}
protected override void Initialize()
{
Register(
new JoinTables()
.Left(new SQLTableResults())
.Right(new WebServiceResults())
);
Register(new MergeTables());
foreach (var error in GetAllErrors())
Console.Write(error.Message);
}
Is this the way to go? I would imagine something more of a stepped process, like
Register(new NotMatchedAtSourceOperation());
Register(new NotMatchedAtTargetOperation());
Register(new MatchedOperation());
but as i understand it, each register returns its rows to the next, so if i filter for the not matched, then the other two will do nothing.
Should i create a new process for each case?
By the way, i am looking for documentation on RhinoEtl. Do you know of any links? Any tutorials?
Determine the merge action in a single operation using a full outer join. You can see an example here. It's not exactly what you need, so I'll try to adapt it to your situation below:
protected override Row MergeRows(Row wsRow, Row dbRow) {
Row row;
// if the db row doesn't exist, then the ws row is new, and it should be inserted
if (dbRow["id"] == null) {
row = wsRow.Clone();
row["action"] = "Insert";
row["deleted"] = false;
return row;
}
// if the ws row doesn't exist, it should be marked as deleted in the database (if not already)
if (wsRow["id"] == null) {
row = dbRow.Clone();
row["deleted"] = true;
row["action"] = dbRow["deleted"].Equals(true) ? "None" : "Update";
return row;
}
// ws and db descriptions match, but check and make sure it's not marked as deleted in database
row = wsRow.Clone();
row["deleted"] = false;
row["action"] = dbRow["deleted"].Equals(true) ? "Update" : "None";
return row;
}
protected override void SetupJoinConditions() {
FullOuterJoin.Left("description").Right("description");
}
After you run this this operation, every row will have an action of "Insert", "Update", or "None." Based on this action, you can compose insert and update statements for a SqlBatchOperation to execute.
I can't seem to find anything on how to edit the data editor settings before umbraco 6.2 (Juno). Is there any simple way, it must be possible. If you don't understand what i mean i want to do the same as http://www.nibble.be/?p=96 - just for umbraco 4.5.2.
Thanks :)
You need to make 3 classes Class 1 DataEditor
public class DataEditor : System.Web.UI.UpdatePanel, umbraco.interfaces.IDataEditor
{
public MWCropperDataEditor(umbraco.interfaces.IData Data, string Configuration)
{
_data = Data;
}
public virtual bool TreatAsRichTextEditor
{
get { return false; }
}
public bool ShowLabel
{
get { return true; }
}
public Control Editor { get { return this; } }
public void Save()
{
this._data.Value = "data;
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
imageUpload = new FileUpload();
imageUpload.ID = "imageUpload";
//shows Image
cropImage = new System.Web.UI.WebControls.Image();
cropImage.Width = width;
cropImage.Height = height;
cropImage.ImageUrl = this._data.Value.ToString();
//Shows dropdown
locationDropDown = new DropDownList();
AddItemsToDropDown();
lblInfo = new Label();
lblInfo.Attributes.Add("id", "title" + base.ClientID);
lblCropInfo = new Label();
lblCropInfo.Text = "Crop Location: ";
base.ContentTemplateContainer.Controls.Add(lblInfo);
base.ContentTemplateContainer.Controls.Add(imageUpload);
base.ContentTemplateContainer.Controls.Add(new LiteralControl("<br/>"));
base.ContentTemplateContainer.Controls.Add(new LiteralControl("<br/>"));
base.ContentTemplateContainer.Controls.Add(lblCropInfo);
base.ContentTemplateContainer.Controls.Add(locationDropDown);
base.ContentTemplateContainer.Controls.Add(new LiteralControl("<br/>"));
base.ContentTemplateContainer.Controls.Add(new LiteralControl("<br/>"));
base.ContentTemplateContainer.Controls.Add(cropImage);
}
}
class 2 DataType
public class MWCropperDataType : umbraco.cms.businesslogic.datatype.BaseDataType, umbraco.interfaces.IDataType
{
private umbraco.interfaces.IDataEditor _Editor;
private umbraco.interfaces.IData _baseData;
private MWCropperPrevalueEditor _prevalueeditor;
public override umbraco.interfaces.IDataEditor DataEditor
{
get
{
if (_Editor == null)
_Editor = new MWCropperDataEditor(Data, ((MWCropperPrevalueEditor)PrevalueEditor).Configuration);
return _Editor;
}
}
public override umbraco.interfaces.IData Data
{
get
{
if (_baseData == null)
_baseData = new umbraco.cms.businesslogic.datatype.DefaultData(this);
return _baseData;
}
}
public override Guid Id
{
get { return new Guid("71518B4E-B1A5-11DD-A22C-8AAA56D89593"); }
}
public override string DataTypeName
{
get { return "MWCropper"; }
}
public override umbraco.interfaces.IDataPrevalue PrevalueEditor
{
get
{
if (_prevalueeditor == null)
_prevalueeditor = new MWCropperPrevalueEditor(this);
return _prevalueeditor;
}
}
}
Class 3 PrevalueEditor
public class MWCropperPrevalueEditor : System.Web.UI.WebControls.PlaceHolder, umbraco.interfaces.IDataPrevalue
{
#region IDataPrevalue Members
// referenced datatype
private umbraco.cms.businesslogic.datatype.BaseDataType _datatype;
private TextBox _txtWidth;
private TextBox _txtHeight;
public MWCropperPrevalueEditor(umbraco.cms.businesslogic.datatype.BaseDataType DataType)
{
_datatype = DataType;
setupChildControls();
}
private void setupChildControls()
{
_txtWidth = new TextBox();
_txtWidth.ID = "txtWidth";
_txtWidth.CssClass = "umbEditorTextField";
Controls.Add(_txtWidth);
_txtHeight = new TextBox();
_txtHeight.ID = "txtHeight";
_txtHeight.CssClass = "umbEditorTextField";
Controls.Add(_txtHeight);
}
public Control Editor
{
get
{
return this;
}
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (!Page.IsPostBack)
{
if (Configuration.Length > 0)
{
string[] value = Configuration.Split(new char[]{';'});
_txtWidth.Text = value[0];
_txtHeight.Text = value[1];
}
else
{
_txtHeight.Text = "100";
_txtWidth.Text = "100";
}
}
}
public void Save()
{
_datatype.DBType = (umbraco.cms.businesslogic.datatype.DBTypes)Enum.Parse(typeof(umbraco.cms.businesslogic.datatype.DBTypes), DBTypes.Ntext.ToString(), true);
string data = _txtWidth.Text+";"+_txtHeight.Text;
SqlHelper.ExecuteNonQuery("delete from cmsDataTypePreValues where datatypenodeid = #dtdefid",
SqlHelper.CreateParameter("#dtdefid", _datatype.DataTypeDefinitionId));
SqlHelper.ExecuteNonQuery("insert into cmsDataTypePreValues (datatypenodeid,[value],sortorder,alias) values (#dtdefid,#value,0,'')",
SqlHelper.CreateParameter("#dtdefid", _datatype.DataTypeDefinitionId), SqlHelper.CreateParameter("#value", data));
}
protected override void Render(HtmlTextWriter writer)
{
writer.WriteLine("<table>");
writer.Write("<tr><th>Width:</th><td>");
_txtWidth.RenderControl(writer);
writer.Write("</td></tr>");
writer.Write("<tr><th>Height:</th><td>");
_txtHeight.RenderControl(writer);
writer.Write("</td></tr>");
writer.Write("</table>");
}
public string Configuration
{
get
{
object conf =
SqlHelper.ExecuteScalar<object>("select value from cmsDataTypePreValues where datatypenodeid = #datatypenodeid",
SqlHelper.CreateParameter("#datatypenodeid", _datatype.DataTypeDefinitionId));
if (conf != null)
return conf.ToString();
else
return "";
}
}
#endregion
public static ISqlHelper SqlHelper
{
get
{
return Application.SqlHelper;
}
}
}
I hope this can help you get started :)
Btw this also works for umbraco 6.2
Settings are called prevalues and you need a PrevalueEditor class that implements IDataPrevalue. Have a look at an example in this blog post:
http://www.eyecatch.no/blog/my-first-umbraco-datatype---part-2-rendering-a-recaptcha-control.aspx
This nibble post concerns doing a similar thing for v4.5 and prior, there is also a link within this for an even older version that I followed a while ago and found very helpful.
http://www.nibble.be/?p=62
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();