2012-07-18 97 views
0

在Visual C#中,我试图从多个文本框(一次一个)取文本并将它们粘贴到记事本中。我通过复制到剪贴板,然后粘贴到记事本中,然后再粘贴到其他文本框中来做到这一点。这个代码表示这样的想法:C#复制粘贴多次不工作

subBox1.SelectAll(); 
subBox1.Copy(); 
SendKeys.Send("%{TAB}"); // alt+tab 
SendKeys.Send("^v");  // paste 
SendKeys.Send("{TAB}");  // tab 

subBox2.SelectAll(); 
subBox2.Copy(); 
SendKeys.Send("^v"); 
SendKeys.Send("{TAB}"); 

subBox3.SelectAll(); 
subBox3.Copy(); 
SendKeys.Send("^v"); 
SendKeys.Send("{TAB}"); 

正如你所看到的,这将复制和粘贴从三个文本框(名为subBox1,2和3)。但是,由于某种原因,只有最后一个文本框的内容才会被复制。这也会发生,如果我注释掉第三个盒子...在这种情况下,只有第二个文本框的内容被复制。我试过使用SelectAll()和Copy(),就像你在这里看到的,以及剪贴板类一样。两者都有同样的问题。

例如,如果文本框内容分别是“asdf”,“qwer”和“zxcv”,我看到的全部是“zxcv”三次。

任何想法为什么会发生这种情况?我一直坚持这一个小时,现在不知道发生了什么。

谢谢!

回答

2

SendKeys不会等待其他应用程序处理您发送的密钥,因此在记事本开始处理您的按键时,您的程序已将subBox3的文本复制到其他文本的顶部。

您需要改用SendWait

同时,而不是发送的Alt + Tab,您可以使用这样的事情:

[DllImport("user32.dll", SetLastError = true)] 
static extern IntPtr FindWindow(string lpClassName, string lpWindowName); 

[DllImport("user32.dll")] 
[return: MarshalAs(UnmanagedType.Bool)] 
static extern bool SetForegroundWindow(IntPtr hWnd); 

// ... 

SetForegroundWindow(FindWindow(null, "Untitled - Notepad")); 
+0

OOOOH,谢谢。我会给你一枪! – MattM 2012-07-18 04:24:43

+0

嗯,现在的替代标签不走。我试着保持alt-tab为“发送”。任何想法为什么这不工作,因为它应该? – MattM 2012-07-18 04:31:53

+0

不确定使用SendKeys替代选项卡。显然没有内置的方法切换到另一个应用程序。我会使用FindWindow + SetForegroundWindow本地调用。 – Blorgbeard 2012-07-18 04:38:05

0

我会使用SendMessage函数进行更精确的结果。要使用SendMessage,首先需要一个有效的窗口句柄来记事本的文本区域。这可以通过多种方式完成,但我更喜欢使用简单的子查找功能。

您需要以下命名空间的进口和PInvoke的声明:

using System; 
using System.Diagnostics; 
using System.Runtime.InteropServices; 
using System.Collections.Generic; 
using System.Text; 

//pinvoke 
[DllImport("user32.dll", CharSet = CharSet.Auto)] 
private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam); 

[DllImport("user32.dll")] 
[return:MarshalAs(UnmanagedType.Bool)] 
private static extern bool GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); 

[DllImport("user32.dll")] 
private static extern int GetWindowTextLength(IntPtr hWnd); 

[DllImport("user32.dll")] 
[return:MarshalAs(UnmanagedType.Bool)] 
private static extern bool EnumChildWindows(IntPtr hParent, delChildWndProc callback, IntPtr lpParam); 
//delegate callback for EnumChildWindows: 
[return:MarshalAs(UnmanagedType.Bool)] 
private delegate bool delChildWndProc(IntPtr hWnd, IntPtr lParam); 

现在,到子窗口查找。基本上类似于FindWindowEx,但我想写我自己的,它会检索多个窗口,可以很好。它使用以下包装类描述调用之间的信息:

private class WindowLookup 
{ 
    public string LookupName { get; private set; } 
    public List<IntPtr> MatchedChildren { get; private set; } 
    public int Depth { get; set; } 
    public int MaxDepth { get; set; } 

    public WindowLookup(string lookup, int maxdepth) 
    { 
     this.MatchedChildren = new List<IntPtr>(); 
     this.LookupName = lookup; 
     this.MaxDepth = maxdepth; 
     if (this.MaxDepth > 0) 
      this.MaxDepth++; //account for the depth past the parent control. 
     this.Depth = 0; 
    } 
} 

然后将下面的功能做的所有工作:

private static List<IntPtr> FindAllWindows(IntPtr hParent, string className, int maxdepth = 0) 
{ 
    var lookup = new WindowLookup(className, maxdepth); 
    var gcAlloc = GCHandle.Alloc(lookup); 
    try 
    { 
     LookupChildProc(hParent, GCHandle.ToIntPtr(gcAlloc)); 
    } 
    finally 
    { 
     if (gcAlloc.IsAllocated) 
      gcAlloc.Free(); 
    } 
    return lookup.MatchedChildren; 
} 

private static bool LookupChildProc(IntPtr hChild, IntPtr lParam) 
{ 
    var handle = GCHandle.FromIntPtr(lParam); 
    WindowLookup lookup = null; 
    if (handle.IsAllocated && (lookup = handle.Target as WindowLookup) != null) 
    { 
     if (lookup.Depth < lookup.MaxDepth || lookup.MaxDepth == 0) 
     { 
      lookup.Depth++; 
      var builder = new StringBuilder(256); 
      if (GetClassName(hChild, builder, builder.Capacity) && builder.ToString().ToLower() == lookup.LookupName.ToLower()) 
       lookup.MatchedChildren.Add(hChild); 
      EnumChildWindows(hChild, LookupChildProc, lParam); 
     } 
    } 
    return true; 
} 

你不必担心这些功能的实现过很多,他们会按原样工作。关键是,使用这些功能,您可以非常轻松地找到notepadEdit窗口(您输入的文本区域)的句柄。

var notepads = Process.GetProcessesByName("notepad"); 
if (notepads.Length > 0) 
{ 
    foreach(var notepad in notepads) //iterate through all the running notepad processes. Of course, you can filter this by processId or whatever. 
    { 
     foreach(var edit in FindAllWindows(notepad.MainWindowHandle, "Edit")) 
     { 
      //next part of the code will go here, read on. 
     } 

    } 
} 

现在,在我离开的代码是在一个循环的中间通过当时运行的每个记事本进程的“编辑”窗口。现在我们有了一个有效的窗口句柄,我们可以使用SendMessage向它发送东西。特别是附加文字。我写了下面的函数来处理文本追加到远程控制:

private static void AppendWindowText(IntPtr hWnd, string text) 
{ 
    if (hWnd != IntPtr.Zero) 
    { 
     //for your reference, 0x0E (WM_GETTEXTLENGTH), 0xB1 (EM_SETSEL), 0xC2 (EM_REPLACESEL) 
     int len = SendMessage(hWnd, 0x000E, IntPtr.Zero, IntPtr.Zero).ToInt32(); 
     var unmanaged = Marshal.StringToHGlobalAuto(text); 
     SendMessage(hWnd, 0x00B1, new IntPtr(len), new IntPtr(len)); 
     SendMessage(hWnd, 0x00C2, IntPtr.Zero, unmanaged); 
     Marshal.FreeHGlobal(unmanaged); 
    } 
} 

现在,我们有我们的AppendWindowText功能,您可以在函数调用在上面的嵌套循环添加到它(这里我把评论):

AppendWindowText(edit, "Some text here"); 

而你有它。这是一个罗嗦的回应,但最终这种方法比使用SendKeys和聚焦窗口等更可靠。你永远不需要失去你自己的应用程序的焦点。

如果您有任何问题,请随时发表评论,我会尽我所能回答。

干杯,
Ĵ

编辑:一些参考:

SendMessage function (MSDN)
EnumChildWindows function (MSDN)
Appending text using SendMessage

+0

非常感谢您的时间。不幸的是,这有点令人难以置信。这是我第一天使用Visual C#以及任何Windows API类型的东西。我刚刚开始了我认为是一个非常简单的项目,让我的脚湿润。但是......关于设置窗口焦点的所有问题,我认为我现在有点过头了。 – MattM 2012-07-18 06:30:24

+0

这很公平,如果你从来没有用C++处理过Win32表单编程,SendMessage可能会非常少。你可以选择的另一种方法是使用'AppActivate'来设置焦点窗口,然后进行粘贴。如果你愿意,我可以使用上面的代码给你写一个单一的函数,你可以简单地调用'AppendTextToNotepad(text)',这样你就可以在闲暇时学习其他代码,但仍然有一个工作可靠的应用程序。 – 2012-07-18 06:56:00

+0

如果实施起来很简单,那就太棒了!我认为在这一点上,我也不确定在哪里粘贴我还没完全理解的代码,哈哈。请让我知道它在哪里。 再次感谢! – MattM 2012-07-18 07:26:53