I'm working on a Windows Forms app and I've come to a point where I can't understand what's happening.
I have something similar to an MVC architecture. Sometimes I want controls that belong to the view to stop listening to events. So inside the view code I've written a method that looks like this:
public void enableEventHandlers(bool enable)
{
if (enable)
{
control.someEvent += someEventHandler;
}
else
{
control.someEvent -= someEventHandler;
}
}
The thing is: when I want to remove an event handler I just call this method with false as a parameter.
If I call this method from inside the view code it works just fine. But if I call this method from inside the controller code, it doesn't work (the event handlers are not removed).
Just to give a little more context:
This works:
public partial class View : Form
{
public void enableEventHandlers(bool enable)
{
// The code I already showed
}
public void doSomething()
{
enableEventHandlers(false);
// do something
enableEventHandlers(true);
}
}
This doens't work:
public class controller
{
private View myView;
public void doSomething()
{
myView.enableEventHandlers(false);
// Do something... but somehow everything inside my Form is still paying attention to events
myView.enableEventHandlers(true);
}
}
Finally I found the problem. It seems that somehow I was attaching an event handler twice to the same Control. I couldn't find the exact line number where I was doing that anyway. The solution I found is to remove an event handler before adding a new one.
So the method enableEventHandlers looks now like this:
public void enableEventHandlers(bool enable) {
if (enable)
{
control.someEvent -= someEventHandler;
control.someEvent += someEventHandler;
}
else
{
control.someEvent -= someEventHandler;
}
}
Thanks for your answers.
I don't know if that's it but you didn't initialize your View. You just say "private View view", but that doesn't point to anywhere. You want to either make a new View by doing private View v = new View(), or let that view point to the view that you want to change the events.
Related
Am new to MVVMCross with xamarin.android so little struck up with a scenario. I have a fab and mvx.recyclerview inside a fragment. so when i click on this fab it will make the recyclerview scroll by a row.
i.e
void onclick(sender object ,eventargs e)
{
mrecyclerview.SmoothScrollToPosition(somevariable++); // do something.
}
this is breaking mvvm pattern, so is there any way or method in the MVVM Cross which i can use to listen back to the View from ViewModel.
fab.click binding with ICommand => viewmodel => view=> updatescroll().
thanks in advance.
Well, since the ViewModel should not know about the View, you should not call any method of it.
I would propose an event in your ViewModel where your View can subscribe. So you call your event something like FabClickDone and your view does what ever it wants, when this event occured. In your case scrolling.
Here is an code example for your ViewModel:
public delegate void FabClickDoneEvent(object sender, EventArgs args);
public event FabClickDoneEvent FabClickDone;
protected virtual void OnFabClickDone()
{
FabClickDone?.Invoke(this, EventArgs.Empty);
}
You then just call it by
void onclick(sender object , eventargs e)
{
// Do something inside your viewmodel
// ...
OnFabClickDone();
}
In your View constructor subscribe to this event:
ViewModel.FabClickDone += ViewModel_FabClickDone;
And create a method where you want to do your scrolling
void ViewModel_FabClickDone(Object sender, EventArgs e)
{
mrecyclerview.SmoothScrollToPosition(somevariable++); // do something.
}
Since you're using MVVMcross I would suggest you using a command, where you call OnFabClickDone();
I am having some problems with WPFs.
I have a project that has multiple windows, so to control this windows, I have created a controller class. This controller will have a instance of each windows:
this.mainWindow = new MainWindow();
this.loginWindow = new LoginWindow();
this.registerWindow = new RegisterWindow();
The problem comes when I callback from any of the windows to the controller class and from this controller I want to update the information of the window (for example update the value of a property), the information is not being updated
// In controller
public void login(String email, String pass)
{
....
this.loginWindow.showErrorInPassword();
}
// In LoginWindow
public void showErrorInPassword()
{
this.emailErrorImage.Visibility = System.Windows.Visibility.Visible;
}
... but if I send from the LoginWindow a reference of itself to the login function on the controller, the emailErrorImage will be shown
public void login(String email, String pass, LoginWindow lw)
{
....
lw.showErrorInPassword();
}
Seems that the instance that I have in the controller is not the same as the one that is being displayed when I do this.loginWindow.show()
Can someone tell me what am I doing wrong?
You are going to need to bind the UI objects to a MVVM class to update each window.
Use events to call back to the controller.
Here is a brief example. First create a class to contain event args. Doesn't really have to contain anything. It just differentiates between different delegates. Make it its own class in the namespace so everything has access to it.
public class SomeEventArgs: EventArgs
{
}
Inside the window class:
public event EventHandler<SomeEventArgs> CallBackToController;
protected virtual void OnCallBackEvent(object sender, SomeEventArgse)
{
EventHandler<SomeEventArgs> handle = CallBackToController;
if (handle != null)
{
handle(this, e);
}
}
In the controller class, after instantiating the window assign the event to a method.
this.loginWindow = new LoginWindow();
this.loginWindow.CallBackToController += new EventHandler<SomeEventArgs>(MethodToHandleEvent);
Then the Method must have the same form as expected:
private void MethodToHandleEvent(object sender, SomeEventArgs e)
{
// Do something in response.
}
Now anytime you call OnCallBackEvent(this, new SomeEventArgs()) from the window class, the controller class will catch the event and execute MethodToHandleEvent
For instance:
private void LoginWindowBtn_Click(object sender, RoutedEventArgs e)
{
// Logged in ok, let the controller know.
OnCallBackEvent(this, new SomeEventArgs ());
}
There are a ton of tutorials on this, I think this is a better approach to passing references of windows from window to window.
I have a UIControl(LatestHeadline) in a view(Home.xaml)(there are around 10 more controls on the same page) which is a textblock.I want to set the text of this control when user clicks on a button(named "Retrieve") from the same view.I have to call the method in utilities library from my viewmodel .The method(GetLatestHeadline) in the utilitieslibrary connects to a web api through webclient class and it fires the (deleagate)method Retrieve_Completed event.
Once this event is completed the required data is retrieved from eventargs paramater e.Now from the utilitieslibrary I want to return the data to the viewmodel so that the I can bind this to the LatestHeadline textblock.
LatestHeadline textblock has binding to a property(named "PropHeadLine") defined in viewmodel.
Is there a way we can achieve this in C#.Net?
If you have access to the utility class from the view model, then surely you can just add a handler to your Retrieve_Completed delegate in the view model.
utilityClass.Retrieve_Completed += UtilityClassRetrieve_Completed;
utilityClass.GetLatestHeadline();
...
public void UtilityClassRetrieve_Completed(EventArgs e)
{
// Do something with your e.New value here in the view model
LatestHeadlineProperty = e.New;
}
Of course, I'm just guessing at what your Retrieve_Completed definition is here, but hopefully you get the idea.
I like the concept of a "callback" for this kind of thing -- specifically, pass an Action<string> from the View Model to the service. The service can then save a reference to the callback, or pass it along to the completed-handler in the "UserState". So, the utilities method would look something like this:
public void GetLatestHeadline(Action<string> callback)
{
_apiClient.Retrieve(userState: callback);
}
private void Retrieve_Completed(object sender, RetrieveCompletedEventArgs args)
{
var callback = args.UserState as Action<string>;
if (callback != null)
callback(args.Result.Headline);
}
Now the view model can pass a callback to the utilities method -- the callback should presumably just set the property "PropHeadLine" to the result value:
private void Button_Click()
{
Utilities.GetLatestHeadline( result => PropHeadLine = result );
}
Expose a Delegate or Event in your service, in your view model just subscribe or hook to
that delegate or event
when ever you want to notify the viewmodel Once a particular operation is completed, just invoke the delegate or event
The method which is hooked in your viewmodel will get called.
In your viewmodel now you can do the necessary actions you want
Have been learning about MVP and have tried writing a test app using it in WinForms. I'm struggling to find a well explained example on how to navigate between my forms/views. As an example, the program starts and I want to show a login dialog then go into my main view if the login was successful. At the moment, my Main method looks something like this:
static void Main()
{
var loginView = Injector.Resolve<ILoginView>();
if (loginView.DoLogin() != LoginResult.OK) return;
var mainView = Injector.Resolve<IMainView>();
Application.Run(mainView); // won't work as mainView isn't a form
}
The Injector object is just a wrapper around an IoC tool (currently StructureMap). The thing is, I've read that I shouldn't really be manually creating instances via the Injector as they should really be done via constructor injection.
I've managed to do this up to a point but not when it comes to navigation. I can't think of an elegant way of moving through my views and was wondering if anyone here might shed some light on this? I've read a little on application controllers but have not found an example to show it clearly.
In regards to the navigation question:
I've managed to do this up to a point but not when it comes to
navigation. I can't think of an elegant way of moving through my views
and was wondering if anyone here might shed some light on this? I've
read a little on application controllers but have not found an example
to show it clearly.
Below is a simplified version of a construct I've used. Note that the setup and tear down hooks are called automatically when the NavigateTo method is called. Also, +1 to #AlexBurtsev, as his answer hints at this very same approach.
// Presenter can and should offer common services for the
// subclasses
abstract class Presenter
{
public void Display()
{
OnDisplay();
}
public void Dismiss()
{
OnDismiss();
}
protected virtual OnDisplay() // hook for subclass
{
}
protected virtual OnDismiss() // hook for subclass
{
}
private NavigationManager _navMgr;
internal NavigationMgr NavigationManager
{
get
{
return _navMgr;
}
set
{
_navMgr = value;
}
}
}
// NavigationManager is used to transition (or navigate)
// between views
class NavigationManager
{
Presenter _current;
// use this override if your Presenter are non-persistent (transient)
public void NavigateTo(Type nextPresenterType, object args)
{
Presenter nextPresenter = Activator.CreateInstance(nextPresenterType);
NavigateTo(nextPresenter);
}
// use this override if your Presenter are persistent (long-lived)
public void NavigateTo(Presenter nextPresenter, object args)
{
if (_current != null)
{
_current.Dismiss()
_current.NavigationMgr = null;
_current = null;
}
if (nextPresenter != null)
{
_current = nextPresenter;
_current.NavigationMgr = this;
_current.Display(args);
}
}
}
class MainMenuPresenter : Presenter
{
private IMainMenuView _mainMenuView = null;
// OnDisplay is your startup hook
protected override void OnDisplay()
{
// get your view from where ever (injection, etc)
_mainMenuView = GetView();
// configure your view
_mainMenuView.Title = GetMainTitleInCurrentLanguage();
// etc
// etc
// listen for relevent events from the view
_mainMenuView.NewWorkOrderSelected += new EventHandler(MainMenuView_NewWorkOrderSelected);
// display to the user
_mainMenuView.Show();
}
protected override void OnDismiss()
{
// cleanup
_mainMenuView.NewWorkOrderSelected -= new EventHandler(MainMenuView_NewWorkOrderSelected);
_mainMenuView.Close();
_mainMenuView = null;
}
// respond to the various view events
private void MainMenuView_NewWorkOrderSelected(object src, EventArgs e)
{
// example of transitioning to a new view here...
NavigationMgr.NavigateTo(NewWorkOrderPresenter, null);
}
}
class NewWorkOrderPresenter : Presenter
{
protected override void OnDisplay()
{
// get the view, configure it, listen for its events, and show it
}
protected override void OnDismiss()
{
// unlisten for events and release the view
}
}
I haven't used WinForms for a long time, but I'll try to answer this. I would use the same strategy as WPF Prism do.
About MainView and Application.Run:
Create a main Region (root Form), with empty container inside which can hold UserControl (I forgot exact class names), then when you need to switch root view, you do RootView.SetView(UserControl view) which will do something like Form.Clear(), Form.AddChild(view).
About the navigation and using container:
You could create a service for navigation: INavigationService which you inject in constructors with method like INavigationService.NavigateView(String(or Type) viewName, params object[] additionalData)
You can insert a method in mainView that returns the actual form.Then you can call
Mainview:IMainView
{
Form GetView()
{
//return new Form();
}
}
In Main you can call ,
Application.Run(mainView.GetView())
I'm using MvvmCross for my application and starting with iPhone. To achieve the sophistication on navigating between views using RequestNavigate and Close I've derived a new presenter from MvxBaseTouchViewPresenter. So far so good.
The problem I'm having now is that it is possible to close or show a view while the previously visible view is still in transition, either opening or closing. The only way I could think of to work round this problem was to use the ViewDidAppear and ViewDidDisappear event handlers of view controllers to call an Action that does the showing of the next view thereby deferring the showing until after the previous view has finished its transition.
This means I have to add:
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
DoPostTransitionAction();
}
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
DoPostTransitionAction();
}
private void DoPostTransitionAction()
{
if( _postTransitionAction != null)_postTransitionAction();
}
private Action _postTransitionAction;
public void ShowActionAfterTransition( Action postTransitionAction)
{
if( this.IsBeingDismissed || this.IsBeingPresented)
{
_postTransitionAction = postTransitionAction;
}
else
{
_postTransitionAction = null;
postTransitionAction();
}
}
to view controllers, introduce an IEventViewController interface to make me add the code and change the Show method of the presenter to be:
public override void Show(MvxShowViewModelRequest request)
{
// find the current top view as it will disappear as a result of
// showing the requested view
// note: this will be null for the first show request
var topViewController = TopViewController;
// if the request is to roll back an existing stack of views
// then rollback to the bottom of the stack
if (request.ClearTop && topViewController != null)
{
RollBackStack(topViewController);
}
// the top view is about to be 'covered' by the view requested
// if the top view is transitioning (appearing or disappearing)
// then wait for it to finish before showing the view requested
var disappearingViewController = topViewController as IEventViewController;
// if the top view is disappearing and it's of the 'event' type
// delay calling Show until after the view has disappeared
if( disappearingViewController != null)
{
// the top view has been requested to close and will fire the
// 'transition complete' event when it has
// register the action to perform when the top view has disappeared
disappearingViewController.ShowActionAfterTransition (() =>
{
Show (CreateView(request), true);
});
}
else
{
// show the view now as the top view is either not closing
// or, if it is, is not going to say when it does
// create a view controller of the type requested
Show(CreateView(request), true);
}
}
What I'd really like to have would be those event handlers in the base class. I wonder what are the chances without me setting myself adrift from the main development path? Partial classes might do it?