create event for notifying completed in another event - c#

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");

Related

Is Threading Communication Needed Here?

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
}));

How to pass EventArgs from View to Presenter in MVP?

I have an application based on MVP, WinForms and EntityFramework.
At one form I need to validate cell value, but I don't know proper way to pass EventArgs from Validating event of DataGridView to my presenter.
I have this Form (unrelated code omitted):
public partial class ChargeLinePropertiesForm : Form, IChargeLinePropertiesView
{
public event Action CellValidating;
public ChargeLinePropertiesForm()
{
InitializeComponent();
dgBudget.CellValidating += (send, args) => Invoke(CellValidating);
}
private void Invoke(Action action)
{
if (action != null) action();
}
public DataGridView BudgetDataGrid
{
get { return dgBudget; }
}
}
Interface:
public interface IChargeLinePropertiesView:IView
{
event Action CellValidating;
DataGridView BudgetDataGrid { get; }
}
And this presenter:
public class ChargeLinePropertiesPresenter : BasePresenter<IChargeLinePropertiesView, ArgumentClass>
{
public ChargeLinePropertiesPresenter(IApplicationController controller, IChargeLinePropertiesView view)
: base(controller, view)
{
View.CellValidating += View_CellValidating;
}
void View_CellValidating()
{
//I need to validate cell here based on dgBudget.CellValidating EventArgs
//but how to pass it here from View?
//typeof(e) == DataGridViewCellValidatingEventArgs
//pseudoCode mode on
if (e.FormattedValue.ToString() == "Bad")
{
View.BudgetDataGrid.Rows[e.RowIndex].ErrorText =
"Bad Value";
e.Cancel = true;
}
//pseudoCode mode off
}
}
Yes, I could expose a property through interface and set my EventArgs to this property in View to get them from Presenter, but this is ugly, isn't it?
public interface IChargeLinePropertiesView:IView
{
event Action CellValidating;
// etc..
}
Using Action is the problem here, it is the wrong delegate type. It doesn't permit passing any arguments. More than one way to solve this problem, you could use Action<CancelEventArgs> for example. But the logical choice is to use the same delegate type that the Validating event uses:
event CancelEventHandler CellValidating;
Now it is easy. In your form:
public event CancelEventHandler CellValidating;
public ChargeLinePropertiesForm() {
InitializeComponent();
dgBudget.CellValidating += (sender, cea) => {
var handler = CellValidating;
if (handler != null) handler(sender, cea);
};
}
In your presenter:
void View_CellValidating(object sender, CancelEventArgs e)
{
//...
if (nothappy) e.Cancel = true;
}

Windows Phone back button not being handled

I have registered a handler with the HardwareButtons.BackPressed event, performed some logic and then set the handled property in the args to true if it applies. The handler runs through without any issue, and the Handled property gets set. The phone still navigates back outside of the app. Am I misunderstanding how to use the event?
Page
public sealed partial class FirstRunPage : VisualStateAwarePage
{
public FirstRunPage()
{
this.InitializeComponent();
#if WINDOWS_PHONE_APP
HardwareButtons.BackPressed += (sender, args) =>
{
bool isHandled = false;
Action handledCallback = () => isHandled = true;
var state = new Dictionary<string, object> { { "Callback", handledCallback } };
((INavigationAware)this.DataContext).OnNavigatedTo("Back", NavigationMode.Back, state);
args.Handled = isHandled;
};
#endif
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
}
}
View model.
public override void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewModelState)
{
if (navigationParameter == null || !navigationParameter.ToString().Equals("Back"))
{
return;
}
if (!viewModelState.ContainsKey("Callback"))
{
return;
}
var callback = (Action)viewModelState["Callback"];
// If the user is new, then we set it to false and invoke our callback.
if (this.IsNewUser)
{
this.IsNewUser = false;
callback();
}
else
{
return;
}
}
Update
I have modified my FirstRunPage to subscribe and unsubscribe as recommended by #Martin but it still closes the app.
public sealed partial class FirstRunPage : VisualStateAwarePage
{
public FirstRunPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
#if WINDOWS_PHONE_APP
HardwareButtons.BackPressed += HardwareBack_OnPressed;
#endif
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
#if WINDOWS_PHONE_APP
HardwareButtons.BackPressed -= HardwareBack_OnPressed;
#endif
}
private void HardwareBack_OnPressed(object sender, BackPressedEventArgs e)
{
Action handledCallback = () => e.Handled = true;
var state = new Dictionary<string, object> { { "Callback", handledCallback } };
((INavigationAware)this.DataContext).OnNavigatedTo("Back", NavigationMode.Back, state);
}
}
With the help of #yasen I was able to get this resolved. The issue stems from the fact that the Prism library has your App.xaml.cs inherit from MvvmAppbase, which intercepts the BackPressed event.
To resolve this, I overrode the MvvmAppBase OnHardwareButtonsBackPressed and added a bit of logic to handle it.
My view model and view both implement a new interface called INavigateBackwards and they're used like this:
View Model
public bool CanNavigateBack()
{
// If the new user is true, then we can't navigate backwards.
// There isn't any navigation stack, so the app will die.
bool canNavigate = !this.IsNewUser;
// Disable the new user mode.
this.IsNewUser = false;
// Return so that the view can return to it's sign-in state.
return canNavigate;
}
View
public sealed partial class FirstRunPage : VisualStateAwarePage, INavigateBackwards
{
private INavigateBackwards ViewModel
{
get
{
return (INavigateBackwards)this.DataContext;
}
}
public FirstRunPage()
{
this.InitializeComponent();
}
public bool CanNavigateBack()
{
return ViewModel.CanNavigateBack();
}
}
Then in the MvvmAppBase subclass, I determine if I need to handle the navigation or not.
MvvmAppBase child
protected override void OnHardwareButtonsBackPressed(object sender, BackPressedEventArgs e)
{
var page = (Page)((Frame)Window.Current.Content).Content;
if (page is INavigateBackwards)
{
var navigatingPage = (INavigateBackwards)page;
if (!navigatingPage.CanNavigateBack())
{
e.Handled = true;
return;
}
}
base.OnHardwareButtonsBackPressed(sender, e);
}
This allows my single view to have multiple states and the user to navigate back from one state to the previous without navigating to an entirely new view.
The reason why your application closes is that the same handler is called more than just once. First handler sets the Handled property to true, but any other subsequent call for the same event fire sets it back to false.
To illustrate it, try this:
public sealed partial class FirstRunPage : VisualStateAwarePage
{
public FirstRunPage()
{
// ...
InitializeComponent();
HardwareButtons.BackPressed += HardwareButtons_BackPressed;
HardwareButtons.BackPressed += HardwareButtons_BackPressed;
}
void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)
{
bool isHandled = false;
Action handledCallback = () => isHandled = true;
var state = new Dictionary<string, object> { { "Callback", handledCallback } };
((INavigationAware)this.DataContext).OnNavigatedTo("Back", NavigationMode.Back, state);
args.Handled = isHandled;
};
}
And set breakpoint to last line of the handler code.
To avoid it, assign your handler in the OnNavigatedTo method of your FirstRunPage, and unregister the handler in OnNavigatedFrom.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
HardwareButtons.BackPressed += HardwareButtons_BackPressed;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
HardwareButtons.BackPressed -= HardwareButtons_BackPressed;
}

ICollectionView's CurrentChanged event not working if we create full property

when we create auto-property of ICollectionView then CurrentChanged event is working properly after refreshing Employee collection.
public ICollectionView EmployeeCollectionView{get; set; }
public EmployeeMasterViewModel(IEmployeeMasterView view, IUnityContainer container)
{
GetEmployee();
EmployeeCollectionView.CurrentChanged += new EventHandler(EmployeeCollectionView_CurrentChanged);
}
And when we create full-property then CurrentChanged event is not working.
private ICollectionView _employeeCollectionView;
public ICollectionView EmployeeCollectionView
{
get { return _employeeCollectionView; }
set { _employeeCollectionView = value; OnPropertyChanged("EmployeeCollectionView");}
}
public EmployeeMasterViewModel(IEmployeeMasterView view, IUnityContainer container)
{
GetEmployee();
EmployeeCollectionView.CurrentChanged += new EventHandler(EmployeeCollectionView_CurrentChanged);
}
void EmployeeCollectionView_CurrentChanged(object sender, EventArgs e)
{
var currentEmployee = EmployeeCollectionView.CurrentItem as EmployeeMaster;
}
please suggest if i missing something.
Have you bind EmployeeCollectionView_CurrentChanged event after refreshing employee collection? because if you refreshing employee collection then EmployeeCollectionView_CurrentChanged connection has been lost.
like-
private void Refresh()
{
GetEmployee();
EmployeeCollectionView.CurrentChanged += new EventHandler(EmployeeCollectionView_CurrentChanged);
}
If you expect EmployeeCollectionView to change (which seems so, otherwise you would not need an OnPropertyChanged, I'd recommend to add the Events in the setter of your property like following:
private ICollectionView _employeeCollectionView;
public ICollectionView EmployeeCollectionView
{
get { return _employeeCollectionView; }
set
{
if (_employeeCollectionView != value)
{
if (_employeeCollectionView != null)
{
_employeeCollectionView.CollectionChanged -= EmployeeCollectionView_CurrentChanged;
}
_employeeCollectionView = value;
_employeeCollectionView.CollectionChanged += EmployeeCollectionView_CurrentChanged;
OnPropertyChanged("EmployeeCollectionView");
}
}
}
public EmployeeMasterViewModel(IEmployeeMasterView view, IUnityContainer container)
{
GetEmployee();
}
private void EmployeeCollectionView_CurrentChanged(object sender, EventArgs e)
{
var currentEmployee = EmployeeCollectionView.CurrentItem as EmployeeMaster;
}

How do I properly implement a model-view-presenter with passive in C# Winforms?

I'm studying design patterns right now, I'm fairly new to this model-view-presenter, although I have already experience in asp.net mvc I'm trying to do an implementation of mvp in winforms.
The string in the textbox will be sorted with an algorithm based on the combobox. Right now when I click the button it throws a null reference exception
Here is the UI:
Here are my classes and codes:
class FormPresenter
{
private ISortingView _view;
private string _algorithm;
private StringToSortModel sortMe = new StringToSortModel();
public FormPresenter(ISortingView view)
{
_view = view;
_view.sortTheString += view_sortString;
sortMe.sortThis = view.stringToSort;
_algorithm = _view.algorithm;
//Algorithm = view.stringToSort;
//sortingform.sortTheString += (obj
}
private void view_sortString(object sender, EventArgs e)
{
SortContext context = new SortContext();
_view.sortedText = context.Sort(sortMe.sortThis.ToCharArray());
}
}
interface ISortingView
{
event EventHandler sortTheString;
string stringToSort { get; }
string algorithm { get; }
string sortedText { get; set; }
}
public partial class SortingForm : Form, ISortingView
{
public SortingForm()
{
InitializeComponent();
comboBox1.Items.Add("Bubble Sort");
comboBox1.Items.Add("Insertion Sort");
comboBox1.SelectedItem = "Bubble Sort";
textBox1.Text = "Emiri";
}
public event EventHandler sortTheString;
public string algorithm { get { return comboBox1.SelectedItem.ToString(); } }
public string stringToSort { get { return textBox1.Text; } }
public string sortedText { get { return label2.Text; } set { label2.Text = value; } }
private void Form1_Load(object sender, EventArgs e)
{
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
//char[] x = textBox1.Text.ToCharArray();
//SortContext con = new SortContext();
//con.SetSortStrategy(new InsertionSort());
//label2.Text = con.Sort(x);
//if(sortString != null)
//{
//this prodcues a null exception error
sortTheString(sender, e);
//}
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var mainForm = new SortingForm();
var presenter = new FormPresenter(mainForm);
Application.Run(new SortingForm());
}
}
I have not included the codes for the model and the classes the contains the sorting functions to keep this post short. The problem I have is that when button is clicked it throws a null reference exception error, something that I have been stuck on for hours already.
Sir/Ma'am your answers would be of great help. Thank you++
Your null is coming from this line
sortTheString(sender, e);
because you are not using the same form instance in your Presenter. Change to this in your main...
Application.Run(mainForm);
The event handler does not have any subscribers (because of the Application.Run(new SortingForm()); C# will treat that as null rather than an empty subscriber list.
ISortingView mainForm = new SortingForm();
var presenter = new FormPresenter(mainForm);
Application.Run(mainForm as Form);

Categories