“כשאנחנו מדביקים טבלה שהועתקה מ-Excel, העיצוב מתפרק. אנחנו רוצים שתודבק כטבלה.” “תוכן שאנחנו מעתיקים ביישום שלנו הופך למשהו מוזר כשאנחנו מדביקים אותו ב-Word.” “אנחנו רוצים להיות מסוגלים לקבל קבצים בגרירה ושחרור.” — בשיחות ייעוץ על שינויי יישומים עסקיים, בקשות סביב העתקה-והדבקה וגרירה ושחרור (D&D) הן קבועות.
דווקא כי אלה “תכונות שכולם לוקחים כמובנות מאליהן”, איך הן באמת עובדות ידוע במפתיע מעט. אם חושבים על הלוח כעל “קופסה ששמים בה נתון אחד”, אי אפשר להסביר למה אותה העתקה מייצרת תוצאות שונות לפי מקום ההדבקה, או למה ההדבקה מפסיקה לעבוד אחרי שסוגרים את יישום המקור. הלוח האמיתי הוא מנגנון ששם את אותו תוכן בכמה פורמטים בבת אחת, ונותן לצד ההדבקה לבחור פורמט שהוא מבין.
וגרירה ושחרור, ביסודו, היא העברת נתוני OLE שמוסרת בדיוק את אותה ייצוג נתונים כמו הלוח (IDataObject), דרך ממשקי COM. במילים אחרות, העתקה-והדבקה ו-D&D הם אחים: מבינים אחד נכון והשני נמצא ממש שם.
המאמר הזה מיועד לאנשי IT בחברות קטנות ובינוניות ולמפתחי יישומי Windows. הוא קושר, בתמונה אחת, איך פורמטי לוח עובדים, את הפרקטיקות בצד ההדבקה ובצד ההעתקה, את הדרך הנכונה לצפות בלוח, את מדיניות הניהול להיסטוריית לוח, סנכרון ענן ו-RDP, ואת המבנה והמלכודות של גרירה ושחרור OLE.
1. השורה התחתונה קודם
- הלוח הוא אזור יחיד שמשותף ליישומים על אותו שולחן עבודה (window station), ומה שיושב שם אינו “נתון אחד” אלא אותו תוכן בכמה פורמטים בבת אחת. הפעלה אחרת, כמו RDP, יש לה במקור לוח אחר; תכונת ההפניה היא מה שמגשר בין השניים. כי היעד בוחר פורמט שהוא מבין, אותה העתקה מייצרת תוצאות שונות לפי מקום ההדבקה.12
- לטקסט, השתמשו ב-CF_UNICODETEXT. CF_TEXT הוא ANSI ותלוי-דף-קוד, ועל מערכות יפניות הוא קרקע פורייה לג’יבריש. המערכת ממירה בין השניים במרומז, אבל הצד הקנוני הוא יוניקוד.3
- קבצים נוסעים כ-CF_HDROP (מערך נתיבים שמסתיים ב-NUL כפול), וטקסט מעוצב משתמש בפורמט הרשום “HTML Format”. ל-HTML Format יש מבנה חריג: טקסט UTF-8 עם כותרת של היסטי בתים.45
- הסיבה האמיתית ל”סגרתי את יישום המקור ואי אפשר יותר להדביק” היא רינדור מעוכב. זה מנגנון ששם לא את המטען אלא רק הבטחה “לייצר אותו כשיבקשו”; אם מדלגים על המימוש ביציאה (תגובה ל-WM_RENDERALLFORMATS, או OleFlushClipboard ל-OLE), ההדבקה מפסיקה לעבוד.26
- התייחסו לנתונים מודבקים כקלט לא-מהימן מבחוץ. מיקרוסופט עצמה קובעת במפורש ש”נתוני לוח אינם מהימנים. פרסו אותם בזהירות”.7
- לצפייה בלוח, AddClipboardFormatListener + WM_CLIPBOARDUPDATE הוא האפשרות היחידה. אל תשתמשו בסקר, ואל תשתמשו ב-SetClipboardViewer הישן (שרשרת הצופים). פורמטים רשומים שמשאירים סודות מחוץ להיסטוריה ולסנכרון (ExcludeClipboardContentFromMonitorProcessing וחבריו) מסופקים גם הם.81
- היסטוריית לוח (Win+V) וסנכרון ענן הם עניין של ניהול IT. אפשר לשלוט בהם עם AllowClipboardHistory ו-AllowCrossDeviceClipboard דרך GPO / Intune (Policy CSP), ולהפניית לוח RDP יש מדיניות ייעודית משלה.91011
- גרירה ושחרור היא COM. אותו IDataObject כמו הלוח מועבר בין IDropSource (מקור הגרירה) ל-IDropTarget (יעד השחרור) דרך לולאת DoDragDrop. RegisterDragDrop דורש אתחול עם OleInitialize (STA).1213
- אי אפשר לשחרר מסייר הקבצים בהרשאה רגילה על יישום מוגבה. UIPI (חסימת הודעות לפי רמת שלמות) היא הסיבה, וזה אילוץ שכדאי לדעת בזמן התכנון.14
להלן נעבור על זה מיסודות הלוח ומעלה.
2. מה הלוח באמת — לא “נתון אחד” אלא “אותו תוכן בכמה פורמטים”
הלוח הוא מנגנון שיתוף נתונים משותף שכל יישום שחולק את אותו שולחן עבודה יכול להגיע אליו (בדיוק יותר, הוא לפי window station: הפעלת משתמש אחרת או הפעלת RDP לכל אחת יש לוח משלה. העתקה-והדבקה עובדת על RDP כי תכונת ההפניה מגשרת בין השניים — פרק 7). העיקרון הראשון הוא שהוא מונע-משתמש: עמדת התכנון הרשמית היא שלא שמים נתונים פנימה או מוציאים אותם מאחורי גבו של המשתמש.1
הנקודה החשובה היא שהעתקה אינה שמה “נתון אחד”. החלון שמעתיק מרוקן את הלוח ואז שם כמה פורמטים ברצף, שמבטאים את אותו תוכן מהפורמט המסוגל יותר אל הפחות מסוגל.2 לדוגמה, כשמעתיקים טבלה בגיליון אלקטרוני, באופן מושגי משהו כמו הבא נמצא על הלוח באותו זמן.
| עדיפות | פורמט | תוכן |
|---|---|---|
| 1 | פורמט פרטי ליישום | ייצוג פנימי מלא, כולל נוסחאות ועיצוב (להדבקה חזרה לאותו יישום) |
| 2 | HTML Format | קטע HTML ששומר את מבנה הטבלה והעיצוב |
| 3 | CSV | טקסט מופרד-תאים |
| 4 | CF_UNICODETEXT | טקסט פשוט מופרד-טאבים |
| 5 | פורמט תמונה | מפת סיביות של איך הטבלה נראית |
צד ההדבקה בוחר מתוך הרשימה הזו פורמט שהוא מבין ומחלץ אותו. מדביקים ב-Word ומקבלים טבלה מעוצבת; מדביקים ב-Notepad ומקבלים טקסט מופרד-טאבים — כי השניים בחרו פורמטים שונים. “התוצאה תלויה במקום ההדבקה” אינו באג; זו התוצאה הרגילה של התכנון הזה.
flowchart TB
accTitle: למה אותה העתקה מייצרת תוצאות שונות לפי מקום ההדבקה
accDescr: צד ההעתקה שם את אותו תוכן על הלוח בכמה פורמטים, וצד ההדבקה בוחר פורמט שהוא מבין, כך ש-Word מקבל טבלה מעוצבת ו-Notepad מקבל טקסט מופרד-טאבים
copy["העתקה: גיליון אלקטרוני"] --> cb["לוח(פורמטים רבים)"]
cb --> rich["פורמטים עשירים יותר"]
cb --> plain["פורמטים פשוטים יותר"]
rich --> f1["פרטי ליישום"]
rich --> f2["HTML Format"]
plain --> f3["CSV"]
plain --> f4["CF_UNICODETEXT"]
f2 -->|"Word"| word["טבלה מעוצבת"]
f4 -->|"Notepad"| notepad["טקסט מופרד-טאבים"]
מצד שני, התלונות בפתיחה — “העיצוב מתפרק”, “משהו מוזר מודבק” — כמעט כולן מצטמצמות לבעיה של איך צד אחד בוחר פורמטים, או איך הצד האחר מציע אותם. פרק 4 מכסה את צד ההדבקה; פרק 5 מכסה את צד ההעתקה.
3. פורמטים סטנדרטיים ופורמטים רשומים — CF_UNICODETEXT, CF_HDROP, HTML Format
3.1. פורמטים סטנדרטיים — השתמשו בצד היוניקוד לטקסט
פורמטים שמערכת ההפעלה מגדירה מראש נקראים פורמטים סטנדרטיים. אלה שמופיעים כל הזמן ביישומים עסקיים הם הבאים.3
| פורמט | ערך | תוכן |
|---|---|---|
| CF_TEXT | 1 | טקסט ANSI (תלוי-דף-קוד) |
| CF_UNICODETEXT | 13 | טקסט יוניקוד. זה הפורמט הקנוני לטקסט |
| CF_HDROP | 15 | רשימת נתיבי קבצים (handle מסוג HDROP) |
| CF_DIB | 8 | מפת סיביות בלתי-תלויה-התקן |
| CF_LOCALE | 16 | מזהה האזור שמשויך לטקסט |
CF_TEXT ו-CF_UNICODETEXT מומרים זה לזה במרומז על ידי המערכת (פורמטים מסונתזים). המרת קוד התווים משתמשת בדף הקוד שמשויך ל-CF_LOCALE.3 הסתמכות על ההמרה הזו משמיטה תווים ש-ANSI לא יכול לייצג (למשל סמלים שקיימים רק ביוניקוד ותווי שילוב), ולכן הכלל הוא לאחד מה שהיישום קורא וכותב על CF_UNICODETEXT (DataFormats.UnicodeText ב-.NET).
flowchart TB
accTitle: המרה מרומזת בין CF_UNICODETEXT ל-CF_TEXT
accDescr: היישום קורא וכותב רק CF_UNICODETEXT; המערכת מסנתזת CF_TEXT בהמרה מרומזת עם דף הקוד של CF_LOCALE. תווים ש-ANSI לא יכול לייצג מושמטים בהמרה הזו
apprw["היישום קורא וכותב"] --> uni["CF_UNICODETEXT"]
uni <-->|"המרת CF_LOCALE"| ansi["CF_TEXT(ANSI)"]
ansi -.-> loss["תווים שאינם ניתנים לייצוג מושמטים"]
3.2. CF_HDROP — קבצים נוסעים כ”רשימת נתיבים”
CF_HDROP הוא מה שמשמש כשמעתיקים קבצים בסייר הקבצים, או כשגוררים ומשחררים קבצים. המטען אינו הקבצים עצמם; זה בלוק זיכרון שמניח מערך “מסתיים ב-NUL כפול”: אחרי כותרת מבנה DROPFILES, מחרוזות נתיב מלא מופרדות בתווי NUL, ומחרוזת ריקה בסוף. pFiles של הכותרת הוא היסט ההתחלה של רשימת הנתיבים, ו-fWide אומר אם המחרוזות הן יוניקוד.4
[DROPFILES header: pFiles=start offset of the path list, fWide=1(Unicode)]
C:\data\a.txt(NUL)C:\data\b.txt(NUL)(NUL)
בקוד נייטיב שולפים אותם אחד אחד עם DragQueryFile; ב-.NET מקבלים אותם כ-string[] דרך DataFormats.FileDrop. העובדה ש”מה שנוסע הוא רק הנתיבים, לא הקבצים עצמם” תשוב ותהיה חשובה ב-D&D של פרקים 8 ו-9.
flowchart TB
accTitle: פריסת בלוק הזיכרון של CF_HDROP
accDescr: מבנה DROPFILES יושב בתחילת הזיכרון הגלובלי; pFiles הוא היסט ההתחלה של רשימת הנתיבים ו-fWide אומר אם זה יוניקוד. אחר כך באים נתיבים מלאים, מופרדי NUL, והבלוק מסתיים במחרוזת ריקה(NUL כפול). מה שנוסע הוא רק הנתיבים, לא הקבצים עצמם
hdr["DROPFILES(pFiles / fWide)"] --> p1["C:\\data\\a.txt + NUL"]
p1 --> p2["C:\\data\\b.txt + NUL"]
p2 --> tail["מחרוזת ריקה(NUL כפול)"]
hdr -.-> note["רק נתיבים נוסעים, לא קבצים"]
3.3. פורמטים רשומים — RegisterClipboardFormat ו-“HTML Format”
לנתונים שפורמטים סטנדרטיים לא יכולים לבטא, יישום יכול לבחור שם ולרשום פורמט משלו. מעבירים שם ל-RegisterClipboardFormat ומקבלים מזהה פורמט בחזרה; רישום תחת אותו שם מיישום אחר מחזיר את אותו מזהה, כך שברגע שמסכימים על השם אפשר לשתף נתונים בין יישומים.1 כשמעבירים נתונים מובנים בין חבילת היישומים שלכם, השתמשו בשם שלא יתנגש, כמו KomuraSoft.Report.RowData.
הפורמט הרשום הייצוגי הוא “HTML Format”, לטקסט מעוצב (יחד עם RTF, אחד משני פורמטי הטקסט העשיר העיקריים). המטען הוא טקסט UTF-8, אבל יש לו מבנה חריג: כותרת שמפרטת היסטי בתים מצורפת מלפנים.5
Version:0.9
StartHTML:<byte offset of the start of the whole HTML>
EndHTML:<byte offset of the end of the whole HTML>
StartFragment:<byte offset of the start of the fragment>
EndFragment:<byte offset of the end of the fragment>
<html><body>
<!--StartFragment--><b>bold</b> fragment text<!--EndFragment-->
</body></html>
כל היסט הוא מיקום בית מתחילת הנתונים, כולל הכותרת עצמה; הנוהג הרגיל הוא לשמור רוחב קבוע (למשל 10 ספרות) ולכתוב את הערכים המדודים בחזרה אחרי שבניתם את הגוף. StartFragment/EndFragment מסמנים את ההתחלה והסוף של “הקטע שהמשתמש באמת בחר” בבתים (לא בתווים). ב-UTF-8 שכולל יפנית, ספירת התווים וספירת הבתים מתפצלות, כך שאם חישוב ההיסט הזה שגוי, הדבקה ביישום אחר משמיטה את ההתחלה או את הסוף. אם מייצרים HTML Format בעצמכם, חייבים למלא את הכותרת במיקומי בתים שנמדדו אחרי קידוד ל-UTF-8.5
flowchart TB
accTitle: איך כותרת HTML Format מתייחסת להיסטים
accDescr: StartHTML ו-EndHTML של הכותרת מצביעים על כל ה-HTML, ו-StartFragment ו-EndFragment מצביעים על הקטע שהמשתמש בחר, שניהם כמיקומי בתים מתחילת הנתונים. כי ספירת תווים וספירת בתים מתפצלות ב-UTF-8, ממלאים את הכותרת במיקומי בתים שנמדדו אחרי קידוד
header["כותרת(היסטי בתים)"] --> html["כל ה-HTML"]
html --> frag["הקטע שנבחר"]
header -.-> byte["היסטים הם בתים אחרי UTF-8"]
CSV (DataFormats.CommaSeparatedValue ב-.NET) גם נפוץ לנתונים טבלאיים. לתאימות עם Excel, הצעת HTML Format (עם עיצוב), CSV (ערכים בלבד), ו-CF_UNICODETEXT (מופרד-טאבים) יחד אומרת שאין צורך לבחור יעד הדבקה יחיד.
4. פרקטיקות בצד ההדבקה — עדיפות פורמט ואימות
4.1. מסתכלים מפורמטים עשירים למטה
הפורמטים על הלוח מסודרים בסדר שצד ההעתקה שם אותם (כלומר, מהיותר מבטא אל הפחות). קו הבסיס של צד ההדבקה הוא להסתכל, בין הפורמטים שאתם יכולים לטפל בהם, החל מזה עם הכי הרבה מידע. ב-Win32 או מונים עם EnumClipboardFormats ומשתמשים בפורמט הראשון שמזהים, או מעבירים רשימת עדיפות משלכם ל-GetPriorityClipboardFormat ונותנים לו לבחור.2
ב-.NET הענף נראה בערך כך.
// Pasting a table: look from rich to plain
var data = Clipboard.GetDataObject();
if (data is null) return;
// Advertising a format does not guarantee the payload is a string. Use this
// branch only when the type also checks out; otherwise fall through to the next candidate
if (data.GetDataPresent(DataFormats.Html)
&& data.GetData(DataFormats.Html) is string html)
{
// Validate the HTML Format header, then import as a table
}
else if (data.GetDataPresent(DataFormats.CommaSeparatedValue))
{
// Import as CSV
}
else if (data.GetDataPresent(DataFormats.UnicodeText))
{
// Import as tab-separated text
}
זו התשובה לתלונה בפתיחה, “הדבקת טבלת Excel מתפרקת”. יישום שקורא רק טקסט פשוט לעולם אינו מקבל את מבנה הטבלה. עד כמה למטה ברשימת הפורמטים מקבלים הוא החלטת תכנון בצד ההדבקה.
flowchart TB
accTitle: הסתעפות הדבקה שמסתכלת מפורמטים עשירים למטה
accDescr: אם HTML Format נוכח והמטען הוא גם מחרוזת, מייבאים כטבלה; אחרת מנסים CSV; אם גם זה חסר, נופלים לטקסט מופרד-טאבים. אם אף אחד מהמועמדים אינו נוכח, מסרבים
startsel["התחלת הדבקה"] --> h{"HTML Format + מחרוזת?"}
h -->|"כן"| useh["אימות כותרת → טבלה"]
h -->|"לא"| c{"CSV נוכח?"}
c -->|"כן"| usec["ייבוא כ-CSV"]
c -->|"לא"| t{"UnicodeText?"}
t -->|"כן"| uset["טקסט מופרד-טאבים"]
t -->|"לא"| giveup["סירוב"]
4.2. נתונים מודבקים הם קלט חיצוני
קל לפספס, אבל תוכן הלוח הוא נתונים מבחוץ, ואתם לא יודעים איזה יישום שם אותם. גם מיקרוסופט מזהירה, בתיעוד לוח OLE, ש”נתוני לוח אינם מהימנים. פרסו אותם בזהירות לפני שמשתמשים בהם ביישום”.7
- אמתו שהיסטי כותרת HTML Format אינם מצביעים מחוץ למאגר (יישומים שפולטים כותרות שבורות קיימים).
- ערכים שמייבאים כמספרים, תאריכים או קודים צריכים לעבור את אותו אימות כמו קלט על המסך.
- שימו הגנה מפני נתונים ענקיים. גם אם מישהו מדביק תמונה של מאות מגה-בתים או מיליוני שורות טקסט, אל תחסמו את ה-UI, וסרבו ברגע שחורגים ממגבלה. הערה: GetData של .NET, ברגע שקוראים לו, מממש את כל המטען למחרוזת מנוהלת (ורינדור מעוכב רץ כחלק מזה), כך ששימת בדיקת גודל אחרי GetData אינה הגנה. ב-Win32, בדיקת GlobalSize על ה-HGLOBAL ש-GetClipboardData מחזיר כן נותנת הגנה בשלב של “אל תמשיכו להמרה ולניתוח כמחרוזת מנוהלת”, אבל לפורמטים של רינדור מעוכב GetClipboardData עצמו מפעיל רינדור, כך שעדיין אי אפשר למנוע מימוש בצד מקור ההעתקה. כדי שה-UI לא יקפא, העבירו את השליפה מתהליכון ה-UI (וגם אז, כי Clipboard של .NET דורש STA, עשו זאת על תהליכון ייעודי שמוגדר ל-STA, לא על תהליכון מאגר Task.Run (MTA) — סעיף 5.1).
הרעיון ש”ערך שמגיע מבחוץ, לא משנה באיזה נתיב, מאומת לפני שמשתמשים בו” הוא אותו רעיון שמפורט ב”Never Use a QR Code’s Decoded Value As-Is”. ההנחה שהדבקה בטוחה כי היא פעולת משתמש היא איך תאונות מתחילות.
flowchart TB
accTitle: מאמתים נתונים מודבקים לפני שמשתמשים בהם
accDescr: נתונים שנלקחים מהלוח עוברים נוכחות-פורמט, טיפוס-מטען, מגבלת-גודל, ואימות-תוכן בסדר הזה; כישלון באחד מהם והסירוב או נפילה לפורמט המועמד הבא
present["הפורמט נוכח?"] --> type["טיפוס המטען תקין?"]
type --> size["הגודל בתוך המגבלה?"]
size --> content["אימות התוכן"]
content --> ok["ייבוא"]
type -.->|"טיפוס שגוי"| rej["סירוב / פורמט הבא"]
size -.->|"גדול מדי"| rej
content -.->|"לא תקין"| rej
5. פרקטיקות בצד ההעתקה — הצעת כמה פורמטים בבת אחת, ורינדור מעוכב
5.1. שמים כמה פורמטים בבת אחת
הפרקטיקה של צד ההעתקה היא ההפך של 4.1: מציעים פורמט עשיר ופורמט פשוט באותו זמן. עם DataObject של WinForms/WPF אפשר לכתוב זאת בכמה שורות.15
// WinForms (System.Windows.Forms). WPF is the same shape with System.Windows DataObject/Clipboard
var data = new DataObject();
data.SetData(DataFormats.Html, htmlFormatText); // HTML Format string including the header
data.SetData(DataFormats.CommaSeparatedValue, csv); // CSV
data.SetData(DataFormats.UnicodeText, plainText); // Plain text
Clipboard.SetDataObject(data, copy: true); // copy:true = keep after the app exits
שתי הערות. ראשית, מחלקת Clipboard של .NET ניתנת לשימוש רק מתהליכון STA.15 תהליכון ה-UI של WinForms/WPF הוא STA בגלל [STAThread], כך שבדרך כלל זו אינה בעיה, אבל מגע בו מתהליכון רקע נכשל (יסודות STA/MTA נמצאים ב”ידע בסיסי על STA/MTA ב-COM”). שנית, מה ש-copy: true אומר קשור לרינדור מעוכב בסעיף הבא.
5.2. רינדור מעוכב — למה “סוגרים את המקור ואי אפשר להדביק”
בניית מטען גדול בהרבה פורמטים בכל פעם היא בזבזנית, ולכן ללוח יש מנגנון בשם רינדור מעוכב. מעבירים NULL כ-handle הנתונים ל-SetClipboardData ובמקום המטען נרשמת רק הבטחה “לייצר אותו כשיבקשו”; כשמישהו מבקש את הפורמט הזה, WM_RENDERFORMAT מגיע למקור ההעתקה, ורק אז הנתונים נוצרים.2
התוצאה של התכנון הזה היא הפתיחה “סגרתי את יישום המקור ואי אפשר יותר להדביק”. לפני שהוא יוצא, מקור ההעתקה מקבל WM_RENDERALLFORMATS ואחראי לממש כל פורמט שעוד לא רונדר; יציאה בלי לעשות זאת והפורמט אובד.2
flowchart TB
accTitle: רינדור מעוכב ולמה סגירה-ואז-הדבקה נכשלת
accDescr: מקור ההעתקה רושם רק הבטחה עם handle NULL, ומממש לפי דרישה דרך WM_RENDERFORMAT. ביציאה הוא אחראי לממש כל פורמט עם WM_RENDERALLFORMATS; מדלגים על זה והפורמט אובד
promise["SetClipboardData NULL = הבטחה"] --> req["צד ההדבקה מבקש"]
req --> render["WM_RENDERFORMAT → בונים עכשיו"]
promise --> quit["מקור ההעתקה עומד לצאת"]
quit -->|"RENDERALLFORMATS"| ok["הדבקה עובדת אחרי יציאה"]
quit -->|"דילוג על מימוש"| lost["הפורמט אובד אחרי סגירה"]
על לוח OLE (הסגנון ששם IDataObject עם OleSetClipboard), הקשר הזה עוד ברור יותר. כל מה שהלוח מחזיק הוא מצביע לאובייקט הנתונים, וקריאה ל-OleFlushClipboard ביציאת היישום מממשת את הנתונים על הלוח, כך שהדבקה עדיין עובדת אחרי יציאה.6 Clipboard.SetDataObject(data, copy: true) של .NET הוא מה שמציין את התנהגות “שמור אחרי יציאה” הזו.
כשמעתיקים טווח גדול ב-Excel ומנסים לצאת, ההנחיה “יש כמות גדולה של מידע על הלוח. האם תרצו להיות מסוגלים להדביק את המידע הזה בתוכנית אחרת אחר כך?” היא בדיוק אישור האם להריץ את המימוש הזה (ה-flush). אם משתמשים ברינדור מעוכב ביישום שלכם, זכרו שהמימוש בזמן יציאה הוא חלק מאותה קבוצה. רינדור מעוכב הוא אופטימיזציית ביצועים, וכי בקשת הרינדור רצה באופן סינכרוני בתוך עיבוד הודעות, נתונים שלוקח זמן רב לייצר יש להם את הפשרה של הקפאת ה-UI.2
6. פרקטיקות לצפייה בלוח — מאזין, ניסיון חוזר, והחרגה מהיסטוריה
6.1. השתמשו ב-AddClipboardFormatListener
דרישות כמו “אנחנו רוצים לזהות ערך מקורא ברקוד או העתקה ממערכת הליבה ולייבא אותם אוטומטית” דורשות לצפות בשינויי לוח. היסטורית יש שלוש שיטות; היום התשובה הנכונה היא אחת.8
| שיטה | הערכה |
|---|---|
| קריאה על טיימר (סקר) | בזבזני, ואפשר להחמיץ עדכונים. אל תשתמשו |
| SetClipboardViewer (שרשרת צופים) | באג ביישום אחד בשרשרת שובר את כל השרשרת. נשמר רק לתאימות לאחור |
| AddClipboardFormatListener | מומלץ. WM_CLIPBOARDUPDATE מגיע לחלון הרשום |
flowchart TB
accTitle: זרימת צפייה בלוח
accDescr: רושמים עם AddClipboardFormatListener כשה-handle נוצר, ו-WM_CLIPBOARDUPDATE מגיע לא משנה איזה יישום העתיק. קוראים עם ניסיון חוזר, ומבטלים רישום באופן סימטרי עם RemoveClipboardFormatListener כשה-handle נהרס
created["AddClipboardFormatListener"] --> wait["המתנה"]
anyapp["יישום כלשהו מעתיק"] --> notify["WM_CLIPBOARDUPDATE"]
wait --> notify
notify --> readtry["קריאה עם ניסיון חוזר(6.2)"]
readtry --> wait
destroyed["RemoveClipboardFormatListener"] -.->|"ביטול רישום"| created
// Minimal WinForms implementation
public partial class MainForm : Form
{
[DllImport("user32.dll", SetLastError = true)]
static extern bool AddClipboardFormatListener(IntPtr hwnd);
[DllImport("user32.dll", SetLastError = true)]
static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
const int WM_CLIPBOARDUPDATE = 0x031D;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
AddClipboardFormatListener(Handle);
}
protected override void OnHandleDestroyed(EventArgs e)
{
// Unregister symmetrically to match handle destruction / recreation
RemoveClipboardFormatListener(Handle);
base.OnHandleDestroyed(e);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_CLIPBOARDUPDATE)
{
// Read Clipboard.GetDataObject() here and import if the format is one you need
}
base.WndProc(ref m);
}
}
6.2. ניסיון חוזר כשאי אפשר לפתוח
רק חלון אחד בכל פעם יכול לפתוח את הלוח; בזמן שתהליך אחר פתח אותו, OpenClipboard נכשל.2 מיד אחרי WM_CLIPBOARDUPDATE, מקור ההעתקה או צופה אחר עדיין פועלים לעיתים קרובות, כך שכשל קריאה זמני הוא אירוע רגיל. שימו תמיד כמה ניסיונות חוזרים עם המתנה קצרה (עשרות מילישניות) ביניהם. שימו לב שעומסי Clipboard של .NET שמאפשרים לציין מספר ניסיונות ומרווח קיימים רק בצד הכתיבה, SetDataObject. אין מקבילה בצד הקריאה (GetDataObject וחבריו), כך שכותבים את catch-wait-retry בעצמכם — ExternalException ב-WinForms, COMException ב-WPF.
flowchart TB
accTitle: זרימת ניסיון חוזר לקריאת לוח
accDescr: רק חלון אחד בכל פעם יכול לפתוח את הלוח, כך שקריאה מיד אחרי הודעת השינוי יכולה להיכשל במירוץ עם תהליך אחר. בחריגה, ממתינים עשרות מילישניות ומנסים שוב; אם מגיעים למגבלה, מוותרים הפעם ומרימים בעדכון הבא
upd["WM_CLIPBOARDUPDATE"] --> tryread["ניסיון קריאה"]
tryread -->|"הצלחה"| useok["ייבוא(בדיקות פרק 4)"]
tryread -->|"בשימוש"| waitretry["המתנה של עשרות מילישניות"]
waitretry -->|"ניסיון חוזר"| tryread
waitretry -->|"מגבלה"| giveup2["ויתור הפעם"]
6.3. משאירים מחוץ להיסטוריה ולסנכרון — טיפול בתכונות העתקה שמטפלות בסודות
ל-Windows יש היסטוריית לוח (Win+V) וסנכרון בין מכשירים (לוח הענן), ונתונים שיישום שם נמצאים בטווח של שניהם כברירת מחדל. יישום ששם סודות כמו סיסמאות או מספרי חשבון על תכונת העתקה שם גם פורמט רשום שמחריג את התוכן מההיסטוריה ומהסנכרון.1
- ExcludeClipboardContentFromMonitorProcessing: שמים את זה ותוכן ההעתקה הזו אינו נכלל לא בהיסטוריה ולא בסנכרון.
- CanIncludeInClipboardHistory (DWORD 0): מדכאים היסטוריה בלבד.
- CanUploadToCloudClipboard (DWORD 0): מדכאים סנכרון בין מכשירים בלבד.
הסיבה שסיסמה שהועתקה על ידי מנהל סיסמאות אינה נשארת ב-Win+V היא המנגנון הזה. מקבלים מזהה פורמט בהעברת השם ל-RegisterClipboardFormat ומגדירים אותו לצד הנתונים הרגילים, כך שכדאי לממש בכל יישום עסקי שמטפל בסודות.
7. הלוח מנקודת מבט IT — היסטוריה, סנכרון ענן, ובקרות RDP
צעד קטן הרחק מפיתוח, הנה הנקודות שחשובות למנהל. היסטוריית לוח צוברת העתקות אחרונות, ולוח הענן מסנכרן העתקות בין מכשירים שמחוברים עם אותו חשבון Microsoft / חשבון Microsoft Entra.10 נוח ככל שזה, זה גם מייצר שאריות ודליפה: מידע אישי שהועתק ממערכת ליבה מצטבר בהיסטוריה, ותוכן שהועתק במחשב עבודה מסתנכרן למחשב אישי.
שתי המדיניות שמשתמשים בהן לשלוט בזה בארגון הן הבאות.
| מה ששולטים בו | GPO (Computer Configuration > Administrative Templates > System > OS Policies) | Policy CSP (Intune) | ברירת מחדל |
|---|---|---|---|
| היסטוריית לוח | Allow Clipboard History | Experience/AllowClipboardHistory | מותר |
| סנכרון בין מכשירים | Allow Clipboard synchronization across devices | Privacy/AllowCrossDeviceClipboard | מותר |
שתיהן זמינות מ-Windows 10 גרסה 1809 ואילך; משביתים אותן והפריטים המתאימים באפליקציית ההגדרות מואפרים, והמדיניות נכנסת לתוקף מיד.910
הקבוע האחר הוא הפניית לוח RDP (שולחן עבודה מרוחק). כברירת מחדל, העתקה-והדבקה עובדת בין המחשב המקומי להפעלה המרוחקת, כך שהיא יכולה להפוך לנתיב להוצאת סודות משרת. מדיניות “Do not allow clipboard redirection” (ערך רישום fDisableClip) יכולה לחסום את שני הכיוונים.11 מהדורות אחרונות של Windows Server / Windows 11 הוסיפו גם מדיניות עדינה יותר, כמו הגבלת כיוון שרת-ללקוח לטקסט בלבד. האם אוסרים לחלוטין או מגבילים בשלבים הוא איזון של תפעול ואבטחה.
flowchart TB
accTitle: נתיבים שתוכן לוח יכול להתפשט בהם, ונקודות הבקרה
accDescr: תוכן מועתק נמצא בטווח של היסטוריה וסנכרון ענן כברירת מחדל, וב-RDP הוא נוסע להפעלה אחרת דרך הפניה. כל נתיב ניתן לשליטה במדיניות, וצד היישום יכול להחריג את עצמו מהיסטוריה ומסנכרון עם פורמטי ההחרגה
cb["לוח"] --> hist["היסטוריה(Win+V)"]
cb --> cloud["סנכרון ענן"]
cb --> rdp["הפניית RDP"]
hist -.-> p1["AllowClipboardHistory"]
cloud -.-> p2["AllowCrossDeviceClipboard"]
rdp -.-> p3["fDisableClip"]
cb -.-> p4["פורמטי החרגה של היישום(6.3)"]
8. גרירה ושחרור היא COM — IDataObject + IDropSource + IDropTarget
8.1. אותם נתונים כמו הלוח, דרך נשיאה אחרת
גרירה ושחרור OLE רצה עם שלושת התפקידים הבאים.12
| תפקיד | מי מממש | עבודה |
|---|---|---|
| IDataObject | מקור הגרירה | המטען שנישא. אותו אובייקט נתונים רב-פורמטי כמו הלוח |
| IDropSource | מקור הגרירה | החלטה אם הגרירה נמשכת או מבוטלת, ומשוב סמן |
| IDropTarget | יעד השחרור | הכרזת קבלה/סירוב ב-DragEnter/DragOver/DragLeave/Drop, וקבלת השחרור |
מקור הגרירה קורא ל-DoDragDrop, לולאת הגרירה מתחילה, וכשהעכבר נכנס לחלון יעד-שחרור אותו IDropTarget מקבל הודעה; בשחרור, ה-IDataObject מועבר. התיעוד הרשמי גם אומר ש”D&D מספק בדיוק את אותה פונקציונליות כמו העתקה-והדבקה בלוח. אם יישום כבר מממש העתקה-והדבקה, התוספת קטנה”.12 במילים אחרות, אובייקט הנתונים הרב-פורמטי שבניתם בפרקים 2 עד 5 הופך למטען D&D כפי שהוא.
flowchart TB
accTitle: זרימת גרירה ושחרור OLE
accDescr: מקור הגרירה שם IDataObject במטען וקורא ל-DoDragDrop כדי להתחיל את לולאת הגרירה; IDropTarget של יעד השחרור מכריז קבלה/סירוב ב-DragEnter ו-DragOver, וב-Drop בוחר פורמט מה-IDataObject ומחלץ אותו
src["IDataObject + IDropSource"] -->|"DoDragDrop"| loop["לולאת גרירה"]
loop -->|"העכבר נכנס"| enter["DragEnter/Over: Effect"]
enter -->|"שחרור כפתור"| drop["IDropTarget.Drop"]
drop --> data["בחירת פורמט וחילוץ"]
8.2. OleInitialize (STA) נדרש
חלון שיהיה יעד שחרור נרשם עם RegisterDragDrop, ויש כאן מלכודת קלאסית. אם אתחלתם COM עם CoInitialize/CoInitializeEx, RegisterDragDrop תמיד נכשל עם E_OUTOFMEMORY; חייבים לאתחל עם OleInitialize.13 OleInitialize מאתחל COM כ-STA, כי D&D היא תכונה שמושרשת בעולם STA של חלונות ומשאבת הודעות. תהליכון הקריאה חייב גם להריץ משאבת הודעות; מדלגים על זה ויישומים אחרים נתקעים במהלך הגרירה.13 הרקע כאן הוא בדיוק דיון מודל התהליכונים ב”ידע בסיסי על STA/MTA ב-COM”.
ביישום WinForms/WPF המסגרת מטפלת באתחול OLE ובמימושי הממשקים, כך שהמפתח צריך רק לכתוב את האירועים.
// WinForms: accept dropped files
listView1.AllowDrop = true;
listView1.DragEnter += (s, e) =>
{
// Also check that the source allows Copy (some sources only allow Move/Link)
e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
&& (e.AllowedEffect & DragDropEffects.Copy) == DragDropEffects.Copy
? DragDropEffects.Copy // Accept: receive as a copy
: DragDropEffects.None; // Do not accept
};
listView1.DragDrop += (s, e) =>
{
// Drag data is also untrusted input. Even if it advertises FileDrop, the payload
// can be null or a different type, and GetData itself can fail
object data;
try { data = e.Data.GetData(DataFormats.FileDrop); }
catch (COMException) { return; }
if (data is not string[] paths) return;
foreach (var path in paths)
{
// Validate the path before importing (Section 9.3)
}
};
הצורה זהה ב-WPF: מקבלים עם AllowDrop="True" ואירועי DragOver/Drop על האלמנט, ומחלצים את מערך הנתיבים עם e.Data.GetData(DataFormats.FileDrop). הכרזת קבלה/סירוב (Effect) בכל DragEnter/DragOver היא מוסכמת IDropTarget; מדלגים עליה ומקבלים את הבאג שבו הסמן נשאר על “לא מותר” ולעולם אינו משתנה.
9. מלכודות D&D — הגבהה, העברה, ואימות נתיב
9.1. אי אפשר לשחרר על יישום שמוגבה כמנהל
משחררים קובץ מסייר הקבצים על יישום שהופעל עם “הפעל כמנהל” ושום דבר לא קורה — זה אינו באג מימוש, זו התנהגות מערכת ההפעלה. UIPI (User Interface Privilege Isolation) חוסם כברירת מחדל הודעות מתהליך שלמות נמוכה יותר לחלון שלמות גבוהה יותר, כך שהודעות שחרור מסייר הקבצים בהרשאה רגילה (שלמות בינונית) לעולם אינן מגיעות ליישום מוגבה.14
flowchart TB
accTitle: איך UIPI חוסם שחרורים על יישום מוגבה
accDescr: הודעות שחרור מסייר הקבצים בשלמות בינונית ליישום מוגבה בשלמות גבוהה נחסמות כברירת מחדל על ידי UIPI ולעולם אינן מגיעות. משאירים את ה-UI בהרשאה רגילה ומבודדים עבודה מועדפת, והשחרור מגיע
explorer["סייר(בינוני)"] -->|"הודעת שחרור"| uipi{"UIPI"}
uipi -->|"חסום"| elevated["יישום מוגבה: אין שחרור"]
uipi -->|"עובר"| normal["UI רגיל: השחרור מגיע"]
normal -.->|"האצלת עבודה מועדפת"| broker["תהליך מוגבה מבודד"]
פתרון עוקף שמתיר בנפרד הודעות ספציפיות כמו WM_DROPFILES עם ChangeWindowMessageFilterEx ידוע,14 אבל מה שהוא מעביר הוא הודעת השחרור הישנה (WM_DROPFILES); הוא אינו פותר D&D של OLE כמכלול. ההנחיה המעשית ברורה: הפסיקו לתכנן את היישום לרוץ מוגבה כל הזמן. מבודדים רק את העבודה שצריכה הגבהה לתהליך נפרד, וה-UI עצמו יכול להישאר בהרשאה רגילה ולקבל D&D (תכנון הבידוד מכוסה בפירוט ב”איך כותבים בפועל הפרדה של "רק תהליכים שדורשים הרשאות מנהל" ביישום Windows”).
9.2. מה DragDropEffects אומר — Move הוא חוזה ש”המקור נעלם”
Copy/Move/Link ב-DragDropEffects אינם קישוט; הם חוזה בין מקור הגרירה ליעד השחרור. מקור הגרירה מכריז על קבוצת האפקטים שהוא מתיר ב-DoDragDrop, יעד השחרור בוחר את האפקט בפועל, וכש-Move מצליח, מקור הגרירה מוחק את הנתונים (הקובץ) — זו המוסכמה. אם צד הקבלה מחזיר Move בלי מחשבה, מקבלים את התאונה “שחררתי וקובץ המקור נעלם”. לשימוש ייבוא ביישום עסקי, צד הקבלה שמכריז Copy הוא ברירת המחדל הבטוחה.
flowchart TB
accTitle: חוזה DragDropEffects — Move מוחק את המקור
accDescr: מקור הגרירה מכריז על קבוצת האפקטים המותרים ב-DoDragDrop, ויעד השחרור בוחר את האפקט בפועל. כש-Move מצליח מקור הגרירה מוחק את הקובץ, לכן לייבוא צד הקבלה צריך להכריז Copy
srcdecl["מקור: אפקטים מותרים"] --> tgtsel["יעד: בחירת Effect"]
tgtsel -->|"Copy"| copyok["המקור נשאר(ייבוא)"]
tgtsel -->|"Move"| moveact["המקור מוחק את הקובץ"]
9.3. אימות נתיב ששוחרר
מה שנוסע ב-CF_HDROP/FileDrop הוא רק הנתיב (סעיף 3.2). לפני שמייבאים, מעבירים אותו באותו אימות קלט לא-מהימן כמו הדבקה.
- קובץ או תיקייה: מחליטים כמפרט מה קורה כשתיקייה שלמה משוחררת (רקורסיה וייבוא, או סירוב).
- מצייני מקום של OneDrive: הנתיב יכול להתקיים בזמן שגוף הקובץ אינו מקומי — קובץ לפי-דרישה. ברגע שפותחים אותו מתחילה הורדה, ובמצב לא מקוון זה נכשל. התנהגות וצעדי נגד נמצאים ב”OneDrive "Files On-Demand" and Business Apps”.
- נתיבים ארוכים ונתיבים חריגים: נתיבים מעל MAX_PATH, נתיבי רשת (UNC), ונתיבים על מדיה נשלפת צריכים להתקבל רק אחרי שאישרתם שהעיבוד במורד יכול לטפל בהם.
- מספר וגודל כולל: כדי ששחרור אלפי קבצים לא יקפיא את ה-UI, עושים את הייבוא אסינכרוני ושמים מגבלה ותצוגת התקדמות.
10. סיכום
- הלוח הוא מנגנון ששם את אותו תוכן בכמה פורמטים בבת אחת באזור יחיד שמשותף בתוך אותו שולחן עבודה (window station). צד ההדבקה בוחר את הפורמט, כך שאותה העתקה מייצרת תוצאות שונות.
- טקסט הוא CF_UNICODETEXT, קבצים הם CF_HDROP, וטקסט מעוצב הוא הפורמט הרשום HTML Format (כותרת היסטי בתים + UTF-8).
- צד ההדבקה מסתכל מעשיר לפשוט ומתייחס למטען כקלט חיצוני. צד ההעתקה מציע כמה פורמטים בבת אחת, ואם הוא משתמש ברינדור מעוכב הוא מממש גם מימוש בזמן יציאה (WM_RENDERALLFORMATS / OleFlushClipboard).
- הצפייה היא AddClipboardFormatListener + WM_CLIPBOARDUPDATE. מתכוננים למירוצי OpenClipboard עם ניסיון חוזר, ומשאירים סודות מחוץ להיסטוריה ולסנכרון עם ExcludeClipboardContentFromMonitorProcessing וחבריו.
- IT יכול לשלוט בהיסטוריית לוח, בסנכרון ענן ובהפניית RDP עם GPO / Intune. ברירת המחדל היא מותר לכולם, לכן מחליטים במכוון בסביבות שמטפלות בסודות.
- D&D היא COM: IDropSource/IDropTarget מוסרים את אותו IDataObject כמו הלוח. RegisterDragDrop דורש OleInitialize (STA).
- שחרורים על יישום מוגבה נחסמים על ידי UIPI. Move ב-DragDropEffects הוא חוזה ש”המקור נעלם”; מאמתים נתיב ששוחרר לפני שמייבאים אותו.
העתקה-והדבקה ו-D&D הן, למשתמש, תכונות שצריכות להרגיש כמו אוויר. בדיוק לכן “אי אפשר להדביק”, “זה מתפרק”, ו”זה נעלם” פוגעים בחוויה כל כך — ולכן יישום שמציע כמה פורמטים ומטפל בשחרורים כראוי הופך את הפעולות היומיומיות לחלקות מעצמו. אני מקווה שזה חומר שימושי כשמחליטים מה לתקן קודם.
מאמרים קשורים
- מה זה COM / ActiveX / OCX — הסבר מרוכז על ההבדלים והקשרים
- ידע בסיסי על STA/MTA ב-COM — מודל השרשור ואיך נמנעים מתקיעה
- Windows Shell Integration Today — Context Menus, File Associations, and What Changed in Windows 11
- Why EXCEL.EXE Processes Remain After C# Excel COM Automation — Reference Release Patterns and the Replacement Decision
- עיצוב UX ליישומי Windows - סדרי עדיפות לפי סביבת שימוש
- OneDrive “Files On-Demand” and Business Apps — The Assumptions Placeholders Break and How to Deal with Them
תחומי ייעוץ קשורים
KomuraSoft LLC מטפלת בתכנון ובמימוש של תמיכת העתקה-והדבקה וגרירה ושחרור ביישומים עסקיים (הצעת כמה פורמטים, תאימות Excel, ייבוא קבצים ששוחררו), בחקירת שורש של בעיות כמו “זה מתפרק כשמדביקים” או “ההעתקה נעלמת”, באוטומציית קלט שצופה בלוח, ובמימושים שמשאירים נתונים סודיים מחוץ להיסטוריה ולסנכרון. מקרים שמערבים את השכבות הנמוכות של COM ו-OLE מתקבלים בברכה גם אם מתחילים מבידוד הסימפטום.
קישורים
-
Microsoft Learn, Clipboard Formats. על כך שחלון יכול לשים את אותו מידע בכמה פורמטי לוח; פורמטים רשומים דרך RegisterClipboardFormat (רישום אותו שם מחזיר את אותו ערך, כך שיישומים יכולים לשתף); פורמטים מסונתזים; והחרגת תוכן מהיסטוריית לוח / סנכרון ענן עם ExcludeClipboardContentFromMonitorProcessing, CanIncludeInClipboardHistory ו-CanUploadToCloudClipboard. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Clipboard Operations. על כך שרק חלון אחד בכל פעם יכול לפתוח את הלוח; שימת פורמטים מהיותר מבטא אל הפחות בזמן העתקה; בחירת פורמט בזמן הדבקה עם EnumClipboardFormats / GetPriorityClipboardFormat; רינדור מעוכב בהעברת NULL ל-SetClipboardData ואחריות WM_RENDERFORMAT / WM_RENDERALLFORMATS; והפשרות של רינדור מעוכב. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, Standard Clipboard Formats. על הגדרות הפורמטים הסטנדרטיים CF_TEXT (ANSI), CF_UNICODETEXT, CF_HDROP, CF_DIB ו-CF_LOCALE, ועל כך שהמערכת ממירה במרומז בין CF_TEXT ל-CF_UNICODETEXT באמצעות דף הקוד שמשויך ל-CF_LOCALE. ↩ ↩2 ↩3
-
Microsoft Learn, Shell Clipboard Formats. על כך ש-CF_HDROP מורכב ממבנה DROPFILES ועוד מערך מחרוזות נתיב מלא שמסתיים ב-NUL כפול; שליפת נתיבים בודדים עם DragQueryFile; ופורמטי מעטפת CFSTR_ שדורשים רישום דרך RegisterClipboardFormat. ↩ ↩2
-
Microsoft Learn, HTML Clipboard Format. על כך שהשם הרשום הוא “HTML Format”; מבנה הכותרת עם היסטי בתים כמו Version, StartHTML, EndHTML, StartFragment ו-EndFragment; הקידוד תמיד UTF-8; ומוסכמת ההערות StartFragment/EndFragment. ↩ ↩2 ↩3
-
Microsoft Learn, OleFlushClipboard function (ole2.h). על כך ש-OleSetClipboard גורם ללוח להחזיק רק מצביע לאובייקט הנתונים; OleFlushClipboard מממש את הנתונים על הלוח כך שהדבקה עדיין עובדת אחרי יציאת היישום; וריקון הלוח עם OleSetClipboard(NULL) כשאין צורך לשמור ביציאה. ↩ ↩2
-
Microsoft Learn, OleGetClipboard function (ole2.h). על איך לקבל IDataObject מהלוח, והאזהרה שנתוני לוח אינם מהימנים ויש לפרס אותם בזהירות לפני שהיישום משתמש בהם. ↩ ↩2
-
Microsoft Learn, Using the clipboard. על השוואת שלוש דרכי הצפייה בלוח (חלונות צופים, מספרי רצף, ומאזיני פורמט); תוכניות חדשות צפויות להשתמש במאזין דרך AddClipboardFormatListener; שרשרת הצופים שברירית כשתחזוקת השרשרת אינה שלמה; ומספרי רצף אינם משהו שכדאי לסרוק. ↩ ↩2
-
Microsoft Learn, Policy CSP - Experience. על התרת או דחיית היסטוריית לוח עם מדיניות Experience/AllowClipboardHistory; זמינות מ-Windows 10 גרסה 1809 ואילך; ברירת המחדל מותר; ומיפוי GPO תחת “System > OS Policies” עם שינויים שנכנסים לתוקף מיד. ↩ ↩2
-
Microsoft Learn, Policy CSP - Privacy. על התרת או דחיית סנכרון לוח בין מכשירים עם מדיניות Privacy/AllowCrossDeviceClipboard; סנכרון שקורה בין מכשירים שמחוברים עם אותו חשבון Microsoft / חשבון Microsoft Entra; ובררת המחדל מותר. ↩ ↩2 ↩3
-
Microsoft Learn, Policy CSP - ADMX_TerminalServer. על כך ש-TS_CLIENT_CLIPBOARD (“Do not allow clipboard redirection”, ערך רישום fDisableClip) יכול לאסור שיתוף לוח בין מקומי למרוחק בהפעלת שולחן עבודה מרוחק, ועל כך שההפניה מותרת כברירת מחדל. ↩ ↩2
-
Microsoft Learn, Drag and Drop (COM). על כך שגרירה ושחרור OLE רצה עם השלושה IDropSource (מקור הגרירה), IDropTarget (יעד השחרור) ו-DoDragDrop (הלולאה ש-OLE מספק); מתן אותה פונקציונליות כמו העתקה-והדבקה בלוח, כך שיישום שכבר מממש העתקה-והדבקה צריך רק תוספת קטנה; וסוגי המשוב. ↩ ↩2 ↩3
-
Microsoft Learn, RegisterDragDrop function (ole2.h). על רישום חלון יעד-שחרור עם IDropTarget; כשל תמיד עם E_OUTOFMEMORY אם COM אותחל עם CoInitialize/CoInitializeEx, כך ש-OleInitialize נדרש; ויישום מקור הגרירה נתקע אם תהליכון הקריאה אינו מריץ משאבת הודעות. ↩ ↩2 ↩3
-
Microsoft Learn, ChangeWindowMessageFilterEx function (winuser.h). על כך ש-UIPI הוא מנגנון אבטחה שכברירת מחדל חוסם קבלת הודעות משולח שלמות נמוכה יותר, ועל התרת הודעות ספציפיות לפי חלון עם מסנן הודעות (MSGFLT_ALLOW). ↩ ↩2 ↩3
-
Microsoft Learn, How to add data to the Clipboard (Windows Forms). על שימת נתונים בכמה פורמטים בבת אחת עם DataObject ו-Clipboard.SetDataObject; הוספה בכמה פורמטים כדי שיישומים אחרים יוכלו לזהות; ומחלקת Clipboard ניתנת לשימוש רק מתהליכון STA, כך ש-[STAThread] נדרש. ↩ ↩2
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
מה "לא מגיב" באמת — איך Windows מחליט שיישום נתקע, ואיך לתכנן יישומים שלא
"לא מגיב" של Windows הוא מנגנון שבו מערכת ההפעלה קובעת שחלון לא שלף הודעה במשך 5 שניות ומחליפה אותו בחלון רפאים. המאמר מכסה את פנים השיפו...
איך בוחרים בין WinForms, WPF ו-WinUI - טבלת החלטה מהשטח
המאמר מסדר את הבחירה בין WinForms, WPF ו-WinUI מנקודות המבט של פיתוח חדש, נכסים קיימים, הפצה, ביטוי ה-UI ומבנה הצוות.
מה זה COM / ActiveX / OCX — הסבר מרוכז על ההבדלים והקשרים
מהו COM, מהו ActiveX ומהו OCX — סקירה מעשית של ההבדלים והקשרים ביניהם, הזיקה ל-OLE, איפה זה נמצא בשימוש, ואיך כדאי להתייחס לזה היום.
למה להשתמש ב-Generic Host וב-BackgroundService של .NET באפליקציית desktop
בכלי Windows ובאפליקציות שרצות ברקע, מסודר כאן איך להשתמש ב-Generic Host וב-BackgroundService כדי לסדר הפעלה, עיבוד תקופתי, סיום, לוג, הג...
Win32 Thread Pool API — מקביליות בלי ליצור תהליכונים, דרך CreateThreadpoolWork
מפזרים קריאות CreateThread בכל הקוד הנייטיב? המאמר מסביר את ה-API של מאגר התהליכונים של Win32 שעוצב מחדש ב-Vista — ארבעת האובייקטים work,...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
העברת ActiveX
החלטות אם לשמר, לעטוף או להחליף רכיבי COM / ActiveX / OCX.
תהליכון ה-UI וטיימרים
תהליכון ה-UI של WPF / WinForms, זרימות אסינכרוניות, Dispatcher ותכנון טיימרים.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח יישומי Windows
יישומים עסקיים, חיבור התקנים וכלי תקשורת, מהגדרת הדרישות ועד הפיתוח.
שימוש חוזר והעברה של נכסים קיימים
שימוש חוזר והעברה של נכסי COM / ActiveX / OCX ותלויות של 32 או 64 סיביות.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- למה העיצוב של טבלה שהועתקה מ-Excel מתפרק כשאני מדביק אותה ביישום שלי?
- הלוח אינו מחזיק "נתון אחד". אותו תוכן מושם בכמה פורמטים בבת אחת (הפורמט הפרטי של יישום המקור, HTML Format, CSV, טקסט יוניקוד וכדומה), ויישום היעד בוחר פורמט שהוא מבין ומחלץ אותו. כשהעיצוב מתפרק, הסיבה הטיפוסית היא שהיעד קורא רק טקסט פשוט (CF_UNICODETEXT). אם רוצים גם את מבנה הטבלה, מממשים את צד ההדבקה כך שיעדיף HTML Format או CSV. ולהפך, אם רוצים שיישומים אחרים ידביקו נכון מהעתקה שנעשתה ביישום שלכם, מציעים בזמן ההעתקה גם פורמט עשיר וגם פורמט פשוט.
- למה אי אפשר יותר להדביק אחרי שסגרתי את היישום שממנו העתקתי?
- כי המקור משתמש ברינדור מעוכב. יישומים שמטפלים בנתונים גדולים אינם שמים את המטען בזמן ההעתקה; הם רושמים על הלוח רק הבטחה ש"ייצרו אותו כשיבקשו". אם המקור אז יוצא בלי לממש את הנתונים בתגובה ל-WM_RENDERALLFORMATS בכיבוי, כל פורמט שעוד לא רונדר אובד. יישום שמשתמש בלוח OLE (IDataObject) יכול לשמור על הדבקה אחרי יציאה בקריאה ל-OleFlushClipboard בכיבוי כדי לממש את הנתונים.
- איך היישום שלי יכול לצפות בשינויי הלוח?
- השיטה המומלצת כיום היא לרשום את החלון כמאזין עם AddClipboardFormatListener ולטפל בהודעת WM_CLIPBOARDUPDATE שמגיעה בכל פעם שהתוכן משתנה. סקר תוכן על טיימר מבזבז עבודה ויכול להחמיץ עדכונים, ושרשרת הצופים הישנה שמבוססת על SetClipboardViewer נשמרת רק לתאימות לאחור, כי באג ביישום אחד בשרשרת שובר את כל השרשרת. שימו לב גם ש-OpenClipboard בקריאה יכול להיכשל כי תהליך אחר מחזיק את הלוח, לכן מממשים ניסיון חוזר עם המתנה קצרה אם רוצים שהקריאה תהיה יציבה.
- יש דרך להשאיר סודות כמו סיסמאות מחוץ להיסטוריית הלוח (Win+V)?
- יש שני מנופים, אחד בצד היישום ואחד בצד המדיניות. בצד היישום, אם שמים גם את הפורמט הרשום ExcludeClipboardContentFromMonitorProcessing כשמעתיקים, התוכן הזה אינו נכלל לא בהיסטוריה ולא בסנכרון בין מכשירים. אפשר גם לשלוט בכל אחד בנפרד עם CanIncludeInClipboardHistory (היסטוריה בלבד) ו-CanUploadToCloudClipboard (סנכרון בלבד). זה המנגנון שמנהלי סיסמאות משתמשים בו. אם רוצים לכבות זאת לכל הארגון, אפשר להשבית את ההיסטוריה ואת סנכרון הענן עצמם עם AllowClipboardHistory ו-AllowCrossDeviceClipboard דרך מדיניות קבוצה או Intune (Policy CSP).
- למה אי אפשר לגרור ולשחרר קובץ על יישום שרץ כמנהל?
- כי מנגנון אבטחה בשם UIPI (User Interface Privilege Isolation) חוסם מסירת הודעות מתהליך שלמות נמוכה יותר לחלון שלמות גבוהה יותר. סייר הקבצים רץ בהרשאה רגילה (שלמות בינונית), כך שהודעות גרירה ושחרור לעולם אינן מגיעות לחלון של יישום מוגבה. פתרון עוקף שמתיר בנפרד הודעות כמו WM_DROPFILES עם ChangeWindowMessageFilterEx ידוע, אבל הוא חל רק על הודעת השחרור הישנה. התיקון האמיתי הוא להפסיק לתכנן את היישום לרוץ מוגבה כל הזמן, ולבודד רק את העבודה שצריכה הגבהה לתהליך נפרד.