Hosted Winform control does not respond to events from WPF - wpf

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.

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.

Related

Intercept Deactivation Strategy of a Closing window?

I'm using the latest version of Caliburn Micro (4.0.173) in an application leveraging IoC.
I do not have a sample project yet has my sandbox is using nugets from Telerik that are not easily findable. But I think a detailed explanation can get the point across:
I have a ProfileEditorViewModel : Conductor<IScreen>.Collection.OneActive resolved by the container (singleton) through the constructor of my ShellViewModel and I use the I use the WindowsManager to open my ProfileEditorViewModel in a different window:
public class ShellViewModel : IShellViewModel{
private readonly IProfileEditorViewModel _profileEditor;
private readonly IWindowManager _windowManager;
public ShellViewModel(IWindowManager windowManager, IProfileEditorViewModel profileEditor){
_windowManager = windowManager;
_profileEditor = profileEditor;
}
//Method triggered by an Action from the View
public void OpenProfileEditor(){
_windowManager.ShowWindowAsync(_profileEditor, null, null);
}
}
When I close the ProfileEditor window (clicking on the top right red cross), it triggers a closing type deactivation, which is coherent with the implementation of a conductor (not being conducted itself) being shutdown. The Screens collection is being wiped and some more cleaning is being done through the Screen.cs implementation of Caliburn.
However, for this application, I'm facing "the view not being unbound" issue (https://github.com/Caliburn-Micro/Caliburn.Micro/issues/361) and next time I open the ProfileEditor, it'll bind a new view (because the Views Collection of my viewmodel was empty) and it create some issue related to UI components used in the view (basically one event from the viewmodel triggers similar actions, on the view(s) side, that all come back to the viewmodel, which create some identification issue)
Reading through the issue 361, I'm currently able to catch the Unloaded event of my ProfileEditor (once closed), and basically clean the DataContext of any view associated to the viewmodel (before Screen cleans the Screens collections). Next time I open the ProfileEditor, it'll bind a new view and it'll be the only one = it works.
However clearing the DataContext may produce some other issue down the road.
What I would like to do, is to avoid clearing the View collection of the ProfileEditorViewModel upon closing. So Caliburn can use this reference the next time it needs to resolve a window/view for the ProfileEditorViewModel (instead of looking for a new one).
Is it possible to intercept the Deactivation / Closing strategy and change the close parameter to false ?
Another solution may be for my ShellView to be a Conductor<>.Collection.AllActive but I can't wrap my head around teh management of a window closing. How to intercept the ProfileEditor windows closing and proceeding with a deactivation of the ProfileEditorViewModel instead of a closing.
I hope it make sense :)
Thank you in advance for your help

How to animate WPF WebBrowser

Just documenting this as a question an answer so that somebody else doesn't have to suffer the same pain.
I have a WPF application that animates pages, much like swiping on an iPhone. All was good until one of the pages needed to contain a WebBrowser. It did not respond at all well to the animation - when it was supposed to slide in, it wouldn't appear until you focused it, and when it was supposed to slide out, it would go away until you moved the mouse over it. In both cases it just popped in/out rather than animating.
Complicating matters, during the project it was decided to move back to .net 3.5 instead of 4 for unrelated reasons.
So the question is: how can I either (a) get the WebBrowser to properly animate; or (b) how can I hide the WebBrowser at the start of animation and show it again at the end. The animation is currently defined in XAML, and I don't particularly want to change it to code.
And a follow up question is: is there a better way, still using .net 3.5?
UPDATE The WPF WebBrowser is so pathetically lame compared to the WinForms one, I have swapped over, using WindowsFormsHost. Everything below still applies, but the WebBrowser is now not so nobbled (eg. it has a DocumentCompleted event).
I pretty quickly gave up on the option to animate the WebBrowser, as it just got all too hard, and instead decided to hide and re-show it. The start of the animation is triggered by a Command on the View Model. It then finds the page that should be displayed, creates it, and kicks off the animation through an attached property that reflects the transition state.
I created an interface, IRequireTransitionInfo, such that a call to IRequireTransitionInfo.TransitioningFrom gives it a chance to hide itself and IRequireTransitionInfo.TransitioningTo to show again. TransitioningFrom was easy, but TransitioningTo had to be called when the storyboard completed.
Initially, in the constructor of the View Model, it went looking for the Storyboard and hooked into its Completed event, as in the code below:
Storyboard animation = Application.Current.FindResource("SlideAnimation") as Storyboard;
if (animation != null)
{
animation.Completed += new EventHandler(animation_Completed);
}
And then the event handler:
void animation_Completed(object sender, EventArgs e)
{
IRequireTransitionInfo info = currentViewModel as IRequireTransitionInfo;
if (info != null)
info.TransitioningTo(currentView);
}
This seemed to be working pretty well with .net 4. After downgrading to .net 3.5, when the code above to hook up the Completed event ran, I got the following error:
Specified value of type 'System.Windows.Media.Animation.Storyboard' must have IsFrozen set to false to modify.
Despite some of the other answers on SO, you cannot unfreeze a frozen Freezable, and moving the code into the constructor of the MainWindow didn't help.
I went down the path of an attached property on the Storyboard that was bound to a command on the View Model.
<Storyboard x:Key="SlideAnimation" local:EventCommand.StoryboardCompleted="{Binding Source={StaticResource Locator}, Path=Current.MainViewModel.StoryboardCompletedCommand}">
However, this resulted in the following error at runtime:
Cannot convert the value in attribute 'ContentTemplate' to object of type 'System.Windows.DataTemplate'. Cannot freeze this Storyboard timeline tree for use across threads.
It seems you can't do any databinding on a Storyboard (under .net 3.5 at least). Consequently, I solved the problem somewhat inelegantly by having the attached property just define the string name of a resource that was expected to implement an interface supporting notification of storyboard completion.
<Storyboard x:Key="SlideAnimation" local:EventCommand.StoryboardCompletedHandler="Locator">
If anybody knows of a better way to handle this situation under .net 3.5, I would be glad to hear.

Exiting an App or Closing a Control When Using MVVM

In my WPF application, I am using the ViewModelLocator without IoC. I am calling the static ViewModelLocator.Cleanup() method provided by the MVVM-Light framework from my own button which is tied to a "close window command". This Command calls the static ViewModelLocator.Cleanup(), which calls an instance Cleanup() method on my MainWindowViewModel instance. The instance Cleanup() method then sets the property to which the MainWindow binds its DataContext, to null. The setter on the property raises a PropertyChanged event. Curiously, setting this property to null does not cause the window to close.
I am trying to understand why this is the case? If I set the MainWindow's DataContext to null, should that not be the same as Window.Close()? In my case, the Window and all of its elements remain on the screen. However, if I attempt further actions, I get null pointer exceptions, indicating the DataContext binding Property has indeed been set to null; this has also been confirmed in the debugger.
I have created a workaround by hooking the Application.Exit event and issuing a Window.Close() in the event handler in order to create my own "Close Window" button (ie, to create same functionality for my own Button / Command as clicking the X button in the upper right of a Window). Since calling a UI element (ie, the Window instance) from MVVM directly is not MVVM friendly, I used a ViewService to implement the Window.Close() functionality in order to keep the workaround MVVM friendly. I am a big fan of the ViewService idiom (or pattern), but I just don't think it should be necessary here; except, I could see how exiting the app is a special case that perhaps should tie-in with the application lifecycle, and .Net seems to only allow exiting a WPF app by issuing the Window.Close() method.
Thoughts appreciated.
I believe I have found the answer to my original question, in addition to the one raised in my comments discussion with flq.
First, the answer to the original question is that the proper way to close the Window is along the lines of what I did in my described "workaround". Closing an app is a View-initiated process, as it is the Window control that has the bits for how to do it. You can of course hook the Application.Exit event so that you can perform cleanup on your ViewModels, prompt the user to save data, etc..
The question raised by me after some interesting discussion with flq is, if I don't just set a control's DataContext (ie, ViewModel) to null in order to release the View and ViewModel resources, how should I do it?
An interesting discussion with some nuances can be found here, but the basic answer is that you find the parent control and remove the control you want to close from its Children list. Note, this is a different technique with a different goal than just making the control not visible by setting is Visibility property to Collapsed. In the following example, "this" is the control to be removed (ie, "Closed"):
Panel p = (Panel) this.Parent;
p.Children.Remove(this);
I am not sure if you still need to then set the child (ie, "this") to null to re-claim its resources, or, if just removing it from the visual tree will cause WPF to re-claim the resources; the above linked discussion makes no mention. As mentioned in the original discussion, the above technique can be supplemented by hooking it to certain events, or using other application specific logic.

wpf dual monitor application?

I'm very new to WPF. I want to create a dual monitor/projector application. What I want to do is have the "presenters screen" on one monitor and another panel on the secondary monitor, similar to how powerpoint works. I'm struggling to wrap my mind around the panels and XAML. So what I'm after is user clicks on a button on screen1 and information gets updated on screen2.
I'm using this code:
this.Width = System.Windows.SystemParameters.VirtualScreenWidth;
this.Height = System.Windows.SystemParameters.VirtualScreenHeight;
this.Top = 0;
this.Left = 0;
to set the width and height of the screen.
Edit:
The later goal is to cause screen2 to retrieve items out of a database based on the selection on screen1
Question: tutorials, places to go, nudges on how to update monitor2 from a button on monitor1
Short Answer
Create a view model that shared between two views; make one of the views the master (makes the changes) and the other pure presentation. The views are new windows. Initially do not be concerned with the window position (we'll get to that later) just get the shared viewmodel working.
Tip: research the MVVM pattern. Google has a lot of articles on the subject.
Long Answer
After you have researched MVVM and created a few example applications (from scatch or using a framework), below are few additional features you want implement to create the "powerpoint-like" application.
Fullscreen Mode
At the very least you will want the presentation window to be full screen. To achieve this, you set the WindowStyle to None and AllowsTransparency to True.
If you want to make the second window also fullscreen you may need to do some Win32 overrides to get the window to maximize properly without covering the taskbar (post a comment if you want to know how to do this).
Detect Multiple Monitors
Get the size and position of the monitors using Win32 Interop commands. There will be plenty of articles on the Internet that will help you with this (or post another StackoverFlow question).
This would be a neat™ feature as it will position the two windows correctly (use the secondary screen as the presentation).
That is all that I can think of now, post-back if you any questions on MVVM or any of the additional points above.
1) You should have 2 Windows, the way this looks I'd make monitor2 a child window of monitor1 (after all, it is a child ;)
What i mean by that, is that StartupUri in App.xaml should point to monitor1, and in monitor1's constructor, you should create an instance of monitor2 (which would be a singleton if i were to do it).
2) To maximize a window on the second screen:
Subscribe to the Loaded event of the window (in code-behind), and set
private void Window_Loaded(object sender, RoutedEventArgs e) {
WindowState = WindowState.Maximized;
}
More info (and source): here
3) As to how to make monitor2 react when you set something in monitor1, make monitor1 and monitor2 bind to the same ViewModel, only they show different stuff.
Hope this helps!

Handling Events from the Business Layer in XAML WPF

I am very new to WPF, and I have a very basic WPF question:
Suppose we have a custom dll with some business logic that fires events such as
ModemIsConnected. On the UI side (.xaml) there is a label that changes its value to OK once that event is fired. I would like to offload the event handling portion (changing label's status and other minor UI tweaks) to a designer. Is it possible to handle events in xaml without creating code-behind?
Thanks!
The simple answer is I don't believe it's possible. There are no doubt things you can do with data binding and the like, but then you're getting beyond the kind of thing you want a designer to be doing.
A couple of options:
Teach the designer the bare minimum of coding an event handler and calling a storyboard. With intellisense and the nice interaction between Blend / Visual Studio you could have someone up and running fairly quickly.
Have them create storyboard animations for any of the 'minor UI tweaks' you require, and then you can hook these up to the event and change your label text with only 3-5 lines of code.
You can have the designer do the UI side without them having to get their hands dirty with code. You could create a class that implements IPropertyNotifyChanged with a boolean property that changes as the modem state changes. The designer can then bind to that property and do fancy triggers, state changes etc etc from within the XAML and the binding system will handle "telling" the UI the value has changed.

Resources