• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            S.l.e!ep.¢%

            像打了激速一樣,以四倍的速度運(yùn)轉(zhuǎn),開(kāi)心的工作
            簡(jiǎn)單、開(kāi)放、平等的公司文化;尊重個(gè)性、自由與個(gè)人價(jià)值;
            posts - 1098, comments - 335, trackbacks - 0, articles - 1
              C++博客 :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理
            ??? 前些天花了很多時(shí)間寫(xiě)這樣一個(gè)軟鍵盤(pán),效果是顯示一個(gè)與鍵盤(pán)外觀相似的視圖,通過(guò)鼠標(biāo)單擊像活動(dòng)窗口發(fā)送虛擬的鍵盤(pán)消息。目標(biāo)是實(shí)現(xiàn)像windows自帶的軟鍵盤(pán)osk相似。
            ??? 看似很簡(jiǎn)單的工作,設(shè)計(jì)中卻遇到了很多困難。
            ??? 困難一:鍵盤(pán)按鍵分類
            ??????? 鍵盤(pán)按鍵有很多種分類方法。
            ??????? 第一種:按顯示分類。按住shift鍵,字母鍵、符號(hào)鍵顯示上面的字符;按下caps lock鍵,字母鍵切換為大寫(xiě)字母。
            ??????? 第二種:按功能分類。大體有可顯示字符類、控制類。控制類包括shift,ctrl等。
            ??????? 為了解決可變的顯示問(wèn)題,采用了一個(gè)自我感覺(jué)非常好的解決方案:字符集、鍵集相互獨(dú)立。如此一來(lái),只要總體按照功能分類,通過(guò)特定功能的按鍵控制有效字符集即可,也就是說(shuō),對(duì)普通按鍵來(lái)說(shuō),它只負(fù)責(zé)到指定的字符集中去取對(duì)應(yīng)序號(hào)的字符即可。
            //LabelSet.h
            #pragma?once

            //字母標(biāo)簽集合
            class?LabelSet
            {
            public:
            ????LabelSet(LPCSTR
            *?_pTable,int?_n);
            ????LPCSTR?getLabel(
            int?_id)?const;

            ????
            ~LabelSet();

            protected:
            ????LabelSet(){}

            private:
            ????LPCSTR
            *?pTable;
            ????
            int?n;
            };

            //相當(dāng)于單刀雙擲開(kāi)關(guān)組
            class?LabelSetEx
            {
            protected:
            ????
            struct?Switch
            ????{
            ????????LabelSet
            *?s[2];
            ????????
            int?at;
            ????};

            public:
            ????LabelSetEx(
            int?_n);
            ????
            bool?addSets(int?id,LPCSTR*?s1,LPCSTR*?s2,int?n,int?at?=?0);
            ????LPCSTR?getLable(
            int?id,int?off)?const;
            ????
            void?turn(int?id);

            ????
            ~LabelSetEx();

            private:
            ????
            int?n;????//開(kāi)關(guān)組總個(gè)數(shù)
            ????Switch*?pGroup;????//開(kāi)關(guān)組
            };

            //
            //LabelSet.cpp
            #include?"StdAfx.h"
            #include?
            "LabelSet.h"
            #include?
            <algorithm>
            #include?
            <cassert>

            using?namespace?std;

            LabelSet::LabelSet(?LPCSTR
            *?_pTable,int?_n?)
            {
            ????n?
            =?_n;
            ????pTable?
            =?new?LPCSTR[n];
            ????copy(_pTable,_pTable?
            +?_n,pTable);
            }

            LPCSTR?LabelSet::getLabel(?
            int?_id?)?const
            {
            ????
            return?pTable[_id];
            }

            LabelSet::
            ~LabelSet()
            {
            ????delete?[]?pTable;
            }

            LabelSetEx::LabelSetEx(?
            int?_n?)
            {
            ????n?
            =?_n;
            ????pGroup?
            =?new?Switch[n];
            ????memset(pGroup,
            0,n?*?sizeof(pGroup[0]));
            }

            LabelSetEx::
            ~LabelSetEx()
            {
            ????
            while(n--)
            ????{
            ????????
            if(pGroup[n].s[0]?==?pGroup[n].s[1])
            ????????????delete?pGroup[n].s[
            0];
            ????????
            else
            ????????{
            ????????????delete?pGroup[n].s[
            0];
            ????????????delete?pGroup[n].s[
            1];
            ????????}
            ????}
            ????delete?[]?pGroup;
            }

            bool?LabelSetEx::addSets(?int?id,LPCSTR*?s1,LPCSTR*?s2,int?n,int?at?/*=?0*/?)
            {
            ????assert((at?
            &?~1)?==?0);
            ????
            if(pGroup[id].s[0]?!=?NULL)
            ????????
            return?false;
            ????LabelSet
            *?p?=?new?LabelSet(s1,n);
            ????pGroup[id].s[
            0]?=?p;
            ????
            if(s1?==?s2)
            ????????pGroup[id].s[
            1]?=?p;
            ????
            else
            ????????pGroup[id].s[
            1]?=?new?LabelSet(s2,n);
            ????pGroup[id].at?
            =?at;
            ????
            return?true;
            }

            LPCSTR?LabelSetEx::getLable(?
            int?id,int?off?)?const
            {
            ????Switch
            *?p?=?pGroup?+?id;
            ????
            return?p->s[p->at]->getLabel(off);
            }

            void?LabelSetEx::turn(?int?id?)
            {
            ????assert((pGroup
            ->at?&?~1)?==?0);
            ????pGroup[id].at?
            ^=?1;
            }
            ??????? 以上取開(kāi)關(guān)的索引id是指字符集的分類id,在config.h文件下定義了這樣的id
            #pragma?once

            //分類id的定義
            #define?LABEL_SET_ALPHA??0
            #define?LABEL_SET_SYMBOL?1
            #define?LABEL_SET_NUMPAD?2
            #define?LABEL_SET_MAIN???3
            #define?LABEL_SET_HELP???4

            //字母串表
            extern?LPCSTR?AlphaTable1[];????//小寫(xiě)
            extern?LPCSTR?AlphaTable2[];????//大寫(xiě)
            extern?const?int?AlphaTableSize;

            //符號(hào)串表
            extern?LPCSTR?SymbolTable1[];????//
            extern?LPCSTR?SymbolTable2[];????//
            extern?const?int?SymbolTableSize;

            //小鍵盤(pán)數(shù)字表
            extern?LPCSTR?NumPadTable1[];????//數(shù)字
            extern?LPCSTR?NumPadTable2[];????//光標(biāo)控制
            extern?const?int?NumPadTableSize;

            //主鍵盤(pán)單顯
            extern?LPCSTR?MainTable[];
            extern?const?int?MainTableSize;

            //輔助鍵盤(pán)單顯
            extern?LPCSTR?HelpTable[];
            extern?const?int?HelpTableSize;

            struct?KeyConfig
            {
            ????
            short?id;????????//分類id
            ????short?offset;????//類內(nèi)偏移
            ????RECT?rt;????//位置
            ????BYTE?vk;????//虛擬碼
            };

            extern?KeyConfig?kcs[];
            extern?const?int?kcSize;
            extern?const?SIZE?kbSize;
            ??????? 第一次這樣寫(xiě)代碼,寫(xiě)完發(fā)現(xiàn)這樣極大地提高了靈活性,只要在配置文件config.cpp中修改,就可以產(chǎn)生很多種不同的界面(雖然仍然是代碼級(jí)別的,畢竟邁出了第一步,今后還會(huì)嘗試改成xml配置)。
            ??????? 言歸正傳,這樣的設(shè)計(jì)分離了按鍵與顯示,可配置能力大大加強(qiáng)。但仍然存在第二個(gè)大問(wèn)題。
            ??? 問(wèn)題二:輸入焦點(diǎn)的確定
            ??????? 方案一:現(xiàn)在只要在網(wǎng)上搜索“虛擬鍵盤(pán)”,能夠搜到一大溜的源代碼,但只可惜全是同一份拷貝,而且存在一點(diǎn)小錯(cuò)誤。他的解決方案是:利用 PreTranslateMessage,在底層調(diào)用它之前,前臺(tái)窗口仍然沒(méi)有改變,此時(shí)是獲得前一個(gè)前臺(tái)窗口的好時(shí)機(jī),獲得后保存,并將使用 AttachThreadInput將當(dāng)前線程綁定活動(dòng)窗口的消息隊(duì)列,然后在單擊虛擬鍵盤(pán)時(shí)使用SetFocus將保存的窗口設(shè)為焦點(diǎn)(源代碼中同時(shí)使用了SetForgroundWindow和SetFocus,這是失效的原因),然后發(fā)送虛擬按鍵。
            ??????? 方案二:其實(shí)有更簡(jiǎn)便的方法。設(shè)置主窗口屬性為WM_ES_NOACTIVATE,這樣窗口就不會(huì)成為前臺(tái)窗口,不管如何發(fā)送鍵盤(pán)消息,擁有焦點(diǎn)的窗口總會(huì)收到。但此時(shí)仍然存在問(wèn)題。當(dāng)移動(dòng)窗口時(shí),效果不大順暢,而且沒(méi)辦法響應(yīng)菜單命令,那是因?yàn)樵摯翱谑冀K不是前臺(tái)窗口造成的。解決方法就是在單擊標(biāo)題欄時(shí),成為前臺(tái)窗口,釋放是歸還前臺(tái)。
            void?CMainFrame::OnNcLButtonDown(UINT?nHitTest,?CPoint?point)
            {
            ????
            if(m_hForground?==?NULL)
            ????{
            ????????m_hForground?
            =?::GetForegroundWindow();
            ????????ModifyStyleEx(WS_EX_NOACTIVATE,
            0);
            ????????SetForegroundWindow();
            ????}
            ????CFrameWnd::OnNcLButtonDown(nHitTest,?point);
            }
            ??????????????? 但是,如果想當(dāng)然歸還前臺(tái)使用WM_NCLBUTTONUP消息的話,就要讓你失望了,windows似乎有意跟我們開(kāi)玩笑,必須單擊兩次才能響應(yīng)這個(gè)消息。沒(méi)辦法,于是嘗試WM_NCMOUSELEAVE,但效果也不好,最終嘗試WM_NCMOUSEMOVE,很好,這次終于成功了。
            void?CMainFrame::OnNcMouseMove(UINT?nHitTest,?CPoint?point)
            {
            ????
            if(m_hForground?!=?NULL)
            ????{
            ????????::SetForegroundWindow(m_hForground);
            ????????ModifyStyleEx(
            0,WS_EX_NOACTIVATE);
            ????????m_hForground?
            =?NULL;
            ????}
            ????CFrameWnd::OnNcMouseMove(nHitTest,?point);
            }
            ??????? 問(wèn)題到此為止,現(xiàn)在說(shuō)說(shuō)一點(diǎn)小小的發(fā)現(xiàn)。
            ??????? 原本以為一般的按鍵就兩種狀態(tài),通過(guò)down、up改變,如果用方波描述,down就是下降沿觸發(fā),up是上升沿觸發(fā)。也曾了解,像shift這樣的按鍵會(huì)很復(fù)雜,存在多個(gè)狀態(tài)。后來(lái)測(cè)試發(fā)現(xiàn),shift并非一個(gè)特例,所有的按鍵都有4個(gè)狀態(tài),通過(guò)down、up改變狀態(tài)。只是不同按鍵對(duì)狀態(tài)的關(guān)注點(diǎn)不同。
            ??????? 可以做這樣一個(gè)測(cè)試,用GetKeyboardState得到各個(gè)虛擬碼對(duì)應(yīng)的按鍵狀態(tài)。最高位為1時(shí)表示鍵被按下,最高位為1時(shí),如果是lock鍵則表示被鎖住,對(duì)于其他鍵,各有各的作用。
            ??????? 比如一個(gè)鍵,用2位的二進(jìn)制數(shù)表示這些狀態(tài),設(shè)初始狀態(tài)為10,經(jīng)過(guò)down后,變?yōu)?1,經(jīng)過(guò)up后,變?yōu)?1,再經(jīng)過(guò)down后,變?yōu)?0,再經(jīng)過(guò)up后,變?yōu)?0,如此四個(gè)狀態(tài)經(jīng)過(guò)down、up實(shí)現(xiàn)了周期性的狀態(tài)裝換。大體符合這樣的規(guī)律:
            ??????????? 10-(down xor 11)->01->(up xor 10)->11-(down xor 11)->00(up xor 10)->10。
            ??????? 這樣,如果虛擬得比較徹底,在虛擬鍵盤(pán)內(nèi)部可以輕易地實(shí)現(xiàn)狀態(tài)的記憶,并且可以獲得足夠的信息。對(duì)于顯示、控制都非常方便。

            ??? 這只是第一個(gè)版本,還有很多問(wèn)題需要解決。
            ??? 待解決問(wèn)題一:xml配置動(dòng)態(tài)配置鍵盤(pán),及動(dòng)態(tài)更換顯示效果。
            ??? 待解決問(wèn)題二:同步物理鍵盤(pán)。
            ??? 待解決問(wèn)題三:更深層次,防止鍵盤(pán)消息被hook,初步認(rèn)識(shí),似乎可以使用剪貼板。
            ?? 【源代碼1.2版本:http://m.shnenglu.com/Files/yefeng/VirtualKeyboard1.2.rar

            Feedback

            # re: 虛擬鍵盤(pán)(軟鍵盤(pán))設(shè)計(jì)要點(diǎn)   回復(fù)  更多評(píng)論   

            2010-02-03 09:40 by 長(zhǎng)天
            謝謝博主的文章和程序,感謝博主的技術(shù)分享

            # re: 虛擬鍵盤(pán)(軟鍵盤(pán))設(shè)計(jì)要點(diǎn)   回復(fù)  更多評(píng)論   

            2011-04-29 17:58 by ss
            你這鍵盤(pán)我想給他改小點(diǎn),每個(gè)虛擬按鈕的間隔在哪個(gè)函數(shù)改啊!?謝謝了哈

            # re: 虛擬鍵盤(pán)(軟鍵盤(pán))設(shè)計(jì)要點(diǎn)   回復(fù)  更多評(píng)論   

            2011-07-14 16:36 by 李進(jìn)安
            很不錯(cuò),下載學(xué)習(xí)一下

            # re: 虛擬鍵盤(pán)(軟鍵盤(pán))設(shè)計(jì)要點(diǎn)   回復(fù)  更多評(píng)論   

            2013-10-22 16:10 by red
            非常感謝博主!正好要開(kāi)發(fā)軟鍵盤(pán)

            # re: 虛擬鍵盤(pán)(軟鍵盤(pán))設(shè)計(jì)要點(diǎn)   回復(fù)  更多評(píng)論   

            2013-10-23 09:49 by red
            博主 有個(gè)小bug不知道該怎么改
            當(dāng)點(diǎn)擊完某個(gè)鍵的時(shí)候 時(shí)不時(shí)會(huì)出現(xiàn) 該鍵還遺留按下去的藍(lán)色 回不到原本顏色

            是和頁(yè)面的刷新快慢有關(guān)嗎?

            非常感謝
            青草影院天堂男人久久| 精品精品国产自在久久高清 | 看久久久久久a级毛片| 国色天香久久久久久久小说 | AV无码久久久久不卡蜜桃| 亚洲精品美女久久777777| 国产亚洲色婷婷久久99精品91| 亚州日韩精品专区久久久| 国产成人无码久久久精品一| 久久se这里只有精品| 性做久久久久久免费观看| 国产婷婷成人久久Av免费高清 | 99热都是精品久久久久久| 久久久久久久久久久久中文字幕 | 国内精品久久久久久中文字幕 | 久久精品国产亚洲AV高清热| 国产日韩欧美久久| 午夜精品久久久久久毛片| 99久久精品国产一区二区三区 | 精品久久国产一区二区三区香蕉 | 色成年激情久久综合| 久久亚洲AV无码精品色午夜麻豆 | 久久夜色精品国产噜噜麻豆| 久久播电影网| 久久免费精品视频| 粉嫩小泬无遮挡久久久久久| 亚洲欧美日韩久久精品| 国产精品久久久久久久久久影院 | 亚洲国产精品无码久久九九| 97精品伊人久久大香线蕉app| 性欧美丰满熟妇XXXX性久久久| 午夜精品久久久久9999高清| 伊人色综合久久天天| 精品久久久久香蕉网| 精品久久久无码人妻中文字幕豆芽| 久久精品国产99国产精品导航| 亚洲精品无码久久久久| 久久国内免费视频| 久久精品国产亚洲AV嫖农村妇女| 久久99精品久久久大学生| 久久婷婷五月综合国产尤物app|