I'm trying to get this code to add something to the screen once a particular time of day is reached. It's rigged to an event. The code works on a single-threaded program, but not with threading, which is what I need. The data is added, as needed, but doesn't show to the screen like it did on the single-thread execution (timeStack is a StackPanel, TimeEntry is a UserControl).
Code:
Mainwindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
SM = new SessionManager();
SM.NewDayEvent += SplitSession;
InitializeComponent();
//Code removed for clarity
}
private void SplitSession(object sender, EventArgs ea)
{
SM.SplitSession();
string s =((TimeEntry)SM.Entries.Last(x=>x.GetType()==typeof(TimeEntry))).Data.Comment;
AddSessionStamp();
entryAdder_Click(null, null);
((TimeEntry)SM.Entries.Last(x => x.GetType() == typeof(TimeEntry))).Data.Comment = s;
this.Focus();
}
private void AddSessionStamp()
{
TextBlock timeStamp = new TextBlock();
timeStamp.Text = "-----------" + SM.CurrentSession.Name + "-----------";
timeStack.Children.Add(timeStamp);
}
private void entryAdder_Click(object sender, RoutedEventArgs e)
{
//Subscribe to the assorted events
TimeEntry newTE = SM.addNewTimeEntry();
//Subscribe to the assorted events
RegisterToTimeEntry(newTE);
timeStack.Children.Add(newTE);
}
}
SessionManager.cs:
public class SessionManager : INotifyPropertyChanged
{
public delegate void NewDayEventHandler(object sender, EventArgs ea);
public event NewDayEventHandler NewDayEvent;
private Timer _timer;
private Stopwatch _clockWatch;
private DateTime current_time;
#region Properties
public DateTime CurrentTime
{
get
{
return DateTime.Now;
}
set
{
if (current_time != value)
{
current_time = value;
OnPropertyChanged("CurrentTime");
}
}
}
public List<Session> OpenSessions { get; private set; }
public ObservableCollection<UIElement> Entries { get; private set; }
public Session CurrentSession
{
get
{
return current_session;
}
set
{
if (current_session != value)
{
current_session = value;
OnPropertyChanged("CurrentSession");
}
}
}
#endregion
public SessionManager()
{
_clockWatch = new Stopwatch();
_timer = new Timer(1000);//one second
_timer.Elapsed += timerElapsed;
//Code removed for clarity
current_time = new DateTime();
CurrentTime = DateTime.Now;
_timer.Start();
}
#region Methods
#region Event Methods
/// <summary>
/// Registered to Timer.Elapsed Event
/// (See constructor)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void timerElapsed(object sender, ElapsedEventArgs e)
{
CurrentTime = DateTime.Now;
if ((CurrentTime.TimeOfDay.Hours == 13 &&
CurrentTime.TimeOfDay.Minutes == 23 &&
CurrentTime.TimeOfDay.Seconds == 0) &&
NewDayEvent != null)
{
NewDayEvent(this, new EventArgs());
}
}
#endregion
#region Class Methods
private void OnPropertyChanged([CallerMemberName] string member_name = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(member_name));
}
}
#endregion
#endregion
public void SplitSession()
{
Session prevSesh = CurrentSession;
OpenSessions.Add(new Session());
CurrentSession = OpenSessions.Last();
current_session.addComment(
((TimeEntry)Entries.Last(
x => x.GetType() == typeof(TimeEntry))
).Data.Comment);
}
}
Event handler functions are executed in the same Thread as the one who raised the event. Problem is you can't update the UI from another thread that the Dispatcher thread. You'll need to execute the callback function (or at least the update part) inside a Dispatcher Invoke or BeginInvoke:
Application.Current.Dispatcher.Invoke(new Action(() => {
//your UI update
}));
Related
Here is code I have been working on with the help of Deczalof:
Here is the XAML code that I have
<t:PopupEntryFrame2 x:Name="newDeckNameEntry" TextChanged="newDeckNameEntry_TextChanged" />
With code behind:
public partial class CopyDeckPopup : Rg.Plugins.Popup.Pages.PopupPage
{
string originalName;
string originalDescription;
List<Deck> listDecks;
public CopyDeckPopup(string clickedDeckName, string clickedDeckDescription)
{
InitializeComponent();
listDecks = App.DB.GetAllDecks();
newDeckNameEntry.Text = clickedDeckName;
newDeckDescriptionEntry.Text = clickedDeckDescription;
originalName = clickedDeckName;
originalDescription = clickedDeckDescription;
OK_Button.IsEnabled = false;
}
private async void Cancel_Button_Clicked(object sender, EventArgs e)
{
await PopupNavigation.Instance.PopAsync(false);
}
private async void OK_Button_Clicked(object sender, EventArgs e)
{
if (IsBusy)
return;
IsBusy = true;
await PopupNavigation.Instance.PopAsync(false);
var newDeckNameEntryTextTrim = newDeckNameEntry.Text.Trim();
var newDeckDescriptionEntryTextTrim = newDeckDescriptionEntry.Text.Trim();
if (newDeckNameEntryTextTrim != originalName || newDeckDescriptionEntryTextTrim != originalDescription)
{
App.DB.CopyDeckToDb2(originalName, newDeckNameEntryTextTrim, newDeckDescriptionEntryTextTrim);
MessagingCenter.Send<PopupPage>(new PopupPage(), "PageRefresh");
}
IsBusy = false;
}
void newDeckNameEntry_TextChanged(object sender, EntryTextChangedEventArgs e)
{
NewDeckNameEntryValidator(e.NewTextValue);
}
void newDeckDescriptionEntry_TextChanged(object sender, EntryTextChangedEventArgs e)
{
var deckName = newDeckNameEntry.Text.Trim();
var isDeckAvailable = listDecks.Where(x => x.Name == deckName).SingleOrDefault();
if (isDeckAvailable == null)
{
OK_Button.IsEnabled = e.NewTextValue != originalDescription ? true : false;
}
}
void NewDeckNameEntryValidator(string newDeckNameEntry)
{
var newDeckNameEntryTrimmed = newDeckNameEntry.Trim();
var isDeckNameAvailable = listDecks.Where(x => x.Name == newDeckNameEntryTrimmed).SingleOrDefault();
if (string.IsNullOrWhiteSpace(newDeckNameEntryTrimmed) ||
isDeckNameAvailable != null ||
newDeckNameEntryTrimmed.StartsWith("::") ||
newDeckNameEntryTrimmed.EndsWith("::") ||
newDeckNameEntryTrimmed.Count(c => c == ':') > 2)
{
OK_Button.IsEnabled = false;
return;
}
OK_Button.IsEnabled = true;
}
}
and the C# code for a template:
public class PopupEntryFrame2 : CustomFrame
{
CustomEntry entry { get; set; }
public PopupEntryFrame2()
{
entry = new CustomEntry();
entry.SetBinding(PopupEntryFrame2.TextProperty, new Binding("Text", source: this));
entry.TextChanged += (s, a) =>
{
OnTextChanged(new EntryTextChangedEventArgs(a.NewTextValue, a.OldTextValue));
};
Content = entry;
CornerRadius = 5;
HasShadow = false;
SetDynamicResource(BackgroundColorProperty, "EntryFrameBackgroundColor");
SetDynamicResource(BorderColorProperty, "EntryFrameBorderColor");
SetDynamicResource(CornerRadiusProperty, "EntryFrameCornerRadius");
SetDynamicResource(HeightRequestProperty, "PopupEntryFrameHeight");
SetDynamicResource(MarginProperty, "PopupEntryFrameMargin");
SetDynamicResource(PaddingProperty, "PopupEntryFramePadding");
}
public class EntryTextChangedEventArgs : EventArgs
{
public EntryTextChangedEventArgs(String newValue = null, String oldValue = null)
{
NewTextValue = newValue;
OldTextValue = oldValue;
}
public String NewTextValue { get; }
public String OldTextValue { get; }
}
public event EventHandler TextChanged;
protected virtual void OnTextChanged(EntryTextChangedEventArgs args)
{
TextChanged?.Invoke(this, args);
}
public static readonly BindableProperty TextProperty =
BindableProperty.Create(nameof(Text), typeof(string), typeof(PopupEntryFrame2), default(string));
public string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); }
}
The error I get when building is this:
CopyDeckPopup.xaml(22,63): XamlC error XFC0002: EventHandler "newDeckNameEntry_TextChanged"
with correct signature not found in type "DecksTab.Pages.DeckOptions.CopyDeckPopup"
To achieve your goal you can simply add the Entry on your PopupEntryFrame class and define an Event there that connects with the TextChanged event in the original Entry.
This is done as illustrated in the code below (which is based on yours!)
using Test.Renderers;
namespace Test.Templates
{
public class PopupEntryFrame : CustomFrame
{
Entry entry { get; set; }
public PopupEntryFrame()
{
entry = new Entry();
entry.TextChanged += (s, a) =>
{
OnTextChanged(new EntryTextChangedEventArgs());
};
Content = entry;
CornerRadius = 5;
HasShadow = false;
SetDynamicResource(BackgroundColorProperty, "EntryFrameBackgroundColor");
SetDynamicResource(BorderColorProperty, "EntryFrameBorderColor");
SetDynamicResource(CornerRadiusProperty, "EntryFrameCornerRadius");
SetDynamicResource(HeightRequestProperty, "PopupEntryFrameHeight");
SetDynamicResource(MarginProperty, "PopupEntryFrameMargin");
SetDynamicResource(PaddingProperty, "PopupEntryFramePadding");
}
public class EntryTextChangedEventArgs : EventArgs
{
// class members
}
public event EventHandler TextChanged;
protected virtual void OnTextChanged(EntryTextChangedEventArgs args)
{
TextChanged?.Invoke(this, args);
}
}
}
And that's it. By doing that you can now write code like
<t:PopupEntry x:Name="newDeckDescriptionEntry" TextChanged="newDeckDescriptionEntry_TextChanged">
Update
In the comments someone suggested using ContentView, so let's take a look at how the same result could be achieved using that approach.
Disclaimer
First of all, it is important to know that Frame inherits itself from ContentView (from which acctualy it inherits its Content property!). In fact, from the documentation we know that
[Xamarin.Forms.ContentProperty("Content")]
[Xamarin.Forms.RenderWith(typeof(Xamarin.Forms.Platform._FrameRenderer))]
public class Frame : Xamarin.Forms.ContentView, Xamarin.Forms.IBorderElement, Xamarin.Forms.IElementConfiguration<Xamarin.Forms.Frame>
which means that by creating a Class/Control that inherits from Frame means that we are already using the ContentView approach.
Create the ContentView
First of all we create a ContentView and set its content to a new PopupFrame() which itself contains an Entry, as follows
public class PopupEntry : ContentView
{
Entry entry { get; set; }
public PopupEntry()
{
entry = new Entry();
Content = new PopupFrame()
{
Content = entry
};
}
}
Add an Event
Next, as is required by the OP, we define an Event for our ContentView that will be triggered when the Text in the Entry changed. Following the Documentation, this can be achieved by adding the following piece of code:
public class EntryTextChangedEventArgs : EventArgs
{
// class members
}
public event EventHandler TextChanged;
protected virtual void OnTextChanged(EntryTextChangedEventArgs args)
{
TextChanged?.Invoke(this, args);
}
Now, we can "link" the original TextChanged event from the Entry control to the new Event of our ContentView, as follows:
entry.TextChanged += (s, a) =>
{
OnTextChanged(new EntryTextChangedEventArgs());
};
Then, our ContentView code will look like
public class PopupEntry : ContentView
{
Entry entry { get; set; }
public PopupEntry()
{
entry = new Entry();
entry.TextChanged += (s, a) =>
{
OnTextChanged(new EntryTextChangedEventArgs());
};
Content = new PopupFrame()
{
Content = entry
};
}
public class EntryTextChangedEventArgs : EventArgs
{
// class members
}
public event EventHandler TextChanged;
protected virtual void OnTextChanged(EntryTextChangedEventArgs args)
{
TextChanged?.Invoke(this, args);
}
}
Wrapping up
With this ContentView defined, we can now write code like
<t:PopupEntry x:Name="newDeckDescriptionEntry" TextChanged="newDeckDescriptionEntry_TextChanged"/>
And that's it! I hope this was useful.
Happy coding!
P.S.:
A little note about the Event declaration: Since EntryTextChangedEventArgs is a copy of the original TextChangedEventArgs we can define the EntryTextChangedEventArgs class like
public class EntryTextChangedEventArgs : EventArgs
{
public EntryTextChangedEventArgs(String newValue = null, String oldValue = null)
{
NewTextValue = newValue;
OldTextValue = oldValue;
}
public String NewTextValue { get; }
public String OldTextValue { get; }
}
and then when instantiating this class we just feed it directly with the values from TextChangedEventArgs, as follows
entry = new Entry();
entry.TextChanged += (s, a) =>
{
OnTextChanged(new EntryTextChangedEventArgs(a.NewTextValue, a.OldTextValue));
};
In my user control I'm trying to change the image in a PictureBox when the timer is stopped. But it works only for the first time. If I start the timer again the image is not updating. I used the below code to change the image.
public partial class CANMsgLEDControl : UserControl
{
#region Constants
private const int kLedOn = 2;
private const int kLedOff = 4;
#endregion
private bool _blinkEnabled = false;
private int _blinkRate = 500;
private GridStyleInfo _currentCell = null;
private System.Timers.Timer _timer = new System.Timers.Timer();
public CANMsgLEDControl()
{
}
public CANMsgLEDControl(GridStyleInfo cell)
{
InitializeComponent();
picBxLed.Image = imageList1.Images[kLedOff];
_timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
_currentCell = cell;
if (_currentCell != null)
this.BackColor = _currentCell.BackColor;
Create Handle
if (!this.IsHandleCreated)
{
CreateHandle();
}
}
#region Properties
[Browsable(true)]
public bool BlinkEnabled
{
get { return _blinkEnabled; }
set { _blinkEnabled = value; }
}
[Browsable(true)]
public int BlinkRate
{
get { return _blinkRate; }
set
{
_blinkRate = value;
_timer.Interval = _blinkRate;
}
}
#endregion
public void ResetLedColor()
{
this.picBxLed.Image = imageList1.Images[kLedOff];
}
public void BlinkStart()
{
_currentCell.BeginUpdate();
picBxLed.Image = imageList1.Images[kLedOn];
_currentCell.EndUpdate();
_timer.Start();
}
#region Events
private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (InvokeRequired)
{
BeginInvoke(new ElapsedEventHandler(_timer_Elapsed), new object[] { sender, e });
}
else
{
if (this.BlinkEnabled)
{
_timer.Stop();
_currentCell.BeginUpdate();
picBxLed.Image = imageList1.Images[kLedOff];
_currentCell.EndUpdate();
}
}
}
#endregion
}
How to change the image when the timer is stopped? Can anyone suggest a way to overcome this issue?
Regards,
Arulpriya
i have the following problem:
I have a relaycommand with a execute an a canexecute method, but everytime i call raisecanexecutechanged(); it calls raisecanexecutechanged in relaycommand, sets a new delegate for it and then returns back to the view model.
The same setup works in another viewmodel. I checked like 1000 times what's different but i don't find anything.
I would really appreciate if you could help me.
public RelayCommand UpdateAMSCommand { get; private set; }
public AMSSettingsViewModel(IEventAggregator eventAggregator)
{
UpdateAMSCommand = new RelayCommand(OnUpdateAMS, CanUpdateAms);
CustomAMSOffices.ListChanged += listChanged;
CustomAMSContacts.ListChanged += listChanged;
}
private void listChanged(object sender, ListChangedEventArgs e)
{
if (sender != null)
{
if (sender is BindingList<CustomAMSOffice>)
{
BindingList<CustomAMSOffice> temp = (BindingList<CustomAMSOffice>)sender;
if (temp.Count > _amsOfficesItemsCounter)
{
_amsOfficesItemsCounter = temp.Count;
for (int i = 0; i < temp.Count; i++)
{
temp[i].ErrorsChanged += RaiseCanExecuteChanged;
}
}
}
else if (sender is BindingList<CustomAMSContact>)
{
BindingList<CustomAMSContact> temp = (BindingList<CustomAMSContact>)sender;
if (temp.Count > _amsContactsItemsCounter)
{
_amsContactsItemsCounter = temp.Count;
for (int i = 0; i < temp.Count; i++)
{
temp[i].ErrorsChanged += RaiseCanExecuteChanged;
}
}
}
}
UpdateAMSCommand.RaiseCanExecuteChanged();
}
private void RaiseCanExecuteChanged(object sender, DataErrorsChangedEventArgs e)
{
UpdateAMSCommand.RaiseCanExecuteChanged();
}
private bool CanUpdateAms()
{
foreach (var cao in CustomAMSOffices)
{
if (!cao.Check() || cao.HasErrors)
{
return false;
}
}
foreach (var cac in CustomAMSContacts)
{
if (!cac.Check() || cac.HasErrors)
{
return false;
}
}
return true;
}
Edit:
the relaycommand i use: https://github.com/briannoyes/WPFMVVM-StarterCode/blob/master/ZzaDashboard/ZzaDashboard/RelayCommand.cs
Ok, I'm just going to copy paste some code that I have in use, so that you should be able to pop these into your project and use.
First off, the RelayCommand() class. I lifted this code from this msdn page:
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion
#region Constructors
public RelayCommand(Action<object> execute) : this(execute, null) { }
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion
}
Now our ModelView.cs class needs to inherit from INotifyPropertyChangedand will have our RaisePropertyChanged(). Now I usually make just make this a it's own file and have all my ModelViews inherit from it so the code is a little cleaner, but you can do as you please.
Here's how I have it setup though:
BaseViewModel.cs:
public class BaseViewModel : INotifyPropertyChanged
{
internal void RaisePropertyChanged(string prop)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
public event PropertyChangedEventHandler PropertyChanged;
// Any other code we want all model views to have
}
Now for our MainViewModel.cs we will just inherit from BaseViewModel, add our event handlers in, and run it!
Example: ServerViewModel.cs
public class ServerViewModel : BaseViewModel
{
public RelayCommand BroadcastMessageCommand { get; set; }
private string _broadcastmessage;
public string broadcastmessage
{
get { return _broadcastmessage; }
set { _broadcastmessage = value; RaisePropertyChanged("broadcastmessage"); }
}
Server server;
public ServerViewModel()
{
server = new Server();
server.run();
BroadcastMessageCommand = new RelayCommand(BroadcastMessage, CanBroadcast);
}
private bool CanBroadcast(object param)
{
if (string.IsNullOrWhiteSpace(broadcastmessage))
return false;
if (!server.running)
return false;
return true;
}
public void BroadcastMessage(object param)
{
server.BroadcastMessage(broadcastmessage);
broadcastmessage = "";
RaisePropertyChanged("broadcastmessage");
}
}
Now anything in our MainView.xaml that is bound with Command="{Binding broadcastmessage}" will update appropriately. In my case I have this bound to a button and the button will be disabled if there message is empty, or if we are not connected to the server.
Hopefully that's enough code example to get you headed in the right direction! Let me know if you have any questions on it.
Let's try simplifying the code as much as we can until we get this working properly, and then we will slowly add code back until we find the code(s) that are causing trouble.
So let's reduce this to it's barebones and see if we have any success. Try this code:
public RelayCommand UpdateAMSCommand { get; private set; }
public AMSSettingsViewModel(IEventAggregator eventAggregator)
{
UpdateAMSCommand = new RelayCommand(OnUpdateAMS, CanUpdateAms);
CustomAMSOffices.ListChanged += listChanged;
CustomAMSContacts.ListChanged += listChanged;
}
private void listChanged(object sender, ListChangedEventArgs e)
{
UpdateAMSCommand.RaiseCanExecuteChanged();
}
private void RaiseCanExecuteChanged(object sender, DataErrorsChangedEventArgs e)
{
UpdateAMSCommand.RaiseCanExecuteChanged();
}
// This will simply flip from true to false every time it is called.
private bool _canupdate = false;
private bool CanUpdateAms()
{
_canupdate = !_canupdate;
return _canupdate;
}
Edit: I don't know why it doesn't work.
I have a UserControl and a class for updating/storing results in a database. I need to automatically refresh the UserControlResultDislpay upon storing data. I have created and event to trigger a refresh when an Update occurs. I have the following code:
Class InstrumentTest:
public delegate void UpdateResultDisplay(object sender, EventArgs e);
public event UpdateResultDisplay RefreshDisplay;
protected virtual void OnNewResult(EventArgs e)
{
if (RefreshDisplay != null)
RefreshDisplay(this, e);
}
public void UpdateResultDB(ResultDataJFTOT resultData)
{
AnalysisListCommon myresult = PContext.GetInstance().DbHandlerLocal.StoredResult(
resultData.SampleId,
resultData.TestDate.ToString("yyyy-MM-ddTHH:mm", CultureInfo.InvariantCulture),
resultData.InstrumentSn,
StringRepository.constStringSampleName);
if (myresult != null)
{
Result r = new Result(new Guid(myresult.ResultId));
ResultData rd = r.GetResultData("Rating", FindResultDataMode.byVariableIdentifier);
string xmlTubeRating = resultData.tRating.ToString().Replace("#LT#", "<");
rd.Text = xmlRating;
rd.Store();
rd = r.GetResultData("TestDate", FindResultDataMode.byVariableIdentifier);
rd.Text = resultData.Date.ToString();
rd.Store();
OnNewResult(EventArgs.Empty);
}
else
{
AddTestToQueue(resultData);
}
}
public static InstrumentTest Instance()
{
//If instance is null create a new instance of the InstrumentTest
if (instrumentTestInstance == null)
{
instrumentTestInstance = new InstrumentTest();
}
return instrumentTestInstance;
}
Code from UserControl:
public UserControlResultDisplay()
{
this.InitializeComponent();
this.InitializeUIStrings();
this.InitializePlot();
EventListener(resultChanged);
}
private InstrumentTest resultChanged = InstrumentTest.Instance();
public void EventListener(InstrumentTest resultChanged)
{
//resultChanged = (InstrumentTest)obj;
resultChanged.RefreshDisplay += DisplayNewResultData;
}
private void DisplayNewResultData(object sender, EventArgs e)
{
RefreshCurrentResult();
}
I have mainviewmodel where it makes a collection of itemviewmodel and binds that data to a longlist in Mainpage.xaml. Now in the process of making a collection of ItemViewModel I am making a web request and when that downloads I would make a list.
I wanted to know in MainPage as when this download finishes.
MainViewModel
public void LoadData()
{
if (this.CanLoad)
{
WebClient dealsOfDay = new WebClient();
dealsOfDay.DownloadStringCompleted += dealsOfDay_DownloadStringCompleted;
dealsOfDay.DownloadStringAsync(new Uri("http://loadsomedata.php"));
}
else
{
this.IsDataLoaded = false;
}
}
void dealsOfDay_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null && e.Result != null)
{
var deals=//something making a collection.
Items = new ObservableCollection<ItemViewModel>(deals);
NotifyPropertyChanged("Items");
this.IsDataLoaded = true;
}
else
{
MessageBox.Show("Error");
}
}
App.xaml.cs
private static MainViewModel viewModel = null;
/// <summary>
/// A static ViewModel used by the views to bind against.
/// </summary>
/// <returns>The MainViewModel object.</returns>
public static MainViewModel ViewModel
{
get
{
// Delay creation of the view model until necessary
if (viewModel == null)
viewModel = new MainViewModel();
return viewModel;
}
}
MainPage.xaml.cs
Inside the constructor I would set this.
DataContext = App.ViewModel;
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
I think you have no need for bool IsDataLoaded. Instead that create event in MainViewModel and register in Main page.
public event EventHandler DataLoadedEvent;
void dealsOfDay_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null && e.Result != null)
{
var deals=//something making a collection.
Items = new ObservableCollection<ItemViewModel>(deals);
NotifyPropertyChanged("Items");
if ( DataLoadedEvent != null)
{
DataLoadedEvent(this, new EventHandler());
}
}
else
{
MessageBox.Show("Error");
}
}
Now in MainPage constructor register this event.
App.ViewModel.DataLoadedEvent += new EventHandler(data_loadedEvent);
void data_loadedEvent(object sender, EventArgs e)
{
App.ViewModel.LoadData();
}
Remember also that you can subscribe more methods to DownloadStringCompleted - and they will be fired, so maybe there is no need to create new event. Also in many cases you can just perform actions in dealsOfDay_DownloadStringCompleted.
But if you want to make an Event which will be fired when DownloadCompletes it can look like this:
Create a delegate:
public delegate void StatusUpdateHandler(object sender, StatusEventArgs e);
public event StatusUpdateHandler OnUpdateStatus;
For this purpose you need somewhere to define StatusEventArgs Class:
public class StatusEventArgs : EventArgs
{
public string Status { get; private set; }
public StatusEventArgs(string status)
{
Status = status;
}
}
Then your method can look like this:
private void UpdateStatus(string status)
{
if (OnUpdateStatus == null) return;
StatusEventArgs args = new StatusEventArgs(status);
OnUpdateStatus(this, args);
}
Then you can freely subscribe to that event and put in your dealsOfDay_DownloadStringCompleted:
UpdateStatus("Downloaded");