C++/CLI: Embedding MFC into WinForm - winforms

Hello,
Since a few weeks, we are trying to "transform" a MFC dialog into a "MFC form" which can be embedded into a WinForm User Control.
We've succeeded to do that:
We made a WinForm User Control, called Dlg_WU_MFC_Container
When created, the UC creates the MFC form called CDlgEdgeType
Then, every time the UC is resized or moved, we also move and resize the MFC form
Here is the code (I tried to remove a lot of unnecessary stuff..):
Dlg_WU_MFC_Container.h:
#pragma once
public ref class Dlg_WU_MFC_Container : public System::Windows::Forms::UserControl
{
private:
CDlgEdgeType* _dialog;
CWnd *_wnd;
private: //---Local Controls
System::ComponentModel::IContainer^ components;
public:
Dlg_WU_MFC_Container();
~Dlg_WU_MFC_Container();
!Dlg_WU_MFC_Container();
template<class T, class HP>
void InitializeContainer() {
CDlgEdgeType =
}
private:
void RefreshEmbeddedSize();
#pragma region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
void InitializeComponent(void)
{
this->SuspendLayout();
//
// Dlg_WU_MFC_Container
//
this->AutoScaleDimensions = System::Drawing::SizeF(96, 96);
this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Dpi;
this->ForeColor = System::Drawing::SystemColors::WindowText;
this->Margin = System::Windows::Forms::Padding(0);
this->Name = L"Dlg_WU_MFC_Container";
this->Size = System::Drawing::Size(816, 480);
this->SizeChanged += gcnew System::EventHandler(this, &Dlg_WU_MFC_Container::evSizeChanged);
this->VisibleChanged += gcnew System::EventHandler(this, &Dlg_WU_MFC_Container::evVisibleChanged);
this->ResumeLayout(false);
}
#pragma endregion
private: System::Void evSizeChanged(System::Object^ sender, System::EventArgs^ e);
private: System::Void evVisibleChanged(System::Object^ sender, System::EventArgs^ e);
};
Dlg_WU_MFC_Container.cpp:
#include "Dlg_WU_MFC_Container.h"
#include "DlgEdgeType.h"
Dlg_WU_MFC_Container::Dlg_WU_MFC_Container()
{
InitializeComponent();
_wnd = NULL;
_dialog = new CDlgEdgeType();
}
Dlg_WU_MFC_Container::~Dlg_WU_MFC_Container()
{
if (components)
{
delete components;
}
this->!Dlg_WU_MFC_Container();
}
Dlg_WU_MFC_Container::!Dlg_WU_MFC_Container()
{
// We need to detach the handle to free it for other usage
if (_wnd) {
_wnd->Detach();
delete _wnd;
_wnd = NULL;
}
if (_dialog) {
delete _dialog;
_dialog = NULL;
}
}
System::Void Dlg_WU_MFC_Container::evSizeChanged(System::Object^ sender, System::EventArgs^ e) {
RefreshEmbeddedSize();
}
// Inform the embedded form to adapt to its new size
void Dlg_WU_MFC_Container::RefreshEmbeddedSize() {
if (_dialog && _isShown) {
CRect containerWnd;
containerWnd.left = this->Left;
containerWnd.right = this->Right;
containerWnd.top = this->Top;
containerWnd.bottom = this->Bottom;
_dialog->ReplaceEmbeddedForm(containerWnd);
}
}
System::Void Dlg_WU_MFC_Container::evVisibleChanged(System::Object^ sender, System::EventArgs^ e) {
// _isShown basically useless.. !
if (Visible != _isShown) {
_isShown = Visible;
if (_dialog) {
if (Visible) {
void *handle = Handle.ToPointer();
if (handle) {
// We need to create a new CWnd which will contain
// the handle of the current WinForm control where
// the embedded MFC will be contained
_wnd = new CWnd();
_wnd->Attach((HWND)handle);
_dialog->Create(_wnd);
RefreshEmbeddedSize();
}
} else {
// When the control is not shown anymore, we need to free
// the handle so another control can use it (the handle
// is stored in the MFC permanent map)
_wnd->Detach();
_dialog->DestroyWindow();
delete _wnd;
_wnd = NULL;
}
}
}
}
CDlgEdgeType.cpp:
void CDlgEdgeType::ReplaceEmbeddedForm(CRect &rect) {
if (!GetSafeHwnd()) {
return;
}
SetWindowPos(NULL, rect.left, rect.top, rect.Width(), rect.Height(), SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER);
}
This stuff is working "great" : CDlgEdgeType is well shown in our application and when the application is resized or moved, everything goes well.
Here is my issue:
CDlgEdgeType has Dlg_WU_MFC_Container as parent, great. But the latter doesn't know that the MFC form is a "child".. so the focus is kind of lost and the Arrow keys and the Tab keys simply do not work.
A thing you should know is that Dlg_WU_MFC_Container is added to TabPages of a custom TabControl. So, if the user tries to navigate through MFC form's controls or he tries to navigate through a TreeView with the Arrow keys, the TabControl seems to take over the focus and will change tab.. which is not convenient D:
I have no idea how to solve that issue, neither my colleagues.
Maybe the way we integrate the MFC is wrong, but there are no really topics about that (we see more "Embedding WinForm forms into MFC"..). Also, as our software has a big history, we cannot simply recreate CDlgEdgeType. It's a big dialog and, in fact, we have 7 dialogs like that, the code implements template but I removed them for the clarity of this message..
Thank you !
Sbizz.

Well, we find a way out.. this may not be the best way to do it, but it is working (or at least, it seems to work !).
At first, I've managed to send the keys to the MFC form:
bool Dlg_WU_MFC_Container::ProcessDialogKey(Keys keyData) {
::SendMessage(CWnd::GetFocus()->GetSafeHwnd(), WM_KEYDOWN, (WPARAM)keyData, (LPARAM)0);
return true;
}
Since the TabControl is taking the control through an WM_ACTIVATE, we tried to "override" it by sending also WM_ACTIVATE to the MFC form, so the result is the following:
bool Dlg_WU_MFC_Container::ProcessDialogKey(Keys keyData) {
::SendMessage(CWnd::GetFocus()->GetSafeHwnd(), WM_ACTIVATE, MAKEWPARAM(WA_ACTIVE, 0), (LPARAM)0);
::SendMessage(CWnd::GetFocus()->GetSafeHwnd(), WM_KEYDOWN, (WPARAM)keyData, (LPARAM)0);
return true;
}
The only thing is that the "Tab" key doesn't seems to work but after investigation, it is not needed by our users, so... :D But I think it's just related to the third parameter of WM_ACTIVATE (previous control). It must be used to know which control must be focused after Tab is pressed.
Sbizz

Related

Scrolling in Sticky Notes

I got the following sticky note example:
If the sticky note has more than 9 rows, the additional rows are not visible.
I'm able to navigate through the note with my arrow keys. If I'm going to scroll with the mouse wheel, it seems to ignore the popup and just changes the page.
Is it possible to activate scrolling for sticky note popups?
Edit:The solution outlined below will soon be available as part of the samples included in the PDFTron SDK download. In the meanwhile, I hope that the below solution helps.
Yes, it is possible to activate scrolling for sticky notes.
The problem is most apparent when using the single page view. It appears to work as expected in continuous mode.
However it is not as simple as setting VerticalScrollVisibility = ScrollBarVisibility.Auto;. There are a few files that need to be modified to get this working.
The good news is that we can get the expected behaviour by modifying the code in the provided samples.
Solution
The solution is to add some handling for the PreviewMouseWheel event coming from the PDFViewWPF class.
In the downloaded samples, the following changes were made to get things running as expected:
Add a method to handle the PreviewMouseWheel event in the NoteHost class (Samples/PDFViewWPFTools/CS/Utilities/NoteHost.cs)
internal void HandlePreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var originalSource = (UIElement)e.OriginalSource;
if (originalSource.IsDescendantOf(mNoteBorder) && mTextBox.IsFocused)
{
mTextBox.ScrollToVerticalOffset(mTextBox.VerticalOffset - e.Delta);
e.Handled = true;
}
}
Also make sure to add mTextBox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto; in the NoteHost.CreateNoteAndArrow() method, after the mTextBox object is instantiated (~line 183).
Next, edit the NoteManager class - Samples/PDFViewWPFTools/CS/Utilities/NoteManager.cs - and add a HandlePreviewMouseWheel method. This will internally call the HandlePreviewMouseWheel on each displayed (opened) note and break at the first one where the event gets handled.
internal void HandlePreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
foreach(var note in mActiveNotes)
{
note.Value.HandlePreviewMouseWheel(sender, e);
if(e.Handled)
{
break;
}
}
}
Next, edit the ToolManager class to ensure that the note manager gets a chance to handle the PreviewMouseWheel before attempting a page change. Open Samples/PDFViewWPFTools/CS/ToolManager.cs and navigate to the PDFView_PreviewMouseWheel. The existing method should look like this:
private void PDFView_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
if (mCurrentTool != null && _IsEnabled)
{
ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
ToolManager.ToolType next_tm;
while (true)
{
mCurrentTool.PreviewMouseWheelHandler(sender, e);
next_tm = mCurrentTool.NextToolMode;
if (prev_tm != next_tm)
{
mCurrentTool = CreateTool(next_tm, mCurrentTool);
prev_tm = next_tm;
}
else
{
break;
}
}
}
}
Replace it with the below code:
private void PDFView_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
if (mCurrentTool != null && _IsEnabled)
{
ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
ToolManager.ToolType next_tm;
while (true)
{
mNoteManager.HandlePreviewMouseWheel(sender, e);
if (!e.Handled)
{
mCurrentTool.PreviewMouseWheelHandler(sender, e);
next_tm = mCurrentTool.NextToolMode;
if (prev_tm != next_tm)
{
mCurrentTool = CreateTool(next_tm, mCurrentTool);
prev_tm = next_tm;
}
else
{
break;
}
}
else
{
break;
}
}
}
}
By doing the above, we are giving the NoteManager a chance to handle the PreviewMouseWheel before doing anything else with it.
Another point to note is that we have to now "do the scrolling" in code, using the mTextBox.ScrollToVerticalOffset method in the NoteHost class.

mouse wheel event scroll c++ Visual Studio

I am making a program with windowForms and I want to use the mouse wheel to do some things, like changing image with the mouse wheel. I have been looking for some answers in the microsoft website but I couldn't understand it . Can anyone give me a example about mouse wheel event and what libraries I need to use.
this was the library that i need to use
'#' using '<'System.dll'>'
'#' using '<'System.Windows.Forms.dll'>'
'#' using '<'System.Drawing.dll'>'
public ref class mainWindow : public System::Windows::Forms::Form
{
public:
mainWindow(void)
{
InitializeComponent();
//
//TODO: Add the constructor code here
//
}
//inside InitializeComponent() i wrote:
this->panel_album_info->MouseEnter += gcnew System::EventHandler(this,&mainWindow::panel_album_info_MouseEnter);
this->panel_album_info->MouseWheel += gcnew System::Windows::Forms::MouseEventHandler(this, &mainWindow::panel_album_info_Mouse_Wheel);
code of each function
private: System::Void panel_album_info_Mouse_Wheel(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) {
//if (tenho_o_rato_no_panel_do_album_info == true)
//{
int movimento = e->Delta;
label1->Text = "teste";
if (movimento == 120)
numero_de_deslocações_do_album += 1;
else if (movimento == -120)
numero_de_deslocações_do_album -= 1;
label1->Text = numero_de_deslocações_do_album.ToString();
//}
//else
//return;
}
private: System::Void panel_album_info_MouseEnter(System::Object^ sender, System::EventArgs^ e) {
this->panel_album_info->Select(); //este tambem da mas nao sei qual é a diferença
//this->panel_album_info->Focus();
}
the code allows to scroll something without click it. the component that i want to scope was inside a panel object so i need to focus/select the panel object first.

Remove a ScreenSpaceLines3D Object from a ViewPort?

I am making a 3D Game with WPF in VB, and I am using a ScrennSpaceLines3D Object I found
http://3dtools.codeplex.com/releases/view/2058
but when I try to remove a line I added to the viewport by using
mainViewport.Children.RemoveAt(i)
it gives a NullExceptionError. I have read that this is because it does not totally come off the rendering queue. There have been fixes for c#, but I have yet to find one that works with VB. Is there a way to make this work or possibly draw a line in 3D space some other way? I find it quite ridiculous that VB doesn't even have a way to easily draw 3D lines...
Remove ScreenSpaceLines3D :
foreach (ScreenSpaceLines3D line3D in lines3DList)
{
lines3D.Points.Clear(); // Very importante
_viewport3D.Children.Remove(lines3D);
}
I'm a bit late to the party but i'm having the same issues.
The access violation occurs because each instance registers an event handler to the Rendering event of the composition target
public ScreenSpaceLines3D()
{
...
CompositionTarget.Rendering += OnRender; // <-- this line
}
but forgets to remove it when the instance is removed from the scene.
So to fix this you need to touch the source code:
public ScreenSpaceLines3D()
{
...
// event registration removed
}
private bool AttachedToCompositionTargetRendering { get; set; }
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
base.OnVisualParentChanged(oldParent);
var parent = VisualTreeHelper.GetParent(this);
if (parent == null)
{
if (AttachedToCompositionTargetRendering)
{
CompositionTarget.Rendering -= OnRender;
AttachedToCompositionTargetRendering = false;
}
}
else
{
if (!AttachedToCompositionTargetRendering)
{
CompositionTarget.Rendering += OnRender;
AttachedToCompositionTargetRendering = true;
}
}
}

why is this C++/CLI code not adding the files to a listbox

I have this c++ code that is supposed to detect when a file has been modified in a directory and add the file name to a list box.
This is the file watcher part which is inside a button that starts the monitoring process
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {
array<String^>^ args = Environment::GetCommandLineArgs();
FileSystemWatcher^ fsWatcher = gcnew FileSystemWatcher( );
fsWatcher->Path = "C:\\users\\patchvista2";
fsWatcher->IncludeSubdirectories = true;
fsWatcher->NotifyFilter = static_cast<NotifyFilters>
(NotifyFilters::FileName |
NotifyFilters::Attributes |
NotifyFilters::LastAccess |
NotifyFilters::LastWrite |
NotifyFilters::Security |
NotifyFilters::Size );
Form1^ handler = gcnew Form1();
fsWatcher->Changed += gcnew FileSystemEventHandler(handler, &Form1::OnChanged);
fsWatcher->Created += gcnew FileSystemEventHandler(handler, &Form1::OnChanged);
fsWatcher->EnableRaisingEvents = true;
}
then for the onchange part I have this code
void OnChanged (Object^ source, FileSystemEventArgs^ e)
{
// Here is the problem
MessageBox::Show(e->FullPath);
listBox1->Items->Add(e->FullPath);
// End problem
System::Security::Cryptography::MD5 ^ md5offile = MD5CryptoServiceProvider::Create();
array<Byte>^ hashValue;
FileStream^ fs = File::Open(e->FullPath, IO::FileMode::Open, IO::FileAccess::Read, IO::FileShare::ReadWrite);
fs->Position = 0;
hashValue = md5offile->ComputeHash(fs);
PrintByteArray(hashValue);
fs->Close();
Application::DoEvents();
}
It will message box me the file name but it will not add it to the list box. I tried having it display the file name to a label but that did not work either. it seems like the screen is not refreshing once this code loop is started. I have this code in vb.net and it does add the file name to the list box. can someone show me why the file name is not getting added tot the list box.
Two things:
You need to keep the FileSystemWatcher alive. It's liable to be garbage collected where you've got it now. (Create a class field and stick it there.)
You need to Invoke onto the UI thread whenever you do anything with a UI component.
Here's the approximate syntax for #2 (I'm away from a compiler at the moment, this may not be exact.)
void Form1::AddToListBox(String^ filename)
{
listBox1->Items->Add(filename);
}
void Form1::OnChanged(Object^ source, FileSystemEventArgs^ e)
{
Action<String^>^ addDelegate = gcnew Action<String^>(this, &Form1::AddToListBox);
this->Invoke(addDelegate, e->FullPath);
...
}

Releasing mouse capture and letting mouse click pass through

I have a control that is similar to a Popup or Menu. I want to display it and when the user clicks outside the bounds of the box, have it hide itself. I've used Mouse.Capture(this, CaptureMode.SubTree) as well as re-acquired the capture the same way Menu/Popup do in OnLostMouseCapture.
When the user clicks outside the bounds of the control, I release the mouse capture in OnPreviewMouseDown. I don't set e.Handled to true. The mouse click will make it to other controls on the main UI, but not to the close button (Red X) for the window. It requires 2 clicks to close the app.
Is there a way to tell WPF to restart the mouse click, or to send a repeated mouse click event?
Here's my code. Note I renamed it to MainMenuControl - I'm not building a Menu, so Menu/MenuItem and Popup aren't options.
public class MainMenuControl : Control
{
static MainMenuControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MainMenuControl), new FrameworkPropertyMetadata(typeof(MainMenuControl)));
}
public MainMenuControl()
{
this.Loaded += new RoutedEventHandler(MainMenuControl_Loaded);
Mouse.AddPreviewMouseDownOutsideCapturedElementHandler(this, OnPreviewMouseDownOutsideCapturedElementHandler);
}
void MainMenuControl_Loaded(object sender, RoutedEventArgs e)
{
this.IsVisibleChanged += new DependencyPropertyChangedEventHandler(MainMenuControl_IsVisibleChanged);
}
void MainMenuControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (this.IsVisible)
{
Mouse.Capture(this, CaptureMode.SubTree);
Debug.WriteLine("Mouse.Capture");
}
}
// I was doing this in OnPreviewMouseDown, but changing to this didn't have any effect
private void OnPreviewMouseDownOutsideCapturedElementHandler(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("OnPreviewMouseDownOutsideCapturedElementHandler");
if (!this.IsMouseInBounds())
{
if (Mouse.Captured == this)
{
Mouse.Capture(this, CaptureMode.None);
Debug.WriteLine("Mouse.Capture released");
}
Debug.WriteLine("Close Menu");
}
}
protected override void OnLostMouseCapture(MouseEventArgs e)
{
base.OnLostMouseCapture(e);
Debug.WriteLine("OnLostMouseCapture");
MainMenuControl reference = e.Source as MainMenuControl;
if (Mouse.Captured != reference)
{
if (e.OriginalSource == reference)
{
if ((Mouse.Captured == null) || (!reference.IsAncestorOf(Mouse.Captured as DependencyObject)))
{
//TODO: Close
Debug.WriteLine("Close Menu");
}
}
// if a child caused use to lose the capture, then recapture.
else if (reference.IsAncestorOf(e.OriginalSource as DependencyObject))
{
if (Mouse.Captured == null)
{
Mouse.Capture(reference, CaptureMode.SubTree);
Debug.WriteLine("Mouse.Capture");
e.Handled = true;
}
}
else
{
//TODO: Close
Debug.WriteLine("Close Menu");
}
}
}
private bool IsMouseInBounds()
{
Point point = Mouse.GetPosition(this);
Rect bounds = new Rect(0, 0, this.Width, this.Height);
return bounds.Contains(point);
}
}
The problem is that the mouse handling you are talking about is outside the WPF eventing system and part of the operating system so we're really talking about two fairly different mouse message queues that interact well enough most of the time but in these edge case we see that the interoperability is not perfect.
You could try to generate Win32 mouse messages or send your own window a close message but all those approaches are hacks. Since popups and menus exhibit exactly the same symptoms you describe, it doesn't seem like there is going to be an easy to way to accomplish what you want as you've described it.
Instead, I suggest that you consider giving up the mouse capture when the mouse leaves the north client area of the window or some other heuristic such as a specified distance from the control. I know this is probably not ideal but it might be a satisfactory compromise if you want the close button to work badly enough.

Resources