0
votes

First, I didn't find any answer so far. The article here on this site does not answer my question: Converter of System.Drawing.Point' to 'System.Windows.Point

In the above article, one suggests to convert like this:

    //convert 
    System.Drawing.Point dp = …;
    return new System.Windows.Point(dp.X, dp.Y);

But the conversion does not seem to get the correct result. I got the System.Drawing.Point from a click event of a WinForm that embeds WPF visual. When I try to identify the Control clicked in WPF visual using above conversion and VisualTreeHelper.HitTest(), the top-left Control can be identified correctly, the more to the right-bottom the more inaccurate. Means the point conversion is not a simple X-to-X and Y-to-Y copy. There must be some other factors involved. Any ideas, do we need consider other display factors in conversion? Please run the attached program and clicking on different point on the Grid, you will find out the strange behavior.

Background:

I have a WPF UI embedded in WinForm. When a HelpButton “?” on the WinForm is clicked followed by a click on a Control in the WPF UI, the WinForm get notified and I can get the correct System.Drawing.Point in the WinForm. Then I want to get the corresponding location clicked in the WPF. I calculated the relative System.Drawing.Point to the ElementHost which is the last WinForm element connecting the first WPF visual, then try to use above way to get System.Windows.Point inside WPF UI, but it does not get the correct point that is corresponding to the location clicked.

More background:

We have some third-party UI libraries as UserControls which cannot be changed. We use a WinForm to hold them. However we want to intrude the UserControls and add Help-Window when user clicks "?" button on the WinForm (then the mouse cursor changes to ?) and then click on an UI control in the UserControls. This works fine if third-party UI is written purely in WinForm.

What we did was that we first got the Point of click inside the WinForm by this: System.Drawing.Point pt = this.PointToClient(e.MousePos);

then got the front-most Control. Form.GetChildAtPoint(pt);

Then got the visual-tree path to that control by recursively climbing-up the visual-tree and record the Name property or Text property of each Control in the path: Control parent = child.Parent;

Then we use the path as a string to look up a predefined table to determine the keyword to identify that Control. Once the Control is identified by the keyword, the keyword can map to a Help-Window or its ToolTip.

The above procedure works fine if everything is WinForm.

However when the UserControl contains an ElementHost and embedded WPF content, the above is broken. I think as long as I can translate the “System.Drawing.Point” to “System.Windows.Point” correctly, then it should work as well.

The following code demonstrate the problem: Reproduction procedure:

  1. Start the program.

  2. Click on "?" button, cursor changes to "?".

  3. Click on one of the 3 buttons, a popup window gives the visual path.

  4. For the two buttons in the Grid, the first one gives WPF path, but the second one does not, because the System.Windows.Point is not correct.

    //Please add into project the reference to (right click the project and select "Add Reference"):
    //WindowsBase
    //WindowsFormsIntegration
    //PresentationCore
    //PresentationFramework
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Forms.Integration;
    using System.Windows.Media;
    namespace WindowsFormsApplication1
    {
    

    public partial class Form1 : Form { [DllImport("user32.dll")] static extern IntPtr GetFocus();

      public Form1()
      {
    
         InitializeComponent();
    
         System.Windows.Controls.Button b = new System.Windows.Controls.Button();
         b.Content = "WPF_Button";
         b.Name = "WPF_Button_name";
         Grid.SetColumn(b, 0);
         Grid.SetRow(b, 0);
    
         System.Windows.Controls.Button b2 = new System.Windows.Controls.Button();
         b2.Content = "WPF_Button2";
         b2.Name = "WPF_Button_name2";
         Grid.SetColumn(b2, 2);
         Grid.SetRow(b2, 2);
    
         Grid g = new Grid();
         g.Name = "WPF_Grid";
         g.Width = 250;
         g.Height = 100;
         g.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
         g.VerticalAlignment = VerticalAlignment.Stretch;
         g.ShowGridLines = true;
    
         // Define the Columns
         ColumnDefinition colDef1 = new ColumnDefinition();
         ColumnDefinition colDef2 = new ColumnDefinition();
         ColumnDefinition colDef3 = new ColumnDefinition();
         g.ColumnDefinitions.Add(colDef1);
         g.ColumnDefinitions.Add(colDef2);
         g.ColumnDefinitions.Add(colDef3);
    
         // Define the Rows
         RowDefinition rowDef1 = new RowDefinition();
         RowDefinition rowDef2 = new RowDefinition();
         RowDefinition rowDef3 = new RowDefinition();
         RowDefinition rowDef4 = new RowDefinition();
         g.RowDefinitions.Add(rowDef1);
         g.RowDefinitions.Add(rowDef2);
         g.RowDefinitions.Add(rowDef3);
         g.RowDefinitions.Add(rowDef4);
    
    
         g.Children.Add(b);
         g.Children.Add(b2);
    
         this.elementHost1.Child = g;
    
      }
    
      // More usefull version of GetChildAtPoint
      public static System.Windows.Forms.Control FindChildAtPoint(System.Windows.Forms.Control parent, System.Drawing.Point pt, ref string path, out System.Drawing.Point relative_point_inside_child)
      {
         //Find a child
         System.Windows.Forms.Control child = parent.GetChildAtPoint(pt);
         //If no child, this is the control at the mouse cursor
         if (child == null)
         {
            path += parent.Name;
            //Offset our current position to be relative to the child to get the point inside the child.
            relative_point_inside_child = new System.Drawing.Point(pt.X, pt.Y);
            return parent;
         }
         path += parent.Name + "\\";
         //If a child, offset our current position to be relative to the child
         System.Drawing.Point childPoint = new System.Drawing.Point(pt.X - child.Location.X, pt.Y - child.Location.Y);
         //Find child of child control at offset position
         return FindChildAtPoint(child, childPoint, ref path, out relative_point_inside_child);
      }
    
    
      private void Form1_HelpRequested(object sender, HelpEventArgs e)
      {
         if (System.Windows.Forms.Control.MouseButtons == MouseButtons.None)
         {
            //No mouse activity, this function is entered because of F1 key press.
            //Get the focused Control. If no focused Control then launch overall Help-Window.
            //
            IntPtr handle = GetFocus();
            if (handle != IntPtr.Zero)
            {
               System.Windows.Forms.Control ctl = System.Windows.Forms.Control.FromHandle(handle);
               //Got the ctl, do whatever
            }
         }
         else
         { // Help Button ? pressed (After click ?, the mouse cursor changes to ?, 
            // Then user can move the mouse to a UI Control and click it, in this case,
            // we need find the control that is clicked, the control might not be focused
            // in such case.
            // Convert screen coordinates to client coordinates
            System.Drawing.Point p = this.PointToClient(e.MousePos);
            System.Drawing.Point pt = this.PointToClient(Cursor.Position);
    
            //Look for control user clicked on
            System.Drawing.Point relative_point_inside_child;
            string path = "";
            System.Windows.Forms.Control ctl = FindChildAtPoint(this, pt, ref path, out relative_point_inside_child);
    
            if (ctl is ElementHost)
            {
               // This is a WPF view embedded in WinForm, get the root Control
               UIElement wpf_root = (ctl as ElementHost).Child;
               if (wpf_root != null)
               {
                  System.Windows.Point pt_WPF = new System.Windows.Point(relative_point_inside_child.X, relative_point_inside_child.Y);
                  HitTestResult htr = VisualTreeHelper.HitTest(wpf_root, pt_WPF);
                  if (htr != null && htr.VisualHit != null)
                  {
                     //IInputElement elem = System.Windows.Input.Mouse.DirectlyOver;
                     string pathWPF = "";
                     if (htr.VisualHit is DependencyObject)
                     {
                        FindPathOfControlWPF(htr.VisualHit, ref pathWPF);
                     }
                     if (pathWPF != "")
                     {
                        path = path + pathWPF;
                     }
                  }
               }
            }
            System.Windows.Forms.MessageBox.Show("Clicked on PATH: " + path);
            e.Handled = true;
         }
    
      }
    
    
      //WPF version of More usefull version of FindPathOfControl
      public static System.Windows.DependencyObject FindPathOfControlWPF(System.Windows.DependencyObject child, ref string path)
      {
         //Find a parent
         System.Windows.DependencyObject parent
            = System.Windows.Media.VisualTreeHelper.GetParent(child);
    
         if (child is System.Windows.Controls.TextBlock)
         {
            path = ":" + (child as System.Windows.Controls.TextBlock).Text + path;
         }
         else if (child is System.Windows.Controls.Label)
         {
            path = ":" + (child as System.Windows.Controls.Label).Content.ToString() + path;
         }
         else if (child is System.Windows.Controls.Primitives.ButtonBase)
         {
            path = ":" + (child as System.Windows.Controls.Primitives.ButtonBase).Content.ToString() + path;
         }
    
         if (child is System.Windows.FrameworkElement)
         {
            path = (child as System.Windows.FrameworkElement).Name + path;
         }
    
         //If no parent, this is the top control
         if (parent == null)
         {
            return child;
         }
         path = "\\" + path;
         //Find parent of child control
         return FindPathOfControlWPF(parent, ref path);
      }
    
      /// <summary>
      /// Required designer variable.
      /// </summary>
      private System.ComponentModel.IContainer components = null;
    
      /// <summary>
      /// Clean up any resources being used.
      /// </summary>
      /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
      protected override void Dispose(bool disposing)
      {
         if (disposing && (components != null))
         {
            components.Dispose();
         }
         base.Dispose(disposing);
      }
    
      #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>
      private void InitializeComponent()
      {
         //this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel();
         //this.flowLayoutPanel1 = new System.Windows.Forms.Panel();
         this.panel1 = new System.Windows.Forms.Panel();
         this.button1 = new System.Windows.Forms.Button();
         this.panel2 = new System.Windows.Forms.Panel();
         this.elementHost1 = new System.Windows.Forms.Integration.ElementHost();
         //this.flowLayoutPanel1.SuspendLayout();
         this.panel1.SuspendLayout();
         this.panel2.SuspendLayout();
         this.SuspendLayout();
         // 
         // flowLayoutPanel1
         // 
         //this.flowLayoutPanel1.Controls.Add(this.panel1);
         //this.flowLayoutPanel1.Controls.Add(this.panel2);
         //this.flowLayoutPanel1.Location = new System.Drawing.Point(24, 33);
         //this.flowLayoutPanel1.Name = "flowLayoutPanel1";
         //this.flowLayoutPanel1.Size = new System.Drawing.Size(242, 205);
         //this.flowLayoutPanel1.TabIndex = 0;
         //this.flowLayoutPanel1.BackColor = System.Drawing.Color.Blue;
         // 
         // panel1
         // 
         this.panel1.Controls.Add(this.button1);
         this.panel1.Location = new System.Drawing.Point(3, 3);
         this.panel1.Name = "panel1";
         this.panel1.Size = new System.Drawing.Size(90, 134);
         this.panel1.TabIndex = 0;
         this.panel1.BackColor = System.Drawing.Color.Green;
         this.panel1.BringToFront();
         // 
         // button1
         // 
         this.button1.Location = new System.Drawing.Point(16, 39);
         this.button1.Name = "button1";
         this.button1.Size = new System.Drawing.Size(60, 35);
         this.button1.TabIndex = 0;
         this.button1.Text = "button1";
         this.button1.UseVisualStyleBackColor = true;
         this.button1.BackColor = System.Drawing.Color.Yellow;
         this.panel1.BringToFront();
         // 
         // panel2
         // 
         this.panel2.Controls.Add(this.elementHost1);
         this.panel2.Location = new System.Drawing.Point(99, 3);
         this.panel2.Name = "panel2";
         this.panel2.Size = new System.Drawing.Size(300, 300);
         this.panel2.TabIndex = 1;
         this.panel2.BackColor = System.Drawing.Color.Purple;
         this.panel1.BringToFront();
         // 
         // elementHost1
         // 
         this.elementHost1.Dock = System.Windows.Forms.DockStyle.Fill;
         this.elementHost1.Location = new System.Drawing.Point(0, 0);
         this.elementHost1.Name = "elementHost1";
         this.elementHost1.TabIndex = 0;
         this.elementHost1.Text = "elementHost1Text";
         this.elementHost1.Child = null;
         this.panel1.BringToFront();
         // 
         // Form1
         // 
         this.HelpButton = true;
         this.MaximizeBox = false;
         this.MinimizeBox = false;
         this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.Form1_HelpRequested);
         this.Size = new System.Drawing.Size(600, 600);
         this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
         this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
         this.ClientSize = new System.Drawing.Size(600, 600);
         //this.Controls.Add(this.flowLayoutPanel1);
         this.Controls.Add(this.panel1);
         this.Controls.Add(this.panel2);
         this.Name = "Form1";
         this.Text = "Form1";
         this.BackColor = System.Drawing.Color.Red;
         //this.flowLayoutPanel1.ResumeLayout(false);
         this.panel1.ResumeLayout(false);
         this.panel2.ResumeLayout(false);
         this.ResumeLayout(false);
    
      }
    
      #endregion
      //private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
      //private System.Windows.Forms.Panel flowLayoutPanel1;
      private System.Windows.Forms.Panel panel1;
      private System.Windows.Forms.Button button1;
      private System.Windows.Forms.Panel panel2;
      private System.Windows.Forms.Integration.ElementHost elementHost1;   
    

    } }

1
Put a small example.aybe
All the words in the world won't convince us that new System.Windows.Point(dp.X, dp.Y) won't work. You need to create a Minimal, Complete, and Verifiable example that demonstrates your issue.Sheridan
I tried to extract a simple example from the complex code, but the example code has problems: the HelpEventArgs.MousePos.X is always 0. It does not happen in the original code. I raised another question on it using the example code here: stackoverflow.com/questions/25874319/…user1943928
Even the example from MSDN has the same issue. msdn.microsoft.com/en-us/library/…user1943928
Got a workaround for the problem of "HelpEventArgs.MousePos.X is always 0" using Cursor.Postion. Now I can post my code here for this problem.user1943928

1 Answers

4
votes

Finally I found the solution (by reading more about WPF screen coordinates and more testing).

The root cause is that the "new System.Windows.Point(dp.X, dp.Y)" won't work reliably on different resolutions, computers with multiple monitors, etc. It's needed to use a transform to get the mouse position relative to the current screen, not the entire viewing area which consists of all monitors.

A different topic question's answer helped me: How do I get the current mouse screen coordinates in WPF?

If the change the following two lines of my code:

          System.Windows.Point pt_WPF = new System.Windows.Point(relative_point_inside_child.X, relative_point_inside_child.Y);
          HitTestResult htr = VisualTreeHelper.HitTest(wpf_root, pt_WPF);

To the following:

          System.Windows.Point pt_WPF = new System.Windows.Point(relative_point_inside_child.X, relative_point_inside_child.Y);
           var transform = PresentationSource.FromVisual(wpf_root).CompositionTarget.TransformFromDevice;
          pt_WPF = transform.Transform(pt_WPF);
          HitTestResult htr = VisualTreeHelper.HitTest(wpf_root, pt_WPF);

Then it works fine.