2008-09-16 21 views
32

Tại sao đoạn mã sau đôi khi gây ra một ngoại lệ với những nội dung "CLIPBRD_E_CANT_OPEN":CLIPBRD_E_CANT_OPEN lỗi khi thiết Clipboard từ NET

Clipboard.SetText(str); 

này thường xảy ra lần đầu tiên vào Clipboard được sử dụng trong các ứng dụng và không sau đó.

+0

Đó là một giải pháp khá kludgey - là nó thực sự là cách duy nhất? – Blorgbeard

+0

Đây có vẻ là cách MS triển khai nó trong Biểu mẫu. Câu hỏi này là về WPF (mặc dù tôi đã không nhận ra nó quan trọng). –

Trả lời

29

Thực ra, tôi nghĩ đây là fault of the Win32 API.

Để đặt dữ liệu trong khay nhớ tạm, trước tiên bạn phải open it. Chỉ có một quy trình có thể mở khay nhớ tạm tại một thời điểm. Vì vậy, khi bạn kiểm tra, nếu một quy trình khác có khay nhớ tạm mở, hãy mở vì bất kỳ lý do nào, nỗ lực mở của bạn sẽ không thành công. Nó chỉ xảy ra khi Terminal Services theo dõi clipboard và trên các phiên bản cũ hơn của Windows (trước Vista), bạn phải mở clipboard để xem nội dung bên trong ... kết thúc bằng cách chặn bạn. Giải pháp duy nhất là đợi cho đến khi Terminal Services đóng clipboard và thử lại.

Điều quan trọng là nhận ra rằng điều này không cụ thể đối với Dịch vụ đầu cuối, mặc dù: nó có thể xảy ra với bất kỳ điều gì. Làm việc với clipboard trong Win32 là một điều kiện chủng tộc khổng lồ. Tuy nhiên, kể từ khi thiết kế bạn chỉ có nghĩa vụ để muck xung quanh với clipboard để đáp ứng với đầu vào của người dùng, điều này thường không trình bày một vấn đề.

30

Điều này là do lỗi/tính năng trong khay nhớ tạm dịch vụ đầu cuối (và các thứ khác có thể) và việc triển khai .NET của khay nhớ tạm. Một sự chậm trễ trong việc mở clipboard gây ra lỗi, thường đi qua trong vài phần nghìn giây.

Giải pháp là thử nhiều lần trong vòng lặp và ngủ ở giữa.

for (int i = 0; i < 10; i++) 
{ 
    try 
    { 
     Clipboard.SetText(str); 
     return; 
    } 
    catch { } 
    System.Threading.Thread.Sleep(10); 
} 
+4

Nếu bạn nhìn vào bên trong của Clipboard.SetText, trên .NET 2.0 SP1 ít nhất, bạn sẽ thấy nó đã có một vòng lặp thử lại/chờ. Thử lại tối đa 10 lần với độ trễ 100ms. –

+12

@Mike: System.Windows.Forms.Clipboard có một thử lại, nhưng System.Windows.Clipboard từ WPF không. –

+0

Điều này đã làm cho các trick cho ứng dụng WPF của chúng tôi, cảm ơn! –

5

Thực tế có thể có một vấn đề khác trong tầm tay. Cuộc gọi khuôn khổ (cả WPF và hương vị Winform) để một cái gì đó như thế này (mã là từ phản xạ):

private static void SetDataInternal(string format, object data) 
{ 
    bool flag; 
    if (IsDataFormatAutoConvert(format)) 
    { 
     flag = true; 
    } 
    else 
    { 
     flag = false; 
    } 
    IDataObject obj2 = new DataObject(); 
    obj2.SetData(format, data, flag); 
    SetDataObject(obj2, true); 
} 

Lưu ý rằng SetDataObject luôn được gọi với đúng trong trường hợp này.

Nội bộ kích hoạt hai cuộc gọi tới win32 api, một để đặt dữ liệu và một để xóa dữ liệu khỏi ứng dụng của bạn để ứng dụng có sẵn sau khi ứng dụng đóng.

Tôi đã xem một số ứng dụng (một số plugin chrome và trình quản lý tải xuống) nghe sự kiện trong khay nhớ tạm. Ngay sau lần truy cập cuộc gọi đầu tiên, ứng dụng sẽ mở khay nhớ tạm để xem dữ liệu và cuộc gọi thứ hai để tuôn ra sẽ không thành công.

Không tìm thấy giải pháp tốt ngoại trừ viết lớp clipboard của riêng tôi sử dụng API win32 trực tiếp hoặc gọi trực tiếp setDataObject bằng false để giữ dữ liệu sau khi ứng dụng đóng.

4

Tôi đã giải quyết vấn đề này cho ứng dụng của riêng mình bằng các hàm Win32 gốc: OpenClipboard(), CloseClipboard() và SetClipboardData().

Bên dưới lớp trình bao bọc mà tôi đã tạo. Bất cứ ai có thể xin vui lòng xem xét nó và cho biết nếu nó là chính xác hay không. Đặc biệt là khi mã được quản lý đang chạy dưới dạng ứng dụng x64 (tôi sử dụng bất kỳ CPU nào trong các tùy chọn dự án). Điều gì sẽ xảy ra khi tôi liên kết tới các thư viện x86 từ ứng dụng x64?

Cảm ơn bạn!

Dưới đây là các mã:

public static class ClipboardNative 
{ 
    [DllImport("user32.dll")] 
    private static extern bool OpenClipboard(IntPtr hWndNewOwner); 

    [DllImport("user32.dll")] 
    private static extern bool CloseClipboard(); 

    [DllImport("user32.dll")] 
    private static extern bool SetClipboardData(uint uFormat, IntPtr data); 

    private const uint CF_UNICODETEXT = 13; 

    public static bool CopyTextToClipboard(string text) 
    { 
     if (!OpenClipboard(IntPtr.Zero)){ 
      return false; 
     } 

     var global = Marshal.StringToHGlobalUni(text); 

     SetClipboardData(CF_UNICODETEXT, global); 
     CloseClipboard(); 

     //------------------------------------------- 
     // Not sure, but it looks like we do not need 
     // to free HGLOBAL because Clipboard is now 
     // responsible for the copied data. (?) 
     // 
     // Otherwise the second call will crash 
     // the app with a Win32 exception 
     // inside OpenClipboard() function 
     //------------------------------------------- 
     // Marshal.FreeHGlobal(global); 

     return true; 
    } 
} 
+0

PS: Tôi cũng đã cố gắng sử dụng cuộc gọi Managed' Clipboard.SetText() 'trước khi gọi hàm" nguyên gốc "(tức là cách gốc được sử dụng chỉ khi cách được quản lý không hoạt động). Nhưng nếu phiên bản được quản lý thất bại, nó sẽ khóa clipboard và sau đó phiên bản gốc cũng không mở được bảng tạm. – Mar

9

Tôi biết câu hỏi này là cũ, nhưng vấn đề vẫn còn tồn tại. Như đã đề cập trước đây, ngoại lệ này xảy ra khi clipboard hệ thống bị chặn bởi một tiến trình khác. Thật không may, có rất nhiều công cụ snipping, chương trình cho ảnh chụp màn hình và các công cụ sao chép tập tin có thể chặn clipboard của Windows. Vì vậy, bạn sẽ nhận được ngoại lệ mỗi khi bạn cố gắng sử dụng Clipboard.SetText(str) khi công cụ như vậy được cài đặt trên PC của bạn.

Giải pháp:

bao giờ sử dụng

Clipboard.SetText(str); 

sử dụng thay vì

Clipboard.SetDataObject(str); 
+2

Điều này thật tuyệt. Tại sao SetDataObject hoạt động sau đó? – Carol

+0

@K_Rol: Có vẻ như câu trả lời của Yishai Galatzer giải thích nó. – Cameron

0

này xảy ra với tôi trong ứng dụng WPF của tôi. Tôi đã nhận OpenClipboard không thành công (Ngoại lệ từ HRESULT: 0x800401D0 (CLIPBRD_E_CANT_OPEN)).

tôi sử dụng

ApplicationCommands.Copy.Execute(null, myDataGrid); 

giải pháp là để xóa clipboard đầu tiên

Clipboard.Clear(); 
ApplicationCommands.Copy.Execute(null, myDataGrid); 
Các vấn đề liên quan