Create the windows of the samples from the container instead of with new() - #870
Merged
Merged
Conversation
…gs with The forms of the samples were created by the container, but everything they opened was not: a control called `new SomeDlg()` and then handed the dialog the telemetry, the configuration and whatever else it needed through the arguments of ShowDialog. The dependencies of a window were therefore the business of whoever happened to open it. Opc.Ua.Samples.WinForms carries the plumbing: `IWindowFactory` creates a window from the container - constructor parameters the caller does not pass are resolved as services - and hands itself to what it created, so a dialog can open the next one. `SampleForm` and `SampleUserControl` expose that factory as `Windows`; the Windows Forms designer writes a parameterless constructor call for the controls of a form, so they are handed the factory by the form the container created rather than taking it themselves. With that in place the client controls, the sample controls and the server controls take their telemetry through their constructors and the show methods lost the parameter. `ServerForm` gained a single container constructor and `ServerFormOptions`, which replaces `ServerForm.Create` and the two hand written provider factories the reference server and the global discovery server had. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…he container The dialogs of the global discovery client took their telemetry and their logger as arguments of ShowDialog and were created with new(); they now take them through their constructors and come out of the window factory. The sample client form and the client form it is built on lost the ServiceMessageContext and the master form from their constructors - the first is derived from the configuration, the second is what OpenForm sets on the window it opens - so the container can create them and Program.cs no longer has a factory of its own. The message box the stack asks its questions through was a class copied into the client controls and the server controls and installed by hand in 45 entry points. It is a registration now, and the entry point helper hands it to the application instance before the host starts. The Windows Forms tests build their forms and dialogs through the same factory, so what they cover is the wiring of the samples and not a second copy of it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The workshop clients kept the last hand rolled window construction: a sample dialog was newed up and handed the telemetry through ShowDialog. They take it through their constructors now and come out of the window factory, which leaves the client controls, the sample controls, the server controls, the global discovery client and the workshop clients all creating their windows the same way. A control which is not below a window the container created now says so - WindowServices.RequireWindows - instead of failing with a null reference on the first dialog it opens. The three tier 2 tests which assemble a form out of single controls hand them a factory of their own through TestWindowFactory, so what they drive is the wiring of the sample rather than an exception. All five test tiers pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…built The reference client had no composition root, so the listener its Server menu arms was built out of the fields of the window. AddReferenceClient() registers it, the window asks the container for one per wait, and tier 2 covers that registration like every other client's. The four Windows Forms solutions carry the new library, and the documentation says what it is for: a README next to it on the factory and on why controls are handed it rather than taking it, the hosting README on what Run<TMainForm> now does, and the testing notes on why a tier 2 form has to come out of the factory. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Around a hundred CA2000 suppressions justified a dialog which was newed up and
never disposed. The window is created by the container now, the analyser does
not flag those lines, and the justification ("ownership is transferred to the
control owner") no longer describes what happens - so they go.
With every entry point on Run<TMainForm>, the overload which took a factory
delegate has no caller left and is private. The four form classes the earlier
commits missed - the reference client window, the sample server window and its
diagnostics control, and the reference server's event log dialog - are on the
sample base classes like the rest, and ExceptionDlg says why it is the one
window the container does not create.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #813.
The problem
A sample form was created by the container, but everything it opened was not. A control
called
new SomeDlg()and then handed the dialog what it needed through the arguments ofShowDialog:The dependencies of a window were therefore the business of whoever opened it, which is why
ITelemetryContext,ApplicationConfigurationandILoggerwere threaded through nearlyevery show method in the repository - 212 construction sites across 185 form and control
classes.
What this does
Samples/WinForms.Common(Opc.Ua.Samples.WinForms) carries the plumbing:IWindowFactory.Create<TWindow>()builds a window out of the container - what the callerpasses is matched to the constructor by type, the rest is resolved as services - and hands
itself to what it created, so a dialog can open the next one.
The Windows Forms designer writes
new SomeCtrl()into the generated code of a form, so acontrol cannot take the factory through its constructor.
SampleFormandSampleUserControlexpose it asWindows, and the form the container created hands it downits control tree once; a control parented later finds it by walking up. A control on neither
says so rather than failing with a null reference. See
the README.
With that in place:
Opc.Ua.ClientControls,Opc.Ua.SampleControls,Opc.Ua.ServerControls, the globaldiscovery client controls and every sample and Workshop application create their windows
through the factory, and their dialogs take the telemetry, the configuration and their
logger through their constructors.
ServerFormhas one container constructor plusServerFormOptions, which replacesServerForm.Create(IServiceProvider)and the two hand written provider factories thereference server and the global discovery server had. The 18 Workshop servers are plain
SampleWinFormsHost.Run<ServerForm>now.ClientFormandSampleClientFormlost theServiceMessageContextand the masterform from their constructors - the first is derived from the configuration, the second is
what
OpenFormsets on the window it opens - soClient.Net4has no form factory either.controls and the server controls and installed by hand in 45 entry points. It is a
registration, and the entry point helper installs it before the host starts.
AddReferenceClient()), so the listenerits Server menu arms comes from the container rather than out of the window's own fields.
new SomeDlg()aregone with it.
Two windows are deliberately still constructed by hand:
ExceptionDlg, which reportsfailures of the sample including a failure to build the container, and the console samples'
own message dialog.
Testing
All five tiers pass, on the tree as it stands:
Tier 2 now builds its forms through the factory too, so what it covers is the wiring of the
samples rather than a second copy of it written in the test; a test which assembles a form
out of single controls hands them a factory through the new
TestWindowFactory.dotnet buildof the solution and of all four.slnfiles is clean, with the same warningset as
master.🤖 Generated with Claude Code