Funciona solo en el primer formulario abierto ... digamos que si tengo MyForm1 y myForm2, entonces abro myForm1, luego MyForm2, el evento ClipboardChanged se generará solo en MyForm1. Quiero decir, en una aplicación MDI ...
serhio
El vínculo está muerto. ¿Alguna copia de seguridad de la que tenga conocimiento? +1 no obstante.
Patrick Hofman
1
Para personas perezosas: configure un temporizador que marque 1 ms. Luego, con cada tick, verifique si su contenido del portapapeles cambió. Estos ganchos están generando alertas de virus y troyanos en mi computadora.
C4d
1
Se pasa cada ventanas MSG a la forma y lo hacen tan difícil de depurar el código
Del mismo modo, SharpClipboard como biblioteca podría ser más beneficioso ya que encapsula las mismas características en una biblioteca de componentes fina. Luego puede acceder a su ClipboardChangedevento y detectar varios formatos de datos cuando se cortan / copian.
Willy Kimura
78
Para completar, aquí está el control que estoy usando en el código de producción. Simplemente arrastre desde el diseñador y haga doble clic para crear el controlador de eventos.
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;
namespaceClipboardAssist {
// Must inherit Control, not Component, in order to have Handle
[DefaultEvent("ClipboardChanged")]
publicpartialclassClipboardMonitor : Control
{
IntPtr nextClipboardViewer;
publicClipboardMonitor()
{
this.BackColor = Color.Red;
this.Visible = false;
nextClipboardViewer = (IntPtr)SetClipboardViewer((int)this.Handle);
}
///<summary>/// Clipboard contents changed.///</summary>publicevent EventHandler<ClipboardChangedEventArgs> ClipboardChanged;
protectedoverridevoidDispose(bool disposing)
{
ChangeClipboardChain(this.Handle, nextClipboardViewer);
}
[DllImport("User32.dll")]
protectedstaticexternintSetClipboardViewer(int hWndNewViewer);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
publicstaticexternboolChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
publicstaticexternintSendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);
protectedoverridevoidWndProc(ref System.Windows.Forms.Message m)
{
// defined in winuser.hconstint WM_DRAWCLIPBOARD = 0x308;
constint WM_CHANGECBCHAIN = 0x030D;
switch (m.Msg)
{
case WM_DRAWCLIPBOARD:
OnClipboardChanged();
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;
}
}
voidOnClipboardChanged()
{
try
{
IDataObject iData = Clipboard.GetDataObject();
if (ClipboardChanged != null)
{
ClipboardChanged(this, new ClipboardChangedEventArgs(iData));
}
}
catch (Exception e)
{
// Swallow or pop-up, not sure// Trace.Write(e.ToString());
MessageBox.Show(e.ToString());
}
}
}
publicclassClipboardChangedEventArgs : EventArgs
{
publicreadonly IDataObject DataObject;
publicClipboardChangedEventArgs(IDataObject dataObject)
{
DataObject = dataObject;
}
}
}
¡Gran trabajo! Sin embargo, su código de llamada de eventos no es seguro para subprocesos. Debería crear una copia local o iniciar el evento con un delegado vacío. También olvidó la palabra clave 'evento' en la definición de ClipboardChanged :)
Ohad Schneider
1
@ohadsc Gracias por las correcciones. Hasta donde yo sé, se llama a WndProc en el hilo de la interfaz de usuario. Dado que la clase se deriva de Control, los clientes también deben llamarla en el hilo de la interfaz de usuario.
dbkk
Funciona solo en el primer formulario abierto ... digamos que si tengo MyForm1 y myForm2, entonces abro myForm1, luego MyForm2, el evento ClipboardChanged se generará solo en MyForm1 ... quiero decir, en una aplicación MDI ...
serhio
De alguna manera, su llamada a SetClipboardViewer establece el código de error 1400 de Win32: "Identificador de ventana no válido". Pero sigue funcionando. Esto me parece un poco extraño.
metacirculo
1
SharpClipboard como biblioteca podría ser más beneficioso ya que encapsula las mismas características en una biblioteca de componentes fina. Luego puede acceder a su ClipboardChangedevento y detectar varios formatos de datos cuando se cortan / copian.
Willy Kimura
26
Tuve este desafío en WPF y terminé usando el enfoque que se describe a continuación. Para formularios de Windows, hay excelentes ejemplos en otras partes de esta respuesta, como el control ClipboardHelper.
Para WPF, no podemos anular WndProc, por lo que tenemos que conectarlo explícitamente con una llamada HwndSource AddHook usando la fuente desde una ventana. El oyente del portapapeles todavía usa la llamada de interoperabilidad nativa AddClipboardFormatListener.
Métodos nativos:
internalstaticclassNativeMethods
{
// See http://msdn.microsoft.com/en-us/library/ms649021%28v=vs.85%29.aspxpublicconstint WM_CLIPBOARDUPDATE = 0x031D;
publicstatic IntPtr HWND_MESSAGE = new IntPtr(-3);
// See http://msdn.microsoft.com/en-us/library/ms632599%28VS.85%29.aspx#message_only
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
publicstaticexternboolAddClipboardFormatListener(IntPtr hwnd);
}
Clase de administrador del portapapeles:
using System.Windows;
using System.Windows.Interop;
publicclassClipboardManager
{
publicevent EventHandler ClipboardChanged;
publicClipboardManager(Window windowSource)
{
HwndSource source = PresentationSource.FromVisual(windowSource) as HwndSource;
if(source == null)
{
thrownew ArgumentException(
"Window source MUST be initialized first, such as in the Window's OnSourceInitialized handler."
, nameof(windowSource));
}
source.AddHook(WndProc);
// get window handle for interop
IntPtr windowHandle = new WindowInteropHelper(windowSource).Handle;
// register for clipboard events
NativeMethods.AddClipboardFormatListener(windowHandle);
}
privatevoidOnClipboardChanged()
{
ClipboardChanged?.Invoke(this, EventArgs.Empty);
}
privatestaticreadonly IntPtr WndProcSuccess = IntPtr.Zero;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, refbool handled)
{
if (msg == NativeMethods.WM_CLIPBOARDUPDATE)
{
OnClipboardChanged();
handled = true;
}
return WndProcSuccess;
}
}
Esto se usa en una ventana de WPF agregando el evento en OnSourceInitialized o posterior, como el evento Window.Loaded o durante la operación. (cuando tenemos suficiente información para usar los ganchos nativos):
publicpartialclassMainWindow : Window
{
publicMainWindow()
{
InitializeComponent();
}
protectedoverridevoidOnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
// Initialize the clipboard now that we have a window soruce to usevar windowClipboardManager = new ClipboardManager(this);
windowClipboardManager.ClipboardChanged += ClipboardChanged;
}
privatevoidClipboardChanged(object sender, EventArgs e)
{
// Handle your clipboard update here, debug logging example:if (Clipboard.ContainsText())
{
Debug.WriteLine(Clipboard.GetText());
}
}
}
Estoy usando este enfoque en un proyecto de analizador de elementos de Path of Exile, ya que el juego expone la información del elemento a través del portapapeles cuando presionas Ctrl-C.
Ok, esta es una publicación antigua, pero encontramos una solución que parece muy simple en comparación con el conjunto actual de respuestas. Estamos usando WPF y queríamos tener nuestros propios Comandos personalizados (en un ContextMenu) habilitados y deshabilitados si el Portapapeles contiene texto. Ya existe un ApplicationCommands.Cut, Copy and Paste y estos comandos responden correctamente al cambio del portapapeles. Así que acabamos de agregar el siguiente EventHandler.
Esta es una solución fantástica al problema específico de habilitar o deshabilitar el comando pegar. Desafortunadamente, no cubre el escenario específico "el texto cambió" y no se activará al copiar varias líneas de texto diferentes, por ejemplo.
Colin Dabritz
11
Hay varias formas de hacer esto, pero esta es mi favorita y me funciona. Creé una biblioteca de clases para que otros puedan agregar el proyecto e incluir la DLL y luego simplemente llamarla y usarla donde quieran dentro de sus aplicaciones.
Cree un proyecto de biblioteca de clases y asígnele el nombre ClipboardHelper.
Reemplace el nombre de Class1 con ClipboardMonitor.
Agregue el siguiente código en él.
Agregue la referencia System.Windows.Forms.
Más pasos bajo código.
using System;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;
namespaceClipboardHelper
{
publicstaticclassClipboardMonitor
{
publicdelegatevoidOnClipboardChangeEventHandler(ClipboardFormat format, object data);
publicstaticevent OnClipboardChangeEventHandler OnClipboardChange;
publicstaticvoidStart()
{
ClipboardWatcher.Start();
ClipboardWatcher.OnClipboardChange += (ClipboardFormat format, object data) =>
{
if (OnClipboardChange != null)
OnClipboardChange(format, data);
};
}
publicstaticvoidStop()
{
OnClipboardChange = null;
ClipboardWatcher.Stop();
}
classClipboardWatcher : Form
{
// static instance of this formprivatestatic ClipboardWatcher mInstance;
// needed to dispose this formstatic IntPtr nextClipboardViewer;
publicdelegatevoidOnClipboardChangeEventHandler(ClipboardFormat format, object data);
publicstaticevent OnClipboardChangeEventHandler OnClipboardChange;
// start listeningpublicstaticvoidStart()
{
// we can only have one instance if this classif (mInstance != null)
return;
var t = new Thread(new ParameterizedThreadStart(x => Application.Run(new ClipboardWatcher())));
t.SetApartmentState(ApartmentState.STA); // give the [STAThread] attribute
t.Start();
}
// stop listening (dispose form)publicstaticvoidStop()
{
mInstance.Invoke(new MethodInvoker(() =>
{
ChangeClipboardChain(mInstance.Handle, nextClipboardViewer);
}));
mInstance.Invoke(new MethodInvoker(mInstance.Close));
mInstance.Dispose();
mInstance = null;
}
// on load: (hide this window)protectedoverridevoidSetVisibleCore(boolvalue)
{
CreateHandle();
mInstance = this;
nextClipboardViewer = SetClipboardViewer(mInstance.Handle);
base.SetVisibleCore(false);
}
[DllImport("User32.dll", CharSet = CharSet.Auto)]
privatestaticextern IntPtr SetClipboardViewer(IntPtr hWndNewViewer);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
privatestaticexternboolChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
privatestaticexternintSendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);
// defined in winuser.hconstint WM_DRAWCLIPBOARD = 0x308;
constint WM_CHANGECBCHAIN = 0x030D;
protectedoverridevoidWndProc(ref Message m)
{
switch (m.Msg)
{
case WM_DRAWCLIPBOARD:
ClipChanged();
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;
}
}
staticreadonlystring[] formats = Enum.GetNames(typeof(ClipboardFormat));
privatevoidClipChanged()
{
IDataObject iData = Clipboard.GetDataObject();
ClipboardFormat? format = null;
foreach (var f in formats)
{
if (iData.GetDataPresent(f))
{
format = (ClipboardFormat)Enum.Parse(typeof(ClipboardFormat), f);
break;
}
}
object data = iData.GetData(format.ToString());
if (data == null || format == null)
return;
if (OnClipboardChange != null)
OnClipboardChange((ClipboardFormat)format, data);
}
}
}
publicenum ClipboardFormat : byte
{
///<summary>Specifies the standard ANSI text format. This static field is read-only.///</summary>///<filterpriority>1</filterpriority>
Text,
///<summary>Specifies the standard Windows Unicode text format. This static field/// is read-only.</summary>///<filterpriority>1</filterpriority>
UnicodeText,
///<summary>Specifies the Windows device-independent bitmap (DIB) format. This static/// field is read-only.</summary>///<filterpriority>1</filterpriority>
Dib,
///<summary>Specifies a Windows bitmap format. This static field is read-only.</summary>///<filterpriority>1</filterpriority>
Bitmap,
///<summary>Specifies the Windows enhanced metafile format. This static field is/// read-only.</summary>///<filterpriority>1</filterpriority>
EnhancedMetafile,
///<summary>Specifies the Windows metafile format, which Windows Forms does not/// directly use. This static field is read-only.</summary>///<filterpriority>1</filterpriority>
MetafilePict,
///<summary>Specifies the Windows symbolic link format, which Windows Forms does/// not directly use. This static field is read-only.</summary>///<filterpriority>1</filterpriority>
SymbolicLink,
///<summary>Specifies the Windows Data Interchange Format (DIF), which Windows Forms/// does not directly use. This static field is read-only.</summary>///<filterpriority>1</filterpriority>
Dif,
///<summary>Specifies the Tagged Image File Format (TIFF), which Windows Forms does/// not directly use. This static field is read-only.</summary>///<filterpriority>1</filterpriority>
Tiff,
///<summary>Specifies the standard Windows original equipment manufacturer (OEM)/// text format. This static field is read-only.</summary>///<filterpriority>1</filterpriority>
OemText,
///<summary>Specifies the Windows palette format. This static field is read-only.///</summary>///<filterpriority>1</filterpriority>
Palette,
///<summary>Specifies the Windows pen data format, which consists of pen strokes/// for handwriting software, Windows Forms does not use this format. This static/// field is read-only.</summary>///<filterpriority>1</filterpriority>
PenData,
///<summary>Specifies the Resource Interchange File Format (RIFF) audio format,/// which Windows Forms does not directly use. This static field is read-only.</summary>///<filterpriority>1</filterpriority>
Riff,
///<summary>Specifies the wave audio format, which Windows Forms does not directly/// use. This static field is read-only.</summary>///<filterpriority>1</filterpriority>
WaveAudio,
///<summary>Specifies the Windows file drop format, which Windows Forms does not/// directly use. This static field is read-only.</summary>///<filterpriority>1</filterpriority>
FileDrop,
///<summary>Specifies the Windows culture format, which Windows Forms does not directly/// use. This static field is read-only.</summary>///<filterpriority>1</filterpriority>
Locale,
///<summary>Specifies text consisting of HTML data. This static field is read-only.///</summary>///<filterpriority>1</filterpriority>
Html,
///<summary>Specifies text consisting of Rich Text Format (RTF) data. This static/// field is read-only.</summary>///<filterpriority>1</filterpriority>
Rtf,
///<summary>Specifies a comma-separated value (CSV) format, which is a common interchange/// format used by spreadsheets. This format is not used directly by Windows Forms./// This static field is read-only.</summary>///<filterpriority>1</filterpriority>
CommaSeparatedValue,
///<summary>Specifies the Windows Forms string class format, which Windows Forms/// uses to store string objects. This static field is read-only.</summary>///<filterpriority>1</filterpriority>
StringFormat,
///<summary>Specifies a format that encapsulates any type of Windows Forms object./// This static field is read-only.</summary>///<filterpriority>1</filterpriority>
Serializable,
}
}
En sus otros proyectos, haga clic derecho en la solución y Agregar -> Salir del proyecto -> ClipboardHelper.csproj
En su proyecto, vaya y haga clic con el botón derecho en Referencias -> Agregar referencia -> Solución -> Seleccionar ClipboardHelper.
En su archivo de clase del tipo de proyecto usando ClipboardHelper.
Ahora puede escribir ClipboardMonitor.Start o .Stop o .OnClipboardChanged
Nunca es nulo porque el constructor lo establece. Lo único que haría de manera diferente es llamar base.Dispose()al método de disposición.
jedmao
De todas formas. Para fines de verificación como los que ha enumerado, debe usar IntPtr.Zero para NULL (tenga en cuenta que no es equivalente a C # null) stackoverflow.com/questions/1456861/…
walter
1
ChangeClipboardChain se ejecuta siempre al salir en todas las muestras de MSDN
Walter
El propósito es eliminar sí mismo de la cadena de visor del portapapeles
Walter
6
SharpClipboard como biblioteca podría ser más beneficioso ya que encapsula las mismas características en una biblioteca de componentes fina. Luego puede acceder a su ClipboardChangedevento y detectar varios formatos de datos cuando se cortan / copian.
Puede elegir los distintos formatos de datos que desea monitorear:
var clipboard = new SharpClipboard();
clipboard.ObservableFormats.Texts = true;
clipboard.ObservableFormats.Files = true;
clipboard.ObservableFormats.Images = true;
clipboard.ObservableFormats.Others = true;
Aquí hay un ejemplo que usa su ClipboardChangedevento:
privatevoidClipboardChanged(Object sender, ClipboardChangedEventArgs e)
{
// Is the content copied of text type?if (e.ContentType == SharpClipboard.ContentTypes.Text)
{
// Get the cut/copied text.
Debug.WriteLine(clipboard.ClipboardText);
}
// Is the content copied of image type?elseif (e.ContentType == SharpClipboard.ContentTypes.Image)
{
// Get the cut/copied image.
Image img = clipboard.ClipboardImage;
}
// Is the content copied of file type?elseif (e.ContentType == SharpClipboard.ContentTypes.Files)
{
// Get the cut/copied file/files.
Debug.WriteLine(clipboard.ClipboardFiles.ToArray());
// ...or use 'ClipboardFile' to get a single copied file.
Debug.WriteLine(clipboard.ClipboardFile);
}
// If the cut/copied content is complex, use 'Other'.elseif (e.ContentType == SharpClipboard.ContentTypes.Other)
{
// Do something with 'e.Content' here...
}
}
También puede averiguar la aplicación en la que se produjo el evento de cortar / copiar junto con sus detalles:
privatevoidClipboardChanged(Object sender, SharpClipboard.ClipboardChangedEventArgs e)
{
// Gets the application's executable name.
Debug.WriteLine(e.SourceApplication.Name);
// Gets the application's window title.
Debug.WriteLine(e.SourceApplication.Title);
// Gets the application's process ID.
Debug.WriteLine(e.SourceApplication.ID.ToString());
// Gets the application's executable path.
Debug.WriteLine(e.SourceApplication.Path);
}
También hay otros eventos, como el MonitorChangedevento que escucha cuando se deshabilita la supervisión del portapapeles, lo que significa que puede habilitar o deshabilitar la supervisión del portapapeles en tiempo de ejecución.
Además de todo esto, dado que es un componente, puede usarlo en la Vista de diseñador arrastrándolo y soltándolo en un formulario de Windows, lo que hace que sea muy fácil para cualquiera personalizar sus opciones y trabajar con sus eventos incorporados.
SharpClipboard parece ser la mejor opción para escenarios de monitoreo de portapapeles en .NET.
Respuestas:
Creo que tendrás que usar algo de p / invoke:
[DllImport("User32.dll", CharSet=CharSet.Auto)] public static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer);
Consulte este artículo sobre cómo configurar un monitor de portapapeles en c #
Básicamente, registras tu aplicación como visor del portapapeles usando
_ClipboardViewerNext = SetClipboardViewer(this.Handle);
y luego recibirá el
WM_DRAWCLIPBOARD
mensaje, que puede manejar anulandoWndProc
:protected override void WndProc(ref Message m) { switch ((Win32.Msgs)m.Msg) { case Win32.Msgs.WM_DRAWCLIPBOARD: // Handle clipboard changed break; // ... } }
(Hay más por hacer; pasar cosas a lo largo de la cadena del portapapeles y anular el registro de su vista, pero puede obtenerlo en el artículo )
fuente
ClipboardChanged
evento y detectar varios formatos de datos cuando se cortan / copian.Para completar, aquí está el control que estoy usando en el código de producción. Simplemente arrastre desde el diseñador y haga doble clic para crear el controlador de eventos.
using System; using System.ComponentModel; using System.Runtime.InteropServices; using System.Windows.Forms; using System.Drawing; namespace ClipboardAssist { // Must inherit Control, not Component, in order to have Handle [DefaultEvent("ClipboardChanged")] public partial class ClipboardMonitor : Control { IntPtr nextClipboardViewer; public ClipboardMonitor() { this.BackColor = Color.Red; this.Visible = false; nextClipboardViewer = (IntPtr)SetClipboardViewer((int)this.Handle); } /// <summary> /// Clipboard contents changed. /// </summary> public event EventHandler<ClipboardChangedEventArgs> ClipboardChanged; protected override void Dispose(bool disposing) { ChangeClipboardChain(this.Handle, nextClipboardViewer); } [DllImport("User32.dll")] protected 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); protected override void WndProc(ref System.Windows.Forms.Message m) { // defined in winuser.h const int WM_DRAWCLIPBOARD = 0x308; const int WM_CHANGECBCHAIN = 0x030D; switch (m.Msg) { case WM_DRAWCLIPBOARD: OnClipboardChanged(); 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; } } void OnClipboardChanged() { try { IDataObject iData = Clipboard.GetDataObject(); if (ClipboardChanged != null) { ClipboardChanged(this, new ClipboardChangedEventArgs(iData)); } } catch (Exception e) { // Swallow or pop-up, not sure // Trace.Write(e.ToString()); MessageBox.Show(e.ToString()); } } } public class ClipboardChangedEventArgs : EventArgs { public readonly IDataObject DataObject; public ClipboardChangedEventArgs(IDataObject dataObject) { DataObject = dataObject; } } }
fuente
ClipboardChanged
evento y detectar varios formatos de datos cuando se cortan / copian.Tuve este desafío en WPF y terminé usando el enfoque que se describe a continuación. Para formularios de Windows, hay excelentes ejemplos en otras partes de esta respuesta, como el control ClipboardHelper.
Para WPF, no podemos anular WndProc, por lo que tenemos que conectarlo explícitamente con una llamada HwndSource AddHook usando la fuente desde una ventana. El oyente del portapapeles todavía usa la llamada de interoperabilidad nativa AddClipboardFormatListener.
Métodos nativos:
internal static class NativeMethods { // See http://msdn.microsoft.com/en-us/library/ms649021%28v=vs.85%29.aspx public const int WM_CLIPBOARDUPDATE = 0x031D; public static IntPtr HWND_MESSAGE = new IntPtr(-3); // See http://msdn.microsoft.com/en-us/library/ms632599%28VS.85%29.aspx#message_only [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool AddClipboardFormatListener(IntPtr hwnd); }
Clase de administrador del portapapeles:
using System.Windows; using System.Windows.Interop; public class ClipboardManager { public event EventHandler ClipboardChanged; public ClipboardManager(Window windowSource) { HwndSource source = PresentationSource.FromVisual(windowSource) as HwndSource; if(source == null) { throw new ArgumentException( "Window source MUST be initialized first, such as in the Window's OnSourceInitialized handler." , nameof(windowSource)); } source.AddHook(WndProc); // get window handle for interop IntPtr windowHandle = new WindowInteropHelper(windowSource).Handle; // register for clipboard events NativeMethods.AddClipboardFormatListener(windowHandle); } private void OnClipboardChanged() { ClipboardChanged?.Invoke(this, EventArgs.Empty); } private static readonly IntPtr WndProcSuccess = IntPtr.Zero; private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (msg == NativeMethods.WM_CLIPBOARDUPDATE) { OnClipboardChanged(); handled = true; } return WndProcSuccess; } }
Esto se usa en una ventana de WPF agregando el evento en OnSourceInitialized o posterior, como el evento Window.Loaded o durante la operación. (cuando tenemos suficiente información para usar los ganchos nativos):
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); // Initialize the clipboard now that we have a window soruce to use var windowClipboardManager = new ClipboardManager(this); windowClipboardManager.ClipboardChanged += ClipboardChanged; } private void ClipboardChanged(object sender, EventArgs e) { // Handle your clipboard update here, debug logging example: if (Clipboard.ContainsText()) { Debug.WriteLine(Clipboard.GetText()); } } }
Estoy usando este enfoque en un proyecto de analizador de elementos de Path of Exile, ya que el juego expone la información del elemento a través del portapapeles cuando presionas Ctrl-C.
https://github.com/ColinDabritz/PoeItemAnalyzer
¡Espero que esto ayude a alguien con el manejo de cambios del portapapeles de WPF!
fuente
ClipboardChanged?.Invoke
ver Usar el nuevo operador condicional nulo en C # 6 , sección Otros escenariosOk, esta es una publicación antigua, pero encontramos una solución que parece muy simple en comparación con el conjunto actual de respuestas. Estamos usando WPF y queríamos tener nuestros propios Comandos personalizados (en un ContextMenu) habilitados y deshabilitados si el Portapapeles contiene texto. Ya existe un ApplicationCommands.Cut, Copy and Paste y estos comandos responden correctamente al cambio del portapapeles. Así que acabamos de agregar el siguiente EventHandler.
ApplicationCommands.Paste.CanExecuteChanged += new EventHandler(Paste_CanExecuteChanged); private void Paste_CanExecuteChanged(object sender, EventArgs e) { ourVariable= Clipboard.ContainsText(); }
De hecho, estamos controlando el CanExecute en nuestro propio comando de esta manera. Funciona para lo que necesitábamos y tal vez ayude a otros.
fuente
Hay varias formas de hacer esto, pero esta es mi favorita y me funciona. Creé una biblioteca de clases para que otros puedan agregar el proyecto e incluir la DLL y luego simplemente llamarla y usarla donde quieran dentro de sus aplicaciones.
Esta respuesta se hizo con la ayuda de este .
Más pasos bajo código.
using System; using System.Windows.Forms; using System.Threading; using System.Runtime.InteropServices; namespace ClipboardHelper { public static class ClipboardMonitor { public delegate void OnClipboardChangeEventHandler(ClipboardFormat format, object data); public static event OnClipboardChangeEventHandler OnClipboardChange; public static void Start() { ClipboardWatcher.Start(); ClipboardWatcher.OnClipboardChange += (ClipboardFormat format, object data) => { if (OnClipboardChange != null) OnClipboardChange(format, data); }; } public static void Stop() { OnClipboardChange = null; ClipboardWatcher.Stop(); } class ClipboardWatcher : Form { // static instance of this form private static ClipboardWatcher mInstance; // needed to dispose this form static IntPtr nextClipboardViewer; public delegate void OnClipboardChangeEventHandler(ClipboardFormat format, object data); public static event OnClipboardChangeEventHandler OnClipboardChange; // start listening public static void Start() { // we can only have one instance if this class if (mInstance != null) return; var t = new Thread(new ParameterizedThreadStart(x => Application.Run(new ClipboardWatcher()))); t.SetApartmentState(ApartmentState.STA); // give the [STAThread] attribute t.Start(); } // stop listening (dispose form) public static void Stop() { mInstance.Invoke(new MethodInvoker(() => { ChangeClipboardChain(mInstance.Handle, nextClipboardViewer); })); mInstance.Invoke(new MethodInvoker(mInstance.Close)); mInstance.Dispose(); mInstance = null; } // on load: (hide this window) protected override void SetVisibleCore(bool value) { CreateHandle(); mInstance = this; nextClipboardViewer = SetClipboardViewer(mInstance.Handle); base.SetVisibleCore(false); } [DllImport("User32.dll", CharSet = CharSet.Auto)] private static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer); [DllImport("User32.dll", CharSet = CharSet.Auto)] private static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext); [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam); // defined in winuser.h const int WM_DRAWCLIPBOARD = 0x308; const int WM_CHANGECBCHAIN = 0x030D; protected override void WndProc(ref Message m) { switch (m.Msg) { case WM_DRAWCLIPBOARD: ClipChanged(); 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; } } static readonly string[] formats = Enum.GetNames(typeof(ClipboardFormat)); private void ClipChanged() { IDataObject iData = Clipboard.GetDataObject(); ClipboardFormat? format = null; foreach (var f in formats) { if (iData.GetDataPresent(f)) { format = (ClipboardFormat)Enum.Parse(typeof(ClipboardFormat), f); break; } } object data = iData.GetData(format.ToString()); if (data == null || format == null) return; if (OnClipboardChange != null) OnClipboardChange((ClipboardFormat)format, data); } } } public enum ClipboardFormat : byte { /// <summary>Specifies the standard ANSI text format. This static field is read-only. /// </summary> /// <filterpriority>1</filterpriority> Text, /// <summary>Specifies the standard Windows Unicode text format. This static field /// is read-only.</summary> /// <filterpriority>1</filterpriority> UnicodeText, /// <summary>Specifies the Windows device-independent bitmap (DIB) format. This static /// field is read-only.</summary> /// <filterpriority>1</filterpriority> Dib, /// <summary>Specifies a Windows bitmap format. This static field is read-only.</summary> /// <filterpriority>1</filterpriority> Bitmap, /// <summary>Specifies the Windows enhanced metafile format. This static field is /// read-only.</summary> /// <filterpriority>1</filterpriority> EnhancedMetafile, /// <summary>Specifies the Windows metafile format, which Windows Forms does not /// directly use. This static field is read-only.</summary> /// <filterpriority>1</filterpriority> MetafilePict, /// <summary>Specifies the Windows symbolic link format, which Windows Forms does /// not directly use. This static field is read-only.</summary> /// <filterpriority>1</filterpriority> SymbolicLink, /// <summary>Specifies the Windows Data Interchange Format (DIF), which Windows Forms /// does not directly use. This static field is read-only.</summary> /// <filterpriority>1</filterpriority> Dif, /// <summary>Specifies the Tagged Image File Format (TIFF), which Windows Forms does /// not directly use. This static field is read-only.</summary> /// <filterpriority>1</filterpriority> Tiff, /// <summary>Specifies the standard Windows original equipment manufacturer (OEM) /// text format. This static field is read-only.</summary> /// <filterpriority>1</filterpriority> OemText, /// <summary>Specifies the Windows palette format. This static field is read-only. /// </summary> /// <filterpriority>1</filterpriority> Palette, /// <summary>Specifies the Windows pen data format, which consists of pen strokes /// for handwriting software, Windows Forms does not use this format. This static /// field is read-only.</summary> /// <filterpriority>1</filterpriority> PenData, /// <summary>Specifies the Resource Interchange File Format (RIFF) audio format, /// which Windows Forms does not directly use. This static field is read-only.</summary> /// <filterpriority>1</filterpriority> Riff, /// <summary>Specifies the wave audio format, which Windows Forms does not directly /// use. This static field is read-only.</summary> /// <filterpriority>1</filterpriority> WaveAudio, /// <summary>Specifies the Windows file drop format, which Windows Forms does not /// directly use. This static field is read-only.</summary> /// <filterpriority>1</filterpriority> FileDrop, /// <summary>Specifies the Windows culture format, which Windows Forms does not directly /// use. This static field is read-only.</summary> /// <filterpriority>1</filterpriority> Locale, /// <summary>Specifies text consisting of HTML data. This static field is read-only. /// </summary> /// <filterpriority>1</filterpriority> Html, /// <summary>Specifies text consisting of Rich Text Format (RTF) data. This static /// field is read-only.</summary> /// <filterpriority>1</filterpriority> Rtf, /// <summary>Specifies a comma-separated value (CSV) format, which is a common interchange /// format used by spreadsheets. This format is not used directly by Windows Forms. /// This static field is read-only.</summary> /// <filterpriority>1</filterpriority> CommaSeparatedValue, /// <summary>Specifies the Windows Forms string class format, which Windows Forms /// uses to store string objects. This static field is read-only.</summary> /// <filterpriority>1</filterpriority> StringFormat, /// <summary>Specifies a format that encapsulates any type of Windows Forms object. /// This static field is read-only.</summary> /// <filterpriority>1</filterpriority> Serializable, } }
Ahora puede escribir ClipboardMonitor.Start o .Stop o .OnClipboardChanged
using ClipboardHelper; namespace Something.Something.DarkSide { public class MainWindow { public MainWindow() { InitializeComponent(); Loaded += MainWindow_Loaded; } void MainWindow_Loaded(object sender, RoutedEventArgs e) { ClipboardMonitor.OnClipboardChange += ClipboardMonitor_OnClipboardChange; ClipboardMonitor.Start(); } private void ClipboardMonitor_OnClipboardChange(ClipboardFormat format, object data) { // Do Something... } }
fuente
Creo que una de las soluciones anteriores no verifica un nulo en el método de eliminación:
using System; using System.ComponentModel; using System.Runtime.InteropServices; using System.Windows.Forms; using System.Drawing; namespace ClipboardAssist { // Must inherit Control, not Component, in order to have Handle [DefaultEvent("ClipboardChanged")] public partial class ClipboardMonitor : Control { IntPtr nextClipboardViewer; public ClipboardMonitor() { this.BackColor = Color.Red; this.Visible = false; nextClipboardViewer = (IntPtr)SetClipboardViewer((int)this.Handle); } /// <summary> /// Clipboard contents changed. /// </summary> public event EventHandler<ClipboardChangedEventArgs> ClipboardChanged; protected override void Dispose(bool disposing) { if(nextClipboardViewer != null) ChangeClipboardChain(this.Handle, nextClipboardViewer); } [DllImport("User32.dll")] protected 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); protected override void WndProc(ref System.Windows.Forms.Message m) { // defined in winuser.h const int WM_DRAWCLIPBOARD = 0x308; const int WM_CHANGECBCHAIN = 0x030D; switch (m.Msg) { case WM_DRAWCLIPBOARD: OnClipboardChanged(); 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; } } void OnClipboardChanged() { try { IDataObject iData = Clipboard.GetDataObject(); if (ClipboardChanged != null) { ClipboardChanged(this, new ClipboardChangedEventArgs(iData)); } } catch (Exception e) { // Swallow or pop-up, not sure // Trace.Write(e.ToString()); MessageBox.Show(e.ToString()); } } } public class ClipboardChangedEventArgs : EventArgs { public readonly IDataObject DataObject; public ClipboardChangedEventArgs(IDataObject dataObject) { DataObject = dataObject; } } }
fuente
base.Dispose()
al método de disposición.SharpClipboard como biblioteca podría ser más beneficioso ya que encapsula las mismas características en una biblioteca de componentes fina. Luego puede acceder a su
ClipboardChanged
evento y detectar varios formatos de datos cuando se cortan / copian.Puede elegir los distintos formatos de datos que desea monitorear:
var clipboard = new SharpClipboard(); clipboard.ObservableFormats.Texts = true; clipboard.ObservableFormats.Files = true; clipboard.ObservableFormats.Images = true; clipboard.ObservableFormats.Others = true;
Aquí hay un ejemplo que usa su
ClipboardChanged
evento:private void ClipboardChanged(Object sender, ClipboardChangedEventArgs e) { // Is the content copied of text type? if (e.ContentType == SharpClipboard.ContentTypes.Text) { // Get the cut/copied text. Debug.WriteLine(clipboard.ClipboardText); } // Is the content copied of image type? else if (e.ContentType == SharpClipboard.ContentTypes.Image) { // Get the cut/copied image. Image img = clipboard.ClipboardImage; } // Is the content copied of file type? else if (e.ContentType == SharpClipboard.ContentTypes.Files) { // Get the cut/copied file/files. Debug.WriteLine(clipboard.ClipboardFiles.ToArray()); // ...or use 'ClipboardFile' to get a single copied file. Debug.WriteLine(clipboard.ClipboardFile); } // If the cut/copied content is complex, use 'Other'. else if (e.ContentType == SharpClipboard.ContentTypes.Other) { // Do something with 'e.Content' here... } }
También puede averiguar la aplicación en la que se produjo el evento de cortar / copiar junto con sus detalles:
private void ClipboardChanged(Object sender, SharpClipboard.ClipboardChangedEventArgs e) { // Gets the application's executable name. Debug.WriteLine(e.SourceApplication.Name); // Gets the application's window title. Debug.WriteLine(e.SourceApplication.Title); // Gets the application's process ID. Debug.WriteLine(e.SourceApplication.ID.ToString()); // Gets the application's executable path. Debug.WriteLine(e.SourceApplication.Path); }
También hay otros eventos, como el
MonitorChanged
evento que escucha cuando se deshabilita la supervisión del portapapeles, lo que significa que puede habilitar o deshabilitar la supervisión del portapapeles en tiempo de ejecución.Además de todo esto, dado que es un componente, puede usarlo en la Vista de diseñador arrastrándolo y soltándolo en un formulario de Windows, lo que hace que sea muy fácil para cualquiera personalizar sus opciones y trabajar con sus eventos incorporados.
SharpClipboard parece ser la mejor opción para escenarios de monitoreo de portapapeles en .NET.
fuente
[DllImport("User32.dll", CharSet = CharSet.Auto)] public static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer); private IntPtr _ClipboardViewerNext; private void Form1_Load(object sender, EventArgs e) { _ClipboardViewerNext = SetClipboardViewer(this.Handle); } protected override void WndProc(ref System.Windows.Forms.Message m) { const int WM_DRAWCLIPBOARD = 0x308; switch (m.Msg) { case WM_DRAWCLIPBOARD: //Clipboard is Change //your code.............. break; default: base.WndProc(ref m); break; } }
fuente