פתרון עבודה ברקע ציור ותצוגה¶
פתרון התרגיל הראשון¶
החלון אינו נהרס מיד בWM_CLOSE אם העבודה עדיין רצה. הוא מבקש עצירה ושומר את הstate עד שהtimer רואה מצב סופי. הworker אינו שולח הודעות ואינו ממתין לUI, ולכן אין מעגל המתנה. הריסת הstate מצטרפת לthread לפני ששדותיו משתחררים.
אפשר להוסיף לתוכנית console נפרדת את הבדיקה הבאה, עם #include "ui_work.h", כדי לבדוק את חוזה החישוב בלי לפתוח חלון. התוצאה של מרוץ בין סיום לביטול יכולה להיות completed או canceled; שתי האפשרויות תקינות כל עוד התוצאה מפורסמת רק בcompleted.
#include "ui_work.h"
#include <chrono>
#include <cstdio>
int main() {
UiWork work;
work.request_stop();
for (;;) {
const auto snapshot = work.snapshot();
if (snapshot.state != WorkState::running) {
std::printf("state=%d progress=%u result=%llu\n",
static_cast<int>(snapshot.state), snapshot.progress,
static_cast<unsigned long long>(snapshot.result));
return 0;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
בדיקה זו אינה מוכיחה שהUI נשאר responsive, כי אין בה UI. לצורך זה נריץ גם את החלון, נזיז אותו ונבדוק קלט בזמן העבודה. הפרדה בין בדיקת רכיב לבדיקה משולבת מונעת מסקנה רחבה יותר מהראיות.
פתרון התרגיל השני¶
הקטע הבא מחליף את בחירת הfont בתוך paint, אחרי שקיבלנו HDC תקין. הוא יוצר font לכל paint כדי להראות את זוגות הבעלות באופן ברור; במוצר אפשר להחזיק cache לפי DPI ולבטל אותו בזמן שינוי מתאים.
const UINT dpi = GetDpiForWindow(window);
HFONT font = CreateFontW(-MulDiv(12, static_cast<int>(dpi), 72), 0, 0, 0,
FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, L"Segoe UI");
if (font) {
HGDIOBJ previous = SelectObject(dc, font);
if (previous && previous != HGDI_ERROR) {
DrawTextW(dc, L"עבודה 17 - report.txt", -1, &client,
DT_RIGHT | DT_RTLREADING | DT_SINGLELINE);
SelectObject(dc, previous);
}
DeleteObject(font);
}
הטקסט מתחיל בעברית, אך גם בתוך המחרוזת יש מקטעים בכיוונים שונים. נבדוק את התוצאה במקום להפוך את כל המחרוזת או להניח שיישור לימין פותר הכול. אם SelectObject נכשל, לא ננסה להחזיר אובייקט לא תקין. אובייקט הfont עדיין בבעלותנו ולכן נמחק אותו.
פתרון התרגיל השלישי¶
ניצור control מסוג BUTTON עם טקסט שמבטא ביטול עבודה ועם WS_TABSTOP. הhandler של WM_COMMAND יבקש עצירה, יעדכן מצב ולא ימתין לסיום. לאחר סיום נשבית את הכפתור ונעדכן control טקסט תקני עם התוצאה.
נבדוק שהfocus מגיע לכפתור בסדר הגיוני ושאפשר להפעיל אותו במקלדת. בעץ UI Automation נדרוש שם, control type מתאים ויכולת Invoke. אם נוסיף progress bar תקני, נבדוק גם את טווח הערכים ואת הערך הנוכחי. עבור רכיב מותאם שנצייר בעצמנו נצטרך provider וpatterns שקולים, כולל טיפול בחיי החלון כשהלקוח של האוטומציה מחזיק reference אחרי סגירה.