We have a WPF user control that provides drag and drop functionality. We’ve also used an adorner to provide visual drag/drop feedback to the user about the framework element being dragged. If the hosting application for our user control is WPF, we can drag from the user control and drop to the target control that is hosted in the WPF form and we see the adorner. We have no issues with this scenario.
However, we have an issue because we need this WPF user control to be inside a VB.NET windows form. For our Windows Form project, We added a reference to the WindowsIntegration assembly and added a WPF control inside an ElementHost. This works great and we can drag and drop but the one issue we can’t solve is that when we drag from the user control to the target element hosted in the VB.NET windows form, the adorner doesn’t show.
Please help!
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 am using a control from a dll in my html page as
<object id="ATT" width="100%" height="100%"
classid="CLSID:E20EC898-E4EE-4A20-A5A5-E10144FDC6BA"
codebase="AvtechMediaControl.cab#version=1,1,7,6" VIEWASTEXT>
</object>
But I want to access the same into my WPF application.
When I try to choose the DLL in the toolbox, i get the following error,
“The following controls were successfully added to the toolbox but are not enabled in the active designer”
I need a way to add it into my WPF project. I do not want to go with the webbrowser control.
ActiveX controls cannot be directly added to a WPF view, but then can be hosted inside of Windows Form control. And WPF can host WinForms controls. Microsoft has a walk through describing the process.
The short description of this is that you'll add a WindowsFormHost to you XAML view. The MSDN walk though shows this being done in the code behind, but the WindowsFormHost can be added in the XAML. Then you'll set the Child of the WindowsFormHost to the ActiveX control. This will have to be done in the code behind as there is no support for adding ActiveX controls in the XAML editor.
You also need to be aware of the behavior of a hosted control in WPF. No WPF content can overlap the hosted control. The hosted control will always draw over top of your WPF content.
I have a WPF control in an ElementHost on a WinForms form. I've added the Form to a DockPanel Suite pane. The WPF content displays correctly when the Form is floating, or docked left/right/top/bottom, but when DockState is Document the WPF content disappears, leaving a blank docked Form.
I've experimented with creating empty test WPF controls, and this behaviour occurs whenever an ElementHost is used on a WinForm, and docked using DPS. Can anyone suggest where to look for a fix ? I've set the WPF rendering to Software only, but it doesn't help with this problem.
Many thanks.
WinForms App using .NET 4.0, VS2012 on Windows 7 on VMWare Fusion on MacBook Pro.
After much poking around, the problem turned out to be due to a ToolStripContainer that was on the same form as the DPS DockPanel. When the ElementHost form was docked as document, this was being rendered over the top of it. I replaced the ToolStripContainer with a separate ToolStrip and MenuStrip (following the DockSample), and all works fine.
I have a custom UserControl made with C# WinForms that is exposed via COM to a C++ host app. When I wrote and debugged the UserControl, I had a C# test app and made sure the application works from that perspective. C# WinForms control in a C# WinForms environment has no problems.
When testing it through our C++ COM host application, UI events no longer work. Interacting through the hosting C++ applications UI, I can set the properties and see the response in the hosted control. However, when I click on a control the whole app freezes. I've observed the behavior with:
Buttons
Tab Pages
Check Boxes
Value Up/Down
Text Boxes
As long as I don't click on an active UI component in the hosted COM control, it behaves normally--until an event happens where the COM control needs to create a new tab page.
I've tried setting all kinds of break points, but it freezes before we get to the hosted COM control and the whole app is frozen. The alternate COM control that is arguably more complicated doesn't seem to have this behavior.
Any clues what is going on? I'm lost trying to figure out the problem.
New Information
The contents of the whole UserControl was in a TabControl with several tabs. I created a single button outside the TabControl, and when that was clicked I could interact with the controls inside the TabControl on each of the pages that were defined when the ComControl was initialized.
However, If I create a new TabPage or instantiate a FolderBrowserDialog I'm right back to the old behavior. So, how do I use TabPages and such safely from within a ComControl?
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