Reputation: 19546
I have an application that holds an image in a picture box. When you press ctrl+C it copies the image to clipboard. I use a thread to perform the actual clipboard operations.
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!";
}
}
}
Right now, if I repeatly send ctrl+C (eg: hold the keys down), a new thread would be spawned. How can I change the code so that I re-use the clipboard thread, and have it tell me whether it's currently copying data or not so that I don't try to execute another copy command while it's still working.
UPDATE
Now it works
I've changed it to use a background worker
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();
}
But now an exception occurs on
Clipboard.Clear();
saying
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.
Upvotes: 0
Views: 4428
Reputation: 35869
I would recommend doing the Save during the click--you should be doing it on the UI thread anyway. You could use InvokeRequired/Invoke; but then you don't have any control over when that Save is going to take over the UI thread. It could be a noticeable amount of time after the Click and be disconcerting to the user. Or, it could be after the user changes the image. It sucks that it will take any time away from the UI thread; but the closer to the Click the better from a usability standpoint. In which case, maybe something like:
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();
}
}
Upvotes: 1
Reputation: 51359
First off, you should be invoking when accessing UI controls from a background thread:
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!";
}
}
Then invoke that from a BackgroundWorker. Lots of examples of that are available on the web.
Upvotes: 1
Reputation: 516
Can't you just store the thread as a member? Unless I'm misunderstanding the question, Thread.IsAlive will do what you want. However if you are modifying UI controls as a poster above said you should really use Invoke to make sure this happens on the UI thread.
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!";
}
}
}
Upvotes: 0
Reputation: 19296
Try something like that:
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!";
}
}
}
}
Upvotes: 0