As per MSDN guidelines we need to put all the app's settings into the SettingsPane and then the app should update all pages when the settings is applied.
In my app I need to have a reset option which brings the app to the default settings. There are 2 pages, Calendar.xaml and HistoryStatistics.xaml that i need to update when the reset button is pressed. All the data of the app is put in a singleton class called CycleManager. I have used a SettingsFlyout control from the Callisto Toolkit.
App.Xaml
Registered the settings in the App.xaml
SettingsPane.GetForCurrentView().CommandsRequested += OnCommandsRequested;
and in OnCommandsRequested function, created the reset handler
var reset = new SettingsCommand("reset", "Reset", (handler) =>
{
var settings = new SettingsFlyout();
settings.Content = new ResetUserControl();
settings.HeaderBrush = new SolidColorBrush(_background);
settings.Background = new SolidColorBrush(_background);
settings.HeaderText = "Reset";
settings.IsOpen = true;
});
args.Request.ApplicationCommands.Add(reset);
CycleManager.cs
In the CycleManager class, there is a m_Reset variable,its setter and getter and an event handler called ResetClicked
public event EventHandler ResetClicked;
public bool Reset
{
get
{
return m_reset;
}
set
{
m_reset = value;
if (ResetClicked != null)
ResetClicked(this, EventArgs.Empty);
}
}
Next is the part where i have associated this handler in my first class calendar.xaml
Calendar.xaml
In the constructor of the class I declare the event handler
CycleManager pCycMan = CycleManager.Instance;
pCycMan.ResetClicked += this.ResetClicked;
followed by the definition of the event handler
private async void ResetClicked(object sender, EventArgs e)
{
CycleManager pCycMan = CycleManager.Instance;
if (pCycMan.Reset == true)
{
try
{
await Windows.Storage.ApplicationData.Current.ClearAsync(Windows.Storage.ApplicationDataLocality.Local);
pCycMan.InitializeValues();
}
catch (Exception)
{
}
}
CreateCalendar();// UI is loaded
}
In the constructor of the HistoryStatistics.xaml I have done the same thing as above
HistoryStatistics.xaml
public HistoryStatistics()
{
CycleManager pCycMan = CycleManager.Instance;
pCycMan.ResetClicked += this.ResetClicked;
}
and defined
private void ResetClicked(object sender, EventArgs e)
{
CycleManager pCycMan = CycleManager.Instance;
if (pCycMan.Reset == true)
{
await Windows.Storage.ApplicationData.Current.ClearAsync(Windows.Storage.ApplicationDataLocality.Local);
pCycMan.InitializeValues();
LoadListView();// loads the UI
DisplayStatistics();//loads the UI for the page
}
}
Now the problem
Is this the right approach?
When Reset is pressed in the first from the second page(HistoryStatistcs), the reset clicked function declared in the first page(Calendar.xaml.cs) is called first and then the one in HistoryStatistics. And both gets executed async! :(
Is this a right behaviour?
This question is quite long. Hope everybody understood the scenario and question.
There is nothing wrong with the behaviour you outlined. Two pages subscribe to an event and event uses multi cast delegate which means they will both get fired.
I think you need a simpler behaviour here. Each xaml page should subscribe to that event on OnNavigatedTo and should unsubscribe in OnNavigatedFrom.
That way only one of the two actually executes the cleanup.
The complexity/confusion is likely coming because of not using the MVVM (model, view, and view model) separation. you may want to read about this. keeping the separation helps. Below are few pointers on this. but not necessarily a full design for your app.
in this example: CycleManager.Instance is kind of serving the model (the data). You may want to rename ResetClicked to SettingChanged and think of the event as notification for clients that one or more settings properties exposed has changed. It should also expose ResetSettings() method that can be called by ResetUserControl.
// model for the settings
class SettingsManager
{
public event EventHandler SettingsChanged;
public async void ResetSettings()
{
await Windows.Storage.ApplicationData.Current.ClearAsync
(Windows.Storage.ApplicationDataLocality.Local);
// initialize all values to default values;
this._intializeValues();
if (this.SettingsChanged != null)
this.SettingsChanged(this, EventArgs.Empty);
}
}
HistoryStatistics and Calendar class should have view model that should listen for SettingsChanged event and update the properties exposed. Each page view (xaml) binds to the properties exposed by the respective view model. This will require some refactoring of current code.
Without that, ResetClick eventhandlers can be changed to SettingChanged event handlers and take required action. They need not call setting mgr to initialize values.
class HistoryStatistics
{
private void SettingsChanged(object sender, EventArgs e)
{
SettingsManager settingsManager = SettingsManager.Instance;
LoadListView();// loads the UI
DisplayStatistics();//loads the UI for the page
}
}
HTH.
Related
I have a frame and few pages in my WPF application.
My navigation is controlled by buttons. On each button I have click handler that creates new page with some parameters:
private void ButtonProductionAuto_OnClick(ref TechModbus, RoutedEventArgs e)
{
FrameMain.Content = new PageProductionAuto(someobject, this);
}
private void ButtonProductionManual_OnClick(ref TechModbus, RoutedEventArgs e)
{
FrameMain.Content = new PageProductionManual(someobject, this);
}
When I'm switching between pages - previous pages still exist in memory and they react on some custom events.
(edit)
This is my code related with events:
public PageProductionAuto(ref TechModbus modbus, MainWindow wnd)
{
// ...
wnd.KeyDown += Wnd_KeyDown;
wnd.KeyUp += Wnd_KeyUp;
m.OnReadFinished += Modbus_OnReadFinished;
// ...
}
How can I dispose these pages or how can I avoid double-fire on my events when page is opened second time?
You should unregister the events on leaving the page.
GarbageCollector will then "dispose" (it's actually not a dispose) by itsself when there are no more references on those objects(PageProductionAuto and PageProductionManual).
Quoting MS:
The reason WPF controls don't implement IDisposable is because they have nothing to dispose. They have no handle to clean up, and no unmanaged resources to release. To ensure your memory is cleaned up, just make sure nothing has a reference to the controls once you're finished with them.
Your question is incomplete. But I can answer the question about "how to avoid multiple instances" part of it. To dispose your pages, you have to detach your events, remove them from the "openedPages" collection, and dispose where possible.
List<object> openedPages = new List<object>();
private void ButtonProductionAuto_OnClick(object sender, RoutedEventArgs e)
{
var page = openedPages.FirstOrDefault(p => p.GetType().Equals(typeof(PageProductionAuto)));
if(page == null)
{
page = new PageProductionAuto(someobject, this);
opendPages.Add(page);
}
else
{
page.SetObjects(someobject, this); // create a method to set "someObject" to your page.
}
FrameMain.Content = page;
}
What I did to avoid this was I have a window with 2 frames in it. I placed one of each source XAML in each frame so frame 1 was XAML 1 and frame 2 was XAML 2. Then I just edited the visibility to collapsed and visible. Then you don't have any page changes or instances getting created. You just create the original 2 instances.
I have class Step which has a collection of Task i.e List .
Step has properties Status , Time . Task also has the same properties. The values of Status and Time for Step need to be updated whenver anyone of the Tasks get their Time or Status changed.
For this , I am adding handlers to each task in the Step class.
private void AddHandlers()
{
foreach (Task tsk in Tasks)
{
tsk.PropertyChanged += HandleStatusChanged;
tsk.PropertyChanged += HandleTimeChanged;
}
}
private void HandleStatusChanged(object sender, EventArgs e)
{
UpdateStepStatusFromTasks();
}
private void HandleTimeChanged(object sender, EventArgs e)
{
UpdateStepTimesFromTasks();
}
private void UpdateStepTimesFromTasks()
{
// logic for calculating Time for Step
}
private void UpdateStepStatusFromTasks()
{
// logic for calculating Status for Step
}
Here is the Property changed event handler in Task
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
My issue is that even if I change only Task Time , it calls both the handlers Status and time as they are subscribed to the same property changed event on task.
How can i bifurcate the Property changed event based on Property called from and ensure that only the respective handlers get called and not both together ?
Sorry if this sounds silly , but I am somewhat a beginner to WPF.
Regards,
P
You need to check the parameter of the args that are passed in to get the name of the property.
First get rid of your double subscription.
private void AddHandlers()
{
foreach (Task tsk in Tasks)
{
tsk.PropertyChanged += HandlePropertyChanged;
}
}
Then use the correct signature for your event so you get the correct type of event args.
private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
Now that we have PropertyChangedEventArgs instead of just EventArgs we can check the PropertyName property and call the needed method.
private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch(e.PropertyName)
{
case "Status":
UpdateStepStatusFromTasks();
break;
case "Time":
UpdateStepTimesFromTasks();
break;
}
}
As you need more properties handled you can just add them to the switch statement.
P.S. Instead of manually subscribing to each Task you can use a BindingList<Task> as the collection that holds the tasks, you can then subscribe to the ListChanged event, that event will be raised if any of the items in the list raise PropertyChanged (be sure to enable RaiseListChangedEvents and check ListChangedEventArgs.ListChangedType is equal to ListChangedType.ItemChanged).
Every event has "accessors" add or remove. Something similar like get/set for properties. This accessors can show you the nature of the event. Every event has an InvocationList, which represents a collection of object that it will notify when the event is raised. Using this accessors you can you can have more control over what get notified and what not. When you subscribe to the event, the subscribed object get inserted into the Invocation list.
Since you are subscribing the same object for both events, you will have it triggered twice.
Only thing you can do is to check the name of the property that got updated
public void ChangedHandler(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName=="Time"){//do something}
else if (e.PropertyName == "Date") {doSomething}
}
Since you are dealing with WPF, I see a strange pattern here. You are raising the events from various methods. You should be raising the event from a property for which you want the notification to happen, which is bound to a control.
public class MyVM
{
private string _status = "status1";
public string Status
{
get
{
return _status;
}
set
{
if(_status!=value)
{
_status =value
OnPropertyChanged("Status");
}
}
}
}
You can improve on this using various things like "nameof", baseClasses, or MethorVeawers like FODY
So, the obvious thing here is that you are attaching two handlers to the `` event so everything is being processed twice. It needs be only subscribed to once.
But rather than making a lot of complicated methods with code bouncing around all over the place, I prefer to using Microsoft's Reactive Extensions (Rx) - NuGet "Rx-Main" - to do anything with events. After learning a few basic operators it really makes working with events much much easier.
Rx is, in overly simplistic terms, LINQ for Events. It lets you work with queries to handle events rather than enumerables. It creates observables.
First, I would create this observable:
var tpns = // IObservable<{anonymous}>
from t in Tasks.ToObservable()
from ep in Observable.FromEventPattern<
PropertyChangedEventHandler, PropertyChangedEventArgs>(
h => t.PropertyChanged += h,
h => t.PropertyChanged -= h)
select new { Task = t, ep.EventArgs.PropertyName };
This query basically takes the list of Tasks and converts all of the PropertyChanged events of each task in a single observable that returns each Task when that task had a property change and the PropertyName of the task that changed.
Now it's easy to create a couple more observables that filter by PropertyName and return the Task:
IObservable<Task> statusChanges =
from tpn in tpns
where tpn.PropertyName == "Status"
select tpn.Task;
IObservable<Task> timeChanges =
from tpn in tpns
where tpn.PropertyName == "Time"
select tpn.Task;
Those should be really simple to understand.
Now subscribe to each (basically like attaching to events):
IDisposable statusSubscription =
statusChanges
.Subscribe(task => UpdateStepStatusFromTasks());
IDisposable timeSubscription =
timeChanges
.Subscribe(task => UpdateStepTimesFromTasks());
You'll notice each subscription is an IDisposable. Instead of detaching from events using the -= operator you simply call .Dispose() on the subscription and all of the underlying event handlers are detached for you.
Now I would recommend changing the AddHandlers method to return an IDisposable. Then the code that calls AddHandlers can dispose of the handlers - if needed - to make sure you can clean up before exiting.
So the complete code would look like this:
private IDisposable AddHandlers()
{
var tpns = // IObservable<{anonymous}>
from t in Tasks.ToObservable()
from ep in Observable.FromEventPattern<
PropertyChangedEventHandler, PropertyChangedEventArgs>(
h => t.PropertyChanged += h,
h => t.PropertyChanged -= h)
select new { Task = t, ep.EventArgs.PropertyName };
IObservable<Task> statusChanges =
from tpn in tpns
where tpn.PropertyName == "Status"
select tpn.Task;
IObservable<Task> timeChanges =
from tpn in tpns
where tpn.PropertyName == "Time"
select tpn.Task;
IDisposable statusSubscription =
statusChanges
.Subscribe(task => UpdateStepStatusFromTasks());
IDisposable timeSubscription =
timeChanges
.Subscribe(task => UpdateStepTimesFromTasks());
return new CompositeDisposable(statusSubscription, timeSubscription);
}
The only new thing there is the CompositeDisposable which joins the two IDiposable subscriptions into a single IDisposable.
The very nice thing about this approach is that most of the code now sits nicely in a single method. It makes it easy to understand and maintain when done this way - at least after a small learning curve. :-)
New to C#. Like the title, I'm having difficulty trying to raise an event. It will eventually then be consumed on another form.
What I'm trying to do is have many instances of a custom user control (my event raising form(s)) that creates a tcp client, connects, and then closes. When this tcp client has an "error", be it a catch exception, I want an event to be raised. I'm forcing the error right now by having my internet turned off to test. My first problem is I can't even get the event to be raised at all. I'll show the event code I'm working with on my custom user control:
public delegate void TaskCompleteEventHandler(object sender, TaskCompleteEventArgs e);
public event TaskCompleteEventHandler TaskComplete;
public class TaskCompleteEventArgs : System.EventArgs
{
// add local member variables to hold text
private string errorString;
// class constructor
public TaskCompleteEventArgs(string ErrorString)
{
this.errorString = ErrorString;
}
// Property
public string ErrorString
{
get
{
return errorString;
}
set
{
errorString = value;
}
}
}
This is my method that processes the exception and ideally would raise the event and allow the host form to print the string and exception accordingly.
private void ErrorLogging(string ex)
{
errorString = String.Format(/*...errorString formatting...*/);
// instance the event args and pass it the errorString value
TaskCompleteEventArgs args = new TaskCompleteEventArgs(errorString);
// raise the event with the updated arguments
TaskComplete(this, args); //----> THIS IS WHERE I GET AN ERROR!! <----
this.Dispose();
}
The error is Object reference not set to an instance of an object.
Here's the Watch screen of my TaskComplete(this, args)
I can't seem to debug this... I'm just not strong enough yet to know what I've done wrong. How is it causing side effects?
I'm sure I'm going to have more issues on my main form when I get this going... Does anyone have a clue what's causing this? Thanks in advance.
EDIT: On my main form:
public Form1()
{
InitializeComponent();
// Start control disabled and subscribe each control the event
foreach (var control in controlList)
{
control.Enabled = false;
control.TaskComplete += new dev_emu_project.dev_emu_widget.TaskCompleteEventHandler(OnTaskComplete);
}
}
List<dev_emu_project.dev_emu_widget> controlList = new List<dev_emu_project.dev_emu_widget>();
public void OnTaskComplete(object sender, dev_emu_project.TaskCompleteEventArgs e)
{
//.... work for processing
}
}
You are getting a NullReferenceException because you're invoking an empty event, meaning no delegate has been registered to it. You need to make sure TaskComplete isn't null before invoking it.
Add a null check before invoking to make sure someone did register to your event:
if (TaskComplete != null)
{
TaskComplete(this, args);
}
From MSDN Event Tutorial:
Invoking an event
Once a class has declared an event, it can treat that event just like a field of the indicated delegate type. The field will either be null, if no client has hooked up a delegate to the event, or else it refers to a delegate that should be called when the event is invoked. Thus, invoking an event is generally done by first checking for null and then calling the event
C# has gone to great lengths to ensure that you cannot "fire" an event, e.g.:
form.FormClosed(this, new FormClosedEventArgs(CloseReason.UserClosing));
button.Click(this, new EventArgs());
customer.AddressChanged(this, new EventArgs());
don't compile because you cannot fire an event this way.
This seems to have been a conscious decision on the part of the language designers. They seem to be intentionally trying to prevent "bad behavior".
i am trying to find the intended replacement.
Practical Example:
void ShowPopup(Form form)
{
ToolStripDropDown toolDrop = new ToolStripDropDown();
ToolStripControlHost toolHost = new ToolStripControlHost(form);
toolHost.Margin = new Padding(0);
toolDrop.Padding = new Padding(0);
toolDrop.Items.Add(toolHost);
toolDrop.Closed += toolDrop_Closed;
toolDrop.Show(screenLocation);
}
void toolDrop_Closed(object sender, ToolStripDropDownClosedEventArgs e)
{
//The form's FormClosed event doesn't fire when shown as a popup
//Fire the event manually
form.FormClosed(this, new FormClosedEventArgs(CloseReason.UserClosing));
}
If i'm not supposed to fire events from outside the object - what is the technique intended to replace it?
If there is no intended replacement for firing events, is there any article, book, or Channel 9 video that explains how i should handle this situation?
An event inside a class has often a public method used by class clients to invoke the event from outside of the class:
public class Foo {
public event FooDelegate FooEvent;
public void RaiseFoo() {
if ( FooEvent != null ) FooEvent();
}
}
If an event is missing such public "trigger", it usually means that for some reason you should not be able to raise the event from outside.
In your example, the event can of course be raised with
form.Close();
with, apart from other things, also raises the event.
I'm using MVVM Light to build a WP7 (Windows Phone 7) application. I wish to have all the work performed by the Model to be run on a background thread. Then, when the work is done, raise an event so that the ViewModel can process the data.
I have already found out that I cannot invoke a Delegate asynchronously from an WP7 app.
Currently I am trying to use ThreadPool.QueueUserWorkItem() to run some code on a background thread and use MVVM Light's DispatcherHelper.CheckBeginInvodeOnUI() to raise an event on the UI thread to signal the ViewModel that the data has been loaded (this crashes VS2010 and Blend 4 when they try to display a design-time view).
Is there any sample code to run some code on a background thread and then dispatch an event back to the UI thread for a WP7 app?
Thanks in advance,
Jeff.
Edit - Here is a sample Model
public class DataModel
{
public event EventHandler<DataLoadingEventArgs> DataLoadingComplete;
public event EventHandler<DataLoadingErrorEventArgs> DataLoadingError;
List<Data> _dataCasch = new List<Data>();
public void GetData()
{
ThreadPool.QueueUserWorkItem(func =>
{
try
{
LoadData();
if (DataLoadingComplete != null)
{
//Dispatch complete event back to the UI thread
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
//raise event
DataLoadingComplete(this, new DataLoadingEventArgs(_dataCasch));
});
}
}
catch (Exception ex)
{
if (DataLoadingError != null)
{
//Dispatch error event back to the UI thread
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
//raise error
DataLoadingError(this, new DataLoadingErrorEventArgs(ex));
});
}
}
});
}
private void LoadData()
{
//Do work to load data....
}
}
Here's how I'd approach a solution to this.
Your ViewModel implements INotifyPropertyChanged right? There's no need to dispatch the Events. Just raise them "bare" in the Model, then dispatch the RaisePropertyChanged in the ViewModel.
And yes, you should have some sort of singleton model/database in your code. After all, what is a SQL Database if not some gigantic singleton? Since we don't have a database in WP7, don't be shy creating a singleton object. I have one called "Database" :)
I've just tried threading my dataloads in there, and realise that in fact the best approach is simply implementing INotifyPropertyChanged right down at the model level. There's no shame in this.
So given that, here's what I'm doing in the singleton Database object to load and return my Tours "table" (note the thread.sleep to make it take a visible amount of time to load, normally its sub 100ms). Database class now implements INotifyPropertyChanged, and raises events when loading is completed:
public ObservableCollection<Tour> Tours
{
get
{
if ( _tours == null )
{
_tours = new ObservableCollection<Tour>();
ThreadPool.QueueUserWorkItem(LoadTours);
}
return _tours;
}
}
private void LoadTours(object o)
{
var start = DateTime.Now;
//simlate lots of work
Thread.Sleep(5000);
_tours = IsoStore.Deserialize<ObservableCollection<Tour>>( ToursFilename ) ?? new ObservableCollection<Tour>();
Debug.WriteLine( "Deserialize time: " + DateTime.Now.Subtract( start ).ToString() );
RaisePropertyChanged("Tours");
}
You follow? I'm deserializing the Tour list on a background thread, then raising a propertychanged event.
Now in the ViewModel, I want a list of TourViewModels to bind to, which I select with a linq query once I see that the Tours table has changed. It's probably a bit cheap to listen for the Database event in the ViewModel - it might be "nicer" to encapsulate that in the model, but let's not make work we we don't need to eh?
Hook the Database event in the Viewmodel's constructor:
public TourViewModel()
{
Database.Instance.PropertyChanged += DatabasePropertyChanged;
}
Listen for the appropriate table change (we love magic strings! ;-) ):
private void DatabasePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "Tours")
{
LoadTourList();
}
}
Select the records I want from the table, then tell the view there is new data:
public void LoadTourList()
{
AllTours = ( from t in Database.Instance.Tours
select new TourViewModel( t ) ).ToList();
RaisePropertyChanged( "AllTours" );
}
And lastly, in your ViewModelBase, it's best to check if your RaisePropertyChanged needs dispatching. My "SafeDispatch" method is pretty much the same as the one from MVVMlight:
private void RaisePropertyChanged(string property)
{
if ( PropertyChanged != null )
{
UiHelper.SafeDispatch(() =>
PropertyChanged(this, new PropertyChangedEventArgs(property)));
}
}
This works perfectly in my code, and I think is fairly tidy?
Lastly, extra for experts: in WP7, it might be good to add a ProgressBar with IsIndeterminate=True to your page - this will display the "dotted" progress bar. Then what you can do is when the ViewModel first loads you could set a "ProgressBarVisible" property to Visible (and raise the associated PropertyChanged event). Bind the ProgressBar's visibility to this ViewModel property. When the Database PropertyChanged event fires, set the visibility to Collapsed to make the progressbar go away.
This way the user will see the "IsIndeterminate" progress bar at the top of their screen while the deserialization is running. Nice!
I have not developed for WP7 before, but I found this article that might be useful!
Here is the Dining Philosopher sample code from the article that should give you a good idea on how to raise an event to the UI from another thread:
public DinnersViewModel(IDinnerCatalog catalog)
{
theCatalog = catalog;
theCatalog.DinnerLoadingComplete +=
new EventHandler<DinnerLoadingEventArgs>(
Dinners_DinnerLoadingComplete);
}
public void LoadDinners()
{
theCatalog.GetDinners();
}
void Dinners_DinnerLoadingComplete(
object sender, DinnerLoadingEventArgs e)
{
// Fire Event on UI Thread
View.Dispatcher.BeginInvoke(() =>
{
// Clear the list
theDinners.Clear();
// Add the new Dinners
foreach (Dinner d in e.Results)
theDinners.Add(d);
if (LoadComplete != null)
LoadComplete(this, null);
});
}
I hope it's helpful :).
One thing that's confusing: you said that when you use the helper to raise the event, then VS2010 crashes... what exactly are you seeing when it's crashing? Are you getting an exception?
Jeff, I'm still figuring this stuff out myself. I posted a similar question and ended up answering it myself by building a simple sample. Here:
A super-simple MVVM-Light WP7 sample?
The summary is:
1) I derived my Model (yes my model) from ViewModelBase. This gives me Mvvm-Light's implementation of messaging and INotifyPropertyChanged which is handy. You could argue this is not "pure" but I don't think it matters.
2) I used Mvvm-Light DispatcherHelper.CheckBeginInvokeOnUIhelper just as you did (from my Model, NOT my ViewModel).
Hope this helps.