1

I'm creating an R process from a C# application and the R script that runs creates a Tk window. Right now the window is always showing up on the primary monitor, while my C# application is running on the 2nd monitor. How do I move it to the 2nd monitor (when the parent process is there) or get it to be created there in the first place?

Here's how I launch the process right now:

 var process = new System.Diagnostics.Process
 {
     StartInfo = new ProcessStartInfo(rFilepath, String.Format("\"{0}\"", scriptFilepath))
 };
 process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
 process.StartInfo.CreateNoWindow = true;
 process.StartInfo.UseShellExecute = false;
 process.StartInfo.RedirectStandardOutput = process.StartInfo.RedirectStandardError = true;
 process.Start();
 process.EnableRaisingEvents = true;
 process.Exited += (x, y) =>
 {
      if (process.ExitCode != 0)
          Program.HandleException(new Exception(String.Format("Output:\r\n{0}\r\n\r\nError:\r\n{1}",
                                                process.StandardOutput.ReadToEnd(),
                                                process.StandardError.ReadToEnd())));
 };

Here's the part of the R script that sets up the main window:

base <- tktoplevel()
tkwm.title(base, "AppName")

// create frames and controls here and put them in with tkgrid

tcl("wm", "attributes", base, topmost=TRUE)
tcl("wm", "attributes", base, topmost=FALSE)
tkfocus(base)
Matt Chambers
  • 2,229
  • 1
  • 25
  • 43

1 Answers1

1

I'm not a R expert, but since there's no answers for your question, I'll try one.

Here follows a generic code that can move the main window of a child process from C#. Since it uses the WinAPI SetWindowPos function, some P/Invoke was used here.

The imports from SetWindowPosFlags is a bit scary, but it's just a C# version of the original headers documented in Windows API.

The code below, moves the window X +10 and Y +10 relative to your parent process window. You can change the X and Y to reflect your actual need.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

namespace WindowsPosTests
{
[Flags()]
enum SetWindowPosFlags : uint
{
    /// <summary>If the calling thread and the thread that owns the window are attached to different input queues, 
    /// the system posts the request to the thread that owns the window. This prevents the calling thread from 
    /// blocking its execution while other threads process the request.</summary>
    /// <remarks>SWP_ASYNCWINDOWPOS</remarks>
    AsynchronousWindowPosition = 0x4000,
    /// <summary>Prevents generation of the WM_SYNCPAINT message.</summary>
    /// <remarks>SWP_DEFERERASE</remarks>
    DeferErase = 0x2000,
    /// <summary>Draws a frame (defined in the window's class description) around the window.</summary>
    /// <remarks>SWP_DRAWFRAME</remarks>
    DrawFrame = 0x0020,
    /// <summary>Applies new frame styles set using the SetWindowLong function. Sends a WM_NCCALCSIZE message to 
    /// the window, even if the window's size is not being changed. If this flag is not specified, WM_NCCALCSIZE 
    /// is sent only when the window's size is being changed.</summary>
    /// <remarks>SWP_FRAMECHANGED</remarks>
    FrameChanged = 0x0020,
    /// <summary>Hides the window.</summary>
    /// <remarks>SWP_HIDEWINDOW</remarks>
    HideWindow = 0x0080,
    /// <summary>Does not activate the window. If this flag is not set, the window is activated and moved to the 
    /// top of either the topmost or non-topmost group (depending on the setting of the hWndInsertAfter 
    /// parameter).</summary>
    /// <remarks>SWP_NOACTIVATE</remarks>
    DoNotActivate = 0x0010,
    /// <summary>Discards the entire contents of the client area. If this flag is not specified, the valid 
    /// contents of the client area are saved and copied back into the client area after the window is sized or 
    /// repositioned.</summary>
    /// <remarks>SWP_NOCOPYBITS</remarks>
    DoNotCopyBits = 0x0100,
    /// <summary>Retains the current position (ignores X and Y parameters).</summary>
    /// <remarks>SWP_NOMOVE</remarks>
    IgnoreMove = 0x0002,
    /// <summary>Does not change the owner window's position in the Z order.</summary>
    /// <remarks>SWP_NOOWNERZORDER</remarks>
    DoNotChangeOwnerZOrder = 0x0200,
    /// <summary>Does not redraw changes. If this flag is set, no repainting of any kind occurs. This applies to 
    /// the client area, the nonclient area (including the title bar and scroll bars), and any part of the parent 
    /// window uncovered as a result of the window being moved. When this flag is set, the application must 
    /// explicitly invalidate or redraw any parts of the window and parent window that need redrawing.</summary>
    /// <remarks>SWP_NOREDRAW</remarks>
    DoNotRedraw = 0x0008,
    /// <summary>Same as the SWP_NOOWNERZORDER flag.</summary>
    /// <remarks>SWP_NOREPOSITION</remarks>
    DoNotReposition = 0x0200,
    /// <summary>Prevents the window from receiving the WM_WINDOWPOSCHANGING message.</summary>
    /// <remarks>SWP_NOSENDCHANGING</remarks>
    DoNotSendChangingEvent = 0x0400,
    /// <summary>Retains the current size (ignores the cx and cy parameters).</summary>
    /// <remarks>SWP_NOSIZE</remarks>
    IgnoreResize = 0x0001,
    /// <summary>Retains the current Z order (ignores the hWndInsertAfter parameter).</summary>
    /// <remarks>SWP_NOZORDER</remarks>
    IgnoreZOrder = 0x0004,
    /// <summary>Displays the window.</summary>
    /// <remarks>SWP_SHOWWINDOW</remarks>
    ShowWindow = 0x0040,
}

public partial class Form1 : Form
{
    [DllImport("user32.dll", SetLastError = true)]
    static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);

    public Form1()
    {
        InitializeComponent();
    }        

    private void button1_Click(object sender, EventArgs e)
    {
        Process process = new Process();
        process.StartInfo.WindowStyle = ProcessWindowStyle.Normal;

        process.StartInfo.FileName = Application.ExecutablePath;

        //Starts new process
        process.Start();

        //Wait for process main handle be acquired
        while (process.MainWindowHandle == IntPtr.Zero)            
            Thread.Sleep(1);            

        //Move window
        SetWindowPosFlags flags = SetWindowPosFlags.ShowWindow | SetWindowPosFlags.IgnoreResize | SetWindowPosFlags.IgnoreZOrder;
        SetWindowPos(process.MainWindowHandle, new IntPtr(0), this.Left + 10, this.Top + 10, 0, 0, flags);
    }

}

}

Eric Lemes
  • 561
  • 3
  • 10
  • Thanks for your answer. Does this work with ProcessWindowStyle.Hidden and CreateNoWindow = true? I have to use these or else the R console shows up. I suspect that may be the "main window" and the Tk/Tcl GUI that gets launched is a child window. – Matt Chambers Jul 21 '14 at 15:29
  • No, it doesn't. It can be a problem if the "main" window for Tk/Tcl is the console window. Please, try without the same options I've provided and tell me if the console windows moves or the main window. If it isn't, we'll need to find a way to get the handle to your desired window. – Eric Lemes Jul 21 '14 at 16:15
  • Nice to hear that. Good :-D – Eric Lemes Jul 22 '14 at 12:29