Скачайте пример здесь. Код (ASM): ; GUI # include win64a.inc .code WinMain proc local msg:MSG LEFT equ 0 TOP equ 0 RIGHT equ 780 BOTTOM equ 800 invoke InitCommonControls ;Создание родительского окна jns @f xor ebx,ebx @@: mov esi,IMAGE_BASE mov ecx,offset FileName invoke LoadCursorFromFile mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOW+1;hbrBackground push rax ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra pushaddr WndProc;lpfnWndProc mov rax,((CS_HREDRAW or CS_VREDRAW)shl 32)+sizeof WNDCLASSEX push rax ;cbSize & style invoke RegisterClassEx,esp ;addr WNDCLASSEX push rbx push rsi ;rsi = 400000h shl esi,9 ;rsi = CW_USEDEFAULT push rbx push rbx push RIGHT push BOTTOM push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPED or WS_VISIBLE or \ WS_CAPTION or WS_SYSMENU or WS_MINIMIZEBOX ;цикл сообщений lea edi,msg @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp ;Оконная процедура WndProc proc hWnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD local tci:TCITEMA local ps:PAINTSTRUCT local y:DWORD local hdc:QWORD local tm:TEXTMETRICA mov hWnd,rcx cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_CREATE je wmCREATE cmp edx,WM_NOTIFY je wmNOTIFY cmp edx,WM_PAINT je wmPAINT wmDEFAULT:leave jmp NtdllDefWindowProc_ wmDESTROY:invoke RtlExitUserProcess,NULL wmCREATE:invoke GetClientRect,,&expRect ;создать закладку push rbx push IMAGE_BASE push rbx push hWnd mov eax,expRect.bottom sub eax,720 push rax mov eax,expRect.right push rax push TOP push rbx sub esp,20h invoke CreateWindowEx,WS_EX_RIGHTSCROLLBAR,&aSysTabControl32,&NullString,WS_VISIBLE or \ WS_TABSTOP or WS_CHILD mov hTabWnd,rax ;создания элемента STATIC push rbx push IMAGE_BASE push rbx push hWnd push BOTTOM+100 push RIGHT push TOP+25 push LEFT sub esp,20h invoke CreateWindowEx,0,&aStatic,&expTxt0,SS_LEFT or WS_CHILD or WS_VISIBLE mov hStaticText1,rax ;создания элемента STATIC для вывода текста функцией SendMessage push rbx push IMAGE_BASE push rbx push hWnd push BOTTOM push RIGHT;640 push TOP+25 push LEFT sub esp,20h invoke CreateWindowEx,0,&aStatic,0,SS_LEFT or WS_CHILD; or WS_VISIBLE mov hStaticText2,rax ;Заполняем структуру TCITEMA mov tci.imask,TCIF_TEXT or tci.iImage,-1 @@:;создаем 10 закладок mov rax,handle1[rbx*8] mov tci.pszText,rax;название закладки mov tci.lParam,rbx;номер закладки neg rax add rax,handle1[rbx*8+8] mov tci.cchTextMax,eax invoke SendMessage,hTabWnd,TCM_INSERTITEMA,ebx,&tci inc ebx cmp ebx,10 jb @b xor ebx,ebx ;--------------------------------------------------------------- invoke GetDC,hWnd mov hdc,rax invoke GetTextMetrics,eax,&tm mov eax,tm.tmHeight add eax,tm.tmExternalLeading mov font_height,eax ;строим растр, совместимый с окном invoke CreateCompatibleDC,hdc mov MemDC,rax invoke CreateCompatibleBitmap,hdc,RIGHT,BOTTOM mov hbit,rax invoke SelectObject,MemDC,eax invoke GetStockObject,WHITE_BRUSH invoke SelectObject,MemDC,eax invoke SetBkMode,MemDC,TRANSPARENT invoke PatBlt,MemDC,0,0,RIGHT,BOTTOM,PATCOPY invoke ReleaseDC,hWnd,hdc jmp wmBYE wmNOTIFY:;изменение состояния закладки cmp [r9+NMHDR._code],TCN_SELCHANGE jne wmBYE;DEFAULT invoke SendMessage,hWnd,WM_SETTEXT,0,&ClassName invoke ShowWindow,hStaticText2,SW_HIDE invoke ShowWindow,hStaticText1,SW_HIDE;спрятать "статический" текст invoke PatBlt,MemDC,0,0,RIGHT,BOTTOM,PATCOPY xor ebx,ebx mov edi,font_height mov y,ebx invoke SendMessage,hTabWnd,TCM_GETCURSEL,0,0 jmp [handle+rax*8];переходим на выбранную закладку DrawTextEx1::;вывод текста при помощи функции DrawTextEx invoke DrawTextEx,MemDC,&expTxt5,-1,&expRect,DT_LEFT or DT_WORDBREAK or DT_EXPANDTABS or \ DT_END_ELLIPSIS or DT_MODIFYSTRING or DT_WORD_ELLIPSIS,0 jmp a1 wmPAINT:invoke BeginPaint,,&ps invoke BitBlt,eax,0,0,RIGHT,BOTTOM,MemDC,0,-28,SRCCOPY invoke EndPaint,hWnd,&ps jmp wmBYE ExtTextOut1::;вывод текста при помощи функции ExtTextOut @@: mov r10d,stringtable2[rbx] mov eax,stringtable2[rbx+4] or eax,eax jz a1 sub eax,r10d invoke ExtTextOut,MemDC,LEFT,y,0,&expRect,r10,rax,0 add y,edi add ebx,4 jmp @b STATICText::;вывод стического текста invoke ShowWindow,hStaticText1,SW_SHOW jmp wmBYE PolyTextOut1::;вывод текста при помощи функции PolyTextOut invoke PolyTextOut,MemDC,&pptxt,19 jmp a1 TextOut1::;вывод текста при помощи функции TextOut @@: mov r9d,stringtable3[rbx] mov eax,stringtable3[rbx+4] or eax,eax jz a1 sub eax,r9d mov [rsp+20h],rax invoke TextOut,MemDC,LEFT,y add y,edi add ebx,4 jmp @b DrawText1::;вывод текста при помощи функции DrawText invoke DrawText,MemDC,&expTxt1,-1,&expRect,DT_WORDBREAK jmp a1 Title1::invoke SetWindowText,hWnd,&expTxt8 jmp a1 Title2::invoke SendMessage,hWnd,WM_SETTEXT,0,&expTxt9 jmp a1 TabbedTextOut1::;вывод текста при помощи функции TabbedTextOut @@: mov r9d,stringtable4[rbx] or r9,r9 jz a1 invoke TabbedTextOut,MemDC,LEFT,y,,-1,0,0,0 add y,edi add ebx,4 jmp @b a1: xor ebx,ebx invoke InvalidateRect,hWnd,0,TRUE jmp wmBYE SendMessage1::invoke SendMessage,hStaticText2,WM_SETTEXT,0,&expTxt7 invoke ShowWindow,hStaticText2,SW_SHOW wmBYE: leave xor eax,eax ret WndProc endp ;--------------------------------------- ClassName db 'Uncle Remus tales:#5a Вывод текста всеми способами' NullString db 0 FileName db "br_Rabbit3.cur",0 hTabWnd dq ? aSysTabControl32 db "SysTabControl32",0 tab0 db "Static",0 tab1 db "DrawTextEx",0 tab2 db "TextOut",0 tab3 db "TabbedTextOut",0 tab4 db "DrawText",0 tab5 db "PolyTextOut",0 tab6 db "ExtTextOut",0 tab7 db "SendMessage",0 tab8 db "Title1",0 tab9 db "Title2",0 handle1 dq tab0,tab1,tab2,tab3,tab4,tab5,tab6,tab7,tab8,tab9,handle1 handle dq STATICText,DrawTextEx1,TextOut1,TabbedTextOut1,DrawText1 dq PolyTextOut1,ExtTextOut1,SendMessage1,Title1,Title2 ;--------------------------------------------------------------------------------------------- expTxt0 db 'Было еще не так поздно, а госпожа Салли, мама семилетнего Джоэля, уже начала волноваться: "Где же он до ' db 'сих пор носится, этот сорванец?" Она обыскала весь дом, обыскала весь двор, но мальчика нигде не было. ' db 'Вдруг госпожа Салли услышала голоса в хижине старого негра Римуса. Она заглянула в окно и увидела, что Джоэль ' db 'сидит у дядюшки Римуса на коленях и, прислонив голову к плечу старика, внимательно ' db 'смотрит в его доброе морщинистое лицо. И госпожа Салли поняла, что негр рассказывает ' db 'ее сыну одну из тех чудесных историй, которые он рассказывал и ей, когда она была крохотной ' db 'девочкой. Госпожа Салли улыбнулась и задумчиво побрела к своему дому.',10 db 'Но мы останемся и вместе с Джоулем послушаем дядюшку Римуса. Вот что он рассказал в ' db 'этот вечер: -- С незапамятных пор, мальчик, Братец Лис гонялся за Братцем Кроликом. И чего ' db 'только он ни придумывал, чтобы поймать его, а Братец Кролик чего только не придумывал, ' db 'чтобы не угодить в лапы Братцу Лису.',10 db 'Вот однажды и сказал сам себе Братец Лис: -- "Хватит, пора покончить с Братцем Кроликом раз' db 'и навсегда!" И едва он произнес эти слова, Братец Кролик тут как тут. Бежит по лужайке, взбрыкивает ' db 'задом, прядет ушами, резвый, веселый, словно жеребчик, вырвавшийся на волю. Да еще и песенку напевает:',10 db ' Дидель-дум-дай, дидель-дум-дай,',10 db ' Я выбежал на луг.',10 db ' Дидель-дум-дай, дидель-дум-дай,',10 db ' Как хорошо вокруг!',10 db ' Хочу -- туда, хочу -- сюда',10 db ' Скачу средь бела дня',10 db ' И Братцу Лису никогда',10 db ' Здесь не найти меня',10 db 'Тут он увидел Братца Лиса и остановился, как вкопанный.',10 db '"Ну, что же ты, Братец Кролик? Иди сюда" -- сказал Братец Лис',10 db '"Не могу, Братец Лис, ноги в траве запутались" -- ответил Братец Кролик.',10 db '"Тогда я сам подойду к тебе. Нам надо поговорить об одном важном деле."',10 db '"Не советую рисковать, Братец Лис. Приближаться ко мне опасно."',10 db '"Почему. Братец Кролик?"',10 db '"День для меня сегодня плох -- я нахватался где-то блох. Говори оттуда, только погромче"',10 db 'Братец Лис так бы и бросился на Братца Кролика, да побоялся заразиться блохами.',10 db '"Ну, хорошо, Братец Кролик, я не буду приближаться к тебе", -- говорит Братец Лис, а сам думает -- "О каком бы важном деле поговорить с братцем Кроликом, чтобы обмануть его?" ' db 'И послушай, что придумал Братец Лис: "Вчера я встретил Братца Медведя, он пристыдил меня и сказал, что раз мы с тобой соседи, то, как и подобает соседям, должны жить в дружбе и согласии. Я обещал Братцу медведю потолковать с тобой об этом."',10 db 'Братец Кролик почесал лапой за ухом, будто его действительно кусают блохи. Он не поверил ни одному слову, но вида не подал.',10 db '"Я не против мира", -- сказал он. -- "А чтобы закрепить наши добрососедские отношения, приходи завтра к нам на обед. Мы хоть и небогаты, но, я думаю, моя жена сумеет приготовить что-нибудь, достойное такого высокого гостя, как ты".',10 db '"Благодарю за приглашение, Братец Кролик. Я непременно приду", -- сказал Братец Лис, а про себя подумал -- "Это именно то, что мне и надо. Завтра же я полакомлюсь свежей крольчатиной"',10 db '"Итак, мы договорились", -- Братец Кролик откланялся и поскакал прочь.',10 db 'На следующий день, рано утром, пока маленькие крольчата еще нежились в своих кроватках, папа Кролик и мама Крольчиха пошли в огород, -- а это было единственное, что осторожная Крольчиха держала снаружи дома, -- нарвали там разного дурмана и ' db 'конопли, вернувшись домой, приготовили такую отраву, что одной ложки было достаточно, чтобы отбросить все четыре лапы. Когда угощение было готово, Кролики сели за стол и стали ждать Братца Лиса. Ждут-пождут, а Братец Лис не появляется.',10 db 'Тут один из маленьких крольчат, игравший на полу, подбежал к двери и посмотрел в дырочку от выпавшего сучка.',10 db '"Папа, мама!" -- радостно закричал он -- "Там во дворе, совсем близко, лежит какая-то рыжая длинная штука. Может быть мы сбегаем и поглядим?"',10 db 'Братец Кролик тоже припал к дырочке. И как ты думаешь, что он увидел? -- Из-за угла дома торчит лисий хвост. Братец Лис-то надеялся сцапать либо самого Братца Кролика, либо кого-нибудь из маленьких крольчат. ' db 'Вот и затаился, а про свой длинный хвост забыл.',0 expTxt1 db '-- Давным-давно это было, -- начал дядюшка Римус, надевая очки, чтобы вставить нить в иголку. У него',10 db 'прохудился пиджак, и он хотел починить его. Но нить никак не попадала в игольное ушко',10 db '-- Дядюшка Римус, давай я тебе помогу -- предолжил Джоэль',10 db 'А ну-ка, мальчик -- старик протянул ему иголку и нитку',10 db 'Джоэль мигом вставил нить, удивляясь, почему это не получилось у дядюшки Римуса.',10 db '-- У тебя глаза молодые, зоркие, а мои даже с очками никуда не годятся -- Дядюшка Римус вздохнул и стал',10 db 'продолжать рассказ. -- Да, очень и очень давно это случилось, когда еще не было на свете ни тебя, ни твоего отца,',10 db 'ни твоей мамы, ни твоего дедушки, ни твоей бабушки.',10 db '-- А ты был, дядюшка Римус?',10 db '-- Нет, мальчик, и меня не было. Не было вообще никого из живущего теперь на земле человеческого рода. Вот',10 db 'тогда и порешили все звери от мала до велика провести заседание.',10 db '-- Какое заседание, дядюшка Римус?',10 db '-- Обыкновенное, на котором обсуждают какие-нибудь выжные вопросы. В те времена, -- пояснил старик, заметив',10 db 'на лице ребенка некоторое недоверие, -- у зверей разума было гораздо больше, чем теперь, во всяком случае,',10 db 'не меньше, чем сейчас у людей. Как порешили, так и зделали. Прекратили все распри, уладили все дела и в',10 db 'назначенный день собрались в назначенном месте. Был тут и Слон и Осел, Верблюд и Носорог и прочие звери,',10 db 'вплоть до маленьких раков.',10 db 'Император Лев, властелин зверей, поприветствовал своих подданых, сел на трон и заседание началось.',10 db '-- Что же они обсуждали, дядюшка Римус? -- спросил Джоэль. Он хотя и был маленький, но, часто слышал',10 db 'разговоры взрослых, неплохо разбирался в политических событиях.',10 db '-- Я не помню, какие именно вопросы они обсуждали, но знаю точно, что речи там произносились длинные,',10 db 'шума было много. Все лезли на трибуну, оттаптывали друг другу ноги. Крепкие словечки так и летали в воздухе.',10 db 'Словом, это походило на предвыборный митинг в нашем штате, когда твой отец хотел стать губернатором, но у',10 db 'него ничего не вышло. Заседание было в самом разгаре, но тут случилось непредвиденное: Слон протискиваясь',10 db 'к трибуне, нечаянно наступил на Рака. Ну, ты, разумеется, знаешь: где ступит Слон, там трава не растет.',10 db 'Раздался хруст, и когда Слон поднял ногу, бедного Рака больше не было видно, как будто его вообще не',10 db 'существовало. Остальные раки пришли в негодование. Самый усатый из них закричал:',10 db '"Господа раки! Прошу всех собраться у выхода!"',10 db 'Раки сползлись все вместе и сочинили протест, состоящий из множества пунктов, один грозней другого. в конце',10 db 'они написали:',10 db ' Мы заявляем: если Слон',10 db ' Не будет осужден.',10 db ' А нанесенный нам урон',10 db ' Не будет возмещен,',10 db ' То мы ряды свои сомкнем,',10 db ' Шагнем вперед спиной.',10 db ' И, как один, навек уйдем',10 db ' С поверхности земной',10 db '-- Дядюшка Римус, а почему они написали: "шагнем вперед спиной"? -- спросил мальчик с недоумением.',10 db '-- Мальчик, мальчик! -- старик сдвинул очки на лоб -- Разве ты никогда не видел, как ходят раки?',10 db '-- Нет, дядюшка.',10 db '-- Раки, мальчик, это такой народ, который всегда ходит задом наперед.',10 db 'Пояснив, дядюшка Римус продолжил:',10 db '-- С этим протестом раки попытались пролезть на трибуну вне очереди. Но, Боже мой, поднялся такой шум, что',10 db 'никого не было слышно, особенно ящериц и черепах, которые и без того-то не разговаривали.',10 db 'Император Лев, властелин зверей, пытался урезонить Носорога, который шумел больше всех, Гиена стала смеяться',10 db 'в кулачки. Слон, неловко повернувшись, раздавил еще одного Рака и чуть было не наступил на Черепаху.',10 db 'Раки вновь сползлись в одну кучу, сочинили новый протест, с еще большим количеством пунктов, и вторично',10 db 'полезли к трибуне, требуя. чтобы им предоставили слово. Но с таким же успехом могла бы пищать мышь рядом',10 db 'с грохочущим барабаном. Других зверей больше интересовало, как поставить и решить собственные вопросы. Бедные',10 db 'раки не знали, как им быть. Они злились все сильнее, метались из стороны в сторону. Наконец, сговорившись с',10 db 'ящерицами и черепахами, просверлили дыры в земле, заползли в них, и были таковы.',0
Код (ASM): expTxt2A db 'На другой день мальчик пришёл к дядюшке Римусу послушать, чем кончилась история с лошадью Братца' expTxt2B db 'Кролика. Но дядюшка Римус был не в духе.' expTxt2C db '-— Плохим мальчикам я не рассказываю никаких сказок, —- сказал он.' expTxt2D db '—- Но ведь я не плохой, дядюшка Римус!' expTxt2E db '—- А кто кур гонял сегодня утром? И кто стрелял из рогатки? И кто в обед науськал собаку на моего' expTxt2F db 'поросёнка? И ко мне на крышу кто бросал камни?' expTxt2G db '— Я не нарочно, дядюшка Римус, и я больше не буду. Пожалуйста, дядюшка Римус, а я коржиков тебе принесу.' expTxt2H db '— Коржики — они, конечно, лучше на вкус, чем на слух…' expTxt2I db 'Но, прежде чем старик кончил, Джоэль стрелой умчался прочь, а через минуту вернулся назад с полными ' expTxt2J db 'карманами коржиков.' expTxt2K db '— Право, твоя мама подумает, что у крыс по соседству вот как животы раздуло! — усмехнулся дядюшка Римус.' expTxt2L db ' — Эти вот я сейчас съем, — продолжал он, раскладывая коржики на две одинаковые кучки, — а вот эти оставлю' expTxt2M db 'на воскресенье… Так до чего ж мы дошли? Я и забыл, что у нас делали Братец Лис и Братец Кролик.' expTxt2N db '— Кролик прискакал на Лисе верхом к Матушке Мидоус и привязал Лиса к коновязи.' expTxt2O db '— Ага! — сказал дядюшка Римус. — Так вот, привязал он свою лошадь к коновязи, а сам пошёл в дом, закурил' expTxt2P db 'сигару. Они болтали с Матушкой Мидоус и с девочками и пели, и девочки играли на пианино. Потом Братцу' expTxt2Q db 'Кролику пришло время уходить.' expTxt2R db 'Попрощался он и пошёл к коновязи такой важной походкой, вроде как барин. Сел на Лиса и поехал прочь.' expTxt2S db 'Старый Лис ничего не сказал. Он только стиснул зубы и поскакал вперёд. Но Братец Кролик знал, что Лис' expTxt2T db 'так и кипит от злости. Ох и струсил же он!' expTxt2U db 'А Лис бежал, бежал, пока не выбрался на лужайку, подальше от дома Матушки Мидоус. Тут он как с цепи' expTxt2V db 'сорвался. Уж он бесился: и фыркал, и бранился, и визжал, и прыгал, и кружился… ' expTxt2W db 'Так и этак старался сбросить Братца Кролика со спины. Но Кролик держался крепко. Выгнет спину Лис, а' expTxt2X db 'Кролик его шпорами. Старый Лис и вверх и вбок, щёлк да щёлк зубами — чуть свой собственный хвост не отгрыз.' expTxt2Y db 'Потом вдруг на землю — и ну кататься. Тут Кролик и вылетел из седла. Но, прежде чем Лис вскочил на ноги,' expTxt2Z db 'Кролик в кусты — и наутёк. А Лис за ним, да так шибко — еле-еле успел Кролик нырнуть в дупло.' expTxt2a db 'Дыра была маленькая, Лису никак не пролезть. Вот лёг он, отдышался, стал думать, как быть теперь с Кроликом.' expTxt2b db 'А пока так лежал Старый Лис, пролетал мимо Братец Сарыч.' expTxt2c db 'Увидал, что Лис лежит, как дохлый, — дай, думает, закушу дохлятинкой. Сел на ветку, похлопал крыльями.' expTxt2d db 'Наклонил голову набок и говорит, будто сам себе:' expTxt2e db '— Помер Братец Лис. А мне как жалко!' expTxt2f db '— Нет, я жив, — говорит Лис. — Я загнал сюда Братца Кролика. Уж на этот раз он не уйдёт, хоть до Нового' expTxt2g db 'года буду ждать тут.' expTxt2h db 'Потолковали они ещё. Сарыч согласился постеречь Кролика, пока Братец Лис сбегает за топором. Лис убежал,' expTxt2i db 'а Сарыч стал, стоит у дупла. Вот, как стало тихо, Кролик подошёл поближе к дыре и кричит:' expTxt2j db '— Братец Лис! А Братец Лис!' expTxt2k db 'Но Лис был уже далеко, и никто не ответил. Тогда Кролик закричал:' expTxt2l db '— Ах, ты не хочешь отвечать, Братец Лис? И не надо! Всё равно я знаю, что ты тут стоишь. А мне и дела нет.' expTxt2m db 'Я просто хотел сказать тебе: вот если б тут был Братец Сарыч!' expTxt2n db 'Тогда Сарыч ответил лисьим голосом:' expTxt2o db '— А зачем тебе нужен Братец Сарыч?' expTxt2p db '— Да так, просто тут серая белка в дупле, а жирная, — сколько живу, такой не видал. Был бы тут Братец Сарыч,' expTxt2q db 'уж он бы полакомился Белочкой.' expTxt2r db 'Сарыч опять лисьим голосом:' expTxt2s db '— А как бы поймал её Братец Сарыч?' expTxt2t db '— А тут, на другой стороне дерева, маленькая дырочка, — говорит Кролик. — Был бы тут Братец Сарыч, стал бы' expTxt2u db 'он возле той дырочки, а я бы выгнал оттуда белку.' expTxt2v db '— Ну гони, гони, — сказал Сарыч, — а я постараюсь, чтоб она не ушла от Братца Сарыча.' expTxt2w db 'Тогда Кролик поднял шум, будто гонит кого-то, и Сарыч побежал на ту сторону ловить белку. А Кролик шмыг из' expTxt2x db 'дупла — и во все лопатки домой.' expTxt2y db 'Тут дядюшка Римус взял коржик, откинул назад голову, положил коржик в рот. Потом закрыл глаза и принялся жевать,' expTxt2z db 'бормоча под нос песенку.' expTxt2@ dd 0 stringtable2 dd expTxt2A,expTxt2B,expTxt2C,expTxt2D,expTxt2E,expTxt2F, expTxt2G,expTxt2H, expTxt2I,expTxt2J,expTxt2K,expTxt2L dd expTxt2M,expTxt2N,expTxt2O,expTxt2P,expTxt2Q,expTxt2R,expTxt2S, expTxt2T,expTxt2U,expTxt2V,expTxt2W,expTxt2X,expTxt2Y dd expTxt2Z,expTxt2a,expTxt2b,expTxt2c,expTxt2d,expTxt2e,expTxt2f, expTxt2g,expTxt2h,expTxt2i,expTxt2j,expTxt2k,expTxt2l dd expTxt2m,expTxt2n,expTxt2o,expTxt2p,expTxt2q,expTxt2r,expTxt2s,expTxt2t, expTxt2u,expTxt2v,expTxt2w,expTxt2x,expTxt2y dd expTxt2z,expTxt2@,0 expTxt3A db 'Дядюшка Римус посадил Джоэля на колени и ласково поглаживая его льняные волосы начал рассказывать.' expTxt3B db '-- Братец Опоссум и Братец Енот были такими друзьями, что их как говорится, водой не разольешь.' expTxt3C db 'Куда Братец Опоссум -- туда и Братец Енот, куда Братец Енот -- туда и Братец Опоссум. И вот как-то' expTxt3D db 'вечером Братец Опоссум гостил у Братца Енота. Они съели блюдо тушеных овощей, выкурили по сигаре' expTxt3E db 'и решили навестить знакомых, которые жили по соседству. Старик, вероятно, забыл, что накануне он' expTxt3F db 'не закончил рассказ про Братца Кролика, попавшего в беду, а маленький Джоэл решил, что это' expTxt3G db 'продолжение вчерашнего рассказа и не стал перебивать дядюшку Римуса.' expTxt3H db '-- Они добежали бы быстро, ведь оба они бегают как гнедая лошадь твоего отца, но Братец Опоссум то' expTxt3I db 'и дело останавливался, чтобы набить живот лесными сливами, а Братец Енот, поджидая его, дурачился' expTxt3J db '-- то квакал, как лягушка, то верещал, как головастик. Вдруг Братец Енот и Братец Опоссум услышали,' expTxt3K db 'что в глубине леса лает Собака' expTxt3L db '"Братец Опоссум, а что ты будешь делать, если Сестрица Собака прибежит сюда?" -- спросил Братец Енот.' expTxt3M db '"Если она прибежит сюда, Братец Енот, я не брошу тебя на произвол судьбы. Ты можешь смело положиться на' expTxt3N db 'меня", -- ответил Братец Опоссум -- "А что ты сделаешь?"' expTxt3O db '"Кто, я?" -- удивился Братец Енот -- "Пусть она только появится, я ей покажу, где раки зимуют".' expTxt3P db '-- "И Сестрица Собака появилась?" -- спросил нетерпеливо Джоэль' expTxt3Q db 'А собака увидала их и не стала тратить времени зря. Она и здороваться не стала.' expTxt3R db 'Прямо кинулась на них — и всё тут.' expTxt3S db 'Братец Опоссум в ту же минуту осклабился, рот до ушей, и кувырнулся на спину, будто мёртвый.' expTxt3T db 'А Енот — тот мастер был драться. Подмял под себя собаку и ну трепать. Правду сказать, от собаки не много' expTxt3U db 'осталось, а то, что осталось, вырвалось — и наутёк, в самую чащу, будто кто пальнул из ружья.' expTxt3V db 'Вот Братец Енот привёл свой костюм в порядок, встряхнулся, а Братец Опоссум всё лежал как мёртвый. Потом' expTxt3W db 'осторожно привстал, огляделся да как бросится бежать, только пятки засверкали.' expTxt3X db 'В другой раз, как повстречались Опоссум и Енот, говорит Опоссум:' expTxt3Y db '— Здравствуй, Братец Енот! Как поживаешь?' expTxt3Z db 'Но Енот — руки в карманы, здороваться не хочет.' expTxt3a db '— Ты что ж это нос воротишь, Братец Енот? — спрашивает Опоссум.' expTxt3b db '— Я с трусами и разговаривать не хочу, — отвечает Енот. — Ступай своей дорогой!' expTxt3c db 'Опоссум разобиделся — страх.' expTxt3d db '— Кто ж это трус, нельзя ли узнать?' expTxt3e db '— Да ты, конечно, — говорит Енот. — Очень нужны мне такие приятели, что кидаются на спину и строят из себя' expTxt3f db 'мёртвых, чуть дело дойдёт до драки!' expTxt3g db 'Опоссум, как услышал эти слова, ну смеяться, ну хохотать.' expTxt3h db '— Неужто ты думаешь, Братец Енот, что это я со страху? Не думаешь ли ты, что я испугался несчастного пса?' expTxt3i db 'И чего мне было бояться? Я ведь отлично знал, что, если я не слажу с этой собакой, ты-то задашь ей жару.' expTxt3j db 'Да я просто лежал и смотрел, как ты треплешь её, и ждал, когда придёт мой черёд позабавиться.' expTxt3k db 'Но Енот только нос наморщил:' expTxt3l db '— Рассказывай сказки, Братец Опоссум. Как дотронулась до тебя собака, ты сразу кувырнулся и прикинулся' expTxt3m db 'мёртвым.' expTxt3n db '— Так ведь я говорю тебе, Братец Енот, что это совсем не от страху. Я одной только вещи и боюсь на свете' expTxt3o db '— это щекотки. А когда эта собака ткнулась носом мне в рёбра, я рассмеялся, и так разобрал меня смех, что' expTxt3p db 'вот не шелохнуть ни рукой, ни ногой! Конечно, её счастье, что я боюсь щекотки, а то ещё минутка, и я' expTxt3q db 'разорвал бы её в клочья. Драки я не боюсь никакой, Братец Енот, но щекотка — это дело другое. С кем угодно' expTxt3r db 'согласен я драться, но только — чур — без щекотки.' expTxt3s db '— Вот с того самого дня, — продолжал дядюшка Римус, глядя, как завивается в кольца дымок из трубки, — и до' expTxt3t db 'сих пор так боится щекотки Братец Опоссум: тронь его только между рёбер — кидается на спину и хохочет до' expTxt3u db 'упаду, так что не может шевельнуть ни рукой, ни ногой.' expTxt3v db 0 stringtable3 dd expTxt3A,expTxt3B,expTxt3C,expTxt3D,expTxt3E,expTxt3F,expTxt3G,expTxt3H,expTxt3I,expTxt3J,expTxt3K,expTxt3L dd expTxt3M,expTxt3N,expTxt3O,expTxt3P,expTxt3Q,expTxt3R,expTxt3S,expTxt3T,expTxt3U,expTxt3V,expTxt3W,expTxt3X,expTxt3Y,expTxt3Z dd expTxt3a,expTxt3b,expTxt3c,expTxt3d,expTxt3e,expTxt3f,expTxt3g,expTxt3h,expTxt3i,expTxt3j,expTxt3k,expTxt3l dd expTxt3m,expTxt3n,expTxt3o,expTxt3p,expTxt3q,expTxt3r,expTxt3s,expTxt3t,expTxt3u,expTxt3v,0 expTxt4A db 'Заметив, что старый негр сидит на пороге хижины и ничем не занят, Джоэль присел рядышком.',0 expTxt4B db 'Негр молчал, и Джоэль молчал, не решаясь нарушить ход мыслей старика. Но время шло, и',0 expTxt4C db 'мальчик не выдержал.',0 expTxt4D db '-- "Дядюшка Римус, а Братец Лис съел Братца Кролика?" -- спросил он осторожно.',0 expTxt4E db '— Разве ж я не рассказывал тебе об этом, дружок? Ну да, я ведь сонный был, и в голове',0 expTxt4F db 'у меня всё спуталось, и мама как раз позвала тебя. О чём же мы тогда толковали? Помню, помню. Ты, никак, и',0 expTxt4G db 'глазки уже трёшь? Нет, плакать по Братцу Кролику погоди. Даром, что ли, он был такой шустрый? Ты послушай,',0 expTxt4H db 'что дальше будет.',0 expTxt4I db 'Приклеился, значит, Братец Кролик к Чучелку, а Старый Лис ну кататься по земле, ну хохотать. А потом говорит:',0 expTxt4J db '— Сдаётся мне, Братец Кролик, на этот раз я тебя поймал. Может, я и ошибаюсь, но кажется мне,',0 expTxt4K db 'что поймал. Ты всё тут скакал и потешался надо мной, но теперь конец твоим шуткам. И кто просил',0 expTxt4L db 'тебя лезть не в своё дело? И зачем сдалось тебе это Чучелко? И кто это прилепил тебя к нему? Никто,',0 expTxt4M db 'никто в целом свете! Никто не просил тебя, а просто ты сам взял и влепился в это Чучелко! И сам ты во',0 expTxt4N db 'всём виноват, Братец Кролик! Так и надо тебе, так и будешь сидеть, пока я не наберу хворосту и не зажгу его,',0 expTxt4O db 'потому что я, конечно, зажарю тебя сегодня, Братец Кролик.',0 expTxt4P db 'Так сказал Старый Лис.',0 expTxt4Q db 'А Кролик отвечает так смирно, послушно:',0 expTxt4R db '— Делай со мной что хочешь, братец Лис, только, пожалуйста, не вздумай бросить меня в этот терновый куст.',0 expTxt4S db 'Жарь меня, как хочешь. Братец Лис, только не бросай меня в этот терновый куст.',0 expTxt4T db '— Пожалуй, слишком много возни с костром, — говорит Лис. — Пожалуй, я лучше, повешу тебя, Братец Кролик.',0 expTxt4U db '— Вешай, как хочешь высоко, Братец Лис, — говорит Кролик, — только бы ты не вздумал бросить меня в этот',0 expTxt4V db 'терновый куст.',0 expTxt4W db '— Верёвки-то у меня нет, — говорит Лис, — так что, пожалуй, я утоплю тебя.',0 expTxt4X db '— Топи меня так глубоко, как хочешь, Братец Лис, — говорит Кролик, — только не бросай меня в этот терновый куст.',0 expTxt4Y db 'Но Братец Лис хотел расправиться с Кроликом покрепче; — Ну, — говорит, — раз ты боишься, как раз и брошу',0 expTxt4Z db 'тебя в терновый куст.',0 expTxt4a db '— Где тебе! — говорит Братец Кролик. — С Чучелком-то я слишком тяжёл, не добросишь.',0 expTxt4b db 'Схватил Лис Кролика за уши да как тряхнёт! Отклеилось, упало Чучелко.',0 expTxt4c db '— А вот и доброшу, — говорит Лис.',0 expTxt4d db 'Как размахнётся, как бросит Кролика в серёдку тернового куста, даже треск пошёл.',0 expTxt4e db 'Встал Лис на задние лапы, смотрит, что будет с Кроликом. Вдруг слышит — кличет его кто-то. Глядь — там,',0 expTxt4f db 'на пригорке. Братец Кролик на брёвнышке, нога на ногу, сидит-посиживает, смолу из шерсти вычёсывает щепкой.',0 expTxt4g db 'Понял тут Лис, что опять остался в дураках. А Братцу Кролику позлить его охота, он и кричит:',0 expTxt4h db '— Терновый куст — мой дом родной. Братец Лис! Терновый куст — мой дом родной!',0 expTxt4i db 'Вскочил и пропал, как сверчок в золе.',0 expTxt4@ dd 0 stringtable4 dd expTxt4A,expTxt4B,expTxt4C,expTxt4D,expTxt4E,expTxt4F, expTxt4G, expTxt4H,expTxt4I,expTxt4J,expTxt4K,expTxt4L,expTxt4M,expTxt4N,expTxt4O,expTxt4P,expTxt4Q,expTxt4R,expTxt4S,expTxt4T,expTxt4U,expTxt4V,expTxt4W dd expTxt4X,expTxt4Y,expTxt4Z,expTxt4a,expTxt4b,expTxt4c,expTxt4d,expTxt4e, expTxt4f,expTxt4g,expTxt4h,expTxt4i,expTxt4@,0 expTxt5 db '-- "Дядюшка Римус, а Братец Лис и в самом деле никогда не поймал Братца Кролика?' db '-- спосил маленький Джоэль',10 db 'на следующий вечер',10 db '-- "Один раз Братец Лис чуть-чуть не поймал Братца Кролика. Это случилось вскоре' db 'после истории с укропом.',10 db 'Братец Лис рыскал повсюду вынюхивая, где что плохо лежит, -- стянул у соседей большой кусок смолы.',10 db 'Я знаю, дядюшка Римус -- смолой ты натираешь дратву, когда чинишь обувь.',10 db '-- Правильно, Джоэль. Смолой натирают дратву, мажут днища лодок, крыши домов, делают ' db 'асфальт... Послушай',10 db 'ЧТО придумал Братец Лис. Он размягчил смолу и вылепил из нее человечка, да так ловко, что его можно было',10 db 'запросто принять за живого. Выражение лица у этого смоляного человечка было свирепое, а от солнца он стал',10 db 'таким липким, что до него нельзя было дотронуться. Братец Лис назвал его -- Липкий Джек, а для пущей',10 db 'важности надел на него свою старую соломенную шляпу. Этого мистера Джека -- Липкого Человечка, Братец Лис',10 db 'поставил на полянке, куда Братец Кролик обычно прибегал порезвиться перед обедом, а сам спрятался в кустах',10 db 'неподалеку.',10 db 'Прошло немного времени, скачет Братец Кролик -- прыг, прыг, прыг. Шерстка лоснится, грудь -- колесом, хвост',10 db '-- торчком. Ладный, да ловкий. Скачет и поет:',10 db ' Укроп для Лиса был в тот день,',10 db ' Как наголову снег',10 db ' Со мной тебе, трухлявый пень,',10 db ' не справиться вовек',10 db 'А Братец Лис лежит в кустах и ухмыляется -- "Посмотрим, Братец Кролик, как ты запоешь в объятиях липкого',10 db 'Джека"',10 db 'Выбежал Братец Кролик на полянку и остолбенел -- прямо перед ним стоит черный незнакомец в соломенной',10 db 'шляпе и вид его не предвещает ничего хорошего.',10 db 'Братец Лис в кустах даже дышать перестал.',10 db '"Добрый день, мистер!" -- вежливо сказал Братец Кролик -- "Чудесная погода сегодня. Не правда ли?"',10 db 'Смоляной человечек не ответил ничего. А Братец Лис лежал тихо-тихо.',10 db '"Как поживаете, мистер?... Извините, не имею чести знать ваше имя" -- Братец Кролик замолчал ожидая ответа.',10 db 'Братец Лис довольнеханек, но лежит тихо.',10 db '"Как поживаете, я спрашиваю. Или вы глухой, мистер? Если так, то я могу подойти поближе и говорить громче"',10 db 'Смоляное чучелко не проронило и слова. А поскольку смола на солнце начала плавиться, лицо его перекосило и',10 db 'стало еще свирепее.',10 db '"Однако, парень, ты невежа, как я погляжу" -- сказал Братец Кролик -- "К тебе обращается почтенный',10 db 'джентельмен, а ты молчишь словно воды в рот набрал, да еще и рожи корчишь"',10 db 'Братец Лис тихонько хихикнул, а смоляной человечек презрительно посмотрел на Братца Кролика и опять ничего',10 db 'не ответил Братец Кролик начал терять терпение -- "Ну, хорошо, я тебя научу вежливости. Если ты сейчас же не',10 db 'снимешь шляпу и не скажешь -- Добрый день, Братец Кролик! -- я тебя вздую"',10 db 'Смоляное чучелко молчит, а Братец Лис лежит тихо.',10 db 'Братец Кролик закипел -- "Ах, ты, такой-сякой невежа, вот я тебе сейчас как врежу!" ',10 db 'Он размахнулся и со всей силы ударил смоляного человечка кулаком в ухо. Лапа, конечно же, завязла в смоле,',10 db 'да так крепко, что не оторвать. Братец Лис в кустах чуть не описался от удовольствия.',10 db '"Эй, черножопый, что ты делаешь?! Отпусти мою лапу!" -- закричал Братец Кролик -- "Это не честный прием.',10 db 'Если ты сейчас же не отпустишь мою лапу, то получишь еще!" Но Липкий Джек и не думает отпускать его лапу.',10 db 'А Братец Лис лежит тихо.',10 db '"Ах, так! Ну, погоди!" -- и Братец Кролик стукнул Смоляного Человечка другой лапой. И другая лапа завязла.',10 db 'Братец Лис внесебя от радости, но ждет, не показывается',10 db 'А Братец Кролик не на шутку разбушевался -- "Отпусти, говорю! А не отпустишь -- я пну тебя в живот" Но',10 db 'липкий Джек молчит и крепко держит Братца Кролика. Братец Кролик пнул ненавистного незнакомца одной ногой,',10 db 'другой -- обе задние лапы Братца Кролика также завязли. А Братец Лис не торопится выходить. "Последний раз',10 db 'тебя предупреждаю", -- разъярился Братец Кролик -- "Если не отпустишь. я ударю так, что у тебя искры из глаз',10 db 'посыпятся"',0 expTxt7 db '-— Если я не ошибаюсь, -— начал дядюшка Римус, -— Братец Сарыч всё стерёг дупло, куда спрятался Кролик',10,'и откуда он давно уже выскочил. На этом мы кончили, Джоэль?',10 db 'Братец Сарыч совсем приуныл. Но он обещал Лису, что постережет Кролика.',10 db '"Дай-ка, -— думает, — подожду Братца Лиса, обману его как-нибудь".',10 db 'Глядь-поглядь —- скачет из лесу Лис с топором на плече.',10 db '-— Ну, что слышно, Братец Сарыч? Всё там Братец Кролик?',10 db '— Там, конечно, — отвечает Сарыч. — Притаился — видно, вздремнул.',10 db '— Ну, уж я разбужу его, — говорит Лис.',10 db 'Скинул он тут свой пиджак, поплевал на руки, взял топор. Размахнулся, как ударит по дереву — поу! Всякий раз, как стукнет топор, — поу! Сарыч скок да скок, а сам приговаривает:',10 db '— Он там, Братец Лис! Он там, он там!',10 db 'Брызнет в сторону щепка, подскочит Сарыч, голову набок, кричит:',10 db '— Он там, Братец Лис! Я слышу, он там!',10 db 'А Лис знай рубит и рубит. Уж совсем мало осталось рубить, опустил Лис топор, чтобы перевести дух, вдруг видит — сидит у него за спиной Сарыч, усмехается.',10 db 'Смекнул Лис, что тут дело не чисто. А Сарыч знай твердит:',10 db '— Он там, Братец Лис! Он там, мне видать его хвостик!',10 db 'Тут Лис заглянул в дупло и кричит:',10 db '— Погляди, Братец Сарыч, что там торчит? Не нога ли это братца Кролика?',10 db 'Сарыч и сунул голову в дупло. А Лис его — хвать за шею.',10 db 'Уж Сарыч и крыльями хлопал и бился — всё без толку. Лису ловко было держать его, он прижал его к земле, не пускает. Тут Сарыч взмолился:',10 db '— Отпусти меня, Братец Лис! Отпусти, Братец Кролик — он тут, совсем близко! Ещё два разочка ударь топором, и он твой!',10 db '— А не лжёшь ли ты, Братец Сарыч?',10 db '— Да нет же, он тут, он тут! Отпусти меня к моей жёнушке, Братец Лис! Он тут, Братец Лис, он тут!',10 db '— Почему-то клочок его шерсти тут, на кусте ежевики, — говорит Братец Лис, — а пришёл он с другой стороны!',10 db 'Тогда Сарыч рассказал всё, как было.',10 db '— Я такого пройдохи, сколько живу, не видывал, Братец Лис, — сказал Сарыч.',10 db 'А Лис говорит:',10 db '— Всё равно ты ответишь мне за него, Братец Сарыч. Я ушёл, Братец Кролик был в дупле, а ты остался стеречь его. Прихожу — ты тут, а Братца Кролика нет.',10 db 'Придётся мне зажарить тебя вместо Кролика, Братец Сарыч.',10 db '— Ну, если ты бросишь меня в огонь, я улечу, Братец Лис, — говорит Сарыч.',10 db '— А я сперва расшибу тебя оземь, Братец Сарыч.',10 db 'Так сказал Лис, ухватил Сарыча за хвост, размахнулся… А перья вырвались из хвоста, и Сарыч полетел кверху.',10 db 'Летит и кричит:',10 db '— Спасибо, что дал мне разгон, Братец Лис! Спасибо, что дал мне разгон!',10 db 'И улетел. А Лис только зубами защёлкал с досады.',10 db '— А что сталось с Кроликом, дядюшка Римус?',10 db '— Да ты не заботься о Братце Кролике, дружок. Я всё тебе про него расскажу.',0 expTxt8 db 'Вывод текста в заголовок окна через SetWindowText',0 expTxt9 db 'Вывод текста в заголовок окна через SendMessage',0 expTxt10 db 'Вывод текста при помощи PolyTextOut API',0 pptxt POLYTEXT <0,TOP+16*0, expTxt6b-expTxt6a,expTxt6a,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*1, expTxt6c-expTxt6b,expTxt6b,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*2, expTxt6d-expTxt6c,expTxt6c,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*3, expTxt6e-expTxt6d,expTxt6d,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*4, expTxt6f-expTxt6e,expTxt6e,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*5, expTxt6g-expTxt6f,expTxt6f,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*6, expTxt6h-expTxt6g,expTxt6g,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*7, expTxt6i-expTxt6h,expTxt6h,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*8, expTxt6j-expTxt6i,expTxt6i,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*9, expTxt6k-expTxt6j,expTxt6j,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*10,expTxt6l-expTxt6k,expTxt6k,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*11,expTxt6m-expTxt6l,expTxt6l,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*12,expTxt6n-expTxt6m,expTxt6m,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*13,expTxt6o-expTxt6n,expTxt6n,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*14,expTxt6p-expTxt6o,expTxt6o,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*15,expTxt6q-expTxt6p,expTxt6p,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*16,expTxt6r-expTxt6q,expTxt6q,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*17,expTxt6s-expTxt6r,expTxt6r,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*18,aStatic -expTxt6t,expTxt6s,ETO_OPAQUE,{0,0,100,200},0> expTxt6a db 'Раз после ужина мальчик прибежал к старому негру, чтобы послушать ещё про Братца Кролика и его' expTxt6b db 'приятелей. Дядюшка Римус был очень весел в этот день. Только Джоэль сунул голову в дверь, он ' expTxt6c db 'услышал песенку:' expTxt6d db ' Где ты, Братец Кролик?' expTxt6e db ' Сидишь на крылечке,' expTxt6f db ' Куришь сигару,' expTxt6g db ' Пускаешь колечки?' expTxt6h db 'И мальчик тотчас вспомнил, как гнался за Кроликом Старый Лис.' expTxt6i db '— Дядюшка Римус, — спросил Джоэль, — а Кролик совсем удрал, когда отлепился от Чучелка?' expTxt6j db '— Что ты, дружок! Зачем ему было совсем удирать? Такой человек, как Братец Кролик, да вдруг удирать! Конечно,' expTxt6k db 'он посидел дома, пока не выскреб из шерсти смолу; день, другой посидел и опять за своё: скачет то здесь, то' expTxt6l db 'там, как ни в чём не бывало. Все соседи посмеивались над Кроликом:' expTxt6m db '— Ну-ка, ну-ка, Братец Кролик, расскажи, что случилось у тебя со Смоляным Чучелком? ' expTxt6n db 'Уж так-то ему это надоело. Вот зашёл он раз навестить свою соседку, Матушку Мидоус с дочками, а девчонки ну' expTxt6o db ' потешаться над ним, ну хохотать. Братец Кролик сидел спокойно, будто оглох.' expTxt6p db '— А кто это — Матушка Мидоус? — спросил мальчик.' expTxt6q db '— Не перебивай, дружок. Ну просто так говориться в сказке: Матушка Мидоус с дочками, а больше я ничего не знаю.' expTxt6r db 'Слушал, слушал Кролик, как они потешались над ним, потом положил ногу на ногу, подмигнул девочкам и говорит:' expTxt6s db '— Милые вы мои, да ведь Братец Лис у моего папаши тридцать лет был верховой лошадью: может, и больше, но' expTxt6t db 'тридцать — это наверное. Так он сказал, и встал, и откланялся, и пошёл прочь медленным, важным шагом.' aStatic db 'STATIC' expTxt11 db 0 hStaticText1 dq ? hStaticText2 dq ? expRect RECT <> MemDC dq ? hbit dq ? font_height dd ? end В главе показан пример окна с "закладками" (Tab Controls). Окно с закладками позволяет отображать несколько страниц связанных элементов управления внутри одного окна. Способ обращения к каждой странице подобен выбору закладки в книге: щелчок на ярлычке в строке закладок, отображаемой в верхней части окна. Создание закладок Для создания закладок используется функция CreateWindow/CreateWindowEx. В качестве имени класса используется SysTabControl32. Фрагмент программы, создающий закладку. Код (ASM): wmCREATE:invoke GetClientRect,,&expRect ;создать закладку push rbx push IMAGE_BASE push rbx push hWnd mov eax,expRect.bottom sub eax,720 push rax mov eax,expRect.right push rax push TOP push rbx sub esp,20h invoke CreateWindowEx,WS_EX_RIGHTSCROLLBAR,&aSysTabControl32,&NullString,WS_VISIBLE or \ WS_TABSTOP or WS_CHILD mov hTabWnd,rax . . . . . ;Заполняем структуру TCITEMA mov tci.imask,TCIF_TEXT or tci.iImage,-1 @@:;создаем 10 закладок mov rax,handle1[rbx*8] mov tci.pszText,rax;название закладки mov tci.lParam,rbx;номер закладки neg rax add rax,handle1[rbx*8+8] mov tci.cchTextMax,eax invoke SendMessage,hTabWnd,TCM_INSERTITEMA,ebx,&tci inc ebx cmp ebx,10 jb @b xor ebx,ebx Перед созданием закладки вызывается GetClientRect ― для получения размеров рабочей области главного окна. При создании закладки ее размеры изменяются таким образом, чтобы полностью заполнить рабочую область родительского окна. После создания закладки приложение может передавать ей управляющие сообщение, а сама закладка, изменяя свое состояние, будет посылать сообщения родительскому окну. Элементы закладки определяются структурами типа TC_ITEM Код (C): typedef struct tagTCITEMA { UINT imask; DWORD dwState; DWORD dwStateMask; LPSTR pszText; int cchTextMax; int iImage; LPARAM lParam; } TCITEMA, *LPTCITEMA; Поле imask этой структуры указывает, в каком из ее полей ― pszText, iImage или lParam ― содержится реальное значение, определяющее элемент закладки, когда в структуру записываются данные из закладки. Поле imask может содержать одно или несколько значений из таблицы. КонстантаЗначениеНазначениеhexbinTCIF_TEXT100001данные содержатся в поле pszTextTCIF_IMAGE200010данные содержатся в поле iImageTCIF_RTLREADING400100для отображения текста в pszText справа налево в еврейских или арабских версиях WindowsTCIF_PARAM801000данные содержатся в поле lParamTCIF_ALLB01011все три поля lParam, iImage и pszText содержат данныеTCIF_STATE1010000данные содержатся в поле dwStateПри инициализации отдельного элемента закладки поле pszText содержит текст, отражаемый в этом элементе. При получении информации из элемента закладки это поле содержит указатель на символьный массив, в который будет записан соответствующий текст. В этом случае поле cchTextMax содержит количество символов, записанных в pszText Если с закладкой связан список изображений (графических объектов), то поле iImage задает индекс изображения, связанного с элементом закладки. Если список изображений с закладкой не связывается, то значение в этом поле должно быть равным -1 Поле lParam может содержать любые данные, задаваемые пользователем. Текущее состояние закладки dwState может принимать значения: TCIS_BUTTONPRESSED ― элемент выбран TCIS_HIGHLIGHTED ― элемент выделен и отображается с использованием текущего цвета выделения
Управляющие сообщения КонстантаHexwParamlParamДействиеПримерTCM_FIRST1300Значения сообщений типа TCM_ зависит от версии Windows и от значения TCM_FIRSTTCM_GETIMAGELISTTCM_FIRST+200В случае успеха возвращается дескриптор списка изображений, в случае неуспеха возвращается 0invoke SendMessage, hTabWnd, TCM_GETIMAGELIST, 0, 0TCM_SETIMAGELISTTCM_FIRST+30Дескриптор списка изображений ассоциированный с вкладкойВ случае успеха возвращается дескриптор списка изображений, в случае неуспеха возвращается 0invoke SendMessage, hTabWnd, TCM_SETIMAGELIST, 0, handleTCM_GETITEMCOUNTTCM_FIRST+400Получение количества элементов на закладкеinvoke SendMessage, hTabWnd, TCM_GETITEMCOUNT, 0, 0TCM_GETITEMTCM_FIRST+5индекс элемента закладкиуказатель на структуру типа TC_ITEM, в которую записывается информация о заданном элементеПолучение информации о заданном элементе закладки. В случае успешного завершения возвращается ненулевое значение, в противном случае ― нульinvoke SendMessage, hTabWnd, TCM_GETITEM, index, &tciTCM_SETITEMTCM_FIRST+6индекс элемента закладкиуказатель на структуру типа TC_ITEM, в которой содержится информация о заданном элементеУстановка новой информации относительно заданного элемента закладки. В случае успешного завершения возвращается ненулевое значение, в противном случае ― нульinvoke SendMessage, hTabWnd, TCM_SETITEM, index, &tciTCM_INSERTITEMTCM_FIRST+7индекс элемента закладки, после которого вставляется новый элементуказатель на структуру типа TC_ITEM, определяющую новый элементсоздание (вставка) нового элемента закладки. В случае успешного завершения возвращается ненулевое значение, в противном случае ― нульinvoke SendMessage, hTabWnd, TCM_INSERTITEM, index, &tciTCM_DELETEITEMTCM_FIRST+8индекс удаляемого элемента0Удаление из закладки заданного элемента. В случае успешного завершения возвращается ненулевое значение, в противном случае ― нульinvoke SendMessage, hTabWnd, TCM_DELETEITEM, index, &tciTCM_DELETEALLITEMSTCM_FIRST+900Удаление из закладки всех элементов. В случае успешного завершения возвращается ненулевое значение, в противном случае ― нульinvoke SendMessage, hTabWnd, TCM_DELETEALLITEMS, 0, 0TCM_GETITEMRECTTCM_FIRST+10индекс элемента закладкиУказатель на структуру RECT, в которую передадут координаты ограничивающего вкладку прямоугольника, в области отображенияПолучение координат ограничивающего прямоугольник для закладки. Возвращает TRUE в случае успеха или FALSE в противном случаеinvoke SendMessage, hTabWnd, TCM_GETITEMRECT, index, &RectTCM_GETCURSELTCM_FIRST+1100Возвращает индекс текущего выбранного элемента. Если никакой элемент не выбран, возвращается -1invoke SendMessage, hTabWnd, TCM_GETCURSEL, 0, 0TCM_SETCURSELTCM_FIRST+12индекс выбранного элемента закладки0Возвращает индекс предыдущего выбранного элемента. Если никакой элемент не выбран, возвращается -1invoke SendMessage, hTabWnd, TCM_SETCURSEL, index, 0TCM_HITTESTTCM_FIRST+130содержит указатель на структуру TCHITTESTINFO которая определяет позицию на экранеВозвращает индекс элемента закладки или -1, если в указанной позиции нет вкладкиinvoke SendMessage, hTabWnd, TCM_HITTEST, 0, &tciTCM_SETITEMEXTRATCM_FIRST+14Количество дополнительных байтов0Устанавливает количество байтов на закладку, зарезервированное для данных приложения в элементе управления вкладкой. Возвращает TRUE в случае успеха или FALSE в противном случаеinvoke SendMessage, hTabWnd, TCM_SETITEMEXTRA, number, 0TCM_ADJUSTRECTTCM_FIRST+40задает тип операции. Если он не равен нулю, получить размеры окна исходя из размеров рабочей области. В противном случае размеры рабочей области исходя из размеров окнасодержит указатель на структуру RECT, в которую записываются координаты исходного прямоугольника. При возвращении в эту структуру записываются координаты результирующего прямоугольникаПолучение размеров окна в соответствии с размерами рабочей областиinvoke SendMessage, hTabWnd, TCM_ADJUSTRECT, 0, &RectTCM_SETITEMSIZETCM_FIRST+410младшие 16 бит содержат новую ширину рисунка, старшие 16 бит ― новую высоту в пикселяхВозвращает старые ширину и высоту рисунка. В младших 16 битах содержатся старая ширина рисунка, в старших 16 битах ― высотаinvoke SendMessage, hTabWnd, TCM_SETITEMSIZE, 0, intTCM_REMOVEIMAGETCM_FIRST+42индекс удаляемого рисунка0удаляет рисунок из закладкиinvoke SendMessage, hTabWnd, TCM_REMOVEIMAGE, index, 0TCM_SETPADDINGTCM_FIRST+430В младших 16 битах содержится величину горизонтального заполнения в пикселях, в старших 16 битах ― количество отступов по вертикали в пикселях.Устанавливает количество места (отступ) вокруг значка и метки каждой закладкиinvoke SendMessage, hTabWnd, TCM_SETPADDING, 0, intTCM_GETROWCOUNTTCM_FIRST+4400Возвращает количество строк на закладкеinvoke SendMessage, hTabWnd, TCM_GETROWCOUNT, 0, 0TCM_GETTOOLTIPSTCM_FIRST+4500Извлекает дескриптор всплывающей подсказки, связанной с закладкой. Возвращает дескриптор всплывающей подсказки в случае успеха или NULL в противном случаеinvoke SendMessage, hTabWnd, TCM_GETTOOLTIPS, 0, 0TCM_SETTOOLTIPSTCM_FIRST+46Дескриптор к элементу управления "подсказка"0Назначает всплывающую подсказку закладке. При использовании сообщения TCM_GETTOOLTIPS можно получить "всплывающую подсказку", связанную с закладкой,invoke SendMessage, hTabWnd, TCM_SETTOOLTIPS, handle, 0TCM_GETCURFOCUSTCM_FIRST+4700Возвращает индекс элемента закладки, который имеет фокусinvoke SendMessage, hTabWnd, TCM_GETCURFOCUS, 0, 0TCM_SETCURFOCUSTCM_FIRST+48Индекс вкладки, которая получает фокус0Устанавливает фокус на конкретную закладкуinvoke SendMessage, hTabWnd, TCM_SETCURFOCUS, index, 0Вновь созданная закладка не содержит никаких элементов. Поэтому программа должна направить закладке хотя бы одно сообщение TCM_INSERTITEM. Сообщение TCM_ADJUSTRECT необходимо для определения размеров рабочей области ("страницы") закладки. Рабочая область ― это та часть окна закладки, где не отображаются элементы закладки. Нотификационные сообщения При воздействии на закладку генерируется сообщение WM_NOTIFY, которое может содержать один из двух нотификационных кодов TCN_SELCHANGEили TCN_SELCHANGING. Код TCN_SELCHANGING используется, когда текущий выбор элемента закладки должен измениться. Код TCN_SELCHANGE передается после выбора нового элемента закладки. Параметр lParam сообщения WM_NOTIFY содержит указатель на структуру типа NMHDR Код (ASM): NMHDR STRUC hwndFrom QWORD ? idFrom QWORD ? _code DWORD ? NMHDR ENDS Нотификационный код содержится в поле _code, а дескриптор закладки, пославшей сообщение, в поле hwndFrom Когда сообщение WM_NOTIFY в функции WndProc содержит код TCN_SELCHANGE, в рабочую область выводится текст выбранным способом Код (ASM): wmNOTIFY:;изменение состояния закладки cmp [r9+NMHDR._code],TCN_SELCHANGE jne wmBYE;DEFAULT . . . . invoke SendMessage,hTabWnd,TCM_GETCURSEL,0,0 jmp [handle+rax*8];переходим на выбранную закладку Стили элементов Tab Control (Tab Control Styles ― TCS_) Tab control stylehexОписаниеTCS_TABS0Tab Control отображаются в виде закладок с границей вокруг области отображения TCS_SINGLE- LINE0Элементы Tab Control вытянуты в одну строку. Что бы увидеть те Tab Control'ы, которые не вошли на форму можно воспользоваться прокруткой TCS_RIGHT- JUSTIFY0Ширина каждого Tab Control увеличивается, чтобы каждая строка Tab Control заполнила всю ширину. Используется со стилем TCS_MULTILINETCS_SCROLL- OPPOSITE1Неиспользуемые Tab Control перемещаются на противоположную сторону элемента управления при выборе новой вкладки TCS_BOTTOM2Отображает вкладки в нижней части элемента управления TCS_RIGHT2Tab Control на правой стороне элементов управления, требуется стиль TCS_VERTICAL TCS_MULTI- SELECT4При выборе вкладки с удержанием клавиши CTRL можно выбрать несколько вкладок. Применяется к вкладкам со стилем TCS_BUTTONS TCS_FLAT- BUTTONS8Изменяет внешний вид выбранной вкладки на "плоскую" кнопку. Применяется к элементам Tab Control со стилем TCS_BUTTONS TCS_FORCE- ICONLEFT10Tab Control с иконкой по левому краю вкладки фиксированной ширины. Может использоваться только со стилем TCS_FIXEDWIDTHTCS_FORCE- LABELLEFT20Выравнивает надпись по левому краю Tab Control с фиксированной шириной; надпись отображается справа от значка, а не центрируется. Может использоваться только со стилем TCS_FIXEDWIDTH и подразумевает стиль TCS_FORCEICONLEFTTCS_HOT- TRACK40Элементы под указателем подсвечиваются автоматически. Вы можете проверить, включено ли горячее отслеживание, вызвав SystemParametersInfoTCS_VERTICAL80элементы Tab Control расположены в левой части элемента управления. Чтобы Tab Control появились в правой части элемента управления, объедините этот стиль со стилем TCS_RIGHT TCS_BUTTONS100Все элементы Tab Control в виде кнопок TCS_MULTI- LINE200Все элементы Tab Control видны одновременно и могут отображаться в несколько строк TCS_FIXED- WIDTH400Все элементы Tab Control с одинаковой шириной. Стиль нельзя комбинировать со стилем TCS_RIGHTJUSTIFYTCS_RAGGED- RIGHT800Оставляет неровный правый край, не растягивая ряд Tab Control, чтобы заполнить всю ширину элемента управленияTCS_FOCUS- ONBUTTON- DOWN1000Если элемент Tab Control выбран, то он получает фокус ввода TCS_OWNER- DRAWFIXED2000За рисование Tab Control отвечает родительское окноTCS_TOOLTIPS4000У элемента Tab Control есть всплывающая подсказкаTCS_FOCUS- NEVER8000Выбранный элемент Tab Control не получает фокус ввода Расширенные стилиTCS_EX_FLAT- SEPARATORS1Указывает, что элемент управления вкладкой будет рисовать разделители между элементами вкладки. Этот расширенный стиль влияет только на элементы управления вкладками, которые имеют стили TCS_BUTTONS и TCS_FLATBUTTONS. По умолчанию создание элемента управления вкладками со стилем TCS_BUTTONS или со стилем TCS_FLATBUTTONS устанавливает этот расширенный стиль. Если вам не нужны разделители, вы должны удалить этот расширенный стиль после создания элемента управленияTCS_EX_REGIS-TERDROP2Элемент управления вкладками генерирует коды уведомлений TCN_GETOBJECT, чтобы запросить объект перетаскивания при перетаскивании объекта на элементы вкладки в элементе управления. Приложение должно вызвать CoInitialize или OleInitialize перед установкой этого стиля.Иконки на Tab Control Создаем 24-разрядный 00.bmp файл с размером 96х32 пикселей (3 картинки 32х32) asm-файл Код (ASM): ; GUI # include win64a.inc RIGHT equ 300 BOTTOM equ 120 IDI_NRM_BITMAP equ 2000 .code WinMain proc local msg:MSG ;Создание родительского окна xor ebx,ebx mov esi,IMAGE_BASE mov ecx,offset FileName invoke LoadCursorFromFile mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOW+1 ;hbrBackground push 10003h ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra pushaddr WndProc ;lpfnWndProc push sizeof WNDCLASSEX ;cbSize & style invoke RegisterClassEx,esp ;addr WNDCLASSEX push rbx ;без дополнительных аргументов push rsi ;rsi = 400000h дескриптор приложения shl esi,9 ;rsi = CW_USEDEFAULT X и Y координаты окна определяет Windows push rbx ;нет меню push rbx ;нет родителя push BOTTOM ;высота push RIGHT ;ширина push rsi ;Y-координата окна push rsi ;X-координата окна sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPED or WS_VISIBLE \ or WS_CAPTION or WS_SYSMENU or WS_MINIMIZEBOX ;цикл сообщений lea edi,msg @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp ;Оконная процедура WndProc proc hWnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD local tci:TCITEMA local expRect:RECT local hTabWnd:qword mov hWnd,rcx cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_CREATE je wmCREATE wmDEFAULT:leave jmp NtdllDefWindowProc_ wmDESTROY:invoke RtlExitUserProcess,NULL wmCREATE:lea edx,expRect invoke GetClientRect ;создать закладку push rbx push IMAGE_BASE push rbx push hWnd mov eax,expRect.bottom push rax ;высота mov eax,expRect.right push rax ;ширина push rbx ;Y-координата push rbx ;X-координата mov r8d,offset NullString mov edx,offset aSysTabControl32 sub esp,20h invoke CreateWindowEx,WS_EX_RIGHTSCROLLBAR,,,WS_VISIBLE \ or WS_TABSTOP or WS_CHILD or TCS_FORCEICONLEFT mov hTabWnd,rax invoke ImageList_Create,32,32,ILC_COLOR24 + ILC_MASK,3,0 mov hImageList, rax invoke LoadBitmap,IMAGE_BASE,IDI_NRM_BITMAP push rax ; for DeleteObject invoke ImageList_Add, hImageList, rax, 0 pop rax invoke DeleteObject,rax invoke SendMessage, hTabWnd, TCM_SETIMAGELIST, 0, hImageList mov tci.imask,TCIF_TEXT or TCIF_IMAGE ; Text + image @@:;создаем 3 закладки mov rax,handle[rbx*8] mov tci.pszText,rax;название закладки mov tci.lParam,rbx;номер закладки mov tci.iImage,ebx; index to tab control's image neg rax add rax,handle[rbx*8+8] mov tci.cchTextMax,eax invoke SendMessage,hTabWnd,TCM_INSERTITEMA,ebx,&tci inc ebx jnp @b;cmp ebx,3 xor ebx,ebx wmBYE: leave xor eax,eax ret WndProc endp ;--------------------------------------- ClassName db 'TabControl with icons' NullString db 0 FileName db "br_Rabbit3.cur",0 aSysTabControl32 db "SysTabControl32",0 tab0 db "one",0 tab1 db "two",0 tab2 db "three",0 handle dq tab0,tab1,tab2,handle hImageList dq ? end rc-файл Код (C): #define IDI_NRM_BITMAP 2000 IDI_NRM_BITMAP BITMAP DISCARDABLE "00.bmp" результат
Глава двенадцатая. Как Братец Кролик осваивал мышь Кролик шел по лесу, игриво повиливая бедрами и мурлыча себе под нос «А я простила, я простила его опять-опять-опять»1. Внезапно на него откуда-то сбоку прыгнула огромная рыжая тень. ― «Привет, Братец Кролик!» ― довольно пропыхтел Братец Лис. Правда, пока он это пыхтел, довольства в его голосе поубавилось. Братец Лис унюхал, что пахнет Братец Кролик вовсе не Кроликом, а духами и пудрой, и разглядел, что ресницы у Братца Кролика явно темнее, длиннее и гуще, чем обычно бывают у кроликов. Помимо остальных подозрительных моментов, Братец Кролик и не пытался вырваться. Он томно обмяк на пыльной дороге. ― «Я не Кролик, я Крольчиха... противный.» ― проворковала Сестрица Крольчиха, одновременно, пытаясь пощекотать длинным серым ухом могучую грудь Братца Лиса. К чести Братец Лиса надо отметить, что кoлeбaлcя он не более секунды. «Запах духов, конечно, не очень... но на вкус это ничуть не повлияло», ― сыто размышлял он во время послеобеденного отдыха. Мы научимся получать и отвечать на ввод с мыши. Учебная программа ожидает нажатия на левую кнопку мыши и отображает текстовую строку в том месте клиентской области, где произошел щелчок мышью. Скачайте пример здесь.Теория ― мать склерозаТак же, как и при вводе с клавиатуры, Windows определяет и посылает уведомления об активности мыши относительно какого-либо окна. Эта активность включает в себя одинарные и двойные нажатия на правую, левую, среднюю клавишу, скролинг мышью, передвижения курсора через окно. В отличие от клавиатуры, сообщения от которой направляются окну, имеющему в данный момент фокус ввода, сведения о котором передаются окну, над которым находится мышь, независимо от того, активно оно или нет. Вдобавок, обрабатываются сообщения от мыши, связанные с неклиентской частью окна, но их, как правило, игнорируют. Сфокусируемся на сообщениях связанных с клиентской областью. Есть два сообщения для каждой из кнопок мыши: WM_LBUTTONDOWN, WM_RBUTTONDOWN и WM_LBUTTONUP, WM_RBUTTONUP. Если мышь трехкнопочная, то есть еще WM_MBUTTONDOWN и WM_MBUTTONUP. Когда курсор мыши двигается над клиентской областью, Windows шлет WM_MOUSEMOVE окну, над которым он находится. Окно может получать сообщения о двойных нажатиях, WM_LBUTTONDBCLK или WM_RBUTTONDBCLK, тогда и только тогда, когда окно имеет стиль CS_DBLCLKS, или же оно будет получать только серию сообщений об одинарных нажатиях. Полный список сообщений, поступающих от мыши СообщениеhexОписаниеWM_MOUSEACTIVATE21Нажата клавиша мыши над неактивным окномWM_NCHITTEST84Перемещение мыши в любом месте экранаWM_NCMOUSEMOVE0A0Перемещение курсора мыши во внешней области окнаWM_NCLBUTTONDOWN0A1Нажата левая клавиша мыши во внешней области окнаWM_NCLBUTTONUP0A2Отпущена левая клавиша мыши во внешней области окнаWM_NCLBUTTONDBLCLK0A3Двойной щелчок левой клавишей мыши во внешней (non-client) области окнаWM_NCRBUTTONDOWN0A4Нажата правая клавиша мыши во внешней области окнаWM_NCRBUTTONUP0A5Отпущена правая клавиша мыши во внешней области окнаWM_NCRBUTTONDBLCLK0A6Двойной щелчок правой клавишей мыши во внешней области окнаWM_NCMBUTTONDOWN0A7Нажата средняя клавиша мыши во внешней области окнаWM_NCMBUTTONUP0A8Отпущена средняя клавиша мыши во внешней области окнаWM_NCMBUTTONDBLCLK0A9Двойной щелчок средней клавишей мыши во внешней области окнаWM_MOUSEMOVE200Перемещение курсора мыши во внутренней области окнаWM_LBUTTONDOWN201Нажата левая клавиша мыши во внутренней области окнаWM_LBUTTONUP202Отпущена левая клавиша мыши во внутренней области окнаWM_LBUTTONDBLCLK203Двойной щелчок левой клавишей мыши во внутренней (client) области окнаWM_RBUTTONDOWN204Нажата правая клавиша мыши во внутренней области окнаWM_RBUTTONUP205Отпущена правая клавиша мыши во внутренней области окнаWM_RBUTTONDBLCLK206Двойной щелчок правой клавишей мыши во внутренней области окнаWM_MBUTTONDOWN207Нажата средняя клавиша мыши во внутренней области окнаWM_MBUTTONUP208Отпущена средняя клавиша мыши во внутренней области окнаWM_MBUTTONDBLCLK209Двойной щелчок средней клавишей мыши во внутренней области окнаВо всех этих сообщениях значение lParam содержит позицию мыши. Младшие 16 разрядов (с 0 по 15) содержат x-координату, старшие 16 разрядов (с 16 по 31) ― y-координату верхнего левого угла клиентской области окна. Параметр wParam содержит информацию о состоянии кнопок мыши и о кнопках клавиатуры Shift и Ctrl.Практика ― сестра шизофренииСкачайте пример здесь. Код (ASM): include win64a.inc .code WinMain proc local msg:MSG xor ebx,ebx mov esi,IMAGE_BASE mov eax,10027h mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOW+1;hbrBackground push 10003h ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra pushaddr WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp ;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE lea edi,msg @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc proc hwnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD local ps:PAINTSTRUCT mov edi,offset expClick mov hwnd,rcx cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_PAINT je wmPAINT cmp dx,WM_LBUTTONDOWN je wmLBUTTONDOWN leave jmp DefWindowProc wmDESTROY:invoke ExitProcess,NULL wmLBUTTONDOWN:;low order lParam = x high order lParam = y mov word ptr [rdi+sizeof expClick+POINT.x],r9w ;mov expHPoint.x,r9w shr r9,16 mov [rdi+sizeof expClick+POINT.y],r9d ;mov expHPoint.y,r9d mov r8b,TRUE mov [rdi],r8b ;mov expClick,TRUE invoke InvalidateRect,,0 ;hWnd,0,TRUE jmp wmBYE wmPAINT:invoke BeginPaint,,&ps cmp byte ptr [rdi],TRUE ;cmp expClick,TRUE jne wmPAINT_END mov dword ptr [rbp-60h],lengthof expTxt lea r9d,[rdi+sizeof expClick+sizeof POINT] ;mov r9,offset expTxt mov r8d,[rdi+sizeof expClick+POINT.y] ;mov r8,expHPoint.y mov edx,[rdi+sizeof expClick+POINT.x] ;mov rdx,expHPoint.x invoke TextOut,ps.hdc ;hdc,expHPoint.x,expHPoint.y,offset expTxt,lengthof expTxt wmPAINT_END:invoke EndPaint,hwnd,&ps wmBYE:leave retn WndProc endp ;--------------------------------------- ClassName db 'Win64 Iczelion''s lesson #7:Mouse Input',0 expClick db FALSE expHPoint POINT <0> expTxt db 'You clicked here!',0 end Разбор полетов Код (ASM): wmLBUTTONDOWN:;low order lParam = x high order lParam = y mov word ptr [rdi+sizeof expClick+POINT.x],r9w ;mov expHPoint.x,r9w shr r9,16 mov [rdi+sizeof expClick+POINT.y],r9d ;mov expHPoint.y,r9d mov r8b,TRUE mov [rdi],r8b ;mov expClick,TRUE invoke InvalidateRect,,0 ;hWnd,0,TRUE Процедура окна ждет нажатия на левую клавишу мыши. Когда WndProc получает сообщение WM_LBUTTONDOWN, регистр r9=lParam содержит координаты курсора мыши в клиентской области. Координаты выделяются из lParam и сохраняются их в переменной типа POINT, определенной следующим образом: Код (ASM): POINT STRUCT x dd ? y dd ? POINT ENDS Затем переменная expClick получает значение TRUE ― это означает, что в клиентской области была нажата левая клавиша мыши для того, чтобы отрисовывающий код в секции WM_PAINT, знал, что было нажатие в клиентской области, и значит поэтому он может нарисовать строку в позиции, где была мышь при нажатии. Затем мы вызываем функцию InvalidateRect, чтобы заставить окно полностью перерисовать клиентскую область. Код (ASM): wmPAINT:invoke BeginPaint,,&ps cmp byte ptr [rdi],TRUE ;cmp expClick,TRUE jne wmPAINT_END mov dword ptr [rbp-60h],lengthof expTxt lea r9d,[rdi+sizeof expClick+sizeof POINT] ;mov r9,offset expTxt mov r8d,[rdi+sizeof expClick+POINT.y] ;mov r8,expHPoint.y mov edx,[rdi+sizeof expClick+POINT.x] ;mov rdx,expHPoint.x invoke TextOut,ps.hdc ;hdc,expHPoint.x,expHPoint.y,offset expTxt,lengthof expTxt wmPAINT_END:invoke EndPaint,hwnd,&ps Отрисовывающий код в секции wmPAINT должен проверять, равно ли содержимое переменной expClick TRUE, потому что когда окно создается, процедура окна получает сообщение WM_PAINT в то время, пока не было сделано еще ни одного нажатия, то есть строку отрисовывать нельзя. Содержимое переменной expClick при запуске программы равно FALSE и становится равным TRUE, когда происходит нажатие на левую клавишу мыши. Если, по крайней мере, одно нажатие на мышь произошло, отрисовывается строка «You clicked here!» в клиентской области в позиции, где была мышь при нажатии. 1 Песня ВИА Гра «Попытка номер 5», текст и музыка Константина Меладзе Глава тринадцатая. Братец Кролик продолжает осваивать мышь Братца Кролика замучила депрессия до такой степени, что он решил покончить с собой. Но не каким-нибудь там простым образом, вроде отравиться, а самым естественным для кроликов. И он пошел на поиски Братца Лиса. По злосчастному стечению обстоятельств, Братец Лис как раз в тот же самый день твердо решил, что он садится на особую тибетскую диету, которая, среди прочего, категорически исключала из рациона крольчатину. Он как раз направлялся на опушку, где росла невероятно полезная для фигуры и блеска шерсти травка, когда ему наперерез бросился Братец Кролик. Братец Лис зажмурился, чтоб не видеть сладкого мяса и попятился в чащу. Братец Кролик молча и решительно последовал за ним, след в след. Братец Лис приоткрыл один глаз, застонал и проглотил слюну. Братец Кролик, не сводя с Братца Лиса горящих безумным огнем глаз, расчетливо повернул голову, чтоб удобнее было разглядеть хрупкую шейку. Братец Лис не выдержал и пустился наутек. «Фаст-фуд, говорите», ― в истерике думал Братец Лис, спасаясь бегством от озверевшего завтрака, ― «Теперь понятно, какой от него вред организму. Бегом от инфаркта!» В отличии от восьмой главы, где обрабатывалось событие по нажатию левой клавиши мыши и выводилась единственная надпись «You clicked here!», мы будем обрабатывать нажатие на левую и правую клавиши мыши, одинарные и двойные щелчки и будем выводить надписи «right-click», «left-click», «right-click double» и «left-click double». Этих надписей будет столько, какое значение мы присвоим константе MAXRECTS. Если количество нажатий превысит число MAXRECTS ― будет раздаваться звуковой сигнал.Практика ― сестра шизофренииСкачайте пример здесь Код (ASM): include win64a.inc MAXRECTS = 40;the maximum number of processed mouse clicks .code WinMain proc local msg:MSG xor ebx,ebx mov esi,IMAGE_BASE mov eax,10027h mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOW+1;hbrBackground push 10003h ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra pushaddr WndProc ;lpfnWndProc mov rax,(CS_DBLCLKS shl 32)+sizeof WNDCLASSEX push rax ;cbSize & style invoke RegisterClassEx,esp ;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE lea edi,msg @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc proc hwnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD local ps:PAINTSTRUCT mov lParam,r9 mov hwnd,rcx xor eax,eax cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_PAINT je wmPAINT cmp dx,WM_LBUTTONDOWN je wmLBUTTONDOWN cmp dx,WM_RBUTTONDOWN je wmRBUTTONDOWN cmp dx,WM_LBUTTONDBLCLK je wmLBUTTONDBCLK cmp dx,WM_RBUTTONDBLCLK je wmRBUTTONDBCLK leave jmp DefWindowProc wmDESTROY:invoke ExitProcess,NULL wmLBUTTONDBCLK:;low order lParam = x high order lParam = y mov eax,0C0000000h jmp wmRBUTTONDOWN wmRBUTTONDBCLK:mov eax,80000000h jmp wmRBUTTONDOWN wmLBUTTONDOWN:mov eax,40000000h wmRBUTTONDOWN:add eax,r9d;lParam cmp nextRect,MAXRECTS-1 jge @f inc nextRect mov edx,nextRect mov [rdx*4+recs],eax mov r8b,TRUE invoke InvalidateRect,,FALSE ;перерисовать часть окна jmp wmBYE @@: invoke MessageBeep,0 jmp wmBYE wmPAINT:mov ebx,nextRect inc ebx ;cmp nextRect,-1 jz wmBYE invoke BeginPaint,,&ps invoke SetBkMode,eax,TRANSPARENT @@: mov eax,[rbx*4+recs-4] xor ecx,ecx shld ecx,eax,2 inc ecx shl ecx,4 movzx edx,ax ;edx равно младшим 16 битам lParam (координата X) shl eax,2 ;удаляем метку "левый/правый клик" и "двойной/одинарный" shr eax,18 mov r8d,eax ;r8 равно старшим 16 битам lParam (координата X) mov rax,[PS+rcx] ;в rax длина текста mov r9,[PS+rcx+8] ;в r9 указатель на строку invoke TextOut,ps.hdc,,,,eax dec ebx jnz @b wmPAINT_END:invoke EndPaint,hwnd,&ps wmBYE:leave retn WndProc endp ;--------------------------------------- ClassName db "Win64 Iczelion's lesson #7a: Mouse Input",0 text1 db "right-click" text2 db "left-click" text3 db "right-click double" text4 db "left-click double" PS dq 0,0,lengthof text1, text1, lengthof text2, text2,lengthof text3, text3, lengthof text4, text4 recs dd MAXRECTS dup(?) nextRect dd -1 end Разбор полетовMAXRECTS - константа, значение которой определяет максимальное количество обрабатываемых кликов мыши, которые пользователь может разместить в окне. Если вам захочется обрабатывать больше кликов, достаточно увеличить значение MAXRECTS. В массиве recs хранятся координаты всех прямоугольников. Переменная nextRect используется и как счетчик прямоугольников, и как индекс в массиве recs. Когда пользователь нажимает на левую кнопку мыши при нахождении курсора над окном приложению посылается сообщение WM_LBUTTONDOWN, когда на правую - WM_RBUTTONDOWN. При двойных щелчках WM_LBUTTONDBLCLK и WM_RBUTTONDOWNDBLCLK соответственно. При обработке сообщений WM_LBUTTONDOWN, RBUTTONDOWN, WM_RBUTTONDOWN и WM_LBUTTONDBLCLK параметр lParam процедуры окна WndProc содержит координаты X и Y курсора мыши в момент щелчка. Координата X хранится в 16 младших битах lParam, а координата Y ― в 16 старших битах. Параметр wParam определяет, какие виртуальные клавиши были нажаты в момент щелчка. Его значение может состоять из любого сочетания констант MK_CONTROL, MK_MBUTTON, MK_RBUTTON и MK_SHIFT, представляющих клавишу CONTROL, среднюю клавишу мыши и клавишу SHIFT соответственно. Когда приложение получило сообщение WM_LBUTTONDOWN, RBUTTONDOWN, WM_RBUTTONDOWN или WM_LBUTTONDBLCLK оно должно нарисовать прямоугольник с текстом «right-click», «left-click», «right-click double» или «left-click double» в месте щелчка. Структурный тип RECT содержит координаты левой, верхней, правой и нижней сторон прямоугольника. Если пользователь не вывел на экран предельное количество прямоугольников программа увеличит значение счетчика прямоугольников Код (ASM): inc nextRect mov edx,nextRect Затем координаты X, Y курсора мыши вместе с меткой правая или левая кнопка и двойной или одинарный щелчок клавиши мыши копируются в массив recs Код (ASM): wmLBUTTONDBCLK:;low order lParam = x high order lParam = y mov eax,0C0000000h jmp wmRBUTTONDOWN wmRBUTTONDBCLK:mov eax,80000000h jmp wmRBUTTONDOWN wmLBUTTONDOWN:mov eax,40000000h wmRBUTTONDOWN:add eax,r9d;перед получением сообщения WM_RBUTTONDOWN eax равно 0 . . . mov [rdx*4+recs],eax mov r8b,TRUE invoke InvalidateRect,,FALSE ;перерисовать часть окна Вызов функции InvalidateRect приводит к тому, что окну посылается сообщение WM_PAINT. Функция InvalidateRect получает при вызове три аргумента: логический номер окна, адрес структуры RECT и логическое значение, которое указывает, должен ли перерисовываться фон окна. Затем программа использует координаты курсора мыши для определения той минимальной прямоугольной области окна, которую необходимо перерисовать для отображения происшедших изменений Код (ASM): mov eax,[rbx*4+recs-4] xor ecx,ecx shld ecx,eax,2 ;два старших бита из eax в ecx inc ecx ;учитываем пустую строчку при старте программы shl ecx,4 ;умножаем содержимое есх на 16 movzx edx,ax ;edx равно младшим 16 битам lParam (координата X) shl eax,2 ;удаляем метку "левый/правый клик" и "двойной/одинарный" shr eax,18 mov r8d,eax ;r8 равно старшим 16 битам lParam (координата X) mov rax,[PS+rcx] ;в rax длина текста mov r9,[PS+rcx+8] ;в r9 указатель на строку invoke TextOut,ps.hdc,,,,eax Обработка сообщения WM_PAINT начинается с проверки того, имеются ли прямоугольники, которые необходимо нарисовать Код (ASM): wmPAINT: mov ebx,nextRect inc ebx;cmp nextRect,-1 jz wmBYE;если массив координат пуст, тогда ничего не делаем . . . wmBYE: leave ret Если массив координат recs не пуст, тогда вызывается функция BeginPaint для заполнения структуры PAINTSTRUCT и получения hDC Код (ASM): lea edx,ps invoke BeginPaint invoke SetBkMode,eax,TRANSPARENT Однако на этот раз поле rcPaint структуры PAINTSTRUCT содержит прямоугольник, переданный нами при вызове InvalidateRect. Он представляет собой минимальную часть окна подлежащую перерисовке. При использовании hDC, возвращаемого функцией BeginPaint, Windows не будет ничего рисовать за пределами этого прямоугольника, который называется областью отсечения (clipping region) ― это позволяет ускорить обновление окна. После вызова BeginPaint функция SetBkMode устанавливает для надписей прозрачный фон, далее по значению в 31-ом и 30-ом бите расшифровывается какая надпись и какой длины будет выведена функцией TextOut на экран и какие координаты будут у этой надписи и далее в цикле от nextRect до 0 выводим прямоугольники на экран. Код (ASM): wmPAINT: mov ebx,nextRect inc ebx ;cmp nextRect,-1 . . . @@: mov eax,[rbx*4+recs-4];-4 компенсирует увеличение rbx на 1 xor ecx,ecx shld ecx,eax,2 inc ecx shl ecx,4 movzx edx,ax ;edx равно младшим 16 битам lParam (координата X) shl eax,2 ;удаляем метку "левый/правый клик" и "двойной/одинарный" shr eax,18 mov r8d,eax ;r8 равно старшим 16 битам lParam (координата X) mov rax,[PS+rcx] ;в rax длина текста mov r9,[PS+rcx+8] ;в r9 указатель на строку invoke TextOut,ps.hdc,,,,eax dec ebx jnz @b © Mikl___ 2019
Глава четырнадцатая. "Липкая" надпись Скачайте пример здесь Код (ASM): include win64a.inc .code WinMain proc local msg:MSG xor ebx,ebx mov esi,IMAGE_BASE mov eax,10027h mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOW+1;hbrBackground push 10003h ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra pushaddr WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp ;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE lea edi,msg @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc proc hwnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD local ps:PAINTSTRUCT mov hwnd,rcx mov edi,offset expHPoint cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_PAINT je wmPAINT cmp dx,WM_MOUSEMOVE je wmMOUSEMOVE leave jmp DefWindowProc wmDESTROY:invoke ExitProcess,NULL wmMOUSEMOVE:;low order lParam = x high order lParam = y mov word ptr [rdi+POINT.x],r9w ;mov expHPoint.x,r9w shr r9,16 mov [rdi+POINT.y],r9d ;mov expHPoint.y,r9d mov r8b,TRUE invoke InvalidateRect,,0;,TRUE jmp wmBYE wmPAINT:invoke BeginPaint,,&ps mov dword ptr [rbp-60h],lengthof expTxt lea r9d,[rdi+sizeof POINT] ;mov r9d,offset expTxt mov r8d,[rdi+POINT.y] ;mov r8d,expHPoint.y mov edx,[rdi+POINT.x] ;mov edx,expHPoint.x invoke TextOut,ps.hdc invoke EndPaint,hwnd,&ps wmBYE:leave retn WndProc endp ;--------------------------------------- ClassName db "Win64 Iczelion's lesson #7e: липкая надпись",0 expHPoint POINT <0> expTxt db "Win64 assembly is great and easy",0 end © Mikl___ 2021
Глава одиннадцатая. Братец Кролик изучает иконки и курсоры ОпределениеКурсoр ― элемент графического интерфейса, который указывает на объект, с которым будет производиться взаимодействие с помощью клавиатуры, мыши или другого устройства управления. Курсор может иметь любую форму. Основной его частью является «активный пиксель» («горячее пятно» ― hotspot) ― точка на экране, в которой расположен элемент, через который происходит взаимодействие пользователя и программы. Большинство курсоров имеют форму, указывающую на расположение этой точки: у курсоров-«стрелок» hotspot расположен на острие стрелки (); у курсоров-«перекрестий» ― в центре (); если же курсор не предполагает взаимодействия с объектом на экране, например, курсор-«ожидание», тогда положение hotspot в иконке не имеет значения (). В Windows курсоры хранятся в файлах с расширениями CUR («статичные» курсоры) и ANI («анимированные» курсоры). Большинство курсоров представляют собой изображения размером 32×32 пикселя, и могут иметь несколько цветовых вариаций для различной глубины цвета, установленной в системе (True Color, High Color, 256 цветов, 16 цветов, монохромный). Начиная с Windows XP курсоры мыши поддерживают режим 32bpp (bits-per-pixel), с указанием альфа-канала для каждой точки. Графические программы для рисования используют собственные курсоры для отражения различных действий программы (курсоры в виде «кистей», «карандашей», «ластиков», «пипеток» и тому подобное). Иконка ― элемент графического интерфейса, небольшая картинка, представляющая приложение, файл, каталог, окно, компонент операционной системы, устройство и тому подобное. В ответ на щелчок, совершенный мышью или другим указательным устройством ввода на иконке, обычно выполняется соответствующее действие (запуск приложения, открытие файла и так далее). Windows выводит на экран пользовательские иконки в: левом верхнем углу строки заголовка окна приложения списках программ меню Start панели задач в нижней части экрана списке программы Windows Explorer В Windows используются иконки квадратного формата со стороной: 16, 24, 32, 40, 48, 60, 72, 92, 108, 128 или 256 пикселов. Обычно файл иконки содержит в себе несколько отдельных изображений различных размеров и с различным количеством используемых цветов. Это связано с тем, что при автоматизированном уменьшении растрового изображения оно теряет резкость, а при увеличении — детализацию. Многие среды и приложения задают «иконки по умолчанию». Иконка для приложения выбирается стандартной, для файла — в зависимости от типа файла (часто это иконка приложения, обрабатывающего этот файл «по-умолчанию»). Иконки для графических файлов получаются из содержимого самого файла (уменьшенная копия изображения, «thumbnail»). Не все иконки статичны. Иконка, представляющая связь с Интернетом, показывает идет ли передача данных, иконка диспетчера задач ― загрузку процессора и так далее. В Windows иконки хранятся в ICO-файлах. Один ICO-файл содержит одну или несколько иконок, размер и цветность каждой иконки задается отдельно. ICO-формат поддерживает внедрение изображений в формате JPEG и PNG, но обычно данные иконок хранятся в несжатом виде. Для цветопередачи пикселов иконок используют: True Color (глубина цвета равна 32 бита), для каждого пиксела изображения используется: 8 бит для представления красной 8 ― синей 8 бит ― зеленой составляющей (28 = 256 возможных значения). Таким образом получаются 256 x 256 x 256 = 224 = 16777216 цветовых оттенков. Также может использоваться (а может и не использоваться ) 8-битный альфа-канал, позволяющий реализовать 256 уровней частичной прозрачности. С помощью альфа-канала можно отображать иконки со сглаженными (размытыми) краями и тенью, сочетающимися с любым фоном; маска в этом случае игнорируется. High Color (глубина цвета 16 бит), для каждого пиксела изображения используется: 5 бит для представления красной составляющей (25 = 32 возможных значения) 5 ― синей 6 бит (26 = 64 возможных значения) для представления зеленой, так как человеческий глаз более чувствителен к зеленой составляющей Таким образом получаются 32 x 64 x 32 = 216 = 65536 цветовых оттенков. цвет с фиксированной палитрой (из 256, 16, или 2 цветов), число, соответствующее каждому пикселу указывает не на цвет, а на номер цвета в палитре. По своей структуре изображения в файле ICO и CUR наиболее близки к BMP, но принципиально отличаются от них наличием дополнительного изображения ― маски, накладываемой на задний план при помощи операции «побитового AND», что позволяет реализовать (полную) прозрачность рисунка. Последующее наложение основного изображения с помощью операции «XOR» может дать «инверсные» пиксели в тех местах, где задний план не был замаскирован. Структура ICO и CUR-форматов Иконки и курсоры создаются с помощью графического редактора и сохраняются в файлах с расширением ICO и CUR соответственно. ICO-формат аналогичен CUR-формату, предназначенному для хранения курсоров. Отличие состоит в численном значении поля type в заголовочной структуре, и интерпретации значений полей planes и bpp. ICO/CUR-файл состоит из заголовка каталога информации об изображениях непосредственно изображений Заголовок Размер заголовка 6 байт: ПолеТипОписаниеreservedWORDЗарезервировано. Всегда 0.typeWORDТип файла: 1 для иконок 2 для курсоров Иные значения недопустимыcountWORDКоличество изображений в файле, минимум 1.Каталог информации об изображениях Представляет собой последовательные записи фиксированного размера (16 байт), следующие одна за другой. Количество записей определяется полем count заголовка. ПолеТипОписаниеwidthBYTEШирина изображения в точках. Принимает значения от 0 до 255. Если 0, тогда ширина изображения 256 точек.heightBYTEВысота изображения в точках. Принимает значения от 0 до 255. Если 0, тогда высота изображения 256 точек.colorsBYTEКоличество цветов в палитре изображения. Для полноцветных иконок должно быть 0.reservedBYTEЗарезервировано. Всегда должно быть 0, однако иконки, которые создаются встроенными средствами .NET (System.Drawing.Icon.Save) содержат в этом поле значение 255.planesWORD В ICO количество плоскостей. Может быть 0 или 1. В CUR горизонтальная координата «горячей точки» в пикселях относительно левого края изображения. bppWORD В ICO количество битов на пиксель (bits-per-pixel). Может быть 0, так как вычисляется на основе других данных; например, если изображение не хранится в формате PNG, тогда количество битов на пиксель рассчитывается на основе информации о размере растра, а также его ширине и высоте. Если изображение хранится в формате PNG, то соответствующая информация хранится в самом PNG. Указывать в этом поле 0 не рекомендуется, так как логика выбора наилучшего изображения в различных версиях Windows неизвестна. В CUR вертикальная координата «горячей точки» в пикселях относительно верхнего края изображения. sizeDWORDРазмер растра в байтахoffsetDWORDАбсолютное смещение растра в файле.Иконки и курсоры ― это виды ресурсов (resources) Windows. Ресурсы являются данными, хранятся в EXE-файле, но расположены не в сегменте данных, а в сегменте ресурсов. К ресурсам нет непосредственного доступа через переменные. Они должны быть загружены из EXE-файла в память. Когда Windows загружает в память код и данные для выполнения программы, ресурсы остаются на диске. Когда Windows нужен конкретный ресурс, его загружают в память. Иконки и курсоры добавляются в RC-файл (файл ресурсов) с помощью операторов ICON и CURSOR <Имя_иконки> ICON <Имя_файла> <Имя_курсора> CURSOR <Имя_файла>Имя_курсора и Имя_иконки ― имена, индентифицирующие иконку и курсор, используются для обращения к этим ресурсам. Загрузить иконку или курсор в приложени можно либо по имени, либо по идентификатору. Если Вы используете не предопределенные системой курсоры или иконки, тогда их требуется загрузить до регистрации окна функциями LoadIcon и LoadCursor и запомнить дескриптор курсора и иконки в соответствующих полях структуры WNDCLASSEX Синтаксис
Код (C): HICON WINAPI LoadIcon( _In_opt_ HINSTANCE hInstance,/*дескриптор модуля, в ресурсы которого загружена иконка. Если этот параметр равен нулю, тогда загружается системная иконка*/ _In_ LPCTSTR lpIconName /*строка с именем загружаемой из ресурсов иконки. Иногда, этот параметр может содержать идентификатор ресурса*/ ); Синтаксис Код (C): HCURSOR WINAPI LoadCursor( _In_opt_ HINSTANCE hInstance,/*дескриптор модуля, в ресурсы которого загружен курсор. Если этот параметр равен нулю, тогда загружается системный курсор*/ _In_ LPCTSTR lpCursorName /*строка с именем загружаемого из ресурсов курсора. Иногда, этот параметр может содержать идентификатор ресурса*/ ); Код (ASM): .data MyIcon db "MY_ICON",0 MySmIcon db "MY_SMALL_ICON",0 MyCursor db "MY_CURSOR",0 ... local wc:WNDCLASSEX ... mov edx,offset MyIcon invoke LoadIcon,400000h mov wc.hIcon,rax ;загрузка иконки mov edx,offset MySmIcon invoke LoadIcon,400000h mov wc.hIconSm,rax ;загрузка малой иконки mov edx,offset MyCuror invoke LoadCuror,400000h mov wc.hCuror,rax ;загрузка курсора где 400000h ― дескриптор нашей программы Можно при вызовах функции LoadIcon для загрузки стандартной и малой иконок сослаться на одну и ту же иконку стандартного размера, а Windows при выводе малой иконки приведет иконку стандартного размера к необходимому. Если в дальнейшем вы захотите изменить иконку, это можно сделать с помощью функции SetClassLong. Предположим, что у вас в описании ресурсов есть вторая иконка: Код (C): SecondIcon ICON icon2.ico Следующий фрагмент программы заменяет иконку MyIcon иконкой SecondIcon: Код (ASM): mov edx,offset SecondIcon invoke LoadIcon,400000h invoke SetClassLong, hwnd, GCL_HICON, rax Малую иконку можно заменить с помощью GCL_HICONSM.Создание пользовательских курсора и иконки из CUR-файла без использования компилятора ресурсовСкачайте пример здесь В качестве курсора и иконки используем иконку из файла Cursor.cur. Убеждаемся, что разницы между курсором и иконкой нет. Код (ASM): ; GUI # include win64a.inc .code WinMain proc local msg:MSG xor ebx,ebx mov esi,IMAGE_BASE mov edi,offset ClassName mov ecx,offset FileName invoke LoadCursorFromFile push rax ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOWTEXT;hbrBackground push rax ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra db 68h dd WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp ;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE lea edi,msg @@: invoke GetMessage,edi,NULL,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc:cmp edx,WM_DESTROY je wmDESTROY jmp DefWindowProc wmDESTROY: invoke ExitProcess,NULL ;----------------------------------- ClassName db "Win64 Iczelion's lesson #7-1:Курсор и иконка из файла *.cur",0 FileName db "Images\Cursor.cur",0 end Создание пользовательских курсора и иконки из CUR-файла с использованием компилятора ресурсовДобавляем курсор и иконку из файла Cursor.cur в секцию ресурсов. Для этого создаем файл tut_07b.rc со следующим содержанием Код (C): #define ID_CURSOR 18 ID_CURSOR CURSOR "Images\\cursor.cur" в очередной раз вносим изменения в файл asm2.bat Код (Text): cls set masm64_path=\masm64 set filename=%1 if exist %1.exe del %1.exe %masm64_path%\bin\ml64 /Cp /c /I"%masm64_path%\Include" %filename%.asm || exit if exist %1.rc ( <-- если существует rc-файл с таким же именем, как и asm-файл тогда %masm64_path%\bin\RC /r %filename%.rc || exit %masm64_path%\bin\link /SUBSYSTEM:WINDOWS /LIBPATH:"%masm64_path%\Lib" ^ /entry:WinMain %filename%.obj %filename%.res /LARGEADDRESSAWARE:NO ^ /ALIGN:16 /SECTION:.text,W ^ /BASE:0x400000 /STUB:%masm64_path%\bin\stubby.exe || exit del %filename%.res ) else ( <-- иначе %masm64_path%\bin\link /SUBSYSTEM:WINDOWS /LIBPATH:"%masm64_path%\Lib" ^ /entry:WinMain %filename%.obj /LARGEADDRESSAWARE:NO ^ /ALIGN:16 /SECTION:.text,W ^ /BASE:0x400000 /STUB:%masm64_path%\bin\stubby.exe || exit ) if exist %1.res del %1.res del %filename%.obj содержимое tut_07b.asm Код (ASM): ; GUI # include win64a.inc ID_CURSOR equ 18 .code WinMain proc local msg:MSG xor ebx,ebx mov esi,IMAGE_BASE mov edi,offset ClassName invoke LoadCursor,esi,ID_CURSOR push rax ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOWTEXT;hbrBackground push rax ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra db 68h dd WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp ;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE lea edi,msg @@: invoke GetMessage,edi,NULL,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc:cmp edx,WM_DESTROY je wmDESTROY jmp DefWindowProc wmDESTROY: invoke ExitProcess,NULL ;----------------------------------- ClassName db "Win64 Iczelion's lesson #7-2:Курсор и иконка из ресурсов",0 end Создание курсора и иконки из бинарных данныхВ секцию данных добавим рисунок из файла Cursor.cur, воспользовавшись программой bintodb.exe из \masm32\tools\bin2db, которая превратит рисунок в текстовый файл, состоящий из DB-наборов. При помощи функции CreateIconFromResource получаем указатель на иконку, и, передав его DrawIcon, выводим рисунок из Cursor.cur на клиентскую область окна. Функция CreateIconFromResource создает иконку или курсор из байтов ресурса. Синтаксис Код (C): HICON CreateIconFromResource ( PBYTE presbits, // указатель на буфер, содержащий ресурс иконки или курсора QWORD dwResSize,// размер в байтах BOOL fIcon, /* флажок иконки или курсора. Если этот параметр TRUE, тогда должна быть создана иконка. Если FALSE, должен быть создан курсор.*/ QWORD dwVer // версия формата Windows ); номер версии формата иконки или курсора для байтов ресурса, может быть одним из следующих значений: ФорматdwVerWindows 2.x20000hWindows 3.x и старше30000hФункция DrawIcon рисует иконку в рабочей области окна заданным контекстом устройства. Синтаксис Код (C): BOOL DrawIcon ( HDC hDC,// дескриптор контекста устройства int X, // x-координата верхнего левого угла int Y, // y- координата верхнего левого угла HICON hIcon// дескриптор иконы, которую надо нарисовать ); Примеp:
Код (ASM): ; GUI # include win64a.inc ICON_SIZE = 744 ;общий размер файла Cursor.cur =766 байт (заголовок=22 картинка=744) .code WinMain proc local msg:MSG xor ebx,ebx mov ecx,offset ptIcon invoke CreateIconFromResource,,ICON_SIZE,-1,30000h mov hIcon,rax push rax ;hIconSm mov edi,offset ClassName push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOW;hbrBackground push rax ;hCursor push rax ;hIcon mov esi,IMAGE_BASE push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra db 68h dd WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,rsp ;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE lea edi,msg @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc proc hWnd:HWND local ps:PAINTSTRUCT mov hWnd,rcx cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_PAINT je wmPAINT leave jmp DefWindowProc wmDESTROY:invoke ExitProcess,NULL wmPAINT:lea edx,ps invoke BeginPaint invoke DrawIcon,eax,134,68,hIcon lea edx,ps invoke EndPaint,hWnd wmBYE:leave retn WndProc endp ;--------------------------------------- ClassName db "Win64 Iczelion's lesson #7-3: Создание курсора и иконки из бинарных данных",0 hIcon dq ? ptIcon:;изображение из файла Cursor.cur ;пропускаем 22 байта заголовка db 40,0,0,0,32,0,0,0,64,0,0,0,1,0,4,31 dup(0) db 128,0,0,128,0,0,0,128,128,0,128,0 db 0,0,128,0,128,0,128,128,0,0,192,192,192,0,128,128 db 128,0,0,0,255,0,0,255,0,0,0,255,255,0,255,0 db 0,0,255,0,255,0,255,255,0,0,255,255,255,0,0,10 db 5 dup(170),160,10 dup(0),3 dup(170),160,12 dup(0) db 10,170,0,10,4 dup(170),0,128,8 dup(0) db 10,6 dup(170),168,6 dup(0) db 8,10,170,160,0,0,0,10,170,170,7 dup(0) db 170,170,1,16,5 dup(170),168,5 dup(0) db 10,170,160,17,10,4 dup(170),0,0,0,128,0,0,0 db 74,170,160,16,8 dup(170),160,0,8,0 db 0,170,160,10,9 dup(170),0,128,10 db 3 dup(170),10,170,160,8,6 dup(170),168,0,170 db 170,170,160,0,170,138,160,136,160,4 dup(170),160,10,170 db 170,74,170,170,168,170,15,0,0,10,170,170,170,168,10,170 db 164,10,170,170,10,160,255,0,15,0,10,170,170,0,10,170 db 64,10,160,168,10,160,255,240,15,0,0,0,0,0,10,160 db 0,170,160,8,10,160,4 dup(255),10,0,0,0,0,170 db 170,170,0,128,10,160,255,255,15,255,10,0,0,0,7,10 db 170,160,8,248,10,170,15,240,15,240,170,0,0,0,15,112 db 0,15,255,255,0,170,160,10,168,10,160,128,0,0,15,5 dup(255) db 128,170,170,170,138,170,8,0,0,0,7,5 dup(255) db 248,10,170,160,96,0,0,0,0,0,0,6 dup(255) db 128,0,15,255,247,112,0,0,0,0,9 dup(255) db 119,119,128,0,128,0,0,6 dup(255) db 127,136,136,128,8,255,255,248,0,8,127 db 5 dup(255),247,112,8,4 dup(255),240,0,0,15 db 5 dup(255),248,143,5 dup(255),240,0,0,135 db 12 dup(255),248,0,0,0,12 dup(255),0,0,0,0 db 7,10 dup(255),240,5 dup(0),135,8 dup(255),247,7 dup(0) db 7,6 dup(255),247,8,8 dup(0) db 8,127,3 dup(255),247,8,10 dup(0) db 128,0,0,0,8,5 dup(0),192,0 db 255,255,224,0,15,255,240,0,1,255,248,0,0,255,248,0 db 0,127,248,0,0,63,240,0,0,7,224,0,0,3,128,0 db 0,1,15 dup(0),1,0,0,0,3,4 dup(0,0,0,31) db 0,0,0,63,0,0,0,127,128,0 db 0,63,128,0,0,7,128,0,0,3,128,0,0,3,192,0 db 0,3,192,0,0,3,224,0,0,7,240,0,0,15,248,0 db 0,31,254,0,0,63,255,128,0,255,255,240,3,255 end Использование альтернативных курсоровЕсли вы используете дочерние окна, можно сделать так, чтобы курсор выглядел по-разному в разных окнах. Если в вашей программе определяются классы этих дочерних окон, то для каждого класса вы можете использовать свой курсор путем соответствующей установки поля hCursor в каждом классе окна. А если вы используете предопределенные дочерние элементы управления, то изменить поле hCursor класса окна можно с помощью функции: Код (ASM): mov edx,offset childcursor invoke LoadCursor,400000h invoke SetClassLong,hwndChild,GCL_HCURSOR,rax Если вы разделяете рабочую область окна вашей программы на маленькие логические области без использования дочерних окон, то для изменения курсора мыши вы можете использовать функцию SetCursor: Код (ASM): invoke SetCursor,hCursor Функцию SetCursor следует вызывать при обработке сообщения WM_MOUSEMOVE. В противном случае для перерисовки курсора при его движении Windows использует курсор, ранее заданный в классе окна.Загрузка стандартных иконок и курсоровПоскольку параметр hInstance равен NULL, Windows знает, что эта иконка или курсор являются системной. Системные иконки и курсоры являются частью файла драйвера дисплея. Заполняя структуру WNDCLASSEX дескрипторами системного курсора и иконки можно и не использовать функции LoadIcon и LoadCursor. Дело в том, что эти функции нужны, если загружаемая иконка или курсор изготовлены собственноручно или позаимствованы из какой-либо понравившейся программы. Для стандартных (системных) иконок, битмапов и курсоров конкретной версии Windows идентификаторы неизменны и их можно посмотреть при помощи «связки» из функций wsprintf +LoadIcon/Cursor/Bitmap + MessageBox Код (ASM): ; GUI # include win64a.inc .code WinMain proc local hCursor:QWORD local buffer[100]:BYTE invoke LoadCursor,NULL,IDC_ARROW mov hCursor,rax invoke LoadIcon,NULL,IDI_APPLICATION invoke wsprintf,&buffer,&fmt,hCursor,rax invoke MessageBox,NULL,&buffer,&MsgCaption,MB_OK invoke ExitProcess,NULL WinMain endp ;----------------------------------- MsgCaption db "Iczelion's tutorial #2b",0 fmt db "hCursor = %08Xh",0Ah,"hIcon = %08Xh",0 end Полученный при помощи этой программы дескриптор курсора вставлялся в программу, выводящую окно, и проверялся результат. Начав с курсоров IDC_ARROW, IDC_IBEAM очень скоро замечаем, что дескрипторы курсоров могут быть только нечетными числами (иначе окно становится невидимым). Теперь уже без «связки» wsprintf + LoadCursor + MessageBox подставляем значения в шаблон окна и смотрим, что будет выведено в качестве курсора: Когда, дескриптор окажется больше 10025h (у Вас может быть другое число), в качестве курсора появляется стандартная системная иконка IDI_APPLICATION, IDI_QUESTION и так далее, то есть еще раз убеждаемся, что разница между иконкой и курсором отсутствует (на это я наткнулся случайно, когда во время заполнения WNDCLASSEX перепутал местами функции LoadIcon и LoadCursor и только потом прочитал о ICO и CUR-форматах ) Для части системных курсоров и иконок в файле windows.inc константы IDC_ и IDI_ отсутствуют константы IDI_APPLICATION и IDI_WINLOGO хотя и имеют разное значение (32512 и 32517 соответственно), но соответствуют одной и той же иконке. IDC_SIZE (=32640) и IDC_ICON (=32641), актуальны только для Windows NT, а для остальных Windows не соответствуют никакому курсору. Часть курсоров, принадлежащих системе, чьи идентификаторы не попали в файл windows.inc можно посмотреть на вкладке «Панель управления» → «Оборудование и звук» → «Устройства и принтеры» → «Мышь» → «Указатели»
Результат исследования помещен в таблицу. Повторюсь еще раз ― значение дескриптора зависит от конкретной версии Window и у Вас могут быть совсем другие значения типзначениеописание и предназначениедескрипторкартинкаКурсорыIDC_ARROW32512Курсор по умолчанию — стрелка. Предполагается, что объект под таким курсором самостоятельно сообщает пользователю своим видом, можно ли с ним взаимодействовать;10003hIDC_IBEAM32513Текстовое выделение — сигнализирует о том, что в это поле можно вводить текст, а также для него действуют выделения и контекстные меню, характерные для полей текстового ввода;10005hIDC_WAIT32514Курсор ожидания — сообщает пользователю, что в текущий момент взаимодействие с элементом невозможно по причине выполнения программой каких-то операций. По окончании выполнения курсор должен вернуться в исходное состояние;10007hIDC_CROSS32515Перекрестие — используется прежде всего для графического выделения;10009hIDC_UPARROW32516Специальное выделение1000BhIDC_SIZE32640Только для Windows NT: Четырехнаправленная стрелка0IDC_ICON32641Только для Windows NT: Пустая иконка0IDC_SIZENWSE32642Курсор изменения размеров с левого верхнего и правого нижнего углов1000DhIDC_SIZENESW32643Курсор изменения размеров с левого нижнего и правого верхнего углов1000FhIDC_SIZEWE32644Курсор изменения размеров с левого и правого края10011hIDC_SIZENS32645Курсор изменения размеров с верхнего и нижнего краев10013hIDC_SIZEALL32646Курсор перемещения ― сигнализирует о том, что для выбранного объекта доступна функция перетаскивания;10015hIDC_NO32648Курсор запрета действия ― сообщает, что какое-то действие (перетаскивание, нажатие и т. д.) для данного элемента недоступно.10017hIDC_APPSTARTING32650Курсор фонового режима ― показывает, что действие запущено, но видимых результатов может не быть. Такой курсор используется для того, чтобы пользователь увидел, что команда принята и повторно нажимать на объект не требуется. В некоторых графических средах рядом с курсором отображается движущаяся иконка запущенного приложения;10019hIDC_HELP32651Курсор режима справки ― сигнализирует, что для данного объекта по нажатию кнопки мыши доступна справка, либо имеется всплывающая подсказка;1001Bh―32631Рукописный ввод1001DhIDC_HAND32649Курсор-рука ― для обозначения гиперссылок, чтобы сообщить пользователю о том, что гиперссылка работает;1001Fh―――10021h―32663Ожидание готовности CD/DVD10023h
Иконки――иконка программы малая10025hIDI_APPLICATION32512иконка программы большая10027hIDI_EXCLAMATION, IDI_WARNING32515Восклицательный знак (используется в предупреждающих сообщениях)10029hIDI_QUESTION32514Вопросительный знак (используется в подсказках)1002BhIDI_HAND, IDI_ERROR32513серьезные предупреждающие сообщения1002DhIDI_ASTERISK, IDI_INFORMATION32516Звездочка (используется в информационных сообщениях)1002FhIDI_WINLOGO32517Иконка приложения по-умолчанию Windows 2000: Логотип Windows10031hIDI_SHIELD32518Приложение требует особых прав по безопасности.10033hКурсоры―32652Курсор изменения размеров с верхнего и нижнего краев3602E7h―32653Курсор изменения размеров с левого и правого края350291h―32654Курсор перемещения ― сигнализирует о том, что для выбранного объекта доступна функция перетаскивания;5F02BFh―32655―19A0237h―32656―85B0241h―32657―5A029Dh―32658―17A0223h―32659―1940223h―32660―16D02CBh―32661―9302ADh―32662―290295h© Mikl___ 2021
Глава шестнадцатая. Братец Кролик изучает меню — Что же, Братец Си никогда-никогда не поймал Братца Кролика? А, дядюшка Римус? — спросил Джоэль на другой вечер. — Было и так, дружок, чуть-чуть не поймал. Помнишь, как Братец Кролик надул его с укропом?Теория:Меню ― это один из важнейших компонентов вашего окна. Меню является списком всех возможностей, которые программа предлагает пользователю. Пользователь не обязан читать мануал, поставляемый с программой, чтобы использовать ее, он может досконально исследовать меню, чтобы получить представление о возможностях данной программы и начать «играть» с ней немедленно. Так как меню ― это инструмент для того, чтобы дать пользователю «быстрый старт», вы должны следовать стандарту. Первые два пункта меню должны быть «Файл» и «Редактирование», а последний ― «Помощь». Вы можете вставить ваши собственные пункты между «Редактированием» и «Помощью». Если пункт меню вызывает диалоговое окно, вам нужно заканчивать название пункта эллипсисом («...»). Меню ― это разновидность ресурсов. Есть несколько видов ресурсов, таких как диалоговые окна, строковые таблицы, иконки, битмапы, меню и так далее. Ресурсы описываются в отдельном файле, называющемся файлом ресурсов, который, как правило, имеет расширение RC. Вы можете соединять ресурсы с исходным кодом во время линковки. Окончательный продукт ― это исполняемый файл, который содержит как инструкции, так и ресурсы. Вы можете писать файлы ресурсов, используя любой текстовый редактор. Они состоят из набора фраз, определяющих внешний вид и другие атрибуты ресурсов, используемых в программе. Файлы ресурсов можно создовать в текстовом виде (при условии, что вы знакомы с их синтаксисом), а можно воспользоваться редактором ресурсов. Важно понимать, что файл ресурсов ― это обычный текстовый файл. После его создания его компилируют и превращают в двоичный файл с расширением RES. Важно! Комментарии в файлах ресурсов, как и комментарии в программах на C++, начинаются с двойного символа наклонной черты. Вы описываете ресурс меню примерно так: Код (C): Имя_меню MENU { список пунктов меню } Си-программисты могут заметить, что это похоже на объявление структуры. Имя_меню ― это имя меню, за ним следует ключевое слово MENU и список пунктов меню, заключенный в фигурные скобки. Вместо { и } вы можете использовать BEGIN и END. Этот вариант больше понравится программистам на Паскале. Список меню включает в себя выражения MENUITEM или POPUP. MENUITEM определяет пункт меню, который не является подменю. Его синтаксис следующий: Код (C): MENUITEM "&название_пункта_меню", ID [, опции] Выражение начинается ключевым словом MENUITEM, за который следует текст, который будет отображаться. Обратите внимание на амперсанд («&»). Его действие заключается в том, что следующий за ним символ будет подчеркнут. Затем идет строка в качестве ID пункта меню. ID ― это номер, который будет использоваться для обозначения пункта меню в сообщении, посылаемое процедуре окно, когда этот пункт меню будет выбран. Каждое ID должно быть уникальным. Доступны следующие опции: GRAYEDпункт меню неактивен, и он не генерирует сообщение WM_COMMAND. Текст пункта меню отображается серым цветом.INACTIVEпункт меню неактивен, и он не генерирует сообщение WM_COMMAND. Текст пункта меню отображается нормально.MENUBREAKэтот пункт меню и последующие пункты отображаются после новой линии меню.HELPэтот пункт меню и последующие пункты выравнены по правой стороне.Вы можете использовать одну из вышеописанных опций или комбинировать их оператором «or». Учтите, что INACTIVE и GRAYED не могут комбинироваться вместе. Выражение POPUP имеет следующий синтаксис: Код (Text): POPUP "&название_пункта_меню" [, опции] { список пунктов меню } Выражение POPUP определяет пункт меню, при выборе которого выпадает список пунктов в маленьком рoрuр-окне. Список меню может быть выражением MENUITEM или POPUP. Есть специальный вид выражения MENUITEM ― MENUITEM SEPARATOR, который отрисовывает горизонтальную линию в popup-окне. Последний шаг ― ссылка на скрипт ресурса меню в программе. Вы можете сделать это в двух разных местах. В поле lpszMenuName структуры WNDCLASSEX. Скажем, если у вас было меню под названием «FirstMenu», вы можете присоединить меню к вашему окну следующим образом: Код (ASM): .data MenuName db "FirstMenu",0 ... .code ... mov wc.lpszMenuName, OFFSET MenuName С помощью параметра-дескриптора меню в функции CreateWindowEx: Код (ASM): .data MenuName db "FirstMenu",0 hMenu dq ? ... .code ... invoke LoadMenu, hInst, &MenuName mov hMenu,rax invoke CreateWindowEx,NULL,&ClsName,\ &Caption, WS_OVERLAPPEDWINDOW+WS_VISIBLE,\ CW_USEDEFAULT,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,\ NULL,hMenu,\ hInst,NULL\ Вы можете спросить, в чем разница между этими двумя методами? Когда вы делаете ссылку на меню в структуре WNDCLASSEX, меню становится «меню по умолчанию» для данного класса окна. Каждое окно этого класса будет иметь такое меню. Если вы хотите, чтобы каждое окно, созданное из одного класса, имело разное меню, вы можете выбрать второй подход. В этом случае, любое окно, которому передается дескриптор меню в функции CreateWindowEx будет иметь меню, которое замещает «меню по умолчанию», указанное в структуре WNDCLASSEX. Сейчас мы узнаем, как меню уведомляет процедуру окна о том, что пользователь выбрал пункт меню. Когда пользователь выберет пункт меню, процедура окна получит сообщение WM_COMMAND. Младшие 16 разрядов wParam'а содержит ID выбранного пункта меню. Теперь у нас достаточно информации для того, чтобы создать и использовать меню. Давайте сделаем это.Создание меню через файл ресурсовСкачайте пример здесь Первый пример показывает нам как создать и использовать меню, указав имя меню в классе окна. Код (ASM): ; GUI # include win64a.inc ZZZ_TEST equ 0 ;ID меню ZZZ_OPEN equ 1 ZZZ_SAVE equ 2 ZZZ_EXIT equ 3 .code WinMain proc local msg:MSG xor ebx,ebx mov esi,IMAGE_BASE mov eax,10029h mov edi,offset ClassName push rax ;Иконка окна по умолчанию push rdi ;Указатель на имя нашего класса pushaddr menu_name ;Меню push COLOR_WINDOW+1;Фон нашего окна push 10005h ;Курсор окна по умолчанию (здесь указан ID обычной стрелки) push rax ;Иконка окна по умолчанию push rsi ;Адрес нашей программы в памяти - Windows всегда её грузит ;по этому адресу push rbx ;cbClsExtra & cbWndExtra pushaddr WndProc ;Адрес процедуры обработки событий push sizeof WNDCLASSEX;размер структуры WNDCLASSEX и cтиль нашего окна invoke RegisterClassEx,esp;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE;создать окно lea edi,msg ;цикл обработки сообщений @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc proc hwnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_COMMAND je wmCOMMAND leave jmp DefWindowProc wmDESTROY:invoke ExitProcess,NULL;завершение программы wmCOMMAND:cmp r8,ZZZ_EXIT je wmDESTROY show_msg:mov rdx,menu_handlers[r8*8] mov r8d,offset menu_name invoke MessageBox,,,,MB_OK wmBYE: leave retn WndProc endp ;--------------------------------------- ClassName db 'Win64 Iczelion''s lesson #8: создание меню через файл ресурсов',0 menu_name db 'ZZZ_Menu',0 ;Имя нашего меню в файле ресурсов test_msg db 'You select menu item TEST',0 open_msg db 'You select menu item OPEN',0 save_msg db 'You select menu item SAVE',0 menu_handlers dq test_msg, open_msg, save_msg end Для работы с ресурсами нам потребуется создать файл с таким же именем, но с расширением RC, ниже показано его содержимое Код (ASM): #define ZZZ_TEST 0 #define ZZZ_OPEN 1 #define ZZZ_SAVE 2 #define ZZZ_EXIT 3 ZZZ_Menu MENU { POPUP "&File" { MENUITEM "&Test",ZZZ_TEST MENUITEM "&Open",ZZZ_OPEN MENUITEM "&Save",ZZZ_SAVE MENUITEM SEPARATOR MENUITEM "&Exit",ZZZ_EXIT } MENUITEM "&Exit",ZZZ_EXIT } Анализ:В следующих четырех строках определяются четыре константы Код (C): #define ZZZ_TEST 0 /* все равно, что ZZZ_TEST equ 0*/ #define ZZZ_OPEN 1 #define ZZZ_SAVE 2 #define ZZZ_EXIT 3 Константы соответствуют идентификаторам ресурсов, с помощью которых программа обращается к командам меню. Точно такие же идентификаторы есть и в основном файле Код (ASM): ZZZ_TEST equ 0 ZZZ_OPEN equ 1 ZZZ_SAVE equ 2 ZZZ_EXIT equ 3 Важно чтобы в разные файлы включались одни и те же идентификаторы. Обратите внимание, что значения идентификаторов начинается с нуля и идут по порядкуДобавление меню в класс окнаДалее вы должны указать меню в классе окна. Если взглянуть на определение структуры WNDCLASSEX в основном приложении, можно найти соответствующую строку
Код (ASM): xor ebx,ebx mov esi,IMAGE_BASE mov eax,10029h mov edi,offset ClassName push rax ;Иконка окна по умолчанию push rdi ;Указатель на имя нашего класса db 68h dd menu_name ;адрес имени меню push COLOR_WINDOW+1;Фон нашего окна push 10005h ;Курсор окна по умолчанию (здесь указан ID обычной стрелки) push rax ;Иконка окна по умолчанию push rsi ;Адрес нашей программы в памяти - Windows всегда её грузит ;по этому адресу push rbx ;cbClsExtra & cbWndExtra db 68h dd WndProc ;Адрес процедуры обработки событий push sizeof WNDCLASSEX;размер структуры WNDCLASSEX и стиль нашего окна invoke RegisterClassEx,esp;addr WNDCLASSEX . . . menu_name db 'ZZZ_Menu',0 ;Имя нашего меню в файле ресурсов То же имя должно быть и в файле ресурсов Код (C): ZZZ_Menu MENU { Добавление имени меню в структуру WNDCLASSEX ― это все что требуется для установления связи между приложением и меню.Обработка команд менюТеперь, когда в главном окне нашего приложения появилось меню, вам наверняка хочется знать, как реагировать на действия пользователя при выполнении им определенной команды. При выполнении команды меню Windows посылает вашему приложению сообщение ― в данном случаеWM_COMMAND. Это означает, что вы должны включить в процедуру окна обработку сообщения WM_COMMAND. Когда приложение получает сообщение WM_COMMAND в параметре wParam хранится идентификатор выполненной команды меню. При выборе пунктов меню «Test», «Open» и «Save» должны быть выведены мессаджбоксы с сообщениями «You select menu item TEST», «You select menu item OPEN» и «You select menu item SAVE» соответственно, а при выборе пункта меню «Exit» мы должны закрыть приложение. Iczelion на нашем месте три раза повторяет одинаковые фрагменты Код (ASM): .ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF ax==IDM_TEST invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK .ELSEIF ax==IDM_HELLO invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK .ELSEIF ax==IDM_GOODBYE invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK .ELSE invoke DestroyWindow,hWnd .ENDIF А мы будем только менять сообщение у мессаджбокса в зависимости от содержимого wParam и если wParam = IDM_EXIT перейдем на обработку сообщения WM_DESTROY. Простенько, и со вкусом! Код (ASM): cmp edx,WM_COMMAND je wmCOMMAND leave jmp DefWindowProc wmDESTROY:invoke ExitProcess,NULL wmCOMMAND:cmp r8,ZZZ_EXIT je wmDESTROY show_msg:mov rdx,menu_handlers[r8*8] mov r8d,offset menu_name invoke MessageBox,,,,MB_OK . . . test_msg db 'You select menu item TEST',0 open_msg db 'You select menu item OPEN',0 save_msg db 'You select menu item SAVE',0 menu_handlers dq test_msg, open_msg, save_msg К моменту перехода на show_msg регистр RCX = hwnd, а регистр R8 = wParam. Так как ID пунктов меню идут по порядку от 0 до 3, а в переменной menu_handlers по порядку расположены адреса сообщений ― когда пользователь выбирает «Test», «Open» и «Save», мы отображаем соответствующую текстовую строку при помощи MessageBox.Глава семнадцатая. Братец Кролик создает меню через функцию LoadMenu Этот способ Iczelion назвал альтернативным методом для того, чтобы загружать меню в окно. Исходный код практически такой же как в главе двенадцатой. Файл ресурсов такой же. Есть небольшие изменения в исходнике. Для большей убедительности уберем указатель на имя меню из определения структуры WNDCLASSEX Код (ASM): xor ebx,ebx mov esi,IMAGE_BASE mov eax,10029h mov edi,offset ClassName push rax ;hIconSm push rdi ;Указатель на имя нашего класса push rbx ;lpszMenuName = 0 - меню в WNDCLASS не предусмотрено push COLOR_WINDOW+1;Фон нашего окна push 10005h ;Курсор окна по умолчанию (здесь указан ID обычной стрелки) push rax ;Иконка окна по умолчанию push rsi ;Адрес нашей программы в памяти - Windows всегда грузит ее ;по этому адресу push rbx ;cbClsExtra & cbWndExtra pushaddr WndProc ;Адрес процедуры обработки событий push sizeof WNDCLASSEX;размер структуры WNDCLASSEX и стиль нашего окна invoke RegisterClassEx,esp;addr WNDCLASSEX Перед вызовом CreateWindowEx, мы вызываем LoadMenu, передавая ему дескриптор процесса и указатель на имя меню. LoadMenu возвращает дескриптор нашего меню, который мы передаем CreateWindowEx. Код (ASM): invoke LoadMenu,esi,ZZZ_Menu push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rax push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE Скачайте пример здесь Полный текст программы Код (ASM): ; GUI # include win64a.inc ZZZ_TEST equ 0 ZZZ_OPEN equ 1 ZZZ_SAVE equ 2 ZZZ_EXIT equ 3 ZZZ_Menu equ 30 .code WinMain proc local msg:MSG xor ebx,ebx mov esi,IMAGE_BASE mov eax,10029h mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOW+1;hbrBackground push 10005h ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra pushaddr WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp;addr WNDCLASSEX invoke LoadMenu,esi,ZZZ_Menu push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rax push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE lea edi,msg @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc proc hwnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_COMMAND je wmCOMMAND leave jmp DefWindowProc wmDESTROY:invoke ExitProcess,NULL wmCOMMAND:cmp r8,ZZZ_EXIT je wmDESTROY show_msg:mov rdx,menu_handlers[r8*8] mov r8d,offset menu_name invoke MessageBox,,,,MB_OK wmBYE: leave retn WndProc endp ;data ClassName db 'Win64 Iczelion''s lesson #8a: Menu and LoadMenu',0 menu_name db 'ZZZ_Menu',0 test_msg db 'You select menu item TEST',0 open_msg db 'You select menu item OPEN',0 save_msg db 'You select menu item SAVE',0 menu_handlers dq test_msg, open_msg, save_msg end Код (C): #define ZZZ_TEST 0 #define ZZZ_OPEN 1 #define ZZZ_SAVE 2 #define ZZZ_EXIT 3 #define ZZZ_Menu 30 ZZZ_Menu MENU { POPUP "&File" { MENUITEM "&Test",ZZZ_TEST MENUITEM "&Open",ZZZ_OPEN MENUITEM "&Save",ZZZ_SAVE MENUITEM SEPARATOR MENUITEM "&Exit",ZZZ_EXIT } MENUITEM "&Exit",ZZZ_EXIT } Глава восемнадцатая. Программное создание меню В этой главе Братец Кролик научился, создавать меню программно, при помощи функций CreateMenu, CreatePopupMenu, AppendMenu, SetMenuТеория:Меню чаще всего описывается в файле ресурсов, так как в этом случае облегчается задача изменения его состава или пунктов меню. Но в некоторых случаях может оказывается более удобным создавать меню программно, без помощи редактора и компилятора ресурсов. Программное создание меню включает следующие действия:
создание линейки меню приложения (пока пустой) функцией CreateMenu с получением дескриптора этого меню; создание «всплывающего» меню для помещения в него требуемого набора пунктов меню (но пока тоже пустого и без названия) функцией CreatePopupMenu с получением дескриптора «всплывающего» меню; заполнение «всплывающего» меню при помощи функции AppendMenu пунктами меню. Функции AppendMenu передается четыре параметра: первым параметром указывается дескриптор всплывающего меню; вторым параметром указывается константа MF_STRING (или MF_SEPARATOR для разделительной линии); третьим параметром ― идентификатор данного пункта меню; четвертым параметром ― адрес строки текста, представляющего собой название пункта меню (для разделительной линии передается значение NULL). включение «всплывающего» меню в линейку меню при помощи функции AppendMenu и передачи этой функции: первым параметром имени «всплывающего» меню;? вторым параметром указывается константа MF_POPUP; третьим ― дескриптор «всплывающего» меню; четвертым ― адрес строки с названием «всплывающего» меню; присоединение всего меню к главному окну приложения функцией SetMenu. Скачайте пример здесь Код (ASM): ; GUI # include win64a.inc ZZZ_TEST equ 0 ZZZ_OPEN equ 1 ZZZ_SAVE equ 2 ZZZ_EXIT equ 3 ;----------------------------------------- .code WinMain proc local msg:MSG xor ebx,ebx mov eax,10029h push rax ;hIconSm mov edi,offset ClassName push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOW;hbrBackground push 10005h ;hCursor push rax ;hIcon mov esi,IMAGE_BASE push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra db 68h dd WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE lea edi,msg @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc proc hWnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD mov hWnd,rcx cmp edx,WM_CREATE je wmCREATE cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_COMMAND je wmCOMMAND leave jmp DefWindowProc wmDESTROY:invoke ExitProcess,0 wmCREATE: invoke CreatePopupMenu mov edi,eax mov r9d,offset menu_test1 invoke AppendMenu,edi,MF_STRING,ZZZ_TEST mov r9d,offset menu_open1 invoke AppendMenu,edi,MF_STRING,ZZZ_OPEN mov r9d,offset menu_save1 invoke AppendMenu,edi,MF_STRING,ZZZ_SAVE invoke AppendMenu,edi,MF_SEPARATOR,0,0 mov r9d,offset menu_exit1 invoke AppendMenu,edi,MF_STRING,ZZZ_EXIT invoke CreateMenu mov hMenu,rax mov r9d,offset menu_file invoke AppendMenu,eax,MF_POPUP,edi mov r9d,offset menu_exit1 invoke AppendMenu,hMenu,MF_STRING,ZZZ_EXIT invoke SetMenu,hWnd,hMenu jmp wmBYE wmCOMMAND:cmp r8,ZZZ_EXIT je wmDESTROY show_msg:mov rdx,menu_handlers[r8*8] mov r8d,offset menu_name invoke MessageBox,,,,MB_OK wmBYE: leave retn WndProc endp ;data ClassName db 'Win64 Iczelion''s lesson #8c: Program creation menu',0 menu_file db 'File',0 menu_test1 db 'Test',0 menu_open1 db 'Open',0 menu_save1 db 'Save',0 menu_exit1 db 'Exit',0 menu_name db 'ZZZ_Menu',0 test_msg db 'You select menu item TEST',0 open_msg db 'You select menu item OPEN',0 save_msg db 'You select menu item SAVE',0 menu_handlers dq test_msg, open_msg, save_msg hMenu dq ? end Функция CreatePopupMenu создает «всплывающее» меню, подменю или контекстное меню. Меню вначале пустое. Вы можете вставлять или добавлять в конец пункты меню при помощи использования функции InsertMenuItem. Вы можете также использовать функцию InsertMenu, чтобы вставить пункты меню, а функцию AppendMenu, чтобы добавлять в конец пункты меню. Функция CreateMenu создает меню. Меню вначале пустое, но оно может быть заполнено пунктами меню при помощи использования функцийInsertMenuItem, AppendMenu и InsertMenu. Функция SetMenu связывает новое меню с заданным окном. Код (C): BOOL SetMenu ( HWND hWnd, // дескриптор окна, с которым должно быть связано меню HMENU hMenu // дескриптор меню. Если hMenu = NULL, текущее меню окна удаляется ); Функция AppendMenu добавляет в конец определяемой строки меню, «всплывающего» меню, подменю или контекстного меню новый пункт. Вы можете использовать эту функцию, чтобы определить содержание, внешний вид и характеристики пункта меню. Функция AppendMenu была заменена функцией InsertMenuItem. Вы можете все еще использовать AppendMenu, в том случае, если Вы не нуждаетесь в какой-либо из расширенных функций InsertMenuItem. Синтаксис Код (C): BOOL AppendMenu( HMENU hMenu, // дескриптор меню, который будет изменен UINT uFlags, /* флажки пункта меню, которые управляют внешним видом и характеристиками нового пункта меню. Этот параметр может быть комбинация значений, перечисленных ниже */ UINT uIDNewItem,/* идентификатор пункта меню или, если параметр uFlags установлен в MF_POPUP, дескриптор "всплывающего" меню или подменю */ LPCTSTR lpNewItem// пункт контекстного меню. Интерпретация lpNewItem зависит от того, включает ли параметр uFlags в себя флажок MF_BITMAP, MF_OWNERDRAW или MF_STRING ); Прикладная программа должна вызывать функцию DrawMenuBar всякий раз, когда меню изменяется, независимо от того, находится оно или нет в отображаемом окне. Следующие флажки могут быть установлены в параметре uFlags: uFlagsbinhexОписаниеMF_ENABLED00000000000000000Включает пункт меню, так что он может быть выбран и восстанавливает его из недоступного состояния.MF_STRING00000000000000000Определяет, что пункт меню ― текстовая строка; параметр lpNewItem указывает на строку.MF_UNCHECKED00000000000000000Удаляет галочку рядом с пунктом («значение по умолчанию»). Если прикладная программа предоставляет точечные рисунки (значки) «галочки», тогда этот флажок показывает на экране снятую метку «галочки» рядом с пунктом меню.MF_INSERT00000000000000000MF_BYCOMMAND00000000000000000MF_UNHILITE00000000000000000MF_GRAYED00000000000000011Отключает пункт меню и окрашивает его в серый цвет (делает недоступным), так что он не может быть выбран.MF_DISABLED00000000000000102Отключает пункт меню, так что он не может быть выбран, но флажок не окрашивает его в серый цвет.MF_BITMAP00000000000001004Использует точечный рисунок как пункт меню. Параметр lpNewItem содержит дескриптор точечного рисунка.MF_CHECKED00000000000010008Помещает галочку рядом с пунктом меню. Если прикладная программа предоставляет значки «галочки» тогда этот флажок показывает на экране точечный рисунок галочки рядом с пунктом меню.MF_POPUP000000000001000010Определяет, что пункт меню открывает «всплывающее» меню или подменю. Параметр uIDNewItem определяет дескриптор «всплывающего» меню или подменю. Этот флажок используется, чтобы добавить имя меню в строке меню, или пункт меню, который открывает подменю «всплывающего» меню, подменю или контекстное меню.MF_MENUBARBREAK000000000010000020Исполняет такую же функцию, как и флажок MF_MENUBREAK для строки меню. Для «всплывающего» меню, подменю или контекстного меню, новый столбец отделяется от старого столбца вертикальной линией.MF_MENUBREAK000000000100000040Помещает пункт в новую строку (для строки меню) или в новом столбце (для «всплывающего» меню, подменю, или контекстного меню) без разделения столбцов.MF_HILITE000000001000000080MF_END000000001000000080MF_CHANGE000000001000000080MF_OWNERDRAW0000000100000000100Определяет, что пункт является «собственным» пунктом (нарисованным пользователем). Перед тем как меню отображается впервые, окно, которое владеет им, получает сообщение WM_MEASUREITEM, чтобы извлечь данные о ширине и высоте пункта меню. Сообщение WM_DRAWITEM затем отправляется в оконную процедуру окна владельца всякий раз, когда внешний вид пункта меню должен модифицироваться. Содержит 32-разрядное значение, предоставленное прикладной программой, которое может быть использовано, чтобы утвердить, что дополнительные данные касаются пункта меню. Значение является членом itemData структуры, указываемой параметром lParam при помощи передачи сообщения WM_MEASURE или WM_DRAWITEM, когда создается меню, или его внешний вид модифицируется.MF_APPEND0000000100000000100MF_USECHECKBITMAPS0000001000000000200MF_DELETE0000001000000000200MF_BYPOSITION0000010000000000400MF_SEPARATOR0000100000000000800Рисует горизонтальную разделительную линию. Этот флажок используется только во «всплывающем» меню, подменю или контекстном меню. Строка не может быть недоступна, заблокирована, или выделена. Параметры lpNewItem и uIDNewItem игнорируются.MF_SEPARATOR0000100000000000800MF_DEFAULT00010000000000001000MF_REMOVE00010000000000001000MF_SYSMENU00100000000000002000MF_HELP01000000000000004000MF_RIGHTJUSTIFY01000000000000004000MF_MOUSESELECT10000000000000008000Глава девятнадцатая. Создание динамического меню Во многих программах меню может динамически изменяться во время работы ― исчезают и добавляются пункты меню, одно меню встраивается в другое и тому подобное. Пример простейших манипуляций с меню приведен в этой главе.Теория:В приложение открывается окно с кнопкой и «пустым» меню. При нажатии на кнопку появляется одно меню (MENUC), которое сменяется следующим меню (MENUP), которое заменяет «пустое» меню и так далее по кругу. При передвижении по меню название его пунктов и заголовков «выпадающих» (popup) подменю отображается в заголовке окна
Скачайте пример здесь Код (ASM): ; GUI # include win64a.inc STYLBTN equ BS_DEFPUSHBUTTON+WS_VISIBLE+WS_CHILD sizeof_buffer = 128 .code WinMain proc local msg:MSG xor ebx,ebx mov esi,IMAGE_BASE mov eax,10027h mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOW+1;hbrBackground push 10005h ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra db 68h dd WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp ;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE lea edi,msg @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc proc hWnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD local MENI:MENUITEMINFO local buffer[sizeof_buffer]:BYTE mov hWnd,rcx mov lParam,r9 mov wParam,r8 cmp edx,WM_CREATE je wmCREATE cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_COMMAND je wmCOMMAND cmp edx,WM_MENUSELECT je wmMENUSELECT wmDEFAULT:leave jmp DefWindowProc wmDESTROY:invoke ExitProcess,NULL wmCOMMAND:mov rax,HWNDBTN;проверить, не нажата ли кнопка cmp r9,rax ;cmp lParam,rax je YES_BUT cmp r8w,5 ;word ptr wParam,5;проверить, не выбран ли пункт меню MENUC - выход je wmDESTROY cmp r8w,4 ;cmp word ptr wParam,4;проверить, не выбран ли пункт меню с идентификатором 4 jne wmBYE YES_BUT:mov r9d,offset SPACE;обработка нажатия кнопки. сначала стереть текст в заголовке invoke SendMessage,,WM_SETTEXT,0 dec PRIZN jns @f add PRIZN,3;if (PRIZN < 0) PRIZN=2 jmp @f wmCREATE:push rbx push IMAGE_BASE push rbx push rcx;[hWnd] push 25 push 120 push 10 push 10 mov r8d,offset CPBUT mov edx,offset CLSBUTN sub esp,20h invoke CreateWindowEx,0,,,STYLBTN mov HWNDBTN,rax;запомнить дескриптор кнопки ;загрузить необходимое меню @@: mov edx,PRIZN invoke LoadMenu,IMAGE_BASE invoke SetMenu,hWnd,eax ;установить меню jmp wmBYE wmMENUSELECT:mov rcx,r9;lParam = 0 ? jecxz wmBYE;пропускаем первое сообщение при обращении к меню xor r8,r8 test word ptr wParam+2,MF_POPUP;проверяем, что активизировано - пункт меню или заголовок выпадающего меню setne r8b ;если 0, то в lword wParam идентификатор пункта меню ;если 1, то в lword wParam номер заголовка выпадающего меню mov MENI.hSubMenu,rcx;MENI.hSubMenu:=lParam ;заполнение структуры для вызова функции GetMenuItemInfo lea r9d,MENI mov MENI.cbSize,sizeof MENUITEMINFO mov MENI.fMask,MIIM_TYPE lea eax,buffer mov MENI.dwTypeData,rax;указатель на буфер, получающий необходимую строку mov MENI.cch,sizeof_buffer;длина буфера movzx rdx,word ptr wParam;получить информацию о выбранном пункте меню invoke GetMenuItemInfo or eax,eax ;cmp eax,0;проверить результат выполнения функции jz wmBYE invoke SendMessage,hWnd,WM_SETTEXT,0,MENI.dwTypeData;вывести название пункта меню как заголовок всего окна wmBYE: leave retn WndProc endp ;data ClassName db 'Win64 Iczelion''s lesson #8d: Создание динамического меню' SPACE db 0 CPBUT db 'Change menu',0 CLSBUTN db 'BUTTON',0 HWNDBTN dq ? PRIZN dd 0 end В ресурсах программы находятся два меню. Код (C): #define MENUC 2 #define MENUP 1 MENUP MENU { POPUP "First point" { MENUITEM "First",1 MENUITEM "Second",2 } POPUP "Second point" { MENUITEM "Third",3 MENUITEM "Fourth",4 MENUITEM SEPARATOR POPUP "Else submenu" { MENUITEM "Additional point",6 } MENUITEM "Exit",5 } } MENUC MENU { POPUP "First set" { MENUITEM "White",101 MENUITEM "Grey",102 MENUITEM "Black",103 } POPUP "Second set" { MENUITEM "Red",104 MENUITEM "Blue",105 MENUITEM "Green",106 } } В переменной PRIZN хранится состояние меню: 2 ― загружено меню MENUC 1 ― загружено меню MENUP 0 ― меню отсутствует Начальное состояние обеспечивается заданием меню при регистрации класса окна Код (ASM): xor ebx,ebx mov esi,IMAGE_BASE mov eax,10027h mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push rbx ;меню отсутствует push COLOR_WINDOW+1;hbrBackground push 10005h ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra db 68h dd WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp;addr WNDCLASSEX При нажатии на кнопку «Change menu» происходит циклическое изменение значения PRIZN от 0 к 1, далее 2 и снова 0 Код (ASM): YES_BUT:mov r9d,offset SPACE;обработка нажатия кнопки. сначала стереть текст в заголовке invoke SendMessage,,WM_SETTEXT,0 dec PRIZN jns @f add PRIZN,3;if (PRIZN < 0) PRIZN=2 jmp @f . . . ;загрузить необходимое меню @@: mov edx,PRIZN invoke LoadMenu,IMAGE_BASE invoke SetMenu,hWnd,eax ;установить меню При определении названия выбранного пункта меню приходит сообщение WM_MENUSELECT. При активизации меню приходит сообщениеWM_MENUSELECT со значением lParam, которое определяет идентификатор меню равным нулю. Код (ASM): wmMENUSELECT:mov rcx,r9;lParam = 0 ? jecxz wmBYE;пропускаем первое сообщение при обращении к меню При получении сообщения WM_MENUSELECT в младших 16 разрядах параметра wParam может содержаться либо идентификатор пункта меню, либо номер заголовка выпадающего меню. Определить, что выбрано, можно по старшим 16 разрядам параметра wParam Код (ASM): xor r8,r8 test word ptr wParam+2,MF_POPUP;проверяем, что активизировано - пункт меню или заголовок выпадающего меню setne r8b ;если 0, то в lword wParam идентификатор пункта меню ;если 1, то в lword wParam номер заголовка выпадающего меню mov MENI.hSubMenu,rcx;MENI.hSubMenu:=lParam ;заполнение структуры для вызова функции GetMenuItemInfo lea r9d,MENI mov MENI.cbSize,sizeof MENUITEMINFO mov MENI.fMask,MIIM_TYPE lea eax,buffer mov MENI.dwTypeData,rax;указатель на буфер, получающий необходимую строку mov MENI.cch,sizeof_buffer;длина буфера movzx rdx,word ptr wParam;получить информацию о выбранном пункте меню invoke GetMenuItemInfo or eax,eax ;cmp eax,0;проверить результат выполнения функции jz wmBYE invoke SendMessage,hWnd,WM_SETTEXT,0,MENI.dwTypeData;вывести название пункта меню как заголовок всего окна после команды setne выполняющейся после test регистр r8b установится в 1 или сбросится в 0. Далее, для получения строки-названия используется функция GetMenuItemInfo. Третьим параметром этой функции как раз и может быть либо ноль, либо единица. Если ноль, то второй параметр ― это идентификатор пункта меню, если единица, то второй параметр ― номер заголовка выпадающего меню. Четвертым параметром является указатель на структуру, которая и будет заполняться в результате выполнения функции. Некоторые поля этой структуры должны быть заполнены заранее. Поле dwTypeData, которое должно содержать указатель на буфер, получающий необходимую нам строку. Поле cchдолжно содержать длину этого буфера. Для того чтобы поля idwTypeData и cch трактовались функцией как указатель на буфер и его длину, поля fMask и fType должны быть правильно заполнены. Поле cbSize должно содержать длину всей структуры. После получения нужной информации, то есть строки-названия пункта меню, при помощи функции SendMessage посылается сообщение WM_SETTEXT, которое дает команду установить заголовок окна. Функция GetMenuItemInfo извлекает информацию о пункте меню. Код (C): BOOL WINAPI GetMenuItemInfo( HMENU hMenu, // Дескриптор меню, которое содержит пункт меню UINT uItem, /* Идентификатор или позиция пункта меню, о котором надо получить информацию. Предназначение этого параметра зависит от значения fByPosition*/ BOOL fByPosition, /* Значение, определяющее предназначение uItem. Если этот параметр ― FALSE, uItem - идентификатор пункта меню. Иначе, это - позиция пункта меню*/ LPMENUITEMINFO lpmii /*Указатель на структуру MENUITEMINFO, которая определяет информацию, чтобы извлечь и получить информацию о пункте меню */ );
Глава двадцатая. «Плавающее» менюТеория:В приложениях Windows широко используется «плавающее» меню (не путать с «всплывающим» меню (popup menu), являющимся элементом обычного меню), активизируеммые обычно щелчком правой клавиши мыши. «Плавающее» меню появляется в том месте экрана, где в данный момент находится курсор мыши. Поскольку «плавающее» меню должно активизироваться нажатием правой клавиши мыши, в оконную функцию следует включить обработку сообщения WM_RBUTTONDOWN, и все действия по созданию меню выполнить в функции обработки этого сообщения. Установка «плавающего» меню почти не отличается от программного создания обычного статического меню и включает в себя следующие действия: создание «всплывающего» меню для помещения в него требуемого набора пунктов (пока пустого) функцией CreatePopupMenu с получением дескриптора «всплывающего» меню (линейка меню в этом случае не создается); заполнение «всплывающего» меню конкретными пунктами функцией AppendMenu в точности так же, как и при создании обычного меню. Поскольку линейки меню в этом случае нет, «всплывающее» меню ни к чему не подсоединяется; объявление созданного «всплывающего» меню «плавающим» при помощи функции TrackPopupMenu. Второй параметр этой функции задается равным 0. В качестве третьего и четвертого параметров указывают текущие координаты курсора мыши, пятый параметр дескриптор окна, шестой NULL Для более точного позиционирования рекомендуют передавать экранные координаты мыши из структурной переменной MSG Скачайте пример здесь Код (ASM): ; GUI # include win64a.inc ZZZ_TEST equ 0 ZZZ_OPEN equ 1 ZZZ_SAVE equ 2 ZZZ_EXIT equ 3 ;----------------------------------------- .code WinMain proc enter 20h,0 xor ebx,ebx mov esi,IMAGE_BASE mov eax,10027h mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOW+1;hbrBackground push 10005h ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra db 68h dd WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE mov edi,offset msg @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc proc hWnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD local rect:RECT local hMenu:QWORD mov hWnd,rcx cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_COMMAND je wmCOMMAND cmp edx,WM_RBUTTONDOWN je wmRBUTTONDOWN wmDEFAULT:leave jmp DefWindowProc wmDESTROY:invoke ExitProcess,NULL wmRBUTTONDOWN: invoke CreatePopupMenu mov edi,eax;hPopupMenu,eax invoke CreateMenu mov hMenu,rax;hMenu=rbp-20h mov r9d,offset menu_test1 invoke AppendMenu,edi,MF_STRING,ZZZ_TEST mov r9d,offset menu_open1 invoke AppendMenu,edi,MF_STRING,ZZZ_OPEN mov r9d,offset menu_save1 invoke AppendMenu,edi,MF_STRING,ZZZ_SAVE invoke AppendMenu,edi,MF_SEPARATOR,0,0 mov r9d,offset menu_exit1 invoke AppendMenu,edi,MF_STRING,ZZZ_EXIT mov r9d,offset menu_file invoke AppendMenu,hMenu,MF_POPUP,edi mov r9d,offset menu_exit1 invoke AppendMenu,hMenu,MF_STRING,ZZZ_EXIT ;xor edx,edx;mov edx,TPM_LEFTALIGN+TPM_LEFTBUTTON mov r8d,msg.pt.x mov r9d,msg.pt.y mov [rbp-20h],rbx mov rax,hWnd mov [rbp-28h],rax mov [rbp-30h],rbx invoke TrackPopupMenu,edi,0 lea edx,rect invoke GetWindowRect,hWnd jmp wmBYE wmCOMMAND:cmp r8d,ZZZ_EXIT je wmDESTROY show_msg: mov rdx,[menu_handlers+r8*8] mov r8d,offset menu_name invoke MessageBox,,,,MB_OK wmBYE: leave retn WndProc endp ;--------------------------------------- ClassName db 'Win64 Iczelion''s lesson #8e: Создание плавающего меню',0 menu_file db 'File',0 menu_test1 db 'Test',0 menu_open1 db 'Open',0 menu_save1 db 'Save',0 menu_exit1 db 'Exit',0 menu_name db 'ZZZ_Menu',0 test_msg db 'You select menu item TEST',0 open_msg db 'You select menu item OPEN',0 save_msg db 'You select menu item SAVE',0 menu_handlers dq test_msg, open_msg, save_msg msg MSG <> end Функция TrackPopupMenu показывает на экране контекстное меню в заданном расположении и устанавливает выбор пунктов в меню. Контекстное меню может появляться в любом месте на экране. Код (C): BOOL TrackPopupMenu( HMENU hMenu, /* Идентифицирует контекстное меню, которое нужно отобразить. Дескриптор может быть получен путем вызова CreatePopupMenu, чтобы создать новое контекстное меню, или путем вызова GetSubMenu, чтобы извлечь дескриптор подменю, связанного с существующим пунктом меню */ UINT uFlags, // флажки расположения на экране и кнопки мыши*/ int x, // горизонтальная позиция в экранных координатах*/ int y, // вертикальная позиция в экранных координатах*/ int nReserved, // зарезервирован, должен быть ноль*/ HWND hWnd, /* Идентифицирует окно, которое владеет контекстным меню. Это окно принимает все сообщения от меню. Окно не получает сообщение WM_COMMAND от меню до тех пор, пока функция не возвратит значение */ CONST RECT *prcRect /* Указывает на структуру RECT, которая определяет часть экрана, в которой пользователь может выбирать контекстное меню без ее освобождения. Если prcRect = NULL, контекстное меню освобождается, если пользователь щелкает кнопкой мыши вне контекстного меню */ ); Набор битовых флажков uFlags, которые определяют параметры функции. Используйте одну из следующих констант битовых флажков, чтобы определить, как функция установит контекстное меню по горизонтали: TPM_CENTERALIGN4Если этот флажок установлен, функция выравнивает по центру горизонтали контекстное меню, относительно координаты, определяемой параметром x.TPM_LEFTALIGN0Если этот флажок установлен, функция устанавливает контекстное меню так, чтобы его левая сторона была выровнена по координате, определяемой параметром x.TPM_RIGHTALIGN8Устанавливает контекстное меню так, чтобы правая сторона была выровнена по координате, определяемой параметром x.Используйте одну из следующих констант битового флажка, чтобы определить, какой кнопкой мыши установится контекстное меню: TPM_LEFTBUTTON0Если этот флажок установлен, контекстное меню устанавливается левой кнопкой мыши.TPM_RIGHTBUTTON2Если этот флажок установлен, контекстное меню устанавливается правой кнопкой мыши.
Глава двадцать первая. Создание меню через Template-структуру В этой главе Братец Кролик научился обходиться без компилятора ресурсов и вставлять в окно меню при помощи функции LoadMenuIndirect. Скачайте пример здесь Код (ASM): include win64a.inc ZZZ_TEST equ 0 ZZZ_OPEN equ 1 ZZZ_SAVE equ 2 ZZZ_EXIT equ 3 ;----------------macros------------------ du macro string local bslash bslash = 0 irpc c,<string> if bslash eq 0 if '&c' eq "/" bslash = 1 elseif '&c'gt 127 db ('&c'- 0B0h),4 else dw '&c' endif else bslash = 0 if '&c' eq "n" DW 0Dh,0Ah elseif '&c' eq "/" dw '/' elseif '&c' eq "r" dw 0Dh elseif '&c' eq "l" dw 0Ah elseif '&c' eq "s" dw 20h elseif '&c' eq "c" dw 3Bh elseif '&c' eq "t" dw 9 endif endif endm dw 0 endm ;----------------------------------------- .code WinMain proc local msg:MSG xor ebx,ebx mov esi,IMAGE_BASE mov eax,10027h mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOW+1;hbrBackground push 10003h ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra pushaddr WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp ;addr WNDCLASSEX invoke LoadMenuIndirect,&appMenuTemplate push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rax push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE lea edi,msg @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc proc hwnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_COMMAND je wmCOMMAND leave jmp DefWindowProc wmDESTROY:invoke ExitProcess,NULL wmCOMMAND:cmp r8,ZZZ_EXIT je wmDESTROY show_msg:mov rdx,menu_handlers[r8*8] invoke MessageBox,,,&menu_name,MB_OK wmBYE: leave retn WndProc endp ;--------------------------------------- ClassName db 'Win64 Iczelion''s lesson #8b: Menu and template-structure',0 menu_name db 'ZZZ_Menu',0 test_msg db 'You select menu item TEST',0 open_msg db 'You select menu item OPEN',0 save_msg db 'You select menu item SAVE',0 menu_handlers dq test_msg, open_msg, save_msg align 8 appMenuTemplate dw 1 ; menu template version dw 4 ; offset from end of this word to menu item list dd 0 ; menu bar help ID dd MFT_STRING,MFS_ENABLED,0 dw MFR_POPUP ; first column du <&File> dw 0 ; pad to align 4 dd 0 ; popup help ID dd MFT_STRING,MFS_ENABLED,ZZZ_TEST dw 0 du <&Test> dw 0 ; pad to align 4 dd MFT_STRING,MFS_ENABLED,ZZZ_OPEN dw 0 du <&Open> dw 0 ; pad to align 4 dd MFT_STRING,MFS_ENABLED,ZZZ_SAVE dw 0 du <&Save> dw 0 ; pad to align 4 dd MFT_SEPARATOR,0,0 dw 0,0 ; pad to align 4 dd MFT_STRING,MFS_ENABLED,ZZZ_EXIT dw MFR_END ; bottom of column du <&Exit> dw 0 ; pad to align 4 dd MFT_STRING,MFS_ENABLED,ZZZ_EXIT dw MFR_END ; second column, last one du <&Exit> dw 0 ; pad to align 4 dd 0 ; popup help ID end Меню может быть создано из шаблона меню, который формируется в памяти во время выполнения программы. Например, прикладная программа, которая позволяет пользователю настраивать свое меню, может создать шаблон меню в памяти, основанной на стандартных установках пользователя. Приложение может затем сохранить шаблон в файле или в системном реестре для будущего использования. Чтобы создать меню из шаблона в памяти, используют функцию LoadMenuIndirect. Это может быть полезно если ваша прикладная программа создает шаблоны меню динамически. Синтаксис функции LoadMenuIndirect Код (C): HMENU LoadMenuIndirect { CONST MENUTEMPLATE *lpMenuTemplate // адрес шаблона меню ); Стандартный шаблон меню состоит из структуры MENUITEMTEMPLATEHEADER, сопровождаемой одной или несколькими структурами MENUITEMTEMPLATE. Расширенный шаблон меню состоит из структуры MENUEX_TEMPLATE_HEADER, сопровождаемой одной или несколькими структурами MENUEX_TEMPLATE_ITEM. Структура MENUITEMTEMPLATEHEADER определяет заголовок для шаблона меню. Полный шаблон меню состоит из заголовка и одного или нескольких перечисляемых пунктов меню. Код (ASM): MENUITEMTEMPLATEHEADER STRUCT versionNumber WORD ?;номер версии, должен быть нуль loffset WORD ?;смещение в байтах первой структуры ;MENUITEMTEMPLATE, от конца заголовка. Перечисление пунктов меню начинается ;с этого смещения. Обычно, этот член структуры нулевой, а список пунктов ;меню следует непосредственно после заголовка MENUITEMTEMPLATEHEADER ENDS Одна или несколько структур MENUITEMTEMPLATE объединяются, чтобы сформировать список пунктов меню. Структура MENUEX_TEMPLATE_HEADER определяет заголовок расширенного шаблона меню. Код (C): typedef struct { WORD wVersion; /* Номер версии шаблона. Этот член должен быть равен 1 для расширенных шаблонов меню */ WORD wOffset; /*Смещение первой структуры MENUEX_TEMPLATE_ITEM, относительно конца этого члена структуры. Если первый пункт определяется непосредственно следом за членом dwHelpId, этот член должен быть равен 4 */ DWORD dwHelpId; /*Идентификатор пункта Справка (Help) горизонтального меню */ } MENUEX_TEMPLATE_HEADER; Расширенный шаблон меню состоит из структуры MENUEX_TEMPLATE_HEADER, сопровождаемой одной или несколькими следующими друг за другом структурами MENUEX_TEMPLATE_ITEM. Структуры MENUEX_TEMPLATE_ITEM, которые являются переменными в длине, выравниваются по границам двойного слова. Структура MENUEX_TEMPLATE_ITEM определяет пункт меню в расширенном шаблоне. Код (C): typedef struct { DWORD dwType; /* Тип пункта меню. Может быть комбинацией типов (начинающихся с MFT) значений, перечисляемых структурой MENUITEMINFO */ DWORD dwState; /* Состояние пункта меню. Может быть комбинацией значений состояний (начинающихся с MFS), перечисляемых структурой MENUITEMINFO */ UINT uId; /* Идентификатор пункта меню. Определяемое программой 16-разрядное значение, которое идентифицирует пункт меню. В расширенном ресурсе меню, пункты, которые открывают "выскакивающие" меню или подменю также как командные пункты могут иметь идентификаторы */ WORD bResInfo; /* Значение, устанавливающее, является ли пункт меню последним пунктом в горизонтальном, "выскакивающем" меню, подменю или контекстном меню и является ли он пунктом, который, открывает "выскакивающее" меню или подменю */ WCHAR szText[1];/* Текст пункта меню. Строка UNICODE с нулевым символом в конце, выравнивается по границе слова. Размер пункта меню определяется вариантами, зависящими от длины этой строки */ DWORD dwHelpId;/* Идентификатор Справки (Help) для "выскакивающего" меню или подменю. Включается только для пунктов, которые открывают "выскакивающие" меню или подменю, помещается в начале границы двойного слова, следом за szText переменной длины */ } MENUEX_TEMPLATE_ITEM; Члены структуры: bResInfo может быть нулевым или иметь такие значения: 80hСтруктура определяет последний пункт меню в горизонтальном, "выскакивающем" меню, подменю или контекстном меню.01Структура определяет пункт, который открывает "выскакивающее" меню или подменю. Последующие структуры определяют пункты меню в соответствующем "выскакивающем" меню или подменю.Структура MENUITEMTEMPLATE определяет пункт меню в шаблоне меню. Код (ASM): MENUITEMTEMPLATE STRUCT mtOption WORD ? ;флажки пункта меню mtID WORD ?; Устанавливает идентификатор командный пункта меню ; командный пункт отправляет сообщение о команде своему окну владельцу. Структура ;MENUITEMTEMPLATE для пункта, который открывает "выскакивающее" меню или подменю, ;не содержит mtID mtString WORD ?; строка с нулем в конце для пункта меню MENUITEMTEMPLATE ENDS mtOption устанавливает один или несколько нижеследующих предопределенных параметров меню, которые управляют внешним видом пункта меню: hexMF_INSERT0MF_BYCOMMAND0Определяет идентификатор меню, перед которым вставляется новый элементMF_STRING0Элемент ― текстовая строкаMF_UNHILITE0MF_ENABLED0Элемент меню доступенMF_UNCHECKED0При изображении элемент меню не отмечаетсяMF_GRAYED1Указывает, что пункт меню с самого начала неактивный и нарисован с серым эффектом (недоступен).MF_DISABLED2Элемент меню блокирован, но изображается обычны цветомMF_BITMAP4MF_CHECKED8Указывает, что пункт меню рядом с собой имеет галочку.MF_POPUP10Указывает, что это пункт один из тех, который, открывает "выскакивающее" меню или подменю.MF_MENUBARBREAK20Указывает, что пункт меню помещен в новом столбце. Старые и новые столбцы отделены полосой.MF_MENUBREAK40Указывает, что пункт меню помещен в новом столбце.MF_HILITE80MF_END80MF_CHANGE80MF_APPEND100MF_OWNERDRAW100Указывает, что окно владелец меню ответственно за прорисовку всех визуальных элементов пункта меню, включая выделение, установку "галочки" и неактивного состояния. Этот параметр не допустим для пункта в горизонтальном меню.MF_USECHECKBITMAPS200MF_DELETE200MF_BYPOSITION400Определяет порядковый номер элемента меню, перед которым вставляется новый элемент; если установить nPosition=-1, то элемент будет добавлен в конец менюMF_SEPARATOR800Горизонтальная разделительная линияMF_REMOVE1000MF_DEFAULT1000MF_SYSMENU2000MF_RIGHTJUSTIFY4000MF_HELP4000MF_MOUSESELECT8000
Глава двадцать вторая. Работа с таблицей акселераторов ТеорияВключение акселераторов менюПрежде чем закончить разговор о меню, следует упомянуть еще об одной возможности, связанной с меню. Это акселераторные клавиши, или акселераторы. Акселераторные клавиши ― клавиатурные комбинации, которые можно определить и которые, будучи нажаты, автоматически выбирают соответствующие им команды меню даже в том случае, когда мню не активировано и не отображается. При помощи акселератора можно выбирать соответствующий элемент меню, не обращаясь к самому меню. Термин акселератор является довольно точным, поскольку ввод команд с использованием таких клавиш осуществляется гораздо быстрее, чем активизация меню и выбор этих команд. Для определения акселераторов необходимо добавить таблицу акселераторов в файл ресурсов. Все определения акселераторов имеют следующий общий вид: Код (Text): ИмяТаблицы ACCELERATORS { Клавиша1, MenuID1 [, тип][, параметр] Клавиша2, MenuID2 [, тип][, параметр] . . . КлавишаN, MenuIDN [, тип][, параметр] } ИмяТаблицы имея таблицы акселераторов; Клавиша# определяет клавишу или комбинацию клавиш акселератора; MenuID# идентификатор элемента меню; тип указывает является ли клавиша стандартной (по умолчанию) или виртуальной; параметр может быть одним из следующих макросов: NOINVERT, ALT, SHIFT и CONTROL. Наличие NOINVERT означает, что соответствующий элемент меню при использовании акселератора не будет подсвечен, даже если он присутствует на экране. ALT указывает, что должна быть дополнительно нажата клавиша [Alt]; SHIFT и CONTROL специфицируют дополнительное нажатие клавиш [Shift] и [Ctrl]. Клавиша может быть символом в кавычках, либо целочисленным десятичным ASCII-кодом символа, либо виртуальной клавишей. Если указывается символ в кавычках, предполагается использование ASCII-символа. Если используется десятичное число, следует уточнить, что это код ASCII-символа, задав тип как ASCII. Если предполагается использование виртуальной клавиши, то тип должен быть VIRTKEY. Если клавиша представлена символом верхнего регистр в кавычках, соответствующая команда меню будет вызвана только в том случае, когда пользователь нажмет [Shift] вместе с указанной клавишей. Если клавиша представлена символом нижнего регистра и задан параметр ALT, для вызова соответствующей команды необходимо нажать [Alt] и указанный символ, а если задан символ верхнего регистра и ALT, то для вызова команды нужно вместе с заданной клавишей нажимать [Shift] и [Alt]. Наконец, если требуется, чтобы для вызова команды использовалась комбинация [Ctrl+символ], символ в кавычках должен предваряться символом "^". Виртуальная клавиша ― это системно-независимый код, определенный для основного набора служебных клавиш. Виртуальные клавиши включают определения функциональных клавиш [F1]-[F12], стрелок и других не ASCII-клавиш. Они определены, как макроимена в файлеwindows.inc. Все эти макроимена начинаются с VK_. Для того чтобы использовать виртуальную клавишу как акселератор, нужно просто указать ее макроимя, а тип должен быть VIRTKEY. Можно также использовать ALT, SHIFT и CONTROL для задания соответствующих комбинаций клавиш. вот некоторые примеры: определениенажатие"A", IDM_X[Shift]+[a]"a", IDM_X[a]"^A", IDM_X[Ctrl]+[a]"A", IDM_X, ALT[Alt]+[Shift]+[a]"a", IDM_X, ALT[Alt]+[a]VK_F2, IDM_X[F2]VK_F2, IDM_X, SHIFT[Shift]+[F2]Вот как выглядит RC-файл из предыдущих разделов с добавленной таблицей акселераторов Код (C): #define ZZZ_TEST 0 #define ZZZ_OPEN 1 #define ZZZ_SAVE 2 #define ZZZ_EXIT 3 #define IDM_MENU 37 #define IDR_MAINACCEL 105 IDM_MENU MENU { POPUP "&File" { MENUITEM "&Test\tCtrl+T",ZZZ_TEST MENUITEM "&Open\tCtrl+O",ZZZ_OPEN MENUITEM "&Save\tCtrl+S",ZZZ_SAVE MENUITEM SEPARATOR MENUITEM "&Exit\tCtrl+X",ZZZ_EXIT } MENUITEM "&Exit",ZZZ_EXIT } IDR_MAINACCEL ACCELERATORS DISCARDABLE { // определение акселераторов "^T",ZZZ_TEST "^O",ZZZ_OPEN "^S",ZZZ_SAVE "^X",ZZZ_EXIT } Заметьте, что названия элементов меню расширены и включают в себя названия акселераторов для вызова соответствующих команд. Каждый элемент отделяется от акселератора символом табуляции. Если вы в своем меню будете подключать виртуальные клавиши, то необходимо будет подключить файл "windows.inc", поскольку в нем определены макроимена виртуальных клавиш.Загрузка таблицы акселераторовХотя акселераторы определены в том же файле ресурсов, что и меню, они должны быть отдельно загружены при помощи функцииLoadAccelerators, которая имеет следующий прототип Код (C): HACCEL LoadAccelerators ( HINSTANCE ThisInst, //дескриптор текущего экземпляра приложения LPSTR Name //имя таблицы акселераторов ); Функция возвращает дескриптор загруженной таблицы акселераторов либо NULL, если таблица не может быть загружена Функцию LoadAccelerators нужно вызвать сразу после создания окна. Например таблица акселераторов для IDR_MAINACCEL загружается так: Код (ASM): invoke LoadAccelerators,IMAGE_BASE,IDR_MAINACCEL mov ACC,rax Значение ACC будет использовано позже для обработки акселераторных комбинаций. Несмотря на то, что функция LoadAccelerators загружает таблицу акселераторов, программа не будет их обрабатывать до тех пор, пока в цикл обработки сообщений не добавят функцию TranslateAccelerator Код (C): int TranslateAccelerator ( HWND hWnd, /*дескриптор окна, для которого должны транслироваться акселераторные комбинации*/ HACCEL hAccel, /*дескриптор таблицы акселераторов, возвращенный функцией LoadAccelerators*/ LPMSG lpMess //указатель на сообщения ); Функция TranslateAccelerator преобразует сообщения WM_KEYDOWN и WM_SYSKEY в сообщения WM_COMMAND и M_SYSCOMMAND соответственно. При этом в старшем слове параметра wParam помещается 1, как отличие для акселератора. В младшем слове содержится индентификатор пункта меню. Функция TranslateAccelerator возвращает ненулевое значение, если была использована акселераторная комбинация, и NULL ― в противном случае. С использованием функции TranslateAccelerator цикл обработки сообщений выглядит так Код (ASM): lea edi,msg @@: ;цикл обработки сообщений invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi ;вернуть управление Windows invoke TranslateAccelerator,hwnd,ACC,edi or eax,eax jne @b invoke TranslateMessage,edi;разрешить использование клавиатуры jmp @b ПрактикаСкачайте пример здесь Код (ASM): ; GUI # include win64a.inc ZZZ_TEST equ 0 ZZZ_OPEN equ 1 ZZZ_SAVE equ 2 ZZZ_EXIT equ 3 IDM_MENU equ 37 IDR_MAINACCEL equ 105 .code WinMain proc local msg:MSG xor ebx,ebx mov esi,IMAGE_BASE mov eax,10029h mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push IDM_MENU ;lpszMenuName push COLOR_WINDOW+1;hbrBackground push 10005h ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra db 68h dd WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp ;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE mov hwnd,rax invoke LoadAccelerators,IMAGE_BASE,IDR_MAINACCEL mov ACC,rax lea edi,msg @@: ;цикл обработки сообщений invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi ;вернуть управление Windows invoke TranslateAccelerator,hwnd,ACC,edi or eax,eax jne @b invoke TranslateMessage,edi;разрешить использование клавиатуры jmp @b WinMain endp WndProc proc hWnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_COMMAND je wmCOMMAND leave jmp DefWindowProc wmDESTROY:invoke ExitProcess,NULL wmCOMMAND:and r8d,11y cmp r8d,ZZZ_EXIT je wmDESTROY show_msg:mov edx,menu_handlers[r8*4] mov r8d,offset menu_name invoke MessageBox,,,,MB_OK wmBYE:leave retn WndProc endp ;data ClassName db 'Win64 Iczelion''s lesson #8g: Using the accelerator table',0 menu_name db 'ZZZ_Menu',0 test_msg db 'You select menu item TEST',0 open_msg db 'You select menu item OPEN',0 save_msg db 'You select menu item SAVE',0 menu_handlers dd test_msg, open_msg, save_msg ACC dq ? hwnd dq ? end
Глава двадцать третья. Работа с инструментальной панелью (toolbar) ТеорияДальнейшего улучшения внешнего вида приложения можно добиться, включив в состав главного окна, наряду с линейкой меню (или вместо нее), инструментальную панель, которая представляет собой набор кнопок с рисунками, действующими точно так же, как и пункты обычного меню. Эти кнопки могут дублироваться отдельные команды меню, но могут и дополнять их. На рисунке показана часть главного меню Microsoft Word со строкой заголовка, линейкой обычного меню (Файл, Правка и так далее) и инструментальной панелью с пиктограммами стандартных действий (открытие и сохранение файла, редактирование, форматирование и так далее) Панель инструментов, вероятно, самый распространенный из общих элементов управления ― представляет из себя графическое меню с некоторыми дополнительными свойствами. Команды меню в панели инструментов представлены в виде небольших изображений, имеющих форму кнопок. Часто такая панель используется вместе или вместо обычного меню. Для создания инструментальной панели необходимо прежде всего подготовить файл с растровым изображением рисунка на кнопках (в формате BMP). Могут быть разные варианты размещения кнопок (вплотную друг к другу, с промежутками, группами и так далее). Обычно кнопки имеют размер 16х16 пикселов, и тогда для размещения на инструментальной панели, например, трех кнопок, нужно создать растровое изображение размером 16х48 пикселов. Разумеется рисунки на кнопках могут быть цветными. Поскольку изображение кнопок будет выступать у нас в качестве ресурса, в файл ресурсов необходимо включить ссылку на файл с изображением кнопок, например, таким образом. Код (Text): ID_BNS BITMAP "BTNS.BMP" Здесь ID_BNS ― произвольная символическая константа, которая затем будет использоваться в качестве идентификатора данного ресурса, а BTNS.BMP ― имя файла с изображением кнопок. Действия по программной организации инструментальной панели удобно выполнить при обработке события WM_CREATE. Здесь прежде всего создается (и обнуляется) структурная переменная ― массив из трех (по числу кнопок) структур типа TBBUTTON. Эта структура описана в файле comctrl32.inc. После заполнения перечисленных выше элементов во всех трех (если кнопок три) членах нашего массива следует вызвать функциюCreateToolbarEx, указав в качестве ее параметров: hwnd ― дескриптор родительского окна панели инструментов (дескриптор главного окна) dwStyle ― стиль инструментальной панели. Кроме стиля WS_CHILD, он может включать и другие стандартные стили, такие какWS_BORDER или WS_VISIBLE. Имеются еще два стиля, задаваемых для панели инструментов, один из них TBSTYLE_TOOLTIPSпозволяет использовать подсказки панели инструментов ― tooltips. Другой специфичный для панели инструментов стиль, называемыйTBSYLE_WRAPABLE, обеспечивает возможность отображать длинные панели инструментов (с большим количеством кнопок) в несколько строк. ID ― идентификатор всей инструментальной панели, который в программе не используется и может быть равен, например, -1; NumButtons ― число изображений кнопок в BMP-файле; hInst ― дескриптор текущего экземпляра приложения; BPID ― идентификатор ресурса с изображением кнопок; Buttons ― адрес массива структур типа TBBUTTON; NumButtons ― число кнопок в панели инструментов; ButtonWidth ― ширину каждой кнопки; ButtonHeight ― высоту каждой кнопки; BMPWidth ― ширину изображения на каждой кнопке; BMPHeight ― высоту изображения на каждой кнопке размеры кнопок и изображений на них в принципе могут не совпадать, при указании нулевых значений, размеры кнопок подбираются системой автоматически исходя из размеров изображения на них; Size ― размер структуры TBBUTTON, определяемый с помощью sizeof. Функция CreateToolbarEx возвращает дескриптор панели инструментов. Каждая кнопка панели инструментов должна иметь связанную с ней структуру типа TBBUTTON. Код (ASM): TBBUTTON STRUCT iBitmap DWORD ? ;порядковый номер (индекс) кнопки, этот индекс, в свою очередь, ;определяет смещение в исходном файле ресурсе растра, начиная с которого рисуется ;изображение в кнопке. Индексы начинаются от 0 idCommand DWORD ? ;определяет команду, ассоциированную с кнопкой. При нажатии ;кнопки родительское окно получает сообщение WM_COMMAND. В младшем слове параметра; ;wParam этого сообщения содержится значение idCommand fsState BYTE ? ;начальное состояние кнопки fsStyle BYTE 7 dup (?);стиль кнопки dwData DWORD ? ;может содержать дополнительную информацию, определяемую программистом iString DWORD ? ;содержит индекс строки, ассоциированный с кнопкой. Использовать это поле ;необязательно. Если это поле не используется, тогда его значение равно нулю TBBUTTON ENDS Параметр fsState может принимать одно из следующих значений или комбинацией значений: состояние значениеTBSTATE_CHECKED кнопка нажатаTBSTATE_ENABLED кнопка активна (разрешена)TBSTATE_HIDDEN кнопка скрыта неактивна (запрещена)TBSTATE_INDETERMINATE кнопка закрашена серым цветом и неактивнаTBSTATE_PRESSED кнопка нажатаTBSTATE_WRAP все следующие кнопки будут отображаться в следующей строкеПараметр fsStyle может представлять собой любую из имеющих смысл комбинацию следующих значений: стильэффектTBSTYLE_BUTTONобычная кнопкаTBSTYLE_CHECKпри каждом нажатии кнопки ее состояние изменяется на противоположное (нажата ― отпущена)TBSTYLE_CHECKGROUPкнопка со стилем TBSTYLE_CHECK в группе кнопок, представляющих собой набор параметров, из которых можно выбрать только одинTBSTYLE_GROUPобычная кнопка в группе кнопок, представляющих собой набор параметров, из которых можно выбрать только одинTBSTYLE_SEPзадание интервала между кнопками. При задании этого стиля idCommand должно быть равно нулю. Стиль TBSTYLE_SEP используется для задания промежутков между кнопками панели инструментов при объединении этих кнопок в группы по функциональным признакамПо умолчанию панели инструментов являются полностью автоматическими элементами управления и не требуют от программиста каких-либо действий по управлению ими. Существует возможность программного управления панелями инструментов путем посылки им управляющих сообщений, для чего используется функция SendMessage. Сообщения, которые можно посылать панели инструментов: сообщениеназначениеwParamlParam = 0lParam = 1CHECKBUTTONнажать или отпустить кнопкуидентификатор кнопкиесли кнопка отпущенаесли кнопка нажатаENABLEBUTTONзапретить или разрешить кнопкуидентификатор кнопкиесли кнопка должна быть запрещенаесли кнопка должна быть разрешенаHIDEBUTTONскрыть или отобразить кнопкуидентификатор кнопкиесли кнопку требуется отобразитьесли кнопку необходимо скрытьПри нажатии кнопки инструментальной панели Windows посылает в приложение сообщение WM_COMMAND, которое приводит к вызову с передачей через второй параметр этой функции значение идентификатора нажатой кнопки. Панели инструментов могут также генерировать нотификационные сообщения, которые информируют программу о происходящих в этих панелях событиях. Имена этих сообщений начинаются с TBN_. Скачайте пример здесь
Код (ASM): ; GUI # include win64a.inc ID_1 equ 0 ID_2 equ 1 ID_3 equ 2 .code WinMain proc local msg:MSG xor ebx,ebx mov esi,IMAGE_BASE mov eax,10027h mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOW+1;hbrBackground push 10005h ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra db 68h dd WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE mov edi,eax ;в edi дескриптор главного окна mov qword ptr [rbp-0F8h],LR_LOADFROMFILE mov [rbp-100h],rbx mov edx,offset aMybp1 invoke LoadImage,IMAGE_BASE,,0,0 ;создается структурная переменная - массив из трех структур типа TBBUTTON mov edx,sizeof TBBUTTON mov bl,2 lea rcx,[tbb+rdx*2] @@: mov [rcx+TBBUTTON.iBitmap],ebx;порядковый номер кнопки (кнопки нумеруются от 0) mov [rcx+TBBUTTON.idCommand],ebx;идентификатор кнопки ID_1 ID_2 ID_3 mov [rcx+TBBUTTON.fsState],TBSTATE_ENABLED;состояние кнопки. ;Значение TBSTATE_ENABLED разрешает управление с помощью данной кнопки or ebx,ebx jz @f sub ecx,edx;переходим к следующей структуре TBBUTTON dec ebx jmp @b @@: mov [rsp+60h],rdx;size of structure TBBUTTON mov edx,18 mov [rsp+58h],rdx;ширина и высота mov [rsp+50h],rdx;изображения на каждой кнопке mov [rsp+48h],rdx;ширина и высота mov [rsp+40h],rdx;каждой кнопки mov qword ptr [rsp+38h],3;число кнопок mov [rsp+30h],rcx;адрес массива структуры типа TBBUTTON mov [rsp+28h],rax;идентификатор ресурса с изображением кнопок mov [rsp+20h],rbx;0 mov r9d,3 ;число изображений кнопок в файле bmp or r8,-1;идентификатор всей инструментальной панели, ;который в программе не используется и может быть равен -1 mov edx,WS_CHILD+WS_BORDER+WS_VISIBLE;+TBSTYLE_TOOLTIPS;стиль инструментальной панели mov ecx,edi;дескриптор главного окна invoke CreateToolbarEx invoke CreateSolidBrush,0FF0000h;blue=0FF0000h mov BlueBrush,eax invoke CreateSolidBrush,0FF00h;green=0FF00h mov GreenBrush,eax lea edi,msg @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc proc hWnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD local ps:PAINTSTRUCT mov hWnd,rcx mov wParam,r8 mov lParam,r9 cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_PAINT je wmPAINT cmp edx,WM_SIZE je wmSIZE cmp edx,WM_COMMAND je wmCOMMAND leave jmp DefWindowProc wmDESTROY:invoke ExitProcess,NULL wmPAINT:lea edx,ps invoke BeginPaint mov ecx,sw jecxz @f;в sw при старте программы ноль, если sw=0 ;тогда не нужно изменять цвет окна ;заполняем окно выбранной кистью из массива кистей mov r8d,[BlueBrush+rcx*4-4] lea edx,ps.PAINTSTRUCT.rcPaint invoke FillRect,rax @@: lea edx,ps invoke EndPaint,hWnd;Освобождаем контекст отображения jmp wmBYE wmCOMMAND:mov eax,r8d;wParam;в зависимости от значения в wParam ;либо закрываем окно (wParam=2), либо изменяем его цвет (wParam=0 или 1) jmp [handler+rax*8] ;если wParam=0 или wParam=1 посылаем сообщение WM_PAINT @0: inc eax mov [sw],eax wmSIZE:invoke InvalidateRect,,0,TRUE;при изменении размера окна - получаем ;размеры окна wmBYE:leave retn handler dq @0,@0,wmDESTROY WndProc endp ;data ClassName db 'Win64 Iczelion''s lesson #8h: инструментальная панель ',0 BlueBrush dd ? GreenBrush dd ? sw dd 0 tbb TBBUTTON 3 dup(<0>) aMybp1 db "Images\btns.bmp",0 end Глава двадцать четвертая. Всплывающие подсказки ТеорияВ современных программах, управляемых с помощью меню и инструментальных панелей, принято включать в состав меню всплывающие подсказки («tooltips») ― небольшие текстовые окошки, открываемые автоматически и содержащие краткое описание назначения кнопок, когда курсор мыши помещается на соответствующую кнопку инструментальной панели. Для организации всплывающих подсказок следует прежде всего создать инструментальную панель, как это было описано в предыдущем подразделе, дополнив ее стиль, указываемый в качестве второго параметра функции CreateToolbarEx, константой TBSYLE_TOOLTIPS. Отслеживание положения курсора мыши и вывод на экран требуемых подсказок осуществляется в ответ на сообщение Windows WM_NOTIFY, для обработки которого придется дополнить функцию WinProc. В момент прихода сообщения WM_NOTIFY параметр lParam имеет указатель на структуру NMHDR, Код (ASM): NMHDR STRUCT hwndFrom QWORD ? ;дескриптор элемента управления idFrom QWORD ? ;идентификатор элемента управления icode QWORD ? ;код, передаваемый в сообщение NMHDR ends которая входит в большую по объему структуру типа TOOLTIPTEXT, в которую NMHDR входит в качестве первого элемента. Код (ASM): TOOLTIPTEXT STRUCT hdr NMHDR <> lpszText QWORD ? DWORD ? szText BYTE 80 dup (?) hInst QWORD ? uFlags DWORD ? TOOLTIPTEXT ENDS Таким образом, если использовать параметр lParam непосредственно, он будет служить указателем на структуру NMHDR, и с его помощью можно обращаться к элементам этой структуры, однако остальные поля структуры TOOLTIPTEXT будут недоступны. Если же преобразовать параметр lParam в тип LPTOOLTIPTEXT, то он будет указывать на всю структуру TOOLTIPTEXT, и через него можно получить доступ ко всем элементам этой структуры; через имя вложенной структуры hdr в этом случае можно получить доступ и к элементам структуры NMHDR. Таким образом, допустимы, в частности, следующие обращения: lParam→icode (lParam ― указатель на структуру NMHDR, а icode ― элемент этой структуры); lParam→idFrom (lParam ― указатель на структуру NMHDR, а idFrom ― элемент этой структуры); ((LPTOOLTIPTEXT)lParam)→szText (выражение (LPTOOLTIPTEXT)lParam является указателем на структуру TOOLTIPTEXT, а szText ― элемент этой структуры); ((LPTOOLTIPTEXT)lParam)→hdr.idFrom (обращение к элементам вложенной структуры посредством указателя на внешнюю). Сообщения WM_NOTIFY представляют для нас интерес лишь тогда, когда icode, входящий в структуру NMHDR, равен TTN_NEEDTEXT, а полеidFrom содержится идентификатор кнопки, для которой требуется подсказка. Текст подсказки можно задать одним из трех способов: скопировать текст подсказки в поле szText структуры TOOLTIPTEXT; записать в поле szText указатель на текстовую строку; задать идентификатор ресурса строки. В этом случае идентификатор строки записывается в поле lpszText, а поле hInst содержит дескриптор приложения, содержащего ресурс. Простейшим является способ записать в lpszText указатель на строку, определенную в программе. Следующий фрагмент обрабатывает запросы подсказок в нашей программе. В комментариях прототип фрагмента программы на С Код (ASM): wmNOTIFY: ;r9=lParam cmp dword ptr [r9+NMHDR._code],TTN_NEEDTEXT;if( LPNMHDR(lParam)->code == TTN_NEEDTEXT) jnz wmBYE mov lpttt,r9 ;LPTOOLTIPTEXT lpttt = (LPTOOLTIPTEXT) lParam; mov eax,dword ptr [r9+NMHDR.idFrom] ;switch (lpttt->hdr.idFrom) { cmp eax,ID_3 ;case ID_1:strcpy(lpttt->szText,"Fill blue");break; ja wmBYE ;case ID_2:strcpy(lpttt->szText,"Fill green");break; ;case ID_3:strcpy(lpttt->szText,"Exit"); lea ecx,[r9+TOOLTIPTEXT.szText] invoke lstrcpy,,[pt+rax*8] После того как текст подсказки определен, подсказка отображается автоматически при возврате управления к Windows, и программе больше не нужно выполнять никаких действий. Как видите, подсказки хорошо автоматизированы и просты в использовании.
ПрактикаСкачайте пример здесь Код (ASM): ; GUI # include win64a.inc ID_1 equ 0 ID_2 equ 1 ID_3 equ 2 ;----------------------------------------- .code WinMain proc local msg:MSG xor ebx,ebx mov esi,IMAGE_BASE mov eax,10027h mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOW+1;hbrBackground push 10003h ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra pushaddr WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp ;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE mov rdi,rax ;rdi=hWnd mov qword ptr [rbp-0F8h],LR_LOADFROMFILE mov [rbp-100h],rbx mov edx,offset aMybp1 invoke LoadImage,IMAGE_BASE,,0,0 ;создается структурная переменная - массив из трех структур типа TBBUTTON mov edx,sizeof TBBUTTON mov bl,2 lea rcx,[tbb+rdx*2] @@: mov [rcx+TBBUTTON.iBitmap],ebx;порядковый номер кнопки (кнопки нумеруются от 0) mov [rcx+TBBUTTON.idCommand],ebx;идентификатор кнопки ID_1 ID_2 ID_3 mov [rcx+TBBUTTON.fsState],TBSTATE_ENABLED;;состояние кнопки ;Значение TBSTATE_ENABLED разрешает управление с помощью данной кнопки or ebx,ebx;заполняем массив с ebx как индекс в массиве от 2 до 0 jz @f sub ecx,edx;переходим к следующей структуре TBBUTTON dec ebx jmp @b @@: mov [rbp-0C0h],rdx;size TBBUTTON ;size of structure TBBUTTON mov edx,18 mov [rbp-0C8h],rdx;ширина и высота mov [rbp-0D0h],rdx;изображения на каждой кнопке mov [rbp-0D8h],rdx;ширина и высота mov [rbp-0E0h],rdx;каждой кнопки mov qword ptr [rbp-0E8h],3;число кнопок mov [rbp-0F0h],rcx;адрес массива структуры типа TBBUTTON mov [rbp-0F8h],rax;идентификатор ресурса с изображением кнопок mov [rbp-100h],rbx;0 invoke CreateToolbarEx,edi,WS_CHILD or \ WS_BORDER or WS_VISIBLE or TBSTYLE_TOOLTIPS,-1,3 ;идентификатор всей инструментальной панели, ;который в программе не используется и может быть равен -1 ;3 - число изображений кнопок в файле bmp ;WS_BORDER or WS_VISIBLE or TBSTYLE_TOOLTIPS - стиль инструментальной панели ;edi - дескриптор главного окна invoke CreateSolidBrush,0FF0000h;blue=FF0000h mov BlueBrush,eax;hBlueBrush invoke CreateSolidBrush,000FF00h;green=00FF00h mov GreenBrush,eax lea edi,msg @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc proc hWnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD local ps:PAINTSTRUCT local lpttt:QWORD mov hWnd,rcx mov wParam,r8 mov lParam,r9 cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_PAINT je wmPAINT cmp edx,WM_SIZE je wmSIZE cmp edx,WM_COMMAND je wmCOMMAND cmp edx,WM_NOTIFY je wmNOTIFY leave jmp DefWindowProc wmDESTROY:invoke ExitProcess,NULL wmPAINT:lea edx,ps invoke BeginPaint mov ecx,sw jecxz @f;в sw при старте программы ноль, если sw=0 ;тогда не нужно изменять цвет окна ;заполняем окно выбранной кистью из массива кистей mov r8d,[BlueBrush+rcx*4-4] lea edx,ps.PAINTSTRUCT.rcPaint invoke FillRect,rax @@: lea edx,ps invoke EndPaint,hWnd jmp wmBYE wmNOTIFY: ;r9=[lParam] ;if( LPNMHDR(lParam)->code ==TTN_NEEDTEXT) cmp dword ptr [r9+NMHDR._code],TTN_NEEDTEXT jnz wmBYE mov lpttt,r9 ;LPTOOLTIPTEXT lpttt = (LPTOOLTIPTEXT) lParam; mov eax,dword ptr [r9+NMHDR.idFrom];switch (lpttt->hdr.idFrom) { cmp eax,ID_3 ;case ID_1:strcpy(lpttt->szText,"Fill blue");break; ja wmBYE ;case ID_2:strcpy(lpttt->szText,"Fill green");break; ;case ID_3:strcpy(lpttt->szText,"Exit"); lea ecx,[r9+TOOLTIPTEXT.szText] invoke lstrcpy,,[pt+rax*8] jmp wmBYE wmCOMMAND:mov eax,r8d;wParam;в зависимости от значения в wParam ;либо закрываем окно (wParam=2), либо изменяем его цвет (wParam=0 или 1) jmp [handler+rax*8] ;if wParam=0 or wParam=1 send message WM_PAINT @0: inc eax mov sw,eax wmSIZE:invoke InvalidateRect,,0,TRUE;при изменении размера окна - получаем ;размеры окна wmBYE:leave retn handler dq @0,@0,wmDESTROY WndProc endp ;--------------------------------------- ClassName db 'Win64 Iczelion''s lesson #8i: toolbar and tooltips',0 tbb TBBUTTON 3 dup(<0>) szText1 db 'заполнить синим',0 szText2 db 'заполнить зеленым',0 szText3 db 'выход из программы',0 pt dq szText1,szText2,szText3 aMybp1 db "Images\btns.bmp",0 sw dd 0 BlueBrush dd ? GreenBrush dd ? end Глава двадцать пятая. Инструментальная панель ПрактикаСкачайте пример здесь Для создания инструментальной панели необходимо подготовить файл с растровым изображением рисунка на кнопках (в формате BMP). Но это нужно в том случае если вы хотите разместить в toolbar'е оригинальные рисунки. Если речь идет о стандартных иконках «вырезать», «вставить», «скопировать», «сохранить» и тому подобное ― тогда стоит заглянуть в файл «windows.inc» Код (ASM): STD_CUT equ 0 STD_COPY equ 1 STD_PASTE equ 2 STD_UNDO equ 3 STD_REDOW equ 4 STD_DELETE equ 5 STD_FILENEW equ 6 STD_FILEOPEN equ 7 STD_FILESAVE equ 8 STD_PRINTPRE equ 9 STD_PROPERTIES equ 10 STD_HELP equ 11 STD_FIND equ 12 STD_REPLACE equ 13 STD_PRINT equ 14 Это идентификаторы «стандартных иконок», которые при обработке сообщения WM_CREATE мы можем использовать для создания инструментальной панели. Это позволяет нам отказаться от рисунка стандартных кнопок и использования функции LoadImage. Ниже пример создания кнопки «вырезать» Код (ASM): lea edi,tbb mov tbb.fsStyle,TBSTYLE_BUTTON mov tbb.idCommand,ID_CUT mov tbb.iBitmap,STD_CUT invoke SendMessage,hToolBar,TB_ADDBUTTONS,1,edi