XAML RadGrid
<telerik:RadGridView d:DataContext="{d:DesignInstance {x:Type local:A}}" Name="myGridView" Grid.Column="2" ItemsSource="{Binding Path=MyList}" Margin="7,7,7,2" IsFilteringAllowed="False" ShowColumnHeaders="True" AutoGenerateColumns="True" />
C# Code
Class A:INotifyPropertyChanged
{
private List<Fields> MyList;
public event PropertyChangedEventHandler PropertyChanged;
public List<Fields> _theList
{
get
{
if (MyList == null)
{
MyList = new List<Fields>();
}
return MyList;
}
set
{
if (MyList != value)
{
MyList = value;
PropertyChanged(this, new PropertyChangedEventArgs("_theList"));
}
}
}
}
When the items in MyList change dynamically, the radgridview does not update automatically, It works if I reset the Itemssource in the code:
mygridview.Itemssource = Null;
mygridview.Itemssource = MyList;
I have to reset the itemssource everytime in the code after the MyList changes. Why GridView does not update automatically when contents of MyList change?
Also, During design time it shows me appropriate column headers with no data in the columns because the list is empty. But when I run the application, column headers disappear and no data is displayed in the radgrid when contents of MyList change dynamically.
When the items in MyList change dynamically,
You're notifying when your list property changes...which doesn't cover the above scenario. In order to support what you want, I think you need a collection that supports the INotifyCollectionChanged interface.
http://msdn.microsoft.com/en-us/library/system.collections.specialized.inotifycollectionchanged.aspx
Out of the box, ObservableCollection supports this. It derives from IList<T>. So perhaps you could do:
private IList<Fields> MyList;
private IList<Fields> MyObservableList;
public event PropertyChangedEventHandler PropertyChanged;
public IList<Fields> _theList
{
get
{
if (MyObservableList == null)
{
MyObservableList = new ObservableCollection<Fields>();
}
return MyObservableList;
}
set
{
if (MyList != value)
{
MyList = value;
MyObservableList = new ObservableCollection<Fields>(MyList );
// this will throw a null reference exception if no one' is listening. You
PropertyChanged(this, new PropertyChangedEventArgs("_theList"));
}
}
}
If you can forgoe having a List<T> instance for having an ObserableCollection<T> instance, the above could be even simpler:
private ObservableCollection<Fields> MyList;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Fields> _theList
{
get
{
if (MyList== null)
{
MyList= new ObservableCollection<Fields>();
}
return MyList;
}
set
{
if (MyList != value)
{
MyList = value;
// this will throw a null reference exception if no one' is listening. You should make a method OnPropertyChanged that checks if PropertyChanged != null.
PropertyChanged(this, new PropertyChangedEventArgs("_theList"));
}
}
}
Also, as an aside, usually the private members are _camelCase and the public members are PascalCase...not sure if it's intentional.
Related
I've a parent object (Product) and a child (Inventory). I'm trying to retrieve the value of the child object from a DisplayProducts class that I've created.
public class DisplayProducts {
private Product__c products;
public DisplayProducts(Product__c item) {
this.products = item;
}
// Properties for use in the Visualforce view
public String name {
get { return products.Name; }
}
public String colour {
//error here
get { return products.Inventorys__r.Colour__c; }
}
public class Product {
public List<DisplayProducts> getProducts() {
if(products == null) {
products = new List<DisplayProducts>();
for(Product__c item : [Select ProductID__c,Name, Price__c, (SELECT Inventory__c.Size__c, Inventory__c.Colour__c, Inventory__c.Quantity__c FROM Product__c.Inventorys__r)
From Product__c WHERE ProductID__c = :prodID]) {
products.add(new DisplayProducts(item));
}
}
return products;
}
}
I keep getting a compile error: invalid FK relationship.
I tried
products.Inventorys__r[0].Colour__c;
but it will only retrieved the first element.
How do I retrieve the child item via the DisplayProducts Class? Any help will be greatly appreciated.
Thank you.
The glass mapper will return null object or (no items) for SitecoreQuery and SitecoreChildren attribute that are placed on the GlassModels. These attributes don't take any such parameter where I can specify them to return items if they don't exist in the the context lanaguge. The items e.g. exist in EN but don't exist in en-ES. I need to put a lot of null check in my views to avoid Null exception and makes the views or controller very messy. It is lot of boiler plate code that one has to write to make it work.
In Page Editor the SitecoreChildren returns item and content authors can create items in that langauge version by editing any field on the item. This automatically creates the item in that langauge. However the same code will fail in Preview mode as SitecoreChidren will return null and you see null pointer exception.
SitecoreQuery doesn't return any items in page editor and then Content Authors wont be able to create items in Page editor.
To make the experience good if we can pass a parameter to SiteocreQuery attribute so it disable VsersionCount and returns the items if they dont exist in that langauge.
This is actually not possible. There is an issue on GitHub which would make it easy to create a custom attribute to handle this very easy. Currently you need to create a new type mapper and copy all the code from the SitecoreQueryMapper. I have written a blog post here about how you can create a custom type mapper. You need to create the following classes (example for the SitecoreQuery).
New configuration:
public class SitecoreSharedQueryConfiguration : SitecoreQueryConfiguration
{
}
New attribute:
public class SitecoreSharedQueryAttribute : SitecoreQueryAttribute
{
public SitecoreSharedQueryAttribute(string query) : base(query)
{
}
public override AbstractPropertyConfiguration Configure(PropertyInfo propertyInfo)
{
var config = new SitecoreSharedQueryConfiguration();
this.Configure(propertyInfo, config);
return config;
}
}
New type mapper:
public class SitecoreSharedQueryTypeMapper : SitecoreQueryMapper
{
public SitecoreSharedQueryTypeMapper(IEnumerable<ISitecoreQueryParameter> parameters)
: base(parameters)
{
}
public override object MapToProperty(AbstractDataMappingContext mappingContext)
{
var scConfig = Configuration as SitecoreQueryConfiguration;
var scContext = mappingContext as SitecoreDataMappingContext;
using (new VersionCountDisabler())
{
if (scConfig != null && scContext != null)
{
string query = this.ParseQuery(scConfig.Query, scContext.Item);
if (scConfig.PropertyInfo.PropertyType.IsGenericType)
{
Type outerType = Glass.Mapper.Sc.Utilities.GetGenericOuter(scConfig.PropertyInfo.PropertyType);
if (typeof(IEnumerable<>) == outerType)
{
Type genericType = Utilities.GetGenericArgument(scConfig.PropertyInfo.PropertyType);
Func<IEnumerable<Item>> getItems;
if (scConfig.IsRelative)
{
getItems = () =>
{
try
{
return scContext.Item.Axes.SelectItems(query);
}
catch (Exception ex)
{
throw new MapperException("Failed to perform query {0}".Formatted(query), ex);
}
};
}
else
{
getItems = () =>
{
if (scConfig.UseQueryContext)
{
var conQuery = new Query(query);
var queryContext = new QueryContext(scContext.Item.Database.DataManager);
object obj = conQuery.Execute(queryContext);
var contextArray = obj as QueryContext[];
var context = obj as QueryContext;
if (contextArray == null)
contextArray = new[] { context };
return contextArray.Select(x => scContext.Item.Database.GetItem(x.ID));
}
return scContext.Item.Database.SelectItems(query);
};
}
return Glass.Mapper.Sc.Utilities.CreateGenericType(typeof(ItemEnumerable<>), new[] { genericType }, getItems, scConfig.IsLazy, scConfig.InferType, scContext.Service);
}
throw new NotSupportedException("Generic type not supported {0}. Must be IEnumerable<>.".Formatted(outerType.FullName));
}
{
Item result;
if (scConfig.IsRelative)
{
result = scContext.Item.Axes.SelectSingleItem(query);
}
else
{
result = scContext.Item.Database.SelectSingleItem(query);
}
return scContext.Service.CreateType(scConfig.PropertyInfo.PropertyType, result, scConfig.IsLazy, scConfig.InferType, null);
}
}
}
return null;
}
public override bool CanHandle(AbstractPropertyConfiguration configuration, Context context)
{
return configuration is SitecoreSharedQueryConfiguration;
}
}
And configure the new type mapper in your glass config (mapper and parameters for the constructor):
container.Register(Component.For<AbstractDataMapper>().ImplementedBy<SitecoreSharedQueryTypeMapper>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemPathParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemIdParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemIdNoBracketsParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemEscapedPathParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemDateNowParameter>>().LifeStyle.Transient);
You can then simply change the SitecoreQuery attribute on your model to SitecoreSharedQuery:
[SitecoreSharedQuery("./*")]
public virtual IEnumerable<YourModel> YourItems { get; set; }
For the children you could either use the shared query mapper and querying the children or create the same classes for a new SitecoreSharedChildren query.
Edit: Added bindings for IEnumerable<ISitecoreQueryParameter> as they are missing and therefor it threw an error.
I am quite new at Android.
So I am a bit confused of working with fragments.
I have found a very great tutorial.
So I have working code. But it is the layout oft a normal activity.
Then I tried to include it into a navigation drawer.
So the list view with data will only be shown when the menu item has been selected.
On the fragment View there is a never ending loading Dialog.
While debugging I have figured out that the code loads still the data and inserts it into feedItems.
So feedItems is filled correctly.
Now after listAdapter.notifyDataSetChanged() there happens nothing.
So here that is my code:
public class FragmentNews extends ListFragment {
private static final String TAG = FragmentNews.class.getSimpleName();
private ListView listView;
private FeedListAdapter listAdapter;
private List<FeedItem> feedItems;
private String URL_FEED = "http://address.com";
public FragmentNews(){}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);
loadDataForNews();
}
private void loadDataForNews(){
listView = this.getListView();
feedItems = new ArrayList<FeedItem>();
listAdapter = new FeedListAdapter(getActivity(), feedItems);
listView.setAdapter(listAdapter);
// We first check for cached request
Cache cache = AppController.getInstance().getRequestQueue().getCache();
Entry entry = cache.get(URL_FEED);
if (entry != null) {
// fetch the data from cache
try {
String data = new String(entry.data, "UTF-8");
try {
parseJsonFeed(new JSONObject(data));
} catch (JSONException e) {
e.printStackTrace();
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
// making fresh volley request and getting json
JsonObjectRequest jsonReq = new JsonObjectRequest(Method.GET,
URL_FEED, null, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
VolleyLog.d(TAG, "Response: " + response.toString());
if (response != null) {
parseJsonFeed(response);
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d(TAG, "Error: " + error.getMessage());
}
});
// Adding request to volley request queue
AppController.getInstance().addToRequestQueue(jsonReq);
}
}
// List View Feed
private void parseJsonFeed(JSONObject response) {
try {
JSONArray feedArray = response.getJSONArray("feed");
for (int i = 0; i < feedArray.length(); i++) {
JSONObject feedObj = (JSONObject) feedArray.get(i);
FeedItem item = new FeedItem();
item.setId(feedObj.getInt("id"));
item.setName(feedObj.getString("name"));
// Image might be null sometimes
String image = feedObj.isNull("image") ? null : feedObj
.getString("image");
item.setImge(image);
item.setStatus(feedObj.getString("status"));
item.setProfilePic(feedObj.getString("profilePic"));
item.setTimeStamp(feedObj.getString("timeStamp"));
// url might be null sometimes
String feedUrl = feedObj.isNull("url") ? null : feedObj
.getString("url");
item.setUrl(feedUrl);
feedItems.add(item);
}
// notify data changes to list adapater
listAdapter.notifyDataSetChanged();
} catch (JSONException e) {
e.printStackTrace();
}
}
}
Can the problem be that the inflater of listAdapter is null?
Thanks for help!
Sometimes listAdapter.notifyDataSetChanged() does not work properly.
Try removing
listAdapter = new FeedListAdapter(getActivity(), feedItems);
listView.setAdapter(listAdapter);
from loadDataForNews() and adding in
place of listAdapter.notifyDataSetChanged();
i have a set of item under clarisonic catalog.I want to create the same set of items in another place.for example.I want to create same set of items under amazon with different template.
First i am getting all child items under clarisonic catalog and getting the name of those items.I am creating new items under Amazon with the names which i got it earlier with different template.
I want to create same set of items under amazon[all items under clarisonic catalog].
public void EntryPath(Item item)
{
List<string> ObjSiteNames = getMultiListValues(item, "Select Site");
GetChildrenSelectedItem(item, ObjSiteNames);
RecursiveItemCreation(item);
}
public List<string> getMultiListValues(Sitecore.Data.Items.Item item, string FieldID)
{
Sitecore.Data.Fields.MultilistField multiselect = item.Fields[FieldID];
return multiselect.GetItems().Select(a => a.Name).ToList();
}
public void GetChildrenSelectedItem(Item getChildredItem, List<string> sitesnmaes)
{
string defaultSitePath = "/sitecore/content/Administration/Sites";
masterDb = Sitecore.Configuration.Factory.GetDatabase("master");
templateItem = masterDb.GetItem("/sitecore/templates/User Defined/SC-DW Data/Generic/Widgets/NavigationItem");
foreach (string str in sitesnmaes)
{
StringBuilder strBuilder = new StringBuilder();
strBuilder.Append(defaultSitePath).Append("/").Append(str);
itemDesPath = masterDb.GetItem(strBuilder.ToString());
}
}
public void RecursiveItemCreation(Item Getchilds)
{
foreach (Item i in Getchilds.GetChildren())
{
i.Template = masterDb.GetItem("/sitecore/templates/User Defined/SC-DW Data/Generic/Widgets/NavigationItem").
if ((i.HasChildren))
{
}
else
{
itemDesPath.Add(i.Name, templateItem);
foreach (Item ItemDes in itemDesPath.Axes.GetDescendants())
{
if (ItemDes.Name == i.Name)
{
ItemDes.Editing.BeginEdit();
ItemDes.Fields["Datasource"].Value = i.Paths.Path;
ItemDes.Editing.EndEdit();
}
}
}
}
From what I understand you want to copy the whole tree below Clarisonic Catalog item to the Amazon node. The only difference is that the created items should use different template (/sitecore/templates/User Defined/SC-DW Data/Generic/Widgets/NavigationItem).
Code below should do the job. I haven't tested it but I'm sure you can solve all the problems you'll encounter.
public void CopyTreeStructure(Item source, Item target)
{
// find the new template you want to use
TemplateItem newTemplate = new TemplateItem(source.Database.GetItem("/sitecore/templates/User Defined/SC-DW Data/Generic/Widgets/NavigationItem"));
foreach (Item child in source.Children)
{
// create the copy of original item using new template
Item copiedItem = CreateItemUsingNewTemplate(child, target, newTemplate);
// repeat for all descendants recursively
CopyTreeStructure(child, copiedItem);
}
}
private Item CreateItemUsingNewTemplate(Item source, Item targetParent, TemplateItem templateToUse)
{
// create item
Item copiedItem = targetParent.Add(source.Name, templateToUse);
// pre-read all fields
source.Fields.ReadAll();
using (new EditContext(copiedItem))
{
// update all the fields of new item
foreach (Field field in source.Fields)
{
copiedItem[field.Name] = source[field.Value];
}
}
// return copied item so we can copy it's descendants
return copiedItem;
}
When I manually archive an item which is referenced by other items Sitecore popup dialog box with Actions – how to handle the links.
If the item is configured for automatic archiving with “Set Archive Date” and it is archived seems that Sitecore is choosing by default “Leave Links” action, so all links to the archived item will be broken.
How/Where could I hooked up in order to stop archiving of item (scheduled archiving) which is referenced by other items? I would like to stop archiving and create some rapport that that archiving was not successful.
In order to prevent Sitecore from archiving linked items, you need to overrider 2 classes.
First of them is ArchiveItem so it's checking whether item is linked before archiving it:
namespace My.Assembly.And.Namespace
{
public class MyArchiveItem : Sitecore.Tasks.ArchiveItem
{
public MyArchiveItem(System.DateTime taskDate) : base(taskDate)
{
}
public override void Execute()
{
using (new Sitecore.SecurityModel.SecurityDisabler())
{
lock (SyncRoot)
{
Sitecore.Data.Items.Item item = GetItem();
if (item != null && HasLink(Sitecore.Globals.LinkDatabase, item))
{
Sitecore.Diagnostics.Log.Error(string.Format(
"Item {0} or one of its descendants are linked from other items. "
+ "Remove link before scheduling archive.", item.Paths.FullPath), this);
// uncomment next line if you don't want to retry archiving attempt
//Sitecore.Globals.TaskDatabase.Remove(this);
return;
}
}
}
base.Execute();
}
private static bool HasLink(Sitecore.Links.LinkDatabase linkDatabase, Sitecore.Data.Items.Item item)
{
Sitecore.Links.ItemLink[] referrers = linkDatabase.GetReferrers(item);
if (referrers.Length > 0)
{
if (referrers.Any(link => link.SourceFieldID != Sitecore.FieldIDs.Source))
{
return true;
}
}
foreach (Sitecore.Data.Items.Item item2 in item.Children)
{
if (HasLink(linkDatabase, item2))
{
return true;
}
}
return false;
}
}
}
Second class which you need to override is SqlServerTaskDatabase so it schedules the overriden MyArchiveItem task instead of the original Sitecore ArchiveItem:
namespace My.Assembly.And.Namespace
{
public class MySqlServerTaskDatabase : Sitecore.Data.SqlServer.SqlServerTaskDatabase
{
public MySqlServerTaskDatabase(string connectionString) : base(connectionString)
{
}
public override void UpdateItemTask(Sitecore.Tasks.Task task, bool insertIfNotFound)
{
Sitecore.Data.Sql.SqlBatch batch = new Sitecore.Data.Sql.SqlBatch(true);
BindTaskData(task, batch);
string sql = GetUpdateSql() +
" WHERE [ItemID] = #itemID AND [Database] = #databaseName AND [taskType] = #taskType";
batch.AddSql(sql);
if (insertIfNotFound)
{
AddInsertTask(batch, true);
}
batch.Execute(ConnectionString);
}
protected new virtual void BindTaskData(Sitecore.Tasks.Task task,
Sitecore.Data.Sql.SqlBatch batch)
{
System.DateTime taskDate = task.TaskDate;
if (taskDate == System.DateTime.MinValue)
{
taskDate = (System.DateTime)System.Data.SqlTypes.SqlDateTime.MinValue;
}
batch.AddParameter("taskID", task.ID);
batch.AddParameter("nextRun", taskDate);
if (task is Sitecore.Tasks.ArchiveItem)
{
batch.AddParameter("taskType",
Sitecore.Reflection.ReflectionUtil.GetTypeString(typeof(MyArchiveItem)));
}
else
{
batch.AddParameter("taskType", ReflectionUtil.GetTypeString(task.GetType()));
}
batch.AddParameter("parameters", task.Parameters);
batch.AddParameter("recurrence", task.RecurrencePattern);
batch.AddParameter("itemID", task.ItemID);
batch.AddParameter("databaseName", task.DatabaseName);
if (string.IsNullOrEmpty(task.InstanceName))
{
batch.AddParameter("instanceName", System.DBNull.Value);
}
else
{
batch.AddParameter("instanceName", task.InstanceName);
}
}
}
}
The last thing you need to do is to update Sitecore config to point at MySqlServerTaskDatabase:
<TaskDatabase type="My.Assembly.And.Namespace.MySqlServerTaskDatabase, My.Assembly">
<param connectionStringName="core"/>
</TaskDatabase>
The information about failed archiving attempt will be stored in log files. You may want to update this part to store it in your custom reports.
Below goes additional information which is not necessary for your original problem to work.
You can also hook before the schedule is set as described below to inform user that the item won't be archived.
First create the class that will override ArchiveDateForm class:
namespace My.Assembly.And.Namespace
{
public class MyArchiveDateForm
: Sitecore.Shell.Applications.Dialogs.ArchiveDate.ArchiveDateForm
{
protected override bool SetItemArchiveDate
(Sitecore.Data.Items.Item item, string value)
{
if (HasLink(Sitecore.Globals.LinkDatabase, item))
{
Sitecore.Web.UI.Sheer.SheerResponse.Alert(
"Item or one of its descendants are linked from other items. "
+ "Remove link before scheduling archive.", new string[0]);
return false;
}
return base.SetItemArchiveDate(item, value);
}
private static bool HasLink(Sitecore.Links.LinkDatabase linkDatabase,
Sitecore.Data.Items.Item item)
{
Sitecore.Links.ItemLink[] referrers =
linkDatabase.GetReferrers(item);
if (referrers.Length > 0)
{
if (referrers.Any(
link => link.SourceFieldID != Sitecore.FieldIDs.Source))
{
return true;
}
}
foreach (Sitecore.Data.Items.Item item2 in item.Children)
{
if (HasLink(linkDatabase, item2))
{
return true;
}
}
return false;
}
}
}
Then find the file /sitecore/shell/applications/dialogs/archive item/archive date.xml. Change the 6th line to point at the new class:
<CodeBeside Type="My.Assembly.And.Namespace.MyArchiveDateForm,My.Assembly" />
And that's it. Whenever one will try to schedule archiving of an linked item, Sitecore will display information that the item cannot be archived.