Reputation: 1403
I am using WndProc
and WM_DRAWCLIPBOARD
to monitor the Windows clipboard and save text and images copied. It's working well except for one scenario. I am using NotifyIcon
and have the application docked to the notification area by default (ShowInTaskbar
= False, WindowState
= Minimized). The copy logic works well except after I open my form. When I right click the icon for the app and click Open, WM_DRAWCLIPBOARD
isn't called anymore when I copy a piece of text or image, although WndProc
is called constantly.
Does NotifyIcon
use a different thread than the main UI, if so it could be that the NotifyIcon
thread isn't listening to the UI thread. Note that I'm not actually Form.Show()
ing, rather I
m just maximizing the form and showing the taskbar icon. The opposite is done when the form is "closed". Here is my code I am using. It's just one class/form, so here is all of it.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Copy_Storer
{
[DefaultEvent("ClipboardChanged")]
public partial class frmMain : Form
{
IntPtr _NextClipboardViewer;
bool _AppStarting = true;
public frmMain()
{
InitializeComponent();
_NextClipboardViewer = (IntPtr)SetClipboardViewer((int)this.Handle);
}
private void frmMain_Load(object sender, EventArgs e)
{
nfyApp.ShowBalloonTip(10000);
cmsCSRightClick.ItemClicked += new ToolStripItemClickedEventHandler(cmsCSRightClick_ItemClicked);
_AppStarting = false;
}
[DllImport("User32.dll")]
public static extern int
SetClipboardViewer(int hWndNewViewer);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool
ChangeClipboardChain(IntPtr hWndRemove,
IntPtr hWndNewNext);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hwnd, int wMsg,
IntPtr wParam,
IntPtr lParam);
void DisplayClipboardData()
{
object dataConverted = null;
string destinationFileName = string.Empty;
string destinationFilePath = string.Empty;
string dataType = string.Empty;
// Problem: After opening the form, the clipboard monitoring function stops working.
try
{
IDataObject iData = new DataObject();
iData = Clipboard.GetDataObject();
if (iData.GetDataPresent(DataFormats.Text) || iData.GetDataPresent(DataFormats.UnicodeText) ||
iData.GetDataPresent(DataFormats.Rtf))
{
// First check if the file already exists.
dataConverted = iData.GetData(DataFormats.Text); //http://social.msdn.microsoft.com/Forums/vstudio/en-US/9a09cb14-5eb3-4b74-9cf1-ac9e0ae641fc/convert-string-to-unicode?forum=csharpgeneral
destinationFileName = string.Format("{0} {1}.txt", "CopiedText", DateTime.Today.ToString("MM-dd-yy"));
destinationFilePath = string.Format("{0}\\{1}", Path.GetTempPath(), destinationFileName);
dataType = DataFormats.Text.ToString();
if (File.Exists(destinationFilePath))
{
// Append to the file
File.AppendAllText(destinationFilePath, string.Format("\r\n{0}", dataConverted));
}
else
{
// Create the file
File.WriteAllText(destinationFilePath, dataConverted.ToString());
}
}
else if (iData.GetDataPresent(DataFormats.Bitmap))
{
// Works for most (or all?) image types.
dataConverted = iData.GetData(DataFormats.Bitmap);
destinationFileName = string.Format("{0} {1} {2}.bmp", "CopiedImage", new Random().Next(1, 10000).ToString(), DateTime.Today.ToString("MM-dd-yy"));
destinationFilePath = string.Format("{0}\\{1}", Path.GetTempPath(), destinationFileName);
dataType = DataFormats.Bitmap.ToString();
while (File.Exists(destinationFilePath))
{
// Write a new path.
destinationFileName = string.Format("{0} {1} {2}.bmp", "CopiedImage", new Random().Next(1, 100000).ToString(), DateTime.Today.ToString("MM-dd-yy"));
destinationFilePath = string.Format("{0}\\{1}", Path.GetTempPath(), destinationFileName);
}
Bitmap copiedImage = (Bitmap)dataConverted;
copiedImage.Save(destinationFilePath);
}
// Add file to grid.
FileInfo f = new FileInfo(destinationFilePath);
string[] newRow = new string[] {destinationFileName, destinationFilePath, dataType, f.Length.ToString()};
dgvFilesCopied.Rows.Add(newRow);
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
protected override void Dispose(bool disposing)
{
ChangeClipboardChain(this.Handle, _NextClipboardViewer);
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
protected override void WndProc(ref System.Windows.Forms.Message m)
{
// When user opens main form, WndProc is still reacting
// defined in winuser.h
const int WM_DRAWCLIPBOARD = 0x308;
const int WM_CHANGECBCHAIN = 0x030D;
bool randomValue = true;
if (m.Msg == /*WM_SIZE*/ 0x0005)
{
if (this.WindowState == FormWindowState.Minimized) randomValue = false;
}
switch (m.Msg)
{
case WM_DRAWCLIPBOARD:
if (!_AppStarting)
{
DisplayClipboardData();
SendMessage(_NextClipboardViewer, m.Msg, m.WParam, m.LParam);
}
break;
case WM_CHANGECBCHAIN:
if (m.WParam == _NextClipboardViewer)
_NextClipboardViewer = m.LParam;
else
SendMessage(_NextClipboardViewer, m.Msg, m.WParam, m.LParam);
break;
default:
base.WndProc(ref m);
break;
}
}
private void cmsCSRightClick_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
switch (e.ClickedItem.Text)
{
case "Open":
// We want to show the form.
ShowHideMainForm(true);
break;
}
}
private void nfyApp_MouseDoubleClick(object sender, MouseEventArgs e)
{
// Show files saved
ShowHideMainForm(true);
if (dgvFilesCopied.Rows.Count < 1)
{
lblHeader.Text = "No files have been copied recently.";
}
}
private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
{
// We only want to hide the form when using the X button of the form.
ShowHideMainForm(false);
e.Cancel = true;
// The form/app can be closed when the icon is right clicked -> Close.
}
private void ShowHideMainForm(bool show)
{
if (show)
{
this.ShowInTaskbar = true;
this.WindowState = FormWindowState.Normal;
}
else
{
this.ShowInTaskbar = false;
this.WindowState = FormWindowState.Minimized;
}
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
this.Close();
}
}
}
Would appreciate another set of eyes. Thanks.
Upvotes: 0
Views: 618
Reputation: 941990
_NextClipboardViewer = (IntPtr)SetClipboardViewer((int)this.Handle);
You put this code in the wrong spot. The Handle
property is not guaranteed to be a stable property, it can change when you modify certain properties of the Form. Like the ShowInTaskbar property, under the hood it is a style flag that's passed to CreateWindowEx(). Changing it requires the window to be recreated. That changes the Handle, now the viewer chain is broken.
You need to do it like this instead:
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
_NextClipboardViewer = SetClipboardViewer(this.Handle);
}
protected override void OnHandleDestroyed(EventArgs e) {
ChangeClipboardChain(this.Handle, _NextClipboardViewer);
base.OnHandleDestroyed(e);
}
You can now also set a breakpoint on OnHandleCreated() and see what statement in your code caused this to happen. The second break was the original troublemaker. Not anymore. Note that your declaration for SetClipboardViewer() is wrong.
Upvotes: 3