Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WinForms Designer breaks when child control uses WhenActivated #1016

Closed
shiftkey opened this issue Jan 7, 2016 · 13 comments · Fixed by #1651
Closed

WinForms Designer breaks when child control uses WhenActivated #1016

shiftkey opened this issue Jan 7, 2016 · 13 comments · Fixed by #1651

Comments

@shiftkey
Copy link
Contributor

shiftkey commented Jan 7, 2016

Note: I haven't tested this on anything before VS2015 Update 1.

Repro:

git clone https://github.com/shiftkey/reactiveui-winforms-example.git
git checkout 3d5ac4580b64dea7e65d771119c9b53a28e40fca

Then open the WindowsFormsApplication1.sln solution in Visual Studio, build the project and open the Form1 design view. You should see something like this:

screen shot 2016-01-07 at 8 50 17 pm

THIS IS IMPORTANT I THINK: The Don't know how to detect when WindowsFormsApplication1.CustomControl is activated/deactivated, you may need to implement IActivationForViewFetcher error can be traced to this code. I'm not really across everything that's happening when RxUI is loaded in a design time context - I'd love any help if others know more...

The stack trace is all kinds of boring:

at ReactiveUI.ViewForMixins.WhenActivated(IActivatable This, Func`1 block, IViewFor view)
at ReactiveUI.ViewForMixins.WhenActivated(IActivatable This, Action`1 block, IViewFor view)
at ReactiveUI.ViewForMixins.WhenActivated(IActivatable This, Action`1 block)
at WindowsFormsApplication1.CustomControl..ctor() in C:\Users\shiftkey\Documents\Visual Studio 2015\Projects\WindowsFormsApplication1\WindowsFormsApplication1\CustomControl.cs:line 20 

As soon as I saw this I felt myself dragged back to the distant past, when I cared about the design time experience of WPF libraries and found myself immensely frustrated by all the rough edges that resulted when you tried to play the game. So I ran the other way.

So we have a top-level form that uses WhenActivated - it doesn't have an issue in the design view - and it hosts a child UserControl which also uses `WhenActivated - but it triggers the error.

I'm logging this for a couple of reasons:

  • unless the rest of the core team has objections, I'd love to encourage WhenActivated usage as a Good Thing To Do
  • WhenActivated seems to break for nested controls only - not surprising, maybe just a clue more than anything
  • the design time experience is crucial for WinForms developers - if ReactiveUI is interfering with non-trivial layouts, it reflects badly on ReactiveUI

I've not got the full picture on how much work is involved with fixing this, but given we keep getting WinForms questions I'd love to help make our story about it better - and this was something I found fairly early in my travels...

@kentcb
Copy link
Contributor

kentcb commented Jan 8, 2016

There are two problems working in tandem to create this experience:

  1. The Winforms designer does not execute the constructor for the top-level control. However, it does execute constructors for child controls. Hence, opening the child control in the designer doesn't execute any WhenActivated logic, but opening the main form executes the child control's WhenActivated logic (but not the main form's WhenActivated logic). Even worse, the DesignMode property, which might have provided a simple workaround, appears to be false for child controls opened in the designer :/ Splat's InDesignMode method also returns false.
  2. Winforms registrations are not being picked up at design time, so any call to WhenActivated against Winforms components will fail

If we decide that WhenActivated logic should not execute in design mode (certainly that's my current feeling), there are some hacky workarounds that we could perhaps incorporate into Splat. I tried shoving this directly in my repro and it seems to work.

If we decide that WhenActivated logic should execute in design mode, we'd need to ensure the registrations are being picked up.

@shiftkey
Copy link
Contributor Author

shiftkey commented Jan 8, 2016

@kentcb thanks for looking into this as well - I didn't anticipate an easy solution for this 😢

@kentcb
Copy link
Contributor

kentcb commented Jan 8, 2016

Hmm, in attempting to solve a different issue, I may have found a reasonable workaround for this problem too.

I wanted to define base classes for ReactiveUserControl<TViewModel> and ReactiveForm<TViewModel>. This is entirely possible, but the designer doesn't like a generic base class (because per the above discussion, it does not construct the type being designed, but rather the super type of the type being designed). To workaround this, one must define a subclass specifically for the designer:

public class MainFormDesignable : ReactiveForm<MainViewModel>
{
}

public class MainForm : MainFormDesignable
{
    public MainForm()
    {
        this.WhenActivated(...);
    }
}

As a consequence of this, the designer does not execute the MainForm constructor - only the MainFormDesignable constructor!

I can't even the Winforms designer, but this does appear to work. You can find my repro at https://github.com/kentcb/winforms-rxui-repro.

@goliat43
Copy link

This is a age old and "well known" bug in WinForms.
https://support.microsoft.com/en-us/kb/839202

I would recommend working around it by adding static property "IsRunning" in Program class (default value false), setting it to true in Main() method before entering Run loop and then checking that static property instead of broken designer property.

Of course many similar solutions can be devised...

@shiftkey
Copy link
Contributor Author

@goliat43 so this is something that needs to be fixed in the application, rather than something we can address in ReactiveUI? 😢

@goliat43
Copy link

Ah. Perhaps a bit too quick to post. I read this report as a "my app isn't working" and just pointed out the quickest route to fix it.

Please note that my experience with ReactiveUI so far is very close to zero so take the speculations below for what they are.. Speculations :)

The root of the problem is that WhenActivated depends on external state (via activationFetcherCache) and throws an exception if that isn't initialized, unfortunately I'm not familiar enough with ReactiveUI to determine if this is the only (and best) way to do it.

I think this would work if it would fail silently instead but doesn't feel like a perfect solution to me. Perhaps it would be possible to have a IVewForWinforms<> that implements a different IActivatable (IActivatableWinforms) and thereby binds to another implementation of Activate that can fail silently? This would still have the drawback of possible silent failures, but would at least limit them to WinForms..

So it certainly looks possible to work out on ReactiveUI side as far as I can see but question is if this narrow case that most WinForms devs will already have some mechanism to guard against (static IsRunning, using bool designMode = LicenseManager.UsageMode == LicenseUsageMode.Designtime and similar) is worth the work?

The obvious limitation of not fixing as I see it is that WhenActivated won't be very usable for someone that distributes CustomControls, but to be honest, who does that these days except for established firms like DevExpress et. al. who still are very unlikely to depend on external (non standard MS) stuff. For me, when I write WinForms code and notice the designer breaking I put a if(IsRunning) on reflex. Perhaps this isn't the best way but it works and I have seen plenty of controls doing the same..

@ghuntley
Copy link
Member

ghuntley commented May 21, 2017

Generics & Designer

Did a couple hours of research into this tonight and blew right through the timebox and then some reading so much old technical content; sometimes via internet archive. The pattern Kent stumbled upon was brought up many times as being the path forward.

public class MainFormDesignable : ReactiveForm<MainViewModel>
{
}

public class MainForm : MainFormDesignable
{
    public MainForm()
    {
        this.WhenActivated(...);
    }
}

I'm happy with recommending via documentation and in the samples that users create these wrappers as it appears to be in line with what the rest of the ecosystem did. There was a Microsoft Connect bug opened re: generics and the designer and it was closed as WONTFIX.

This article is a good read - might be able to use the TypeDescriptionProviderAttribute where you can give the designer a replacement class that it should instantiate instead.

http://www.pocketsilicon.com/post/Using-Visual-Studio-Whidbey-to-Design-Abstract-Forms

Design Mode Detection

As for detecting if in design mode; this was a good read @ https://stackoverflow.com/questions/34664/designmode-with-controls

@ghuntley
Copy link
Member

ghuntley commented May 21, 2017

I'm finishing up some "official" samples for RxUI that will live alongside the in the main repo. Pretty keen to ship ReactiveUserControl and ReactiveForm. What's outstanding however is how to handle WhenActivated and make mode detection less hacky.

@natalie-o-perret
Copy link

natalie-o-perret commented Aug 22, 2017

@ghuntley any news?
I am thinking maybe a design based on attributes + reflections instead of interfaces for the views would have been a better choice for the Winforms platform (although I do understand the need to keep the API consistent and UI agnostic).
Another solution would be a sort of factory based on a given form and heavy reflection to make it compliant with the API, not as straightforward but that would keep the rest of the API consistent, I am not a big fan of creating intermediate types (or the TypeDescriptor trick) just for the sake of the designer and I would rather like to see this hidden away via a factory.

@ghuntley
Copy link
Member

@ehouarn-perret looking for someone to take lead on polishing up reactiveui-winforms. My preference is to keep the API consistent and UI agnostic. I've sent you a slack invitation - drop on by #reactiveui-winforms.

@Asesjix
Copy link
Contributor

Asesjix commented May 12, 2018

The protected property DesignMode in each control can be used to check whether the designer is currently running. In my PR, I added a check to the ActivationForViewFetcher class.
I know the check with the LicenseManager is a bit dirty, but it's the only way I know to check it outside of a Component in a fast way.

@Asesjix
Copy link
Contributor

Asesjix commented May 19, 2018

Sorry but I have to reopen that... but fix comes in 5 minutes.

@Asesjix Asesjix reopened this May 19, 2018
@Asesjix Asesjix mentioned this issue May 19, 2018
2 tasks
@Asesjix
Copy link
Contributor

Asesjix commented May 19, 2018

The DependencyResolverMixins did not register the ActivationForViewFetcher during the designtime, because the version of the specified assembly did not match.

@glennawatson glennawatson added this to the vNext milestone Jul 21, 2018
glennawatson pushed a commit that referenced this issue Mar 23, 2019
@lock lock bot added the outdated label Jun 25, 2019
@lock lock bot locked and limited conversation to collaborators Jun 25, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants