Działa tylko na pierwszym otwartym formularzu ... powiedzmy, że mam MyForm1 i myForm2, więc otwieram myForm1, a następnie MyForm2, zdarzenie ClipboardChanged zostanie zgłoszone tylko w MyForm1. To znaczy w aplikacji MDI ...
serhio
Link nie działa. Jakaś kopia zapasowa, o której jesteś świadomy? Mimo to +1.
Patrick Hofman,
1
Dla leniwych: ustaw zegar, który tyka co 1 ms. Następnie przy każdym ticku sprawdzaj, czy zawartość twojego schowka uległa zmianie. Te haki podnoszą alarmy o wirusach i trojanach na moim komputerze.
C4d,
1
Przekazuje wszystkie pliki MSG systemu Windows do formularza i utrudnia debugowanie kodu
Podobnie, SharpClipboard jako biblioteka może przynieść więcej korzyści, ponieważ zawiera te same funkcje w jednej doskonałej bibliotece komponentów. Możesz wtedy uzyskać dostęp do jego ClipboardChangedzdarzenia i wykryć różne formaty danych podczas ich wycinania / kopiowania.
Willy Kimura,
78
Aby uzyskać kompletność, oto formant, którego używam w kodzie produkcyjnym. Po prostu przeciągnij z projektanta i kliknij dwukrotnie, aby utworzyć procedurę obsługi zdarzeń.
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;
}
}
}
Dobra robota! Twój kod wywołujący wydarzenie nie jest jednak bezpieczny dla wątków. Należy utworzyć kopię lokalną lub zainicjować zdarzenie z pustym delegatem. Zapomniałeś również słowa kluczowego „wydarzenie” w definicji Schowka Zmieniono :)
Ohad Schneider
1
@ohadsc Dziękuję za poprawki. O ile wiem, WndProc jest wywoływany w wątku interfejsu użytkownika. Ponieważ klasa pochodzi od Control, klienci powinni wywoływać ją również w wątku interfejsu użytkownika.
dbkk
Działa tylko na pierwszym otwartym formularzu ... powiedz, czy mam MyForm1 i myForm2, więc otwieram myForm1, potem MyForm2, zdarzenie ClipboardChanged zostanie wywołane tylko w MyForm1 ... to znaczy w aplikacji MDI ...
serhio
W jakiś sposób wywołanie SetClipboardViewer ustawia kod błędu Win32 1400: „Nieprawidłowy uchwyt okna.”. Ale nadal działa. Wydaje mi się to trochę dziwne.
metacircle
1
SharpClipboard jako biblioteka może przynosić więcej korzyści, ponieważ zawiera te same funkcje w jednej doskonałej bibliotece komponentów. Możesz wtedy uzyskać dostęp do jego ClipboardChangedzdarzenia i wykryć różne formaty danych podczas ich wycinania / kopiowania.
Willy Kimura
26
Miałem to wyzwanie w WPF i ostatecznie skorzystałem z podejścia opisanego poniżej. W przypadku formularzy systemu Windows istnieją doskonałe przykłady w innych miejscach w tej odpowiedzi, takie jak kontrolka ClipboardHelper.
W przypadku WPF nie możemy przesłonić WndProc, więc musimy go jawnie podłączyć za pomocą wywołania HwndSource AddHook przy użyciu źródła z okna. Odbiornik schowka nadal używa natywnego wywołania międzyoperacyjnego AddClipboardFormatListener.
Metody natywne:
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);
}
Klasa menedżera schowka:
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;
}
}
Jest to używane w oknie WPF przez dodanie zdarzenia w OnSourceInitialized lub nowszym, takim jak zdarzenie Window.Loaded lub podczas operacji. (kiedy mamy wystarczająco dużo informacji, aby użyć natywnych hooków):
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());
}
}
}
Używam tego podejścia w projekcie analizatora przedmiotów Path of Exile, ponieważ gra ujawnia informacje o przedmiocie za pośrednictwem schowka po naciśnięciu Ctrl-C.
Ok, więc to jest stary post, ale znaleźliśmy rozwiązanie, które wydaje się bardzo proste w porównaniu z obecnym zestawem odpowiedzi. Używamy WPF i chcieliśmy mieć własne niestandardowe polecenia (w menu kontekstowym) włączające i wyłączające, jeśli schowek zawiera tekst. Istnieje już ApplicationCommands. Wytnij, Kopiuj i Wklej, a te polecenia poprawnie reagują na zmianę schowka. Więc właśnie dodaliśmy następujący EventHandler.
Świetne rozwiązanie, bo to takie proste ... Dzięki!
okieh
1
To fantastyczne rozwiązanie konkretnego problemu związanego z włączaniem lub wyłączaniem polecenia wklejania. Niestety nie obejmuje on konkretnego scenariusza „zmieniono tekst” i nie będzie uruchamiany na przykład podczas kopiowania tekstu w wielu różnych wierszach.
Colin Dabritz
11
Jest na to wiele sposobów, ale ten jest moim ulubionym i działa dla mnie. Stworzyłem bibliotekę klas, aby inni mogli dodać projekt i dołączyć bibliotekę DLL, a następnie po prostu ją wywołać i używać w dowolnym miejscu w swoich aplikacjach.
Utwórz projekt biblioteki klas i nadaj mu nazwę ClipboardHelper.
Zastąp nazwę Class1 nazwą ClipboardMonitor.
Dodaj do niego poniższy kod.
Dodaj odniesienie do System.Windows.Forms.
Więcej kroków pod kodem.
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,
}
}
W innych projektach kliknij prawym przyciskiem myszy rozwiązanie i Dodaj -> Wyjście z projektu -> ClipboardHelper.csproj
W projekcie przejdź do i kliknij prawym przyciskiem myszy References -> Add Reference -> Solution -> Select ClipboardHelper.
W pliku klasy typu projektu przy użyciu ClipboardHelper.
Możesz teraz wpisać ClipboardMonitor.Start lub .Stop lub .OnClipboardChanged
Nigdy nie jest null, ponieważ konstruktor go ustawia. Jedyną rzeczą, którą zrobiłbym inaczej, jest wywołanie base.Dispose()metody dispose.
jedmao
Tak czy inaczej. Do celów weryfikacji, takich jak te, które wymieniłeś, powinieneś użyć IntPtr.Zero dla NULL (zauważ, że nie jest to równoważne z C # null) stackoverflow.com/questions/1456861/ ...
walter
1
ChangeClipboardChain jest wykonywany zawsze przy wyjściu we wszystkich próbkach MSDN
Walter
Celem jest usunięcie się z łańcucha przeglądarek schowka
walter
6
SharpClipboard jako biblioteka może przynosić więcej korzyści, ponieważ zawiera te same funkcje w jednej doskonałej bibliotece komponentów. Możesz wtedy uzyskać dostęp do jego ClipboardChangedzdarzenia i wykryć różne formaty danych podczas ich wycinania / kopiowania.
Możesz wybrać różne formaty danych, które chcesz monitorować:
var clipboard = new SharpClipboard();
clipboard.ObservableFormats.Texts = true;
clipboard.ObservableFormats.Files = true;
clipboard.ObservableFormats.Images = true;
clipboard.ObservableFormats.Others = true;
Oto przykład wykorzystujący jego ClipboardChangedzdarzenie:
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...
}
}
Możesz również dowiedzieć się, w jakiej aplikacji wystąpiło zdarzenie wycinania / kopiowania wraz z jej szczegółami:
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);
}
Istnieją również inne zdarzenia, takie jak MonitorChangedzdarzenie, które nasłuchuje, gdy monitorowanie schowka jest wyłączone, co oznacza, że możesz włączyć lub wyłączyć monitorowanie schowka w czasie wykonywania.
Oprócz tego wszystkiego, ponieważ jest to komponent, możesz go używać w widoku projektanta , przeciągając i upuszczając go do formularza systemu Windows, dzięki czemu każdy może bardzo łatwo dostosować jego opcje i pracować z wbudowanymi zdarzeniami.
SharpClipboard wydaje się być najlepszą opcją dla scenariuszy monitorowania schowka w .NET.
Odpowiedzi:
Myślę, że będziesz musiał użyć p / invoke:
[DllImport("User32.dll", CharSet=CharSet.Auto)] public static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer);
Zobacz ten artykuł na temat konfigurowania monitora schowka w języku C #
Zasadniczo rejestrujesz swoją aplikację jako przeglądarkę schowka za pomocą
_ClipboardViewerNext = SetClipboardViewer(this.Handle);
a następnie otrzymasz
WM_DRAWCLIPBOARD
wiadomość, którą możesz obsłużyć, zastępującWndProc
:protected override void WndProc(ref Message m) { switch ((Win32.Msgs)m.Msg) { case Win32.Msgs.WM_DRAWCLIPBOARD: // Handle clipboard changed break; // ... } }
(Jest więcej do zrobienia; przekazywanie rzeczy wzdłuż łańcucha schowka i wyrejestrowywanie widoku, ale możesz to uzyskać z artykułu )
źródło
ClipboardChanged
zdarzenia i wykryć różne formaty danych podczas ich wycinania / kopiowania.Aby uzyskać kompletność, oto formant, którego używam w kodzie produkcyjnym. Po prostu przeciągnij z projektanta i kliknij dwukrotnie, aby utworzyć procedurę obsługi zdarzeń.
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; } } }
źródło
ClipboardChanged
zdarzenia i wykryć różne formaty danych podczas ich wycinania / kopiowania.Miałem to wyzwanie w WPF i ostatecznie skorzystałem z podejścia opisanego poniżej. W przypadku formularzy systemu Windows istnieją doskonałe przykłady w innych miejscach w tej odpowiedzi, takie jak kontrolka ClipboardHelper.
W przypadku WPF nie możemy przesłonić WndProc, więc musimy go jawnie podłączyć za pomocą wywołania HwndSource AddHook przy użyciu źródła z okna. Odbiornik schowka nadal używa natywnego wywołania międzyoperacyjnego AddClipboardFormatListener.
Metody natywne:
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); }
Klasa menedżera schowka:
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; } }
Jest to używane w oknie WPF przez dodanie zdarzenia w OnSourceInitialized lub nowszym, takim jak zdarzenie Window.Loaded lub podczas operacji. (kiedy mamy wystarczająco dużo informacji, aby użyć natywnych hooków):
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()); } } }
Używam tego podejścia w projekcie analizatora przedmiotów Path of Exile, ponieważ gra ujawnia informacje o przedmiocie za pośrednictwem schowka po naciśnięciu Ctrl-C.
https://github.com/ColinDabritz/PoeItemAnalyzer
Mam nadzieję, że to pomoże komuś z obsługą zmiany schowka WPF!
źródło
ClipboardChanged?.Invoke
zobaczyć Korzystanie z nowego zerowego operatora warunkowego w C # 6 , sekcja Inne scenariuszeOk, więc to jest stary post, ale znaleźliśmy rozwiązanie, które wydaje się bardzo proste w porównaniu z obecnym zestawem odpowiedzi. Używamy WPF i chcieliśmy mieć własne niestandardowe polecenia (w menu kontekstowym) włączające i wyłączające, jeśli schowek zawiera tekst. Istnieje już ApplicationCommands. Wytnij, Kopiuj i Wklej, a te polecenia poprawnie reagują na zmianę schowka. Więc właśnie dodaliśmy następujący EventHandler.
ApplicationCommands.Paste.CanExecuteChanged += new EventHandler(Paste_CanExecuteChanged); private void Paste_CanExecuteChanged(object sender, EventArgs e) { ourVariable= Clipboard.ContainsText(); }
W ten sposób kontrolujemy CanExecute w naszym własnym poleceniu. Działa na to, czego potrzebowaliśmy i może pomoże innym.
źródło
Jest na to wiele sposobów, ale ten jest moim ulubionym i działa dla mnie. Stworzyłem bibliotekę klas, aby inni mogli dodać projekt i dołączyć bibliotekę DLL, a następnie po prostu ją wywołać i używać w dowolnym miejscu w swoich aplikacjach.
Ta odpowiedź została udzielona za pomocą tego .
Więcej kroków pod kodem.
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, } }
Możesz teraz wpisać ClipboardMonitor.Start lub .Stop lub .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... } }
źródło
Uważam, że jedno z wcześniejszych rozwiązań nie sprawdza wartości null w metodzie dispose:
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; } } }
źródło
base.Dispose()
metody dispose.SharpClipboard jako biblioteka może przynosić więcej korzyści, ponieważ zawiera te same funkcje w jednej doskonałej bibliotece komponentów. Możesz wtedy uzyskać dostęp do jego
ClipboardChanged
zdarzenia i wykryć różne formaty danych podczas ich wycinania / kopiowania.Możesz wybrać różne formaty danych, które chcesz monitorować:
var clipboard = new SharpClipboard(); clipboard.ObservableFormats.Texts = true; clipboard.ObservableFormats.Files = true; clipboard.ObservableFormats.Images = true; clipboard.ObservableFormats.Others = true;
Oto przykład wykorzystujący jego
ClipboardChanged
zdarzenie: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... } }
Możesz również dowiedzieć się, w jakiej aplikacji wystąpiło zdarzenie wycinania / kopiowania wraz z jej szczegółami:
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); }
Istnieją również inne zdarzenia, takie jak
MonitorChanged
zdarzenie, które nasłuchuje, gdy monitorowanie schowka jest wyłączone, co oznacza, że możesz włączyć lub wyłączyć monitorowanie schowka w czasie wykonywania.Oprócz tego wszystkiego, ponieważ jest to komponent, możesz go używać w widoku projektanta , przeciągając i upuszczając go do formularza systemu Windows, dzięki czemu każdy może bardzo łatwo dostosować jego opcje i pracować z wbudowanymi zdarzeniami.
SharpClipboard wydaje się być najlepszą opcją dla scenariuszy monitorowania schowka w .NET.
źródło
[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; } }
źródło