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>
Related
I am using UWP to create an app for a server application and I have made a class serverclass.h in c++ in which I have a string output. Now I want to print this output in a textbox in xaml. I have attached the codes below. When I'm using this->DataContext=ser; it is giving me an error:"function Windows::UI::Xaml::FrameworkElement::DataContext::set cannot be called with the given argument list".
What is the problem here?
mainpage.xaml.cpp
serverclass ser;
MainPage::MainPage()
{
InitializeComponent();
this->DataContext = ser;
}
void App1::MainPage::Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
ser.output = "hey";
ser.connect();
}
serverclass.h
class serverclass
{
public:
string output;
}
"mainpage.xaml"
<TextBox Text="{Binding output}" Height="50" FontSize="30px">
</TextBox>
If you want to print your string output in a textbox in xaml, you could use data binding referring to the document and sample.
For the scenario mentioned by you, you could check the following code:
serverclass.h
namespace YourAppName{//Put the serverclass into the namespace
public ref class serverclass sealed : Windows::UI::Xaml::Data::INotifyPropertyChanged
{
private:
Platform::String^ output;
public:
virtual event Windows::UI::Xaml::Data::PropertyChangedEventHandler^ PropertyChanged;
serverclass()
{ }
property Platform::String^ Output {
Platform::String^ get() { return output; }
void set(Platform::String^ value) {
if (output != value)
{
output = value;
PropertyChanged(this, ref new Windows::UI::Xaml::Data::PropertyChangedEventArgs("Output"));
}
}
};
};
}
MainPage.xaml.h
public ref class MainPage sealed
{
public:
MainPage();
property serverclass^ Ser {
serverclass^ get() { return ser; }
void set(serverclass^ value) {
ser = value;
}
}
private:
serverclass^ ser;
void Button_Click(Platform::Object^ sender,
Windows::UI::Xaml::RoutedEventArgs^ e);
};
MainPage.xaml.cpp
MainPage::MainPage()
{
InitializeComponent();
this->ser = ref new serverclass();
ser->Output = "world";
this->DataContext = ser;
}
void DataContextSample::MainPage::Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
ser->Output = "hello";
}
MainPage.xaml
<StackPanel>
<TextBox Text="{x:Bind Ser.Output,Mode=TwoWay}" Width="100" Height="30" Margin="10"/>
<Button Click="Button_Click" Content="click me"/>
</StackPanel>
I am trying to display grouped list of songs, which I get from server. Songs are grouped in sets according to their first letter. I've created two classes in this purpose SongSet and Song.
public class SongSet
{
public string FirstCharacter { get; set; }
public List<Song> ListOfSongs { get; set; } = new List<Song>();
public SongSet(string firstChar)
{
this.FirstCharacter = firstChar;
}
}
public class Song
{
public string SongTitle { get; set; }
public Song(string title)
{
this.SongTitle = title;
}
}
After received list from server I put it to list of SongSet objects.
public static List<SongSet> ListOfSongsFromServer { get; set; } = new List<SongSet>();
And here my code to display a whole list.
ListView listView = new ListView
{
IsGroupingEnabled = true,
GroupDisplayBinding = new Binding("FirstCharacter"),
ItemsSource = PlayerPageVM.ListOfSongsFromServer,
ItemTemplate = new DataTemplate(() =>
{
Label titleLabel = new Label();
titleLabel.SetBinding(Label.TextProperty, "SongTitle");
return new ViewCell
{
View = new StackLayout
{
Padding = new Thickness(0, 5),
Orientation = StackOrientation.Horizontal,
Children =
{
new StackLayout
{
VerticalOptions=LayoutOptions.Center,
Spacing=0,
Children =
{
titleLabel
}
}
}
}
};
})
};
this.Content = new StackLayout
{
Children =
{
listView
}
};
After build application I've received only list with first letters but without song titles uder them. Can anyone help me how to achievie this?
You want to display grouped list of songs, you could use CollectionView. Because it provides the easy way to show Group data.
Display grouped data:
<CollectionView ItemsSource="{Binding Animals}"
IsGrouped="true">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
...
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
And the problem of mode data could refer to document to make SongSet inherit from Lsit<Song> to generate a grouped data.
public class SongSet : List<Song>
{
public string FirstCharacter { get; set; }
public SongSet(string firstChar,List<Song> ListOfSongs): base(ListOfSongs)
{
this.FirstCharacter = firstChar;
}
}
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.
I am just converting and app from C#/WPF to C#/UWP (Windows 10) and it seems that IList does not update the UWP ListView.
So first question - should it ?
and if not then how should I bind the IList property to ensure the ListView will update if items are added or removed?
Maybe some more context - new items are getting created on a background thread - and the exact same method/code seems to work fine with C#/WPF.
<ListView x:Name="siteListView" SelectionChanged="SiteListView_SelectionChanged"
DataContext="{x:Bind _this}"
ItemsSource="{x:Bind Customer.items, Mode=OneWay}" >
public class Customer : RealmObject
{
[PrimaryKey]
public string id { get; set; } = Guid.NewGuid().ToString();
public string name { get; set; }
public string address { get; set; }
public string contact { get; set; }
public float complianceScore { get; set; }
public string completionStatus { get; set; }
public IList<Site> sites { get; }
}
So it seems Realm IList does not implement the IList interface because of some conflicts with Android and as a result UWP bindings won't work. BUT I found a relatively simple solution. https://github.com/realm/realm-dotnet/issues/1575
public partial class SimpleList : ContentPage
{
ObservableCollection<Item> observableItems = new ObservableCollection<Item>();
IRealmCollection<Item> rawItems;
public class SimpleList()
{
InitializeComponent();
rawItems = realm.All<Item>().AsRealmCollection();
foreach (Item item in rawItems)
{
observableItems.Add(item);
}
rawItems.SubscribeForNotifications((sender, changes, error) =>
{
if (changes != null)
{
foreach (int i in changes.DeletedIndices)
{
observableItems.RemoveAt(i);
}
foreach (int i in changes.InsertedIndices)
{
observableItems.Insert(i, rawItems[i]);
}
foreach (int i in changes.ModifiedIndices)
{
observableItems.RemoveAt(i);
observableItems.Insert(i, rawItems[i]);
}
}
if (error != null)
{
// TODO: Handle Exceptions
}
});
MyListView.ItemsSource = observableItems;
}
async void Handle_ItemSelected(object sender, ItemTappedEventArgs e)
{
if (e.Item == null)
{
return;
}
// DO Stuff
// Deselect Item
((ListView)sender).SelectedItem = null;
}
}
I've created my own model property, which says "ErrorMessage could not be found":
[DataType(DataType.EmailAddress)]
[Required]
[Display(Name = "Email")]
[StringLength(80, ErrorMessage = "Email too large.")]
[RegularExpressionTimeOut(#"^([a-zA-Z0-9._-]+)#(outlook|hotmail|yahoo)\.\w{2,}$", 5),
ErrorMessage = "Invalid email."] // 5 seconds
public string Email { get; set; }
And the class of this property:
public class RegularExpressionTimeOut :
System.ComponentModel.DataAnnotations.ValidationAttribute
{
public System.TimeSpan TimeOut { get; set; }
public string Pattern { get; set; }
//public string ErrorMessage { get; set; }
//new System.TimeSpan(0, 0, 5)
/// <summary>
/// Constructor
/// </summary>
/// <param name="pattern">Regular expression as string</param>
/// <param name="timeOut">Number of seconds</param>
public RegularExpressionTimeOut(string pattern, int timeOut)
{
this.Pattern = pattern;
this.TimeOut = new System.TimeSpan(0, 0, timeOut);
}
public override bool IsValid(object value)
{
//if (!string.IsNullOrEmpty(this.ErrorMessage) && ...)
return (new System.Text.RegularExpressions.Regex(this.Pattern,
System.Text.RegularExpressions.RegexOptions.None, this.TimeOut))
.IsMatch(this.Pattern);
}
}
Then, the question is, how can I show the ErrorMessage?
Updated with the FormatErrorMessage
My web don't show the error message anyway.
public class RegularExpressionTimeOut : System.ComponentModel.DataAnnotations.ValidationAttribute
{
public System.TimeSpan TimeOut { get; set; }
public string Pattern { get; set; }
private const string DefaultErrorMessage = "Invalid email.";
//private string ErrorMessage;
/// <summary>
/// Constructor
/// </summary>
/// <param name="pattern">Regular expression as string</param>
/// <param name="timeOut">Number of seconds</param>
public RegularExpressionTimeOut(string pattern, int timeOut) : base(DefaultErrorMessage)
{
this.Pattern = pattern;
this.TimeOut = new System.TimeSpan(0, 0, timeOut);
//this.ErrorMessage = errorMessage;
}
public override bool IsValid(object value)
{
//if (!string.IsNullOrEmpty(this.ErrorMessage) && ...)
return (new System.Text.RegularExpressions.Regex(this.Pattern, System.Text.RegularExpressions.RegexOptions.None, this.TimeOut)).IsMatch(this.Pattern);
}
public override string FormatErrorMessage(string ErrorMessage)
{
return string.Format(ErrorMessageString, ErrorMessage);
}
}
You RegularExpressionTimeOut constructor does not have a parameter for ErrorMessage. The typical usage is
public class RegularExpressionTimeOut : ValidationAttribute
{
private const string DefaultErrorMessage = "Invalid email";
public RegularExpressionTimeOut(string pattern, int timeOut) : base(DefaultErrorMessage)
and usually an override is added to format a specific message provide in the Attribute where you access values such as the property name (although in your case you don't seem to be needing or wanting that.
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, .....);
}
Side note: your closing parenthesis is not located correctly. It should be
[RegularExpressionTimeOut(#"....", 5, ErrorMessage = "Invalid email.")]
Edit
Your not actually testing the value of the Email property which is posted back (you just testing that the regex pattern matches itself. You need to change the IsValid() method to
public override bool IsValid(object value)
{
string email = (string)value; // cast it to a string for use in the Regex method
return (new Regex(this.Pattern, RegexOptions.None, this.TimeOut)).IsMatch(email);
}
Note also that the regex pattern you have shown should take a few millisecond to test so its not clear why you would need this. In addition, you do not have any associated client side validation (your attribute does not implement ICientValitable and in any case there would be no point since javascript regex does not have a timeout) so the message would only be returned if you return the view when ModelState.IsValid == false. In that case, your message "Invalid email" would be rather confusing to the user and it should probably indicate that it needs to be #outlook or #hotmail etc.