I'm trying to remove an item from a ListBox. The command is properly fired and the item is correctly removed from the database but the list is not refreshed.
Here is the ViewModel. I'm using MVVM Light 4.1
public class ViewAllViewModel : ViewModelBase
{
public ViewAllViewModel()
{
NavigateToAddNew = new RelayCommand(() => NavigationController<Views>.Current.NavigateTo(Views.AddNew));
Remove = new RelayCommand<int>(DeleteMeasure);
using (var repository = App.ServiceLocator.Get<IRepository>())
{
Measures = new ObservableCollection<Measure>(repository.Measures);
}
}
private void DeleteMeasure(int measureId)
{
Measure measure;
using (IRepository repository = App.ServiceLocator.Get<IRepository>())
{
measure = repository.Measures.Single(m => m.Id == measureId);
repository.Measures.Delete(measure);
repository.SaveChanges();
}
measure = Measures.Single(m => m.Id == measureId);
if (Measures.Remove(measure))
{
RaisePropertyChanged(() => Measures);
}
}
public RelayCommand NavigateToAddNew { get; set; }
public RelayCommand<int> Remove { get; set; }
private ObservableCollection<Measure> _measures;
public ObservableCollection<Measure> Measures
{
get { return _measures; }
set { Set(() => Measures, ref _measures, value); }
}
}
Thanks for the help.
PS: I know there are similar questions but none of the accepted answers worked for me :(
EDIT 1
This is the code I use in the XAML page to bind the ListBox to the list of items:
<ListBox Grid.Row="1" DataContext="{Binding Path=Measures}" ItemsSource="{Binding}" />
here is the binding of the ViewModel to the main container
<Grid DataContext="{Binding Source={StaticResource Locator}, Path=ViewAll}" x:Name="LayoutRoot" />
EDIT 2
This is the full code of the ViewModel
public class ViewAllViewModel : ViewModelBase
{
public ViewAllViewModel()
{
NavigateToAddNew = new RelayCommand(() => NavigationController<Views>.Current.NavigateTo(Views.AddNew));
Remove = new RelayCommand<int>(DeleteMeasure);
LoadMeasures();
Messenger.Default.Register<PropertyChangedMessage<ObservableCollection<Measure>>>(this, message => LoadMeasures());
}
private void LoadMeasures()
{
using (var repository = App.ServiceLocator.Get<IRepository>())
{
Measures = new ObservableCollection<Measure>(repository.Measures.OrderByDescending(m => m.MeasureDate).ThenByDescending(m => m.Id).Take(20));
}
}
private void DeleteMeasure(int measureId)
{
Measure measure;
using (IRepository repository = App.ServiceLocator.Get<IRepository>())
{
measure = repository.Measures.Single(m => m.Id == measureId);
repository.Measures.Delete(measure);
repository.SaveChanges();
}
measure = Measures.Single(m => m.Id == measureId);
Measures.Remove(measure);
RaisePropertyChanged("LastMeasure", null, measure, true);
}
public RelayCommand NavigateToAddNew { get; set; }
public RelayCommand<int> Remove { get; set; }
private ObservableCollection<Measure> _measures;
public ObservableCollection<Measure> Measures
{
get { return _measures; }
set { Set(() => Measures, ref _measures, value); }
}
}
I don't see anything obviously wrong. All I can suggest is to try simplifying your ListBox to just this:
<ListBox Grid.Row="1" ItemsSource="{Binding Path=Measures}" />
And remove the code that calls RaisePropertyChanged(() => Measures); (since it should not be needed).
If neither of those work, I would test to see what happens if you completely reset your Measures property, as in:
private void DeleteMeasure(int measureId)
{
using (IRepository repository = App.ServiceLocator.Get<IRepository>())
{
var measure = repository.Measures.Single(m => m.Id == measureId);
repository.Measures.Delete(measure);
repository.SaveChanges();
}
Measures = repository.Measures;
}
If that causes a successful refresh of the ListBox, it would imply that something is going on with the ObservableCollection.
Related
I am having an issue with saving of my picker value. I am rewriting old code and i am struggling with saving a picker value. Once I select the picker i have the value but it doesn't go through the getter. I have similar method on other picker it works there perfectly co I dont really understand what is wrong. The only thing is that if you notice the older piece there is the Model property.Position when filling the value. Please see my notes in the code. I have Inotify in the model as advised and in the vm
public class SettingsViewModel : ObservableObject
{
private List<CustomTextSize> _textSize = new List<CustomTextSize>();
private CustomTextSize _selectedTextSize;
public CustomTextSize SelectedTextSize
{
get => _selectedTextSize; // doesnt go through here
set
{
if (_selectedTextSize != value && _selectedTextSize != null)
{
this.IsBusy = true;
SetProperty(ref _selectedTextSize, value);
NotificationService.ShowToast("Probíhá ukládání, prosím čekejte");
UserSettings newSetting = new UserSettings()
{
DateSaved = DateTime.Now,
OptionId = (int)SettingOption.TextFontSize,
Value = value.Position.ToString()
};
new Thread(new ThreadStart(async () =>
{
await LangUpDataSaverLoader.SaveUserSettings(newSetting);
LangUpDataSaverLoader.LoadUserSettingsAndFillAppValues();
})).Start();
this.IsBusy = false;
}
}
}
public List<CustomTextSize> TextSize
{
get => _textSize;
set => SetProperty(ref _textSize, value);
}
_textSize = FillTextSizeOptions();
var customSize = _textSize.FirstOrDefault(m => m.Value == LangUpUserCustomSettings.CustomFontSize); // here should be position? But then I am getting that I cannot conevrt int to Model.
if (customSize != null)
{
_selectedTextSize = customSize;
}
}
Model
public class CustomTextSize : ObservableObject {
public int Position { get; set; }
public int Value { get; set; }
public string Text { get; set; }
}
Xaml
<Picker x:Name="textSize" WidthRequest="300"
VerticalOptions="CenterAndExpand" FontSize="20"
Title="{ grial:Translate A_PickerSizeOfText }"
ItemsSource ="{Binding TextSize}" ItemDisplayBinding="{Binding Text}"
SelectedItem ="{Binding SelectedTextSize, Mode=TwoWay}"
grial:PickerProperties.BorderStyle="Default"
BackgroundColor="Transparent"
TextColor= "{ DynamicResource ListViewItemTextColor }" >
</Picker>
Old piece of code that I am rewrting
Spinner textSizeSpinner = new Spinner(MainActivity.Instance);
ArrayAdapter textSizeAdapter = new ArrayAdapter(MainActivity.Instance, Android.Resource.Layout.SimpleSpinnerDropDownItem);
textSizeAdapter.AddAll(FillTextSizeOptions().Select(m => m.Text).ToList());
textSizeSpinner.Adapter = textSizeAdapter;
textSizeSpinner.SetBackgroundColor(Color.FromHex("#3680b2").ToAndroid());
fontSizeStepper.Children.Add(textSizeSpinner);
initialTextSizeSpinnerPosition = FillTextSizeOptions().FirstOrDefault(m => m.Value == LangUpUserCustomSettings.CustomFontSize).Position; //here is the .Position
textSizeSpinner.SetSelection(initialTextSizeSpinnerPosition);
protected async void TextSizeSelected(object sender, AdapterView.ItemSelectedEventArgs e)
{
if (e.Position != initialTextSizeSpinnerPosition)
{
dialog.SetProgressStyle(ProgressDialogStyle.Spinner);
dialog.SetCancelable(false);
dialog.SetMessage("Probíhá ukládání, prosím čekejte");
dialog.Show();
Spinner spinner = (Spinner)sender;
Model.UserSettings newSetting = new Model.UserSettings()
{
DateSaved = DateTime.Now,
OptionId = (int)SettingOption.TextFontSize,
Value = FillTextSizeOptions().First(m => m.Position == e.Position).Value.ToString(),
};
await LangUpDataSaverLoader.SaveUserSettings(newSetting);
initialTextSizeSpinnerPosition = e.Position;
LangUpDataSaverLoader.LoadUserSettingsAndFillAppValues();
dialog.Dismiss();
}
}
I am creating a chat application in xamarin.forms.What I am trying to achieve is whenever user typed message contains a URL, that should be highlighted and provide click to it.For this feature I found Span in Label text.When user click on send button of chat , I will check for URL and make it as another span.I got this idea from Lucas Zhang - MSFT form this question here.
The problem is I am trying to do the spanning in view model and the individual chat bubble is in another view cell which will call as ItemTemplate in my chat listview. Anyway the spanning is not working as I intended ie; it doesn't highlight .
My view Model.
public Queue<Message> DelayedMessages { get; set; } = new Queue<Message>();
public ObservableCollection<Message> Messages { get; set; } = new ObservableCollection<Message>();
public string TextToSend { get; set; }
public ChatPageViewModel()
{
OnSendCommand = new Command(() =>
{
if (!string.IsNullOrEmpty(TextToSend))
{
var urlStr = TextToSend;
int startIndex = 0, endIndex = 0;
if (urlStr.Contains("www."))
{
startIndex = urlStr.IndexOf("www.");
}
if (urlStr.Contains(".com"))
{
endIndex = urlStr.IndexOf(".com") + 3;
}
if (startIndex != 0 || endIndex != 0)
{
var formattedString = new FormattedString();
Span span1 = new Span() { Text = urlStr.Substring(0, startIndex), TextColor = Color.Black };
formattedString.Spans.Add(span1);
Span span2 = new Span() { Text = urlStr.Substring(startIndex, endIndex - startIndex + 1), TextColor = Color.LightBlue };
span2.GestureRecognizers.Add(new TapGestureRecognizer()
{
NumberOfTapsRequired = 1,
Command = new Command(() => {
})
});
formattedString.Spans.Add(span2);
Span span3 = new Span() { Text = urlStr.Substring(endIndex, urlStr.Length - 1 - endIndex), TextColor = Color.Black };
formattedString.Spans.Add(span3);
var message = new Message
{
Text = formattedString.ToString(),
IsIncoming = false,
MessageDateTime = DateTime.Now
};
Messages.Add(message);
TextToSend = string.Empty;
}
else
{
var message = new Message
{
Text = urlStr.ToString(),
IsIncoming = false,
MessageDateTime = DateTime.Now
};
Messages.Add(message);
TextToSend = string.Empty;
}
}
});
}
Single chat Bubble XAML
<Label x:Name="OutgoingMessage" TextColor="White" FormattedText="{Binding Text}" HorizontalOptions="End" >
</Label>
My Chat page XAML
<Grid RowSpacing="0" Margin="0,20,0,0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="1" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListView Grid.Row="0"
ItemTemplate="{StaticResource MessageTemplateSelector}"
ItemsSource="{Binding Messages,Mode=OneWay}"
Margin="0"
SelectionMode="None"
FlowDirection="RightToLeft"
HasUnevenRows="True" x:Name="ChatList"
VerticalOptions="FillAndExpand"
SeparatorColor="Transparent"
>
</ListView>
<BoxView HorizontalOptions="FillAndExpand"
HeightRequest="1"
BackgroundColor="#F2F3F5"
Grid.Row="1"/>
<partials:ChatInputBarView Grid.Row="2"
Margin="0,0,0,0"
x:Name="chatInput"/>
</Grid>
ChatPage.xaml.cs
public partial class ChatPage : ContentPage
{
ChatPageViewModel vm;
public ChatPage()
{
InitializeComponent();
this.BindingContext = vm= new ChatPageViewModel();
}
}
Messages class
public class Message : ObservableObject
{
string text;
public string Text
{
get { return text; }
set { SetProperty(ref text, value); }
}
DateTime messageDateTime;
public DateTime MessageDateTime
{
get { return messageDateTime; }
set { SetProperty(ref messageDateTime, value); }
}
public string MessageTimeDisplay => MessageDateTime.Humanize();
bool isIncoming;
public bool IsIncoming
{
get { return isIncoming; }
set { SetProperty(ref isIncoming, value); }
}
}
Any Help is appreciated.
EDIT:
This question was actually continuation of question. Previously I used AwesomeHyperLinkLabel fromlink. The problem was I cant manage the click event of that label.Thats why I moved with label span.Thanks to Leo Zhu - MSFT For the render changes.
For Android:
[assembly: ExportRenderer(typeof(AwesomeHyperLinkLabel), typeof(AwesomeHyperLinkLabelRenderer))]
namespace App18.Droid
{
public class AwesomeHyperLinkLabelRenderer : LabelRenderer
{
public AwesomeHyperLinkLabelRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
var view = (AwesomeHyperLinkLabel)Element;
if (view == null) return;
TextView textView = new TextView(Forms.Context);
textView.LayoutParameters = new LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent);
textView.SetTextColor(view.TextColor.ToAndroid());
// Setting the auto link mask to capture all types of link-able data
textView.AutoLinkMask = MatchOptions.All;
// Make sure to set text after setting the mask
textView.Text = view.Text;
AddHyperlinksManually(textView);
//textView.SetTextSize(ComplexUnitType.Dip, (float)view.FontSize);
// overriding Xamarin Forms Label and replace with our native control
SetNativeControl(textView);
}
public static void AddHyperlinksManually(TextView _tv)
{
SpannableStringBuilder currentSpan = new SpannableStringBuilder(_tv.Text);
Linkify.AddLinks(currentSpan, MatchOptions.WebUrls);
var objects = currentSpan.GetSpans(0, currentSpan.Length(), Java.Lang.Class.FromType(typeof(URLSpan)));
var urlSpans = new URLSpan[objects.Length];
for (var i = 0; i < urlSpans.Length; i++)
{
urlSpans[i] = objects[i] as URLSpan;
}
foreach (URLSpan _url in urlSpans)
{
int iStart = currentSpan.GetSpanStart(_url);
int iEnd = currentSpan.GetSpanEnd(_url);
currentSpan.RemoveSpan(_url);
currentSpan.SetSpan(new CustomURLSpan(_url.URL), iStart, iEnd, SpanTypes.InclusiveInclusive);
_tv.SetText(currentSpan, TextView.BufferType.Normal);
_tv.MovementMethod = LinkMovementMethod.Instance;
}
}
public class CustomURLSpan : ClickableSpan
{
string mTargetURL;
public CustomURLSpan(string _url) {
mTargetURL =_url;
}
public override void OnClick(Android.Views.View widget)
{
//here you could handle the click event,and you could use MessagingCenter to send mTargetURL to your Page.
Console.WriteLine("Click");
}
}
}
The mistake was with my model.Changed string to FormattedString and also changed in the viewmodel
public class Message : ObservableObject
{
FormattedString text;
public FormattedString Text
{
get { return text; }
set { SetProperty(ref text, value); }
}
DateTime messageDateTime;
public DateTime MessageDateTime
{
get { return messageDateTime; }
set { SetProperty(ref messageDateTime, value); }
}
public string MessageTimeDisplay => MessageDateTime.Humanize();
bool isIncoming;
public bool IsIncoming
{
get { return isIncoming; }
set { SetProperty(ref isIncoming, value); }
}
}
I have a TableView in iOS and, in my ViewModel, I have a property to Selected Item in TableView, but I don't know how to bind the Selected Item for this property. How can I do that? My project is cross-platform. I have an Android project and an iOS project. In Android project, I did the bind:
<Mvx.MvxListView
android:id="#+id/lstViewTasks"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:focusableInTouchMode="true"
android:choiceMode="multipleChoice"
local:MvxBind="ItemsSource Tasks; SelectedItem SelectedTask; ItemClick ShowTaskCommand"
local:MvxItemTemplate="#layout/projectmytasksitem" />
but I can't do a equivalent bind in iOS.
That's my TableViewController:
[Register("ProjectMyTasksViewc")]
public class ProjectMyTasksViews : MvxTableViewController<ProjectMyTasksViewModel>
{
//other things
var source = new MvxSimpleTableViewSource(TableView, ProjectMyTasksItem.Key, ProjectMyTasksItem.Key);
TableView.Source = source;
this.CreateBinding(source).To<ProjectMyTasksViewModel>(viewModel => viewModel.Tasks).Apply();
this.CreateBinding(source).For(s => s.SelectedItem).To<ProjectMyTasksViewModel>(viewModel => viewModel.SelectedTask).Apply();
this.CreateBinding(source).For(tableSource => tableSource.SelectionChangedCommand).To<ProjectMyTasksViewModel>(viewModel => viewModel.ShowTaskCommand).Apply();
}
Here is my ViewModel:
public class ProjectMyTasksViewModel : MvxViewModel
{
public Action ShowTaskCommandAction { get; set; }
private IList<Task> _tasks;
public IList<Task> Tasks
{
get { return _tasks; }
set { _tasks = value; RaisePropertyChanged(() => Tasks); }
}
private Task _selectedTask;
public Task SelectedTask
{
get { return _selectedTask; }
set { _selectedTask = value; RaisePropertyChanged(() => SelectedTask); }
}
private MvxCommand _showTaskCommand;
public MvxCommand ShowTaskCommand
{
get
{
_showTaskCommand = _showTaskCommand ?? (_showTaskCommand = new MvxCommand(ExecuteShowTaskCommand));
return _showTaskCommand;
}
}
private void ExecuteShowTaskCommand()
{
if (!SelectedTask.IsCompleted)
{
ShowTaskCommandAction?.Invoke();
}
}
}
I believe it has to do with the timing of your ShowTaskCommand getting executed vs the set of SelectedTask. So if you commented out the code inside ExecuteShowTaskCommand and place a breakpoint inside ExecuteShowTaskCommand as well as the set of SelectedTask you would find that the ExecuteShowTaskCommand is running first and then the set of the SelectedTask.
Alternative implementation
To avoid the timing issue you can instead pass the selected task into your command as a parameter.
MvxCommand<Task> _showTaskCommand;
public MvxCommand<Task> ShowTaskCommand =>
_showTaskCommand ?? (_showTaskCommand = new MvxCommand<Task>(ExecuteShowTaskCommand));
private void ExecuteShowTaskCommand(Task selectedTask)
{
if (!selectedTask.IsCompleted)
{
ShowTaskCommandAction?.Invoke();
}
}
Please Help to Reproduce .. to Searchable Long List Selector.
How to Create Searchable Text Box For Long List Selector in Windows Phone 8 Platform .
any help is appreciated
thanks in advance.
//View
So create TextBox and a button =>
<StackPanel Orientation="Horizontal">
<TextBox Width="200" Text="{Binding Text,Mode=TwoWay}"></TextBox>
<Button Command="{Binding Search}"></Button>
</StackPanel>
//ViewModel
private List<AlphaKeyGroup<CountryInfo>> _dataSource;
public List<AlphaKeyGroup<CountryInfo>> DataSource
{
get { return _dataSource; }
set
{
_dataSource= value;
RaisePropertyChanged(() => DataSource);
}
}
private string _searchText;
public string SearchText
{
get { return _searchText; }
set {
_searchText= value;
RaisePropertyChanged(() => SearchText);
}
}
public RelayCommand Search {get;set;}
//Constructor
Search = new RelayCommand(setSearchList);
//Methode
private void setSearchList(){
if(string.isNullOrEmpty(SearchText)){
DataSource = AlphaKeyGroup<CountryInfo>.CreateGroups(CountryInfoList, System.Threading.Thread.CurrentThread.CurrentUICulture, (CountryInfo s) => { return s.Name; }, true);
}else{
var listCopy= CountryInfoList.where(item=> item.Name == SearchText).ToList();
DataSource = AlphaKeyGroup<CountryInfo>.CreateGroups(listCopy, System.Threading.Thread.CurrentThread.CurrentUICulture, (CountryInfo s) => { return s.Name; }, true); }
}
Your itemSource is the DataSource property.
I'm currently having an issue with WP7's TimePicker, specifically with binding it to a ViewModel. The TimePicker in question sets the time of day for an alarm. When the page is first loaded, the TimePicker correctly displays the value of the Alarm object (the default value of 12:00am in this case). However, when the user chooses a new value this is not reflected in the model - it's overridden with the previous value of 12:00am.
I'm using MVVM to create this form and hold the data bindings. Is there anything in particular that I'm doing wrong?
(View) AlarmEditorControl.xaml
<TextBlock Height="30" HorizontalAlignment="Left" Margin="1,6,0,0" Name="lblAlarmTime" Text="Alarm Time:" VerticalAlignment="Top" Grid.Column="2" FontSize="26" />
<!-- Data binding isn't working for updates! -->
<toolkit:TimePicker HorizontalAlignment="Left" Margin="140,34,0,0" Name="tpAlarmTime" VerticalAlignment="Top" Width="161" Grid.Column="1" Grid.ColumnSpan="2" Value="{Binding Path=Time, Mode=TwoWay}" />
(ViewModel) AlarmEditorModel.cs
[DataContractAttribute]
public class AlarmEditorModel
{
private int _index;
[DataMemberAttribute]
public Alarm Alarm { get; set; }
[DataMemberAttribute]
public int Index
{
get
{
return _index;
}
set
{
_index = value;
}
}
public AlarmEditorModel(int index)
{
_index = index;
Alarm = new Alarm();
// Get the list of alarms
AlarmSerializer serializer = new AlarmSerializer();
// Check the index is in range
List<Alarm> alarms = serializer.AlarmList;
if (_index > -1 && index < alarms.Count)
{
Alarm = alarms[_index];
}
}
public void Commit()
{
// Get the current list of alarms
AlarmSerializer serializer = new AlarmSerializer();
List<Alarm> alarms = serializer.AlarmList;
// Replace our new value
alarms[_index] = Alarm;
serializer.AlarmList = alarms;
}
}
(Model) Alarm.cs
[DataContract]
public class Alarm : INotifyPropertyChanged
{
private bool _active;
private DateTime _time;
[DataMember]
public string Name { get; set; }
[DataMember]
public DateTime Time
{
get
{
return _time;
}
set
{
if (_time != value)
{
_time = value;
RaisePropertyChanged("Time");
}
}
}
[DataMember]
public AlarmFrequency Frequency { get; set; }
[DataMember]
public AlarmTone Tone { get; set; }
[DataMember]
public bool Active {
get {
return _active;
}
set {
_active = value;
}
}
public string AlarmTimeString {
get {
return Time.ToShortTimeString();
}
}
/**
* Default Constructor
*/
public Alarm()
{
Debug.WriteLine("Alarm: Using default constructor");
this.Name = "New Alarm";
this.Time = DateTime.Today;
this.Frequency = new AlarmFrequency();
this.Tone = new AlarmTone();
this.Active = true;
Debug.WriteLine("Alarm hours is " + this.Time.Hour);
}
/**
* Parameterised constructor
*/
public Alarm(string Name, DateTime Time, AlarmFrequency Frequency,
AlarmTone Tone, bool Active)
{
Debug.WriteLine("Alarm: Using parameterised constructor");
this.Name = Name;
this.Time = Time;
this.Frequency = Frequency;
this.Tone = Tone;
this.Active = Active;
}
}
(Calling Page) NewAlarm.xaml.cs
private List<Channel> feeds;
private AlarmEditorModel _aem;
private int _index;
public NewAlarm()
{
InitializeComponent();
feeds = new List<Channel>();
feeds.Add(new Channel(null, null, "Feed 1", DateTime.Now));
feeds.Add(new Channel(null, null, "Feed 2", DateTime.Now));
}
/**
* Setup functions when the page is loaded
*/
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
// Function vars + debug
Debug.WriteLine("Navigating to");
// Check if we're recovering from tombstone
if (!StateUtilities.IsLaunching && this.State.ContainsKey("AlarmState"))
{
// Recover the saved model
_aem = (AlarmEditorModel)this.State["AlarmState"];
}
else
{
try
{
// Editing an alarm.
_index = Convert.ToInt32(this.NavigationContext.QueryString["index"]);
Debug.WriteLine("Editing an alarm");
}
catch (KeyNotFoundException knfe)
{
Debug.WriteLine(knfe.Message);
// No index provided, new alarm
_index = -1;
}
// Set the model from the index
_aem = new AlarmEditorModel(_index);
}
AlarmEditor.DataContext = _aem.Alarm;
Debug.WriteLine(_aem.Alarm.Time.Hour);
}
/**
* Preserve alarm details when tombstoning
*/
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
if (this.State.ContainsKey("AlarmState"))
{
this.State["AlarmState"] = _aem;
}
else
{
this.State.Add("AlarmState", _aem);
}
StateUtilities.IsLaunching = false;
}
EDIT 1
It would appear the setter for Alarm.Time is being called twice. By adding the following debug lines to the Time property:
[DataMember]
public DateTime Time
{
get
{
return _time;
}
set
{
Debug.WriteLine("Current time is " + _time.ToShortTimeString());
Debug.WriteLine("New time is " + value.ToShortTimeString());
if (_time != value)
{
Debug.WriteLine("Changing time value");
_time = value;
RaisePropertyChanged("Time");
}
}
}
The following output is produced in the log when setting the time to 9:10am:
Current time is 4:00 AM
New time is 9:10 AM
Changing time value
Current time is 12:00 AM
New time is 4:00 AM
Changing time value
Problem solved I think. I needed to make an additional check in OnNavigatedTo when recovering from a Tombstone in order to get the value of the TimePicker before it was overwritten by the ViewModel:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
// Check if we're recovering from tombstone
if (!StateUtilities.IsLaunching && this.State.ContainsKey("AlarmState"))
{
// Recover the saved model
_aem = (AlarmEditorModel)this.State["AlarmState"];
// Use the value from the TimePicker
_aem.Alarm.Time = (DateTime)AlarmEditor.tpAlarmTime.Value;
}
else
...
Need to do a few more tests on this solution, but it seems to be doing the job so far.