I have a multi threaded wpf application with various HW interfaces.
I want to react to several HW failures that can happen.
For example :
one of the interfaces is a temperature sensor and i want that from a certain temp. a meesage would appear and notify the user that it happened.
i came up with the follwing design :
/// <summary>
/// This logic reacts to errors that occur during the system run.
/// The reaction is set by the component that raised the error.
/// </summary>
public class ErrorHandlingLogic : Logic
{
}
the above class would consume ErrorEventData that holds all the information about the error that occurred.
public class ErrorEventData : IEventData
{
#region public enum
public enum ErrorReaction
{
}
#endregion public enum
#region Private Data Memebers and props
private ErrorReaction m_ErrorReaction;
public ErrorReaction ErrorReactionValue
{
get { return m_ErrorReaction; }
set { m_ErrorReaction = value; }
}
private string m_Msg;
public string Msg
{
get { return m_Msg; }
set { m_Msg = value; }
}
private string m_ComponentName;
public string ComponentName
{
get { return m_ComponentName; }
set { m_ComponentName = value; }
}
#endregion Private Data Memebers and props
public ErrorEventData(ErrorReaction reaction, string msg, string componenetName)
{
m_ErrorReaction = reaction;
m_Msg = msg;
m_ComponentName = componenetName;
}
}
the above ErrorHandlingLogic would decide what to do with the ErrorEventData sent to him from various components of the application.
if needed it would be forwarded to the GUI to display a message to the user.
so what do you think is it a good design ?
thanks,
Adiel.
It seems fair enough, however, in terms of design I would have probably just went with a standard Event with custom event args.
Here is an example:
public interface IEventData
{
ErrorReaction Reaction { get; }
string Message { get; }
ComponentName { get; }
}
public class HardwareChangeEventData : IEventData
{
public HardwareChangeEventData(ErrorReaction reaction, string msg, string componentName)
{
Reaction = reaction;
Message = msg;
ComponentName = componentName;
}
public ErrorReaction Reaction { get; private set; }
public string Message { get; private set; }
public ComponentName { get; private set; }
}
....
// introduce a base class so all hardware components can raise the event
public class HardwareComponent
{
public delegate void HardwareChangedEventHandler(IEventData ed);
public event HardwareChangedEventHandler HardwareChanged;
//event-invoking method that derived classes can override.
protected virtual void OnHardwareChanged(IEventData ed)
{
HardwareChangedEventHandler handler = HardwareChanged;
if (handler != null)
{
handler(this, ed);
}
}
}
public class TemperatureGauge : HardwareComponent
{
public void Monitor()
{
// example logic
while (...)
{
if (Temperature < LowThreshold)
{
IEventData ed = new HardwareChangeEventData(ErrorReaction.IncreaseTemp, "Temperature too low!", "TemperatureGauge");
OnHardwareChanged(ed);
}
}
}
public override OnHardwareChanged(IEventData ed)
{
// do something with ed internally (if applicable)
// forward event on to base so it can be passed out to subscribers
base.OnHardwareChanged(ed);
}
}
Above code looks fine.
But for notifying different components , i would say look for Observer pattern ( Event/Deleagte)
if you are going to handle error in WPF why don't use validators for that? see this acticle
Related
I have achieved desired result with MessagingCenter, but I have got an information from reading Xamarin articles that MessagingCenter is not the preferred way to trigger 30+ events. Additional to that I have to unsubscribe from MessagingCenter after action has been done. I want to have Settings page where I would have 30+ settings that have to be changed across whole application in different views. How I can inject SettingsViewModel into other ViewModels in Xamarin.Forms application?
SettingsViewModel.cs:
namespace MessagingCenterApp.ViewModels
{
public class SettingsViewModel : BaseViewModel, ISettingsViewModel
{
public ICommand ChangeCommand { get; set; }
public SettingsViewModel()
{
Title = "Settings";
this.BoxColor = Color.Red;
this.ChangeCommand = new Command(this.ChangeColor);
}
private void ChangeColor()
{
this.BoxColor = Color.FromHex(this.BoxColorS);
MessagingCenter.Send<Object, Color>(this, "boxColor", this.BoxColor);
}
private Color _boxColor;
public Color BoxColor
{
get => _boxColor;
set
{
_boxColor = value;
this.OnPropertyChanged();
}
}
private string _boxColorS;
public string BoxColorS
{
get => Preferences.Get("BoxColor", "#17805d");
set
{
Preferences.Set("BoxColor", value);
this.ChangeColor();
this.OnSettingsChanged();
this.OnPropertyChanged();
}
}
public event EventHandler<SettingsChangedEventArgs> SettingsChanged;
private void OnSettingsChanged() => this.SettingsChanged?.Invoke(this, new SettingsChangedEventArgs(this.Settings));
public Settings Settings { get; private set; }
}
}
HomeViewModel.cs:
namespace MessagingCenterApp.ViewModels
{
public class HomeViewModel : BaseViewModel
{
public HomeViewModel()
{
this.Title = "Home";
MessagingCenter.Subscribe<Object, Color>(this, "boxColor", (sender, arg) =>
{
System.Diagnostics.Debug.WriteLine("received color = " + arg);
this.BoxColor = arg;
});
this.BoxColor = Color.Red;
this.SettingsViewModel = new SettingsViewModel();
this.SettingsViewModel.SettingsChanged += OnSettingsChanged;
}
private void OnSettingsChanged(object sender, SettingsChangedEventArgs e)
{
throw new NotImplementedException();
}
private Color _boxColor;
public Color BoxColor
{
get => _boxColor;
set
{
_boxColor = value;
OnPropertyChanged();
}
}
private ISettingsViewModel SettingsViewModel { get; }
}
}
Should I somehow do all in MainViewModel? I mean:
namespace MessagingCenterApp.ViewModels
{
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
this.SettingsViewModel = new SettingsViewModel();
this.HomeViewModel = new HomeViewModel(this.SettingsViewModel);
}
public SettingsViewModel SettingsViewModel { get; set; }
public HomeViewModel HomeViewModel { get; }
}
}
Then initialized it in AppShell? I could not get this approach working.
Important! I don't want to use any MVVM framework! Only native behaviour.
mvvmcross' Messenger is alleged to be "lighter weight" than X-Form's built-in Messaging Center.
I use mvvmcross Messenger by defining some helper methods in a "BasePage". Then each page inherits from "BasePage" rather than "ContentPage".
This automatically handles "unsubscribe" of each method. And makes it easier to manage mvvmcross' "subscription tokens".
BasePage.xaml.cs:
// If not using mvvmcross, this could inherit from ContentPage instead.
public class BasePage : MvxContentPage
{
protected readonly IMvxMessenger Messenger;
public BasePage()
{
this.Messenger = Mvx.IoCProvider.Resolve<IMvxMessenger>();
}
protected override void OnAppearing()
{
base.OnAppearing();
// Examples of subscribing to messages. Your subclasses of BasePage can also do this.
this.Subscribe<MyMessage1>(OnMyMessage1);
this.SubscribeOnMainThread<MyMessage2>(OnMyMessage2);
}
protected override void OnDisappearing()
{
UnsubscribeAll();
base.OnDisappearing();
}
#region Messenger Subscriptions
protected List<MvxSubscriptionToken> _subscriptions = new List<MvxSubscriptionToken>();
/// <summary>
/// Create subscription and add to "_subscriptions".
/// Call this from subclass' OnAppearing, once per subscription.
/// Automatically unsubscribed in OnDisappearing.
/// </summary>
/// <param name="token"></param>
/// <param name="msgType"></param>
protected void Subscribe<T>(Action<T> onMessage) where T : MvxMessage
{
var token = this.Messenger.Subscribe<T>(onMessage);
// Hold token to avoid GC of the subscription.
_subscriptions.Add(token);
}
protected void SubscribeOnMainThread<T>(Action<T> onMessage) where T : MvxMessage
{
var token = this.Messenger.SubscribeOnMainThread<T>(onMessage);
// Hold token to avoid GC of the subscription.
_subscriptions.Add(token);
}
/// <summary>
/// OnDisappearing calls this.
/// </summary>
private void UnsubscribeAll()
{
if (_subscriptions.Count > 0)
{
foreach (MvxSubscriptionToken token in _subscriptions)
{
// Per "https://www.mvvmcross.com/documentation/plugins/messenger", this is sufficient to Unsubscribe:
// "Subscriptions can be cancelled at any time using the Unsubscribe method on the IMvxMessenger or by calling Dispose() on the subscription token."
token.Dispose();
}
_subscriptions.Clear();
}
}
#endregion
}
For view models, class would be "BaseViewModel", that your view models inherit from. Contents similar to above, but different method names for Appearing/Disappearing.
BaseViewModel.cs:
public class BaseViewModel : MvxViewModel
{
...
// mvvmcross' MvxViewModel provides these.
protected override void ViewAppearing()
{
...
}
protected override void ViewDisappearing()
{
...
}
... Messenger Subscriptions methods ...
}
So, I have a statusbar as UserControl.
Model:
public class StatusBarModel : BindableBase
{
private string _status;
public string Status
{
get { return _status; }
set
{
_status = value;
RaisePropertyChanged("Status");
}
}
private int _p_value;
public int P_Value
{
get { return _p_value; }
set
{
_p_value = value;
RaisePropertyChanged("P_Value");
}
}
}
ViewModel:
public class StatusBarVM : BindableBase
{
readonly source.elements.StatusBar.StatusBarModel _model = new source.elements.StatusBar.StatusBarModel();
public StatusBarVM()
{
_model.PropertyChanged += (s, e) => { RaisePropertyChanged(e.PropertyName); };
}
public string Status
{
get { return _model.Status; }
set { _model.Status = value; }
}
public int P_Value
{
get { return _model.P_Value; }
set { _model.P_Value = value; }
}
}
And for example I wanna change Status variable from others ViewModels.
How I can do it? I have seen examples with only buttons and etc.
There are multiple ways to achieve your requirement. as #bitclicker says, you can use static class that hold its value. But I think It is too much that makes it static class, because that variable value may be used only two viewmodel.
I suggest you communicate between two view model. you will find Prism's event aggregator or you could implement your own event publish-subscriber model. making your own event pub-sub model would help you to make a first step into the design pattern.
You could create a static class to hold that value.
public static class Globals()
{
public static StatusBarModel GlobalStatus { get; set; }
}
Then whenever you want to alter it you just do
Globals.GlobalStatus.Status = "something";
Globals.GlobalStatus.P_Value = 14;
does that accomplish what you need?
I have a checkbox which is binded to a class variable in the xaml code:
<CheckBox x:Name="cbxUseBubbleNotifications" Margin="20" IsChecked="{Binding Path=pcdLoggerData.UseBubbleNotifications, Mode=TwoWay}" Content="_Use bubble notifications" HorizontalAlignment="Left" VerticalAlignment="Top" Style="{DynamicResource CheckboxSwitchStyle}" />
this should be supposed to be a two way binding but what happen is:
the checkbox is set to CHECKED ----> the var pcdLoggerData.UseBubbleNotifications is automatically OK
the class is serialized (through datacontract serialization but I think that doesn't change anything).
I restart the program and so the pcdLoggerData.UseBubbleNotifications is automatically set to true
4 the checkbox is not set to TRUE <----- ERROR
point 4 is not correct: since two way I expect to do that automatically.
My class is:
[DataContract]
public class PCDLoggerBinSerializableData
{
public PCDLoggerBinSerializableData() { }
public PCDLoggerBinSerializableData(string _languageInUse, bool _useBubbleNotifications)
{
LanguageInUse = _languageInUse;
UseBubbleNotifications = _useBubbleNotifications;
}
[DataMember]
public string LanguageInUse { get; set; }
[DataMember]
public bool UseBubbleNotifications { get; set; }
}
}
Even more important I have to set another variable according to the same value/variations of pcdLogger.UseBubbleNotifications and that is a STATIC var.
something like Bubble.NoBubbles = !pcdmisData.UseBubbleNotifications
So two problems:
databinding not TWO-WAY working (only one way)
how to databind also another static var?
Thanks
--ADD--
Not working I put breakpoints in all parts of the class and they never were it.
This is how I did it:
[DataContract]
public class PCDLoggerBinSerializableData: INotifyPropertyChanged
{
#region CONSTRUCTORS
public PCDLoggerBinSerializableData() { }
public PCDLoggerBinSerializableData(string _languageInUse, bool _useBubbleNotifications)
{
LanguageInUse = _languageInUse;
UseBubbleNotifications = _useBubbleNotifications;
}
#endregion
#region OPTIONS
[DataMember]
public string LanguageInUse { get; set; }
[DataMember]
private bool useBubbleNotifications;
public bool UseBubbleNotifications
{
get { return useBubbleNotifications; }
set
{
useBubbleNotifications = value;
Bubble.NoBubblesPlease = !useBubbleNotifications;
OnPropertyChange("UseBubbleNotifications");
}
}
#endregion
#region NOTIFIER
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChange(string inName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("inName"));
}
#endregion
}
It would be something like:
public bool UseBubbleNotifications
{
get
{
return useBubbleNotifications;
}
set
{
useBubbleNotifications = value;
Other_Static_Variable = value;
OnPropertyChange("UseBubbleNotifications");
}
}
public void OnPropertyChange(string inName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("inName"));
}
}
Something like this may work. Of course your class would have to inherit the INotifyPropertyChanged interface.
I have a C# WPF application built with Visual Studio 2015. I'm using MVVM and the Observer Pattern.
My Provider is a user control called 'ucClientFilter1ViewModel' that contains two text box controls where the user can search for a client(s):
namespace NSUCClientControls
{
public class ucClientFilter1ViewModel : ViewModelBase, IObservable<ClientFilterParameter>
{
private string filterLocation;
private string whereSearch1;
private string whereSearch2;
private List<IObserver<ClientFilterParameter>> observers;
public ucClientFilter1ViewModel()
{
observers = new List<IObserver<ClientFilterParameter>>();
}
public string FilterLocation
{
get { return filterLocation; }
set { filterLocation = value; }
}
public string WhereSearch1
{
get { return whereSearch1; }
set
{
whereSearch1 = value;
TestUpdateGrid(filterLocation);
}
}
public string WhereSearch2
{
get { return whereSearch2; }
set
{
whereSearch2 = value;
TestUpdateGrid(filterLocation);
}
}
private void TestUpdateGrid(string _filterLocation)
{
var filterInfo = new ClientFilterParameter(this);
foreach (var observer in observers)
{
observer.OnNext(filterInfo);
}
}
public IDisposable Subscribe(IObserver<ClientFilterParameter> observer)
{
// Check whether observer is already registered. If not, add it
if (!observers.Contains(observer))
{
observers.Add(observer);
// Provide observer with existing data
var filterInfo = new ClientFilterParameter(this);
observer.OnNext(filterInfo);
}
return new Unsubscriber<ClientFilterParameter>(observers, observer);
}
internal class Unsubscriber<ClientFilterParameter> : IDisposable
{
private IObserver<ClientFilterParameter> observer;
private List<IObserver<ClientFilterParameter>> observers;
public Unsubscriber(List<IObserver<ClientFilterParameter>> _observers, IObserver<ClientFilterParameter> _observer)
{
observers = _observers;
observer = _observer;
}
public void Dispose()
{
if (observers.Contains(observer))
{
observers.Remove(observer);
}
}
}
}
}
My Observer is a user control called 'ucClientGrid1ViewModel' that contains a datagrid where the search results are displayed.
namespace NSUCClientControls
{
public class ucClientGrid1ViewModel : ViewModelBase, IObserver<ClientFilterParameter>
{
private IDisposable cancellation;
private ObservableCollection<Client> clientsMultiple;
public ucClientGrid1ViewModel()
{
}
public ObservableCollection<Client> ClientsMultiple
{
get
{
var myClientDataAccess = new ClientDataAccess();
clientsMultiple = myClientDataAccess.GetClientListFromSQL_Test2();
return clientsMultiple;
}
set
{
}
}
public virtual void Subscribe(ucClientFilter1ViewModel provider)
{
cancellation = provider.Subscribe(this);
}
public void OnNext(ClientFilterParameter myFilter)
{
OnPropertyChanged("ClientsMultiple");
var myDummyWindow = new dummyWindow();
myDummyWindow.Show();
myDummyWindow.Close();
}
public void OnError(Exception error)
{
throw new NotImplementedException();
}
public void OnCompleted()
{
throw new NotImplementedException();
}
}
}
This all works and I get the search results that I am expecting. But what I don't understand is why the inclusion of the following lines actually speed things up!
var myDummyWindow = new dummyWindow();
myDummyWindow.Show();
myDummyWindow.Close();
I'm new to MVVM and the observer pattern, so as I was writing the code I had included message boxes at various points to help me to follow the flow of it. It was all working as expected. Then I removed the message boxes and it still worked but the application was pausing at the end before you could continue to keep searching.
Putting a message box back in at the end prevented this pause. Replacing the message box with a "DummyWindow" that just opens and closes has the same affect and prevents the pause at the end. This is what I currently have but I'd rather not leave this in there.
Presumably opening the window causes something else to happen which stops some redundant process, and this then prevents the pause? What else could I do to prevent the pause at the end, without using this DummyWindow?
I've tried searching on here and with Bing with no luck.
Thanks in advance!
Edit:
ViewModelBase...
namespace NSCommon
{
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
{
protected ViewModelBase()
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public void Dispose()
{
OnDispose();
}
protected virtual void OnDispose()
{
}
}
}
ClientFilterParameter...
namespace NSCommon
{
public class ClientFilterParameter
{
public ClientFilterParameter(ucClientFilter1ViewModel myFilter)
{
FilterLocation = myFilter.FilterLocation;
WhereSearch1 = myFilter.WhereSearch1;
WhereSearch2 = myFilter.WhereSearch2;
}
private string filterLocation;
private string whereSearch1;
private string whereSearch2;
public string FilterLocation
{
get { return filterLocation; }
set { filterLocation = value; }
}
public string WhereSearch1
{
get { return whereSearch1; }
set { whereSearch1 = value; }
}
public string WhereSearch2
{
get { return whereSearch2; }
set { whereSearch2 = value; }
}
}
}
I have the following classes:
public abstract class BaseGridViewModel
{
protected BaseGridViewModel()
{
Timer = new List<long>();
}
public List<long> Timer { get; set; }
}
public class CityReportViewModel : BaseGridViewModel
{
public IEnumerable<City.Grid> Grid { get; set; }
}
In my action code I am doing timing like this:
var vm = new CityReportViewModel();
var sw = Stopwatch.StartNew();
try {
//
vm.Timer.Add(sw.ElapsedMilliseconds);
//
vm.Timer.Add(sw.ElapsedMilliseconds);
//
// Lots more vm.Timer.Add lines ...
} catch (Exception e) {
log(e);
} finally {
sw.Stop();
}
Because I do a lot of timing I have the vm.Timer.Add code repeated many times in my controllers.
Is there some way I could simplify the timer coding by making a change to the ViewModels. The kind of thing I am thinking of is to set up the timer as a field in the ViewModels and then have some way of telling the ViewModel that I want to record a new timing event.
I'm not really sure about your requirement, but it sounds like you need an observer.
Your 'BaseGridViewModel' may register himself to an obeserver. The observer raises may an event to update all timers in all attached BaseGridViewModels.
Edit
This is what I'm talking about:
public class TimerUpdateService
{
public TimerUpdateService()
{
// May create here a Stopwatch to measure your time.
}
private EventHandler<EventArgs> _updater;
public void Register(BaseGridViewModel baseGridViewModel)
{
_updater += baseGridViewModel.timerUpdate_UpDateTimers;
}
public void Unregister(BaseGridViewModel baseGridViewModel)
{
_updater -= baseGridViewModel.timerUpdate_UpDateTimers;
}
/// <summary>
/// Call this method to refresh all timers on the registred 'BaseGridViewModel'.
/// </summary>
public void UpdateAllViewModels()
{
EventHandler<EventArgs> handler = _updater;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
public abstract class BaseGridViewModel
{
protected BaseGridViewModel()
{
Timer = new List<long>();
}
public void timerUpdate_UpDateTimers(object sender, EventArgs e)
{
Timer.Add(e.elapsed);
}
public List<long> Timer { get; set; }
}
public class CityReportViewModel : BaseGridViewModel
{
public IEnumerable<City.Grid> Grid { get; set; }
}
May this is what you need. The above code is only an example..