Hosted Winform control does not respond to events from WPF
Asked Answered
N

1

6

This is my first question in StackOverflow. Due to lack of reputations, I couldn't post any links or images. I've been working on the following issue for more than 2 days. Any help would greatly be appreciated.

Before I get into my question, here is what I have and what I'm expecting:

  • I have a Windows Form which hosts WPF in an ElementHost control.
  • And then, I have a Winforms UserControl similar to DateTimePicker. This
    is hosted inside a WindowsFormsHost control.

The above scenario is un-avoidable for the following reasons:

  • The authorization dialog to all our applications is developed in Winforms, and takes a Winforms instance as its parameter. There is no WPF version introduced yet. Therefore, I had to use an ElementHost to host my View inside the Windows Form.
  • The Winforms control hosted inside my WPF is also un-avoidable. We have our own DateTime Winforms UserControl that behaves similar to the DateTimePicker Winforms control, but has lot more complexities involved. So, replacing this control with a WPF version is out of question.

Expected Functionality:
I have a

  • WPF control (say, a textbox)

  • A DateTime Winforms UserControl that I was mentioning above.

  • And a Cancel button that basically resets the above controls.

When I hit the Cancel button, I'm publishing an event from the ViewModel, say RunViewModel to the WPF UserControl code behind file, say RunView.xaml.cs.

eventAggregator.GetEvent<ResetDateTimeEvent>().Publish(true);

In the code behind file, I've subscribed to the event as follows

eventAggregator.GetEvent<ResetDateTimeEvent>().Subscribe(ResetDateTimeHandler);

The WPF control resets to its default value, but the DateTime UserControl does not reset.

So, for testing purposes, I removed the ElementHost control, and just had my WPF View with a WindowsFormsHost control that hosts the DateTime Winforms UserControl, and a WPF "Cancel" button.

When I click on the button, the value on the DateTime control resets to its default value.

Then, I thought this might be an issue with my DateTime Winforms UserControl. So, I replaced my DateTime Winforms UserControl with a Winforms Textbox control in my actual application. So now the nesting is as follows:

WinForms-ElementHost-WPF-WindowsFormsHost-Winforms Textbox

Here is the xaml code.

<WindowsFormsHost x:Name="ReportFromDtTmHost" Margin="8,0"  Grid.Column="0"
                                           LostFocus="ReportFromDtTmHost_LostFocus">
      <WindowsFormsHost.Child>
        <winforms:TextBox x:Name="ReportFromDateTime"/>
      </WindowsFormsHost.Child>
</WindowsFormsHost> 

On Initial load, I’m loading the Textbox with Initial Load Text text

private void Window_Loaded(object sender, EventArgs e)
{
  ReportFromDateTime.Text = "Initial Load Text";
}

As I was mentioning above, when I hit the Cancel button, this is what happens:

  • Publish the event from ViewModel

    eventAggregator.GetEvent().Publish(true);

  • Subscribe to the event in the code behind file (xaml.cs):

    eventAggregator.GetEvent().Subscribe(ResetDateTimeHandler);

  • EventHandler for the published event.

    private void ResetDateTimeHandler(bool cancelClicked) { ReportFromDateTime.Text = "Reset to Default"; }

As you can see in the above code, I’m resetting the Text on clicking the Cancel button.

During Debugging, I could see the Text property being changed to "Reset to Default", but the UI does not show these changes.

Here is the wierd part:

The Child property on the WindowsFormsHost control is different from the actual “ReportFromDateTime” Textbox control.

While debugging, I could see that the Child and Name property on the WindowsFormsHost control were different. The Name property is empty,

ReportFromDtTmHost.Child.Name = ""

which rather should be ReportFromDateTime.

It almost seems like the Host and the Child controls are getting re-created.

As far as I see it, I think the extra level of nesting (WinForms-ElementHost-WPF-WindowsFormsHost-Winforms Textbox) might be causing issues during the interoperations between WPF and Winforms.
I’ve done a lot of research and searched lot of links for suggestions. I found none pointing out this issue. Some of them were close. Here are a couple of links:

The this suggests to reproduce the message loop under the “Surrogate Windows Forma Message Loop” section.

Here is one more link that explains the issue with nesting under the Nesting section.

I apologize for being verbose. Just wanted you guys to get a clear picture of my problem. Please let me know if you have any questions regarding the post. Any suggestions would be greatly appreciated.

EDIT:

We were able to resolve the issue, but still, it is a work-around. Here is what we did: There were two ways to resolve this issue, but both were related to using static.

Static Winforms control:

We used the following static Winforms control

public static class ControlHolder
{
  public static TextBox ReportFromDateTimeInstance;
}

In the OnChanged event of the "actual" control, we dump the actual control, ReportFromDateTime to the static control, ReportFromDateTimeInstance.

private void ReportFromDateTime_TextChanged(object sender, EventArgs e)
{
  ControlHolder.ReportFromDateTimeInstance = (TextBox)sender;
}

And from then on, wherever we update the actual control (as in ResetDateTimeHandler method), we update the static control

private void ResetDateTimeHandler(bool cancelClicked)
{
  ControlHolder.ReportFromDateTimeInstance = "Text changed";
}

This shows the updated value on the Front-End

Static EventAggregator

This work-around was provided by one of our colleague.

In this case, we are using our actual control, ReportFromDateTime, rather than the static control, ControlHolder.ReportFromDateTimeInstance

We used a static event aggregator for publishing/subscribing the ResetDateTimeEvent instead of using the Event Aggregator instance provided by Unity Container. So, instead of

eventAggregator.GetEvent<ResetDateTimeEvent>.Publish(true);

we used:

ResetDateTimeEvent.Instance.Publish(true);

And in the subscription:

ResetDateTimeEvent.Instance.Subscribe(ResetDateTimeHandler);

I know that we need not use a static event aggregator in this scenario since we are using the instance provided by Unity Container (which makes sure that a single instance is shared by all the ViewModels), but this also has resolved the issue.

So, I'm still confused on why the above two scenarios are solving the problem. Is it the static-ness that is solving the issue ?

As I was already saying, I feel that the controls are getting re-created, and by the time we have the controls in hand, they have been already re-created.

Any suggestions would greatly be appreciated.

Nuno answered 10/3, 2015 at 6:13 Comment(0)
B
0

I've done the same thing before in an app that had a WPF control inside of a WinForms control. The WPF used Prism/Unity (later switched to MEF) to start everything up. However, this created a whole new EventAggregator by default in the bootstrapper, so I had override the default IEventAggregator in the container with a static one b/c the WinForm side had already been created and was using its own IEventAggregator instance. The symptom was similar in that published events were not received.

In a mixed system such as yours, singletons are great for ensuring that everything's feeding off of the same reference, especially when your startup is in stages (WinForms then WPF).

Simple answer: yes, use singletons for shared references between WinForms code and WPF code. Those singletons can be fed into the container in the WPF bootstrapper so that injection still occurs in the WPF side, too.

Barabarabarabas answered 26/8, 2015 at 19:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.