I have a user control with several buttons, which need to take different actions depending on the class using it.
The problem is that I don't know how to implement those handlers because when using my user control from the final app I don't have direct access to the buttons to specify which handler handles which events.
How would you do that?
Another way to do this is to expose the events through events in your UserControl :
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public event RoutedEventHandler Button1Click;
private void button1_Click(object sender, RoutedEventArgs e)
{
if (Button1Click != null) Button1Click(sender, e);
}
}
This gives your usercontrol a Button1Click event that hooks up to that button within your control.
I would create a command for each button and delegate for each "handler". Than you can expose delegates to the user (final app) and internally call them on Execute() method on the commands. Something like this:
public class MyControl : UserControl {
public ICommand FirstButtonCommand {
get;
set;
}
public ICommand SecondButtonCommand {
get;
set;
}
public Action OnExecuteFirst {
get;
set;
}
public Action OnExecuteSecond {
get;
set;
}
public MyControl() {
FirstButtonCommand = new MyCommand(OnExecuteFirst);
FirstButtonCommand = new MyCommand(OnExecuteSecond);
}
}
Of cource, "MyCommand" needs to implement ICommand. You also need to bind your commands to coresponding buttons. Hope this helps.
Related
I am trying to implement the MVP pattern into my WinForms project. However, the method 'Activate' in my Presenter that is subscribed to my 'ActivatedForm' event from my View, does not seem to fire when i load the form. I have tested it simply by printing someting in the 'Activate' method. Why is this not working properly?
I have posted my code below.
I think it has something to do with the fact that I am creating the Presenter with the concrete View even though the _view attribute is of the interface type 'IHomeScreenView'.
I know that the 'HomeScreenView_Activated' event occurs, because I have put a print in there too and that worked.
The 'ActivatedForm' event just always returns null there which means that nothing is subscribed to the event.
IHomeScreenView.cs
public interface IHomeScreenView
{
List<string> ExistingAssessments { get; set; }
event EventHandler<EventArgs> ActivatedForm;
event EventHandler<EventArgs> CreatingNewAssessment;
event EventHandler<EventArgs> AddingNewStandard;
event EventHandler<EventArgs> OpeningAssessment;
}
HomeScreenView.cs
public partial class HomeScreenView : Form, IHomeScreenView
{
private HomeScreenPresenter homeScreenPresenter;
public List<string> ExistingAssessments
{
get { return recentAssessments.Items.Cast<string>().ToList(); }
set { recentAssessments.DataSource = value; }
}
public event EventHandler<EventArgs> ActivatedForm;
public event EventHandler<EventArgs> CreatingNewAssessment;
public event EventHandler<EventArgs> AddingNewStandard;
public event EventHandler<EventArgs> OpeningAssessment;
// Initialize homescreen.
public HomeScreenView()
{
InitializeComponent();
}
// Fires the activating form event.
private void HomeScreenView_Activated(object sender, EventArgs e)
{
ActivatedForm?.Invoke(this, EventArgs.Empty);
}
HomeScreenPresenter.cs
public class HomeScreenPresenter
{
private IHomeScreenView _view;
private AssessmentsModel _assessmentsModel;
public HomeScreenPresenter(IHomeScreenView view)
{
_assessmentsModel = new AssessmentsModel();
_view = view;
_view.ActivatedForm += Activate;
_view.CreatingNewAssessment += CreateNewAssessment;
_view.AddingNewStandard += AddNewStandard;
_view.OpeningAssessment += OpenAssessment;
}
public void Activate(object sender, EventArgs e)
{
Debug.Print("hi");
HashSet<string> items = new HashSet<string>(_assessmentsModel.GetDataList("Assessments", "assessment_name"));
List<string> assessments = items.ToList();
_view.ExistingAssessments = assessments;
}
I hope someone can help, thank you.
The Form.Activated event is only fired when the form is visible. See the documentation.
When the application is active and has multiple forms, the active form is the form with the input focus. A form that is not visible cannot be the active form. The simplest way to activate a visible form is to click it or use an appropriate keyboard combination.
If your form is already visible when the presenter is being created, then the activated event has already fired. You could call Form.Activate() once the presenter is created and the eventhandler is hooked up.
Summary
I'm experimenting with the MVP pattern in a Windows Forms application.
I'd like to make both my Presenters and Views platform agnostic, so if I wish to port my application to another platform, say the Web or mobile, I simply need to implement the views with platform dependant GUI, and my presenters can still be platform independent.
And now I wonder, how to ShowDialog() using MVP and Passive Views?
To my understanding so far, passive views shouldn't know/care about any presenter. They don't even know it exists. So, the solution presented in this question's answer is not suitable, according to me:
Refactoring Form.ShowDialog() code to MVP
Some code samples to help on the understanding:
ApplicationView
public partial class ApplicationForm : Form, IApplicationView {
// I ensure that all the IApplicationView events are raised
// upon button clicks text changed, etc.
// The presenter, which this view ignores the existence,
// is subscribed to the events this view raises.
}
ApplicationPresenter
public class ApplicationPresenter
: Presenter<IApplicationView>
, IApplicationPresenter {
public ApplicationPresenter(IApplicationView view) : base(view) {
View.OnViewShown += OnViewShown;
}
public void OnViewShown() {
IAuthenticaitonView authView = new AuthenticationForm();
IAuthenticationPresenter authPresenter = new AuthenticationPresenter(authView);
authPresenter.ShowDialog(); // 1.
}
}
This is where I'm struggling. The ApplicationPresenter is like the master in the universer and may be aware of the user authentication through both the IAuthenticationView and IAuthenticationPresenter.
IAuthenticationView
public interface IAuthenticationView : IDialogView {
string ErrorMessage { get; set; }
string Instance { get; set; }
IEnumerable<string> Instances { get; set; }
string Login { get; set; }
string Password {get; set; }
void EnableConnectButton(bool enabled);
event VoidEventHandler OnConnect;
event SelectionChangedEventHandler OnDatabaseInstanceChanged;
event VoidEventHandler OnLoginChanged;
event VoidEventHandler OnPasswordChanged;
}
IDialogView
public interface IDialogView : IView {
void ShowDialog();
}
IView
public interface IView {
void Show();
event VoidEventHandler OnViewInitialize;
event VoidEventHandler OnViewLoad;
event VoidEventHandler OnViewShown;
}
IAuthenticationPresenter
public interface IAuthenticationPresenter : IPresenter<IAuthenticationView> {
void OnConnect();
void OnViewDatabaseInstanceChanged(SelectionChangedEventArgs e);
void OnViewLoginChanged();
void OnViewPasswordChanged();
}
IPresenter<V>
public interface IPresenter<V> where V : IView {
V View { get; }
OnViewInitialize();
OnViewLoad();
ShowView();
}
Based on these premisses:
The presenter shall be platform agnostic
Only the view knows how to show/display itself (WPF, Mobile, Silverlight, Web, WinForms...)
The view MUST provide a way to show itself
The view doesn't have to be platform agnostic, since the display will differ from a platform to another
But the presenter shall order the view when to show itself
I came to this:
IView
public interface IView {
void OnShowView();
}
IPresenter<V>
public interface IPresenter<V>where V : IView {
void ShowView();
event VoidEventHandler OnShowView;
}
Presenter<V>
public abstract class Presenter<V> : IPresenter<V> {
public Presenter(V view) {
View = view;
OnShowView += View.OnShowView;
}
public void ShowView() { raiseShowViewEvent(); }
public event VoidEventHandler OnShowView;
private void raiseShowViewEvent() { if (OnShowView != null) OnShowView(); }
}
So, following the logic of where I struggled so far, I solved it by doing this:
ApplicationForm
public partial class ApplicationForm : Form, IApplicationView {
private void ApplicationForm_Shown(object sender, EventArgs e) { raiseOnViewShown(); }
private void raiseOnViewShownEvent() { if (OnViewShown != null) OnViewShown(); }
}
ApplicationPresenter
public void OnViewShown() {
// This method is the subscriber of the IView.OnViewShown event
// The event is raised with the ApplicationForm_Shown event.
IAuthenticationView authView = new AuthenticationForm();
IAuthenticationPresenter authPresenter = new AuthenticationPresenter(authView);
authPresenter.ShowView(); // 1.
}
This raises the OnShowView event which the IAuthenticationView has subscribed. Then, back in the form, the view's response to the event is:
AuthenticationForm
public partial class AuthenticationForm : Form, IAuthenticationView {
public void OnShowView() { ShowDialog(); }
}
Then, the view shows itself as a dialog/modal window.
Can someone tell me how can I have a feature in my UserControl, that can let the host windowsform know what is the control is doing?
For example my usercontrol has a filebrowser, and if user uses this file browser to open a file I want in the statusstrip bar of my form to write "Loading file(s)".
Will this require using events? if so, how can I have a single event inside usercontrol to report anything it does (then I guess I have to call this event in all methods in the usercontrol).
Simple
Yes, expose an event on the user control that the Form can subscribe to. You should use the standard event pattern:
class MyUserControl : UserControl
{
public event EventHandler<EventArgs> FileOpened;
protected virtual void OnFileOpened(EventArgs e)
{
EventHandler<EventArgs> handler = FileOpened;
if (handler != null)
handler(this, e);
}
}
Then when the file is opened you call OnFileOpened(EventArgs.Empty) which fires the event.
With custom EventArgs
Now the Form probably needs to know what file was opened. You could expose a property on the user control that the Form can use to find out, or you can provide that information in your event like so:
public class FileOpenedEventArgs : EventArgs
{
private string filename;
public FileOpenedEventArgs(string filename)
{
this.filename = filename;
}
public string Filename { get { return filename; } }
}
class MyUserControl : UserControl
{
public event EventHandler<FileOpenedEventArgs> FileOpened;
protected virtual void OnFileOpened(FileOpenedEventArgs e)
{
EventHandler<FileOpenedEventArgs> handler = FileOpened;
if (handler != null)
handler(this, e);
}
}
Then you fire the event with OnFileOpened(new FileOpenedEventArgs(filename)).
Optimal
When you create an event handler public event delegate Name;, you are allocating storage for the delegate on your object. Objects (especially Controls) often have a huge number of events that are never subscribed to. That's a whole lot of allocated storage not being used. There's an optimization built into the framework in the form of a EventHandlerList. This handy object stores event handlers only when they are actually used. All System.Windows.Forms.Control objects derive from System.ComponentModel.Component and it already provides an (protected) EventHandlerList that you can access in your derived Control.
To use it, you first create a static object that uniquely identifies your event, and then you provide the add {} and remove {} methods manually. Like so:
class MyUserControl : UserControl
{
private static readonly object FileOpenedKey = new Object();
public event EventHandler<FileOpenedEventArgs> FileOpened
{
add { Events.AddHandler(FileOpenedKey, value); }
remove { Events.RemoveHandler(FileOpenedKey, value); }
}
protected virtual void OnFileOpened(FileOpenedEventArgs e)
{
var handler = (EventHandler<FileOpenedEventArgs>)Events[FileOpenedKey];
if (handler != null)
handler(this, e);
}
}
Yes, you will need to create an event and subscribe to it. One suggestion following the standard pattern for events:
enum ControlStatus {Idle, LoadingFile, ...}
class StatusChangedEventArgs : EventArgs
{
public ControlStatus Status {get; private set;}
public StatusChangedEventArgs(ControlStatus status)
: base()
{
this.Status = status;
}
}
partial class MyControl : UserControl
{
public ControlStatus Status {get; private set;}
public event EventHandler<StatusChangedEventArgs> StatusChanged;
protected virtual void OnStatusChanged(StatusChangedEventArgs e)
{
var hand = StatusChanged;
if(hand != null) hand(this, e);
}
void LoadFiles()
{
...
Status = ControlStatus.LoadingFiles;
OnStatusChanged(new StatusChangedEventArgs(this.Status));
...
Status = ControlStatus.Idle;
OnStatusChanged(new StatusChangedEventArgs(this.Status));
}
}
partial class MyHostWindowsForm : Form
{
public MyHostWindowsForm()
{
var ctl = new MyControl();
...
ctl.StatusChanged += ctl_StatusChanged;
}
void ctl_StatusChanged(object sender, StatusChangedEventArgs e)
{
switch(e.Status)
{
case ControlStatus.Idle:
statusStripBar.Text = null;
break;
case ControlStatus.LoadingFiles:
statusStripBar.Text = "Loading file(s)";
break;
...
}
}
}
I have a window that essentially runs a timer. When the timer hits 0 I want to bring the window to the front so that it is visible and not hidden behind some other application.
From what I can gather I would simply call window.activate() to accomplish this but with mvvm my view model doesn't have a reference to window.
A "purist" MVVM solution is to use a behavior. Below is a behavior for a Window with an Activated property. Setting the property to true will activate the window (and restore it if it is minimized):
public class ActivateBehavior : Behavior<Window> {
Boolean isActivated;
public static readonly DependencyProperty ActivatedProperty =
DependencyProperty.Register(
"Activated",
typeof(Boolean),
typeof(ActivateBehavior),
new PropertyMetadata(OnActivatedChanged)
);
public Boolean Activated {
get { return (Boolean) GetValue(ActivatedProperty); }
set { SetValue(ActivatedProperty, value); }
}
static void OnActivatedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) {
var behavior = (ActivateBehavior) dependencyObject;
if (!behavior.Activated || behavior.isActivated)
return;
// The Activated property is set to true but the Activated event (tracked by the
// isActivated field) hasn't been fired. Go ahead and activate the window.
if (behavior.AssociatedObject.WindowState == WindowState.Minimized)
behavior.AssociatedObject.WindowState = WindowState.Normal;
behavior.AssociatedObject.Activate();
}
protected override void OnAttached() {
AssociatedObject.Activated += OnActivated;
AssociatedObject.Deactivated += OnDeactivated;
}
protected override void OnDetaching() {
AssociatedObject.Activated -= OnActivated;
AssociatedObject.Deactivated -= OnDeactivated;
}
void OnActivated(Object sender, EventArgs eventArgs) {
this.isActivated = true;
Activated = true;
}
void OnDeactivated(Object sender, EventArgs eventArgs) {
this.isActivated = false;
Activated = false;
}
}
The behavior requires a reference to System.Windows.Interactivity.dll. Fortunately, this is now available on NuGet in the Blend.Interactivity.Wpf package.
The behavior is attached to a Window in XAML like this:
<Window ...>
<i:Interaction.Behaviors>
<Behaviors:ActivateBehavior Activated="{Binding Activated, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
The view-model should expose a boolean Activated property. Setting this property to true will activate the window (unless it is already activated). As an added bonus it will also restore a minimized window.
You could go about it in a couple of ways - adding a reference to the window could work since the viewmodel is not coupled with the view but related to it, but I don't really like that approach since it pretty much does couple your view to your viewmodel - which is not really the point of MVVM
A better approach may be to have your viewmodel raise an event or a command which the view can handle. This way the view gets to decide what UI action is associated with the command/event
e.g. simply
class SomeView
{
void HandleSomeCommandOrEvent()
{
this.Activate();
}
}
Of course how you wire this up is up to you but I'd probably try and get routed commands happening
Edit: You can't really 'bind' a simple event, since it's invoked from the viewmodel.
A simple event based example is just to add the event to the viewmodel and handle it directly ... e.g. imagine the following MainWindow with a ViewModel property
public partial class MainWindow : Window
{
MainWindowViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new MainWindowViewModel();
ViewModel.ShowMessage += ViewModel_ShowMessage;
this.DataContext = ViewModel;
}
void ViewModel_ShowMessage(object sender, ShowMessageEventArgs e)
{
MessageBox.Show(e.Message, "Some caption", MessageBoxButton.OK);
}
}
Then the ViewModel can just fire the event:
// The view model
public class MainWindowViewModel
{
// The button click command
public RelayCommand ButtonClickCommand { get; set; }
// The event to fire
public event EventHandler<ShowMessageEventArgs> ShowMessage;
public MainWindowViewModel()
{
ButtonClickCommand = new RelayCommand(ButtonClicked);
}
void ButtonClicked(object param)
{
// This button is wired up in the view as normal and fires the event
OnShowMessage("You clicked the button");
}
// Fire the event - it's up to the view to decide how to implement this event and show a message
void OnShowMessage(string message)
{
if (ShowMessage != null) ShowMessage(this, new ShowMessageEventArgs(message));
}
}
public class ShowMessageEventArgs : EventArgs
{
public string Message { get; private set; }
public ShowMessageEventArgs(string message)
{
Message = message;
}
}
The XAML would be:
<Button Command="{Binding ButtonClickCommand}">Click me!</Button>
So the button invokes the command, which in turn fires the event which the view (MainWindow) handles and shows a messagebox. This way the view/UI decides on the course of action based on the type of event raised. Of course it could be your timer which fired the event
You can always go down the more involved route such as some of the answers on this question...
How should the ViewModel close the form?
but to be honest, it depends if you really need it - a simple event works well - some people overcomplicate things for the sake of elegance, but at the detriment of simplicity and productivity!
I would go this way:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
// View
public partial class TestActivateWindow : Window
{
public TestActivateWindow() {
InitializeComponent();
Messenger.Default.Register<ActivateWindowMsg>(this, (msg) => Activate());
}
}
// View Model
public class MainViewModel: ViewModelBase
{
ICommand _activateChildWindowCommand;
public ICommand ActivateChildWindowCommand {
get {
return _activateChildWindowCommand?? (_activateChildWindowCommand = new RelayCommand(() => {
Messenger.Default.Send(new ActivateWindowMsg());
}));
}
}
}
public class ActivateWindowMsg
{
}
I have a custom set of UserControls: NavigationBar and NavigationItem.
I'd like that whenever the user clicks anywhere in the NavigationItem, an event is fired. I don't know how to set this up.
http://i.stack.imgur.com/ocP2D.jpg
I've tried this:
public partial class NavigationBar : UserControl
{
public NavigationBar()
{
InitializeComponent();
SetupEvents();
}
public List<NavigationItem> NavigationItems { private get; set; }
public NavigationItem SelectedItem { get; set; }
private void SetupEvents()
{
navigationItem1.Click += new EventHandler(navigationItemClick);
}
void navigationItemClick(object sender, EventArgs e)
{
MessageBox.Show("Clicked on " + sender.ToString());
}
}
But that event only fires when the user specifically clicks on the NavigationItem control, but not when he clicks on the picture or text. (Those are PictureBox and Label).
What would be the best course of action? I'd like to create something well, not hacky code. Thanks!
Put something like this into your class:
public event EventHandler NavigationItemClick;
This creates a new event in your class named NavigationItemClick. The form designer will even see it.
In your method navigationItemClick you can do this to call the event.
EventHandler handler = this.NavigationItemClick;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
It is important to save the event into the handler variable to avoid race conditions. EventHandler is a delegate, so you call it like a method, hence the line in the if statement. The if itself makes sure that someone has attached to your event.