Laden...

Clipboard.IsCurrent() funktioniert nicht ?!?!

Erstellt von Weressated vor einem Jahr Letzter Beitrag vor einem Jahr 802 Views
Weressated Themenstarter:in
28 Beiträge seit 2023
vor einem Jahr
Clipboard.IsCurrent() funktioniert nicht ?!?!

Hallo.

Ich muss für eine Anwendung die Zwischenablage (Clipboard) auf Änderungen überwachen. Dazu wird mit AddClipboardFormatListener() das Hauptfenster der WPF-Anwendung registriert, mit HwndSource.AddHook() ein Hook auf der WndProc() installiert, und anschließend können dann die WM_CLIPBOARDUPDATE Nachrichten verarbeitet werden:

private const int WM_CLIPBOARDUPDATE = 0x031D;

protected override void OnSourceInitialized(EventArgs e)
{
	base.OnSourceInitialized(e);
	HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
	if (source != null)
	{
		source.AddHook(WndProc);
		AddClipboardFormatListener(m_hWndSelf = source.Handle);
	}
}

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
	switch (msg)
	{
		case WM_CLIPBOARDUPDATE:
			[...]
			handled = true;
			break;
	}
	return IntPtr.Zero;
}

[DllImport("user32.dll", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AddClipboardFormatListener(IntPtr hWnd);

Das klappt so weit auch. Das Problem ist jetzt, dass Windows bei jeder Clipboard-Änderung häufig ++:::

Laut Google ist das anscheinend "normal" so und man muss damit umgehen 🙄

Natürlich soll aber bei einer Änderung des Clipboard-Inhalt der neue Inhalt nur ++:::

Meine Idee war daher, den Clipboard-Inhalt mit Clipboard.GetDataObject() zu lesen, wenn ein WM_CLIPBOARDUPDATE rein kommt. Außerdem wird der alte Inhalt als IDataObject in einer statischen Variable gespeichert. Daher kann bei jeder eingehenden WM_CLIPBOARDUPDATE Message mittels Clipboard.IsCurrent() geprüft werden, ob sich der Inhalt seit dem vorhergehenden WM_CLIPBOARDUPDATE tatsächlich geändert hat (oder eben nicht).

Leider funktioniert Clipboard.IsCurrent() anscheinend gar nicht ?!?! 😡

Die Funktion liefert immer false zurück, selbst wenn sich der Clipboard-Inhalt definitiv nicht geändert hat, seit der vorhergehenden WM_CLIPBOARDUPDATE Message.

Sogar folgender Code, der den Clipboard-Inhalt holt und sofort prüft, ob der Inhalt noch aktuell ist, liefert bei meinem Test immer false zurück:

IDateObject data = Clipboard.GetDataObject();
Clipboard.IsCurrent(data); // <-- false !!!

Wie kann das denn sein? 🤔

Returns
true if the specified data object matches what is on the system Clipboard; otherwise, false. Clipboard.IsCurrent(IDataObject) Method (System.Windows)

Danke!

J
251 Beiträge seit 2012
vor einem Jahr
Weressated Themenstarter:in
28 Beiträge seit 2023
vor einem Jahr

Danke! Du hast recht, die Win32 API Dokumentation von OleIsCurrentClipboard() sagt es:

Hinweise
OleIsCurrentClipboard() funktioniert nur für das Datenobjekt, das in der OleSetClipboard()-Funktion verwendet wird. Es kann vom Verbraucher des Datenobjekts nicht aufgerufen werden, um festzustellen, ob das Objekt, das sich in der Zwischenablage im vorherigen OleGetClipboard()-Aufruf befindet, weiterhin in der Zwischenablage vorhanden ist.

Da ist die Dokumentation von Clipboard.IsCurrent() leider sehr unvollständig!

😠

FRAGE: Wie kann man sonst feststellen, ob sich der Clipboard-Inhalt seit dem letzten WM_CLIPBOARDUPDATE geändert hat oder nicht ???

16.835 Beiträge seit 2008
vor einem Jahr

Nicht nötig so viel in Fettschrift zu schreiben. Wir können lesen 😉

Ich glaube das hier hilft Dir clipboard-change-notification

Weressated Themenstarter:in
28 Beiträge seit 2023
vor einem Jahr

Nicht nötig so viel in Fettschrift zu schreiben. Wir können lesen 😉

Und wenn ich die Code-Ausdrücke nicht so vorbildlich hervor gehoben hätte, dann wäre als erstes der Kommentar gekommen, man soll doch gefälligst den Text gescheit formatieren. Schon klar 😁

Ich glaube das hier hilft Dir
>

Leider nein.

Das ist ja im Prinzip genau das, was ich bereits umgesetzt habe, bloß dass die noch die veraltete SetClipboardViewer() API mit WM_DRAWCLIPBOARD und WM_CHANGECBCHAIN benutzten, anstatt AddClipboardFormatListener() mit WM_CLIPBOARDUPDATE.
Using the Clipboard - Win32 apps

Ansonsten ist die Idee genau gleich: Anwendungs-Fenster für Update-Nachrichten registrieren, und anschließend die eingehenden Update-Nachrichten in der WndProc() verarbeiten.

Er hat das hübsch als Klasse verpackt, aber ich sehe nicht, dass er irgendwo "mehrfach" Events ausfiltert.

Das Problem ist nämlich immer das selbe: Man bekommt pro Änderung des Clipboard-Inhalts oftmals mehrere solcher Benachrichtigungen vom System!

Übrigens geben sich in dieser Beziehung WM_CLIPBOARDUPDATE und WM_DRAWCLIPBOARD nichts. Die kommen beide gleichermaßen mehrfach rein. Das hab ich schon ausprobiert 😉

Das Problem ist anscheinend bekannt:
https://github.com/sudhakar3697/node-clipboard-event/issues/2

Wie also findet man heraus, ob sich bei einer eingehenden WM_CLIPBOARDUPDATE Message wirklich etwas geändert hat, seit der letzten? Wenn sich nichts geändert hat, soll keine erneute Verarbeitung stattfinden!


Und nein, GetClipboardSequenceNumber() hilft da ebenfalls nicht:
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclipboardsequencenumber

Es zählt offensichtlich pro WM_CLIPBOARDUPDATE Nachricht hoch, nicht pro tatsächlicher Änderung des Clipboard-Inhalt. Wäre ja auch zu einfach 😜

4.939 Beiträge seit 2008
vor einem Jahr

Du könntest den Hashcode der Formate vergleichen:


int hash = data.GetData(format).GetHashCode();

Also eine Liste für jedes Format (data.GetFormats()) erzeugen und dessen Hashcodes speichern.
Dann bei WM_CLIPBOARDUPDATE die Formate und deren Hashcodes vergleichen.

PS: Statt Fettdruck entweder "Code (Inline)" oder "vorformatierter Text" (die beiden rechten Buttons) hier im Forum benutzen.

Weressated Themenstarter:in
28 Beiträge seit 2023
vor einem Jahr

Du könntest den Hashcode der Formate vergleichen:

  
int hash = data.GetData(format).GetHashCode();  
  

Also eine Liste für jedes Format (data.GetFormats()) erzeugen und dessen Hashcodes speichern.
Dann bei WM_CLIPBOARDUPDATE die Formate und deren Hashcodes vergleichen.

Also ein Hash nur über die verfügbaren Formate bringt nichts, denke ich. Es kann ja sein, dass vor und nach einer Änderung die genau selben Formate verfügbar sind. Ob sich der eigentliche Inhalt geändert hat, kann man nicht sagen.

Man kann sich natürlich für jedes der verfügbaren Formate (oder zumindest für die relevanten) die eigentlichen Daten holen und darüber dann jeweils einen Hash berechnen. Daran könnte man Änderungen am Inhalt erkennen, das ist klar. Aber bei jeder eingehenden WM_CLIPBOARDUPDATE-Nachricht sämtliche Daten neu aus dem Clipboard zu holen und zu hashen, das erscheint mir ein sehr großer Overhead zu sein, den man ja gerade an der Stelle vermeiden will... 😭

Ich habe noch im Netz eine andere "Lösung" gefunden:

Man soll bei einer eingehenden WM_CLIPBOARDUPDATE-Nachricht einen Timer starten und die Verarbeitung erst dann machen, wenn der Timer auslöst. Weitere WM_CLIPBOARDUPDATE-Nachrichten werden ignoriert, wenn sie eingehen, während der Timer bereits gestartet wurde aber noch nicht ausgelöst hat. Denke das würde funktionieren, aber es erscheint mir ein ziemlich "komplexer" Workaround für ein Problem, das gar nicht existieren sollte...

PS: Statt Fettdruck entweder "Code (Inline)" oder "vorformatierter Text" (die beiden rechten Buttons) hier im Forum benutzen.

👍

4.939 Beiträge seit 2008
vor einem Jahr

Ich denke, der Workaround ist schon sinnvoll. Es wird wohl so sein, daß für jedes Format, das zum Clipboard hinzugefügt wird, ein WM_CLIPBOARDUPDATE ausgelöst wird (je nach Anwendung können ja z.B. TEXT, HTML und RTF als Formate erzeugt werden). Daher könntest du testweise mal die Formate während eines WM_CLIPBOARDUPDATE loggen.

Geht es dir bei deinem Programm denn nur um ein spezielles Format (also z.B TEXT) oder alle möglichen Formate (auch BITMAP etc.)?

PS: Als Programm zum Ansehen der verschiedenen Formate kann ich Free Clipboard Viewer empfehlen.

Weressated Themenstarter:in
28 Beiträge seit 2023
vor einem Jahr

Es wird wohl so sein, daß für jedes Format, das zum Clipboard hinzugefügt wird, ein WM_CLIPBOARDUPDATE ausgelöst wird (je nach Anwendung können ja z.B. TEXT, HTML und RTF als Formate erzeugt werden). Daher könntest du testweise mal die Formate während eines WM_CLIPBOARDUPDATE loggen.

Es sind immer die selben drei Formate (Text, Unicode und String), bei jeder WM_CLIPBOARDUPDATE Message.

Kann schon sein, dass für jedes Format eine separate WM_CLIPBOARDUPDATE Nachricht verschickt wird, aber es sind schon alle frei Format da, bevor die erste Nachricht ankommt.

Geht es dir bei deinem Programm denn nur um ein spezielles Format (also z.B TEXT) oder alle möglichen Formate (auch BITMAP etc.)?

Erstmal nur Text.

Also ich habe jetzt den Workaround mit dem Timer umgesetzt, mit 25 ms Tick-Intervall. Und es scheint so weit sinnvoll zu funktionieren.

4.939 Beiträge seit 2008
vor einem Jahr

Hast du auch explizit GetFormats(false) aufgerufen (denn GetFormats() liefert auch alle konvertierbaren Formate), s. GetFormats(Boolean)?

Weressated Themenstarter:in
28 Beiträge seit 2023
vor einem Jahr

Hast du auch explizit GetFormats(false) aufgerufen (denn GetFormats() liefert auch alle konvertierbaren Formate), s.
>
?

Nein, hatte ich nicht. Mit GetFormats(false) bekomme ich nur noch "UnicodeText", aber trotzdem mehrere WM_CLIPBOARDUPDATE Nachrichten, z.B.:

[004420BC] WM_CLIPBOARDUPDATE
[004420BC] GetFormats() = [UnicodeText]
[004420CB] WM_CLIPBOARDUPDATE
[004420CB] GetFormats() = [UnicodeText]
[004420EB] OnClipboardChanged()
4.939 Beiträge seit 2008
vor einem Jahr

Dafür habe ich dann auch keine Erklärung - dann bleibt wohl wirklich nur die Timer-gesteuerte Umsetzung.

Weressated Themenstarter:in
28 Beiträge seit 2023
vor einem Jahr

dann bleibt wohl wirklich nur die Timer-gesteuerte Umsetzung.

Es sieht wohl so aus. Ich habe es nun so gelöst, dass der OnClipboardChanged()-Handler indirekt per Timer aufgerufen wird. Der Timer wird gestartet wenn ein WM_CLIPBOARDUPDATE eingeht. Außerdem wird in OnClipboardChanged() immer zuerst der Hash berechnet und mit dem alten Hash vergleichen (der letzte Hash ist in einer statischen Variable gesichert). Die eigentliche Verarbeitung und das Update des UI findet nur statt, wenn sich der Hash seit dem letzten Mal geändert hat.

Warum einfach, wenn's auch kompliziert geht? 😁

16.835 Beiträge seit 2008
vor einem Jahr

Wenn man als einziger nen Workaround erfindet, dann riecht das immer etwas fischig...
Auf GitHub gibts ja hunderte Clipboard Projekte mit Win32/.NET/C#. Dutzende Extensions, Viewer, Watchers... paar Stück überflogen und nirgends hab ich nen Timer gesehen.

Sind die alle viel simpler als Dein Vorhaben, oder wieso brauchen die das nicht - aber Du diesen Timer Workaround?

PS: versuchst Du Rekorde bei den Beitragsbearbeitungen aufzustellen?
Schick doch den Beitrag erst ab, wenn er fertig ist.

Weressated Themenstarter:in
28 Beiträge seit 2023
vor einem Jahr

Wenn man als einziger nen Workaround erfindet, dann riecht das immer etwas fischig...
Auf GitHub gibts ja hunderte Clipboard Projekte mit Win32/.NET/C#. Dutzende Extensions, Viewer, Watchers... paar Stück überflogen und nirgends hab ich nen Timer gesehen.
Sind die alle viel simpler als Dein Vorhaben, oder wieso brauchen die das nicht - aber Du diesen Timer Workaround?

Wenn Du eine einfachere/bessere Lösung hast, dann gerne her damit 🙂

Fakt ist, man findet im Netz diverse Diskussionen dazu, dass Windows gerne mal etliche WM_CLIPBOARDUPDATE Messages für jede einzelne Clipboard-Änderung liefert, siehe z.B.:
WM_CLIPBOARDUPDATE event may be triggered multiple times for a single cut action on Windows · Issue #2 · sudhakar3697/node-clipboard-event
clipboard-listener-event-is-being-called-twice
https://www.autohotkey.com/boards/viewtopic.php?style=1&t=96544

I've seen such madness before. Excel used to perform 24 separate operations when copying a chart.
[...]The usual mitigation strategy is to avoid reacting to every update, and react to the LAST update after a reasonable "settle time" has elapsed with no further clipboard notifications. 500ms will usually be more than adequate.

Bin also nicht der erste, der über dieses Phänomen gestolpert ist!

Eine Lösung, wie man dieses Verhalten abstellen kann, hab ich aber leider nicht gesehen. Anscheinend gilt hier: „It's not a bug, it's a feature“ 🙄

Klar, man kann das einfach ignorieren und bei jeder WM_CLIPBOARDUPDATE Message einfach (erneut) die komplette Verarbeitung durchführen. Das "funktioniert" schon.

Nur ist es eben nicht sonderlich performant und irgendwie auch "unschön", sinnlos die selbe Berechnung mehrmals hintereinander durchzuführen...

versuchst Du Rekorde bei den Beitragsbearbeitungen aufzustellen?

Du bringst mich ja auf Ideen 👍

Aber im Ernst: Ich ergänze eben oftmals noch weitere Informationen, wenn es (aus meiner Sicht) Sinn macht.

Schick doch den Beitrag erst ab, wenn er fertig ist.

Gegenvorschlag: Lass doch mal die Leute machen 😉

T
2.224 Beiträge seit 2008
vor einem Jahr

Eigentlich steht doch in deiner Antwort ein brauchbarer Ansatz.
Am besten wäre es, wenn du einfach erst auf die letzte empfange Nachricht wartest und die anderen erstmal ignorierst.
Bei dem 500ms hast du eigentlich einen guten Zeitrahmen an den du dich halten kannst.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

Weressated Themenstarter:in
28 Beiträge seit 2023
vor einem Jahr

Eigentlich steht doch in deiner Antwort ein brauchbarer Ansatz.
Am besten wäre es, wenn du einfach erst auf die letzte empfange Nachricht wartest und die anderen erstmal ignorierst.

Genau! Das ist ja der Ansatz mit dem Timer, den ich schlussendlich umgesetzt habe. Denn Du weißt ja nie, wenn eine so eine Nachricht rein kommt, ob da gleich hinterher noch eine weitere Nachricht kommen wird oder ob das jetzt (erstmal) die letzte war. Also "einfach erst auf die letzte empfange Nachricht warten" geht wohl nur mit dem Umweg über einen Timer.

Der Hash ist quasi nur eine zweite Sicherheit. Wenn man eh den Clipboard-Inhalt holt (beim Timer-Event), dann ist es kaum Overhead, noch kurz den Hash-Wert abzugleichen.

Es ist nur seltsam, dass solche "Workarounds" überhaupt nötig sind. Über das Problem stolpert schließlich jeder früher oder später, der sich mit WM_CLIPBOARDUPDATE auseinander setzt. Würde Windows von vorne herein nur eine Nachricht pro Änderung schicken – das Timeout könnte man von mir aus bei AddClipboardFormatListener() als Parameter festlegen – oder würde wenigstens Clipboard.IsCurrent() so funktionieren, wie man es erwartet, dann könnte man sich das alles ja sparen...

Egal. Hätte, wäre und wenn. Für mich ist der Fall jetzt erstmal gelöst, denke ich 😉

[Ein Thema als "gelöst" markieren gibt's hier anscheinend nicht, oder übersehe ich schon wieder was?]