C# 使用线程将图像复制到剪贴板并管理线程

C# 使用线程将图像复制到剪贴板并管理线程,c#,winforms,C#,Winforms,我有一个将图像保存在图片框中的应用程序。按ctrl+C时,它会将图像复制到剪贴板。我使用一个线程来执行实际的剪贴板操作 protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if (keyData == (Keys.Control | Keys.C)) { clipboardThread = new Thread(copy_to_clipboard); clip

我有一个将图像保存在图片框中的应用程序。按ctrl+C时,它会将图像复制到剪贴板。我使用一个线程来执行实际的剪贴板操作

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    if (keyData == (Keys.Control | Keys.C))
    {
        clipboardThread = new Thread(copy_to_clipboard);
        clipboardThread.SetApartmentState(ApartmentState.STA);
        clipboardThread.Start();
        return true;
    }
    return base.ProcessCmdKey(ref msg, keyData);
}

private void copy_to_clipboard()
{
    if (pic_display.Image != null)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            clipboardStatus.Text = "Copying image to clipboard...";
            pic_display.Image.Save(stream, ImageFormat.Png);
            var data = new DataObject("PNG", stream);
            Clipboard.Clear();
            Clipboard.SetDataObject(data, true);
            clipboardStatus.Text = "Copied successfully!";
        }
    }
}
现在,如果我重复发送ctrl+C(例如:按住键),就会产生一个新线程。如何更改代码以便重新使用剪贴板线程,并让它告诉我当前是否正在复制数据,以便在它仍在工作时不尝试执行另一个复制命令

更新

现在它起作用了

我已将其更改为使用后台工作程序

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    if (keyData == (Keys.Control | Keys.C))
    {
        if (!backgroundWorker1.IsBusy)
            backgroundWorker1.RunWorkerAsync();
        return true;
    }
    return base.ProcessCmdKey(ref msg, keyData);
}

private void copy_to_clipboard()
{
    using (var stream = new MemoryStream())
    {
        clipboardStatus.Text = "Copying image to clipboard...";
        pic_display.Invoke((Action)(() => {
            if (pic_display.Image != null)
              pic_display.Image.Save(stream, ImageFormat.Png); 
        }));
        if (stream.Position == 0) return; // No image was saved
        var data = new DataObject("PNG", stream);
        BeginInvoke ( (Action) ( ()=> {
            Clipboard.Clear();
            Clipboard.SetDataObject(data, true);
        }
        clipboardStatus.Text = "Copied successfully!";
    }
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    copy_to_clipboard();
}
但是现在,在上发生了一个异常

Clipboard.Clear()

Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it.

试试这样:

object lockObj = new object();

        private void copy_to_clipboard()
        {
            lock (lockObj)
            {
                if (pictureBox1.Image != null)
                    {
                        using (MemoryStream stream = new MemoryStream())
                        {
                            clipboardStatus.Text = "Copying image to clipboard...";                            
                            pictureBox1.Image.Save(stream, ImageFormat.Png);
                            var data = new DataObject("PNG", stream);
                            Clipboard.Clear();
                            Clipboard.SetDataObject(data, true);                            
                            clipboardStatus.Text = "Copied successfully!";
                        }
                    } 
            }
        }

首先,您应该在从后台线程访问UI控件时调用:

private void copy_to_clipboard()
{
    using (var stream = new MemoryStream())
    {
        clipboardStatus.Text = "Copying image to clipboard...";
        pic_display.Invoke((Action)()=> { 
            if (pic_display.Image != null)
              pic_display.Image.Save(stream, ImageFormat.Png); 
        });
        if (stream.Position == 0) return; // No image was saved
        var data = new DataObject("PNG", stream);
        Clipboard.Clear();
        Clipboard.SetDataObject(data, true);
        clipboardStatus.Text = "Copied successfully!";
    }
}

然后从后台工作人员那里调用它。网络上有很多这样的例子。

你不能将线程作为成员存储吗?除非我误解了这个问题,否则Thread.IsAlive会做你想做的事。但是,如果您正在修改UI控件,正如上面的海报所说的,您应该真正使用Invoke来确保这在UI线程上发生


我建议在单击过程中执行保存操作——无论如何,您都应该在UI线程上执行保存操作。您可以使用invokererequired/Invoke;但是,您无法控制保存何时接管UI线程。这可能是一个明显的时间后,点击量和令人不安的用户。或者,可能是在用户更改图像之后。很糟糕的是,它需要离开UI线程的任何时间;但是从可用性的角度来看,点击越近越好。在这种情况下,可能类似于:


我想象你可以把它构造成一个无限循环,反复尝试锁定一个可数信号量来阻止它自己。然后,您可以减少控制线程的信号量,使其运行一次循环,然后再次阻塞。这可能需要多长时间?除非图像是巨大的,否则它几乎肯定不是应该放在背景线上的东西。此外,如果
picu display
是PictureBox,那么您已经违反了规则。您不应该从后台线程与UI控件交互。@ChrisShain因为我正在将其转换为png,所以可能需要一秒钟的时间。我应该使用backgroundworker吗?是的,在调用Image.Save时应该使用invoke。我将添加作为答案。我切换到
线程
,因为我收到一条消息,说线程单元状态必须是STA。尽管在这段代码中,VS(2008)说它不能将lambda表达式转换为委托,因为它不是委托类型。是的,你不能使用BackgroundWorker,因为它使用线程池线程,你没有任何权限将其更改为STA。有关lambda问题,请参阅我的编辑。STA问题无论如何都是一个危险的话题。您不需要它是STA,因为您不应该从后台线程触摸UI。你应该调用UI线程,这是STA。奇怪的是,它看起来应该可以工作,但现在我得到了一个语法错误。我尝试将整个lambda函数包装在
Action(…)
中,但是调用方法说它无法将void转换为delegateoh,nvm它是
(Action)(()=>{…})

protected Thread clipboardThread;

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{

if (keyData == (Keys.Control | Keys.C))
{
    if (clipBoardThread == null)
    {
       clipboardThread = new Thread(copy_to_clipboard);
       clipboardThread.SetApartmentState(ApartmentState.STA);
       clipboardThread.IsBackGround = false;
    }
    if (!clipboardThread.IsAlive)
    {
       clipboardThread.Start();
    }
    return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}

private void copy_to_clipboard()
{
if (pic_display.Image != null)
{
    using (MemoryStream stream = new MemoryStream())
    {
        clipboardStatus.Text = "Copying image to clipboard...";
        pic_display.Image.Save(stream, ImageFormat.Png);
        var data = new DataObject("PNG", stream);
        Clipboard.Clear();
        Clipboard.SetDataObject(data, true);
        clipboardStatus.Text = "Copied successfully!";
    }
}
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    if (keyData == (Keys.Control | Keys.C))
    {
        if (pic_display.Image != null)
        {
            MemoryStream stream = new MemoryStream())
            clipboardStatus.Text = "Copying image to clipboard...";
            pic_display.Image.Save(stream, ImageFormat.Png);

            clipboardThread = new Thread(copy_to_clipboard);
            clipboardThread.SetApartmentState(ApartmentState.STA);
            clipboardThread.Start(stream);
        }
        return true;
    }
    return base.ProcessCmdKey(ref msg, keyData);
}

private void copy_to_clipboard(object state)
{
    var stream = (Stream) state;
    try
    {
        var data = new DataObject("PNG", stream);
        Clipboard.Clear();
        Clipboard.SetDataObject(data, true);
        BeginInvoke((MethodInvoker) (() => clipboardStatus.Text = "Copied successfully!"));
    }
    finally
    {
        stream.Dispose();
    }
}