I have been looking at Prism to host an old Winforms application. This is being used in our migration from Winforms -> WPF in our company. However, I have come across some issues.
In short I have a project outlined like so:
Project Outline
DataManager WPF Project
MEF Module that on Initialize registers a view with the region manager and references the old winforms project
- View -- which contains a WPF UserControl and has a WindowsFormsHost which points to the main view of the old winforms project.
DataManager Winforms project
Contains an old Module that needs to be intialized with views
Host
Contains the shell and bootstrapper
- Shell has the main region in which the WPF DataManager View gets injected into.
So the problem I am having is as follows. The old Winforms view loads correctly in the WPF shell via the new WPF view. However, any content added to the view after this does not get updated. I tried adding some buttons to the screen. I also tried clearing all the controls.
Looking around here, I went with the suggestion of listening to the Size Changed event and calling Child.Refresh on the WPFHost, but still this did not work.
Is this a WPF specific issue like discussed here : Does WPF refresh drawing similar to Windows Forms?
Or is it an issue with not refreshing the view in the region manager correctly? WPF, Prism v2, Region in a modal dialog, add region in code behind
Neither of which seem to work!
Perhaps I need to fire a redraw or similar in the winforms control itself?
Edit: Calling a Refresh() after updating the UI did not resolve the issue.
Edit 2: Also tried this in the old Winforms (.net 2.0) application and it displayed correctly!
Ok, I added a standard winforms project with some controls instead of the legacy project I am using and it worked. It is possibly missing some steps in the initialization of the legacy winforms project.
Related
Over a year ago I began writing a .NET Framework 4.6.1 app using Windows Forms. At the time I knew about WPF, yet was familiar with Windows Forms and it had most of the controls I needed. For the missing controls, I wrote two in Windows Forms and one in WPF. All of these coexisted fine, with the WPF control containerized within an element host.
This week I began the migration process to .NET Core 3.1. My tests with a copy of the project were positive, as well as initial results with the actual migration. After minor refactoring, the solution built and ran without issue. Then the gremlin appeared after opening the main UI form in the WinForms Designer. Back in .NET Framework, all of my custom controls appeared inside the Designer's Toolbox, providing easy drag-and-drop onto the form. In .NET Core, only my WinForms controls appeared in the Toolbox, not my WPF control. Because the Designer could not see that control, it stripped it from the Form's designer code, leaving an empty element host behind.
Here's the kicker. After reverting the Designer's changes, any direct manual edits to the Form's designer code is accepted, and building the project succeeds and runs fine. So for some reason the Designer does not like WPF controls in WinForms.
Things I've tried:
During my testing I discovered that the main WinForms UI needed both "UseWindowsForms" and "UseWPF" set to "true" for the project to compile. I then added the "UseWindowsForms" parameter to the WPF user control library. This caused the control to appear in the Designer's Toolbox, yet attempting to add the control resulted in this error: "Failed to create component ... Microsoft.DotNet.DesignTools.Client.DesignToolsServerException ... Make sure the type implements IComponent and provides an appropriate public constructor. Appropriate constructors either take no parameters or take a single IContainer parameter." And the existing WPF control in the code was still removed.
I copied the WPF control from the library to the main UI project, edited the namespace, and removed the library project reference. Same result as above.
Created a new Windows Forms User Control library, added "UseWPF" to the project, and copied the WPF control to this library. Same result as above.
Back to the test copy of my project, I followed Microsoft's guides for "try-convert" and "upgrade-assistant". The latter seemed promising at first, as it replaced, modified, or removed outdated referenced and packages. But, no success.
Tried the above migration steps with both .NET Core 3.1 and .NET 5. Same results.
The point I'm at now is to keep manually editing the Form's designer code. Not ideal for large changes, and also not sustainable if/when this project is passed to another developer. Any thoughts? Should I attempt porting the Windows Forms UI to WPF? Or is this simply a maturity issue with the still relatively new .NET Core Windows Forms Designer?
Visual Studio version: Community 2019 16.9.3
Screenshot of IComponent error:
enter image description here
I finally figured out a workaround; the idea was sparked by the Microsoft Docs page for the ElementHost control:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.integration.elementhost?view=netcore-3.1
In essence, move the WPF control hosting from the main UI into a WinForms control library. Hence, the User control in this WinForms control library will be a wrapper for the WPF control. Here are the steps I took in my test VS solution:
Remove the "true" entry from the main UI project file I had added during testing.
In the new WinForms control library, add "true" to this project file underneath the "true" entry. This enables the library to be a bridge between the two UI frameworks.
If the WPF control to host is in a dedicated control library (like mine), then add this project as a dependency in the WinForms control library.
Create a new User control in the WinForms library, if there isn't one already.
In the Designer, add a panel container to the control with "Fill" docking. I named my panel as "panelWpf".
Here is where the Microsoft Doc comes in. In the code behind file for the WinForms control, I first added an ElementHost control and the WPF control as private global variables. Then, in the WinForms "Load" event, I set the ElementHost docking style, added the WPF as a child, and finally added ElementHost as a control to the "panelWpf" container. Below is the code from this file. "WpfControlLibrary31" is my WPF control library project, and "TestControl31" is the WPF control itself. Lastly, "WpfTest" is the name of the wrapper WinForms User control.
After building the WinForms control library, it appeared in the main UI project's Toolbox, and I was able to add it to the form like any other Windows Forms control. The next steps will be to add event handlers, getters, setters, etc. to the control for the needed interaction.
using System;
using System.Windows.Forms;
using System.Windows.Forms.Integration;
namespace WinFormsLibrary
{
public partial class WpfTest : UserControl
{
// ElementHost for the WPF control
private ElementHost host = new ElementHost();
// WPF control to be hosted
private WpfControlLibrary31.TestControl31 uc = new WpfControlLibrary31.TestControl31();
public WpfTest()
{
InitializeComponent();
}
private void WpfTest_Load(object sender, EventArgs e)
{
// set the docking style for the ElementHost
host.Dock = DockStyle.Fill;
// add the WPF control as a child of ElementHost
host.Child = uc;
// add the ElementHost as a control of the panel container
panelWpf.Controls.Add(host);
}
}
}
Thoughts:
Some may wonder why I used the panel container. And in this simple experiment it was overkill; I could have simply docked the ElementHost to the control itself. However, if the WinForms User control has a more complex design, then the panel will be a placeholder while still allowing use of the Designer. Also, if a border or similar design is needed around the WPF control, then this should be possible with the panel.
Having the ElementHost and WPF control object as global allows access from all the control's methods, obviously, just like any controls added in the Designer itself.
The WPF control to host does not need to be in a dedicated WPF control library project. If it is a pre-existing WPF control (e.g. MediaElement), then use it for the global WPF object.
This WinForms control library is what I have been needing to consolidate and improve efficiency of my custom controls. So this issue with the .NET Core WinForms Designer turned out to be a blessing in disguise.
What are your thoughts? Thanks for the brainstorming help!
I have a GLFW3 OpenGL project written in C++. I was looking to create a modern GUI to go with it. I have limited experience with XAML and windows WPF but it is intuitive to work with so I really want this to work.
I found 'WpfUnmanagedOpenGL' template from Github https://github.com/kopaka1822/WpfUnmanagedOpenGL and it runs great with a C++ DLL communicating with the Csharp OpenGL host window that is already working within the form using GLAD, not GLFW.
I managed to create a GLFW window with my existing code and it displays as a second popup window when the WPF form is compiled and run. See:
How can I embed this correctly?
I found this question on StackOverflow: embed window(glfwCreateWindow) as child to C++ MFC parent form .
Essentially it says I can set the GLFW window to be a child window of the WPF form. However I cannot get the WPF parent window handle to set it all up.
PLEASE help.
Thanks,
You may want to try to do the opposite. Rather than embedding your Win32 app inside your WPF form, embed your WPF form within your Win32 app instead.
Official docs:
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/walkthrough-hosting-wpf-content-in-win32?view=netframeworkdesktop-4.8
A really neat tutorial:
https://www.codeproject.com/Articles/5253279/Create-An-Awesome-WPF-UI-for-Your-Cplusplus-QT-App
This is for the legacy .NET Framework though. I have no clue if the same technique can be applied in the new .NET Core framework though.
I have a problem that is similar to that posted here. I have posted before about using Caliburn.Micro in an Excel Document customization where the WPF controls are on an ActionsPane without receiving much help.
I currently have this working by placing the ShellView user control onto the design surface of the ActionsPane inside the ElementHost. Most things work as normal with Caliburn.Micro, however I do not get any Activate or Deactivate events because I haven't got a reference to a Caliburn.Micro WindowManager.
If I create a new window manager and use it to show a window using ShowWindow, then the window appears as a completely separate window (not in the ActionPane) however I do get Activate/Deactivate events.
Is there some way to attach Caliburn.Micro's Window manager to the ActionPane within Excel. I already have a MEF Bootstrapper passing false to BootstrapperBase.
Regards
Alan
I am trying to create a new Control (Let us for now forget about UserControl or CustomControl).
I open a Windows phone Application project in vs2010 and then add a new item Windows phone Control Library and then I use it in my phone application.
Everything works fine. Now if I want to create a windows phone control to be used in another application how should I be going about it? New application means a separate new solution.
Phone Application1 -> Have my own control inside the solution and hence I am able to use it.
How can I use this same control in another new solution?
I am not sure if this has been answered before. Does any solution already exist?
Cavet:
I tried creating a new Windows phone Class Library in a vs2010 instance but it didn't work because it didn't had a xaml form. When I tried to manually include it and write the logic it build successfully but now even if I include the dll of this project I don't get the control in the toolbox. This process does not make much sense to me as I am creating a class library and not a control but still wanted to give it a try.
To show controls from your assembly in the toolbox, you should do design-time assembly. You can look at these articles for the beginning:
Link2
Link1
It's not easy and perhaps you can live without that if your control assembly is not a commercial product. To use any control from other assembly in application:
add project reference to your control assembly
include xmlns namespace attribute corresponding to your assembly to any page of your application. Just start typing 'xmlns=' near other xmlns definitions in the page header and VS intellisense will show you the list of available namespaces. Choose the needed one and then set namespace alias to use on the page. It should look something like xmlns:myNamespace="clr-namespace:MyAssembly.Namespace.;assembly=MyAssembly"
insert control from your assembly into page using xaml editor
now you should be able to see your control in xaml designer and edit its properties from the property grid
.
I am not sure if the above mentioned method would solve the case, because as far as I have seen it is not possible to create a Windows Phone Control Library in VS2010. So this is what I did..
Created a Windows Phone Control Library and write my own custom control.
Build the project.
Back to Vs2010, added the reference to the control in the app in which I wish to use.
As simple as that. I don't know why it was initially a little bit confusing (perhaps I would have got confused with the binding and stuff). Anyways its quite simple though. Thank you for the reply.
I am currently exploring the option of porting some older VB6 application to WPF with C#. The plan, in phase one, is to port several key forms and not all the application. The theoretical goal is to open the VB6 form in a container of some sort within WPF via an ActiveX dll.
Is this even possible?
I've tried looking at the Interop and can't seem to find a solid example of how get it to work with anything but Win32 controls, not a full form. I have full access to the old VB6 code and can modify it in anyway needed.
The following screenshot of the main WPF app would serve as the wrapper/container:
http://www.evocommand.com/junk_delete_me/main_menu_mockup.png
The current VB6 maintenance screen that would be loaded in the “white space” section on the right side of the previous screen.
I was able to accomplish the task with the following steps:
Created a new VB6 Active X Control Project. Copied and pasted the entire contents of the VB6 form controls and code behind into the new control. There are several elements that have to be handled in switching to a control:
you lose the ability to display
the caption of the form in the
previous manner. You can work around
it with alternate controls
(label/borderlesstextbox, etc) that
accomplish the same functionality if
needed. This wasn’t a priority since
each screen was being hosted in a
browser like tab system in our new
.Net project.
All mousepointer references have to
be changed from Me.Mousepointer to
Screen.mousepointer
You cannot use Me.Hide and have to
alternate events to hide the .Net
container.
Any and all references to
Me.[anything] have to be removed or
replaced with UserControl.[anything]
if they are applicable.
If you use any functions that
reference a
[yourcontrol].Contianer.Property on a
form they will need to be altered to
loop through the UserControl.Controls
collection instead and “Container” is
invalid for vb6 ActiveX controls
All non-modal forms/dialog boxes
must be removed from the project as
there is now no Hwnd to handle in WPF.
You get an error of 'Non-modal forms
cannot be displayed in this host
application from an ActiveX DLL,
ActiveX Control, or Property page'.
In our case we had a simple splash
screen that would display when certain
long processes/reports displayed to
let the user know what was running.
I was unable to directly add the VB6 control via the interop to a WPF project . As such a new .Net “Windows Form Control Library” project was created. A reference to the VB6 OCX was added to the project. The VB6 Control s were then added to the .Net toolbox by “right click” –> “Add Item” and pointing a com reference to the VB6 control ocx. The .Net control was then used to host/serve the VB6 Control.
To display host a form in the VB6 and get it to fire the necessary initialization functionality the VB6 OCX controls were defaulted in a Visible.False manner so they were initially added to the .Net OCX as invisible controls. When needed the VB6 control is set to visible = True which fires the UserControl_Show() event. All code formerly in Form_Load() was moved to this event. The show event was the easiest method of accessing the Form_Load as needed. MSDN: “The control does not receive Show events if the form is hidden and then shown again, or if the form is minimized and then restored. The control’s window remains on the form during these operations, and its Visible property doesn’t change.”
Wrapping the vb6 controls within a .Net Winform control resolved the issue with Radio/Option buttons being rendered as black as outlined elsewhere in one of my responses to this question without having to convert the frames to Picture boxes as suggested.
In the WPF app as a menu choice is selected xaml code is dynamically created and displayed via a wrapper with a WindowsFormsHost tag. A dynamically created control object from the .Net winform app is then pushed into the WindowsFormsHost tag on the xaml and the control is made visible on the .net project which fires vb6 UserControl_Show and then load and display of the vb6 form.
I think what you will have to do is extract the VB6 form contents into an ActiveX control. You can then expose this in your ActiveX dll and place that in your WPF form. I doubt it's possible to host a VB6 form within any other type of form.
Can you even load that VB6 form in another VB6 form? I suggest you get that working first.
There is no reliable way to set parent of a VB6 form. You can always hack it or use plain ActiveX control (UserControl in VB6) as UI container instead of VB6 forms.
I found a method to do what was needed within WinForms rather than WPF at this point.
http://www.codeproject.com/KB/vb-interop/VB6formsinNET.aspx
I figure if I can get it working 100% there I can port it over to WPF or worse case host the WinForm element in the WPF form if I absolutely have too (U-G-L-Y).
Anyway, I've gotten a bit closer, but am having a very odd issue with certain controls painting too the screen. Radio/Option buttons are rendering as solid black:
http://www.evocommand.com/junk_delete_me/optionbuttons.png
I've tried explicitly changing the controls' background color from buttonface to a fixed color and it still does it. I'm assuming it's a layering issue with the option buttons being within the frame control. I'm at a bit of a loss on how to proceed without massive rework of the VB6 content to change the options buttons to checkboxes. It's a hefty app and there are 600+ option button controls across the application that I don't exactly want to deal with.
EDIT:
I was able to confirm it has something to do with the layering of the option within a Frame control. If pulled out to the base form the issue does not occur:
http://www.evocommand.com/junk_delete_me/optionbuttons2.png