Laden...

ClipboardViewer: WM_DRAWCLIPBOARD kommt 2mal / nach einiger Zeit Stackoverflow in OnClipboardChange

Erstellt von KainPlan vor 12 Jahren Letzter Beitrag vor 12 Jahren 1.697 Views
K
KainPlan Themenstarter:in
133 Beiträge seit 2009
vor 12 Jahren
ClipboardViewer: WM_DRAWCLIPBOARD kommt 2mal / nach einiger Zeit Stackoverflow in OnClipboardChange

Huhu,

Ich hab für meine Appbar einen ClipboardViewer geschrieben er ermöglicht das Zwischenspeichern und wiederaufrufen von Clipboardeinträgen (Bilder, Text, Dateien, etc..). Nun hab ich zwei Probleme:

  1. Wenn ich per SetData() daten ins Clipboard schreibe wird WM_DRAWCLIPBOARD 2mal vom System ausgegeben. Auch wenn ich z.B. im Firefox Text kopiere, jedoch nicht wenn ich in Notepad Text kopiere. Einige Seiten im Internet behaupten das durch eine falsche Verwendung der Clipboard API dieser Fehler auftritt. Nun mach ich aber doch eigentlich nichts falsch in der SetData() Methode, oder? Und NEIN innerhalb des Programms wir die Methode NICHT 2mal aufgerufen!

  2. Ab und an nach gewisser Laufzeit schmiert OnClipboardChange() mit einem Stackoverflow Error ab. Das kann ich mir absolut nicht erklären. Je nachdem welchen Inhalt man gerade kopiert hat (von einem anderen Programm aus) z.B. Text tritt der Stackoverflow innerhalb der Funktion (in dem Fall) Clipboard.ContainsText() auf.

Weis hier jemand evtl. rat?

Hier die Klasse:


    /// <summary>
    /// Clipboardformate.
    /// </summary>
    public enum CBDataType
    {
        /// <summary>
        /// Nicht untersüztes Clipboardformat
        /// </summary>
        Unsupported,
        
        /// <summary>
        /// Daten im Filelist-Format vorhanden.
        /// </summary>
        Files,

        /// <summary>
        /// Format im Audio-Format vorhanden.
        /// </summary>
        Audio,

        /// <summary>
        /// Format im Image-Format vorhanden.
        /// </summary>
        Image,

        /// <summary>
        /// Format im Text-Format vorhanden.
        /// </summary>
        Text,
    }

    public delegate void ClipboardWatcherOnChangeEventHandler(CBDataType type, object data);

    public class ClipboardWatcher
    {
        #region WIN API
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer);
        [DllImport("user32.dll")]
        public 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);
        [DllImport("user32.dll")]
        static extern IntPtr GetClipboardOwner();
        [DllImport("user32.dll")]
        private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
        [DllImport("kernel32.dll")]
        static extern uint GetCurrentProcessId();
        private const UInt32 WM_CHANGECBCHAIN       = 0x030D;
        private const UInt32 WM_DRAWCLIPBOARD       = 0x0308;
        private const UInt32 WM_CLOSE               = 0x0010;
        private const UInt32 WM_DESTROY             = 0x0002;
        #endregion

        private static bool _instance = false;
        private IntPtr _nextCBWatcher;
        private IntPtr _ownerWnd;
        private bool _wasReset;

        public event ClipboardWatcherOnChangeEventHandler ClipboardChange;
        
        public ClipboardWatcher(IntPtr OwnerWnd) 
        {
            ClipboardChange = null;
            _nextCBWatcher = IntPtr.Zero;
            _ownerWnd = OwnerWnd;
            _nextCBWatcher = SetClipboardViewer(_ownerWnd);

            if (_instance) throw new Exception("Only one instance at a time. TODO: Check SetData() triggering twice our MessageProc...");

            _instance = true;
            _wasReset = false;
        }

        public static void SetData(CBDataType type, object data) 
        {
            object dat;
            switch (type)
            {
                case CBDataType.Files:
                    dat = data as StringCollection;
                    if (dat == null) throw new Exception("Invalid data.");
                    Clipboard.SetFileDropList((StringCollection)dat);
                    break;

                case CBDataType.Audio:
                    dat = data as Stream;
                    if (dat == null) throw new Exception("Invalid data.");
                    Clipboard.SetAudio((Stream)dat);
                    break;

                case CBDataType.Image:
                    dat = data as Image;
                    if (dat == null) throw new Exception("Invalid data.");
                    Clipboard.SetImage((Image)dat);
                    break;

                case CBDataType.Text:
                    dat = data as string;
                    if (dat == null) throw new Exception("Invalid data.");
                    Clipboard.SetText((string)dat);
                    break;

                default: throw new Exception("Unsupported Clipboardformat.");
            }

        }

        public void SetClipboardViewer()
        {
            _wasReset = true;
            ChangeClipboardChain(_ownerWnd, _nextCBWatcher);
            _nextCBWatcher = SetClipboardViewer(_ownerWnd);
        }

        public void MessageProc(ref Message m) 
        {
            if (m.Msg == WM_CHANGECBCHAIN)
            {
                if (m.WParam == _nextCBWatcher)
                {
                    _nextCBWatcher = m.LParam;
                }
                else
                {
                    SendMessage(_nextCBWatcher, m.Msg, m.WParam, m.LParam);
                }
            }
            else if (m.Msg == WM_DRAWCLIPBOARD)
            {
                uint cpid = 0, pid = 0;

                if (_wasReset)
                {
                    _wasReset = false;
                    return;
                }
                
                GetWindowThreadProcessId(GetClipboardOwner(), out pid);
                cpid = GetCurrentProcessId();

                if (pid != cpid)
                    OnClipboardChange();

                if (_nextCBWatcher != IntPtr.Zero)
                    SendMessage(_nextCBWatcher, m.Msg, m.WParam, m.LParam);
            }
            else if (m.Msg == WM_DESTROY)
            {
                ChangeClipboardChain(_ownerWnd, _nextCBWatcher);
            }
        }

        protected virtual void OnClipboardChange() 
        {
            object obj = null;
            CBDataType objType = CBDataType.Unsupported;

            if (Clipboard.ContainsFileDropList())
            {
                obj = Clipboard.GetFileDropList();
                objType = CBDataType.Files;
            }
            else if (Clipboard.ContainsAudio())
            {
                obj = Clipboard.GetAudioStream();
                objType = CBDataType.Audio;
            }
            else if (Clipboard.ContainsImage())
            {
                obj = Clipboard.GetImage();
                objType = CBDataType.Image;
            }
            else if (Clipboard.ContainsText())
            {
                obj = Clipboard.GetText();
                objType = CBDataType.Text;
            }
            else
                obj = Clipboard.GetDataObject();

            Control ctrl = ClipboardChange.Target as Control;
            if (ClipboardChange != null)
            {
                if (ctrl != null && ctrl.InvokeRequired)
                    ClipboardChange.Invoke(objType, obj);
                else
                    ClipboardChange(objType, obj);
            }
        }
    }

771 Beiträge seit 2009
vor 12 Jahren

Hi,

wie sieht denn genau der StackTrace bei beiden Fehlern aus?
Beim ersten könntest du ihn evtl. mit einer boolschen Variablen (ähnlich wie in [FAQ] Event nur bei Benutzeraktion auslösen, nicht bei programmtechnischer Änderung) abfangen.
Und beim zweiten tritt der StackOverflow wirklich nur innerhalb der ContainsText-Methode auf (oder wird dein eigener Code zwischendurch auch noch aufgerufen)?

K
KainPlan Themenstarter:in
133 Beiträge seit 2009
vor 12 Jahren

also der stackoverflow tritt wirklich nur innerhalb der framework-methode auf. was soll auch innerhalb der OnClipboardChange()-methode einen stackoverflow verursachen...
diese zeilen:


                GetWindowThreadProcessId(GetClipboardOwner(), out pid);
                cpid = GetCurrentProcessId();

                if (pid != cpid)
                    OnClipboardChange();

verhindern ja schon das der ausführende prozess OnClipboardChange() aufruft. das problem bleibt weiterhin das einige programme windows dazu bringen WM_DRAWCLIPBOARD zweimal auszuspucken. in einigen artikeln wird beschrieben das die FALSCHE nutzung des clipboards dazu führt aber wenn schon das .NET Framework diesen fehler produziert dann denk ich das es tiefere gründe hat, oder die bei ms kiffen tatsächlich. ^^