I have an ObservableCollection<Category> binded to a NavigationView, where Category is a custom class that implements INotifyPropertyChanged. I created a DataTemplate to display the element of the collection
<DataTemplate x:Key="CategoryTemplate">
<NavigationViewItem Icon="{Binding Icon}" RightTapped="CategoryItem_RightTapped">
<local:CategoryViewItem CategoryItem="{Binding Mode=TwoWay}"/>
</NavigationViewItem>
</DataTemplate>
Now I want to add some default NavigationViewItem and a NavigationViewItemSeparator at the top of the list with a different DataTemplate keeping the second part "Observable" and "Notifying changes of properties". You can see an example of what I mean in the image below.
For your requirement, you need make MenuItemTemplateSelector for NavigationView. And pass the different DataTemplate base on the data source.
Default NavigationViewItem and NavigationViewItemSeparator Data Model
public class CategoryBase { }
public class DefaultCategory: CategoryBase
{
public string Name { get; set; }
public string Tooltip { get; set; }
public Symbol Glyph { get; set; }
}
public class CustomCategory : CategoryBase
{
public SymbolIcon Icon { get; set; }
public string Title { get; set; }
}
public class Separator : CategoryBase { }
MenuItemTemplateSelector
public class MenuItemTemplateSelector : DataTemplateSelector
{
internal DataTemplate SeparatorTemplate = (DataTemplate)XamlReader.Load(
#"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
<NavigationViewItemSeparator />
</DataTemplate>");
public DataTemplate DefaultItemTemlate { get; set; }
public DataTemplate CustomItemTemlate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
return item is Separator ? SeparatorTemplate : item is CustomCategory ? CustomItemTemlate : DefaultItemTemlate;
}
}
Xaml Code
<Page.Resources>
<local:MenuItemTemplateSelector x:Key="selector">
<local:MenuItemTemplateSelector.DefaultItemTemlate>
<DataTemplate x:DataType="local:DefaultCategory">
<NavigationViewItem Content="{x:Bind Name}">
<NavigationViewItem.Icon>
<SymbolIcon Symbol="{x:Bind Glyph}" />
</NavigationViewItem.Icon>
</NavigationViewItem>
</DataTemplate>
</local:MenuItemTemplateSelector.DefaultItemTemlate>
<local:MenuItemTemplateSelector.CustomItemTemlate>
<DataTemplate>
<NavigationViewItem Icon="{Binding Icon}">
<TextBlock Text="{Binding Title}" />
</NavigationViewItem>
</DataTemplate>
</local:MenuItemTemplateSelector.CustomItemTemlate>
</local:MenuItemTemplateSelector>
</Page.Resources>
<Grid>
<NavigationView
x:Name="nvSample"
MenuItemTemplateSelector="{StaticResource selector}"
MenuItemsSource="{x:Bind Categories, Mode=OneWay}"
/>
<Button Click="Button_Click" Content="AddItem" />
</Grid>
Usage
public MainPage()
{
this.InitializeComponent();
Categories = new ObservableCollection<CategoryBase>();
Categories.Add(new CustomCategory { Title = "This is Titlte", Icon = new SymbolIcon(Symbol.Play) });
}
public ObservableCollection<CategoryBase> Categories { get; }
private void Button_Click(object sender, RoutedEventArgs e)
{
Categories.Insert(0, new Separator());
Categories.Insert(0, new DefaultCategory { Name = "Category 1", Glyph = Symbol.Home, Tooltip = "This is category 1" });
}
You could find the code sample here, and this NavigationView document that you could refer.
Related
I have an ObservableCollection<List<Model>> Data in my ViewModel.
In my Page I need a CarouselView, in which each ItemTemplate shows the data of the Data list in a ListView.
Currently, I am doing that in that way:
<CarouselView ItemsSource="{Binding Data}">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
...
<ListView ItemsSource="{Binding .}">
...
</ListView>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
In the way I am doing that I get a "Specified cast not valid" exception, in which I see the following additional information:
{System.InvalidCastException: Specified cast is not valid.
at (wrapper castclass) System.Object.__castclass_with_cache(object,intptr,intptr)
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].ActivateContent (System.Int32 index, System.Object item) [0x00032]
in <62e3629c74b84e3d834046331d2bb5f8>:0
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].CreateContent (System.Int32 index, System.Object item, System.Boolean insert) [0x00000]
in <62e3629c74b84e3d834046331d2bb5f8>:0
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].GetOrCreateContent (System.Int32 index, System.Object item) [0x00023]
in <62e3629c74b84e3d834046331d2bb5f8>:0
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].get_Item (System.Int32 index) [0x0000e]
in <62e3629c74b84e3d834046331d2bb5f8>:0
at Xamarin.Forms.Platform.iOS.ListViewRenderer+ListViewDataSource.GetCellForPath (Foundation.NSIndexPath indexPath) [0x00007]
in D:\a\_work\1\s\Xamarin.Forms.Platform.iOS\Renderers\ListViewRenderer.cs:1397
at Xamarin.Forms.Platform.iOS.ListViewRenderer+ListViewDataSource.GetCell (UIKit.UITableView tableView, Foundation.NSIndexPath indexPath) [0x00021]
in D:\a\_work\1\s\Xamarin.Forms.Platform.iOS\Renderers\ListViewRenderer.cs:1105
at (wrapper managed-to-native) UIKit.UIApplication.UIApplicationMain(int,string[],intptr,intptr)
at UIKit.UIApplication.Main (System.String[] args, System.Type principalClass, System.Type delegateClass) [0x0003b]
in /Users/builder/azdo/_work/1/s/xamarin-macios/src/UIKit/UIApplication.cs:85
at App.iOS.Application.Main (System.String[] args) [0x00001]
in <Path>\Main.cs:18 }
The Model holds only string values, so the exception cannot come from this.
I'm not sure why you're getting that specific exception. I couldn't get the ListView inside of a CarouselView to work either.
However, it works when you use a bindable StackLayout instead of a ListView. My guess is that the bindable StackLayout doesn't support scrolling and thus doesn't fight with the CarouselView but I don't know.
MainPage, MainViewModel and items
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Xamarin.Forms;
namespace App1
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
}
public class MainViewModel
{
public ObservableCollection<Item> Data { get; }
public MainViewModel()
{
Data = new ObservableCollection<Item>(GenerateItems());
}
private IEnumerable<Item> GenerateItems()
{
return Enumerable.Range(1, 10)
.Select(a => new Item
{
ItemTitle = $"Item {a}",
SubItems = Enumerable.Range(1, 10).Select(b => new SubItem { SubItemTitle = $"SubItem {b}" }).ToList()
});
}
}
public class Item
{
public string ItemTitle { get; set; }
public List<SubItem> SubItems { get; set; }
}
public class SubItem
{
public string SubItemTitle { get; set; }
}
}
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="App1.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App1">
<ContentPage.BindingContext>
<local:MainViewModel />
</ContentPage.BindingContext>
<CarouselView ItemsSource="{Binding Data}">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding ItemTitle}" />
<StackLayout BindableLayout.ItemsSource="{Binding SubItems}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label Text="{Binding SubItemTitle}" />
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
</ContentPage>
Result:
I have a selectList of foreignkey (like a parent). Currently, the ID are binded for the value and item name. I want to change that for my Name property:
Here's my models:
public class Genus
{
public int GenusID { get; set; }
public EnumCategory Category { get; set; }
public string Name { get; set; }
public List<Species> Species { get; set; }
}
public class Species
{
public int SpeciesID { get; set; }
public int GenusID { get; set; }
public virtual Genus Genus { get; set; }
public string Name { get; set; }
}
On my Create and Edit Species page I have this code:
<select asp-for="Species.GenusID" class ="form-control" asp-items="ViewBag.GenusID"></select>
This code are generated by default when We Add Razor Page Scaffold. Well, the result are on the line 1, and what I want of result on the line 2:
<option selected="selected" value="1">1</option>
<option selected="selected" value="1">NameProperty</option> <!-- Species.Genus.Name -->
Do you have an idea to make that right ?
Thanks per advance
Looking at the code for the NavigationMetadata class in the scaffolding repo (https://github.com/aspnet/Scaffolding) I have found the following commented code which describe the behaviour.
// The default for the display property is the primary key of the navigation.
DisplayPropertyName = PrimaryKeyNames[0];
// If there is a non nullable string property in the navigation's target type, we use that instead.
var displayPropertyCandidate = navigation
.GetTargetType()
.GetProperties()
.FirstOrDefault(p => !p.IsNullable && p.ClrType == typeof(string));
if (displayPropertyCandidate != null)
{
DisplayPropertyName = displayPropertyCandidate.Name;
}
So if you want the Name property to show by default instead of the ID, then ensure it is the first string property in the model (yours already is), and then make sure it's not nullable.
protected override void OnModelCreating(ModelBuilder builder)
{
// ...
builder.Entity<Genus>().Property(l => l.Name).IsRequired();
// ...
}
I have created a bunch of custom templates to store items (such as Industries, Subindustries, etc.) in Sitecore. I now want to go about loading these into my Sitecore MVC model.
The lists are located in sitecore > Content > Lists. For example inside the Lists folder there is a folder called Country. I want to get back all the items within the Country folder and populate them as unordered list in my view.
UPDATE: I implemented the Glass.Mapper.Sc method suggested below. It is fully operational now.
This is what my working model looks like now:
using Glass.Mapper.Sc.Configuration;
using Glass.Mapper.Sc.Configuration.Attributes;
using Sitecore.Data.Items;
using Sitecore.Mvc.Presentation;
using System;
using System.Collections.Generic;
namespace Sitecore.Web.Models
{
public class Registration: IRenderingModel
{
public Rendering Rendering { get; set; }
public Item Item { get; set; }
public Item PageItem { get; set; }
public IEnumerable<CountryChildItem> CountryList { get; set; }
[SitecoreType(AutoMap = true)]
public class CountryItem
{
public virtual IEnumerable<CountryChildItem> Children { get; set; }
}
[SitecoreType(AutoMap = true)]
public class CountryChildItem
{
[SitecoreId]
public virtual Guid Id { get; set; }
[SitecoreInfo(SitecoreInfoType.Path)]
public virtual string Path { get; set; }
[SitecoreField]
public virtual string DisplayName { get; set; }
[SitecoreField]
public virtual string Abbreviation { get; set; }
}
public void Initialize(Rendering rendering)
{
Rendering = rendering;
Item = rendering.Item;
PageItem = PageContext.Current.Item;
}
}
}
and this is what my working contoller looks like:
using Glass.Mapper.Sc;
using Sitecore.Web.Models;
using System.Web.Mvc;
namespace Sitecore.Web.Controllers
{
public class RegistrationController : Controller
{
Registration registrationModel = new Registration();
public ActionResult Index()
{
ISitecoreContext sitecoreContext = new SitecoreContext();
ISitecoreService service = new SitecoreService(sitecoreContext.Database);
Registration.CountryItem countryItem = service.GetItem<Registration.CountryItem>("/sitecore/content/Lists/Country");
registrationModel.CountryList = countryItem.Children;
return View(registrationModel);
}
}
}
and a snippet of my working view:
<ul class="select-menu-options dropdown-menu">
#foreach (var country in Model.CountryList)
{
<li>#country.DisplayName</li>
}
</ul>
If I were in your position I'd look into Glassmapper for Sitecore.
It's a fairly lightweight ORM for Sitecore.
http://www.glass.lu/Mapper/Sc
I'd also suggest moving the lists located in
sitecore > Templates > User Defined > Lists > Content
to some where under either
sitecore > Content
or
sitecore > System
(whichever makes more sence)
UPDATE:
Try adding this above your class:
[SitecoreType(AutoMap = true)]
public class CountryItem
{
//...
}
If you change your CountryItem and other model classes to inherit from SearchResultItem like that:
[PredefinedQuery("TemplateID", ComparisonType.Equal, "{ID-OF-CountryItem-TEMPLATE}", typeof(ID))]
public class CountryItem : Sitecore.ContentSearch.SearchTypes.SearchResultItem
{
[IndexField("_displayname")]
public virtual string DisplayName { get; set; }
[IndexField("abbreviation")]
public string Abbreviation { get; set; }
}
You should be able to use Sitecore indexes to retrieve all the countries and other lists like that:
private static string IndexName
{
get
{
return string.Format("sitecore_{0}_index", (Context.ContentDatabase ?? Context.Database).Name);
}
}
private static string Language { get { return Context.Language.Name; } }
public IEnumerable<CountryItem> GetCountries()
{
using (var context = ContentSearchManager.GetIndex(IndexName).CreateSearchContext())
{
IQueryable<CountryItem> queryable = context.GetQueryable<CountryItem>();
queryable = queryable.Where(i => i.Language == Language);
queryable = queryable.Where(i => i.LatestVersion);
// ... maybe excluding standard values or some other filters
var searchResults = queryable.GetResults();
return queryable.ToList();
}
}
Please be aware that this is just an example. You need to test it and most probably adapt to your solution.
And as Dar Brett mentioned, you should not keep any data items under the Templates node.
I'm developing an application where I need to save a List of objects of type ExerciseObject. I don't understand why this data isn't persisting in the IsolatedStorageSettings after application restart while all of my other data is (including other objects I created).
Here is the ExerciseObject class in which I create a list containing this object type to be stored to IsolatedStorageSettings. The interesting part is that while the application is open, the data is saved, it's just once I restart the app that only the List of ExerciseObject data is lost.
public class ExerciseObject
{
public ExerciseObject(string description, int caloriesBurned, bool burned)
{
this.Description = description;
this.CaloriesBurned = caloriesBurned;
this.Burned = burned; // true if activity, false if food
if (this.Burned) // text should be green
this.TextColor = new SolidColorBrush(Colors.Green);
else
this.TextColor = new SolidColorBrush(Colors.Red);
}
public string Description { get; set; }
public int CaloriesBurned { get; set; }
public bool Burned { get; set; }
public SolidColorBrush TextColor { get; set; }
}
This is how I am adding to the list:
ExerciseObject exerciseObj = new ExerciseObject(this.txtActivity.Text, int.Parse(this.txtBurned.Text), true);
List<ExerciseObject> tempList = (List<ExerciseObject>)IsolatedStorageSettings.ApplicationSettings["ListExerciseObjects"];
tempList.Add(exerciseObj);
IsolatedStorageSettings.ApplicationSettings["ListExerciseObjects"] = tempList;
And this is how I am accessing the List:
// Get the list of exercise objects from the isolated storage
List<ExerciseObject> exerciseObjects = (List<ExerciseObject>)IsolatedStorageSettings.ApplicationSettings["ListExerciseObjects"];
// Setting data context of listBox to the list of exercise objects for now
this.listBoxEntries.DataContext = exerciseObjects;
I tried your example, and it seems that the type SolidColorBrush is not serializable. The phone internals calls the iso storage "Save" method when the app exists and this is failing silently. to repro, remove the TextColor property or apply the "IgnoreDataMemberAttribute" on the property and observe that the issue goes away.
There are various ways to go about fixing this. I would personally derive the type of brush to apply at runtime, from your "burned" property.
I have attached a working example of your code which now stores the actual color as opposed to a SolidColorBrush object if you still wish to go store it.
Main.cs
// Constructor
public MainPage()
{
InitializeComponent();
BindExercises();
}
private void AddExercise(object sender, RoutedEventArgs e)
{
var exercise = new ExerciseObject("Activity added at: " + DateTime.Now.Ticks, (DateTime.Now.Second + 200), true);
IsolatedStorageSettingsManager.AddToCollection("ListExerciseObjects", exercise);
this.BindExercises();
}
private void BindExercises()
{
// Setting data context of listBox to the list of exercise objects for now
this.listBoxEntries.ItemsSource = IsolatedStorageSettingsManager.Get<IEnumerable<ExerciseObject>>("ListExerciseObjects").ToObservableCollection();
}
private void RemoveAllExercises(object sender, RoutedEventArgs e)
{
IsolatedStorageSettingsManager.Remove("ListExerciseObjects");
this.BindExercises();
}
public static class EnumerableExtensions
{
public static ObservableCollection<T> ToObservableCollection<T>(this IEnumerable<T> myList)
{
if (myList == null) return null;
var oc = new ObservableCollection<T>();
foreach (var item in myList)
oc.Add(item);
return oc;
}
}
public class ExerciseObject
{
public ExerciseObject() { }
public ExerciseObject(string description, int caloriesBurned, bool burned)
{
this.Description = description;
this.CaloriesBurned = caloriesBurned;
this.Burned = burned; // true if activity, false if food
if (this.Burned) // text should be green
this.Color = Colors.Green;
else
this.Color = Colors.Red;
}
public string Description { get; set; }
public int CaloriesBurned { get; set; }
public bool Burned { get; set; }
public Color Color { get; set; }
[IgnoreDataMemberAttribute]
public SolidColorBrush TextColor
{
get
{
return new SolidColorBrush(this.Color);
}
}
}
public class IsolatedStorageSettingsManager
{
private static readonly IsolatedStorageSettings isolatedStorageSettings = IsolatedStorageSettings.ApplicationSettings;
public static void Add<T>(string key, T value)
{
if (isolatedStorageSettings.Contains(key))
{
isolatedStorageSettings[key] = value;
}
else
{
isolatedStorageSettings.Add(key, value);
}
Save();
}
public static T Get<T>(string key, T #default = default(T))
{
T value;
if (isolatedStorageSettings.TryGetValue(key, out value))
{
return value;
}
return #default; // TODO: tell it what to do if the key is not found.
}
/// <summary>
/// Special [very crude] method which handles collections.
/// </summary>
/// <typeparam name="T">
/// The type of object to be serialized.
/// </typeparam>
/// <param name="key">
/// The key to assign to the object.
/// </param>
/// <param name="newValue">
/// The new record to add.
/// </param>
/// <returns>
/// The newly updated collection.
/// </returns>
public static IEnumerable<T> AddToCollection<T>(string key, T newValue) where T : class
{
List<T> currentValues;
if (isolatedStorageSettings.Contains(key))
{
currentValues = isolatedStorageSettings[key] as List<T>;
if (currentValues == null)
{
throw new InvalidCastException("The current values in the isolated storage settings " + key + "is not of a valid type");
}
currentValues.Add(newValue);
isolatedStorageSettings[key] = currentValues;
}
else
{
currentValues = new List<T> { newValue };
isolatedStorageSettings.Add(key, currentValues);
}
Save();
return currentValues;
}
public static void Remove(string key)
{
if (isolatedStorageSettings.Contains(key))
{
isolatedStorageSettings.Remove(key);
Save();
}
}
private static void Save()
{
isolatedStorageSettings.Save();
}
}
Xaml
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Content="Add Exercise" Click="AddExercise" />
<Button Grid.Row="1" Content="Clear All" Click="RemoveAllExercises" />
<ListBox x:Name="listBoxEntries" Grid.Row="2">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Description }" />
<TextBlock Margin="15 0 0 0" Text="{Binding CaloriesBurned }" Foreground="{Binding TextColor}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
i'm creating demo project there i create Item which contains sub-Item now i want to render these using web controller my code like this
site items created as following image
and my glass mapper code is as:
public static class GlassMapperSc
{
public static void Start()
{
//create the resolver
var resolver = DependencyResolver.CreateStandardResolver();
//install the custom services
GlassMapperScCustom.CastleConfig(resolver.Container);
//create a context
var context = Glass.Mapper.Context.Create(resolver);
context.Load(
GlassMapperScCustom.GlassLoaders()
);
GlassMapperScCustom.PostLoad();
}
public class DesktopHome
{
public virtual string Title { get; set; }
public virtual string Description { get; set; }
public virtual string LeftRotatorTitle { get; set; }
public virtual string RightRotatorTitle { get; set; }
}
public class GlobalsItem
{
public class HeaderTemplateItem
{
public class NavItem
{
public virtual string Title { get; set; }
public virtual string Link { get; set; }
public virtual IEnumerable<NavItem> Children { get; set; }
}
}
}
}
i'm able to get parent items but not able to get child items please anyone help me to figure out this issue
Define your Modal Class as:
[SitecoreClass]
public class Header
{
[SitecoreInfo(SitecoreInfoType.Url)]
public virtual string About{ get; set; }
[SitecoreField]
public virtual string Home{ get; set; }
[SitecoreField]
public virtual string Services{ get; set; }
[SitecoreField]
public virtual IEnumerable<Header> Links { get; set; }
}
Configuring the application
To configure Glass Mapper is really straight forward. Open or create a Global.ascx file in your project and on the application start add the following code:
protected void Application_Start(object sender, EventArgs e)
{
AttributeConfigurationLoader loader = new AttributeConfigurationLoader(
new string[] { "Glass.Sitecore.Mapper.Demo.Application.Domain, Glass.Sitecore.Mapper .Demo" }
);
Persistence.Context context = new Context(loader, null);
}
your view code will be as:
<div>
<h1>
<asp:Literal runat="server" ID="About" />
</h1>
<div class="body">
<asp:Literal runat="server" ID="Home" />
</div>
<div class="links">
<asp:Repeater runat="server" ID="links">
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li><a href='<%# DataBinder.Eval(Container.DataItem,"Url") %>'>
<%# DataBinder.Eval(Container.DataItem,"Services") %></a> </li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</div>
Next lets look at the code behind page, for simplicity everything is going in the Page_Load method:
protected void Page_Load(object sender, EventArgs e)
{
ISitecoreContext context = new SitecoreContext();
DemoClass item = context.GetCurrentItem<DemoClass>();
title.Text = item.Title;
body.Text = item.Body;
links.DataSource = item.Links;
links.DataBind();
}